about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/oauthlib
diff options
context:
space:
mode:
authorS. Solomon Darnell2025-03-28 21:52:21 -0500
committerS. Solomon Darnell2025-03-28 21:52:21 -0500
commit4a52a71956a8d46fcb7294ac71734504bb09bcc2 (patch)
treeee3dc5af3b6313e921cd920906356f5d4febc4ed /.venv/lib/python3.12/site-packages/oauthlib
parentcc961e04ba734dd72309fb548a2f97d67d578813 (diff)
downloadgn-ai-master.tar.gz
two version of R2R are here HEAD master
Diffstat (limited to '.venv/lib/python3.12/site-packages/oauthlib')
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/__init__.py34
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/common.py432
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth1/__init__.py23
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/__init__.py365
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/__init__.py8
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/access_token.py215
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/authorization.py158
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/base.py244
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/pre_configured.py14
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/request_token.py209
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/resource.py163
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/signature_only.py82
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/errors.py76
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/parameters.py133
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/request_validator.py849
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/signature.py852
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/utils.py83
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth2/__init__.py36
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/__init__.py16
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/clients/__init__.py14
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/clients/backend_application.py74
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/clients/base.py604
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/clients/legacy_application.py84
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/clients/mobile_application.py174
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/clients/service_application.py189
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/clients/web_application.py222
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/endpoints/__init__.py17
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/endpoints/authorization.py114
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/endpoints/base.py113
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/endpoints/introspect.py120
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/endpoints/metadata.py238
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/endpoints/pre_configured.py216
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/endpoints/resource.py84
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/endpoints/revocation.py126
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/endpoints/token.py119
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/errors.py400
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/grant_types/__init__.py11
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py548
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/grant_types/base.py268
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/grant_types/client_credentials.py123
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/grant_types/implicit.py376
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/grant_types/refresh_token.py136
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/grant_types/resource_owner_password_credentials.py199
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/parameters.py471
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/request_validator.py680
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/tokens.py356
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/utils.py83
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc8628/__init__.py10
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc8628/clients/__init__.py8
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc8628/clients/device.py95
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/openid/__init__.py7
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/openid/connect/__init__.py0
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/__init__.py0
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/endpoints/__init__.py9
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/endpoints/pre_configured.py97
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/endpoints/userinfo.py106
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/exceptions.py149
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/grant_types/__init__.py13
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/grant_types/authorization_code.py43
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/grant_types/base.py326
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/grant_types/dispatchers.py101
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/grant_types/hybrid.py63
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/grant_types/implicit.py51
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/grant_types/refresh_token.py34
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/request_validator.py320
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/tokens.py48
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/signals.py40
-rw-r--r--.venv/lib/python3.12/site-packages/oauthlib/uri_validate.py190
68 files changed, 11861 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/__init__.py b/.venv/lib/python3.12/site-packages/oauthlib/__init__.py
new file mode 100644
index 00000000..d9a5e38e
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/__init__.py
@@ -0,0 +1,34 @@
+"""
+    oauthlib
+    ~~~~~~~~
+
+    A generic, spec-compliant, thorough implementation of the OAuth
+    request-signing logic.
+
+    :copyright: (c) 2019 by The OAuthlib Community
+    :license: BSD, see LICENSE for details.
+"""
+import logging
+from logging import NullHandler
+
+__author__ = 'The OAuthlib Community'
+__version__ = '3.2.2'
+
+logging.getLogger('oauthlib').addHandler(NullHandler())
+
+_DEBUG = False
+
+def set_debug(debug_val):
+	"""Set value of debug flag
+	
+    :param debug_val: Value to set. Must be a bool value.
+	"""
+	global _DEBUG
+	_DEBUG = debug_val
+
+def get_debug():
+	"""Get debug mode value. 
+	
+	:return: `True` if debug mode is on, `False` otherwise
+	"""
+	return _DEBUG	
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/common.py b/.venv/lib/python3.12/site-packages/oauthlib/common.py
new file mode 100644
index 00000000..395e75ef
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/common.py
@@ -0,0 +1,432 @@
+"""
+oauthlib.common
+~~~~~~~~~~~~~~
+
+This module provides data structures and utilities common
+to all implementations of OAuth.
+"""
+import collections
+import datetime
+import logging
+import re
+import time
+import urllib.parse as urlparse
+from urllib.parse import (
+    quote as _quote, unquote as _unquote, urlencode as _urlencode,
+)
+
+from . import get_debug
+
+try:
+    from secrets import SystemRandom, randbits
+except ImportError:
+    from random import SystemRandom, getrandbits as randbits
+
+UNICODE_ASCII_CHARACTER_SET = ('abcdefghijklmnopqrstuvwxyz'
+                               'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+                               '0123456789')
+
+CLIENT_ID_CHARACTER_SET = (r' !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMN'
+                           'OPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}')
+
+SANITIZE_PATTERN = re.compile(r'([^&;]*(?:password|token)[^=]*=)[^&;]+', re.IGNORECASE)
+INVALID_HEX_PATTERN = re.compile(r'%[^0-9A-Fa-f]|%[0-9A-Fa-f][^0-9A-Fa-f]')
+
+always_safe = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+               'abcdefghijklmnopqrstuvwxyz'
+               '0123456789' '_.-')
+
+log = logging.getLogger('oauthlib')
+
+
+# 'safe' must be bytes (Python 2.6 requires bytes, other versions allow either)
+def quote(s, safe=b'/'):
+    s = s.encode('utf-8') if isinstance(s, str) else s
+    s = _quote(s, safe)
+    # PY3 always returns unicode.  PY2 may return either, depending on whether
+    # it had to modify the string.
+    if isinstance(s, bytes):
+        s = s.decode('utf-8')
+    return s
+
+
+def unquote(s):
+    s = _unquote(s)
+    # PY3 always returns unicode.  PY2 seems to always return what you give it,
+    # which differs from quote's behavior.  Just to be safe, make sure it is
+    # unicode before we return.
+    if isinstance(s, bytes):
+        s = s.decode('utf-8')
+    return s
+
+
+def urlencode(params):
+    utf8_params = encode_params_utf8(params)
+    urlencoded = _urlencode(utf8_params)
+    if isinstance(urlencoded, str):
+        return urlencoded
+    else:
+        return urlencoded.decode("utf-8")
+
+
+def encode_params_utf8(params):
+    """Ensures that all parameters in a list of 2-element tuples are encoded to
+    bytestrings using UTF-8
+    """
+    encoded = []
+    for k, v in params:
+        encoded.append((
+            k.encode('utf-8') if isinstance(k, str) else k,
+            v.encode('utf-8') if isinstance(v, str) else v))
+    return encoded
+
+
+def decode_params_utf8(params):
+    """Ensures that all parameters in a list of 2-element tuples are decoded to
+    unicode using UTF-8.
+    """
+    decoded = []
+    for k, v in params:
+        decoded.append((
+            k.decode('utf-8') if isinstance(k, bytes) else k,
+            v.decode('utf-8') if isinstance(v, bytes) else v))
+    return decoded
+
+
+urlencoded = set(always_safe) | set('=&;:%+~,*@!()/?\'$')
+
+
+def urldecode(query):
+    """Decode a query string in x-www-form-urlencoded format into a sequence
+    of two-element tuples.
+
+    Unlike urlparse.parse_qsl(..., strict_parsing=True) urldecode will enforce
+    correct formatting of the query string by validation. If validation fails
+    a ValueError will be raised. urllib.parse_qsl will only raise errors if
+    any of name-value pairs omits the equals sign.
+    """
+    # Check if query contains invalid characters
+    if query and not set(query) <= urlencoded:
+        error = ("Error trying to decode a non urlencoded string. "
+                 "Found invalid characters: %s "
+                 "in the string: '%s'. "
+                 "Please ensure the request/response body is "
+                 "x-www-form-urlencoded.")
+        raise ValueError(error % (set(query) - urlencoded, query))
+
+    # Check for correctly hex encoded values using a regular expression
+    # All encoded values begin with % followed by two hex characters
+    # correct = %00, %A0, %0A, %FF
+    # invalid = %G0, %5H, %PO
+    if INVALID_HEX_PATTERN.search(query):
+        raise ValueError('Invalid hex encoding in query string.')
+
+    # We want to allow queries such as "c2" whereas urlparse.parse_qsl
+    # with the strict_parsing flag will not.
+    params = urlparse.parse_qsl(query, keep_blank_values=True)
+
+    # unicode all the things
+    return decode_params_utf8(params)
+
+
+def extract_params(raw):
+    """Extract parameters and return them as a list of 2-tuples.
+
+    Will successfully extract parameters from urlencoded query strings,
+    dicts, or lists of 2-tuples. Empty strings/dicts/lists will return an
+    empty list of parameters. Any other input will result in a return
+    value of None.
+    """
+    if isinstance(raw, (bytes, str)):
+        try:
+            params = urldecode(raw)
+        except ValueError:
+            params = None
+    elif hasattr(raw, '__iter__'):
+        try:
+            dict(raw)
+        except ValueError:
+            params = None
+        except TypeError:
+            params = None
+        else:
+            params = list(raw.items() if isinstance(raw, dict) else raw)
+            params = decode_params_utf8(params)
+    else:
+        params = None
+
+    return params
+
+
+def generate_nonce():
+    """Generate pseudorandom nonce that is unlikely to repeat.
+
+    Per `section 3.3`_ of the OAuth 1 RFC 5849 spec.
+    Per `section 3.2.1`_ of the MAC Access Authentication spec.
+
+    A random 64-bit number is appended to the epoch timestamp for both
+    randomness and to decrease the likelihood of collisions.
+
+    .. _`section 3.2.1`: https://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01#section-3.2.1
+    .. _`section 3.3`: https://tools.ietf.org/html/rfc5849#section-3.3
+    """
+    return str(str(randbits(64)) + generate_timestamp())
+
+
+def generate_timestamp():
+    """Get seconds since epoch (UTC).
+
+    Per `section 3.3`_ of the OAuth 1 RFC 5849 spec.
+    Per `section 3.2.1`_ of the MAC Access Authentication spec.
+
+    .. _`section 3.2.1`: https://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01#section-3.2.1
+    .. _`section 3.3`: https://tools.ietf.org/html/rfc5849#section-3.3
+    """
+    return str(int(time.time()))
+
+
+def generate_token(length=30, chars=UNICODE_ASCII_CHARACTER_SET):
+    """Generates a non-guessable OAuth token
+
+    OAuth (1 and 2) does not specify the format of tokens except that they
+    should be strings of random characters. Tokens should not be guessable
+    and entropy when generating the random characters is important. Which is
+    why SystemRandom is used instead of the default random.choice method.
+    """
+    rand = SystemRandom()
+    return ''.join(rand.choice(chars) for x in range(length))
+
+
+def generate_signed_token(private_pem, request):
+    import jwt
+
+    now = datetime.datetime.utcnow()
+
+    claims = {
+        'scope': request.scope,
+        'exp': now + datetime.timedelta(seconds=request.expires_in)
+    }
+
+    claims.update(request.claims)
+
+    token = jwt.encode(claims, private_pem, 'RS256')
+    token = to_unicode(token, "UTF-8")
+
+    return token
+
+
+def verify_signed_token(public_pem, token):
+    import jwt
+
+    return jwt.decode(token, public_pem, algorithms=['RS256'])
+
+
+def generate_client_id(length=30, chars=CLIENT_ID_CHARACTER_SET):
+    """Generates an OAuth client_id
+
+    OAuth 2 specify the format of client_id in
+    https://tools.ietf.org/html/rfc6749#appendix-A.
+    """
+    return generate_token(length, chars)
+
+
+def add_params_to_qs(query, params):
+    """Extend a query with a list of two-tuples."""
+    if isinstance(params, dict):
+        params = params.items()
+    queryparams = urlparse.parse_qsl(query, keep_blank_values=True)
+    queryparams.extend(params)
+    return urlencode(queryparams)
+
+
+def add_params_to_uri(uri, params, fragment=False):
+    """Add a list of two-tuples to the uri query components."""
+    sch, net, path, par, query, fra = urlparse.urlparse(uri)
+    if fragment:
+        fra = add_params_to_qs(fra, params)
+    else:
+        query = add_params_to_qs(query, params)
+    return urlparse.urlunparse((sch, net, path, par, query, fra))
+
+
+def safe_string_equals(a, b):
+    """ Near-constant time string comparison.
+
+    Used in order to avoid timing attacks on sensitive information such
+    as secret keys during request verification (`rootLabs`_).
+
+    .. _`rootLabs`: http://rdist.root.org/2010/01/07/timing-independent-array-comparison/
+
+    """
+    if len(a) != len(b):
+        return False
+
+    result = 0
+    for x, y in zip(a, b):
+        result |= ord(x) ^ ord(y)
+    return result == 0
+
+
+def to_unicode(data, encoding='UTF-8'):
+    """Convert a number of different types of objects to unicode."""
+    if isinstance(data, str):
+        return data
+
+    if isinstance(data, bytes):
+        return str(data, encoding=encoding)
+
+    if hasattr(data, '__iter__'):
+        try:
+            dict(data)
+        except TypeError:
+            pass
+        except ValueError:
+            # Assume it's a one dimensional data structure
+            return (to_unicode(i, encoding) for i in data)
+        else:
+            # We support 2.6 which lacks dict comprehensions
+            if hasattr(data, 'items'):
+                data = data.items()
+            return {to_unicode(k, encoding): to_unicode(v, encoding) for k, v in data}
+
+    return data
+
+
+class CaseInsensitiveDict(dict):
+
+    """Basic case insensitive dict with strings only keys."""
+
+    proxy = {}
+
+    def __init__(self, data):
+        self.proxy = {k.lower(): k for k in data}
+        for k in data:
+            self[k] = data[k]
+
+    def __contains__(self, k):
+        return k.lower() in self.proxy
+
+    def __delitem__(self, k):
+        key = self.proxy[k.lower()]
+        super().__delitem__(key)
+        del self.proxy[k.lower()]
+
+    def __getitem__(self, k):
+        key = self.proxy[k.lower()]
+        return super().__getitem__(key)
+
+    def get(self, k, default=None):
+        return self[k] if k in self else default
+
+    def __setitem__(self, k, v):
+        super().__setitem__(k, v)
+        self.proxy[k.lower()] = k
+
+    def update(self, *args, **kwargs):
+        super().update(*args, **kwargs)
+        for k in dict(*args, **kwargs):
+            self.proxy[k.lower()] = k
+
+
+class Request:
+
+    """A malleable representation of a signable HTTP request.
+
+    Body argument may contain any data, but parameters will only be decoded if
+    they are one of:
+
+    * urlencoded query string
+    * dict
+    * list of 2-tuples
+
+    Anything else will be treated as raw body data to be passed through
+    unmolested.
+    """
+
+    def __init__(self, uri, http_method='GET', body=None, headers=None,
+                 encoding='utf-8'):
+        # Convert to unicode using encoding if given, else assume unicode
+        encode = lambda x: to_unicode(x, encoding) if encoding else x
+
+        self.uri = encode(uri)
+        self.http_method = encode(http_method)
+        self.headers = CaseInsensitiveDict(encode(headers or {}))
+        self.body = encode(body)
+        self.decoded_body = extract_params(self.body)
+        self.oauth_params = []
+        self.validator_log = {}
+
+        self._params = {
+            "access_token": None,
+            "client": None,
+            "client_id": None,
+            "client_secret": None,
+            "code": None,
+            "code_challenge": None,
+            "code_challenge_method": None,
+            "code_verifier": None,
+            "extra_credentials": None,
+            "grant_type": None,
+            "redirect_uri": None,
+            "refresh_token": None,
+            "request_token": None,
+            "response_type": None,
+            "scope": None,
+            "scopes": None,
+            "state": None,
+            "token": None,
+            "user": None,
+            "token_type_hint": None,
+
+            # OpenID Connect
+            "response_mode": None,
+            "nonce": None,
+            "display": None,
+            "prompt": None,
+            "claims": None,
+            "max_age": None,
+            "ui_locales": None,
+            "id_token_hint": None,
+            "login_hint": None,
+            "acr_values": None
+        }
+        self._params.update(dict(urldecode(self.uri_query)))
+        self._params.update(dict(self.decoded_body or []))
+
+    def __getattr__(self, name):
+        if name in self._params:
+            return self._params[name]
+        else:
+            raise AttributeError(name)
+
+    def __repr__(self):
+        if not get_debug():
+            return "<oauthlib.Request SANITIZED>"
+        body = self.body
+        headers = self.headers.copy()
+        if body:
+            body = SANITIZE_PATTERN.sub('\1<SANITIZED>', str(body))
+        if 'Authorization' in headers:
+            headers['Authorization'] = '<SANITIZED>'
+        return '<oauthlib.Request url="{}", http_method="{}", headers="{}", body="{}">'.format(
+            self.uri, self.http_method, headers, body)
+
+    @property
+    def uri_query(self):
+        return urlparse.urlparse(self.uri).query
+
+    @property
+    def uri_query_params(self):
+        if not self.uri_query:
+            return []
+        return urlparse.parse_qsl(self.uri_query, keep_blank_values=True,
+                                  strict_parsing=True)
+
+    @property
+    def duplicate_params(self):
+        seen_keys = collections.defaultdict(int)
+        all_keys = (p[0]
+                    for p in (self.decoded_body or []) + self.uri_query_params)
+        for k in all_keys:
+            seen_keys[k] += 1
+        return [k for k, c in seen_keys.items() if c > 1]
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth1/__init__.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/__init__.py
new file mode 100644
index 00000000..9caf12a9
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/__init__.py
@@ -0,0 +1,23 @@
+"""
+oauthlib.oauth1
+~~~~~~~~~~~~~~
+
+This module is a wrapper for the most recent implementation of OAuth 1.0 Client
+and Server classes.
+"""
+from .rfc5849 import (
+    SIGNATURE_HMAC, SIGNATURE_HMAC_SHA1, SIGNATURE_HMAC_SHA256,
+    SIGNATURE_HMAC_SHA512, SIGNATURE_PLAINTEXT, SIGNATURE_RSA,
+    SIGNATURE_RSA_SHA1, SIGNATURE_RSA_SHA256, SIGNATURE_RSA_SHA512,
+    SIGNATURE_TYPE_AUTH_HEADER, SIGNATURE_TYPE_BODY, SIGNATURE_TYPE_QUERY,
+    Client,
+)
+from .rfc5849.endpoints import (
+    AccessTokenEndpoint, AuthorizationEndpoint, RequestTokenEndpoint,
+    ResourceEndpoint, SignatureOnlyEndpoint, WebApplicationServer,
+)
+from .rfc5849.errors import (
+    InsecureTransportError, InvalidClientError, InvalidRequestError,
+    InvalidSignatureMethodError, OAuth1Error,
+)
+from .rfc5849.request_validator import RequestValidator
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/__init__.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/__init__.py
new file mode 100644
index 00000000..c559251f
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/__init__.py
@@ -0,0 +1,365 @@
+"""
+oauthlib.oauth1.rfc5849
+~~~~~~~~~~~~~~
+
+This module is an implementation of various logic needed
+for signing and checking OAuth 1.0 RFC 5849 requests.
+
+It supports all three standard signature methods defined in RFC 5849:
+
+- HMAC-SHA1
+- RSA-SHA1
+- PLAINTEXT
+
+It also supports signature methods that are not defined in RFC 5849. These are
+based on the standard ones but replace SHA-1 with the more secure SHA-256:
+
+- HMAC-SHA256
+- RSA-SHA256
+
+"""
+import base64
+import hashlib
+import logging
+import urllib.parse as urlparse
+
+from oauthlib.common import (
+    Request, generate_nonce, generate_timestamp, to_unicode, urlencode,
+)
+
+from . import parameters, signature
+
+log = logging.getLogger(__name__)
+
+# Available signature methods
+#
+# Note: SIGNATURE_HMAC and SIGNATURE_RSA are kept for backward compatibility
+# with previous versions of this library, when it the only HMAC-based and
+# RSA-based signature methods were HMAC-SHA1 and RSA-SHA1. But now that it
+# supports other hashing algorithms besides SHA1, explicitly identifying which
+# hashing algorithm is being used is recommended.
+#
+# Note: if additional values are defined here, don't forget to update the
+# imports in "../__init__.py" so they are available outside this module.
+
+SIGNATURE_HMAC_SHA1 = "HMAC-SHA1"
+SIGNATURE_HMAC_SHA256 = "HMAC-SHA256"
+SIGNATURE_HMAC_SHA512 = "HMAC-SHA512"
+SIGNATURE_HMAC = SIGNATURE_HMAC_SHA1  # deprecated variable for HMAC-SHA1
+
+SIGNATURE_RSA_SHA1 = "RSA-SHA1"
+SIGNATURE_RSA_SHA256 = "RSA-SHA256"
+SIGNATURE_RSA_SHA512 = "RSA-SHA512"
+SIGNATURE_RSA = SIGNATURE_RSA_SHA1  # deprecated variable for RSA-SHA1
+
+SIGNATURE_PLAINTEXT = "PLAINTEXT"
+
+SIGNATURE_METHODS = (
+    SIGNATURE_HMAC_SHA1,
+    SIGNATURE_HMAC_SHA256,
+    SIGNATURE_HMAC_SHA512,
+    SIGNATURE_RSA_SHA1,
+    SIGNATURE_RSA_SHA256,
+    SIGNATURE_RSA_SHA512,
+    SIGNATURE_PLAINTEXT
+)
+
+SIGNATURE_TYPE_AUTH_HEADER = 'AUTH_HEADER'
+SIGNATURE_TYPE_QUERY = 'QUERY'
+SIGNATURE_TYPE_BODY = 'BODY'
+
+CONTENT_TYPE_FORM_URLENCODED = 'application/x-www-form-urlencoded'
+
+
+class Client:
+
+    """A client used to sign OAuth 1.0 RFC 5849 requests."""
+    SIGNATURE_METHODS = {
+        SIGNATURE_HMAC_SHA1: signature.sign_hmac_sha1_with_client,
+        SIGNATURE_HMAC_SHA256: signature.sign_hmac_sha256_with_client,
+        SIGNATURE_HMAC_SHA512: signature.sign_hmac_sha512_with_client,
+        SIGNATURE_RSA_SHA1: signature.sign_rsa_sha1_with_client,
+        SIGNATURE_RSA_SHA256: signature.sign_rsa_sha256_with_client,
+        SIGNATURE_RSA_SHA512: signature.sign_rsa_sha512_with_client,
+        SIGNATURE_PLAINTEXT: signature.sign_plaintext_with_client
+    }
+
+    @classmethod
+    def register_signature_method(cls, method_name, method_callback):
+        cls.SIGNATURE_METHODS[method_name] = method_callback
+
+    def __init__(self, client_key,
+                 client_secret=None,
+                 resource_owner_key=None,
+                 resource_owner_secret=None,
+                 callback_uri=None,
+                 signature_method=SIGNATURE_HMAC_SHA1,
+                 signature_type=SIGNATURE_TYPE_AUTH_HEADER,
+                 rsa_key=None, verifier=None, realm=None,
+                 encoding='utf-8', decoding=None,
+                 nonce=None, timestamp=None):
+        """Create an OAuth 1 client.
+
+        :param client_key: Client key (consumer key), mandatory.
+        :param resource_owner_key: Resource owner key (oauth token).
+        :param resource_owner_secret: Resource owner secret (oauth token secret).
+        :param callback_uri: Callback used when obtaining request token.
+        :param signature_method: SIGNATURE_HMAC, SIGNATURE_RSA or SIGNATURE_PLAINTEXT.
+        :param signature_type: SIGNATURE_TYPE_AUTH_HEADER (default),
+                               SIGNATURE_TYPE_QUERY or SIGNATURE_TYPE_BODY
+                               depending on where you want to embed the oauth
+                               credentials.
+        :param rsa_key: RSA key used with SIGNATURE_RSA.
+        :param verifier: Verifier used when obtaining an access token.
+        :param realm: Realm (scope) to which access is being requested.
+        :param encoding: If you provide non-unicode input you may use this
+                         to have oauthlib automatically convert.
+        :param decoding: If you wish that the returned uri, headers and body
+                         from sign be encoded back from unicode, then set
+                         decoding to your preferred encoding, i.e. utf-8.
+        :param nonce: Use this nonce instead of generating one. (Mainly for testing)
+        :param timestamp: Use this timestamp instead of using current. (Mainly for testing)
+        """
+        # Convert to unicode using encoding if given, else assume unicode
+        encode = lambda x: to_unicode(x, encoding) if encoding else x
+
+        self.client_key = encode(client_key)
+        self.client_secret = encode(client_secret)
+        self.resource_owner_key = encode(resource_owner_key)
+        self.resource_owner_secret = encode(resource_owner_secret)
+        self.signature_method = encode(signature_method)
+        self.signature_type = encode(signature_type)
+        self.callback_uri = encode(callback_uri)
+        self.rsa_key = encode(rsa_key)
+        self.verifier = encode(verifier)
+        self.realm = encode(realm)
+        self.encoding = encode(encoding)
+        self.decoding = encode(decoding)
+        self.nonce = encode(nonce)
+        self.timestamp = encode(timestamp)
+
+    def __repr__(self):
+        attrs = vars(self).copy()
+        attrs['client_secret'] = '****' if attrs['client_secret'] else None
+        attrs['rsa_key'] = '****' if attrs['rsa_key'] else None
+        attrs[
+            'resource_owner_secret'] = '****' if attrs['resource_owner_secret'] else None
+        attribute_str = ', '.join('{}={}'.format(k, v) for k, v in attrs.items())
+        return '<{} {}>'.format(self.__class__.__name__, attribute_str)
+
+    def get_oauth_signature(self, request):
+        """Get an OAuth signature to be used in signing a request
+
+        To satisfy `section 3.4.1.2`_ item 2, if the request argument's
+        headers dict attribute contains a Host item, its value will
+        replace any netloc part of the request argument's uri attribute
+        value.
+
+        .. _`section 3.4.1.2`: https://tools.ietf.org/html/rfc5849#section-3.4.1.2
+        """
+        if self.signature_method == SIGNATURE_PLAINTEXT:
+            # fast-path
+            return signature.sign_plaintext(self.client_secret,
+                                            self.resource_owner_secret)
+
+        uri, headers, body = self._render(request)
+
+        collected_params = signature.collect_parameters(
+            uri_query=urlparse.urlparse(uri).query,
+            body=body,
+            headers=headers)
+        log.debug("Collected params: {}".format(collected_params))
+
+        normalized_params = signature.normalize_parameters(collected_params)
+        normalized_uri = signature.base_string_uri(uri, headers.get('Host', None))
+        log.debug("Normalized params: {}".format(normalized_params))
+        log.debug("Normalized URI: {}".format(normalized_uri))
+
+        base_string = signature.signature_base_string(request.http_method,
+                                                      normalized_uri, normalized_params)
+
+        log.debug("Signing: signature base string: {}".format(base_string))
+
+        if self.signature_method not in self.SIGNATURE_METHODS:
+            raise ValueError('Invalid signature method.')
+
+        sig = self.SIGNATURE_METHODS[self.signature_method](base_string, self)
+
+        log.debug("Signature: {}".format(sig))
+        return sig
+
+    def get_oauth_params(self, request):
+        """Get the basic OAuth parameters to be used in generating a signature.
+        """
+        nonce = (generate_nonce()
+                 if self.nonce is None else self.nonce)
+        timestamp = (generate_timestamp()
+                     if self.timestamp is None else self.timestamp)
+        params = [
+            ('oauth_nonce', nonce),
+            ('oauth_timestamp', timestamp),
+            ('oauth_version', '1.0'),
+            ('oauth_signature_method', self.signature_method),
+            ('oauth_consumer_key', self.client_key),
+        ]
+        if self.resource_owner_key:
+            params.append(('oauth_token', self.resource_owner_key))
+        if self.callback_uri:
+            params.append(('oauth_callback', self.callback_uri))
+        if self.verifier:
+            params.append(('oauth_verifier', self.verifier))
+
+        # providing body hash for requests other than x-www-form-urlencoded
+        # as described in https://tools.ietf.org/html/draft-eaton-oauth-bodyhash-00#section-4.1.1
+        # 4.1.1. When to include the body hash
+        #    *  [...] MUST NOT include an oauth_body_hash parameter on requests with form-encoded request bodies
+        #    *  [...] SHOULD include the oauth_body_hash parameter on all other requests.
+        # Note that SHA-1 is vulnerable. The spec acknowledges that in https://tools.ietf.org/html/draft-eaton-oauth-bodyhash-00#section-6.2
+        # At this time, no further effort has been made to replace SHA-1 for the OAuth Request Body Hash extension.
+        content_type = request.headers.get('Content-Type', None)
+        content_type_eligible = content_type and content_type.find('application/x-www-form-urlencoded') < 0
+        if request.body is not None and content_type_eligible:
+            params.append(('oauth_body_hash', base64.b64encode(hashlib.sha1(request.body.encode('utf-8')).digest()).decode('utf-8')))
+
+        return params
+
+    def _render(self, request, formencode=False, realm=None):
+        """Render a signed request according to signature type
+
+        Returns a 3-tuple containing the request URI, headers, and body.
+
+        If the formencode argument is True and the body contains parameters, it
+        is escaped and returned as a valid formencoded string.
+        """
+        # TODO what if there are body params on a header-type auth?
+        # TODO what if there are query params on a body-type auth?
+
+        uri, headers, body = request.uri, request.headers, request.body
+
+        # TODO: right now these prepare_* methods are very narrow in scope--they
+        # only affect their little thing. In some cases (for example, with
+        # header auth) it might be advantageous to allow these methods to touch
+        # other parts of the request, like the headers—so the prepare_headers
+        # method could also set the Content-Type header to x-www-form-urlencoded
+        # like the spec requires. This would be a fundamental change though, and
+        # I'm not sure how I feel about it.
+        if self.signature_type == SIGNATURE_TYPE_AUTH_HEADER:
+            headers = parameters.prepare_headers(
+                request.oauth_params, request.headers, realm=realm)
+        elif self.signature_type == SIGNATURE_TYPE_BODY and request.decoded_body is not None:
+            body = parameters.prepare_form_encoded_body(
+                request.oauth_params, request.decoded_body)
+            if formencode:
+                body = urlencode(body)
+            headers['Content-Type'] = 'application/x-www-form-urlencoded'
+        elif self.signature_type == SIGNATURE_TYPE_QUERY:
+            uri = parameters.prepare_request_uri_query(
+                request.oauth_params, request.uri)
+        else:
+            raise ValueError('Unknown signature type specified.')
+
+        return uri, headers, body
+
+    def sign(self, uri, http_method='GET', body=None, headers=None, realm=None):
+        """Sign a request
+
+        Signs an HTTP request with the specified parts.
+
+        Returns a 3-tuple of the signed request's URI, headers, and body.
+        Note that http_method is not returned as it is unaffected by the OAuth
+        signing process. Also worth noting is that duplicate parameters
+        will be included in the signature, regardless of where they are
+        specified (query, body).
+
+        The body argument may be a dict, a list of 2-tuples, or a formencoded
+        string. The Content-Type header must be 'application/x-www-form-urlencoded'
+        if it is present.
+
+        If the body argument is not one of the above, it will be returned
+        verbatim as it is unaffected by the OAuth signing process. Attempting to
+        sign a request with non-formencoded data using the OAuth body signature
+        type is invalid and will raise an exception.
+
+        If the body does contain parameters, it will be returned as a properly-
+        formatted formencoded string.
+
+        Body may not be included if the http_method is either GET or HEAD as
+        this changes the semantic meaning of the request.
+
+        All string data MUST be unicode or be encoded with the same encoding
+        scheme supplied to the Client constructor, default utf-8. This includes
+        strings inside body dicts, for example.
+        """
+        # normalize request data
+        request = Request(uri, http_method, body, headers,
+                          encoding=self.encoding)
+
+        # sanity check
+        content_type = request.headers.get('Content-Type', None)
+        multipart = content_type and content_type.startswith('multipart/')
+        should_have_params = content_type == CONTENT_TYPE_FORM_URLENCODED
+        has_params = request.decoded_body is not None
+        # 3.4.1.3.1.  Parameter Sources
+        # [Parameters are collected from the HTTP request entity-body, but only
+        # if [...]:
+        #    *  The entity-body is single-part.
+        if multipart and has_params:
+            raise ValueError(
+                "Headers indicate a multipart body but body contains parameters.")
+        #    *  The entity-body follows the encoding requirements of the
+        #       "application/x-www-form-urlencoded" content-type as defined by
+        #       [W3C.REC-html40-19980424].
+        elif should_have_params and not has_params:
+            raise ValueError(
+                "Headers indicate a formencoded body but body was not decodable.")
+        #    *  The HTTP request entity-header includes the "Content-Type"
+        #       header field set to "application/x-www-form-urlencoded".
+        elif not should_have_params and has_params:
+            raise ValueError(
+                "Body contains parameters but Content-Type header was {} "
+                "instead of {}".format(content_type or "not set",
+                                        CONTENT_TYPE_FORM_URLENCODED))
+
+        # 3.5.2.  Form-Encoded Body
+        # Protocol parameters can be transmitted in the HTTP request entity-
+        # body, but only if the following REQUIRED conditions are met:
+        # o  The entity-body is single-part.
+        # o  The entity-body follows the encoding requirements of the
+        #    "application/x-www-form-urlencoded" content-type as defined by
+        #    [W3C.REC-html40-19980424].
+        # o  The HTTP request entity-header includes the "Content-Type" header
+        #    field set to "application/x-www-form-urlencoded".
+        elif self.signature_type == SIGNATURE_TYPE_BODY and not (
+                should_have_params and has_params and not multipart):
+            raise ValueError(
+                'Body signatures may only be used with form-urlencoded content')
+
+        # We amend https://tools.ietf.org/html/rfc5849#section-3.4.1.3.1
+        # with the clause that parameters from body should only be included
+        # in non GET or HEAD requests. Extracting the request body parameters
+        # and including them in the signature base string would give semantic
+        # meaning to the body, which it should not have according to the
+        # HTTP 1.1 spec.
+        elif http_method.upper() in ('GET', 'HEAD') and has_params:
+            raise ValueError('GET/HEAD requests should not include body.')
+
+        # generate the basic OAuth parameters
+        request.oauth_params = self.get_oauth_params(request)
+
+        # generate the signature
+        request.oauth_params.append(
+            ('oauth_signature', self.get_oauth_signature(request)))
+
+        # render the signed request and return it
+        uri, headers, body = self._render(request, formencode=True,
+                                          realm=(realm or self.realm))
+
+        if self.decoding:
+            log.debug('Encoding URI, headers and body to %s.', self.decoding)
+            uri = uri.encode(self.decoding)
+            body = body.encode(self.decoding) if body else body
+            new_headers = {}
+            for k, v in headers.items():
+                new_headers[k.encode(self.decoding)] = v.encode(self.decoding)
+            headers = new_headers
+        return uri, headers, body
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/__init__.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/__init__.py
new file mode 100644
index 00000000..9f30389f
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/__init__.py
@@ -0,0 +1,8 @@
+from .access_token import AccessTokenEndpoint
+from .authorization import AuthorizationEndpoint
+from .base import BaseEndpoint
+from .request_token import RequestTokenEndpoint
+from .resource import ResourceEndpoint
+from .signature_only import SignatureOnlyEndpoint
+
+from .pre_configured import WebApplicationServer  # isort:skip
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/access_token.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/access_token.py
new file mode 100644
index 00000000..13665db0
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/access_token.py
@@ -0,0 +1,215 @@
+# -*- coding: utf-8 -*-
+"""
+oauthlib.oauth1.rfc5849.endpoints.access_token
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This module is an implementation of the access token provider logic of
+OAuth 1.0 RFC 5849. It validates the correctness of access token requests,
+creates and persists tokens as well as create the proper response to be
+returned to the client.
+"""
+import logging
+
+from oauthlib.common import urlencode
+
+from .. import errors
+from .base import BaseEndpoint
+
+log = logging.getLogger(__name__)
+
+
+class AccessTokenEndpoint(BaseEndpoint):
+
+    """An endpoint responsible for providing OAuth 1 access tokens.
+
+    Typical use is to instantiate with a request validator and invoke the
+    ``create_access_token_response`` from a view function. The tuple returned
+    has all information necessary (body, status, headers) to quickly form
+    and return a proper response. See :doc:`/oauth1/validator` for details on which
+    validator methods to implement for this endpoint.
+    """
+
+    def create_access_token(self, request, credentials):
+        """Create and save a new access token.
+
+        Similar to OAuth 2, indication of granted scopes will be included as a
+        space separated list in ``oauth_authorized_realms``.
+
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :returns: The token as an urlencoded string.
+        """
+        request.realms = self.request_validator.get_realms(
+            request.resource_owner_key, request)
+        token = {
+            'oauth_token': self.token_generator(),
+            'oauth_token_secret': self.token_generator(),
+            # Backport the authorized scopes indication used in OAuth2
+            'oauth_authorized_realms': ' '.join(request.realms)
+        }
+        token.update(credentials)
+        self.request_validator.save_access_token(token, request)
+        return urlencode(token.items())
+
+    def create_access_token_response(self, uri, http_method='GET', body=None,
+                                     headers=None, credentials=None):
+        """Create an access token response, with a new request token if valid.
+
+        :param uri: The full URI of the token request.
+        :param http_method: A valid HTTP verb, i.e. GET, POST, PUT, HEAD, etc.
+        :param body: The request body as a string.
+        :param headers: The request headers as a dict.
+        :param credentials: A list of extra credentials to include in the token.
+        :returns: A tuple of 3 elements.
+                  1. A dict of headers to set on the response.
+                  2. The response body as a string.
+                  3. The response status code as an integer.
+
+        An example of a valid request::
+
+            >>> from your_validator import your_validator
+            >>> from oauthlib.oauth1 import AccessTokenEndpoint
+            >>> endpoint = AccessTokenEndpoint(your_validator)
+            >>> h, b, s = endpoint.create_access_token_response(
+            ...     'https://your.provider/access_token?foo=bar',
+            ...     headers={
+            ...         'Authorization': 'OAuth oauth_token=234lsdkf....'
+            ...     },
+            ...     credentials={
+            ...         'my_specific': 'argument',
+            ...     })
+            >>> h
+            {'Content-Type': 'application/x-www-form-urlencoded'}
+            >>> b
+            'oauth_token=lsdkfol23w54jlksdef&oauth_token_secret=qwe089234lkjsdf&oauth_authorized_realms=movies+pics&my_specific=argument'
+            >>> s
+            200
+
+        An response to invalid request would have a different body and status::
+
+            >>> b
+            'error=invalid_request&description=missing+resource+owner+key'
+            >>> s
+            400
+
+        The same goes for an an unauthorized request:
+
+            >>> b
+            ''
+            >>> s
+            401
+        """
+        resp_headers = {'Content-Type': 'application/x-www-form-urlencoded'}
+        try:
+            request = self._create_request(uri, http_method, body, headers)
+            valid, processed_request = self.validate_access_token_request(
+                request)
+            if valid:
+                token = self.create_access_token(request, credentials or {})
+                self.request_validator.invalidate_request_token(
+                    request.client_key,
+                    request.resource_owner_key,
+                    request)
+                return resp_headers, token, 200
+            else:
+                return {}, None, 401
+        except errors.OAuth1Error as e:
+            return resp_headers, e.urlencoded, e.status_code
+
+    def validate_access_token_request(self, request):
+        """Validate an access token request.
+
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :raises: OAuth1Error if the request is invalid.
+        :returns: A tuple of 2 elements.
+                  1. The validation result (True or False).
+                  2. The request object.
+        """
+        self._check_transport_security(request)
+        self._check_mandatory_parameters(request)
+
+        if not request.resource_owner_key:
+            raise errors.InvalidRequestError(
+                description='Missing resource owner.')
+
+        if not self.request_validator.check_request_token(
+                request.resource_owner_key):
+            raise errors.InvalidRequestError(
+                description='Invalid resource owner key format.')
+
+        if not request.verifier:
+            raise errors.InvalidRequestError(
+                description='Missing verifier.')
+
+        if not self.request_validator.check_verifier(request.verifier):
+            raise errors.InvalidRequestError(
+                description='Invalid verifier format.')
+
+        if not self.request_validator.validate_timestamp_and_nonce(
+                request.client_key, request.timestamp, request.nonce, request,
+                request_token=request.resource_owner_key):
+            return False, request
+
+        # The server SHOULD return a 401 (Unauthorized) status code when
+        # receiving a request with invalid client credentials.
+        # Note: This is postponed in order to avoid timing attacks, instead
+        # a dummy client is assigned and used to maintain near constant
+        # time request verification.
+        #
+        # Note that early exit would enable client enumeration
+        valid_client = self.request_validator.validate_client_key(
+            request.client_key, request)
+        if not valid_client:
+            request.client_key = self.request_validator.dummy_client
+
+        # The server SHOULD return a 401 (Unauthorized) status code when
+        # receiving a request with invalid or expired token.
+        # Note: This is postponed in order to avoid timing attacks, instead
+        # a dummy token is assigned and used to maintain near constant
+        # time request verification.
+        #
+        # Note that early exit would enable resource owner enumeration
+        valid_resource_owner = self.request_validator.validate_request_token(
+            request.client_key, request.resource_owner_key, request)
+        if not valid_resource_owner:
+            request.resource_owner_key = self.request_validator.dummy_request_token
+
+        # The server MUST verify (Section 3.2) the validity of the request,
+        # ensure that the resource owner has authorized the provisioning of
+        # token credentials to the client, and ensure that the temporary
+        # credentials have not expired or been used before.  The server MUST
+        # also verify the verification code received from the client.
+        # .. _`Section 3.2`: https://tools.ietf.org/html/rfc5849#section-3.2
+        #
+        # Note that early exit would enable resource owner authorization
+        # verifier enumertion.
+        valid_verifier = self.request_validator.validate_verifier(
+            request.client_key,
+            request.resource_owner_key,
+            request.verifier,
+            request)
+
+        valid_signature = self._check_signature(request, is_token_request=True)
+
+        # log the results to the validator_log
+        # this lets us handle internal reporting and analysis
+        request.validator_log['client'] = valid_client
+        request.validator_log['resource_owner'] = valid_resource_owner
+        request.validator_log['verifier'] = valid_verifier
+        request.validator_log['signature'] = valid_signature
+
+        # We delay checking validity until the very end, using dummy values for
+        # calculations and fetching secrets/keys to ensure the flow of every
+        # request remains almost identical regardless of whether valid values
+        # have been supplied. This ensures near constant time execution and
+        # prevents malicious users from guessing sensitive information
+        v = all((valid_client, valid_resource_owner, valid_verifier,
+                 valid_signature))
+        if not v:
+            log.info("[Failure] request verification failed.")
+            log.info("Valid client:, %s", valid_client)
+            log.info("Valid token:, %s", valid_resource_owner)
+            log.info("Valid verifier:, %s", valid_verifier)
+            log.info("Valid signature:, %s", valid_signature)
+        return v, request
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/authorization.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/authorization.py
new file mode 100644
index 00000000..00d9576b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/authorization.py
@@ -0,0 +1,158 @@
+# -*- coding: utf-8 -*-
+"""
+oauthlib.oauth1.rfc5849.endpoints.authorization
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This module is an implementation of various logic needed
+for signing and checking OAuth 1.0 RFC 5849 requests.
+"""
+from urllib.parse import urlencode
+
+from oauthlib.common import add_params_to_uri
+
+from .. import errors
+from .base import BaseEndpoint
+
+
+class AuthorizationEndpoint(BaseEndpoint):
+
+    """An endpoint responsible for letting authenticated users authorize access
+    to their protected resources to a client.
+
+    Typical use would be to have two views, one for displaying the authorization
+    form and one to process said form on submission.
+
+    The first view will want to utilize ``get_realms_and_credentials`` to fetch
+    requested realms and useful client credentials, such as name and
+    description, to be used when creating the authorization form.
+
+    During form processing you can use ``create_authorization_response`` to
+    validate the request, create a verifier as well as prepare the final
+    redirection URI used to send the user back to the client.
+
+    See :doc:`/oauth1/validator` for details on which validator methods to implement
+    for this endpoint.
+    """
+
+    def create_verifier(self, request, credentials):
+        """Create and save a new request token.
+
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :param credentials: A dict of extra token credentials.
+        :returns: The verifier as a dict.
+        """
+        verifier = {
+            'oauth_token': request.resource_owner_key,
+            'oauth_verifier': self.token_generator(),
+        }
+        verifier.update(credentials)
+        self.request_validator.save_verifier(
+            request.resource_owner_key, verifier, request)
+        return verifier
+
+    def create_authorization_response(self, uri, http_method='GET', body=None,
+                                      headers=None, realms=None, credentials=None):
+        """Create an authorization response, with a new request token if valid.
+
+        :param uri: The full URI of the token request.
+        :param http_method: A valid HTTP verb, i.e. GET, POST, PUT, HEAD, etc.
+        :param body: The request body as a string.
+        :param headers: The request headers as a dict.
+        :param credentials: A list of credentials to include in the verifier.
+        :returns: A tuple of 3 elements.
+                  1. A dict of headers to set on the response.
+                  2. The response body as a string.
+                  3. The response status code as an integer.
+
+        If the callback URI tied to the current token is "oob", a response with
+        a 200 status code will be returned. In this case, it may be desirable to
+        modify the response to better display the verifier to the client.
+
+        An example of an authorization request::
+
+            >>> from your_validator import your_validator
+            >>> from oauthlib.oauth1 import AuthorizationEndpoint
+            >>> endpoint = AuthorizationEndpoint(your_validator)
+            >>> h, b, s = endpoint.create_authorization_response(
+            ...     'https://your.provider/authorize?oauth_token=...',
+            ...     credentials={
+            ...         'extra': 'argument',
+            ...     })
+            >>> h
+            {'Location': 'https://the.client/callback?oauth_verifier=...&extra=argument'}
+            >>> b
+            None
+            >>> s
+            302
+
+        An example of a request with an "oob" callback::
+
+            >>> from your_validator import your_validator
+            >>> from oauthlib.oauth1 import AuthorizationEndpoint
+            >>> endpoint = AuthorizationEndpoint(your_validator)
+            >>> h, b, s = endpoint.create_authorization_response(
+            ...     'https://your.provider/authorize?foo=bar',
+            ...     credentials={
+            ...         'extra': 'argument',
+            ...     })
+            >>> h
+            {'Content-Type': 'application/x-www-form-urlencoded'}
+            >>> b
+            'oauth_verifier=...&extra=argument'
+            >>> s
+            200
+        """
+        request = self._create_request(uri, http_method=http_method, body=body,
+                                       headers=headers)
+
+        if not request.resource_owner_key:
+            raise errors.InvalidRequestError(
+                'Missing mandatory parameter oauth_token.')
+        if not self.request_validator.verify_request_token(
+                request.resource_owner_key, request):
+            raise errors.InvalidClientError()
+
+        request.realms = realms
+        if (request.realms and not self.request_validator.verify_realms(
+                request.resource_owner_key, request.realms, request)):
+            raise errors.InvalidRequestError(
+                description=('User granted access to realms outside of '
+                             'what the client may request.'))
+
+        verifier = self.create_verifier(request, credentials or {})
+        redirect_uri = self.request_validator.get_redirect_uri(
+            request.resource_owner_key, request)
+        if redirect_uri == 'oob':
+            response_headers = {
+                'Content-Type': 'application/x-www-form-urlencoded'}
+            response_body = urlencode(verifier)
+            return response_headers, response_body, 200
+        else:
+            populated_redirect = add_params_to_uri(
+                redirect_uri, verifier.items())
+            return {'Location': populated_redirect}, None, 302
+
+    def get_realms_and_credentials(self, uri, http_method='GET', body=None,
+                                   headers=None):
+        """Fetch realms and credentials for the presented request token.
+
+        :param uri: The full URI of the token request.
+        :param http_method: A valid HTTP verb, i.e. GET, POST, PUT, HEAD, etc.
+        :param body: The request body as a string.
+        :param headers: The request headers as a dict.
+        :returns: A tuple of 2 elements.
+                  1. A list of request realms.
+                  2. A dict of credentials which may be useful in creating the
+                  authorization form.
+        """
+        request = self._create_request(uri, http_method=http_method, body=body,
+                                       headers=headers)
+
+        if not self.request_validator.verify_request_token(
+                request.resource_owner_key, request):
+            raise errors.InvalidClientError()
+
+        realms = self.request_validator.get_realms(
+            request.resource_owner_key, request)
+        return realms, {'resource_owner_key': request.resource_owner_key}
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/base.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/base.py
new file mode 100644
index 00000000..7831be7c
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/base.py
@@ -0,0 +1,244 @@
+# -*- coding: utf-8 -*-
+"""
+oauthlib.oauth1.rfc5849.endpoints.base
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This module is an implementation of various logic needed
+for signing and checking OAuth 1.0 RFC 5849 requests.
+"""
+import time
+
+from oauthlib.common import CaseInsensitiveDict, Request, generate_token
+
+from .. import (
+    CONTENT_TYPE_FORM_URLENCODED, SIGNATURE_HMAC_SHA1, SIGNATURE_HMAC_SHA256,
+    SIGNATURE_HMAC_SHA512, SIGNATURE_PLAINTEXT, SIGNATURE_RSA_SHA1,
+    SIGNATURE_RSA_SHA256, SIGNATURE_RSA_SHA512, SIGNATURE_TYPE_AUTH_HEADER,
+    SIGNATURE_TYPE_BODY, SIGNATURE_TYPE_QUERY, errors, signature, utils,
+)
+
+
+class BaseEndpoint:
+
+    def __init__(self, request_validator, token_generator=None):
+        self.request_validator = request_validator
+        self.token_generator = token_generator or generate_token
+
+    def _get_signature_type_and_params(self, request):
+        """Extracts parameters from query, headers and body. Signature type
+        is set to the source in which parameters were found.
+        """
+        # Per RFC5849, only the Authorization header may contain the 'realm'
+        # optional parameter.
+        header_params = signature.collect_parameters(headers=request.headers,
+                                                     exclude_oauth_signature=False, with_realm=True)
+        body_params = signature.collect_parameters(body=request.body,
+                                                   exclude_oauth_signature=False)
+        query_params = signature.collect_parameters(uri_query=request.uri_query,
+                                                    exclude_oauth_signature=False)
+
+        params = []
+        params.extend(header_params)
+        params.extend(body_params)
+        params.extend(query_params)
+        signature_types_with_oauth_params = list(filter(lambda s: s[2], (
+            (SIGNATURE_TYPE_AUTH_HEADER, params,
+                utils.filter_oauth_params(header_params)),
+            (SIGNATURE_TYPE_BODY, params,
+                utils.filter_oauth_params(body_params)),
+            (SIGNATURE_TYPE_QUERY, params,
+                utils.filter_oauth_params(query_params))
+        )))
+
+        if len(signature_types_with_oauth_params) > 1:
+            found_types = [s[0] for s in signature_types_with_oauth_params]
+            raise errors.InvalidRequestError(
+                description=('oauth_ params must come from only 1 signature'
+                             'type but were found in %s',
+                             ', '.join(found_types)))
+
+        try:
+            signature_type, params, oauth_params = signature_types_with_oauth_params[
+                0]
+        except IndexError:
+            raise errors.InvalidRequestError(
+                description='Missing mandatory OAuth parameters.')
+
+        return signature_type, params, oauth_params
+
+    def _create_request(self, uri, http_method, body, headers):
+        # Only include body data from x-www-form-urlencoded requests
+        headers = CaseInsensitiveDict(headers or {})
+        if ("Content-Type" in headers and
+                CONTENT_TYPE_FORM_URLENCODED in headers["Content-Type"]):
+            request = Request(uri, http_method, body, headers)
+        else:
+            request = Request(uri, http_method, '', headers)
+
+        signature_type, params, oauth_params = (
+            self._get_signature_type_and_params(request))
+
+        # The server SHOULD return a 400 (Bad Request) status code when
+        # receiving a request with duplicated protocol parameters.
+        if len(dict(oauth_params)) != len(oauth_params):
+            raise errors.InvalidRequestError(
+                description='Duplicate OAuth1 entries.')
+
+        oauth_params = dict(oauth_params)
+        request.signature = oauth_params.get('oauth_signature')
+        request.client_key = oauth_params.get('oauth_consumer_key')
+        request.resource_owner_key = oauth_params.get('oauth_token')
+        request.nonce = oauth_params.get('oauth_nonce')
+        request.timestamp = oauth_params.get('oauth_timestamp')
+        request.redirect_uri = oauth_params.get('oauth_callback')
+        request.verifier = oauth_params.get('oauth_verifier')
+        request.signature_method = oauth_params.get('oauth_signature_method')
+        request.realm = dict(params).get('realm')
+        request.oauth_params = oauth_params
+
+        # Parameters to Client depend on signature method which may vary
+        # for each request. Note that HMAC-SHA1 and PLAINTEXT share parameters
+        request.params = [(k, v) for k, v in params if k != "oauth_signature"]
+
+        if 'realm' in request.headers.get('Authorization', ''):
+            request.params = [(k, v)
+                              for k, v in request.params if k != "realm"]
+
+        return request
+
+    def _check_transport_security(self, request):
+        # TODO: move into oauthlib.common from oauth2.utils
+        if (self.request_validator.enforce_ssl and
+                not request.uri.lower().startswith("https://")):
+            raise errors.InsecureTransportError()
+
+    def _check_mandatory_parameters(self, request):
+        # The server SHOULD return a 400 (Bad Request) status code when
+        # receiving a request with missing parameters.
+        if not all((request.signature, request.client_key,
+                    request.nonce, request.timestamp,
+                    request.signature_method)):
+            raise errors.InvalidRequestError(
+                description='Missing mandatory OAuth parameters.')
+
+        # OAuth does not mandate a particular signature method, as each
+        # implementation can have its own unique requirements.  Servers are
+        # free to implement and document their own custom methods.
+        # Recommending any particular method is beyond the scope of this
+        # specification.  Implementers should review the Security
+        # Considerations section (`Section 4`_) before deciding on which
+        # method to support.
+        # .. _`Section 4`: https://tools.ietf.org/html/rfc5849#section-4
+        if (not request.signature_method in
+                self.request_validator.allowed_signature_methods):
+            raise errors.InvalidSignatureMethodError(
+                description="Invalid signature, {} not in {!r}.".format(
+                    request.signature_method,
+                    self.request_validator.allowed_signature_methods))
+
+        # Servers receiving an authenticated request MUST validate it by:
+        #   If the "oauth_version" parameter is present, ensuring its value is
+        #   "1.0".
+        if ('oauth_version' in request.oauth_params and
+                request.oauth_params['oauth_version'] != '1.0'):
+            raise errors.InvalidRequestError(
+                description='Invalid OAuth version.')
+
+        # The timestamp value MUST be a positive integer. Unless otherwise
+        # specified by the server's documentation, the timestamp is expressed
+        # in the number of seconds since January 1, 1970 00:00:00 GMT.
+        if len(request.timestamp) != 10:
+            raise errors.InvalidRequestError(
+                description='Invalid timestamp size')
+
+        try:
+            ts = int(request.timestamp)
+
+        except ValueError:
+            raise errors.InvalidRequestError(
+                description='Timestamp must be an integer.')
+
+        else:
+            # To avoid the need to retain an infinite number of nonce values for
+            # future checks, servers MAY choose to restrict the time period after
+            # which a request with an old timestamp is rejected.
+            if abs(time.time() - ts) > self.request_validator.timestamp_lifetime:
+                raise errors.InvalidRequestError(
+                    description=('Timestamp given is invalid, differ from '
+                                 'allowed by over %s seconds.' % (
+                                     self.request_validator.timestamp_lifetime)))
+
+        # Provider specific validation of parameters, used to enforce
+        # restrictions such as character set and length.
+        if not self.request_validator.check_client_key(request.client_key):
+            raise errors.InvalidRequestError(
+                description='Invalid client key format.')
+
+        if not self.request_validator.check_nonce(request.nonce):
+            raise errors.InvalidRequestError(
+                description='Invalid nonce format.')
+
+    def _check_signature(self, request, is_token_request=False):
+        # ---- RSA Signature verification ----
+        if request.signature_method == SIGNATURE_RSA_SHA1 or \
+           request.signature_method == SIGNATURE_RSA_SHA256 or \
+           request.signature_method == SIGNATURE_RSA_SHA512:
+            # RSA-based signature method
+
+            # The server verifies the signature per `[RFC3447] section 8.2.2`_
+            # .. _`[RFC3447] section 8.2.2`: https://tools.ietf.org/html/rfc3447#section-8.2.1
+
+            rsa_key = self.request_validator.get_rsa_key(
+                request.client_key, request)
+
+            if request.signature_method == SIGNATURE_RSA_SHA1:
+                valid_signature = signature.verify_rsa_sha1(request, rsa_key)
+            elif request.signature_method == SIGNATURE_RSA_SHA256:
+                valid_signature = signature.verify_rsa_sha256(request, rsa_key)
+            elif request.signature_method == SIGNATURE_RSA_SHA512:
+                valid_signature = signature.verify_rsa_sha512(request, rsa_key)
+            else:
+                valid_signature = False
+
+        # ---- HMAC or Plaintext Signature verification ----
+        else:
+            # Non-RSA based signature method
+
+            # Servers receiving an authenticated request MUST validate it by:
+            #   Recalculating the request signature independently as described in
+            #   `Section 3.4`_ and comparing it to the value received from the
+            #   client via the "oauth_signature" parameter.
+            # .. _`Section 3.4`: https://tools.ietf.org/html/rfc5849#section-3.4
+
+            client_secret = self.request_validator.get_client_secret(
+                request.client_key, request)
+
+            resource_owner_secret = None
+            if request.resource_owner_key:
+                if is_token_request:
+                    resource_owner_secret = \
+                        self.request_validator.get_request_token_secret(
+                            request.client_key, request.resource_owner_key,
+                            request)
+                else:
+                    resource_owner_secret = \
+                        self.request_validator.get_access_token_secret(
+                            request.client_key, request.resource_owner_key,
+                            request)
+
+            if request.signature_method == SIGNATURE_HMAC_SHA1:
+                valid_signature = signature.verify_hmac_sha1(
+                    request, client_secret, resource_owner_secret)
+            elif request.signature_method == SIGNATURE_HMAC_SHA256:
+                valid_signature = signature.verify_hmac_sha256(
+                    request, client_secret, resource_owner_secret)
+            elif request.signature_method == SIGNATURE_HMAC_SHA512:
+                valid_signature = signature.verify_hmac_sha512(
+                    request, client_secret, resource_owner_secret)
+            elif request.signature_method == SIGNATURE_PLAINTEXT:
+                valid_signature = signature.verify_plaintext(
+                    request, client_secret, resource_owner_secret)
+            else:
+                valid_signature = False
+
+        return valid_signature
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/pre_configured.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/pre_configured.py
new file mode 100644
index 00000000..23e3cfc8
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/pre_configured.py
@@ -0,0 +1,14 @@
+from . import (
+    AccessTokenEndpoint, AuthorizationEndpoint, RequestTokenEndpoint,
+    ResourceEndpoint,
+)
+
+
+class WebApplicationServer(RequestTokenEndpoint, AuthorizationEndpoint,
+                           AccessTokenEndpoint, ResourceEndpoint):
+
+    def __init__(self, request_validator):
+        RequestTokenEndpoint.__init__(self, request_validator)
+        AuthorizationEndpoint.__init__(self, request_validator)
+        AccessTokenEndpoint.__init__(self, request_validator)
+        ResourceEndpoint.__init__(self, request_validator)
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/request_token.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/request_token.py
new file mode 100644
index 00000000..0323cfb8
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/request_token.py
@@ -0,0 +1,209 @@
+# -*- coding: utf-8 -*-
+"""
+oauthlib.oauth1.rfc5849.endpoints.request_token
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This module is an implementation of the request token provider logic of
+OAuth 1.0 RFC 5849. It validates the correctness of request token requests,
+creates and persists tokens as well as create the proper response to be
+returned to the client.
+"""
+import logging
+
+from oauthlib.common import urlencode
+
+from .. import errors
+from .base import BaseEndpoint
+
+log = logging.getLogger(__name__)
+
+
+class RequestTokenEndpoint(BaseEndpoint):
+
+    """An endpoint responsible for providing OAuth 1 request tokens.
+
+    Typical use is to instantiate with a request validator and invoke the
+    ``create_request_token_response`` from a view function. The tuple returned
+    has all information necessary (body, status, headers) to quickly form
+    and return a proper response. See :doc:`/oauth1/validator` for details on which
+    validator methods to implement for this endpoint.
+    """
+
+    def create_request_token(self, request, credentials):
+        """Create and save a new request token.
+
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :param credentials: A dict of extra token credentials.
+        :returns: The token as an urlencoded string.
+        """
+        token = {
+            'oauth_token': self.token_generator(),
+            'oauth_token_secret': self.token_generator(),
+            'oauth_callback_confirmed': 'true'
+        }
+        token.update(credentials)
+        self.request_validator.save_request_token(token, request)
+        return urlencode(token.items())
+
+    def create_request_token_response(self, uri, http_method='GET', body=None,
+                                      headers=None, credentials=None):
+        """Create a request token response, with a new request token if valid.
+
+        :param uri: The full URI of the token request.
+        :param http_method: A valid HTTP verb, i.e. GET, POST, PUT, HEAD, etc.
+        :param body: The request body as a string.
+        :param headers: The request headers as a dict.
+        :param credentials: A list of extra credentials to include in the token.
+        :returns: A tuple of 3 elements.
+                  1. A dict of headers to set on the response.
+                  2. The response body as a string.
+                  3. The response status code as an integer.
+
+        An example of a valid request::
+
+            >>> from your_validator import your_validator
+            >>> from oauthlib.oauth1 import RequestTokenEndpoint
+            >>> endpoint = RequestTokenEndpoint(your_validator)
+            >>> h, b, s = endpoint.create_request_token_response(
+            ...     'https://your.provider/request_token?foo=bar',
+            ...     headers={
+            ...         'Authorization': 'OAuth realm=movies user, oauth_....'
+            ...     },
+            ...     credentials={
+            ...         'my_specific': 'argument',
+            ...     })
+            >>> h
+            {'Content-Type': 'application/x-www-form-urlencoded'}
+            >>> b
+            'oauth_token=lsdkfol23w54jlksdef&oauth_token_secret=qwe089234lkjsdf&oauth_callback_confirmed=true&my_specific=argument'
+            >>> s
+            200
+
+        An response to invalid request would have a different body and status::
+
+            >>> b
+            'error=invalid_request&description=missing+callback+uri'
+            >>> s
+            400
+
+        The same goes for an an unauthorized request:
+
+            >>> b
+            ''
+            >>> s
+            401
+        """
+        resp_headers = {'Content-Type': 'application/x-www-form-urlencoded'}
+        try:
+            request = self._create_request(uri, http_method, body, headers)
+            valid, processed_request = self.validate_request_token_request(
+                request)
+            if valid:
+                token = self.create_request_token(request, credentials or {})
+                return resp_headers, token, 200
+            else:
+                return {}, None, 401
+        except errors.OAuth1Error as e:
+            return resp_headers, e.urlencoded, e.status_code
+
+    def validate_request_token_request(self, request):
+        """Validate a request token request.
+
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :raises: OAuth1Error if the request is invalid.
+        :returns: A tuple of 2 elements.
+                  1. The validation result (True or False).
+                  2. The request object.
+        """
+        self._check_transport_security(request)
+        self._check_mandatory_parameters(request)
+
+        if request.realm:
+            request.realms = request.realm.split(' ')
+        else:
+            request.realms = self.request_validator.get_default_realms(
+                request.client_key, request)
+        if not self.request_validator.check_realms(request.realms):
+            raise errors.InvalidRequestError(
+                description='Invalid realm {}. Allowed are {!r}.'.format(
+                    request.realms, self.request_validator.realms))
+
+        if not request.redirect_uri:
+            raise errors.InvalidRequestError(
+                description='Missing callback URI.')
+
+        if not self.request_validator.validate_timestamp_and_nonce(
+                request.client_key, request.timestamp, request.nonce, request,
+                request_token=request.resource_owner_key):
+            return False, request
+
+        # The server SHOULD return a 401 (Unauthorized) status code when
+        # receiving a request with invalid client credentials.
+        # Note: This is postponed in order to avoid timing attacks, instead
+        # a dummy client is assigned and used to maintain near constant
+        # time request verification.
+        #
+        # Note that early exit would enable client enumeration
+        valid_client = self.request_validator.validate_client_key(
+            request.client_key, request)
+        if not valid_client:
+            request.client_key = self.request_validator.dummy_client
+
+        # Note that `realm`_ is only used in authorization headers and how
+        # it should be interpreted is not included in the OAuth spec.
+        # However they could be seen as a scope or realm to which the
+        # client has access and as such every client should be checked
+        # to ensure it is authorized access to that scope or realm.
+        # .. _`realm`: https://tools.ietf.org/html/rfc2617#section-1.2
+        #
+        # Note that early exit would enable client realm access enumeration.
+        #
+        # The require_realm indicates this is the first step in the OAuth
+        # workflow where a client requests access to a specific realm.
+        # This first step (obtaining request token) need not require a realm
+        # and can then be identified by checking the require_resource_owner
+        # flag and absence of realm.
+        #
+        # Clients obtaining an access token will not supply a realm and it will
+        # not be checked. Instead the previously requested realm should be
+        # transferred from the request token to the access token.
+        #
+        # Access to protected resources will always validate the realm but note
+        # that the realm is now tied to the access token and not provided by
+        # the client.
+        valid_realm = self.request_validator.validate_requested_realms(
+            request.client_key, request.realms, request)
+
+        # Callback is normally never required, except for requests for
+        # a Temporary Credential as described in `Section 2.1`_
+        # .._`Section 2.1`: https://tools.ietf.org/html/rfc5849#section-2.1
+        valid_redirect = self.request_validator.validate_redirect_uri(
+            request.client_key, request.redirect_uri, request)
+        if not request.redirect_uri:
+            raise NotImplementedError('Redirect URI must either be provided '
+                                      'or set to a default during validation.')
+
+        valid_signature = self._check_signature(request)
+
+        # log the results to the validator_log
+        # this lets us handle internal reporting and analysis
+        request.validator_log['client'] = valid_client
+        request.validator_log['realm'] = valid_realm
+        request.validator_log['callback'] = valid_redirect
+        request.validator_log['signature'] = valid_signature
+
+        # We delay checking validity until the very end, using dummy values for
+        # calculations and fetching secrets/keys to ensure the flow of every
+        # request remains almost identical regardless of whether valid values
+        # have been supplied. This ensures near constant time execution and
+        # prevents malicious users from guessing sensitive information
+        v = all((valid_client, valid_realm, valid_redirect, valid_signature))
+        if not v:
+            log.info("[Failure] request verification failed.")
+            log.info("Valid client: %s.", valid_client)
+            log.info("Valid realm: %s.", valid_realm)
+            log.info("Valid callback: %s.", valid_redirect)
+            log.info("Valid signature: %s.", valid_signature)
+        return v, request
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/resource.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/resource.py
new file mode 100644
index 00000000..8641152e
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/resource.py
@@ -0,0 +1,163 @@
+# -*- coding: utf-8 -*-
+"""
+oauthlib.oauth1.rfc5849.endpoints.resource
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This module is an implementation of the resource protection provider logic of
+OAuth 1.0 RFC 5849.
+"""
+import logging
+
+from .. import errors
+from .base import BaseEndpoint
+
+log = logging.getLogger(__name__)
+
+
+class ResourceEndpoint(BaseEndpoint):
+
+    """An endpoint responsible for protecting resources.
+
+    Typical use is to instantiate with a request validator and invoke the
+    ``validate_protected_resource_request`` in a decorator around a view
+    function. If the request is valid, invoke and return the response of the
+    view. If invalid create and return an error response directly from the
+    decorator.
+
+    See :doc:`/oauth1/validator` for details on which validator methods to implement
+    for this endpoint.
+
+    An example decorator::
+
+        from functools import wraps
+        from your_validator import your_validator
+        from oauthlib.oauth1 import ResourceEndpoint
+        endpoint = ResourceEndpoint(your_validator)
+
+        def require_oauth(realms=None):
+            def decorator(f):
+                @wraps(f)
+                def wrapper(request, *args, **kwargs):
+                    v, r = provider.validate_protected_resource_request(
+                            request.url,
+                            http_method=request.method,
+                            body=request.data,
+                            headers=request.headers,
+                            realms=realms or [])
+                    if v:
+                        return f(*args, **kwargs)
+                    else:
+                        return abort(403)
+    """
+
+    def validate_protected_resource_request(self, uri, http_method='GET',
+                                            body=None, headers=None, realms=None):
+        """Create a request token response, with a new request token if valid.
+
+        :param uri: The full URI of the token request.
+        :param http_method: A valid HTTP verb, i.e. GET, POST, PUT, HEAD, etc.
+        :param body: The request body as a string.
+        :param headers: The request headers as a dict.
+        :param realms: A list of realms the resource is protected under.
+                       This will be supplied to the ``validate_realms``
+                       method of the request validator.
+        :returns: A tuple of 2 elements.
+                  1. True if valid, False otherwise.
+                  2. An oauthlib.common.Request object.
+        """
+        try:
+            request = self._create_request(uri, http_method, body, headers)
+        except errors.OAuth1Error:
+            return False, None
+
+        try:
+            self._check_transport_security(request)
+            self._check_mandatory_parameters(request)
+        except errors.OAuth1Error:
+            return False, request
+
+        if not request.resource_owner_key:
+            return False, request
+
+        if not self.request_validator.check_access_token(
+                request.resource_owner_key):
+            return False, request
+
+        if not self.request_validator.validate_timestamp_and_nonce(
+                request.client_key, request.timestamp, request.nonce, request,
+                access_token=request.resource_owner_key):
+            return False, request
+
+        # The server SHOULD return a 401 (Unauthorized) status code when
+        # receiving a request with invalid client credentials.
+        # Note: This is postponed in order to avoid timing attacks, instead
+        # a dummy client is assigned and used to maintain near constant
+        # time request verification.
+        #
+        # Note that early exit would enable client enumeration
+        valid_client = self.request_validator.validate_client_key(
+            request.client_key, request)
+        if not valid_client:
+            request.client_key = self.request_validator.dummy_client
+
+        # The server SHOULD return a 401 (Unauthorized) status code when
+        # receiving a request with invalid or expired token.
+        # Note: This is postponed in order to avoid timing attacks, instead
+        # a dummy token is assigned and used to maintain near constant
+        # time request verification.
+        #
+        # Note that early exit would enable resource owner enumeration
+        valid_resource_owner = self.request_validator.validate_access_token(
+            request.client_key, request.resource_owner_key, request)
+        if not valid_resource_owner:
+            request.resource_owner_key = self.request_validator.dummy_access_token
+
+        # Note that `realm`_ is only used in authorization headers and how
+        # it should be interpreted is not included in the OAuth spec.
+        # However they could be seen as a scope or realm to which the
+        # client has access and as such every client should be checked
+        # to ensure it is authorized access to that scope or realm.
+        # .. _`realm`: https://tools.ietf.org/html/rfc2617#section-1.2
+        #
+        # Note that early exit would enable client realm access enumeration.
+        #
+        # The require_realm indicates this is the first step in the OAuth
+        # workflow where a client requests access to a specific realm.
+        # This first step (obtaining request token) need not require a realm
+        # and can then be identified by checking the require_resource_owner
+        # flag and absence of realm.
+        #
+        # Clients obtaining an access token will not supply a realm and it will
+        # not be checked. Instead the previously requested realm should be
+        # transferred from the request token to the access token.
+        #
+        # Access to protected resources will always validate the realm but note
+        # that the realm is now tied to the access token and not provided by
+        # the client.
+        valid_realm = self.request_validator.validate_realms(request.client_key,
+                                                             request.resource_owner_key, request, uri=request.uri,
+                                                             realms=realms)
+
+        valid_signature = self._check_signature(request)
+
+        # log the results to the validator_log
+        # this lets us handle internal reporting and analysis
+        request.validator_log['client'] = valid_client
+        request.validator_log['resource_owner'] = valid_resource_owner
+        request.validator_log['realm'] = valid_realm
+        request.validator_log['signature'] = valid_signature
+
+        # We delay checking validity until the very end, using dummy values for
+        # calculations and fetching secrets/keys to ensure the flow of every
+        # request remains almost identical regardless of whether valid values
+        # have been supplied. This ensures near constant time execution and
+        # prevents malicious users from guessing sensitive information
+        v = all((valid_client, valid_resource_owner, valid_realm,
+                 valid_signature))
+        if not v:
+            log.info("[Failure] request verification failed.")
+            log.info("Valid client: %s", valid_client)
+            log.info("Valid token: %s", valid_resource_owner)
+            log.info("Valid realm: %s", valid_realm)
+            log.info("Valid signature: %s", valid_signature)
+        return v, request
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/signature_only.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/signature_only.py
new file mode 100644
index 00000000..d693ccb7
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/endpoints/signature_only.py
@@ -0,0 +1,82 @@
+# -*- coding: utf-8 -*-
+"""
+oauthlib.oauth1.rfc5849.endpoints.signature_only
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This module is an implementation of the signing logic of OAuth 1.0 RFC 5849.
+"""
+
+import logging
+
+from .. import errors
+from .base import BaseEndpoint
+
+log = logging.getLogger(__name__)
+
+
+class SignatureOnlyEndpoint(BaseEndpoint):
+
+    """An endpoint only responsible for verifying an oauth signature."""
+
+    def validate_request(self, uri, http_method='GET',
+                         body=None, headers=None):
+        """Validate a signed OAuth request.
+
+        :param uri: The full URI of the token request.
+        :param http_method: A valid HTTP verb, i.e. GET, POST, PUT, HEAD, etc.
+        :param body: The request body as a string.
+        :param headers: The request headers as a dict.
+        :returns: A tuple of 2 elements.
+                  1. True if valid, False otherwise.
+                  2. An oauthlib.common.Request object.
+        """
+        try:
+            request = self._create_request(uri, http_method, body, headers)
+        except errors.OAuth1Error as err:
+            log.info(
+                'Exception caught while validating request, %s.' % err)
+            return False, None
+
+        try:
+            self._check_transport_security(request)
+            self._check_mandatory_parameters(request)
+        except errors.OAuth1Error as err:
+            log.info(
+                'Exception caught while validating request, %s.' % err)
+            return False, request
+
+        if not self.request_validator.validate_timestamp_and_nonce(
+                request.client_key, request.timestamp, request.nonce, request):
+            log.debug('[Failure] verification failed: timestamp/nonce')
+            return False, request
+
+        # The server SHOULD return a 401 (Unauthorized) status code when
+        # receiving a request with invalid client credentials.
+        # Note: This is postponed in order to avoid timing attacks, instead
+        # a dummy client is assigned and used to maintain near constant
+        # time request verification.
+        #
+        # Note that early exit would enable client enumeration
+        valid_client = self.request_validator.validate_client_key(
+            request.client_key, request)
+        if not valid_client:
+            request.client_key = self.request_validator.dummy_client
+
+        valid_signature = self._check_signature(request)
+
+        # log the results to the validator_log
+        # this lets us handle internal reporting and analysis
+        request.validator_log['client'] = valid_client
+        request.validator_log['signature'] = valid_signature
+
+        # We delay checking validity until the very end, using dummy values for
+        # calculations and fetching secrets/keys to ensure the flow of every
+        # request remains almost identical regardless of whether valid values
+        # have been supplied. This ensures near constant time execution and
+        # prevents malicious users from guessing sensitive information
+        v = all((valid_client, valid_signature))
+        if not v:
+            log.info("[Failure] request verification failed.")
+            log.info("Valid client: %s", valid_client)
+            log.info("Valid signature: %s", valid_signature)
+        return v, request
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/errors.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/errors.py
new file mode 100644
index 00000000..8774d407
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/errors.py
@@ -0,0 +1,76 @@
+"""
+oauthlib.oauth1.rfc5849.errors
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Error used both by OAuth 1 clients and provicers to represent the spec
+defined error responses for all four core grant types.
+"""
+from oauthlib.common import add_params_to_uri, urlencode
+
+
+class OAuth1Error(Exception):
+    error = None
+    description = ''
+
+    def __init__(self, description=None, uri=None, status_code=400,
+                 request=None):
+        """
+        description:    A human-readable ASCII [USASCII] text providing
+                        additional information, used to assist the client
+                        developer in understanding the error that occurred.
+                        Values for the "error_description" parameter MUST NOT
+                        include characters outside the set
+                        x20-21 / x23-5B / x5D-7E.
+
+        uri:    A URI identifying a human-readable web page with information
+                about the error, used to provide the client developer with
+                additional information about the error.  Values for the
+                "error_uri" parameter MUST conform to the URI- Reference
+                syntax, and thus MUST NOT include characters outside the set
+                x21 / x23-5B / x5D-7E.
+
+        state:  A CSRF protection value received from the client.
+
+        request:  Oauthlib Request object
+        """
+        self.description = description or self.description
+        message = '({}) {}'.format(self.error, self.description)
+        if request:
+            message += ' ' + repr(request)
+        super().__init__(message)
+
+        self.uri = uri
+        self.status_code = status_code
+
+    def in_uri(self, uri):
+        return add_params_to_uri(uri, self.twotuples)
+
+    @property
+    def twotuples(self):
+        error = [('error', self.error)]
+        if self.description:
+            error.append(('error_description', self.description))
+        if self.uri:
+            error.append(('error_uri', self.uri))
+        return error
+
+    @property
+    def urlencoded(self):
+        return urlencode(self.twotuples)
+
+
+class InsecureTransportError(OAuth1Error):
+    error = 'insecure_transport_protocol'
+    description = 'Only HTTPS connections are permitted.'
+
+
+class InvalidSignatureMethodError(OAuth1Error):
+    error = 'invalid_signature_method'
+
+
+class InvalidRequestError(OAuth1Error):
+    error = 'invalid_request'
+
+
+class InvalidClientError(OAuth1Error):
+    error = 'invalid_client'
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/parameters.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/parameters.py
new file mode 100644
index 00000000..2163772d
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/parameters.py
@@ -0,0 +1,133 @@
+"""
+oauthlib.parameters
+~~~~~~~~~~~~~~~~~~~
+
+This module contains methods related to `section 3.5`_ of the OAuth 1.0a spec.
+
+.. _`section 3.5`: https://tools.ietf.org/html/rfc5849#section-3.5
+"""
+from urllib.parse import urlparse, urlunparse
+
+from oauthlib.common import extract_params, urlencode
+
+from . import utils
+
+
+# TODO: do we need filter_params now that oauth_params are handled by Request?
+#       We can easily pass in just oauth protocol params.
+@utils.filter_params
+def prepare_headers(oauth_params, headers=None, realm=None):
+    """**Prepare the Authorization header.**
+    Per `section 3.5.1`_ of the spec.
+
+    Protocol parameters can be transmitted using the HTTP "Authorization"
+    header field as defined by `RFC2617`_ with the auth-scheme name set to
+    "OAuth" (case insensitive).
+
+    For example::
+
+        Authorization: OAuth realm="Example",
+            oauth_consumer_key="0685bd9184jfhq22",
+            oauth_token="ad180jjd733klru7",
+            oauth_signature_method="HMAC-SHA1",
+            oauth_signature="wOJIO9A2W5mFwDgiDvZbTSMK%2FPY%3D",
+            oauth_timestamp="137131200",
+            oauth_nonce="4572616e48616d6d65724c61686176",
+            oauth_version="1.0"
+
+
+    .. _`section 3.5.1`: https://tools.ietf.org/html/rfc5849#section-3.5.1
+    .. _`RFC2617`: https://tools.ietf.org/html/rfc2617
+    """
+    headers = headers or {}
+
+    # Protocol parameters SHALL be included in the "Authorization" header
+    # field as follows:
+    authorization_header_parameters_parts = []
+    for oauth_parameter_name, value in oauth_params:
+        # 1.  Parameter names and values are encoded per Parameter Encoding
+        #     (`Section 3.6`_)
+        #
+        # .. _`Section 3.6`: https://tools.ietf.org/html/rfc5849#section-3.6
+        escaped_name = utils.escape(oauth_parameter_name)
+        escaped_value = utils.escape(value)
+
+        # 2.  Each parameter's name is immediately followed by an "=" character
+        #     (ASCII code 61), a """ character (ASCII code 34), the parameter
+        #     value (MAY be empty), and another """ character (ASCII code 34).
+        part = '{}="{}"'.format(escaped_name, escaped_value)
+
+        authorization_header_parameters_parts.append(part)
+
+    # 3.  Parameters are separated by a "," character (ASCII code 44) and
+    #     OPTIONAL linear whitespace per `RFC2617`_.
+    #
+    # .. _`RFC2617`: https://tools.ietf.org/html/rfc2617
+    authorization_header_parameters = ', '.join(
+        authorization_header_parameters_parts)
+
+    # 4.  The OPTIONAL "realm" parameter MAY be added and interpreted per
+    #     `RFC2617 section 1.2`_.
+    #
+    # .. _`RFC2617 section 1.2`: https://tools.ietf.org/html/rfc2617#section-1.2
+    if realm:
+        # NOTE: realm should *not* be escaped
+        authorization_header_parameters = ('realm="%s", ' % realm +
+                                           authorization_header_parameters)
+
+    # the auth-scheme name set to "OAuth" (case insensitive).
+    authorization_header = 'OAuth %s' % authorization_header_parameters
+
+    # contribute the Authorization header to the given headers
+    full_headers = {}
+    full_headers.update(headers)
+    full_headers['Authorization'] = authorization_header
+    return full_headers
+
+
+def _append_params(oauth_params, params):
+    """Append OAuth params to an existing set of parameters.
+
+    Both params and oauth_params is must be lists of 2-tuples.
+
+    Per `section 3.5.2`_ and `3.5.3`_ of the spec.
+
+    .. _`section 3.5.2`: https://tools.ietf.org/html/rfc5849#section-3.5.2
+    .. _`3.5.3`: https://tools.ietf.org/html/rfc5849#section-3.5.3
+
+    """
+    merged = list(params)
+    merged.extend(oauth_params)
+    # The request URI / entity-body MAY include other request-specific
+    # parameters, in which case, the protocol parameters SHOULD be appended
+    # following the request-specific parameters, properly separated by an "&"
+    # character (ASCII code 38)
+    merged.sort(key=lambda i: i[0].startswith('oauth_'))
+    return merged
+
+
+def prepare_form_encoded_body(oauth_params, body):
+    """Prepare the Form-Encoded Body.
+
+    Per `section 3.5.2`_ of the spec.
+
+    .. _`section 3.5.2`: https://tools.ietf.org/html/rfc5849#section-3.5.2
+
+    """
+    # append OAuth params to the existing body
+    return _append_params(oauth_params, body)
+
+
+def prepare_request_uri_query(oauth_params, uri):
+    """Prepare the Request URI Query.
+
+    Per `section 3.5.3`_ of the spec.
+
+    .. _`section 3.5.3`: https://tools.ietf.org/html/rfc5849#section-3.5.3
+
+    """
+    # append OAuth params to the existing set of query components
+    sch, net, path, par, query, fra = urlparse(uri)
+    query = urlencode(
+        _append_params(oauth_params, extract_params(query) or []))
+    return urlunparse((sch, net, path, par, query, fra))
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/request_validator.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/request_validator.py
new file mode 100644
index 00000000..e937aabf
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/request_validator.py
@@ -0,0 +1,849 @@
+"""
+oauthlib.oauth1.rfc5849
+~~~~~~~~~~~~~~
+
+This module is an implementation of various logic needed
+for signing and checking OAuth 1.0 RFC 5849 requests.
+"""
+from . import SIGNATURE_METHODS, utils
+
+
+class RequestValidator:
+
+    """A validator/datastore interaction base class for OAuth 1 providers.
+
+    OAuth providers should inherit from RequestValidator and implement the
+    methods and properties outlined below. Further details are provided in the
+    documentation for each method and property.
+
+    Methods used to check the format of input parameters. Common tests include
+    length, character set, membership, range or pattern. These tests are
+    referred to as `whitelisting or blacklisting`_. Whitelisting is better
+    but blacklisting can be useful to spot malicious activity.
+    The following have methods a default implementation:
+
+    - check_client_key
+    - check_request_token
+    - check_access_token
+    - check_nonce
+    - check_verifier
+    - check_realms
+
+    The methods above default to whitelist input parameters, checking that they
+    are alphanumerical and between a minimum and maximum length. Rather than
+    overloading the methods a few properties can be used to configure these
+    methods.
+
+    * @safe_characters -> (character set)
+    * @client_key_length -> (min, max)
+    * @request_token_length -> (min, max)
+    * @access_token_length -> (min, max)
+    * @nonce_length -> (min, max)
+    * @verifier_length -> (min, max)
+    * @realms -> [list, of, realms]
+
+    Methods used to validate/invalidate input parameters. These checks usually
+    hit either persistent or temporary storage such as databases or the
+    filesystem. See each methods documentation for detailed usage.
+    The following methods must be implemented:
+
+    - validate_client_key
+    - validate_request_token
+    - validate_access_token
+    - validate_timestamp_and_nonce
+    - validate_redirect_uri
+    - validate_requested_realms
+    - validate_realms
+    - validate_verifier
+    - invalidate_request_token
+
+    Methods used to retrieve sensitive information from storage.
+    The following methods must be implemented:
+
+    - get_client_secret
+    - get_request_token_secret
+    - get_access_token_secret
+    - get_rsa_key
+    - get_realms
+    - get_default_realms
+    - get_redirect_uri
+
+    Methods used to save credentials.
+    The following methods must be implemented:
+
+    - save_request_token
+    - save_verifier
+    - save_access_token
+
+    Methods used to verify input parameters. This methods are used during
+    authorizing request token by user (AuthorizationEndpoint), to check if
+    parameters are valid. During token authorization request is not signed,
+    thus 'validation' methods can not be used. The following methods must be
+    implemented:
+
+    - verify_realms
+    - verify_request_token
+
+    To prevent timing attacks it is necessary to not exit early even if the
+    client key or resource owner key is invalid. Instead dummy values should
+    be used during the remaining verification process. It is very important
+    that the dummy client and token are valid input parameters to the methods
+    get_client_secret, get_rsa_key and get_(access/request)_token_secret and
+    that the running time of those methods when given a dummy value remain
+    equivalent to the running time when given a valid client/resource owner.
+    The following properties must be implemented:
+
+    * @dummy_client
+    * @dummy_request_token
+    * @dummy_access_token
+
+    Example implementations have been provided, note that the database used is
+    a simple dictionary and serves only an illustrative purpose. Use whichever
+    database suits your project and how to access it is entirely up to you.
+    The methods are introduced in an order which should make understanding
+    their use more straightforward and as such it could be worth reading what
+    follows in chronological order.
+
+    .. _`whitelisting or blacklisting`: https://www.schneier.com/blog/archives/2011/01/whitelisting_vs.html
+    """
+
+    def __init__(self):
+        pass
+
+    @property
+    def allowed_signature_methods(self):
+        return SIGNATURE_METHODS
+
+    @property
+    def safe_characters(self):
+        return set(utils.UNICODE_ASCII_CHARACTER_SET)
+
+    @property
+    def client_key_length(self):
+        return 20, 30
+
+    @property
+    def request_token_length(self):
+        return 20, 30
+
+    @property
+    def access_token_length(self):
+        return 20, 30
+
+    @property
+    def timestamp_lifetime(self):
+        return 600
+
+    @property
+    def nonce_length(self):
+        return 20, 30
+
+    @property
+    def verifier_length(self):
+        return 20, 30
+
+    @property
+    def realms(self):
+        return []
+
+    @property
+    def enforce_ssl(self):
+        return True
+
+    def check_client_key(self, client_key):
+        """Check that the client key only contains safe characters
+        and is no shorter than lower and no longer than upper.
+        """
+        lower, upper = self.client_key_length
+        return (set(client_key) <= self.safe_characters and
+                lower <= len(client_key) <= upper)
+
+    def check_request_token(self, request_token):
+        """Checks that the request token contains only safe characters
+        and is no shorter than lower and no longer than upper.
+        """
+        lower, upper = self.request_token_length
+        return (set(request_token) <= self.safe_characters and
+                lower <= len(request_token) <= upper)
+
+    def check_access_token(self, request_token):
+        """Checks that the token contains only safe characters
+        and is no shorter than lower and no longer than upper.
+        """
+        lower, upper = self.access_token_length
+        return (set(request_token) <= self.safe_characters and
+                lower <= len(request_token) <= upper)
+
+    def check_nonce(self, nonce):
+        """Checks that the nonce only contains only safe characters
+        and is no shorter than lower and no longer than upper.
+        """
+        lower, upper = self.nonce_length
+        return (set(nonce) <= self.safe_characters and
+                lower <= len(nonce) <= upper)
+
+    def check_verifier(self, verifier):
+        """Checks that the verifier contains only safe characters
+        and is no shorter than lower and no longer than upper.
+        """
+        lower, upper = self.verifier_length
+        return (set(verifier) <= self.safe_characters and
+                lower <= len(verifier) <= upper)
+
+    def check_realms(self, realms):
+        """Check that the realm is one of a set allowed realms."""
+        return all(r in self.realms for r in realms)
+
+    def _subclass_must_implement(self, fn):
+        """
+        Returns a NotImplementedError for a function that should be implemented.
+        :param fn: name of the function
+        """
+        m = "Missing function implementation in {}: {}".format(type(self), fn)
+        return NotImplementedError(m)
+
+    @property
+    def dummy_client(self):
+        """Dummy client used when an invalid client key is supplied.
+
+        :returns: The dummy client key string.
+
+        The dummy client should be associated with either a client secret,
+        a rsa key or both depending on which signature methods are supported.
+        Providers should make sure that
+
+        get_client_secret(dummy_client)
+        get_rsa_key(dummy_client)
+
+        return a valid secret or key for the dummy client.
+
+        This method is used by
+
+        * AccessTokenEndpoint
+        * RequestTokenEndpoint
+        * ResourceEndpoint
+        * SignatureOnlyEndpoint
+        """
+        raise self._subclass_must_implement("dummy_client")
+
+    @property
+    def dummy_request_token(self):
+        """Dummy request token used when an invalid token was supplied.
+
+        :returns: The dummy request token string.
+
+        The dummy request token should be associated with a request token
+        secret such that get_request_token_secret(.., dummy_request_token)
+        returns a valid secret.
+
+        This method is used by
+
+        * AccessTokenEndpoint
+        """
+        raise self._subclass_must_implement("dummy_request_token")
+
+    @property
+    def dummy_access_token(self):
+        """Dummy access token used when an invalid token was supplied.
+
+        :returns: The dummy access token string.
+
+        The dummy access token should be associated with an access token
+        secret such that get_access_token_secret(.., dummy_access_token)
+        returns a valid secret.
+
+        This method is used by
+
+        * ResourceEndpoint
+        """
+        raise self._subclass_must_implement("dummy_access_token")
+
+    def get_client_secret(self, client_key, request):
+        """Retrieves the client secret associated with the client key.
+
+        :param client_key: The client/consumer key.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :returns: The client secret as a string.
+
+        This method must allow the use of a dummy client_key value.
+        Fetching the secret using the dummy key must take the same amount of
+        time as fetching a secret for a valid client::
+
+            # Unlikely to be near constant time as it uses two database
+            # lookups for a valid client, and only one for an invalid.
+            from your_datastore import ClientSecret
+            if ClientSecret.has(client_key):
+                return ClientSecret.get(client_key)
+            else:
+                return 'dummy'
+
+            # Aim to mimic number of latency inducing operations no matter
+            # whether the client is valid or not.
+            from your_datastore import ClientSecret
+            return ClientSecret.get(client_key, 'dummy')
+
+        Note that the returned key must be in plaintext.
+
+        This method is used by
+
+        * AccessTokenEndpoint
+        * RequestTokenEndpoint
+        * ResourceEndpoint
+        * SignatureOnlyEndpoint
+        """
+        raise self._subclass_must_implement('get_client_secret')
+
+    def get_request_token_secret(self, client_key, token, request):
+        """Retrieves the shared secret associated with the request token.
+
+        :param client_key: The client/consumer key.
+        :param token: The request token string.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :returns: The token secret as a string.
+
+        This method must allow the use of a dummy values and the running time
+        must be roughly equivalent to that of the running time of valid values::
+
+            # Unlikely to be near constant time as it uses two database
+            # lookups for a valid client, and only one for an invalid.
+            from your_datastore import RequestTokenSecret
+            if RequestTokenSecret.has(client_key):
+                return RequestTokenSecret.get((client_key, request_token))
+            else:
+                return 'dummy'
+
+            # Aim to mimic number of latency inducing operations no matter
+            # whether the client is valid or not.
+            from your_datastore import RequestTokenSecret
+            return ClientSecret.get((client_key, request_token), 'dummy')
+
+        Note that the returned key must be in plaintext.
+
+        This method is used by
+
+        * AccessTokenEndpoint
+        """
+        raise self._subclass_must_implement('get_request_token_secret')
+
+    def get_access_token_secret(self, client_key, token, request):
+        """Retrieves the shared secret associated with the access token.
+
+        :param client_key: The client/consumer key.
+        :param token: The access token string.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :returns: The token secret as a string.
+
+        This method must allow the use of a dummy values and the running time
+        must be roughly equivalent to that of the running time of valid values::
+
+            # Unlikely to be near constant time as it uses two database
+            # lookups for a valid client, and only one for an invalid.
+            from your_datastore import AccessTokenSecret
+            if AccessTokenSecret.has(client_key):
+                return AccessTokenSecret.get((client_key, request_token))
+            else:
+                return 'dummy'
+
+            # Aim to mimic number of latency inducing operations no matter
+            # whether the client is valid or not.
+            from your_datastore import AccessTokenSecret
+            return ClientSecret.get((client_key, request_token), 'dummy')
+
+        Note that the returned key must be in plaintext.
+
+        This method is used by
+
+        * ResourceEndpoint
+        """
+        raise self._subclass_must_implement("get_access_token_secret")
+
+    def get_default_realms(self, client_key, request):
+        """Get the default realms for a client.
+
+        :param client_key: The client/consumer key.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :returns: The list of default realms associated with the client.
+
+        The list of default realms will be set during client registration and
+        is outside the scope of OAuthLib.
+
+        This method is used by
+
+        * RequestTokenEndpoint
+        """
+        raise self._subclass_must_implement("get_default_realms")
+
+    def get_realms(self, token, request):
+        """Get realms associated with a request token.
+
+        :param token: The request token string.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :returns: The list of realms associated with the request token.
+
+        This method is used by
+
+        * AuthorizationEndpoint
+        * AccessTokenEndpoint
+        """
+        raise self._subclass_must_implement("get_realms")
+
+    def get_redirect_uri(self, token, request):
+        """Get the redirect URI associated with a request token.
+
+        :param token: The request token string.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :returns: The redirect URI associated with the request token.
+
+        It may be desirable to return a custom URI if the redirect is set to "oob".
+        In this case, the user will be redirected to the returned URI and at that
+        endpoint the verifier can be displayed.
+
+        This method is used by
+
+        * AuthorizationEndpoint
+        """
+        raise self._subclass_must_implement("get_redirect_uri")
+
+    def get_rsa_key(self, client_key, request):
+        """Retrieves a previously stored client provided RSA key.
+
+        :param client_key: The client/consumer key.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :returns: The rsa public key as a string.
+
+        This method must allow the use of a dummy client_key value. Fetching
+        the rsa key using the dummy key must take the same amount of time
+        as fetching a key for a valid client. The dummy key must also be of
+        the same bit length as client keys.
+
+        Note that the key must be returned in plaintext.
+
+        This method is used by
+
+        * AccessTokenEndpoint
+        * RequestTokenEndpoint
+        * ResourceEndpoint
+        * SignatureOnlyEndpoint
+        """
+        raise self._subclass_must_implement("get_rsa_key")
+
+    def invalidate_request_token(self, client_key, request_token, request):
+        """Invalidates a used request token.
+
+        :param client_key: The client/consumer key.
+        :param request_token: The request token string.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :returns: None
+
+        Per `Section 2.3`_ of the spec:
+
+        "The server MUST (...) ensure that the temporary
+        credentials have not expired or been used before."
+
+        .. _`Section 2.3`: https://tools.ietf.org/html/rfc5849#section-2.3
+
+        This method should ensure that provided token won't validate anymore.
+        It can be simply removing RequestToken from storage or setting
+        specific flag that makes it invalid (note that such flag should be
+        also validated during request token validation).
+
+        This method is used by
+
+        * AccessTokenEndpoint
+        """
+        raise self._subclass_must_implement("invalidate_request_token")
+
+    def validate_client_key(self, client_key, request):
+        """Validates that supplied client key is a registered and valid client.
+
+        :param client_key: The client/consumer key.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :returns: True or False
+
+        Note that if the dummy client is supplied it should validate in same
+        or nearly the same amount of time as a valid one.
+
+        Ensure latency inducing tasks are mimiced even for dummy clients.
+        For example, use::
+
+            from your_datastore import Client
+            try:
+                return Client.exists(client_key, access_token)
+            except DoesNotExist:
+                return False
+
+        Rather than::
+
+            from your_datastore import Client
+            if access_token == self.dummy_access_token:
+                return False
+            else:
+                return Client.exists(client_key, access_token)
+
+        This method is used by
+
+        * AccessTokenEndpoint
+        * RequestTokenEndpoint
+        * ResourceEndpoint
+        * SignatureOnlyEndpoint
+        """
+        raise self._subclass_must_implement("validate_client_key")
+
+    def validate_request_token(self, client_key, token, request):
+        """Validates that supplied request token is registered and valid.
+
+        :param client_key: The client/consumer key.
+        :param token: The request token string.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :returns: True or False
+
+        Note that if the dummy request_token is supplied it should validate in
+        the same nearly the same amount of time as a valid one.
+
+        Ensure latency inducing tasks are mimiced even for dummy clients.
+        For example, use::
+
+            from your_datastore import RequestToken
+            try:
+                return RequestToken.exists(client_key, access_token)
+            except DoesNotExist:
+                return False
+
+        Rather than::
+
+            from your_datastore import RequestToken
+            if access_token == self.dummy_access_token:
+                return False
+            else:
+                return RequestToken.exists(client_key, access_token)
+
+        This method is used by
+
+        * AccessTokenEndpoint
+        """
+        raise self._subclass_must_implement("validate_request_token")
+
+    def validate_access_token(self, client_key, token, request):
+        """Validates that supplied access token is registered and valid.
+
+        :param client_key: The client/consumer key.
+        :param token: The access token string.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :returns: True or False
+
+        Note that if the dummy access token is supplied it should validate in
+        the same or nearly the same amount of time as a valid one.
+
+        Ensure latency inducing tasks are mimiced even for dummy clients.
+        For example, use::
+
+            from your_datastore import AccessToken
+            try:
+                return AccessToken.exists(client_key, access_token)
+            except DoesNotExist:
+                return False
+
+        Rather than::
+
+            from your_datastore import AccessToken
+            if access_token == self.dummy_access_token:
+                return False
+            else:
+                return AccessToken.exists(client_key, access_token)
+
+        This method is used by
+
+        * ResourceEndpoint
+        """
+        raise self._subclass_must_implement("validate_access_token")
+
+    def validate_timestamp_and_nonce(self, client_key, timestamp, nonce,
+                                     request, request_token=None, access_token=None):
+        """Validates that the nonce has not been used before.
+
+        :param client_key: The client/consumer key.
+        :param timestamp: The ``oauth_timestamp`` parameter.
+        :param nonce: The ``oauth_nonce`` parameter.
+        :param request_token: Request token string, if any.
+        :param access_token: Access token string, if any.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :returns: True or False
+
+        Per `Section 3.3`_ of the spec.
+
+        "A nonce is a random string, uniquely generated by the client to allow
+        the server to verify that a request has never been made before and
+        helps prevent replay attacks when requests are made over a non-secure
+        channel.  The nonce value MUST be unique across all requests with the
+        same timestamp, client credentials, and token combinations."
+
+        .. _`Section 3.3`: https://tools.ietf.org/html/rfc5849#section-3.3
+
+        One of the first validation checks that will be made is for the validity
+        of the nonce and timestamp, which are associated with a client key and
+        possibly a token. If invalid then immediately fail the request
+        by returning False. If the nonce/timestamp pair has been used before and
+        you may just have detected a replay attack. Therefore it is an essential
+        part of OAuth security that you not allow nonce/timestamp reuse.
+        Note that this validation check is done before checking the validity of
+        the client and token.::
+
+           nonces_and_timestamps_database = [
+              (u'foo', 1234567890, u'rannoMstrInghere', u'bar')
+           ]
+
+           def validate_timestamp_and_nonce(self, client_key, timestamp, nonce,
+              request_token=None, access_token=None):
+
+              return ((client_key, timestamp, nonce, request_token or access_token)
+                       not in self.nonces_and_timestamps_database)
+
+        This method is used by
+
+        * AccessTokenEndpoint
+        * RequestTokenEndpoint
+        * ResourceEndpoint
+        * SignatureOnlyEndpoint
+        """
+        raise self._subclass_must_implement("validate_timestamp_and_nonce")
+
+    def validate_redirect_uri(self, client_key, redirect_uri, request):
+        """Validates the client supplied redirection URI.
+
+        :param client_key: The client/consumer key.
+        :param redirect_uri: The URI the client which to redirect back to after
+                             authorization is successful.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :returns: True or False
+
+        It is highly recommended that OAuth providers require their clients
+        to register all redirection URIs prior to using them in requests and
+        register them as absolute URIs. See `CWE-601`_ for more information
+        about open redirection attacks.
+
+        By requiring registration of all redirection URIs it should be
+        straightforward for the provider to verify whether the supplied
+        redirect_uri is valid or not.
+
+        Alternatively per `Section 2.1`_ of the spec:
+
+        "If the client is unable to receive callbacks or a callback URI has
+        been established via other means, the parameter value MUST be set to
+        "oob" (case sensitive), to indicate an out-of-band configuration."
+
+        .. _`CWE-601`: http://cwe.mitre.org/top25/index.html#CWE-601
+        .. _`Section 2.1`: https://tools.ietf.org/html/rfc5849#section-2.1
+
+        This method is used by
+
+        * RequestTokenEndpoint
+        """
+        raise self._subclass_must_implement("validate_redirect_uri")
+
+    def validate_requested_realms(self, client_key, realms, request):
+        """Validates that the client may request access to the realm.
+
+        :param client_key: The client/consumer key.
+        :param realms: The list of realms that client is requesting access to.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :returns: True or False
+
+        This method is invoked when obtaining a request token and should
+        tie a realm to the request token and after user authorization
+        this realm restriction should transfer to the access token.
+
+        This method is used by
+
+        * RequestTokenEndpoint
+        """
+        raise self._subclass_must_implement("validate_requested_realms")
+
+    def validate_realms(self, client_key, token, request, uri=None,
+                        realms=None):
+        """Validates access to the request realm.
+
+        :param client_key: The client/consumer key.
+        :param token: A request token string.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :param uri: The URI the realms is protecting.
+        :param realms: A list of realms that must have been granted to
+                       the access token.
+        :returns: True or False
+
+        How providers choose to use the realm parameter is outside the OAuth
+        specification but it is commonly used to restrict access to a subset
+        of protected resources such as "photos".
+
+        realms is a convenience parameter which can be used to provide
+        a per view method pre-defined list of allowed realms.
+
+        Can be as simple as::
+
+            from your_datastore import RequestToken
+            request_token = RequestToken.get(token, None)
+
+            if not request_token:
+                return False
+            return set(request_token.realms).issuperset(set(realms))
+
+        This method is used by
+
+        * ResourceEndpoint
+        """
+        raise self._subclass_must_implement("validate_realms")
+
+    def validate_verifier(self, client_key, token, verifier, request):
+        """Validates a verification code.
+
+        :param client_key: The client/consumer key.
+        :param token: A request token string.
+        :param verifier: The authorization verifier string.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :returns: True or False
+
+        OAuth providers issue a verification code to clients after the
+        resource owner authorizes access. This code is used by the client to
+        obtain token credentials and the provider must verify that the
+        verifier is valid and associated with the client as well as the
+        resource owner.
+
+        Verifier validation should be done in near constant time
+        (to avoid verifier enumeration). To achieve this we need a
+        constant time string comparison which is provided by OAuthLib
+        in ``oauthlib.common.safe_string_equals``::
+
+            from your_datastore import Verifier
+            correct_verifier = Verifier.get(client_key, request_token)
+            from oauthlib.common import safe_string_equals
+            return safe_string_equals(verifier, correct_verifier)
+
+        This method is used by
+
+        * AccessTokenEndpoint
+        """
+        raise self._subclass_must_implement("validate_verifier")
+
+    def verify_request_token(self, token, request):
+        """Verify that the given OAuth1 request token is valid.
+
+        :param token: A request token string.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :returns: True or False
+
+        This method is used only in AuthorizationEndpoint to check whether the
+        oauth_token given in the authorization URL is valid or not.
+        This request is not signed and thus similar ``validate_request_token``
+        method can not be used.
+
+        This method is used by
+
+        * AuthorizationEndpoint
+        """
+        raise self._subclass_must_implement("verify_request_token")
+
+    def verify_realms(self, token, realms, request):
+        """Verify authorized realms to see if they match those given to token.
+
+        :param token: An access token string.
+        :param realms: A list of realms the client attempts to access.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :returns: True or False
+
+        This prevents the list of authorized realms sent by the client during
+        the authorization step to be altered to include realms outside what
+        was bound with the request token.
+
+        Can be as simple as::
+
+            valid_realms = self.get_realms(token)
+            return all((r in valid_realms for r in realms))
+
+        This method is used by
+
+        * AuthorizationEndpoint
+        """
+        raise self._subclass_must_implement("verify_realms")
+
+    def save_access_token(self, token, request):
+        """Save an OAuth1 access token.
+
+        :param token: A dict with token credentials.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+
+        The token dictionary will at minimum include
+
+        * ``oauth_token`` the access token string.
+        * ``oauth_token_secret`` the token specific secret used in signing.
+        * ``oauth_authorized_realms`` a space separated list of realms.
+
+        Client key can be obtained from ``request.client_key``.
+
+        The list of realms (not joined string) can be obtained from
+        ``request.realm``.
+
+        This method is used by
+
+        * AccessTokenEndpoint
+        """
+        raise self._subclass_must_implement("save_access_token")
+
+    def save_request_token(self, token, request):
+        """Save an OAuth1 request token.
+
+        :param token: A dict with token credentials.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+
+        The token dictionary will at minimum include
+
+        * ``oauth_token`` the request token string.
+        * ``oauth_token_secret`` the token specific secret used in signing.
+        * ``oauth_callback_confirmed`` the string ``true``.
+
+        Client key can be obtained from ``request.client_key``.
+
+        This method is used by
+
+        * RequestTokenEndpoint
+        """
+        raise self._subclass_must_implement("save_request_token")
+
+    def save_verifier(self, token, verifier, request):
+        """Associate an authorization verifier with a request token.
+
+        :param token: A request token string.
+        :param verifier: A dictionary containing the oauth_verifier and
+                        oauth_token
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+
+        We need to associate verifiers with tokens for validation during the
+        access token request.
+
+        Note that unlike save_x_token token here is the ``oauth_token`` token
+        string from the request token saved previously.
+
+        This method is used by
+
+        * AuthorizationEndpoint
+        """
+        raise self._subclass_must_implement("save_verifier")
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/signature.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/signature.py
new file mode 100644
index 00000000..9cb1a517
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/signature.py
@@ -0,0 +1,852 @@
+"""
+This module is an implementation of `section 3.4`_ of RFC 5849.
+
+**Usage**
+
+Steps for signing a request:
+
+1. Collect parameters from the request using ``collect_parameters``.
+2. Normalize those parameters using ``normalize_parameters``.
+3. Create the *base string URI* using ``base_string_uri``.
+4. Create the *signature base string* from the above three components
+   using ``signature_base_string``.
+5. Pass the *signature base string* and the client credentials to one of the
+   sign-with-client functions. The HMAC-based signing functions needs
+   client credentials with secrets. The RSA-based signing functions needs
+   client credentials with an RSA private key.
+
+To verify a request, pass the request and credentials to one of the verify
+functions. The HMAC-based signing functions needs the shared secrets. The
+RSA-based verify functions needs the RSA public key.
+
+**Scope**
+
+All of the functions in this module should be considered internal to OAuthLib,
+since they are not imported into the "oauthlib.oauth1" module. Programs using
+OAuthLib should not use directly invoke any of the functions in this module.
+
+**Deprecated functions**
+
+The "sign_" methods that are not "_with_client" have been deprecated. They may
+be removed in a future release. Since they are all internal functions, this
+should have no impact on properly behaving programs.
+
+.. _`section 3.4`: https://tools.ietf.org/html/rfc5849#section-3.4
+"""
+
+import binascii
+import hashlib
+import hmac
+import ipaddress
+import logging
+import urllib.parse as urlparse
+import warnings
+
+from oauthlib.common import extract_params, safe_string_equals, urldecode
+
+from . import utils
+
+log = logging.getLogger(__name__)
+
+
+# ==== Common functions ==========================================
+
+def signature_base_string(
+        http_method: str,
+        base_str_uri: str,
+        normalized_encoded_request_parameters: str) -> str:
+    """
+    Construct the signature base string.
+
+    The *signature base string* is the value that is calculated and signed by
+    the client. It is also independently calculated by the server to verify
+    the signature, and therefore must produce the exact same value at both
+    ends or the signature won't verify.
+
+    The rules for calculating the *signature base string* are defined in
+    section 3.4.1.1`_ of RFC 5849.
+
+    .. _`section 3.4.1.1`: https://tools.ietf.org/html/rfc5849#section-3.4.1.1
+    """
+
+    # The signature base string is constructed by concatenating together,
+    # in order, the following HTTP request elements:
+
+    # 1.  The HTTP request method in uppercase.  For example: "HEAD",
+    #     "GET", "POST", etc.  If the request uses a custom HTTP method, it
+    #     MUST be encoded (`Section 3.6`_).
+    #
+    # .. _`Section 3.6`: https://tools.ietf.org/html/rfc5849#section-3.6
+    base_string = utils.escape(http_method.upper())
+
+    # 2.  An "&" character (ASCII code 38).
+    base_string += '&'
+
+    # 3.  The base string URI from `Section 3.4.1.2`_, after being encoded
+    #     (`Section 3.6`_).
+    #
+    # .. _`Section 3.4.1.2`: https://tools.ietf.org/html/rfc5849#section-3.4.1.2
+    # .. _`Section 3.6`: https://tools.ietf.org/html/rfc5849#section-3.6
+    base_string += utils.escape(base_str_uri)
+
+    # 4.  An "&" character (ASCII code 38).
+    base_string += '&'
+
+    # 5.  The request parameters as normalized in `Section 3.4.1.3.2`_, after
+    #     being encoded (`Section 3.6`).
+    #
+    # .. _`Sec 3.4.1.3.2`: https://tools.ietf.org/html/rfc5849#section-3.4.1.3.2
+    # .. _`Section 3.6`: https://tools.ietf.org/html/rfc5849#section-3.6
+    base_string += utils.escape(normalized_encoded_request_parameters)
+
+    return base_string
+
+
+def base_string_uri(uri: str, host: str = None) -> str:
+    """
+    Calculates the _base string URI_.
+
+    The *base string URI* is one of the components that make up the
+     *signature base string*.
+
+    The ``host`` is optional. If provided, it is used to override any host and
+    port values in the ``uri``. The value for ``host`` is usually extracted from
+    the "Host" request header from the HTTP request. Its value may be just the
+    hostname, or the hostname followed by a colon and a TCP/IP port number
+    (hostname:port). If a value for the``host`` is provided but it does not
+    contain a port number, the default port number is used (i.e. if the ``uri``
+    contained a port number, it will be discarded).
+
+    The rules for calculating the *base string URI* are defined in
+    section 3.4.1.2`_ of RFC 5849.
+
+    .. _`section 3.4.1.2`: https://tools.ietf.org/html/rfc5849#section-3.4.1.2
+
+    :param uri: URI
+    :param host: hostname with optional port number, separated by a colon
+    :return: base string URI
+    """
+
+    if not isinstance(uri, str):
+        raise ValueError('uri must be a string.')
+
+    # FIXME: urlparse does not support unicode
+    output = urlparse.urlparse(uri)
+    scheme = output.scheme
+    hostname = output.hostname
+    port = output.port
+    path = output.path
+    params = output.params
+
+    # The scheme, authority, and path of the request resource URI `RFC3986`
+    # are included by constructing an "http" or "https" URI representing
+    # the request resource (without the query or fragment) as follows:
+    #
+    # .. _`RFC3986`: https://tools.ietf.org/html/rfc3986
+
+    if not scheme:
+        raise ValueError('missing scheme')
+
+    # Per `RFC 2616 section 5.1.2`_:
+    #
+    # Note that the absolute path cannot be empty; if none is present in
+    # the original URI, it MUST be given as "/" (the server root).
+    #
+    # .. _`RFC 2616 5.1.2`: https://tools.ietf.org/html/rfc2616#section-5.1.2
+    if not path:
+        path = '/'
+
+    # 1.  The scheme and host MUST be in lowercase.
+    scheme = scheme.lower()
+    # Note: if ``host`` is used, it will be converted to lowercase below
+    if hostname is not None:
+        hostname = hostname.lower()
+
+    # 2.  The host and port values MUST match the content of the HTTP
+    #     request "Host" header field.
+    if host is not None:
+        # NOTE: override value in uri with provided host
+        # Host argument is equal to netloc. It means it's missing scheme.
+        # Add it back, before parsing.
+
+        host = host.lower()
+        host = f"{scheme}://{host}"
+        output = urlparse.urlparse(host)
+        hostname = output.hostname
+        port = output.port
+
+    # 3.  The port MUST be included if it is not the default port for the
+    #     scheme, and MUST be excluded if it is the default.  Specifically,
+    #     the port MUST be excluded when making an HTTP request `RFC2616`_
+    #     to port 80 or when making an HTTPS request `RFC2818`_ to port 443.
+    #     All other non-default port numbers MUST be included.
+    #
+    # .. _`RFC2616`: https://tools.ietf.org/html/rfc2616
+    # .. _`RFC2818`: https://tools.ietf.org/html/rfc2818
+
+    if hostname is None:
+        raise ValueError('missing host')
+
+    # NOTE: Try guessing if we're dealing with IP or hostname
+    try:
+        hostname = ipaddress.ip_address(hostname)
+    except ValueError:
+        pass
+
+    if isinstance(hostname, ipaddress.IPv6Address):
+        hostname = f"[{hostname}]"
+    elif isinstance(hostname, ipaddress.IPv4Address):
+        hostname = f"{hostname}"
+
+    if port is not None and not (0 < port <= 65535):
+        raise ValueError('port out of range')  # 16-bit unsigned ints
+    if (scheme, port) in (('http', 80), ('https', 443)):
+        netloc = hostname  # default port for scheme: exclude port num
+    elif port:
+        netloc = f"{hostname}:{port}"  # use hostname:port
+    else:
+        netloc = hostname
+
+    v = urlparse.urlunparse((scheme, netloc, path, params, '', ''))
+
+    # RFC 5849 does not specify which characters are encoded in the
+    # "base string URI", nor how they are encoded - which is very bad, since
+    # the signatures won't match if there are any differences. Fortunately,
+    # most URIs only use characters that are clearly not encoded (e.g. digits
+    # and A-Z, a-z), so have avoided any differences between implementations.
+    #
+    # The example from its section 3.4.1.2 illustrates that spaces in
+    # the path are percent encoded. But it provides no guidance as to what other
+    # characters (if any) must be encoded (nor how); nor if characters in the
+    # other components are to be encoded or not.
+    #
+    # This implementation **assumes** that **only** the space is percent-encoded
+    # and it is done to the entire value (not just to spaces in the path).
+    #
+    # This code may need to be changed if it is discovered that other characters
+    # are expected to be encoded.
+    #
+    # Note: the "base string URI" returned by this function will be encoded
+    # again before being concatenated into the "signature base string". So any
+    # spaces in the URI will actually appear in the "signature base string"
+    # as "%2520" (the "%20" further encoded according to section 3.6).
+
+    return v.replace(' ', '%20')
+
+
+def collect_parameters(uri_query='', body=None, headers=None,
+                       exclude_oauth_signature=True, with_realm=False):
+    """
+    Gather the request parameters from all the parameter sources.
+
+    This function is used to extract all the parameters, which are then passed
+    to ``normalize_parameters`` to produce one of the components that make up
+    the *signature base string*.
+
+    Parameters starting with `oauth_` will be unescaped.
+
+    Body parameters must be supplied as a dict, a list of 2-tuples, or a
+    form encoded query string.
+
+    Headers must be supplied as a dict.
+
+    The rules where the parameters must be sourced from are defined in
+    `section 3.4.1.3.1`_ of RFC 5849.
+
+    .. _`Sec 3.4.1.3.1`: https://tools.ietf.org/html/rfc5849#section-3.4.1.3.1
+    """
+    if body is None:
+        body = []
+    headers = headers or {}
+    params = []
+
+    # The parameters from the following sources are collected into a single
+    # list of name/value pairs:
+
+    # *  The query component of the HTTP request URI as defined by
+    #    `RFC3986, Section 3.4`_.  The query component is parsed into a list
+    #    of name/value pairs by treating it as an
+    #    "application/x-www-form-urlencoded" string, separating the names
+    #    and values and decoding them as defined by W3C.REC-html40-19980424
+    #    `W3C-HTML-4.0`_, Section 17.13.4.
+    #
+    # .. _`RFC3986, Sec 3.4`: https://tools.ietf.org/html/rfc3986#section-3.4
+    # .. _`W3C-HTML-4.0`: https://www.w3.org/TR/1998/REC-html40-19980424/
+    if uri_query:
+        params.extend(urldecode(uri_query))
+
+    # *  The OAuth HTTP "Authorization" header field (`Section 3.5.1`_) if
+    #    present.  The header's content is parsed into a list of name/value
+    #    pairs excluding the "realm" parameter if present.  The parameter
+    #    values are decoded as defined by `Section 3.5.1`_.
+    #
+    # .. _`Section 3.5.1`: https://tools.ietf.org/html/rfc5849#section-3.5.1
+    if headers:
+        headers_lower = {k.lower(): v for k, v in headers.items()}
+        authorization_header = headers_lower.get('authorization')
+        if authorization_header is not None:
+            params.extend([i for i in utils.parse_authorization_header(
+                authorization_header) if with_realm or i[0] != 'realm'])
+
+    # *  The HTTP request entity-body, but only if all of the following
+    #    conditions are met:
+    #     *  The entity-body is single-part.
+    #
+    #     *  The entity-body follows the encoding requirements of the
+    #        "application/x-www-form-urlencoded" content-type as defined by
+    #        W3C.REC-html40-19980424 `W3C-HTML-4.0`_.
+
+    #     *  The HTTP request entity-header includes the "Content-Type"
+    #        header field set to "application/x-www-form-urlencoded".
+    #
+    # .. _`W3C-HTML-4.0`: https://www.w3.org/TR/1998/REC-html40-19980424/
+
+    # TODO: enforce header param inclusion conditions
+    bodyparams = extract_params(body) or []
+    params.extend(bodyparams)
+
+    # ensure all oauth params are unescaped
+    unescaped_params = []
+    for k, v in params:
+        if k.startswith('oauth_'):
+            v = utils.unescape(v)
+        unescaped_params.append((k, v))
+
+    # The "oauth_signature" parameter MUST be excluded from the signature
+    # base string if present.
+    if exclude_oauth_signature:
+        unescaped_params = list(filter(lambda i: i[0] != 'oauth_signature',
+                                       unescaped_params))
+
+    return unescaped_params
+
+
+def normalize_parameters(params) -> str:
+    """
+    Calculate the normalized request parameters.
+
+    The *normalized request parameters* is one of the components that make up
+    the *signature base string*.
+
+    The rules for parameter normalization are defined in `section 3.4.1.3.2`_ of
+    RFC 5849.
+
+    .. _`Sec 3.4.1.3.2`: https://tools.ietf.org/html/rfc5849#section-3.4.1.3.2
+    """
+
+    # The parameters collected in `Section 3.4.1.3`_ are normalized into a
+    # single string as follows:
+    #
+    # .. _`Section 3.4.1.3`: https://tools.ietf.org/html/rfc5849#section-3.4.1.3
+
+    # 1.  First, the name and value of each parameter are encoded
+    #     (`Section 3.6`_).
+    #
+    # .. _`Section 3.6`: https://tools.ietf.org/html/rfc5849#section-3.6
+    key_values = [(utils.escape(k), utils.escape(v)) for k, v in params]
+
+    # 2.  The parameters are sorted by name, using ascending byte value
+    #     ordering.  If two or more parameters share the same name, they
+    #     are sorted by their value.
+    key_values.sort()
+
+    # 3.  The name of each parameter is concatenated to its corresponding
+    #     value using an "=" character (ASCII code 61) as a separator, even
+    #     if the value is empty.
+    parameter_parts = ['{}={}'.format(k, v) for k, v in key_values]
+
+    # 4.  The sorted name/value pairs are concatenated together into a
+    #     single string by using an "&" character (ASCII code 38) as
+    #     separator.
+    return '&'.join(parameter_parts)
+
+
+# ==== Common functions for HMAC-based signature methods =========
+
+def _sign_hmac(hash_algorithm_name: str,
+               sig_base_str: str,
+               client_secret: str,
+               resource_owner_secret: str):
+    """
+    **HMAC-SHA256**
+
+    The "HMAC-SHA256" signature method uses the HMAC-SHA256 signature
+    algorithm as defined in `RFC4634`_::
+
+        digest = HMAC-SHA256 (key, text)
+
+    Per `section 3.4.2`_ of the spec.
+
+    .. _`RFC4634`: https://tools.ietf.org/html/rfc4634
+    .. _`section 3.4.2`: https://tools.ietf.org/html/rfc5849#section-3.4.2
+    """
+
+    # The HMAC-SHA256 function variables are used in following way:
+
+    # text is set to the value of the signature base string from
+    # `Section 3.4.1.1`_.
+    #
+    # .. _`Section 3.4.1.1`: https://tools.ietf.org/html/rfc5849#section-3.4.1.1
+    text = sig_base_str
+
+    # key is set to the concatenated values of:
+    # 1.  The client shared-secret, after being encoded (`Section 3.6`_).
+    #
+    # .. _`Section 3.6`: https://tools.ietf.org/html/rfc5849#section-3.6
+    key = utils.escape(client_secret or '')
+
+    # 2.  An "&" character (ASCII code 38), which MUST be included
+    #     even when either secret is empty.
+    key += '&'
+
+    # 3.  The token shared-secret, after being encoded (`Section 3.6`_).
+    #
+    # .. _`Section 3.6`: https://tools.ietf.org/html/rfc5849#section-3.6
+    key += utils.escape(resource_owner_secret or '')
+
+    # Get the hashing algorithm to use
+
+    m = {
+        'SHA-1': hashlib.sha1,
+        'SHA-256': hashlib.sha256,
+        'SHA-512': hashlib.sha512,
+    }
+    hash_alg = m[hash_algorithm_name]
+
+    # Calculate the signature
+
+    # FIXME: HMAC does not support unicode!
+    key_utf8 = key.encode('utf-8')
+    text_utf8 = text.encode('utf-8')
+    signature = hmac.new(key_utf8, text_utf8, hash_alg)
+
+    # digest  is used to set the value of the "oauth_signature" protocol
+    #         parameter, after the result octet string is base64-encoded
+    #         per `RFC2045, Section 6.8`.
+    #
+    # .. _`RFC2045, Sec 6.8`: https://tools.ietf.org/html/rfc2045#section-6.8
+    return binascii.b2a_base64(signature.digest())[:-1].decode('utf-8')
+
+
+def _verify_hmac(hash_algorithm_name: str,
+                 request,
+                 client_secret=None,
+                 resource_owner_secret=None):
+    """Verify a HMAC-SHA1 signature.
+
+    Per `section 3.4`_ of the spec.
+
+    .. _`section 3.4`: https://tools.ietf.org/html/rfc5849#section-3.4
+
+    To satisfy `RFC2616 section 5.2`_ item 1, the request argument's uri
+    attribute MUST be an absolute URI whose netloc part identifies the
+    origin server or gateway on which the resource resides. Any Host
+    item of the request argument's headers dict attribute will be
+    ignored.
+
+    .. _`RFC2616 section 5.2`: https://tools.ietf.org/html/rfc2616#section-5.2
+
+    """
+    norm_params = normalize_parameters(request.params)
+    bs_uri = base_string_uri(request.uri)
+    sig_base_str = signature_base_string(request.http_method, bs_uri,
+                                         norm_params)
+    signature = _sign_hmac(hash_algorithm_name, sig_base_str,
+                           client_secret, resource_owner_secret)
+    match = safe_string_equals(signature, request.signature)
+    if not match:
+        log.debug('Verify HMAC failed: signature base string: %s', sig_base_str)
+    return match
+
+
+# ==== HMAC-SHA1 =================================================
+
+def sign_hmac_sha1_with_client(sig_base_str, client):
+    return _sign_hmac('SHA-1', sig_base_str,
+                      client.client_secret, client.resource_owner_secret)
+
+
+def verify_hmac_sha1(request, client_secret=None, resource_owner_secret=None):
+    return _verify_hmac('SHA-1', request, client_secret, resource_owner_secret)
+
+
+def sign_hmac_sha1(base_string, client_secret, resource_owner_secret):
+    """
+    Deprecated function for calculating a HMAC-SHA1 signature.
+
+    This function has been replaced by invoking ``sign_hmac`` with "SHA-1"
+    as the hash algorithm name.
+
+    This function was invoked by sign_hmac_sha1_with_client and
+    test_signatures.py, but does any application invoke it directly? If not,
+    it can be removed.
+    """
+    warnings.warn('use sign_hmac_sha1_with_client instead of sign_hmac_sha1',
+                  DeprecationWarning)
+
+    # For some unknown reason, the original implementation assumed base_string
+    # could either be bytes or str. The signature base string calculating
+    # function always returned a str, so the new ``sign_rsa`` only expects that.
+
+    base_string = base_string.decode('ascii') \
+        if isinstance(base_string, bytes) else base_string
+
+    return _sign_hmac('SHA-1', base_string,
+                      client_secret, resource_owner_secret)
+
+
+# ==== HMAC-SHA256 ===============================================
+
+def sign_hmac_sha256_with_client(sig_base_str, client):
+    return _sign_hmac('SHA-256', sig_base_str,
+                      client.client_secret, client.resource_owner_secret)
+
+
+def verify_hmac_sha256(request, client_secret=None, resource_owner_secret=None):
+    return _verify_hmac('SHA-256', request,
+                        client_secret, resource_owner_secret)
+
+
+def sign_hmac_sha256(base_string, client_secret, resource_owner_secret):
+    """
+    Deprecated function for calculating a HMAC-SHA256 signature.
+
+    This function has been replaced by invoking ``sign_hmac`` with "SHA-256"
+    as the hash algorithm name.
+
+    This function was invoked by sign_hmac_sha256_with_client and
+    test_signatures.py, but does any application invoke it directly? If not,
+    it can be removed.
+    """
+    warnings.warn(
+        'use sign_hmac_sha256_with_client instead of sign_hmac_sha256',
+        DeprecationWarning)
+
+    # For some unknown reason, the original implementation assumed base_string
+    # could either be bytes or str. The signature base string calculating
+    # function always returned a str, so the new ``sign_rsa`` only expects that.
+
+    base_string = base_string.decode('ascii') \
+        if isinstance(base_string, bytes) else base_string
+
+    return _sign_hmac('SHA-256', base_string,
+                      client_secret, resource_owner_secret)
+
+
+# ==== HMAC-SHA512 ===============================================
+
+def sign_hmac_sha512_with_client(sig_base_str: str,
+                                 client):
+    return _sign_hmac('SHA-512', sig_base_str,
+                      client.client_secret, client.resource_owner_secret)
+
+
+def verify_hmac_sha512(request,
+                       client_secret: str = None,
+                       resource_owner_secret: str = None):
+    return _verify_hmac('SHA-512', request,
+                        client_secret, resource_owner_secret)
+
+
+# ==== Common functions for RSA-based signature methods ==========
+
+_jwt_rsa = {}  # cache of RSA-hash implementations from PyJWT jwt.algorithms
+
+
+def _get_jwt_rsa_algorithm(hash_algorithm_name: str):
+    """
+    Obtains an RSAAlgorithm object that implements RSA with the hash algorithm.
+
+    This method maintains the ``_jwt_rsa`` cache.
+
+    Returns a jwt.algorithm.RSAAlgorithm.
+    """
+    if hash_algorithm_name in _jwt_rsa:
+        # Found in cache: return it
+        return _jwt_rsa[hash_algorithm_name]
+    else:
+        # Not in cache: instantiate a new RSAAlgorithm
+
+        # PyJWT has some nice pycrypto/cryptography abstractions
+        import jwt.algorithms as jwt_algorithms
+        m = {
+            'SHA-1': jwt_algorithms.hashes.SHA1,
+            'SHA-256': jwt_algorithms.hashes.SHA256,
+            'SHA-512': jwt_algorithms.hashes.SHA512,
+        }
+        v = jwt_algorithms.RSAAlgorithm(m[hash_algorithm_name])
+
+        _jwt_rsa[hash_algorithm_name] = v  # populate cache
+
+        return v
+
+
+def _prepare_key_plus(alg, keystr):
+    """
+    Prepare a PEM encoded key (public or private), by invoking the `prepare_key`
+    method on alg with the keystr.
+
+    The keystr should be a string or bytes.  If the keystr is bytes, it is
+    decoded as UTF-8 before being passed to prepare_key. Otherwise, it
+    is passed directly.
+    """
+    if isinstance(keystr, bytes):
+        keystr = keystr.decode('utf-8')
+    return alg.prepare_key(keystr)
+
+
+def _sign_rsa(hash_algorithm_name: str,
+              sig_base_str: str,
+              rsa_private_key: str):
+    """
+    Calculate the signature for an RSA-based signature method.
+
+    The ``alg`` is used to calculate the digest over the signature base string.
+    For the "RSA_SHA1" signature method, the alg must be SHA-1. While OAuth 1.0a
+    only defines the RSA-SHA1 signature method, this function can be used for
+    other non-standard signature methods that only differ from RSA-SHA1 by the
+    digest algorithm.
+
+    Signing for the RSA-SHA1 signature method is defined in
+    `section 3.4.3`_ of RFC 5849.
+
+    The RSASSA-PKCS1-v1_5 signature algorithm used defined by
+    `RFC3447, Section 8.2`_ (also known as PKCS#1), with the `alg` as the
+    hash function for EMSA-PKCS1-v1_5.  To
+    use this method, the client MUST have established client credentials
+    with the server that included its RSA public key (in a manner that is
+    beyond the scope of this specification).
+
+    .. _`section 3.4.3`: https://tools.ietf.org/html/rfc5849#section-3.4.3
+    .. _`RFC3447, Section 8.2`: https://tools.ietf.org/html/rfc3447#section-8.2
+    """
+
+    # Get the implementation of RSA-hash
+
+    alg = _get_jwt_rsa_algorithm(hash_algorithm_name)
+
+    # Check private key
+
+    if not rsa_private_key:
+        raise ValueError('rsa_private_key required for RSA with ' +
+                         alg.hash_alg.name + ' signature method')
+
+    # Convert the "signature base string" into a sequence of bytes (M)
+    #
+    # The signature base string, by definition, only contain printable US-ASCII
+    # characters. So encoding it as 'ascii' will always work. It will raise a
+    # ``UnicodeError`` if it can't encode the value, which will never happen
+    # if the signature base string was created correctly. Therefore, using
+    # 'ascii' encoding provides an extra level of error checking.
+
+    m = sig_base_str.encode('ascii')
+
+    # Perform signing: S = RSASSA-PKCS1-V1_5-SIGN (K, M)
+
+    key = _prepare_key_plus(alg, rsa_private_key)
+    s = alg.sign(m, key)
+
+    # base64-encoded per RFC2045 section 6.8.
+    #
+    # 1. While b2a_base64 implements base64 defined by RFC 3548. As used here,
+    #    it is the same as base64 defined by RFC 2045.
+    # 2. b2a_base64 includes a "\n" at the end of its result ([:-1] removes it)
+    # 3. b2a_base64 produces a binary string. Use decode to produce a str.
+    #    It should only contain only printable US-ASCII characters.
+
+    return binascii.b2a_base64(s)[:-1].decode('ascii')
+
+
+def _verify_rsa(hash_algorithm_name: str,
+                request,
+                rsa_public_key: str):
+    """
+    Verify a base64 encoded signature for a RSA-based signature method.
+
+    The ``alg`` is used to calculate the digest over the signature base string.
+    For the "RSA_SHA1" signature method, the alg must be SHA-1. While OAuth 1.0a
+    only defines the RSA-SHA1 signature method, this function can be used for
+    other non-standard signature methods that only differ from RSA-SHA1 by the
+    digest algorithm.
+
+    Verification for the RSA-SHA1 signature method is defined in
+    `section 3.4.3`_ of RFC 5849.
+
+    .. _`section 3.4.3`: https://tools.ietf.org/html/rfc5849#section-3.4.3
+
+        To satisfy `RFC2616 section 5.2`_ item 1, the request argument's uri
+        attribute MUST be an absolute URI whose netloc part identifies the
+        origin server or gateway on which the resource resides. Any Host
+        item of the request argument's headers dict attribute will be
+        ignored.
+
+        .. _`RFC2616 Sec 5.2`: https://tools.ietf.org/html/rfc2616#section-5.2
+    """
+
+    try:
+        # Calculate the *signature base string* of the actual received request
+
+        norm_params = normalize_parameters(request.params)
+        bs_uri = base_string_uri(request.uri)
+        sig_base_str = signature_base_string(
+            request.http_method, bs_uri, norm_params)
+
+        # Obtain the signature that was received in the request
+
+        sig = binascii.a2b_base64(request.signature.encode('ascii'))
+
+        # Get the implementation of RSA-with-hash algorithm to use
+
+        alg = _get_jwt_rsa_algorithm(hash_algorithm_name)
+
+        # Verify the received signature was produced by the private key
+        # corresponding to the `rsa_public_key`, signing exact same
+        # *signature base string*.
+        #
+        #     RSASSA-PKCS1-V1_5-VERIFY ((n, e), M, S)
+
+        key = _prepare_key_plus(alg, rsa_public_key)
+
+        # The signature base string only contain printable US-ASCII characters.
+        # The ``encode`` method with the default "strict" error handling will
+        # raise a ``UnicodeError`` if it can't encode the value. So using
+        # "ascii" will always work.
+
+        verify_ok = alg.verify(sig_base_str.encode('ascii'), key, sig)
+
+        if not verify_ok:
+            log.debug('Verify failed: RSA with ' + alg.hash_alg.name +
+                      ': signature base string=%s' + sig_base_str)
+        return verify_ok
+
+    except UnicodeError:
+        # A properly encoded signature will only contain printable US-ASCII
+        # characters. The ``encode`` method with the default "strict" error
+        # handling will raise a ``UnicodeError`` if it can't decode the value.
+        # So using "ascii" will work with all valid signatures. But an
+        # incorrectly or maliciously produced signature could contain other
+        # bytes.
+        #
+        # This implementation treats that situation as equivalent to the
+        # signature verification having failed.
+        #
+        # Note: simply changing the encode to use 'utf-8' will not remove this
+        # case, since an incorrect or malicious request can contain bytes which
+        # are invalid as UTF-8.
+        return False
+
+
+# ==== RSA-SHA1 ==================================================
+
+def sign_rsa_sha1_with_client(sig_base_str, client):
+    # For some reason, this function originally accepts both str and bytes.
+    # This behaviour is preserved here. But won't be done for the newer
+    # sign_rsa_sha256_with_client and sign_rsa_sha512_with_client functions,
+    # which will only accept strings. The function to calculate a
+    # "signature base string" always produces a string, so it is not clear
+    # why support for bytes would ever be needed.
+    sig_base_str = sig_base_str.decode('ascii')\
+        if isinstance(sig_base_str, bytes) else sig_base_str
+
+    return _sign_rsa('SHA-1', sig_base_str, client.rsa_key)
+
+
+def verify_rsa_sha1(request, rsa_public_key: str):
+    return _verify_rsa('SHA-1', request, rsa_public_key)
+
+
+def sign_rsa_sha1(base_string, rsa_private_key):
+    """
+    Deprecated function for calculating a RSA-SHA1 signature.
+
+    This function has been replaced by invoking ``sign_rsa`` with "SHA-1"
+    as the hash algorithm name.
+
+    This function was invoked by sign_rsa_sha1_with_client and
+    test_signatures.py, but does any application invoke it directly? If not,
+    it can be removed.
+    """
+    warnings.warn('use _sign_rsa("SHA-1", ...) instead of sign_rsa_sha1',
+                  DeprecationWarning)
+
+    if isinstance(base_string, bytes):
+        base_string = base_string.decode('ascii')
+
+    return _sign_rsa('SHA-1', base_string, rsa_private_key)
+
+
+# ==== RSA-SHA256 ================================================
+
+def sign_rsa_sha256_with_client(sig_base_str: str, client):
+    return _sign_rsa('SHA-256', sig_base_str, client.rsa_key)
+
+
+def verify_rsa_sha256(request, rsa_public_key: str):
+    return _verify_rsa('SHA-256', request, rsa_public_key)
+
+
+# ==== RSA-SHA512 ================================================
+
+def sign_rsa_sha512_with_client(sig_base_str: str, client):
+    return _sign_rsa('SHA-512', sig_base_str, client.rsa_key)
+
+
+def verify_rsa_sha512(request, rsa_public_key: str):
+    return _verify_rsa('SHA-512', request, rsa_public_key)
+
+
+# ==== PLAINTEXT =================================================
+
+def sign_plaintext_with_client(_signature_base_string, client):
+    # _signature_base_string is not used because the signature with PLAINTEXT
+    # is just the secret: it isn't a real signature.
+    return sign_plaintext(client.client_secret, client.resource_owner_secret)
+
+
+def sign_plaintext(client_secret, resource_owner_secret):
+    """Sign a request using plaintext.
+
+    Per `section 3.4.4`_ of the spec.
+
+    The "PLAINTEXT" method does not employ a signature algorithm.  It
+    MUST be used with a transport-layer mechanism such as TLS or SSL (or
+    sent over a secure channel with equivalent protections).  It does not
+    utilize the signature base string or the "oauth_timestamp" and
+    "oauth_nonce" parameters.
+
+    .. _`section 3.4.4`: https://tools.ietf.org/html/rfc5849#section-3.4.4
+
+    """
+
+    # The "oauth_signature" protocol parameter is set to the concatenated
+    # value of:
+
+    # 1.  The client shared-secret, after being encoded (`Section 3.6`_).
+    #
+    # .. _`Section 3.6`: https://tools.ietf.org/html/rfc5849#section-3.6
+    signature = utils.escape(client_secret or '')
+
+    # 2.  An "&" character (ASCII code 38), which MUST be included even
+    #     when either secret is empty.
+    signature += '&'
+
+    # 3.  The token shared-secret, after being encoded (`Section 3.6`_).
+    #
+    # .. _`Section 3.6`: https://tools.ietf.org/html/rfc5849#section-3.6
+    signature += utils.escape(resource_owner_secret or '')
+
+    return signature
+
+
+def verify_plaintext(request, client_secret=None, resource_owner_secret=None):
+    """Verify a PLAINTEXT signature.
+
+    Per `section 3.4`_ of the spec.
+
+    .. _`section 3.4`: https://tools.ietf.org/html/rfc5849#section-3.4
+    """
+    signature = sign_plaintext(client_secret, resource_owner_secret)
+    match = safe_string_equals(signature, request.signature)
+    if not match:
+        log.debug('Verify PLAINTEXT failed')
+    return match
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/utils.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/utils.py
new file mode 100644
index 00000000..8fb8302e
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth1/rfc5849/utils.py
@@ -0,0 +1,83 @@
+"""
+oauthlib.utils
+~~~~~~~~~~~~~~
+
+This module contains utility methods used by various parts of the OAuth
+spec.
+"""
+import urllib.request as urllib2
+
+from oauthlib.common import quote, unquote
+
+UNICODE_ASCII_CHARACTER_SET = ('abcdefghijklmnopqrstuvwxyz'
+                               'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+                               '0123456789')
+
+
+def filter_params(target):
+    """Decorator which filters params to remove non-oauth_* parameters
+
+    Assumes the decorated method takes a params dict or list of tuples as its
+    first argument.
+    """
+    def wrapper(params, *args, **kwargs):
+        params = filter_oauth_params(params)
+        return target(params, *args, **kwargs)
+
+    wrapper.__doc__ = target.__doc__
+    return wrapper
+
+
+def filter_oauth_params(params):
+    """Removes all non oauth parameters from a dict or a list of params."""
+    is_oauth = lambda kv: kv[0].startswith("oauth_")
+    if isinstance(params, dict):
+        return list(filter(is_oauth, list(params.items())))
+    else:
+        return list(filter(is_oauth, params))
+
+
+def escape(u):
+    """Escape a unicode string in an OAuth-compatible fashion.
+
+    Per `section 3.6`_ of the spec.
+
+    .. _`section 3.6`: https://tools.ietf.org/html/rfc5849#section-3.6
+
+    """
+    if not isinstance(u, str):
+        raise ValueError('Only unicode objects are escapable. ' +
+                         'Got {!r} of type {}.'.format(u, type(u)))
+    # Letters, digits, and the characters '_.-' are already treated as safe
+    # by urllib.quote(). We need to add '~' to fully support rfc5849.
+    return quote(u, safe=b'~')
+
+
+def unescape(u):
+    if not isinstance(u, str):
+        raise ValueError('Only unicode objects are unescapable.')
+    return unquote(u)
+
+
+def parse_keqv_list(l):
+    """A unicode-safe version of urllib2.parse_keqv_list"""
+    # With Python 2.6, parse_http_list handles unicode fine
+    return urllib2.parse_keqv_list(l)
+
+
+def parse_http_list(u):
+    """A unicode-safe version of urllib2.parse_http_list"""
+    # With Python 2.6, parse_http_list handles unicode fine
+    return urllib2.parse_http_list(u)
+
+
+def parse_authorization_header(authorization_header):
+    """Parse an OAuth authorization header into a list of 2-tuples"""
+    auth_scheme = 'OAuth '.lower()
+    if authorization_header[:len(auth_scheme)].lower().startswith(auth_scheme):
+        items = parse_http_list(authorization_header[len(auth_scheme):])
+        try:
+            return list(parse_keqv_list(items).items())
+        except (IndexError, ValueError):
+            pass
+    raise ValueError('Malformed authorization header')
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth2/__init__.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/__init__.py
new file mode 100644
index 00000000..deefb1af
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/__init__.py
@@ -0,0 +1,36 @@
+"""
+oauthlib.oauth2
+~~~~~~~~~~~~~~
+
+This module is a wrapper for the most recent implementation of OAuth 2.0 Client
+and Server classes.
+"""
+from .rfc6749.clients import (
+    BackendApplicationClient, Client, LegacyApplicationClient,
+    MobileApplicationClient, ServiceApplicationClient, WebApplicationClient,
+)
+from .rfc6749.endpoints import (
+    AuthorizationEndpoint, BackendApplicationServer, IntrospectEndpoint,
+    LegacyApplicationServer, MetadataEndpoint, MobileApplicationServer,
+    ResourceEndpoint, RevocationEndpoint, Server, TokenEndpoint,
+    WebApplicationServer,
+)
+from .rfc6749.errors import (
+    AccessDeniedError, FatalClientError, InsecureTransportError,
+    InvalidClientError, InvalidClientIdError, InvalidGrantError,
+    InvalidRedirectURIError, InvalidRequestError, InvalidRequestFatalError,
+    InvalidScopeError, MismatchingRedirectURIError, MismatchingStateError,
+    MissingClientIdError, MissingCodeError, MissingRedirectURIError,
+    MissingResponseTypeError, MissingTokenError, MissingTokenTypeError,
+    OAuth2Error, ServerError, TemporarilyUnavailableError, TokenExpiredError,
+    UnauthorizedClientError, UnsupportedGrantTypeError,
+    UnsupportedResponseTypeError, UnsupportedTokenTypeError,
+)
+from .rfc6749.grant_types import (
+    AuthorizationCodeGrant, ClientCredentialsGrant, ImplicitGrant,
+    RefreshTokenGrant, ResourceOwnerPasswordCredentialsGrant,
+)
+from .rfc6749.request_validator import RequestValidator
+from .rfc6749.tokens import BearerToken, OAuth2Token
+from .rfc6749.utils import is_secure_transport
+from .rfc8628.clients import DeviceClient
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/__init__.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/__init__.py
new file mode 100644
index 00000000..4b75a8a1
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/__init__.py
@@ -0,0 +1,16 @@
+"""
+oauthlib.oauth2.rfc6749
+~~~~~~~~~~~~~~~~~~~~~~~
+
+This module is an implementation of various logic needed
+for consuming and providing OAuth 2.0 RFC6749.
+"""
+import functools
+import logging
+
+from .endpoints.base import BaseEndpoint, catch_errors_and_unavailability
+from .errors import (
+    FatalClientError, OAuth2Error, ServerError, TemporarilyUnavailableError,
+)
+
+log = logging.getLogger(__name__)
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/clients/__init__.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/clients/__init__.py
new file mode 100644
index 00000000..8fc6c955
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/clients/__init__.py
@@ -0,0 +1,14 @@
+# -*- coding: utf-8 -*-
+"""
+oauthlib.oauth2.rfc6749
+~~~~~~~~~~~~~~~~~~~~~~~
+
+This module is an implementation of various logic needed
+for consuming OAuth 2.0 RFC6749.
+"""
+from .backend_application import BackendApplicationClient
+from .base import AUTH_HEADER, BODY, URI_QUERY, Client
+from .legacy_application import LegacyApplicationClient
+from .mobile_application import MobileApplicationClient
+from .service_application import ServiceApplicationClient
+from .web_application import WebApplicationClient
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/clients/backend_application.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/clients/backend_application.py
new file mode 100644
index 00000000..e11e8fae
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/clients/backend_application.py
@@ -0,0 +1,74 @@
+# -*- coding: utf-8 -*-
+"""
+oauthlib.oauth2.rfc6749
+~~~~~~~~~~~~~~~~~~~~~~~
+
+This module is an implementation of various logic needed
+for consuming and providing OAuth 2.0 RFC6749.
+"""
+from ..parameters import prepare_token_request
+from .base import Client
+
+
+class BackendApplicationClient(Client):
+
+    """A public client utilizing the client credentials grant workflow.
+
+    The client can request an access token using only its client
+    credentials (or other supported means of authentication) when the
+    client is requesting access to the protected resources under its
+    control, or those of another resource owner which has been previously
+    arranged with the authorization server (the method of which is beyond
+    the scope of this specification).
+
+    The client credentials grant type MUST only be used by confidential
+    clients.
+
+    Since the client authentication is used as the authorization grant,
+    no additional authorization request is needed.
+    """
+
+    grant_type = 'client_credentials'
+
+    def prepare_request_body(self, body='', scope=None,
+                             include_client_id=False, **kwargs):
+        """Add the client credentials to the request body.
+
+        The client makes a request to the token endpoint by adding the
+        following parameters using the "application/x-www-form-urlencoded"
+        format per `Appendix B`_ in the HTTP request entity-body:
+
+        :param body: Existing request body (URL encoded string) to embed parameters
+                     into. This may contain extra parameters. Default ''.
+        :param scope:   The scope of the access request as described by
+                        `Section 3.3`_.
+
+        :param include_client_id: `True` to send the `client_id` in the
+                                  body of the upstream request. This is required
+                                  if the client is not authenticating with the
+                                  authorization server as described in
+                                  `Section 3.2.1`_. False otherwise (default).
+        :type include_client_id: Boolean
+
+        :param kwargs:  Extra credentials to include in the token request.
+
+        The client MUST authenticate with the authorization server as
+        described in `Section 3.2.1`_.
+
+        The prepared body will include all provided credentials as well as
+        the ``grant_type`` parameter set to ``client_credentials``::
+
+            >>> from oauthlib.oauth2 import BackendApplicationClient
+            >>> client = BackendApplicationClient('your_id')
+            >>> client.prepare_request_body(scope=['hello', 'world'])
+            'grant_type=client_credentials&scope=hello+world'
+
+        .. _`Appendix B`: https://tools.ietf.org/html/rfc6749#appendix-B
+        .. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3
+        .. _`Section 3.2.1`: https://tools.ietf.org/html/rfc6749#section-3.2.1
+        """
+        kwargs['client_id'] = self.client_id
+        kwargs['include_client_id'] = include_client_id
+        scope = self.scope if scope is None else scope
+        return prepare_token_request(self.grant_type, body=body,
+                                     scope=scope, **kwargs)
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/clients/base.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/clients/base.py
new file mode 100644
index 00000000..d5eb0cc1
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/clients/base.py
@@ -0,0 +1,604 @@
+# -*- coding: utf-8 -*-
+"""
+oauthlib.oauth2.rfc6749
+~~~~~~~~~~~~~~~~~~~~~~~
+
+This module is an implementation of various logic needed
+for consuming OAuth 2.0 RFC6749.
+"""
+import base64
+import hashlib
+import re
+import secrets
+import time
+import warnings
+
+from oauthlib.common import generate_token
+from oauthlib.oauth2.rfc6749 import tokens
+from oauthlib.oauth2.rfc6749.errors import (
+    InsecureTransportError, TokenExpiredError,
+)
+from oauthlib.oauth2.rfc6749.parameters import (
+    parse_token_response, prepare_token_request,
+    prepare_token_revocation_request,
+)
+from oauthlib.oauth2.rfc6749.utils import is_secure_transport
+
+AUTH_HEADER = 'auth_header'
+URI_QUERY = 'query'
+BODY = 'body'
+
+FORM_ENC_HEADERS = {
+    'Content-Type': 'application/x-www-form-urlencoded'
+}
+
+
+class Client:
+    """Base OAuth2 client responsible for access token management.
+
+    This class also acts as a generic interface providing methods common to all
+    client types such as ``prepare_authorization_request`` and
+    ``prepare_token_revocation_request``. The ``prepare_x_request`` methods are
+    the recommended way of interacting with clients (as opposed to the abstract
+    prepare uri/body/etc methods). They are recommended over the older set
+    because they are easier to use (more consistent) and add a few additional
+    security checks, such as HTTPS and state checking.
+
+    Some of these methods require further implementation only provided by the
+    specific purpose clients such as
+    :py:class:`oauthlib.oauth2.MobileApplicationClient` and thus you should always
+    seek to use the client class matching the OAuth workflow you need. For
+    Python, this is usually :py:class:`oauthlib.oauth2.WebApplicationClient`.
+
+    """
+    refresh_token_key = 'refresh_token'
+
+    def __init__(self, client_id,
+                 default_token_placement=AUTH_HEADER,
+                 token_type='Bearer',
+                 access_token=None,
+                 refresh_token=None,
+                 mac_key=None,
+                 mac_algorithm=None,
+                 token=None,
+                 scope=None,
+                 state=None,
+                 redirect_url=None,
+                 state_generator=generate_token,
+                 code_verifier=None,
+                 code_challenge=None,
+                 code_challenge_method=None,
+                 **kwargs):
+        """Initialize a client with commonly used attributes.
+
+        :param client_id: Client identifier given by the OAuth provider upon
+        registration.
+
+        :param default_token_placement: Tokens can be supplied in the Authorization
+        header (default), the URL query component (``query``) or the request
+        body (``body``).
+
+        :param token_type: OAuth 2 token type. Defaults to Bearer. Change this
+        if you specify the ``access_token`` parameter and know it is of a
+        different token type, such as a MAC, JWT or SAML token. Can
+        also be supplied as ``token_type`` inside the ``token`` dict parameter.
+
+        :param access_token: An access token (string) used to authenticate
+        requests to protected resources. Can also be supplied inside the
+        ``token`` dict parameter.
+
+        :param refresh_token: A refresh token (string) used to refresh expired
+        tokens. Can also be supplied inside the ``token`` dict parameter.
+
+        :param mac_key: Encryption key used with MAC tokens.
+
+        :param mac_algorithm:  Hashing algorithm for MAC tokens.
+
+        :param token: A dict of token attributes such as ``access_token``,
+        ``token_type`` and ``expires_at``.
+
+        :param scope: A list of default scopes to request authorization for.
+
+        :param state: A CSRF protection string used during authorization.
+
+        :param redirect_url: The redirection endpoint on the client side to which
+        the user returns after authorization.
+
+        :param state_generator: A no argument state generation callable. Defaults
+        to :py:meth:`oauthlib.common.generate_token`.
+
+        :param code_verifier: PKCE parameter. A cryptographically random string that is used to correlate the
+        authorization request to the token request.
+
+        :param code_challenge: PKCE parameter. A challenge derived from the code verifier that is sent in the
+        authorization request, to be verified against later.
+
+        :param code_challenge_method: PKCE parameter. A method that was used to derive code challenge.
+        Defaults to "plain" if not present in the request.
+        """
+
+        self.client_id = client_id
+        self.default_token_placement = default_token_placement
+        self.token_type = token_type
+        self.access_token = access_token
+        self.refresh_token = refresh_token
+        self.mac_key = mac_key
+        self.mac_algorithm = mac_algorithm
+        self.token = token or {}
+        self.scope = scope
+        self.state_generator = state_generator
+        self.state = state
+        self.redirect_url = redirect_url
+        self.code_verifier = code_verifier
+        self.code_challenge = code_challenge
+        self.code_challenge_method = code_challenge_method
+        self.code = None
+        self.expires_in = None
+        self._expires_at = None
+        self.populate_token_attributes(self.token)
+
+    @property
+    def token_types(self):
+        """Supported token types and their respective methods
+
+        Additional tokens can be supported by extending this dictionary.
+
+        The Bearer token spec is stable and safe to use.
+
+        The MAC token spec is not yet stable and support for MAC tokens
+        is experimental and currently matching version 00 of the spec.
+        """
+        return {
+            'Bearer': self._add_bearer_token,
+            'MAC': self._add_mac_token
+        }
+
+    def prepare_request_uri(self, *args, **kwargs):
+        """Abstract method used to create request URIs."""
+        raise NotImplementedError("Must be implemented by inheriting classes.")
+
+    def prepare_request_body(self, *args, **kwargs):
+        """Abstract method used to create request bodies."""
+        raise NotImplementedError("Must be implemented by inheriting classes.")
+
+    def parse_request_uri_response(self, *args, **kwargs):
+        """Abstract method used to parse redirection responses."""
+        raise NotImplementedError("Must be implemented by inheriting classes.")
+
+    def add_token(self, uri, http_method='GET', body=None, headers=None,
+                  token_placement=None, **kwargs):
+        """Add token to the request uri, body or authorization header.
+
+        The access token type provides the client with the information
+        required to successfully utilize the access token to make a protected
+        resource request (along with type-specific attributes).  The client
+        MUST NOT use an access token if it does not understand the token
+        type.
+
+        For example, the "bearer" token type defined in
+        [`I-D.ietf-oauth-v2-bearer`_] is utilized by simply including the access
+        token string in the request:
+
+        .. code-block:: http
+
+            GET /resource/1 HTTP/1.1
+            Host: example.com
+            Authorization: Bearer mF_9.B5f-4.1JqM
+
+        while the "mac" token type defined in [`I-D.ietf-oauth-v2-http-mac`_] is
+        utilized by issuing a MAC key together with the access token which is
+        used to sign certain components of the HTTP requests:
+
+        .. code-block:: http
+
+            GET /resource/1 HTTP/1.1
+            Host: example.com
+            Authorization: MAC id="h480djs93hd8",
+                                nonce="274312:dj83hs9s",
+                                mac="kDZvddkndxvhGRXZhvuDjEWhGeE="
+
+        .. _`I-D.ietf-oauth-v2-bearer`: https://tools.ietf.org/html/rfc6749#section-12.2
+        .. _`I-D.ietf-oauth-v2-http-mac`: https://tools.ietf.org/html/rfc6749#section-12.2
+        """
+        if not is_secure_transport(uri):
+            raise InsecureTransportError()
+
+        token_placement = token_placement or self.default_token_placement
+
+        case_insensitive_token_types = {
+            k.lower(): v for k, v in self.token_types.items()}
+        if not self.token_type.lower() in case_insensitive_token_types:
+            raise ValueError("Unsupported token type: %s" % self.token_type)
+
+        if not (self.access_token or self.token.get('access_token')):
+            raise ValueError("Missing access token.")
+
+        if self._expires_at and self._expires_at < time.time():
+            raise TokenExpiredError()
+
+        return case_insensitive_token_types[self.token_type.lower()](uri, http_method, body,
+                                                                     headers, token_placement, **kwargs)
+
+    def prepare_authorization_request(self, authorization_url, state=None,
+                                      redirect_url=None, scope=None, **kwargs):
+        """Prepare the authorization request.
+
+        This is the first step in many OAuth flows in which the user is
+        redirected to a certain authorization URL. This method adds
+        required parameters to the authorization URL.
+
+        :param authorization_url: Provider authorization endpoint URL.
+        :param state: CSRF protection string. Will be automatically created if
+            not provided. The generated state is available via the ``state``
+            attribute. Clients should verify that the state is unchanged and
+            present in the authorization response. This verification is done
+            automatically if using the ``authorization_response`` parameter
+            with ``prepare_token_request``.
+        :param redirect_url: Redirect URL to which the user will be returned
+            after authorization. Must be provided unless previously setup with
+            the provider. If provided then it must also be provided in the
+            token request.
+        :param scope: List of scopes to request. Must be equal to
+            or a subset of the scopes granted when obtaining the refresh
+            token. If none is provided, the ones provided in the constructor are
+            used.
+        :param kwargs: Additional parameters to included in the request.
+        :returns: The prepared request tuple with (url, headers, body).
+        """
+        if not is_secure_transport(authorization_url):
+            raise InsecureTransportError()
+
+        self.state = state or self.state_generator()
+        self.redirect_url = redirect_url or self.redirect_url
+        # do not assign scope to self automatically anymore
+        scope = self.scope if scope is None else scope
+        auth_url = self.prepare_request_uri(
+            authorization_url, redirect_uri=self.redirect_url,
+            scope=scope, state=self.state, **kwargs)
+        return auth_url, FORM_ENC_HEADERS, ''
+
+    def prepare_token_request(self, token_url, authorization_response=None,
+                              redirect_url=None, state=None, body='', **kwargs):
+        """Prepare a token creation request.
+
+        Note that these requests usually require client authentication, either
+        by including client_id or a set of provider specific authentication
+        credentials.
+
+        :param token_url: Provider token creation endpoint URL.
+        :param authorization_response: The full redirection URL string, i.e.
+            the location to which the user was redirected after successful
+            authorization. Used to mine credentials needed to obtain a token
+            in this step, such as authorization code.
+        :param redirect_url: The redirect_url supplied with the authorization
+            request (if there was one).
+        :param state:
+        :param body: Existing request body (URL encoded string) to embed parameters
+                     into. This may contain extra parameters. Default ''.
+        :param kwargs: Additional parameters to included in the request.
+        :returns: The prepared request tuple with (url, headers, body).
+        """
+        if not is_secure_transport(token_url):
+            raise InsecureTransportError()
+
+        state = state or self.state
+        if authorization_response:
+            self.parse_request_uri_response(
+                authorization_response, state=state)
+        self.redirect_url = redirect_url or self.redirect_url
+        body = self.prepare_request_body(body=body,
+                                         redirect_uri=self.redirect_url, **kwargs)
+
+        return token_url, FORM_ENC_HEADERS, body
+
+    def prepare_refresh_token_request(self, token_url, refresh_token=None,
+                                      body='', scope=None, **kwargs):
+        """Prepare an access token refresh request.
+
+        Expired access tokens can be replaced by new access tokens without
+        going through the OAuth dance if the client obtained a refresh token.
+        This refresh token and authentication credentials can be used to
+        obtain a new access token, and possibly a new refresh token.
+
+        :param token_url: Provider token refresh endpoint URL.
+        :param refresh_token: Refresh token string.
+        :param body: Existing request body (URL encoded string) to embed parameters
+            into. This may contain extra parameters. Default ''.
+        :param scope: List of scopes to request. Must be equal to
+            or a subset of the scopes granted when obtaining the refresh
+            token. If none is provided, the ones provided in the constructor are
+            used.
+        :param kwargs: Additional parameters to included in the request.
+        :returns: The prepared request tuple with (url, headers, body).
+        """
+        if not is_secure_transport(token_url):
+            raise InsecureTransportError()
+
+        # do not assign scope to self automatically anymore
+        scope = self.scope if scope is None else scope
+        body = self.prepare_refresh_body(body=body,
+                                         refresh_token=refresh_token, scope=scope, **kwargs)
+        return token_url, FORM_ENC_HEADERS, body
+
+    def prepare_token_revocation_request(self, revocation_url, token,
+                                         token_type_hint="access_token", body='', callback=None, **kwargs):
+        """Prepare a token revocation request.
+
+        :param revocation_url: Provider token revocation endpoint URL.
+        :param token: The access or refresh token to be revoked (string).
+        :param token_type_hint: ``"access_token"`` (default) or
+            ``"refresh_token"``. This is optional and if you wish to not pass it you
+            must provide ``token_type_hint=None``.
+        :param body:
+        :param callback: A jsonp callback such as ``package.callback`` to be invoked
+            upon receiving the response. Not that it should not include a () suffix.
+        :param kwargs: Additional parameters to included in the request.
+        :returns: The prepared request tuple with (url, headers, body).
+
+        Note that JSONP request may use GET requests as the parameters will
+        be added to the request URL query as opposed to the request body.
+
+        An example of a revocation request
+
+        .. code-block:: http
+
+            POST /revoke HTTP/1.1
+            Host: server.example.com
+            Content-Type: application/x-www-form-urlencoded
+            Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
+
+            token=45ghiukldjahdnhzdauz&token_type_hint=refresh_token
+
+        An example of a jsonp revocation request
+
+        .. code-block:: http
+
+            GET /revoke?token=agabcdefddddafdd&callback=package.myCallback HTTP/1.1
+            Host: server.example.com
+            Content-Type: application/x-www-form-urlencoded
+            Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
+
+        and an error response
+
+        .. code-block:: javascript
+
+            package.myCallback({"error":"unsupported_token_type"});
+
+        Note that these requests usually require client credentials, client_id in
+        the case for public clients and provider specific authentication
+        credentials for confidential clients.
+        """
+        if not is_secure_transport(revocation_url):
+            raise InsecureTransportError()
+
+        return prepare_token_revocation_request(revocation_url, token,
+                                                token_type_hint=token_type_hint, body=body, callback=callback,
+                                                **kwargs)
+
+    def parse_request_body_response(self, body, scope=None, **kwargs):
+        """Parse the JSON response body.
+
+        If the access token request is valid and authorized, the
+        authorization server issues an access token as described in
+        `Section 5.1`_.  A refresh token SHOULD NOT be included.  If the request
+        failed client authentication or is invalid, the authorization server
+        returns an error response as described in `Section 5.2`_.
+
+        :param body: The response body from the token request.
+        :param scope: Scopes originally requested. If none is provided, the ones
+            provided in the constructor are used.
+        :return: Dictionary of token parameters.
+        :raises: Warning if scope has changed. :py:class:`oauthlib.oauth2.errors.OAuth2Error`
+            if response is invalid.
+
+        These response are json encoded and could easily be parsed without
+        the assistance of OAuthLib. However, there are a few subtle issues
+        to be aware of regarding the response which are helpfully addressed
+        through the raising of various errors.
+
+        A successful response should always contain
+
+        **access_token**
+                The access token issued by the authorization server. Often
+                a random string.
+
+        **token_type**
+            The type of the token issued as described in `Section 7.1`_.
+            Commonly ``Bearer``.
+
+        While it is not mandated it is recommended that the provider include
+
+        **expires_in**
+            The lifetime in seconds of the access token.  For
+            example, the value "3600" denotes that the access token will
+            expire in one hour from the time the response was generated.
+            If omitted, the authorization server SHOULD provide the
+            expiration time via other means or document the default value.
+
+         **scope**
+            Providers may supply this in all responses but are required to only
+            if it has changed since the authorization request.
+
+        .. _`Section 5.1`: https://tools.ietf.org/html/rfc6749#section-5.1
+        .. _`Section 5.2`: https://tools.ietf.org/html/rfc6749#section-5.2
+        .. _`Section 7.1`: https://tools.ietf.org/html/rfc6749#section-7.1
+        """
+        scope = self.scope if scope is None else scope
+        self.token = parse_token_response(body, scope=scope)
+        self.populate_token_attributes(self.token)
+        return self.token
+
+    def prepare_refresh_body(self, body='', refresh_token=None, scope=None, **kwargs):
+        """Prepare an access token request, using a refresh token.
+
+        If the authorization server issued a refresh token to the client, the
+        client makes a refresh request to the token endpoint by adding the
+        following parameters using the `application/x-www-form-urlencoded`
+        format in the HTTP request entity-body:
+
+        :param refresh_token: REQUIRED.  The refresh token issued to the client.
+        :param scope:  OPTIONAL.  The scope of the access request as described by
+            Section 3.3.  The requested scope MUST NOT include any scope
+            not originally granted by the resource owner, and if omitted is
+            treated as equal to the scope originally granted by the
+            resource owner. Note that if none is provided, the ones provided
+            in the constructor are used if any.
+        """
+        refresh_token = refresh_token or self.refresh_token
+        scope = self.scope if scope is None else scope
+        return prepare_token_request(self.refresh_token_key, body=body, scope=scope,
+                                     refresh_token=refresh_token, **kwargs)
+
+    def _add_bearer_token(self, uri, http_method='GET', body=None,
+                          headers=None, token_placement=None):
+        """Add a bearer token to the request uri, body or authorization header."""
+        if token_placement == AUTH_HEADER:
+            headers = tokens.prepare_bearer_headers(self.access_token, headers)
+
+        elif token_placement == URI_QUERY:
+            uri = tokens.prepare_bearer_uri(self.access_token, uri)
+
+        elif token_placement == BODY:
+            body = tokens.prepare_bearer_body(self.access_token, body)
+
+        else:
+            raise ValueError("Invalid token placement.")
+        return uri, headers, body
+
+    def create_code_verifier(self, length):
+        """Create PKCE **code_verifier** used in computing **code_challenge**. 
+        See `RFC7636 Section 4.1`_
+
+        :param length: REQUIRED. The length of the code_verifier.
+
+        The client first creates a code verifier, "code_verifier", for each
+        OAuth 2.0 [RFC6749] Authorization Request, in the following manner:
+
+        .. code-block:: text
+
+               code_verifier = high-entropy cryptographic random STRING using the
+               unreserved characters [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~"
+               from Section 2.3 of [RFC3986], with a minimum length of 43 characters
+               and a maximum length of 128 characters.
+
+        .. _`RFC7636 Section 4.1`: https://tools.ietf.org/html/rfc7636#section-4.1
+        """
+        code_verifier = None
+
+        if not length >= 43:
+            raise ValueError("Length must be greater than or equal to 43")
+
+        if not length <= 128:
+            raise ValueError("Length must be less than or equal to 128")
+
+        allowed_characters = re.compile('^[A-Zaa-z0-9-._~]')
+        code_verifier = secrets.token_urlsafe(length)
+
+        if not re.search(allowed_characters, code_verifier):
+            raise ValueError("code_verifier contains invalid characters")
+
+        self.code_verifier = code_verifier
+
+        return code_verifier
+
+    def create_code_challenge(self, code_verifier, code_challenge_method=None):
+        """Create PKCE **code_challenge** derived from the  **code_verifier**.
+        See `RFC7636 Section 4.2`_
+
+        :param code_verifier: REQUIRED. The **code_verifier** generated from `create_code_verifier()`.
+        :param code_challenge_method: OPTIONAL. The method used to derive the **code_challenge**. Acceptable values include `S256`. DEFAULT is `plain`.
+
+               The client then creates a code challenge derived from the code
+               verifier by using one of the following transformations on the code
+               verifier::
+
+                   plain
+                      code_challenge = code_verifier
+                   S256
+                      code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))
+
+               If the client is capable of using `S256`, it MUST use `S256`, as
+               `S256` is Mandatory To Implement (MTI) on the server.  Clients are
+               permitted to use `plain` only if they cannot support `S256` for some
+               technical reason and know via out-of-band configuration that the
+               server supports `plain`.
+
+               The plain transformation is for compatibility with existing
+               deployments and for constrained environments that can't use the S256 transformation.
+
+        .. _`RFC7636 Section 4.2`: https://tools.ietf.org/html/rfc7636#section-4.2
+        """
+        code_challenge = None
+
+        if code_verifier == None:
+            raise ValueError("Invalid code_verifier")
+
+        if code_challenge_method == None:
+            code_challenge_method = "plain"
+            self.code_challenge_method = code_challenge_method
+            code_challenge = code_verifier
+            self.code_challenge = code_challenge
+
+        if code_challenge_method == "S256":
+            h = hashlib.sha256()
+            h.update(code_verifier.encode(encoding='ascii'))
+            sha256_val = h.digest()
+            code_challenge = bytes.decode(base64.urlsafe_b64encode(sha256_val))
+            # replace '+' with '-', '/' with '_', and remove trailing '='
+            code_challenge = code_challenge.replace("+", "-").replace("/", "_").replace("=", "")
+            self.code_challenge = code_challenge
+
+        return code_challenge
+
+    def _add_mac_token(self, uri, http_method='GET', body=None,
+                       headers=None, token_placement=AUTH_HEADER, ext=None, **kwargs):
+        """Add a MAC token to the request authorization header.
+
+        Warning: MAC token support is experimental as the spec is not yet stable.
+        """
+        if token_placement != AUTH_HEADER:
+            raise ValueError("Invalid token placement.")
+
+        headers = tokens.prepare_mac_header(self.access_token, uri,
+                                            self.mac_key, http_method, headers=headers, body=body, ext=ext,
+                                            hash_algorithm=self.mac_algorithm, **kwargs)
+        return uri, headers, body
+
+    def _populate_attributes(self, response):
+        warnings.warn("Please switch to the public method "
+                      "populate_token_attributes.", DeprecationWarning)
+        return self.populate_token_attributes(response)
+
+    def populate_code_attributes(self, response):
+        """Add attributes from an auth code response to self."""
+
+        if 'code' in response:
+            self.code = response.get('code')
+
+    def populate_token_attributes(self, response):
+        """Add attributes from a token exchange response to self."""
+
+        if 'access_token' in response:
+            self.access_token = response.get('access_token')
+
+        if 'refresh_token' in response:
+            self.refresh_token = response.get('refresh_token')
+
+        if 'token_type' in response:
+            self.token_type = response.get('token_type')
+
+        if 'expires_in' in response:
+            self.expires_in = response.get('expires_in')
+            self._expires_at = time.time() + int(self.expires_in)
+
+        if 'expires_at' in response:
+            try:
+                self._expires_at = int(response.get('expires_at'))
+            except:
+                self._expires_at = None
+
+        if 'mac_key' in response:
+            self.mac_key = response.get('mac_key')
+
+        if 'mac_algorithm' in response:
+            self.mac_algorithm = response.get('mac_algorithm')
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/clients/legacy_application.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/clients/legacy_application.py
new file mode 100644
index 00000000..9920981d
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/clients/legacy_application.py
@@ -0,0 +1,84 @@
+# -*- coding: utf-8 -*-
+"""
+oauthlib.oauth2.rfc6749
+~~~~~~~~~~~~~~~~~~~~~~~
+
+This module is an implementation of various logic needed
+for consuming and providing OAuth 2.0 RFC6749.
+"""
+from ..parameters import prepare_token_request
+from .base import Client
+
+
+class LegacyApplicationClient(Client):
+
+    """A public client using the resource owner password and username directly.
+
+    The resource owner password credentials grant type is suitable in
+    cases where the resource owner has a trust relationship with the
+    client, such as the device operating system or a highly privileged
+    application.  The authorization server should take special care when
+    enabling this grant type, and only allow it when other flows are not
+    viable.
+
+    The grant type is suitable for clients capable of obtaining the
+    resource owner's credentials (username and password, typically using
+    an interactive form).  It is also used to migrate existing clients
+    using direct authentication schemes such as HTTP Basic or Digest
+    authentication to OAuth by converting the stored credentials to an
+    access token.
+
+    The method through which the client obtains the resource owner
+    credentials is beyond the scope of this specification.  The client
+    MUST discard the credentials once an access token has been obtained.
+    """
+
+    grant_type = 'password'
+
+    def __init__(self, client_id, **kwargs):
+        super().__init__(client_id, **kwargs)
+
+    def prepare_request_body(self, username, password, body='', scope=None,
+                             include_client_id=False, **kwargs):
+        """Add the resource owner password and username to the request body.
+
+        The client makes a request to the token endpoint by adding the
+        following parameters using the "application/x-www-form-urlencoded"
+        format per `Appendix B`_ in the HTTP request entity-body:
+
+        :param username:    The resource owner username.
+        :param password:    The resource owner password.
+        :param body: Existing request body (URL encoded string) to embed parameters
+                     into. This may contain extra parameters. Default ''.
+        :param scope:   The scope of the access request as described by
+                        `Section 3.3`_.
+        :param include_client_id: `True` to send the `client_id` in the
+                                  body of the upstream request. This is required
+                                  if the client is not authenticating with the
+                                  authorization server as described in
+                                  `Section 3.2.1`_. False otherwise (default).
+        :type include_client_id: Boolean
+        :param kwargs:  Extra credentials to include in the token request.
+
+        If the client type is confidential or the client was issued client
+        credentials (or assigned other authentication requirements), the
+        client MUST authenticate with the authorization server as described
+        in `Section 3.2.1`_.
+
+        The prepared body will include all provided credentials as well as
+        the ``grant_type`` parameter set to ``password``::
+
+            >>> from oauthlib.oauth2 import LegacyApplicationClient
+            >>> client = LegacyApplicationClient('your_id')
+            >>> client.prepare_request_body(username='foo', password='bar', scope=['hello', 'world'])
+            'grant_type=password&username=foo&scope=hello+world&password=bar'
+
+        .. _`Appendix B`: https://tools.ietf.org/html/rfc6749#appendix-B
+        .. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3
+        .. _`Section 3.2.1`: https://tools.ietf.org/html/rfc6749#section-3.2.1
+        """
+        kwargs['client_id'] = self.client_id
+        kwargs['include_client_id'] = include_client_id
+        scope = self.scope if scope is None else scope
+        return prepare_token_request(self.grant_type, body=body, username=username,
+                                     password=password, scope=scope, **kwargs)
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/clients/mobile_application.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/clients/mobile_application.py
new file mode 100644
index 00000000..b10b41ce
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/clients/mobile_application.py
@@ -0,0 +1,174 @@
+# -*- coding: utf-8 -*-
+"""
+oauthlib.oauth2.rfc6749
+~~~~~~~~~~~~~~~~~~~~~~~
+
+This module is an implementation of various logic needed
+for consuming and providing OAuth 2.0 RFC6749.
+"""
+from ..parameters import parse_implicit_response, prepare_grant_uri
+from .base import Client
+
+
+class MobileApplicationClient(Client):
+
+    """A public client utilizing the implicit code grant workflow.
+
+    A user-agent-based application is a public client in which the
+    client code is downloaded from a web server and executes within a
+    user-agent (e.g. web browser) on the device used by the resource
+    owner.  Protocol data and credentials are easily accessible (and
+    often visible) to the resource owner.  Since such applications
+    reside within the user-agent, they can make seamless use of the
+    user-agent capabilities when requesting authorization.
+
+    The implicit grant type is used to obtain access tokens (it does not
+    support the issuance of refresh tokens) and is optimized for public
+    clients known to operate a particular redirection URI.  These clients
+    are typically implemented in a browser using a scripting language
+    such as JavaScript.
+
+    As a redirection-based flow, the client must be capable of
+    interacting with the resource owner's user-agent (typically a web
+    browser) and capable of receiving incoming requests (via redirection)
+    from the authorization server.
+
+    Unlike the authorization code grant type in which the client makes
+    separate requests for authorization and access token, the client
+    receives the access token as the result of the authorization request.
+
+    The implicit grant type does not include client authentication, and
+    relies on the presence of the resource owner and the registration of
+    the redirection URI.  Because the access token is encoded into the
+    redirection URI, it may be exposed to the resource owner and other
+    applications residing on the same device.
+    """
+    
+    response_type = 'token'
+
+    def prepare_request_uri(self, uri, redirect_uri=None, scope=None,
+                            state=None, **kwargs):
+        """Prepare the implicit grant request URI.
+
+        The client constructs the request URI by adding the following
+        parameters to the query component of the authorization endpoint URI
+        using the "application/x-www-form-urlencoded" format, per `Appendix B`_:
+
+        :param redirect_uri:  OPTIONAL. The redirect URI must be an absolute URI
+                              and it should have been registered with the OAuth
+                              provider prior to use. As described in `Section 3.1.2`_.
+
+        :param scope:  OPTIONAL. The scope of the access request as described by
+                       Section 3.3`_. These may be any string but are commonly
+                       URIs or various categories such as ``videos`` or ``documents``.
+
+        :param state:   RECOMMENDED.  An opaque value used by the client to maintain
+                        state between the request and callback.  The authorization
+                        server includes this value when redirecting the user-agent back
+                        to the client.  The parameter SHOULD be used for preventing
+                        cross-site request forgery as described in `Section 10.12`_.
+
+        :param kwargs:  Extra arguments to include in the request URI.
+
+        In addition to supplied parameters, OAuthLib will append the ``client_id``
+        that was provided in the constructor as well as the mandatory ``response_type``
+        argument, set to ``token``::
+
+            >>> from oauthlib.oauth2 import MobileApplicationClient
+            >>> client = MobileApplicationClient('your_id')
+            >>> client.prepare_request_uri('https://example.com')
+            'https://example.com?client_id=your_id&response_type=token'
+            >>> client.prepare_request_uri('https://example.com', redirect_uri='https://a.b/callback')
+            'https://example.com?client_id=your_id&response_type=token&redirect_uri=https%3A%2F%2Fa.b%2Fcallback'
+            >>> client.prepare_request_uri('https://example.com', scope=['profile', 'pictures'])
+            'https://example.com?client_id=your_id&response_type=token&scope=profile+pictures'
+            >>> client.prepare_request_uri('https://example.com', foo='bar')
+            'https://example.com?client_id=your_id&response_type=token&foo=bar'
+
+        .. _`Appendix B`: https://tools.ietf.org/html/rfc6749#appendix-B
+        .. _`Section 2.2`: https://tools.ietf.org/html/rfc6749#section-2.2
+        .. _`Section 3.1.2`: https://tools.ietf.org/html/rfc6749#section-3.1.2
+        .. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3
+        .. _`Section 10.12`: https://tools.ietf.org/html/rfc6749#section-10.12
+        """
+        scope = self.scope if scope is None else scope
+        return prepare_grant_uri(uri, self.client_id, self.response_type,
+                                 redirect_uri=redirect_uri, state=state, scope=scope, **kwargs)
+
+    def parse_request_uri_response(self, uri, state=None, scope=None):
+        """Parse the response URI fragment.
+
+        If the resource owner grants the access request, the authorization
+        server issues an access token and delivers it to the client by adding
+        the following parameters to the fragment component of the redirection
+        URI using the "application/x-www-form-urlencoded" format:
+
+        :param uri: The callback URI that resulted from the user being redirected
+                    back from the provider to you, the client.
+        :param state: The state provided in the authorization request.
+        :param scope: The scopes provided in the authorization request.
+        :return: Dictionary of token parameters.
+        :raises: OAuth2Error if response is invalid.
+
+        A successful response should always contain
+
+        **access_token**
+                The access token issued by the authorization server. Often
+                a random string.
+
+        **token_type**
+            The type of the token issued as described in `Section 7.1`_.
+            Commonly ``Bearer``.
+
+        **state**
+            If you provided the state parameter in the authorization phase, then
+            the provider is required to include that exact state value in the
+            response.
+
+        While it is not mandated it is recommended that the provider include
+
+        **expires_in**
+            The lifetime in seconds of the access token.  For
+            example, the value "3600" denotes that the access token will
+            expire in one hour from the time the response was generated.
+            If omitted, the authorization server SHOULD provide the
+            expiration time via other means or document the default value.
+
+        **scope**
+            Providers may supply this in all responses but are required to only
+            if it has changed since the authorization request.
+
+        A few example responses can be seen below::
+
+            >>> response_uri = 'https://example.com/callback#access_token=sdlfkj452&state=ss345asyht&token_type=Bearer&scope=hello+world'
+            >>> from oauthlib.oauth2 import MobileApplicationClient
+            >>> client = MobileApplicationClient('your_id')
+            >>> client.parse_request_uri_response(response_uri)
+            {
+                'access_token': 'sdlfkj452',
+                'token_type': 'Bearer',
+                'state': 'ss345asyht',
+                'scope': [u'hello', u'world']
+            }
+            >>> client.parse_request_uri_response(response_uri, state='other')
+            Traceback (most recent call last):
+                File "<stdin>", line 1, in <module>
+                File "oauthlib/oauth2/rfc6749/__init__.py", line 598, in parse_request_uri_response
+                    **scope**
+                File "oauthlib/oauth2/rfc6749/parameters.py", line 197, in parse_implicit_response
+                    raise ValueError("Mismatching or missing state in params.")
+            ValueError: Mismatching or missing state in params.
+            >>> def alert_scope_changed(message, old, new):
+            ...     print(message, old, new)
+            ...
+            >>> oauthlib.signals.scope_changed.connect(alert_scope_changed)
+            >>> client.parse_request_body_response(response_body, scope=['other'])
+            ('Scope has changed from "other" to "hello world".', ['other'], ['hello', 'world'])
+
+        .. _`Section 7.1`: https://tools.ietf.org/html/rfc6749#section-7.1
+        .. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3
+        """
+        scope = self.scope if scope is None else scope
+        self.token = parse_implicit_response(uri, state=state, scope=scope)
+        self.populate_token_attributes(self.token)
+        return self.token
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/clients/service_application.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/clients/service_application.py
new file mode 100644
index 00000000..8fb17377
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/clients/service_application.py
@@ -0,0 +1,189 @@
+# -*- coding: utf-8 -*-
+"""
+oauthlib.oauth2.rfc6749
+~~~~~~~~~~~~~~~~~~~~~~~
+
+This module is an implementation of various logic needed
+for consuming and providing OAuth 2.0 RFC6749.
+"""
+import time
+
+from oauthlib.common import to_unicode
+
+from ..parameters import prepare_token_request
+from .base import Client
+
+
+class ServiceApplicationClient(Client):
+    """A public client utilizing the JWT bearer grant.
+
+    JWT bearer tokes can be used to request an access token when a client
+    wishes to utilize an existing trust relationship, expressed through the
+    semantics of (and digital signature or keyed message digest calculated
+    over) the JWT, without a direct user approval step at the authorization
+    server.
+
+    This grant type does not involve an authorization step. It may be
+    used by both public and confidential clients.
+    """
+
+    grant_type = 'urn:ietf:params:oauth:grant-type:jwt-bearer'
+
+    def __init__(self, client_id, private_key=None, subject=None, issuer=None,
+                 audience=None, **kwargs):
+        """Initialize a JWT client with defaults for implicit use later.
+
+        :param client_id: Client identifier given by the OAuth provider upon
+                          registration.
+
+        :param private_key: Private key used for signing and encrypting.
+                            Must be given as a string.
+
+        :param subject: The principal that is the subject of the JWT, i.e.
+                        which user is the token requested on behalf of.
+                        For example, ``foo@example.com.
+
+        :param issuer: The JWT MUST contain an "iss" (issuer) claim that
+                       contains a unique identifier for the entity that issued
+                       the JWT. For example, ``your-client@provider.com``.
+
+        :param audience: A value identifying the authorization server as an
+                         intended audience, e.g.
+                         ``https://provider.com/oauth2/token``.
+
+        :param kwargs: Additional arguments to pass to base client, such as
+                       state and token. See ``Client.__init__.__doc__`` for
+                       details.
+        """
+        super().__init__(client_id, **kwargs)
+        self.private_key = private_key
+        self.subject = subject
+        self.issuer = issuer
+        self.audience = audience
+
+    def prepare_request_body(self,
+                             private_key=None,
+                             subject=None,
+                             issuer=None,
+                             audience=None,
+                             expires_at=None,
+                             issued_at=None,
+                             extra_claims=None,
+                             body='',
+                             scope=None,
+                             include_client_id=False,
+                             **kwargs):
+        """Create and add a JWT assertion to the request body.
+
+        :param private_key: Private key used for signing and encrypting.
+                            Must be given as a string.
+
+        :param subject: (sub) The principal that is the subject of the JWT,
+                        i.e.  which user is the token requested on behalf of.
+                        For example, ``foo@example.com.
+
+        :param issuer: (iss) The JWT MUST contain an "iss" (issuer) claim that
+                       contains a unique identifier for the entity that issued
+                       the JWT. For example, ``your-client@provider.com``.
+
+        :param audience: (aud) A value identifying the authorization server as an
+                         intended audience, e.g.
+                         ``https://provider.com/oauth2/token``.
+
+        :param expires_at: A unix expiration timestamp for the JWT. Defaults
+                           to an hour from now, i.e. ``time.time() + 3600``.
+
+        :param issued_at: A unix timestamp of when the JWT was created.
+                          Defaults to now, i.e. ``time.time()``.
+
+        :param extra_claims: A dict of additional claims to include in the JWT.
+
+        :param body: Existing request body (URL encoded string) to embed parameters
+                     into. This may contain extra parameters. Default ''.
+
+        :param scope: The scope of the access request.
+
+        :param include_client_id: `True` to send the `client_id` in the
+                                  body of the upstream request. This is required
+                                  if the client is not authenticating with the
+                                  authorization server as described in
+                                  `Section 3.2.1`_. False otherwise (default).
+        :type include_client_id: Boolean
+
+        :param not_before: A unix timestamp after which the JWT may be used.
+                           Not included unless provided. *
+
+        :param jwt_id: A unique JWT token identifier. Not included unless
+                       provided. *
+
+        :param kwargs: Extra credentials to include in the token request.
+
+        Parameters marked with a `*` above are not explicit arguments in the
+        function signature, but are specially documented arguments for items
+        appearing in the generic `**kwargs` keyworded input.
+
+        The "scope" parameter may be used, as defined in the Assertion
+        Framework for OAuth 2.0 Client Authentication and Authorization Grants
+        [I-D.ietf-oauth-assertions] specification, to indicate the requested
+        scope.
+
+        Authentication of the client is optional, as described in
+        `Section 3.2.1`_ of OAuth 2.0 [RFC6749] and consequently, the
+        "client_id" is only needed when a form of client authentication that
+        relies on the parameter is used.
+
+        The following non-normative example demonstrates an Access Token
+        Request with a JWT as an authorization grant (with extra line breaks
+        for display purposes only):
+
+        .. code-block: http
+
+            POST /token.oauth2 HTTP/1.1
+            Host: as.example.com
+            Content-Type: application/x-www-form-urlencoded
+
+            grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer
+            &assertion=eyJhbGciOiJFUzI1NiJ9.
+            eyJpc3Mi[...omitted for brevity...].
+            J9l-ZhwP[...omitted for brevity...]
+
+        .. _`Section 3.2.1`: https://tools.ietf.org/html/rfc6749#section-3.2.1
+        """
+        import jwt
+
+        key = private_key or self.private_key
+        if not key:
+            raise ValueError('An encryption key must be supplied to make JWT'
+                             ' token requests.')
+        claim = {
+            'iss': issuer or self.issuer,
+            'aud': audience or self.audience,
+            'sub': subject or self.subject,
+            'exp': int(expires_at or time.time() + 3600),
+            'iat': int(issued_at or time.time()),
+        }
+
+        for attr in ('iss', 'aud', 'sub'):
+            if claim[attr] is None:
+                raise ValueError(
+                        'Claim must include %s but none was given.' % attr)
+
+        if 'not_before' in kwargs:
+            claim['nbf'] = kwargs.pop('not_before')
+
+        if 'jwt_id' in kwargs:
+            claim['jti'] = kwargs.pop('jwt_id')
+
+        claim.update(extra_claims or {})
+
+        assertion = jwt.encode(claim, key, 'RS256')
+        assertion = to_unicode(assertion)
+
+        kwargs['client_id'] = self.client_id
+        kwargs['include_client_id'] = include_client_id
+        scope = self.scope if scope is None else scope
+        return prepare_token_request(self.grant_type,
+                                     body=body,
+                                     assertion=assertion,
+                                     scope=scope,
+                                     **kwargs)
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/clients/web_application.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/clients/web_application.py
new file mode 100644
index 00000000..50890fbf
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/clients/web_application.py
@@ -0,0 +1,222 @@
+# -*- coding: utf-8 -*-
+"""
+oauthlib.oauth2.rfc6749
+~~~~~~~~~~~~~~~~~~~~~~~
+
+This module is an implementation of various logic needed
+for consuming and providing OAuth 2.0 RFC6749.
+"""
+import warnings
+
+from ..parameters import (
+    parse_authorization_code_response, prepare_grant_uri,
+    prepare_token_request,
+)
+from .base import Client
+
+
+class WebApplicationClient(Client):
+
+    """A client utilizing the authorization code grant workflow.
+
+    A web application is a confidential client running on a web
+    server.  Resource owners access the client via an HTML user
+    interface rendered in a user-agent on the device used by the
+    resource owner.  The client credentials as well as any access
+    token issued to the client are stored on the web server and are
+    not exposed to or accessible by the resource owner.
+
+    The authorization code grant type is used to obtain both access
+    tokens and refresh tokens and is optimized for confidential clients.
+    As a redirection-based flow, the client must be capable of
+    interacting with the resource owner's user-agent (typically a web
+    browser) and capable of receiving incoming requests (via redirection)
+    from the authorization server.
+    """
+    
+    grant_type = 'authorization_code'
+
+    def __init__(self, client_id, code=None, **kwargs):
+        super().__init__(client_id, **kwargs)
+        self.code = code
+
+    def prepare_request_uri(self, uri, redirect_uri=None, scope=None,
+                            state=None, code_challenge=None, code_challenge_method='plain', **kwargs):
+        """Prepare the authorization code request URI
+
+        The client constructs the request URI by adding the following
+        parameters to the query component of the authorization endpoint URI
+        using the "application/x-www-form-urlencoded" format, per `Appendix B`_:
+
+        :param redirect_uri:  OPTIONAL. The redirect URI must be an absolute URI
+                              and it should have been registered with the OAuth
+                              provider prior to use. As described in `Section 3.1.2`_.
+
+        :param scope:  OPTIONAL. The scope of the access request as described by
+                       Section 3.3`_. These may be any string but are commonly
+                       URIs or various categories such as ``videos`` or ``documents``.
+
+        :param state:   RECOMMENDED.  An opaque value used by the client to maintain
+                        state between the request and callback.  The authorization
+                        server includes this value when redirecting the user-agent back
+                        to the client.  The parameter SHOULD be used for preventing
+                        cross-site request forgery as described in `Section 10.12`_.
+
+        :param code_challenge: OPTIONAL. PKCE parameter. REQUIRED if PKCE is enforced. 
+                        A challenge derived from the code_verifier that is sent in the 
+                        authorization request, to be verified against later.
+
+        :param code_challenge_method: OPTIONAL. PKCE parameter. A method that was used to derive code challenge.
+                                      Defaults to "plain" if not present in the request.
+
+        :param kwargs:  Extra arguments to include in the request URI.
+
+        In addition to supplied parameters, OAuthLib will append the ``client_id``
+        that was provided in the constructor as well as the mandatory ``response_type``
+        argument, set to ``code``::
+
+            >>> from oauthlib.oauth2 import WebApplicationClient
+            >>> client = WebApplicationClient('your_id')
+            >>> client.prepare_request_uri('https://example.com')
+            'https://example.com?client_id=your_id&response_type=code'
+            >>> client.prepare_request_uri('https://example.com', redirect_uri='https://a.b/callback')
+            'https://example.com?client_id=your_id&response_type=code&redirect_uri=https%3A%2F%2Fa.b%2Fcallback'
+            >>> client.prepare_request_uri('https://example.com', scope=['profile', 'pictures'])
+            'https://example.com?client_id=your_id&response_type=code&scope=profile+pictures'
+            >>> client.prepare_request_uri('https://example.com', code_challenge='kjasBS523KdkAILD2k78NdcJSk2k3KHG6')
+            'https://example.com?client_id=your_id&response_type=code&code_challenge=kjasBS523KdkAILD2k78NdcJSk2k3KHG6'
+            >>> client.prepare_request_uri('https://example.com', code_challenge_method='S256')
+            'https://example.com?client_id=your_id&response_type=code&code_challenge_method=S256'
+            >>> client.prepare_request_uri('https://example.com', foo='bar')
+            'https://example.com?client_id=your_id&response_type=code&foo=bar'
+
+        .. _`Appendix B`: https://tools.ietf.org/html/rfc6749#appendix-B
+        .. _`Section 2.2`: https://tools.ietf.org/html/rfc6749#section-2.2
+        .. _`Section 3.1.2`: https://tools.ietf.org/html/rfc6749#section-3.1.2
+        .. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3
+        .. _`Section 10.12`: https://tools.ietf.org/html/rfc6749#section-10.12
+        """
+        scope = self.scope if scope is None else scope
+        return prepare_grant_uri(uri, self.client_id, 'code',
+                                 redirect_uri=redirect_uri, scope=scope, state=state, code_challenge=code_challenge,
+                                 code_challenge_method=code_challenge_method, **kwargs)
+
+    def prepare_request_body(self, code=None, redirect_uri=None, body='',
+                             include_client_id=True, code_verifier=None, **kwargs):
+        """Prepare the access token request body.
+
+        The client makes a request to the token endpoint by adding the
+        following parameters using the "application/x-www-form-urlencoded"
+        format in the HTTP request entity-body:
+
+        :param code:    REQUIRED. The authorization code received from the
+                        authorization server.
+
+        :param redirect_uri:    REQUIRED, if the "redirect_uri" parameter was included in the
+                                authorization request as described in `Section 4.1.1`_, and their
+                                values MUST be identical.
+
+        :param body: Existing request body (URL encoded string) to embed parameters
+                     into. This may contain extra parameters. Default ''.
+
+        :param include_client_id: `True` (default) to send the `client_id` in the
+                                  body of the upstream request. This is required
+                                  if the client is not authenticating with the
+                                  authorization server as described in `Section 3.2.1`_.
+        :type include_client_id: Boolean
+
+        :param code_verifier: OPTIONAL. A cryptographically random string that is used to correlate the
+                                        authorization request to the token request.
+
+        :param kwargs: Extra parameters to include in the token request.
+
+        In addition OAuthLib will add the ``grant_type`` parameter set to
+        ``authorization_code``.
+
+        If the client type is confidential or the client was issued client
+        credentials (or assigned other authentication requirements), the
+        client MUST authenticate with the authorization server as described
+        in `Section 3.2.1`_::
+
+            >>> from oauthlib.oauth2 import WebApplicationClient
+            >>> client = WebApplicationClient('your_id')
+            >>> client.prepare_request_body(code='sh35ksdf09sf')
+            'grant_type=authorization_code&code=sh35ksdf09sf'
+            >>> client.prepare_request_body(code_verifier='KB46DCKJ873NCGXK5GD682NHDKK34GR')
+            'grant_type=authorization_code&code_verifier=KB46DCKJ873NCGXK5GD682NHDKK34GR'
+            >>> client.prepare_request_body(code='sh35ksdf09sf', foo='bar')
+            'grant_type=authorization_code&code=sh35ksdf09sf&foo=bar'
+
+        `Section 3.2.1` also states:
+            In the "authorization_code" "grant_type" request to the token
+            endpoint, an unauthenticated client MUST send its "client_id" to
+            prevent itself from inadvertently accepting a code intended for a
+            client with a different "client_id".  This protects the client from
+            substitution of the authentication code.  (It provides no additional
+            security for the protected resource.)
+
+        .. _`Section 4.1.1`: https://tools.ietf.org/html/rfc6749#section-4.1.1
+        .. _`Section 3.2.1`: https://tools.ietf.org/html/rfc6749#section-3.2.1
+        """
+        code = code or self.code
+        if 'client_id' in kwargs:
+            warnings.warn("`client_id` has been deprecated in favor of "
+                          "`include_client_id`, a boolean value which will "
+                          "include the already configured `self.client_id`.",
+                          DeprecationWarning)
+            if kwargs['client_id'] != self.client_id:
+                raise ValueError("`client_id` was supplied as an argument, but "
+                                 "it does not match `self.client_id`")
+
+        kwargs['client_id'] = self.client_id
+        kwargs['include_client_id'] = include_client_id
+        return prepare_token_request(self.grant_type, code=code, body=body,
+                                     redirect_uri=redirect_uri, code_verifier=code_verifier, **kwargs)
+
+    def parse_request_uri_response(self, uri, state=None):
+        """Parse the URI query for code and state.
+
+        If the resource owner grants the access request, the authorization
+        server issues an authorization code and delivers it to the client by
+        adding the following parameters to the query component of the
+        redirection URI using the "application/x-www-form-urlencoded" format:
+
+        :param uri: The callback URI that resulted from the user being redirected
+                    back from the provider to you, the client.
+        :param state: The state provided in the authorization request.
+
+        **code**
+            The authorization code generated by the authorization server.
+            The authorization code MUST expire shortly after it is issued
+            to mitigate the risk of leaks. A maximum authorization code
+            lifetime of 10 minutes is RECOMMENDED. The client MUST NOT
+            use the authorization code more than once. If an authorization
+            code is used more than once, the authorization server MUST deny
+            the request and SHOULD revoke (when possible) all tokens
+            previously issued based on that authorization code.
+            The authorization code is bound to the client identifier and
+            redirection URI.
+
+        **state**
+                If the "state" parameter was present in the authorization request.
+
+        This method is mainly intended to enforce strict state checking with
+        the added benefit of easily extracting parameters from the URI::
+
+            >>> from oauthlib.oauth2 import WebApplicationClient
+            >>> client = WebApplicationClient('your_id')
+            >>> uri = 'https://example.com/callback?code=sdfkjh345&state=sfetw45'
+            >>> client.parse_request_uri_response(uri, state='sfetw45')
+            {'state': 'sfetw45', 'code': 'sdfkjh345'}
+            >>> client.parse_request_uri_response(uri, state='other')
+            Traceback (most recent call last):
+                File "<stdin>", line 1, in <module>
+                File "oauthlib/oauth2/rfc6749/__init__.py", line 357, in parse_request_uri_response
+                    back from the provider to you, the client.
+                File "oauthlib/oauth2/rfc6749/parameters.py", line 153, in parse_authorization_code_response
+                    raise MismatchingStateError()
+            oauthlib.oauth2.rfc6749.errors.MismatchingStateError
+        """
+        response = parse_authorization_code_response(uri, state=state)
+        self.populate_code_attributes(response)
+        return response
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/endpoints/__init__.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/endpoints/__init__.py
new file mode 100644
index 00000000..1695b41b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/endpoints/__init__.py
@@ -0,0 +1,17 @@
+"""
+oauthlib.oauth2.rfc6749
+~~~~~~~~~~~~~~~~~~~~~~~
+
+This module is an implementation of various logic needed
+for consuming and providing OAuth 2.0 RFC6749.
+"""
+from .authorization import AuthorizationEndpoint
+from .introspect import IntrospectEndpoint
+from .metadata import MetadataEndpoint
+from .pre_configured import (
+    BackendApplicationServer, LegacyApplicationServer, MobileApplicationServer,
+    Server, WebApplicationServer,
+)
+from .resource import ResourceEndpoint
+from .revocation import RevocationEndpoint
+from .token import TokenEndpoint
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/endpoints/authorization.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/endpoints/authorization.py
new file mode 100644
index 00000000..71967865
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/endpoints/authorization.py
@@ -0,0 +1,114 @@
+"""
+oauthlib.oauth2.rfc6749
+~~~~~~~~~~~~~~~~~~~~~~~
+
+This module is an implementation of various logic needed
+for consuming and providing OAuth 2.0 RFC6749.
+"""
+import logging
+
+from oauthlib.common import Request
+from oauthlib.oauth2.rfc6749 import utils
+
+from .base import BaseEndpoint, catch_errors_and_unavailability
+
+log = logging.getLogger(__name__)
+
+
+class AuthorizationEndpoint(BaseEndpoint):
+
+    """Authorization endpoint - used by the client to obtain authorization
+    from the resource owner via user-agent redirection.
+
+    The authorization endpoint is used to interact with the resource
+    owner and obtain an authorization grant.  The authorization server
+    MUST first verify the identity of the resource owner.  The way in
+    which the authorization server authenticates the resource owner (e.g.
+    username and password login, session cookies) is beyond the scope of
+    this specification.
+
+    The endpoint URI MAY include an "application/x-www-form-urlencoded"
+    formatted (per `Appendix B`_) query component,
+    which MUST be retained when adding additional query parameters.  The
+    endpoint URI MUST NOT include a fragment component::
+
+        https://example.com/path?query=component             # OK
+        https://example.com/path?query=component#fragment    # Not OK
+
+    Since requests to the authorization endpoint result in user
+    authentication and the transmission of clear-text credentials (in the
+    HTTP response), the authorization server MUST require the use of TLS
+    as described in Section 1.6 when sending requests to the
+    authorization endpoint::
+
+        # We will deny any request which URI schema is not with https
+
+    The authorization server MUST support the use of the HTTP "GET"
+    method [RFC2616] for the authorization endpoint, and MAY support the
+    use of the "POST" method as well::
+
+        # HTTP method is currently not enforced
+
+    Parameters sent without a value MUST be treated as if they were
+    omitted from the request.  The authorization server MUST ignore
+    unrecognized request parameters.  Request and response parameters
+    MUST NOT be included more than once::
+
+        # Enforced through the design of oauthlib.common.Request
+
+    .. _`Appendix B`: https://tools.ietf.org/html/rfc6749#appendix-B
+    """
+
+    def __init__(self, default_response_type, default_token_type,
+                 response_types):
+        BaseEndpoint.__init__(self)
+        self._response_types = response_types
+        self._default_response_type = default_response_type
+        self._default_token_type = default_token_type
+
+    @property
+    def response_types(self):
+        return self._response_types
+
+    @property
+    def default_response_type(self):
+        return self._default_response_type
+
+    @property
+    def default_response_type_handler(self):
+        return self.response_types.get(self.default_response_type)
+
+    @property
+    def default_token_type(self):
+        return self._default_token_type
+
+    @catch_errors_and_unavailability
+    def create_authorization_response(self, uri, http_method='GET', body=None,
+                                      headers=None, scopes=None, credentials=None):
+        """Extract response_type and route to the designated handler."""
+        request = Request(
+            uri, http_method=http_method, body=body, headers=headers)
+        request.scopes = scopes
+        # TODO: decide whether this should be a required argument
+        request.user = None     # TODO: explain this in docs
+        for k, v in (credentials or {}).items():
+            setattr(request, k, v)
+        response_type_handler = self.response_types.get(
+            request.response_type, self.default_response_type_handler)
+        log.debug('Dispatching response_type %s request to %r.',
+                  request.response_type, response_type_handler)
+        return response_type_handler.create_authorization_response(
+            request, self.default_token_type)
+
+    @catch_errors_and_unavailability
+    def validate_authorization_request(self, uri, http_method='GET', body=None,
+                                       headers=None):
+        """Extract response_type and route to the designated handler."""
+        request = Request(
+            uri, http_method=http_method, body=body, headers=headers)
+
+        request.scopes = utils.scope_to_list(request.scope)
+
+        response_type_handler = self.response_types.get(
+            request.response_type, self.default_response_type_handler)
+        return response_type_handler.validate_authorization_request(request)
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/endpoints/base.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/endpoints/base.py
new file mode 100644
index 00000000..3f239917
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/endpoints/base.py
@@ -0,0 +1,113 @@
+"""
+oauthlib.oauth2.rfc6749
+~~~~~~~~~~~~~~~~~~~~~~~
+
+This module is an implementation of various logic needed
+for consuming and providing OAuth 2.0 RFC6749.
+"""
+import functools
+import logging
+
+from ..errors import (
+    FatalClientError, InvalidClientError, InvalidRequestError, OAuth2Error,
+    ServerError, TemporarilyUnavailableError, UnsupportedTokenTypeError,
+)
+
+log = logging.getLogger(__name__)
+
+
+class BaseEndpoint:
+
+    def __init__(self):
+        self._available = True
+        self._catch_errors = False
+        self._valid_request_methods = None
+
+    @property
+    def valid_request_methods(self):
+        return self._valid_request_methods
+
+    @valid_request_methods.setter
+    def valid_request_methods(self, valid_request_methods):
+        if valid_request_methods is not None:
+            valid_request_methods = [x.upper() for x in valid_request_methods]
+        self._valid_request_methods = valid_request_methods
+    
+
+    @property
+    def available(self):
+        return self._available
+
+    @available.setter
+    def available(self, available):
+        self._available = available       
+
+    @property
+    def catch_errors(self):
+        return self._catch_errors
+
+    @catch_errors.setter
+    def catch_errors(self, catch_errors):
+        self._catch_errors = catch_errors
+
+    def _raise_on_missing_token(self, request):
+        """Raise error on missing token."""
+        if not request.token:
+            raise InvalidRequestError(request=request,
+                                      description='Missing token parameter.')
+    def _raise_on_invalid_client(self, request):
+        """Raise on failed client authentication."""
+        if self.request_validator.client_authentication_required(request):
+            if not self.request_validator.authenticate_client(request):
+                log.debug('Client authentication failed, %r.', request)
+                raise InvalidClientError(request=request)
+        elif not self.request_validator.authenticate_client_id(request.client_id, request):
+            log.debug('Client authentication failed, %r.', request)
+            raise InvalidClientError(request=request)
+
+    def _raise_on_unsupported_token(self, request):
+        """Raise on unsupported tokens."""
+        if (request.token_type_hint and
+            request.token_type_hint in self.valid_token_types and
+            request.token_type_hint not in self.supported_token_types):
+            raise UnsupportedTokenTypeError(request=request)
+
+    def _raise_on_bad_method(self, request):
+        if self.valid_request_methods is None:
+            raise ValueError('Configure "valid_request_methods" property first')
+        if request.http_method.upper() not in self.valid_request_methods:
+            raise InvalidRequestError(request=request,
+                                      description=('Unsupported request method %s' % request.http_method.upper()))
+
+    def _raise_on_bad_post_request(self, request):
+        """Raise if invalid POST request received
+        """
+        if request.http_method.upper() == 'POST':
+            query_params = request.uri_query or ""
+            if query_params:
+                raise InvalidRequestError(request=request,
+                                          description=('URL query parameters are not allowed'))
+
+def catch_errors_and_unavailability(f):
+    @functools.wraps(f)
+    def wrapper(endpoint, uri, *args, **kwargs):
+        if not endpoint.available:
+            e = TemporarilyUnavailableError()
+            log.info('Endpoint unavailable, ignoring request %s.' % uri)
+            return {}, e.json, 503
+
+        if endpoint.catch_errors:
+            try:
+                return f(endpoint, uri, *args, **kwargs)
+            except OAuth2Error:
+                raise
+            except FatalClientError:
+                raise
+            except Exception as e:
+                error = ServerError()
+                log.warning(
+                    'Exception caught while processing request, %s.' % e)
+                return {}, error.json, 500
+        else:
+            return f(endpoint, uri, *args, **kwargs)
+    return wrapper
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/endpoints/introspect.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/endpoints/introspect.py
new file mode 100644
index 00000000..3cc61e66
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/endpoints/introspect.py
@@ -0,0 +1,120 @@
+"""
+oauthlib.oauth2.rfc6749.endpoint.introspect
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+An implementation of the OAuth 2.0 `Token Introspection`.
+
+.. _`Token Introspection`: https://tools.ietf.org/html/rfc7662
+"""
+import json
+import logging
+
+from oauthlib.common import Request
+
+from ..errors import OAuth2Error
+from .base import BaseEndpoint, catch_errors_and_unavailability
+
+log = logging.getLogger(__name__)
+
+
+class IntrospectEndpoint(BaseEndpoint):
+
+    """Introspect token endpoint.
+
+   This endpoint defines a method to query an OAuth 2.0 authorization
+   server to determine the active state of an OAuth 2.0 token and to
+   determine meta-information about this token. OAuth 2.0 deployments
+   can use this method to convey information about the authorization
+   context of the token from the authorization server to the protected
+   resource.
+
+   To prevent the values of access tokens from leaking into
+   server-side logs via query parameters, an authorization server
+   offering token introspection MAY disallow the use of HTTP GET on
+   the introspection endpoint and instead require the HTTP POST method
+   to be used at the introspection endpoint.
+   """
+
+    valid_token_types = ('access_token', 'refresh_token')
+    valid_request_methods = ('POST',)
+
+    def __init__(self, request_validator, supported_token_types=None):
+        BaseEndpoint.__init__(self)
+        self.request_validator = request_validator
+        self.supported_token_types = (
+            supported_token_types or self.valid_token_types)
+
+    @catch_errors_and_unavailability
+    def create_introspect_response(self, uri, http_method='POST', body=None,
+                                   headers=None):
+        """Create introspect valid or invalid response
+
+        If the authorization server is unable to determine the state
+        of the token without additional information, it SHOULD return
+        an introspection response indicating the token is not active
+        as described in Section 2.2.
+        """
+        resp_headers = {
+            'Content-Type': 'application/json',
+            'Cache-Control': 'no-store',
+            'Pragma': 'no-cache',
+        }
+        request = Request(uri, http_method, body, headers)
+        try:
+            self.validate_introspect_request(request)
+            log.debug('Token introspect valid for %r.', request)
+        except OAuth2Error as e:
+            log.debug('Client error during validation of %r. %r.', request, e)
+            resp_headers.update(e.headers)
+            return resp_headers, e.json, e.status_code
+
+        claims = self.request_validator.introspect_token(
+            request.token,
+            request.token_type_hint,
+            request
+        )
+        if claims is None:
+            return resp_headers, json.dumps(dict(active=False)), 200
+        if "active" in claims:
+            claims.pop("active")
+        return resp_headers, json.dumps(dict(active=True, **claims)), 200
+
+    def validate_introspect_request(self, request):
+        """Ensure the request is valid.
+
+        The protected resource calls the introspection endpoint using
+        an HTTP POST request with parameters sent as
+        "application/x-www-form-urlencoded".
+
+        * token REQUIRED.  The string value of the token.
+        * token_type_hint OPTIONAL.
+
+        A hint about the type of the token submitted for
+        introspection.  The protected resource MAY pass this parameter to
+        help the authorization server optimize the token lookup.  If the
+        server is unable to locate the token using the given hint, it MUST
+        extend its search across all of its supported token types.  An
+        authorization server MAY ignore this parameter, particularly if it
+        is able to detect the token type automatically.
+
+        *  access_token: An Access Token as defined in [`RFC6749`], `section 1.4`_
+        *  refresh_token: A Refresh Token as defined in [`RFC6749`], `section 1.5`_
+
+        The introspection endpoint MAY accept other OPTIONAL
+        parameters to provide further context to the query.  For
+        instance, an authorization server may desire to know the IP
+        address of the client accessing the protected resource to
+        determine if the correct client is likely to be presenting the
+        token.  The definition of this or any other parameters are
+        outside the scope of this specification, to be defined by
+        service documentation or extensions to this specification.
+
+        .. _`section 1.4`: http://tools.ietf.org/html/rfc6749#section-1.4
+        .. _`section 1.5`: http://tools.ietf.org/html/rfc6749#section-1.5
+        .. _`RFC6749`: http://tools.ietf.org/html/rfc6749
+        """
+        self._raise_on_bad_method(request)
+        self._raise_on_bad_post_request(request)
+        self._raise_on_missing_token(request)
+        self._raise_on_invalid_client(request)
+        self._raise_on_unsupported_token(request)
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/endpoints/metadata.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/endpoints/metadata.py
new file mode 100644
index 00000000..a2820f28
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/endpoints/metadata.py
@@ -0,0 +1,238 @@
+"""
+oauthlib.oauth2.rfc6749.endpoint.metadata
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+An implementation of the `OAuth 2.0 Authorization Server Metadata`.
+
+.. _`OAuth 2.0 Authorization Server Metadata`: https://tools.ietf.org/html/rfc8414
+"""
+import copy
+import json
+import logging
+
+from .. import grant_types, utils
+from .authorization import AuthorizationEndpoint
+from .base import BaseEndpoint, catch_errors_and_unavailability
+from .introspect import IntrospectEndpoint
+from .revocation import RevocationEndpoint
+from .token import TokenEndpoint
+
+log = logging.getLogger(__name__)
+
+
+class MetadataEndpoint(BaseEndpoint):
+
+    """OAuth2.0 Authorization Server Metadata endpoint.
+
+   This specification generalizes the metadata format defined by
+   `OpenID Connect Discovery 1.0` in a way that is compatible
+   with OpenID Connect Discovery while being applicable to a wider set
+   of OAuth 2.0 use cases.  This is intentionally parallel to the way
+   that OAuth 2.0 Dynamic Client Registration Protocol [`RFC7591`_]
+   generalized the dynamic client registration mechanisms defined by
+   OpenID Connect Dynamic Client Registration 1.0
+   in a way that is compatible with it.
+
+   .. _`OpenID Connect Discovery 1.0`: https://openid.net/specs/openid-connect-discovery-1_0.html
+   .. _`RFC7591`: https://tools.ietf.org/html/rfc7591
+   """
+
+    def __init__(self, endpoints, claims={}, raise_errors=True):
+        assert isinstance(claims, dict)
+        for endpoint in endpoints:
+            assert isinstance(endpoint, BaseEndpoint)
+
+        BaseEndpoint.__init__(self)
+        self.raise_errors = raise_errors
+        self.endpoints = endpoints
+        self.initial_claims = claims
+        self.claims = self.validate_metadata_server()
+
+    @catch_errors_and_unavailability
+    def create_metadata_response(self, uri, http_method='GET', body=None,
+                                 headers=None):
+        """Create metadata response
+        """
+        headers = {
+            'Content-Type': 'application/json',
+            'Access-Control-Allow-Origin': '*',
+        }
+        return headers, json.dumps(self.claims), 200
+
+    def validate_metadata(self, array, key, is_required=False, is_list=False, is_url=False, is_issuer=False):
+        if not self.raise_errors:
+            return
+
+        if key not in array:
+            if is_required:
+                raise ValueError("key {} is a mandatory metadata.".format(key))
+
+        elif is_issuer:
+            if not utils.is_secure_transport(array[key]):
+                raise ValueError("key {}: {} must be an HTTPS URL".format(key, array[key]))
+            if "?" in array[key] or "&" in array[key] or "#" in array[key]:
+                raise ValueError("key {}: {} must not contain query or fragment components".format(key, array[key]))
+
+        elif is_url:
+            if not array[key].startswith("http"):
+                raise ValueError("key {}: {} must be an URL".format(key, array[key]))
+
+        elif is_list:
+            if not isinstance(array[key], list):
+                raise ValueError("key {}: {} must be an Array".format(key, array[key]))
+            for elem in array[key]:
+                if not isinstance(elem, str):
+                    raise ValueError("array {}: {} must contains only string (not {})".format(key, array[key], elem))
+
+    def validate_metadata_token(self, claims, endpoint):
+        """
+        If the token endpoint is used in the grant type, the value of this
+        parameter MUST be the same as the value of the "grant_type"
+        parameter passed to the token endpoint defined in the grant type
+        definition.
+        """
+        self._grant_types.extend(endpoint._grant_types.keys())
+        claims.setdefault("token_endpoint_auth_methods_supported", ["client_secret_post", "client_secret_basic"])
+
+        self.validate_metadata(claims, "token_endpoint_auth_methods_supported", is_list=True)
+        self.validate_metadata(claims, "token_endpoint_auth_signing_alg_values_supported", is_list=True)
+        self.validate_metadata(claims, "token_endpoint", is_required=True, is_url=True)
+
+    def validate_metadata_authorization(self, claims, endpoint):
+        claims.setdefault("response_types_supported",
+                          list(filter(lambda x: x != "none", endpoint._response_types.keys())))
+        claims.setdefault("response_modes_supported", ["query", "fragment"])
+
+        # The OAuth2.0 Implicit flow is defined as a "grant type" but it is not
+        # using the "token" endpoint, as such, we have to add it explicitly to
+        # the list of "grant_types_supported" when enabled.
+        if "token" in claims["response_types_supported"]:
+            self._grant_types.append("implicit")
+
+        self.validate_metadata(claims, "response_types_supported", is_required=True, is_list=True)
+        self.validate_metadata(claims, "response_modes_supported", is_list=True)
+        if "code" in claims["response_types_supported"]:
+            code_grant = endpoint._response_types["code"]
+            if not isinstance(code_grant, grant_types.AuthorizationCodeGrant) and hasattr(code_grant, "default_grant"):
+                code_grant = code_grant.default_grant
+
+            claims.setdefault("code_challenge_methods_supported",
+                              list(code_grant._code_challenge_methods.keys()))
+            self.validate_metadata(claims, "code_challenge_methods_supported", is_list=True)
+        self.validate_metadata(claims, "authorization_endpoint", is_required=True, is_url=True)
+
+    def validate_metadata_revocation(self, claims, endpoint):
+        claims.setdefault("revocation_endpoint_auth_methods_supported",
+                          ["client_secret_post", "client_secret_basic"])
+
+        self.validate_metadata(claims, "revocation_endpoint_auth_methods_supported", is_list=True)
+        self.validate_metadata(claims, "revocation_endpoint_auth_signing_alg_values_supported", is_list=True)
+        self.validate_metadata(claims, "revocation_endpoint", is_required=True, is_url=True)
+
+    def validate_metadata_introspection(self, claims, endpoint):
+        claims.setdefault("introspection_endpoint_auth_methods_supported",
+                          ["client_secret_post", "client_secret_basic"])
+
+        self.validate_metadata(claims, "introspection_endpoint_auth_methods_supported", is_list=True)
+        self.validate_metadata(claims, "introspection_endpoint_auth_signing_alg_values_supported", is_list=True)
+        self.validate_metadata(claims, "introspection_endpoint", is_required=True, is_url=True)
+
+    def validate_metadata_server(self):
+        """
+        Authorization servers can have metadata describing their
+        configuration.  The following authorization server metadata values
+        are used by this specification. More details can be found in
+        `RFC8414 section 2`_ :
+
+       issuer
+          REQUIRED
+
+       authorization_endpoint
+          URL of the authorization server's authorization endpoint
+          [`RFC6749#Authorization`_].  This is REQUIRED unless no grant types are supported
+          that use the authorization endpoint.
+
+       token_endpoint
+          URL of the authorization server's token endpoint [`RFC6749#Token`_].  This
+          is REQUIRED unless only the implicit grant type is supported.
+
+       scopes_supported
+          RECOMMENDED.
+
+       response_types_supported
+          REQUIRED.
+
+       Other OPTIONAL fields:
+          jwks_uri,
+          registration_endpoint,
+          response_modes_supported
+
+       grant_types_supported
+          OPTIONAL.  JSON array containing a list of the OAuth 2.0 grant
+          type values that this authorization server supports.  The array
+          values used are the same as those used with the "grant_types"
+          parameter defined by "OAuth 2.0 Dynamic Client Registration
+          Protocol" [`RFC7591`_].  If omitted, the default value is
+          "["authorization_code", "implicit"]".
+
+       token_endpoint_auth_methods_supported
+
+       token_endpoint_auth_signing_alg_values_supported
+
+       service_documentation
+
+       ui_locales_supported
+
+       op_policy_uri
+
+       op_tos_uri
+
+       revocation_endpoint
+
+       revocation_endpoint_auth_methods_supported
+
+       revocation_endpoint_auth_signing_alg_values_supported
+
+       introspection_endpoint
+
+       introspection_endpoint_auth_methods_supported
+
+       introspection_endpoint_auth_signing_alg_values_supported
+
+       code_challenge_methods_supported
+
+       Additional authorization server metadata parameters MAY also be used.
+       Some are defined by other specifications, such as OpenID Connect
+       Discovery 1.0 [`OpenID.Discovery`_].
+
+        .. _`RFC8414 section 2`: https://tools.ietf.org/html/rfc8414#section-2
+        .. _`RFC6749#Authorization`: https://tools.ietf.org/html/rfc6749#section-3.1
+        .. _`RFC6749#Token`: https://tools.ietf.org/html/rfc6749#section-3.2
+        .. _`RFC7591`: https://tools.ietf.org/html/rfc7591
+        .. _`OpenID.Discovery`: https://openid.net/specs/openid-connect-discovery-1_0.html
+        """
+        claims = copy.deepcopy(self.initial_claims)
+        self.validate_metadata(claims, "issuer", is_required=True, is_issuer=True)
+        self.validate_metadata(claims, "jwks_uri", is_url=True)
+        self.validate_metadata(claims, "scopes_supported", is_list=True)
+        self.validate_metadata(claims, "service_documentation", is_url=True)
+        self.validate_metadata(claims, "ui_locales_supported", is_list=True)
+        self.validate_metadata(claims, "op_policy_uri", is_url=True)
+        self.validate_metadata(claims, "op_tos_uri", is_url=True)
+
+        self._grant_types = []
+        for endpoint in self.endpoints:
+            if isinstance(endpoint, TokenEndpoint):
+                self.validate_metadata_token(claims, endpoint)
+            if isinstance(endpoint, AuthorizationEndpoint):
+                self.validate_metadata_authorization(claims, endpoint)
+            if isinstance(endpoint, RevocationEndpoint):
+                self.validate_metadata_revocation(claims, endpoint)
+            if isinstance(endpoint, IntrospectEndpoint):
+                self.validate_metadata_introspection(claims, endpoint)
+
+        # "grant_types_supported" is a combination of all OAuth2 grant types
+        # allowed in the current provider implementation.
+        claims.setdefault("grant_types_supported", self._grant_types)
+        self.validate_metadata(claims, "grant_types_supported", is_list=True)
+        return claims
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/endpoints/pre_configured.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/endpoints/pre_configured.py
new file mode 100644
index 00000000..d64a1663
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/endpoints/pre_configured.py
@@ -0,0 +1,216 @@
+"""
+oauthlib.oauth2.rfc6749.endpoints.pre_configured
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This module is an implementation of various endpoints needed
+for providing OAuth 2.0 RFC6749 servers.
+"""
+from ..grant_types import (
+    AuthorizationCodeGrant, ClientCredentialsGrant, ImplicitGrant,
+    RefreshTokenGrant, ResourceOwnerPasswordCredentialsGrant,
+)
+from ..tokens import BearerToken
+from .authorization import AuthorizationEndpoint
+from .introspect import IntrospectEndpoint
+from .resource import ResourceEndpoint
+from .revocation import RevocationEndpoint
+from .token import TokenEndpoint
+
+
+class Server(AuthorizationEndpoint, IntrospectEndpoint, TokenEndpoint,
+             ResourceEndpoint, RevocationEndpoint):
+
+    """An all-in-one endpoint featuring all four major grant types."""
+
+    def __init__(self, request_validator, token_expires_in=None,
+                 token_generator=None, refresh_token_generator=None,
+                 *args, **kwargs):
+        """Construct a new all-grants-in-one server.
+
+        :param request_validator: An implementation of
+                                  oauthlib.oauth2.RequestValidator.
+        :param token_expires_in: An int or a function to generate a token
+                                 expiration offset (in seconds) given a
+                                 oauthlib.common.Request object.
+        :param token_generator: A function to generate a token from a request.
+        :param refresh_token_generator: A function to generate a token from a
+                                        request for the refresh token.
+        :param kwargs: Extra parameters to pass to authorization-,
+                       token-, resource-, and revocation-endpoint constructors.
+        """
+        self.auth_grant = AuthorizationCodeGrant(request_validator)
+        self.implicit_grant = ImplicitGrant(request_validator)
+        self.password_grant = ResourceOwnerPasswordCredentialsGrant(
+            request_validator)
+        self.credentials_grant = ClientCredentialsGrant(request_validator)
+        self.refresh_grant = RefreshTokenGrant(request_validator)
+
+        self.bearer = BearerToken(request_validator, token_generator,
+                             token_expires_in, refresh_token_generator)
+
+        AuthorizationEndpoint.__init__(self, default_response_type='code',
+                                       response_types={
+                                           'code': self.auth_grant,
+                                           'token': self.implicit_grant,
+                                           'none': self.auth_grant
+                                       },
+                                       default_token_type=self.bearer)
+
+        TokenEndpoint.__init__(self, default_grant_type='authorization_code',
+                               grant_types={
+                                   'authorization_code': self.auth_grant,
+                                   'password': self.password_grant,
+                                   'client_credentials': self.credentials_grant,
+                                   'refresh_token': self.refresh_grant,
+                               },
+                               default_token_type=self.bearer)
+        ResourceEndpoint.__init__(self, default_token='Bearer',
+                                  token_types={'Bearer': self.bearer})
+        RevocationEndpoint.__init__(self, request_validator)
+        IntrospectEndpoint.__init__(self, request_validator)
+
+
+class WebApplicationServer(AuthorizationEndpoint, IntrospectEndpoint, TokenEndpoint,
+                           ResourceEndpoint, RevocationEndpoint):
+
+    """An all-in-one endpoint featuring Authorization code grant and Bearer tokens."""
+
+    def __init__(self, request_validator, token_generator=None,
+                 token_expires_in=None, refresh_token_generator=None, **kwargs):
+        """Construct a new web application server.
+
+        :param request_validator: An implementation of
+                                  oauthlib.oauth2.RequestValidator.
+        :param token_expires_in: An int or a function to generate a token
+                                 expiration offset (in seconds) given a
+                                 oauthlib.common.Request object.
+        :param token_generator: A function to generate a token from a request.
+        :param refresh_token_generator: A function to generate a token from a
+                                        request for the refresh token.
+        :param kwargs: Extra parameters to pass to authorization-,
+                       token-, resource-, and revocation-endpoint constructors.
+        """
+        self.auth_grant = AuthorizationCodeGrant(request_validator)
+        self.refresh_grant = RefreshTokenGrant(request_validator)
+        self.bearer = BearerToken(request_validator, token_generator,
+                             token_expires_in, refresh_token_generator)
+        AuthorizationEndpoint.__init__(self, default_response_type='code',
+                                       response_types={'code': self.auth_grant},
+                                       default_token_type=self.bearer)
+        TokenEndpoint.__init__(self, default_grant_type='authorization_code',
+                               grant_types={
+                                   'authorization_code': self.auth_grant,
+                                   'refresh_token': self.refresh_grant,
+                               },
+                               default_token_type=self.bearer)
+        ResourceEndpoint.__init__(self, default_token='Bearer',
+                                  token_types={'Bearer': self.bearer})
+        RevocationEndpoint.__init__(self, request_validator)
+        IntrospectEndpoint.__init__(self, request_validator)
+
+
+class MobileApplicationServer(AuthorizationEndpoint, IntrospectEndpoint,
+                              ResourceEndpoint, RevocationEndpoint):
+
+    """An all-in-one endpoint featuring Implicit code grant and Bearer tokens."""
+
+    def __init__(self, request_validator, token_generator=None,
+                 token_expires_in=None, refresh_token_generator=None, **kwargs):
+        """Construct a new implicit grant server.
+
+        :param request_validator: An implementation of
+                                  oauthlib.oauth2.RequestValidator.
+        :param token_expires_in: An int or a function to generate a token
+                                 expiration offset (in seconds) given a
+                                 oauthlib.common.Request object.
+        :param token_generator: A function to generate a token from a request.
+        :param refresh_token_generator: A function to generate a token from a
+                                        request for the refresh token.
+        :param kwargs: Extra parameters to pass to authorization-,
+                       token-, resource-, and revocation-endpoint constructors.
+        """
+        self.implicit_grant = ImplicitGrant(request_validator)
+        self.bearer = BearerToken(request_validator, token_generator,
+                             token_expires_in, refresh_token_generator)
+        AuthorizationEndpoint.__init__(self, default_response_type='token',
+                                       response_types={
+                                           'token': self.implicit_grant},
+                                       default_token_type=self.bearer)
+        ResourceEndpoint.__init__(self, default_token='Bearer',
+                                  token_types={'Bearer': self.bearer})
+        RevocationEndpoint.__init__(self, request_validator,
+                                    supported_token_types=['access_token'])
+        IntrospectEndpoint.__init__(self, request_validator,
+                                    supported_token_types=['access_token'])
+
+
+class LegacyApplicationServer(TokenEndpoint, IntrospectEndpoint,
+                              ResourceEndpoint, RevocationEndpoint):
+
+    """An all-in-one endpoint featuring Resource Owner Password Credentials grant and Bearer tokens."""
+
+    def __init__(self, request_validator, token_generator=None,
+                 token_expires_in=None, refresh_token_generator=None, **kwargs):
+        """Construct a resource owner password credentials grant server.
+
+        :param request_validator: An implementation of
+                                  oauthlib.oauth2.RequestValidator.
+        :param token_expires_in: An int or a function to generate a token
+                                 expiration offset (in seconds) given a
+                                 oauthlib.common.Request object.
+        :param token_generator: A function to generate a token from a request.
+        :param refresh_token_generator: A function to generate a token from a
+                                        request for the refresh token.
+        :param kwargs: Extra parameters to pass to authorization-,
+                       token-, resource-, and revocation-endpoint constructors.
+        """
+        self.password_grant = ResourceOwnerPasswordCredentialsGrant(
+            request_validator)
+        self.refresh_grant = RefreshTokenGrant(request_validator)
+        self.bearer = BearerToken(request_validator, token_generator,
+                             token_expires_in, refresh_token_generator)
+        TokenEndpoint.__init__(self, default_grant_type='password',
+                               grant_types={
+                                   'password': self.password_grant,
+                                   'refresh_token': self.refresh_grant,
+                               },
+                               default_token_type=self.bearer)
+        ResourceEndpoint.__init__(self, default_token='Bearer',
+                                  token_types={'Bearer': self.bearer})
+        RevocationEndpoint.__init__(self, request_validator)
+        IntrospectEndpoint.__init__(self, request_validator)
+
+
+class BackendApplicationServer(TokenEndpoint, IntrospectEndpoint,
+                               ResourceEndpoint, RevocationEndpoint):
+
+    """An all-in-one endpoint featuring Client Credentials grant and Bearer tokens."""
+
+    def __init__(self, request_validator, token_generator=None,
+                 token_expires_in=None, refresh_token_generator=None, **kwargs):
+        """Construct a client credentials grant server.
+
+        :param request_validator: An implementation of
+                                  oauthlib.oauth2.RequestValidator.
+        :param token_expires_in: An int or a function to generate a token
+                                 expiration offset (in seconds) given a
+                                 oauthlib.common.Request object.
+        :param token_generator: A function to generate a token from a request.
+        :param refresh_token_generator: A function to generate a token from a
+                                        request for the refresh token.
+        :param kwargs: Extra parameters to pass to authorization-,
+                       token-, resource-, and revocation-endpoint constructors.
+        """
+        self.credentials_grant = ClientCredentialsGrant(request_validator)
+        self.bearer = BearerToken(request_validator, token_generator,
+                             token_expires_in, refresh_token_generator)
+        TokenEndpoint.__init__(self, default_grant_type='client_credentials',
+                               grant_types={
+                                   'client_credentials': self.credentials_grant},
+                               default_token_type=self.bearer)
+        ResourceEndpoint.__init__(self, default_token='Bearer',
+                                  token_types={'Bearer': self.bearer})
+        RevocationEndpoint.__init__(self, request_validator,
+                                    supported_token_types=['access_token'])
+        IntrospectEndpoint.__init__(self, request_validator,
+                                    supported_token_types=['access_token'])
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/endpoints/resource.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/endpoints/resource.py
new file mode 100644
index 00000000..f7562255
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/endpoints/resource.py
@@ -0,0 +1,84 @@
+"""
+oauthlib.oauth2.rfc6749
+~~~~~~~~~~~~~~~~~~~~~~~
+
+This module is an implementation of various logic needed
+for consuming and providing OAuth 2.0 RFC6749.
+"""
+import logging
+
+from oauthlib.common import Request
+
+from .base import BaseEndpoint, catch_errors_and_unavailability
+
+log = logging.getLogger(__name__)
+
+
+class ResourceEndpoint(BaseEndpoint):
+
+    """Authorizes access to protected resources.
+
+    The client accesses protected resources by presenting the access
+    token to the resource server.  The resource server MUST validate the
+    access token and ensure that it has not expired and that its scope
+    covers the requested resource.  The methods used by the resource
+    server to validate the access token (as well as any error responses)
+    are beyond the scope of this specification but generally involve an
+    interaction or coordination between the resource server and the
+    authorization server::
+
+        # For most cases, returning a 403 should suffice.
+
+    The method in which the client utilizes the access token to
+    authenticate with the resource server depends on the type of access
+    token issued by the authorization server.  Typically, it involves
+    using the HTTP "Authorization" request header field [RFC2617] with an
+    authentication scheme defined by the specification of the access
+    token type used, such as [RFC6750]::
+
+        # Access tokens may also be provided in query and body
+        https://example.com/protected?access_token=kjfch2345sdf   # Query
+        access_token=sdf23409df   # Body
+    """
+
+    def __init__(self, default_token, token_types):
+        BaseEndpoint.__init__(self)
+        self._tokens = token_types
+        self._default_token = default_token
+
+    @property
+    def default_token(self):
+        return self._default_token
+
+    @property
+    def default_token_type_handler(self):
+        return self.tokens.get(self.default_token)
+
+    @property
+    def tokens(self):
+        return self._tokens
+
+    @catch_errors_and_unavailability
+    def verify_request(self, uri, http_method='GET', body=None, headers=None,
+                       scopes=None):
+        """Validate client, code etc, return body + headers"""
+        request = Request(uri, http_method, body, headers)
+        request.token_type = self.find_token_type(request)
+        request.scopes = scopes
+        token_type_handler = self.tokens.get(request.token_type,
+                                             self.default_token_type_handler)
+        log.debug('Dispatching token_type %s request to %r.',
+                  request.token_type, token_type_handler)
+        return token_type_handler.validate_request(request), request
+
+    def find_token_type(self, request):
+        """Token type identification.
+
+        RFC 6749 does not provide a method for easily differentiating between
+        different token types during protected resource access. We estimate
+        the most likely token type (if any) by asking each known token type
+        to give an estimation based on the request.
+        """
+        estimates = sorted(((t.estimate_type(request), n)
+                            for n, t in self.tokens.items()), reverse=True)
+        return estimates[0][1] if len(estimates) else None
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/endpoints/revocation.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/endpoints/revocation.py
new file mode 100644
index 00000000..596d0860
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/endpoints/revocation.py
@@ -0,0 +1,126 @@
+"""
+oauthlib.oauth2.rfc6749.endpoint.revocation
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+An implementation of the OAuth 2 `Token Revocation`_ spec (draft 11).
+
+.. _`Token Revocation`: https://tools.ietf.org/html/draft-ietf-oauth-revocation-11
+"""
+import logging
+
+from oauthlib.common import Request
+
+from ..errors import OAuth2Error
+from .base import BaseEndpoint, catch_errors_and_unavailability
+
+log = logging.getLogger(__name__)
+
+
+class RevocationEndpoint(BaseEndpoint):
+
+    """Token revocation endpoint.
+
+    Endpoint used by authenticated clients to revoke access and refresh tokens.
+    Commonly this will be part of the Authorization Endpoint.
+    """
+
+    valid_token_types = ('access_token', 'refresh_token')
+    valid_request_methods = ('POST',)
+
+    def __init__(self, request_validator, supported_token_types=None,
+            enable_jsonp=False):
+        BaseEndpoint.__init__(self)
+        self.request_validator = request_validator
+        self.supported_token_types = (
+            supported_token_types or self.valid_token_types)
+        self.enable_jsonp = enable_jsonp
+
+    @catch_errors_and_unavailability
+    def create_revocation_response(self, uri, http_method='POST', body=None,
+                                   headers=None):
+        """Revoke supplied access or refresh token.
+
+
+        The authorization server responds with HTTP status code 200 if the
+        token has been revoked successfully or if the client submitted an
+        invalid token.
+
+        Note: invalid tokens do not cause an error response since the client
+        cannot handle such an error in a reasonable way.  Moreover, the purpose
+        of the revocation request, invalidating the particular token, is
+        already achieved.
+
+        The content of the response body is ignored by the client as all
+        necessary information is conveyed in the response code.
+
+        An invalid token type hint value is ignored by the authorization server
+        and does not influence the revocation response.
+        """
+        resp_headers = {
+            'Content-Type': 'application/json',
+            'Cache-Control': 'no-store',
+            'Pragma': 'no-cache',
+        }
+        request = Request(
+            uri, http_method=http_method, body=body, headers=headers)
+        try:
+            self.validate_revocation_request(request)
+            log.debug('Token revocation valid for %r.', request)
+        except OAuth2Error as e:
+            log.debug('Client error during validation of %r. %r.', request, e)
+            response_body = e.json
+            if self.enable_jsonp and request.callback:
+                response_body = '{}({});'.format(request.callback, response_body)
+            resp_headers.update(e.headers)
+            return resp_headers, response_body, e.status_code
+
+        self.request_validator.revoke_token(request.token,
+                                            request.token_type_hint, request)
+
+        response_body = ''
+        if self.enable_jsonp and request.callback:
+            response_body = request.callback + '();'
+        return {}, response_body, 200
+
+    def validate_revocation_request(self, request):
+        """Ensure the request is valid.
+
+        The client constructs the request by including the following parameters
+        using the "application/x-www-form-urlencoded" format in the HTTP
+        request entity-body:
+
+        token (REQUIRED).  The token that the client wants to get revoked.
+
+        token_type_hint (OPTIONAL).  A hint about the type of the token
+        submitted for revocation.  Clients MAY pass this parameter in order to
+        help the authorization server to optimize the token lookup.  If the
+        server is unable to locate the token using the given hint, it MUST
+        extend its search across all of its supported token types.  An
+        authorization server MAY ignore this parameter, particularly if it is
+        able to detect the token type automatically.  This specification
+        defines two such values:
+
+                *  access_token: An Access Token as defined in [RFC6749],
+                    `section 1.4`_
+
+                *  refresh_token: A Refresh Token as defined in [RFC6749],
+                    `section 1.5`_
+
+                Specific implementations, profiles, and extensions of this
+                specification MAY define other values for this parameter using
+                the registry defined in `Section 4.1.2`_.
+
+        The client also includes its authentication credentials as described in
+        `Section 2.3`_. of [`RFC6749`_].
+
+        .. _`section 1.4`: https://tools.ietf.org/html/rfc6749#section-1.4
+        .. _`section 1.5`: https://tools.ietf.org/html/rfc6749#section-1.5
+        .. _`section 2.3`: https://tools.ietf.org/html/rfc6749#section-2.3
+        .. _`Section 4.1.2`: https://tools.ietf.org/html/draft-ietf-oauth-revocation-11#section-4.1.2
+        .. _`RFC6749`: https://tools.ietf.org/html/rfc6749
+        """
+        self._raise_on_bad_method(request)
+        self._raise_on_bad_post_request(request)
+        self._raise_on_missing_token(request)
+        self._raise_on_invalid_client(request)
+        self._raise_on_unsupported_token(request)
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/endpoints/token.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/endpoints/token.py
new file mode 100644
index 00000000..ab9e0918
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/endpoints/token.py
@@ -0,0 +1,119 @@
+"""
+oauthlib.oauth2.rfc6749
+~~~~~~~~~~~~~~~~~~~~~~~
+
+This module is an implementation of various logic needed
+for consuming and providing OAuth 2.0 RFC6749.
+"""
+import logging
+
+from oauthlib.common import Request
+from oauthlib.oauth2.rfc6749 import utils
+
+from .base import BaseEndpoint, catch_errors_and_unavailability
+
+log = logging.getLogger(__name__)
+
+
+class TokenEndpoint(BaseEndpoint):
+
+    """Token issuing endpoint.
+
+    The token endpoint is used by the client to obtain an access token by
+    presenting its authorization grant or refresh token.  The token
+    endpoint is used with every authorization grant except for the
+    implicit grant type (since an access token is issued directly).
+
+    The means through which the client obtains the location of the token
+    endpoint are beyond the scope of this specification, but the location
+    is typically provided in the service documentation.
+
+    The endpoint URI MAY include an "application/x-www-form-urlencoded"
+    formatted (per `Appendix B`_) query component,
+    which MUST be retained when adding additional query parameters.  The
+    endpoint URI MUST NOT include a fragment component::
+
+        https://example.com/path?query=component             # OK
+        https://example.com/path?query=component#fragment    # Not OK
+
+    Since requests to the token endpoint result in the transmission of
+    clear-text credentials (in the HTTP request and response), the
+    authorization server MUST require the use of TLS as described in
+    Section 1.6 when sending requests to the token endpoint::
+
+        # We will deny any request which URI schema is not with https
+
+    The client MUST use the HTTP "POST" method when making access token
+    requests::
+
+        # HTTP method is currently not enforced
+
+    Parameters sent without a value MUST be treated as if they were
+    omitted from the request.  The authorization server MUST ignore
+    unrecognized request parameters.  Request and response parameters
+    MUST NOT be included more than once::
+
+        # Delegated to each grant type.
+
+    .. _`Appendix B`: https://tools.ietf.org/html/rfc6749#appendix-B
+    """
+
+    valid_request_methods = ('POST',)
+
+    def __init__(self, default_grant_type, default_token_type, grant_types):
+        BaseEndpoint.__init__(self)
+        self._grant_types = grant_types
+        self._default_token_type = default_token_type
+        self._default_grant_type = default_grant_type
+
+    @property
+    def grant_types(self):
+        return self._grant_types
+
+    @property
+    def default_grant_type(self):
+        return self._default_grant_type
+
+    @property
+    def default_grant_type_handler(self):
+        return self.grant_types.get(self.default_grant_type)
+
+    @property
+    def default_token_type(self):
+        return self._default_token_type
+
+    @catch_errors_and_unavailability
+    def create_token_response(self, uri, http_method='POST', body=None,
+                              headers=None, credentials=None, grant_type_for_scope=None,
+                              claims=None):
+        """Extract grant_type and route to the designated handler."""
+        request = Request(
+            uri, http_method=http_method, body=body, headers=headers)
+        self.validate_token_request(request)
+        # 'scope' is an allowed Token Request param in both the "Resource Owner Password Credentials Grant"
+        # and "Client Credentials Grant" flows
+        # https://tools.ietf.org/html/rfc6749#section-4.3.2
+        # https://tools.ietf.org/html/rfc6749#section-4.4.2
+        request.scopes = utils.scope_to_list(request.scope)
+
+        request.extra_credentials = credentials
+        if grant_type_for_scope:
+            request.grant_type = grant_type_for_scope
+
+        # OpenID Connect claims, if provided.  The server using oauthlib might choose
+        # to implement the claims parameter of the Authorization Request.  In this case
+        # it should retrieve those claims and pass them via the claims argument here,
+        # as a dict.
+        if claims:
+            request.claims = claims
+
+        grant_type_handler = self.grant_types.get(request.grant_type,
+                                                  self.default_grant_type_handler)
+        log.debug('Dispatching grant_type %s request to %r.',
+                  request.grant_type, grant_type_handler)
+        return grant_type_handler.create_token_response(
+            request, self.default_token_type)
+
+    def validate_token_request(self, request):
+        self._raise_on_bad_method(request)
+        self._raise_on_bad_post_request(request)
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/errors.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/errors.py
new file mode 100644
index 00000000..da24feab
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/errors.py
@@ -0,0 +1,400 @@
+"""
+oauthlib.oauth2.rfc6749.errors
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Error used both by OAuth 2 clients and providers to represent the spec
+defined error responses for all four core grant types.
+"""
+import json
+
+from oauthlib.common import add_params_to_uri, urlencode
+
+
+class OAuth2Error(Exception):
+    error = None
+    status_code = 400
+    description = ''
+
+    def __init__(self, description=None, uri=None, state=None,
+                 status_code=None, request=None):
+        """
+        :param description: A human-readable ASCII [USASCII] text providing
+                            additional information, used to assist the client
+                            developer in understanding the error that occurred.
+                            Values for the "error_description" parameter
+                            MUST NOT include characters outside the set
+                            x20-21 / x23-5B / x5D-7E.
+
+        :param uri: A URI identifying a human-readable web page with information
+                    about the error, used to provide the client developer with
+                    additional information about the error.  Values for the
+                    "error_uri" parameter MUST conform to the URI- Reference
+                    syntax, and thus MUST NOT include characters outside the set
+                    x21 / x23-5B / x5D-7E.
+
+        :param state: A CSRF protection value received from the client.
+
+        :param status_code:
+
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        """
+        if description is not None:
+            self.description = description
+
+        message = '({}) {}'.format(self.error, self.description)
+        if request:
+            message += ' ' + repr(request)
+        super().__init__(message)
+
+        self.uri = uri
+        self.state = state
+
+        if status_code:
+            self.status_code = status_code
+
+        if request:
+            self.redirect_uri = request.redirect_uri
+            self.client_id = request.client_id
+            self.scopes = request.scopes
+            self.response_type = request.response_type
+            self.response_mode = request.response_mode
+            self.grant_type = request.grant_type
+            if not state:
+                self.state = request.state
+        else:
+            self.redirect_uri = None
+            self.client_id = None
+            self.scopes = None
+            self.response_type = None
+            self.response_mode = None
+            self.grant_type = None
+
+    def in_uri(self, uri):
+        fragment = self.response_mode == "fragment"
+        return add_params_to_uri(uri, self.twotuples, fragment)
+
+    @property
+    def twotuples(self):
+        error = [('error', self.error)]
+        if self.description:
+            error.append(('error_description', self.description))
+        if self.uri:
+            error.append(('error_uri', self.uri))
+        if self.state:
+            error.append(('state', self.state))
+        return error
+
+    @property
+    def urlencoded(self):
+        return urlencode(self.twotuples)
+
+    @property
+    def json(self):
+        return json.dumps(dict(self.twotuples))
+
+    @property
+    def headers(self):
+        if self.status_code == 401:
+            """
+            https://tools.ietf.org/html/rfc6750#section-3
+
+            All challenges defined by this specification MUST use the auth-scheme
+            value "Bearer".  This scheme MUST be followed by one or more
+            auth-param values.
+            """
+            authvalues = ['error="{}"'.format(self.error)]
+            if self.description:
+                authvalues.append('error_description="{}"'.format(self.description))
+            if self.uri:
+                authvalues.append('error_uri="{}"'.format(self.uri))
+            return {"WWW-Authenticate": "Bearer " + ", ".join(authvalues)}
+        return {}
+
+
+class TokenExpiredError(OAuth2Error):
+    error = 'token_expired'
+
+
+class InsecureTransportError(OAuth2Error):
+    error = 'insecure_transport'
+    description = 'OAuth 2 MUST utilize https.'
+
+
+class MismatchingStateError(OAuth2Error):
+    error = 'mismatching_state'
+    description = 'CSRF Warning! State not equal in request and response.'
+
+
+class MissingCodeError(OAuth2Error):
+    error = 'missing_code'
+
+
+class MissingTokenError(OAuth2Error):
+    error = 'missing_token'
+
+
+class MissingTokenTypeError(OAuth2Error):
+    error = 'missing_token_type'
+
+
+class FatalClientError(OAuth2Error):
+    """
+    Errors during authorization where user should not be redirected back.
+
+    If the request fails due to a missing, invalid, or mismatching
+    redirection URI, or if the client identifier is missing or invalid,
+    the authorization server SHOULD inform the resource owner of the
+    error and MUST NOT automatically redirect the user-agent to the
+    invalid redirection URI.
+
+    Instead the user should be informed of the error by the provider itself.
+    """
+    pass
+
+
+class InvalidRequestFatalError(FatalClientError):
+    """
+    For fatal errors, the request is missing a required parameter, includes
+    an invalid parameter value, includes a parameter more than once, or is
+    otherwise malformed.
+    """
+    error = 'invalid_request'
+
+
+class InvalidRedirectURIError(InvalidRequestFatalError):
+    description = 'Invalid redirect URI.'
+
+
+class MissingRedirectURIError(InvalidRequestFatalError):
+    description = 'Missing redirect URI.'
+
+
+class MismatchingRedirectURIError(InvalidRequestFatalError):
+    description = 'Mismatching redirect URI.'
+
+
+class InvalidClientIdError(InvalidRequestFatalError):
+    description = 'Invalid client_id parameter value.'
+
+
+class MissingClientIdError(InvalidRequestFatalError):
+    description = 'Missing client_id parameter.'
+
+
+class InvalidRequestError(OAuth2Error):
+    """
+    The request is missing a required parameter, includes an invalid
+    parameter value, includes a parameter more than once, or is
+    otherwise malformed.
+    """
+    error = 'invalid_request'
+
+
+class MissingResponseTypeError(InvalidRequestError):
+    description = 'Missing response_type parameter.'
+
+
+class MissingCodeChallengeError(InvalidRequestError):
+    """
+    If the server requires Proof Key for Code Exchange (PKCE) by OAuth
+    public clients and the client does not send the "code_challenge" in
+    the request, the authorization endpoint MUST return the authorization
+    error response with the "error" value set to "invalid_request".  The
+    "error_description" or the response of "error_uri" SHOULD explain the
+    nature of error, e.g., code challenge required.
+    """
+    description = 'Code challenge required.'
+
+
+class MissingCodeVerifierError(InvalidRequestError):
+    """
+    The request to the token endpoint, when PKCE is enabled, has
+    the parameter `code_verifier` REQUIRED.
+    """
+    description = 'Code verifier required.'
+
+
+class AccessDeniedError(OAuth2Error):
+    """
+    The resource owner or authorization server denied the request.
+    """
+    error = 'access_denied'
+
+
+class UnsupportedResponseTypeError(OAuth2Error):
+    """
+    The authorization server does not support obtaining an authorization
+    code using this method.
+    """
+    error = 'unsupported_response_type'
+
+
+class UnsupportedCodeChallengeMethodError(InvalidRequestError):
+    """
+    If the server supporting PKCE does not support the requested
+    transformation, the authorization endpoint MUST return the
+    authorization error response with "error" value set to
+    "invalid_request".  The "error_description" or the response of
+    "error_uri" SHOULD explain the nature of error, e.g., transform
+    algorithm not supported.
+    """
+    description = 'Transform algorithm not supported.'
+
+
+class InvalidScopeError(OAuth2Error):
+    """
+    The requested scope is invalid, unknown, or malformed, or
+    exceeds the scope granted by the resource owner.
+
+    https://tools.ietf.org/html/rfc6749#section-5.2
+    """
+    error = 'invalid_scope'
+
+
+class ServerError(OAuth2Error):
+    """
+    The authorization server encountered an unexpected condition that
+    prevented it from fulfilling the request.  (This error code is needed
+    because a 500 Internal Server Error HTTP status code cannot be returned
+    to the client via a HTTP redirect.)
+    """
+    error = 'server_error'
+
+
+class TemporarilyUnavailableError(OAuth2Error):
+    """
+    The authorization server is currently unable to handle the request
+    due to a temporary overloading or maintenance of the server.
+    (This error code is needed because a 503 Service Unavailable HTTP
+    status code cannot be returned to the client via a HTTP redirect.)
+    """
+    error = 'temporarily_unavailable'
+
+
+class InvalidClientError(FatalClientError):
+    """
+    Client authentication failed (e.g. unknown client, no client
+    authentication included, or unsupported authentication method).
+    The authorization server MAY return an HTTP 401 (Unauthorized) status
+    code to indicate which HTTP authentication schemes are supported.
+    If the client attempted to authenticate via the "Authorization" request
+    header field, the authorization server MUST respond with an
+    HTTP 401 (Unauthorized) status code, and include the "WWW-Authenticate"
+    response header field matching the authentication scheme used by the
+    client.
+    """
+    error = 'invalid_client'
+    status_code = 401
+
+
+class InvalidGrantError(OAuth2Error):
+    """
+    The provided authorization grant (e.g. authorization code, resource
+    owner credentials) or refresh token is invalid, expired, revoked, does
+    not match the redirection URI used in the authorization request, or was
+    issued to another client.
+
+    https://tools.ietf.org/html/rfc6749#section-5.2
+    """
+    error = 'invalid_grant'
+    status_code = 400
+
+
+class UnauthorizedClientError(OAuth2Error):
+    """
+    The authenticated client is not authorized to use this authorization
+    grant type.
+    """
+    error = 'unauthorized_client'
+
+
+class UnsupportedGrantTypeError(OAuth2Error):
+    """
+    The authorization grant type is not supported by the authorization
+    server.
+    """
+    error = 'unsupported_grant_type'
+
+
+class UnsupportedTokenTypeError(OAuth2Error):
+    """
+    The authorization server does not support the hint of the
+    presented token type.  I.e. the client tried to revoke an access token
+    on a server not supporting this feature.
+    """
+    error = 'unsupported_token_type'
+
+
+class InvalidTokenError(OAuth2Error):
+    """
+    The access token provided is expired, revoked, malformed, or
+    invalid for other reasons.  The resource SHOULD respond with
+    the HTTP 401 (Unauthorized) status code.  The client MAY
+    request a new access token and retry the protected resource
+    request.
+    """
+    error = 'invalid_token'
+    status_code = 401
+    description = ("The access token provided is expired, revoked, malformed, "
+                   "or invalid for other reasons.")
+
+
+class InsufficientScopeError(OAuth2Error):
+    """
+    The request requires higher privileges than provided by the
+    access token.  The resource server SHOULD respond with the HTTP
+    403 (Forbidden) status code and MAY include the "scope"
+    attribute with the scope necessary to access the protected
+    resource.
+    """
+    error = 'insufficient_scope'
+    status_code = 403
+    description = ("The request requires higher privileges than provided by "
+                   "the access token.")
+
+
+class ConsentRequired(OAuth2Error):
+    """
+    The Authorization Server requires End-User consent.
+
+    This error MAY be returned when the prompt parameter value in the
+    Authentication Request is none, but the Authentication Request cannot be
+    completed without displaying a user interface for End-User consent.
+    """
+    error = 'consent_required'
+
+
+class LoginRequired(OAuth2Error):
+    """
+    The Authorization Server requires End-User authentication.
+
+    This error MAY be returned when the prompt parameter value in the
+    Authentication Request is none, but the Authentication Request cannot be
+    completed without displaying a user interface for End-User authentication.
+    """
+    error = 'login_required'
+
+
+class CustomOAuth2Error(OAuth2Error):
+    """
+    This error is a placeholder for all custom errors not described by the RFC.
+    Some of the popular OAuth2 providers are using custom errors.
+    """
+    def __init__(self, error, *args, **kwargs):
+        self.error = error
+        super().__init__(*args, **kwargs)
+
+
+def raise_from_error(error, params=None):
+    import inspect
+    import sys
+    kwargs = {
+        'description': params.get('error_description'),
+        'uri': params.get('error_uri'),
+        'state': params.get('state')
+    }
+    for _, cls in inspect.getmembers(sys.modules[__name__], inspect.isclass):
+        if cls.error == error:
+            raise cls(**kwargs)
+    raise CustomOAuth2Error(error=error, **kwargs)
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/grant_types/__init__.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/grant_types/__init__.py
new file mode 100644
index 00000000..eb88cfc2
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/grant_types/__init__.py
@@ -0,0 +1,11 @@
+"""
+oauthlib.oauth2.rfc6749.grant_types
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+"""
+from .authorization_code import AuthorizationCodeGrant
+from .client_credentials import ClientCredentialsGrant
+from .implicit import ImplicitGrant
+from .refresh_token import RefreshTokenGrant
+from .resource_owner_password_credentials import (
+    ResourceOwnerPasswordCredentialsGrant,
+)
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py
new file mode 100644
index 00000000..858855a1
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py
@@ -0,0 +1,548 @@
+"""
+oauthlib.oauth2.rfc6749.grant_types
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+"""
+import base64
+import hashlib
+import json
+import logging
+
+from oauthlib import common
+
+from .. import errors
+from .base import GrantTypeBase
+
+log = logging.getLogger(__name__)
+
+
+def code_challenge_method_s256(verifier, challenge):
+    """
+    If the "code_challenge_method" from `Section 4.3`_ was "S256", the
+    received "code_verifier" is hashed by SHA-256, base64url-encoded, and
+    then compared to the "code_challenge", i.e.:
+
+    BASE64URL-ENCODE(SHA256(ASCII(code_verifier))) == code_challenge
+
+    How to implement a base64url-encoding
+    function without padding, based upon the standard base64-encoding
+    function that uses padding.
+
+    To be concrete, example C# code implementing these functions is shown
+    below.  Similar code could be used in other languages.
+
+    static string base64urlencode(byte [] arg)
+    {
+        string s = Convert.ToBase64String(arg); // Regular base64 encoder
+        s = s.Split('=')[0]; // Remove any trailing '='s
+        s = s.Replace('+', '-'); // 62nd char of encoding
+        s = s.Replace('/', '_'); // 63rd char of encoding
+        return s;
+    }
+
+    In python urlsafe_b64encode is already replacing '+' and '/', but preserve
+    the trailing '='. So we have to remove it.
+
+    .. _`Section 4.3`: https://tools.ietf.org/html/rfc7636#section-4.3
+    """
+    return base64.urlsafe_b64encode(
+        hashlib.sha256(verifier.encode()).digest()
+    ).decode().rstrip('=') == challenge
+
+
+def code_challenge_method_plain(verifier, challenge):
+    """
+    If the "code_challenge_method" from `Section 4.3`_ was "plain", they are
+    compared directly, i.e.:
+
+    code_verifier == code_challenge.
+
+    .. _`Section 4.3`: https://tools.ietf.org/html/rfc7636#section-4.3
+    """
+    return verifier == challenge
+
+
+class AuthorizationCodeGrant(GrantTypeBase):
+
+    """`Authorization Code Grant`_
+
+    The authorization code grant type is used to obtain both access
+    tokens and refresh tokens and is optimized for confidential clients.
+    Since this is a redirection-based flow, the client must be capable of
+    interacting with the resource owner's user-agent (typically a web
+    browser) and capable of receiving incoming requests (via redirection)
+    from the authorization server::
+
+        +----------+
+        | Resource |
+        |   Owner  |
+        |          |
+        +----------+
+             ^
+             |
+            (B)
+        +----|-----+          Client Identifier      +---------------+
+        |         -+----(A)-- & Redirection URI ---->|               |
+        |  User-   |                                 | Authorization |
+        |  Agent  -+----(B)-- User authenticates --->|     Server    |
+        |          |                                 |               |
+        |         -+----(C)-- Authorization Code ---<|               |
+        +-|----|---+                                 +---------------+
+          |    |                                         ^      v
+         (A)  (C)                                        |      |
+          |    |                                         |      |
+          ^    v                                         |      |
+        +---------+                                      |      |
+        |         |>---(D)-- Authorization Code ---------'      |
+        |  Client |          & Redirection URI                  |
+        |         |                                             |
+        |         |<---(E)----- Access Token -------------------'
+        +---------+       (w/ Optional Refresh Token)
+
+    Note: The lines illustrating steps (A), (B), and (C) are broken into
+    two parts as they pass through the user-agent.
+
+    Figure 3: Authorization Code Flow
+
+    The flow illustrated in Figure 3 includes the following steps:
+
+    (A)  The client initiates the flow by directing the resource owner's
+         user-agent to the authorization endpoint.  The client includes
+         its client identifier, requested scope, local state, and a
+         redirection URI to which the authorization server will send the
+         user-agent back once access is granted (or denied).
+
+    (B)  The authorization server authenticates the resource owner (via
+         the user-agent) and establishes whether the resource owner
+         grants or denies the client's access request.
+
+    (C)  Assuming the resource owner grants access, the authorization
+         server redirects the user-agent back to the client using the
+         redirection URI provided earlier (in the request or during
+         client registration).  The redirection URI includes an
+         authorization code and any local state provided by the client
+         earlier.
+
+    (D)  The client requests an access token from the authorization
+         server's token endpoint by including the authorization code
+         received in the previous step.  When making the request, the
+         client authenticates with the authorization server.  The client
+         includes the redirection URI used to obtain the authorization
+         code for verification.
+
+    (E)  The authorization server authenticates the client, validates the
+         authorization code, and ensures that the redirection URI
+         received matches the URI used to redirect the client in
+         step (C).  If valid, the authorization server responds back with
+         an access token and, optionally, a refresh token.
+
+    OAuth 2.0 public clients utilizing the Authorization Code Grant are
+    susceptible to the authorization code interception attack.
+
+    A technique to mitigate against the threat through the use of Proof Key for Code
+    Exchange (PKCE, pronounced "pixy") is implemented in the current oauthlib
+    implementation.
+
+    .. _`Authorization Code Grant`: https://tools.ietf.org/html/rfc6749#section-4.1
+    .. _`PKCE`: https://tools.ietf.org/html/rfc7636
+    """
+
+    default_response_mode = 'query'
+    response_types = ['code']
+
+    # This dict below is private because as RFC mention it:
+    # "S256" is Mandatory To Implement (MTI) on the server.
+    #
+    _code_challenge_methods = {
+        'plain': code_challenge_method_plain,
+        'S256': code_challenge_method_s256
+    }
+
+    def create_authorization_code(self, request):
+        """
+        Generates an authorization grant represented as a dictionary.
+
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        """
+        grant = {'code': common.generate_token()}
+        if hasattr(request, 'state') and request.state:
+            grant['state'] = request.state
+        log.debug('Created authorization code grant %r for request %r.',
+                  grant, request)
+        return grant
+
+    def create_authorization_response(self, request, token_handler):
+        """
+        The client constructs the request URI by adding the following
+        parameters to the query component of the authorization endpoint URI
+        using the "application/x-www-form-urlencoded" format, per `Appendix B`_:
+
+        response_type
+                REQUIRED.  Value MUST be set to "code" for standard OAuth2
+                authorization flow.  For OpenID Connect it must be one of
+                "code token", "code id_token", or "code token id_token" - we
+                essentially test that "code" appears in the response_type.
+        client_id
+                REQUIRED.  The client identifier as described in `Section 2.2`_.
+        redirect_uri
+                OPTIONAL.  As described in `Section 3.1.2`_.
+        scope
+                OPTIONAL.  The scope of the access request as described by
+                `Section 3.3`_.
+        state
+                RECOMMENDED.  An opaque value used by the client to maintain
+                state between the request and callback.  The authorization
+                server includes this value when redirecting the user-agent back
+                to the client.  The parameter SHOULD be used for preventing
+                cross-site request forgery as described in `Section 10.12`_.
+
+        The client directs the resource owner to the constructed URI using an
+        HTTP redirection response, or by other means available to it via the
+        user-agent.
+
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :param token_handler: A token handler instance, for example of type
+                              oauthlib.oauth2.BearerToken.
+        :returns: headers, body, status
+        :raises: FatalClientError on invalid redirect URI or client id.
+
+        A few examples::
+
+            >>> from your_validator import your_validator
+            >>> request = Request('https://example.com/authorize?client_id=valid'
+            ...                   '&redirect_uri=http%3A%2F%2Fclient.com%2F')
+            >>> from oauthlib.common import Request
+            >>> from oauthlib.oauth2 import AuthorizationCodeGrant, BearerToken
+            >>> token = BearerToken(your_validator)
+            >>> grant = AuthorizationCodeGrant(your_validator)
+            >>> request.scopes = ['authorized', 'in', 'some', 'form']
+            >>> grant.create_authorization_response(request, token)
+            (u'http://client.com/?error=invalid_request&error_description=Missing+response_type+parameter.', None, None, 400)
+            >>> request = Request('https://example.com/authorize?client_id=valid'
+            ...                   '&redirect_uri=http%3A%2F%2Fclient.com%2F'
+            ...                   '&response_type=code')
+            >>> request.scopes = ['authorized', 'in', 'some', 'form']
+            >>> grant.create_authorization_response(request, token)
+            (u'http://client.com/?code=u3F05aEObJuP2k7DordviIgW5wl52N', None, None, 200)
+            >>> # If the client id or redirect uri fails validation
+            >>> grant.create_authorization_response(request, token)
+            Traceback (most recent call last):
+                File "<stdin>", line 1, in <module>
+                File "oauthlib/oauth2/rfc6749/grant_types.py", line 515, in create_authorization_response
+                    >>> grant.create_authorization_response(request, token)
+                File "oauthlib/oauth2/rfc6749/grant_types.py", line 591, in validate_authorization_request
+            oauthlib.oauth2.rfc6749.errors.InvalidClientIdError
+
+        .. _`Appendix B`: https://tools.ietf.org/html/rfc6749#appendix-B
+        .. _`Section 2.2`: https://tools.ietf.org/html/rfc6749#section-2.2
+        .. _`Section 3.1.2`: https://tools.ietf.org/html/rfc6749#section-3.1.2
+        .. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3
+        .. _`Section 10.12`: https://tools.ietf.org/html/rfc6749#section-10.12
+        """
+        try:
+            self.validate_authorization_request(request)
+            log.debug('Pre resource owner authorization validation ok for %r.',
+                      request)
+
+        # If the request fails due to a missing, invalid, or mismatching
+        # redirection URI, or if the client identifier is missing or invalid,
+        # the authorization server SHOULD inform the resource owner of the
+        # error and MUST NOT automatically redirect the user-agent to the
+        # invalid redirection URI.
+        except errors.FatalClientError as e:
+            log.debug('Fatal client error during validation of %r. %r.',
+                      request, e)
+            raise
+
+        # If the resource owner denies the access request or if the request
+        # fails for reasons other than a missing or invalid redirection URI,
+        # the authorization server informs the client by adding the following
+        # parameters to the query component of the redirection URI using the
+        # "application/x-www-form-urlencoded" format, per Appendix B:
+        # https://tools.ietf.org/html/rfc6749#appendix-B
+        except errors.OAuth2Error as e:
+            log.debug('Client error during validation of %r. %r.', request, e)
+            request.redirect_uri = request.redirect_uri or self.error_uri
+            redirect_uri = common.add_params_to_uri(
+                request.redirect_uri, e.twotuples,
+                fragment=request.response_mode == "fragment")
+            return {'Location': redirect_uri}, None, 302
+
+        grant = self.create_authorization_code(request)
+        for modifier in self._code_modifiers:
+            grant = modifier(grant, token_handler, request)
+        if 'access_token' in grant:
+            self.request_validator.save_token(grant, request)
+        log.debug('Saving grant %r for %r.', grant, request)
+        self.request_validator.save_authorization_code(
+            request.client_id, grant, request)
+        return self.prepare_authorization_response(
+            request, grant, {}, None, 302)
+
+    def create_token_response(self, request, token_handler):
+        """Validate the authorization code.
+
+        The client MUST NOT use the authorization code more than once. If an
+        authorization code is used more than once, the authorization server
+        MUST deny the request and SHOULD revoke (when possible) all tokens
+        previously issued based on that authorization code. The authorization
+        code is bound to the client identifier and redirection URI.
+
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :param token_handler: A token handler instance, for example of type
+                              oauthlib.oauth2.BearerToken.
+
+        """
+        headers = self._get_default_headers()
+        try:
+            self.validate_token_request(request)
+            log.debug('Token request validation ok for %r.', request)
+        except errors.OAuth2Error as e:
+            log.debug('Client error during validation of %r. %r.', request, e)
+            headers.update(e.headers)
+            return headers, e.json, e.status_code
+
+        token = token_handler.create_token(request, refresh_token=self.refresh_token)
+
+        for modifier in self._token_modifiers:
+            token = modifier(token, token_handler, request)
+
+        self.request_validator.save_token(token, request)
+        self.request_validator.invalidate_authorization_code(
+            request.client_id, request.code, request)
+        headers.update(self._create_cors_headers(request))
+        return headers, json.dumps(token), 200
+
+    def validate_authorization_request(self, request):
+        """Check the authorization request for normal and fatal errors.
+
+        A normal error could be a missing response_type parameter or the client
+        attempting to access scope it is not allowed to ask authorization for.
+        Normal errors can safely be included in the redirection URI and
+        sent back to the client.
+
+        Fatal errors occur when the client_id or redirect_uri is invalid or
+        missing. These must be caught by the provider and handled, how this
+        is done is outside of the scope of OAuthLib but showing an error
+        page describing the issue is a good idea.
+
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        """
+
+        # First check for fatal errors
+
+        # If the request fails due to a missing, invalid, or mismatching
+        # redirection URI, or if the client identifier is missing or invalid,
+        # the authorization server SHOULD inform the resource owner of the
+        # error and MUST NOT automatically redirect the user-agent to the
+        # invalid redirection URI.
+
+        # First check duplicate parameters
+        for param in ('client_id', 'response_type', 'redirect_uri', 'scope', 'state'):
+            try:
+                duplicate_params = request.duplicate_params
+            except ValueError:
+                raise errors.InvalidRequestFatalError(description='Unable to parse query string', request=request)
+            if param in duplicate_params:
+                raise errors.InvalidRequestFatalError(description='Duplicate %s parameter.' % param, request=request)
+
+        # REQUIRED. The client identifier as described in Section 2.2.
+        # https://tools.ietf.org/html/rfc6749#section-2.2
+        if not request.client_id:
+            raise errors.MissingClientIdError(request=request)
+
+        if not self.request_validator.validate_client_id(request.client_id, request):
+            raise errors.InvalidClientIdError(request=request)
+
+        # OPTIONAL. As described in Section 3.1.2.
+        # https://tools.ietf.org/html/rfc6749#section-3.1.2
+        log.debug('Validating redirection uri %s for client %s.',
+                  request.redirect_uri, request.client_id)
+
+        # OPTIONAL. As described in Section 3.1.2.
+        # https://tools.ietf.org/html/rfc6749#section-3.1.2
+        self._handle_redirects(request)
+
+        # Then check for normal errors.
+
+        # If the resource owner denies the access request or if the request
+        # fails for reasons other than a missing or invalid redirection URI,
+        # the authorization server informs the client by adding the following
+        # parameters to the query component of the redirection URI using the
+        # "application/x-www-form-urlencoded" format, per Appendix B.
+        # https://tools.ietf.org/html/rfc6749#appendix-B
+
+        # Note that the correct parameters to be added are automatically
+        # populated through the use of specific exceptions.
+
+        request_info = {}
+        for validator in self.custom_validators.pre_auth:
+            request_info.update(validator(request))
+
+        # REQUIRED.
+        if request.response_type is None:
+            raise errors.MissingResponseTypeError(request=request)
+        # Value MUST be set to "code" or one of the OpenID authorization code including
+        # response_types "code token", "code id_token", "code token id_token"
+        elif not 'code' in request.response_type and request.response_type != 'none':
+            raise errors.UnsupportedResponseTypeError(request=request)
+
+        if not self.request_validator.validate_response_type(request.client_id,
+                                                             request.response_type,
+                                                             request.client, request):
+
+            log.debug('Client %s is not authorized to use response_type %s.',
+                      request.client_id, request.response_type)
+            raise errors.UnauthorizedClientError(request=request)
+
+        # OPTIONAL. Validate PKCE request or reply with "error"/"invalid_request"
+        # https://tools.ietf.org/html/rfc6749#section-4.4.1
+        if self.request_validator.is_pkce_required(request.client_id, request) is True:
+            if request.code_challenge is None:
+                raise errors.MissingCodeChallengeError(request=request)
+
+        if request.code_challenge is not None:
+            request_info["code_challenge"] = request.code_challenge
+
+            # OPTIONAL, defaults to "plain" if not present in the request.
+            if request.code_challenge_method is None:
+                request.code_challenge_method = "plain"
+
+            if request.code_challenge_method not in self._code_challenge_methods:
+                raise errors.UnsupportedCodeChallengeMethodError(request=request)
+            request_info["code_challenge_method"] = request.code_challenge_method
+
+        # OPTIONAL. The scope of the access request as described by Section 3.3
+        # https://tools.ietf.org/html/rfc6749#section-3.3
+        self.validate_scopes(request)
+
+        request_info.update({
+            'client_id': request.client_id,
+            'redirect_uri': request.redirect_uri,
+            'response_type': request.response_type,
+            'state': request.state,
+            'request': request
+        })
+
+        for validator in self.custom_validators.post_auth:
+            request_info.update(validator(request))
+
+        return request.scopes, request_info
+
+    def validate_token_request(self, request):
+        """
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        """
+        # REQUIRED. Value MUST be set to "authorization_code".
+        if request.grant_type not in ('authorization_code', 'openid'):
+            raise errors.UnsupportedGrantTypeError(request=request)
+
+        for validator in self.custom_validators.pre_token:
+            validator(request)
+
+        if request.code is None:
+            raise errors.InvalidRequestError(
+                description='Missing code parameter.', request=request)
+
+        for param in ('client_id', 'grant_type', 'redirect_uri'):
+            if param in request.duplicate_params:
+                raise errors.InvalidRequestError(description='Duplicate %s parameter.' % param,
+                                                 request=request)
+
+        if self.request_validator.client_authentication_required(request):
+            # If the client type is confidential or the client was issued client
+            # credentials (or assigned other authentication requirements), the
+            # client MUST authenticate with the authorization server as described
+            # in Section 3.2.1.
+            # https://tools.ietf.org/html/rfc6749#section-3.2.1
+            if not self.request_validator.authenticate_client(request):
+                log.debug('Client authentication failed, %r.', request)
+                raise errors.InvalidClientError(request=request)
+        elif not self.request_validator.authenticate_client_id(request.client_id, request):
+            # REQUIRED, if the client is not authenticating with the
+            # authorization server as described in Section 3.2.1.
+            # https://tools.ietf.org/html/rfc6749#section-3.2.1
+            log.debug('Client authentication failed, %r.', request)
+            raise errors.InvalidClientError(request=request)
+
+        if not hasattr(request.client, 'client_id'):
+            raise NotImplementedError('Authenticate client must set the '
+                                      'request.client.client_id attribute '
+                                      'in authenticate_client.')
+
+        request.client_id = request.client_id or request.client.client_id
+
+        # Ensure client is authorized use of this grant type
+        self.validate_grant_type(request)
+
+        # REQUIRED. The authorization code received from the
+        # authorization server.
+        if not self.request_validator.validate_code(request.client_id,
+                                                    request.code, request.client, request):
+            log.debug('Client, %r (%r), is not allowed access to scopes %r.',
+                      request.client_id, request.client, request.scopes)
+            raise errors.InvalidGrantError(request=request)
+
+        # OPTIONAL. Validate PKCE code_verifier
+        challenge = self.request_validator.get_code_challenge(request.code, request)
+
+        if challenge is not None:
+            if request.code_verifier is None:
+                raise errors.MissingCodeVerifierError(request=request)
+
+            challenge_method = self.request_validator.get_code_challenge_method(request.code, request)
+            if challenge_method is None:
+                raise errors.InvalidGrantError(request=request, description="Challenge method not found")
+
+            if challenge_method not in self._code_challenge_methods:
+                raise errors.ServerError(
+                    description="code_challenge_method {} is not supported.".format(challenge_method),
+                    request=request
+                )
+
+            if not self.validate_code_challenge(challenge,
+                                                challenge_method,
+                                                request.code_verifier):
+                log.debug('request provided a invalid code_verifier.')
+                raise errors.InvalidGrantError(request=request)
+        elif self.request_validator.is_pkce_required(request.client_id, request) is True:
+            if request.code_verifier is None:
+                raise errors.MissingCodeVerifierError(request=request)
+            raise errors.InvalidGrantError(request=request, description="Challenge not found")
+
+        for attr in ('user', 'scopes'):
+            if getattr(request, attr, None) is None:
+                log.debug('request.%s was not set on code validation.', attr)
+
+        # REQUIRED, if the "redirect_uri" parameter was included in the
+        # authorization request as described in Section 4.1.1, and their
+        # values MUST be identical.
+        if request.redirect_uri is None:
+            request.using_default_redirect_uri = True
+            request.redirect_uri = self.request_validator.get_default_redirect_uri(
+                request.client_id, request)
+            log.debug('Using default redirect_uri %s.', request.redirect_uri)
+            if not request.redirect_uri:
+                raise errors.MissingRedirectURIError(request=request)
+        else:
+            request.using_default_redirect_uri = False
+            log.debug('Using provided redirect_uri %s', request.redirect_uri)
+
+        if not self.request_validator.confirm_redirect_uri(request.client_id, request.code,
+                                                           request.redirect_uri, request.client,
+                                                           request):
+            log.debug('Redirect_uri (%r) invalid for client %r (%r).',
+                      request.redirect_uri, request.client_id, request.client)
+            raise errors.MismatchingRedirectURIError(request=request)
+
+        for validator in self.custom_validators.post_token:
+            validator(request)
+
+    def validate_code_challenge(self, challenge, challenge_method, verifier):
+        if challenge_method in self._code_challenge_methods:
+            return self._code_challenge_methods[challenge_method](verifier, challenge)
+        raise NotImplementedError('Unknown challenge_method %s' % challenge_method)
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/grant_types/base.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/grant_types/base.py
new file mode 100644
index 00000000..ca343a11
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/grant_types/base.py
@@ -0,0 +1,268 @@
+"""
+oauthlib.oauth2.rfc6749.grant_types
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+"""
+import logging
+from itertools import chain
+
+from oauthlib.common import add_params_to_uri
+from oauthlib.oauth2.rfc6749 import errors, utils
+from oauthlib.uri_validate import is_absolute_uri
+
+from ..request_validator import RequestValidator
+from ..utils import is_secure_transport
+
+log = logging.getLogger(__name__)
+
+
+class ValidatorsContainer:
+    """
+    Container object for holding custom validator callables to be invoked
+    as part of the grant type `validate_authorization_request()` or
+    `validate_authorization_request()` methods on the various grant types.
+
+    Authorization validators must be callables that take a request object and
+    return a dict, which may contain items to be added to the `request_info`
+    returned from the grant_type after validation.
+
+    Token validators must be callables that take a request object and
+    return None.
+
+    Both authorization validators and token validators may raise OAuth2
+    exceptions if validation conditions fail.
+
+    Authorization validators added to `pre_auth` will be run BEFORE
+    the standard validations (but after the critical ones that raise
+    fatal errors) as part of `validate_authorization_request()`
+
+    Authorization validators added to `post_auth` will be run AFTER
+    the standard validations as part of `validate_authorization_request()`
+
+    Token validators added to `pre_token` will be run BEFORE
+    the standard validations as part of `validate_token_request()`
+
+    Token validators added to `post_token` will be run AFTER
+    the standard validations as part of `validate_token_request()`
+
+    For example:
+
+    >>> def my_auth_validator(request):
+    ...    return {'myval': True}
+    >>> auth_code_grant = AuthorizationCodeGrant(request_validator)
+    >>> auth_code_grant.custom_validators.pre_auth.append(my_auth_validator)
+    >>> def my_token_validator(request):
+    ...     if not request.everything_okay:
+    ...         raise errors.OAuth2Error("uh-oh")
+    >>> auth_code_grant.custom_validators.post_token.append(my_token_validator)
+    """
+
+    def __init__(self, post_auth, post_token,
+                 pre_auth, pre_token):
+        self.pre_auth = pre_auth
+        self.post_auth = post_auth
+        self.pre_token = pre_token
+        self.post_token = post_token
+
+    @property
+    def all_pre(self):
+        return chain(self.pre_auth, self.pre_token)
+
+    @property
+    def all_post(self):
+        return chain(self.post_auth, self.post_token)
+
+
+class GrantTypeBase:
+    error_uri = None
+    request_validator = None
+    default_response_mode = 'fragment'
+    refresh_token = True
+    response_types = ['code']
+
+    def __init__(self, request_validator=None, **kwargs):
+        self.request_validator = request_validator or RequestValidator()
+
+        # Transforms class variables into instance variables:
+        self.response_types = self.response_types
+        self.refresh_token = self.refresh_token
+        self._setup_custom_validators(kwargs)
+        self._code_modifiers = []
+        self._token_modifiers = []
+
+        for kw, val in kwargs.items():
+            setattr(self, kw, val)
+
+    def _setup_custom_validators(self, kwargs):
+        post_auth = kwargs.get('post_auth', [])
+        post_token = kwargs.get('post_token', [])
+        pre_auth = kwargs.get('pre_auth', [])
+        pre_token = kwargs.get('pre_token', [])
+        if not hasattr(self, 'validate_authorization_request'):
+            if post_auth or pre_auth:
+                msg = ("{} does not support authorization validators. Use "
+                       "token validators instead.").format(self.__class__.__name__)
+                raise ValueError(msg)
+            # Using tuples here because they can't be appended to:
+            post_auth, pre_auth = (), ()
+        self.custom_validators = ValidatorsContainer(post_auth, post_token,
+                                                     pre_auth, pre_token)
+
+    def register_response_type(self, response_type):
+        self.response_types.append(response_type)
+
+    def register_code_modifier(self, modifier):
+        self._code_modifiers.append(modifier)
+
+    def register_token_modifier(self, modifier):
+        self._token_modifiers.append(modifier)
+
+    def create_authorization_response(self, request, token_handler):
+        """
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :param token_handler: A token handler instance, for example of type
+                              oauthlib.oauth2.BearerToken.
+        """
+        raise NotImplementedError('Subclasses must implement this method.')
+
+    def create_token_response(self, request, token_handler):
+        """
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :param token_handler: A token handler instance, for example of type
+                              oauthlib.oauth2.BearerToken.
+        """
+        raise NotImplementedError('Subclasses must implement this method.')
+
+    def add_token(self, token, token_handler, request):
+        """
+        :param token:
+        :param token_handler: A token handler instance, for example of type
+                              oauthlib.oauth2.BearerToken.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        """
+        # Only add a hybrid access token on auth step if asked for
+        if not request.response_type in ["token", "code token", "id_token token", "code id_token token"]:
+            return token
+
+        token.update(token_handler.create_token(request, refresh_token=False))
+        return token
+
+    def validate_grant_type(self, request):
+        """
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        """
+        client_id = getattr(request, 'client_id', None)
+        if not self.request_validator.validate_grant_type(client_id,
+                                                          request.grant_type, request.client, request):
+            log.debug('Unauthorized from %r (%r) access to grant type %s.',
+                      request.client_id, request.client, request.grant_type)
+            raise errors.UnauthorizedClientError(request=request)
+
+    def validate_scopes(self, request):
+        """
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        """
+        if not request.scopes:
+            request.scopes = utils.scope_to_list(request.scope) or utils.scope_to_list(
+                self.request_validator.get_default_scopes(request.client_id, request))
+        log.debug('Validating access to scopes %r for client %r (%r).',
+                  request.scopes, request.client_id, request.client)
+        if not self.request_validator.validate_scopes(request.client_id,
+                                                      request.scopes, request.client, request):
+            raise errors.InvalidScopeError(request=request)
+
+    def prepare_authorization_response(self, request, token, headers, body, status):
+        """Place token according to response mode.
+
+        Base classes can define a default response mode for their authorization
+        response by overriding the static `default_response_mode` member.
+
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :param token:
+        :param headers:
+        :param body:
+        :param status:
+        """
+        request.response_mode = request.response_mode or self.default_response_mode
+
+        if request.response_mode not in ('query', 'fragment'):
+            log.debug('Overriding invalid response mode %s with %s',
+                      request.response_mode, self.default_response_mode)
+            request.response_mode = self.default_response_mode
+
+        token_items = token.items()
+
+        if request.response_type == 'none':
+            state = token.get('state', None)
+            if state:
+                token_items = [('state', state)]
+            else:
+                token_items = []
+
+        if request.response_mode == 'query':
+            headers['Location'] = add_params_to_uri(
+                request.redirect_uri, token_items, fragment=False)
+            return headers, body, status
+
+        if request.response_mode == 'fragment':
+            headers['Location'] = add_params_to_uri(
+                request.redirect_uri, token_items, fragment=True)
+            return headers, body, status
+
+        raise NotImplementedError(
+            'Subclasses must set a valid default_response_mode')
+
+    def _get_default_headers(self):
+        """Create default headers for grant responses."""
+        return {
+            'Content-Type': 'application/json',
+            'Cache-Control': 'no-store',
+            'Pragma': 'no-cache',
+        }
+
+    def _handle_redirects(self, request):
+        if request.redirect_uri is not None:
+            request.using_default_redirect_uri = False
+            log.debug('Using provided redirect_uri %s', request.redirect_uri)
+            if not is_absolute_uri(request.redirect_uri):
+                raise errors.InvalidRedirectURIError(request=request)
+
+            # The authorization server MUST verify that the redirection URI
+            # to which it will redirect the access token matches a
+            # redirection URI registered by the client as described in
+            # Section 3.1.2.
+            # https://tools.ietf.org/html/rfc6749#section-3.1.2
+            if not self.request_validator.validate_redirect_uri(
+                    request.client_id, request.redirect_uri, request):
+                raise errors.MismatchingRedirectURIError(request=request)
+        else:
+            request.redirect_uri = self.request_validator.get_default_redirect_uri(
+                request.client_id, request)
+            request.using_default_redirect_uri = True
+            log.debug('Using default redirect_uri %s.', request.redirect_uri)
+            if not request.redirect_uri:
+                raise errors.MissingRedirectURIError(request=request)
+            if not is_absolute_uri(request.redirect_uri):
+                raise errors.InvalidRedirectURIError(request=request)
+
+    def _create_cors_headers(self, request):
+        """If CORS is allowed, create the appropriate headers."""
+        if 'origin' not in request.headers:
+            return {}
+
+        origin = request.headers['origin']
+        if not is_secure_transport(origin):
+            log.debug('Origin "%s" is not HTTPS, CORS not allowed.', origin)
+            return {}
+        elif not self.request_validator.is_origin_allowed(
+            request.client_id, origin, request):
+            log.debug('Invalid origin "%s", CORS not allowed.', origin)
+            return {}
+        else:
+            log.debug('Valid origin "%s", injecting CORS headers.', origin)
+            return {'Access-Control-Allow-Origin': origin}
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/grant_types/client_credentials.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/grant_types/client_credentials.py
new file mode 100644
index 00000000..e7b46189
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/grant_types/client_credentials.py
@@ -0,0 +1,123 @@
+"""
+oauthlib.oauth2.rfc6749.grant_types
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+"""
+import json
+import logging
+
+from .. import errors
+from .base import GrantTypeBase
+
+log = logging.getLogger(__name__)
+
+
+class ClientCredentialsGrant(GrantTypeBase):
+
+    """`Client Credentials Grant`_
+
+    The client can request an access token using only its client
+    credentials (or other supported means of authentication) when the
+    client is requesting access to the protected resources under its
+    control, or those of another resource owner that have been previously
+    arranged with the authorization server (the method of which is beyond
+    the scope of this specification).
+
+    The client credentials grant type MUST only be used by confidential
+    clients::
+
+        +---------+                                  +---------------+
+        :         :                                  :               :
+        :         :>-- A - Client Authentication --->: Authorization :
+        : Client  :                                  :     Server    :
+        :         :<-- B ---- Access Token ---------<:               :
+        :         :                                  :               :
+        +---------+                                  +---------------+
+
+    Figure 6: Client Credentials Flow
+
+    The flow illustrated in Figure 6 includes the following steps:
+
+    (A)  The client authenticates with the authorization server and
+            requests an access token from the token endpoint.
+
+    (B)  The authorization server authenticates the client, and if valid,
+            issues an access token.
+
+    .. _`Client Credentials Grant`: https://tools.ietf.org/html/rfc6749#section-4.4
+    """
+
+    def create_token_response(self, request, token_handler):
+        """Return token or error in JSON format.
+
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :param token_handler: A token handler instance, for example of type
+                              oauthlib.oauth2.BearerToken.
+
+        If the access token request is valid and authorized, the
+        authorization server issues an access token as described in
+        `Section 5.1`_.  A refresh token SHOULD NOT be included.  If the request
+        failed client authentication or is invalid, the authorization server
+        returns an error response as described in `Section 5.2`_.
+
+        .. _`Section 5.1`: https://tools.ietf.org/html/rfc6749#section-5.1
+        .. _`Section 5.2`: https://tools.ietf.org/html/rfc6749#section-5.2
+        """
+        headers = self._get_default_headers()
+        try:
+            log.debug('Validating access token request, %r.', request)
+            self.validate_token_request(request)
+        except errors.OAuth2Error as e:
+            log.debug('Client error in token request. %s.', e)
+            headers.update(e.headers)
+            return headers, e.json, e.status_code
+
+        token = token_handler.create_token(request, refresh_token=False)
+
+        for modifier in self._token_modifiers:
+            token = modifier(token)
+
+        self.request_validator.save_token(token, request)
+
+        log.debug('Issuing token to client id %r (%r), %r.',
+                  request.client_id, request.client, token)
+        return headers, json.dumps(token), 200
+
+    def validate_token_request(self, request):
+        """
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        """
+        for validator in self.custom_validators.pre_token:
+            validator(request)
+
+        if not getattr(request, 'grant_type', None):
+            raise errors.InvalidRequestError('Request is missing grant type.',
+                                             request=request)
+
+        if not request.grant_type == 'client_credentials':
+            raise errors.UnsupportedGrantTypeError(request=request)
+
+        for param in ('grant_type', 'scope'):
+            if param in request.duplicate_params:
+                raise errors.InvalidRequestError(description='Duplicate %s parameter.' % param,
+                                                 request=request)
+
+        log.debug('Authenticating client, %r.', request)
+        if not self.request_validator.authenticate_client(request):
+            log.debug('Client authentication failed, %r.', request)
+            raise errors.InvalidClientError(request=request)
+        else:
+            if not hasattr(request.client, 'client_id'):
+                raise NotImplementedError('Authenticate client must set the '
+                                          'request.client.client_id attribute '
+                                          'in authenticate_client.')
+        # Ensure client is authorized use of this grant type
+        self.validate_grant_type(request)
+
+        request.client_id = request.client_id or request.client.client_id
+        log.debug('Authorizing access to client %r.', request.client_id)
+        self.validate_scopes(request)
+
+        for validator in self.custom_validators.post_token:
+            validator(request)
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/grant_types/implicit.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/grant_types/implicit.py
new file mode 100644
index 00000000..6110b6f3
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/grant_types/implicit.py
@@ -0,0 +1,376 @@
+"""
+oauthlib.oauth2.rfc6749.grant_types
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+"""
+import logging
+
+from oauthlib import common
+
+from .. import errors
+from .base import GrantTypeBase
+
+log = logging.getLogger(__name__)
+
+
+class ImplicitGrant(GrantTypeBase):
+
+    """`Implicit Grant`_
+
+    The implicit grant type is used to obtain access tokens (it does not
+    support the issuance of refresh tokens) and is optimized for public
+    clients known to operate a particular redirection URI.  These clients
+    are typically implemented in a browser using a scripting language
+    such as JavaScript.
+
+    Unlike the authorization code grant type, in which the client makes
+    separate requests for authorization and for an access token, the
+    client receives the access token as the result of the authorization
+    request.
+
+    The implicit grant type does not include client authentication, and
+    relies on the presence of the resource owner and the registration of
+    the redirection URI.  Because the access token is encoded into the
+    redirection URI, it may be exposed to the resource owner and other
+    applications residing on the same device::
+
+        +----------+
+        | Resource |
+        |  Owner   |
+        |          |
+        +----------+
+             ^
+             |
+            (B)
+        +----|-----+          Client Identifier     +---------------+
+        |         -+----(A)-- & Redirection URI --->|               |
+        |  User-   |                                | Authorization |
+        |  Agent  -|----(B)-- User authenticates -->|     Server    |
+        |          |                                |               |
+        |          |<---(C)--- Redirection URI ----<|               |
+        |          |          with Access Token     +---------------+
+        |          |            in Fragment
+        |          |                                +---------------+
+        |          |----(D)--- Redirection URI ---->|   Web-Hosted  |
+        |          |          without Fragment      |     Client    |
+        |          |                                |    Resource   |
+        |     (F)  |<---(E)------- Script ---------<|               |
+        |          |                                +---------------+
+        +-|--------+
+          |    |
+         (A)  (G) Access Token
+          |    |
+          ^    v
+        +---------+
+        |         |
+        |  Client |
+        |         |
+        +---------+
+
+   Note: The lines illustrating steps (A) and (B) are broken into two
+   parts as they pass through the user-agent.
+
+   Figure 4: Implicit Grant Flow
+
+   The flow illustrated in Figure 4 includes the following steps:
+
+   (A)  The client initiates the flow by directing the resource owner's
+        user-agent to the authorization endpoint.  The client includes
+        its client identifier, requested scope, local state, and a
+        redirection URI to which the authorization server will send the
+        user-agent back once access is granted (or denied).
+
+   (B)  The authorization server authenticates the resource owner (via
+        the user-agent) and establishes whether the resource owner
+        grants or denies the client's access request.
+
+   (C)  Assuming the resource owner grants access, the authorization
+        server redirects the user-agent back to the client using the
+        redirection URI provided earlier.  The redirection URI includes
+        the access token in the URI fragment.
+
+   (D)  The user-agent follows the redirection instructions by making a
+        request to the web-hosted client resource (which does not
+        include the fragment per [RFC2616]).  The user-agent retains the
+        fragment information locally.
+
+   (E)  The web-hosted client resource returns a web page (typically an
+        HTML document with an embedded script) capable of accessing the
+        full redirection URI including the fragment retained by the
+        user-agent, and extracting the access token (and other
+        parameters) contained in the fragment.
+
+   (F)  The user-agent executes the script provided by the web-hosted
+        client resource locally, which extracts the access token.
+
+   (G)  The user-agent passes the access token to the client.
+
+    See `Section 10.3`_ and `Section 10.16`_ for important security considerations
+    when using the implicit grant.
+
+    .. _`Implicit Grant`: https://tools.ietf.org/html/rfc6749#section-4.2
+    .. _`Section 10.3`: https://tools.ietf.org/html/rfc6749#section-10.3
+    .. _`Section 10.16`: https://tools.ietf.org/html/rfc6749#section-10.16
+    """
+
+    response_types = ['token']
+    grant_allows_refresh_token = False
+
+    def create_authorization_response(self, request, token_handler):
+        """Create an authorization response.
+
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :param token_handler: A token handler instance, for example of type
+                              oauthlib.oauth2.BearerToken.
+
+        The client constructs the request URI by adding the following
+        parameters to the query component of the authorization endpoint URI
+        using the "application/x-www-form-urlencoded" format, per `Appendix B`_:
+
+        response_type
+                REQUIRED.  Value MUST be set to "token" for standard OAuth2 implicit flow
+                           or "id_token token" or just "id_token" for OIDC implicit flow
+
+        client_id
+                REQUIRED.  The client identifier as described in `Section 2.2`_.
+
+        redirect_uri
+                OPTIONAL.  As described in `Section 3.1.2`_.
+
+        scope
+                OPTIONAL.  The scope of the access request as described by
+                `Section 3.3`_.
+
+        state
+                RECOMMENDED.  An opaque value used by the client to maintain
+                state between the request and callback.  The authorization
+                server includes this value when redirecting the user-agent back
+                to the client.  The parameter SHOULD be used for preventing
+                cross-site request forgery as described in `Section 10.12`_.
+
+        The authorization server validates the request to ensure that all
+        required parameters are present and valid.  The authorization server
+        MUST verify that the redirection URI to which it will redirect the
+        access token matches a redirection URI registered by the client as
+        described in `Section 3.1.2`_.
+
+        .. _`Section 2.2`: https://tools.ietf.org/html/rfc6749#section-2.2
+        .. _`Section 3.1.2`: https://tools.ietf.org/html/rfc6749#section-3.1.2
+        .. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3
+        .. _`Section 10.12`: https://tools.ietf.org/html/rfc6749#section-10.12
+        .. _`Appendix B`: https://tools.ietf.org/html/rfc6749#appendix-B
+        """
+        return self.create_token_response(request, token_handler)
+
+    def create_token_response(self, request, token_handler):
+        """Return token or error embedded in the URI fragment.
+
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :param token_handler: A token handler instance, for example of type
+                              oauthlib.oauth2.BearerToken.
+
+        If the resource owner grants the access request, the authorization
+        server issues an access token and delivers it to the client by adding
+        the following parameters to the fragment component of the redirection
+        URI using the "application/x-www-form-urlencoded" format, per
+        `Appendix B`_:
+
+        access_token
+                REQUIRED.  The access token issued by the authorization server.
+
+        token_type
+                REQUIRED.  The type of the token issued as described in
+                `Section 7.1`_.  Value is case insensitive.
+
+        expires_in
+                RECOMMENDED.  The lifetime in seconds of the access token.  For
+                example, the value "3600" denotes that the access token will
+                expire in one hour from the time the response was generated.
+                If omitted, the authorization server SHOULD provide the
+                expiration time via other means or document the default value.
+
+        scope
+                OPTIONAL, if identical to the scope requested by the client;
+                otherwise, REQUIRED.  The scope of the access token as
+                described by `Section 3.3`_.
+
+        state
+                REQUIRED if the "state" parameter was present in the client
+                authorization request.  The exact value received from the
+                client.
+
+        The authorization server MUST NOT issue a refresh token.
+
+        .. _`Appendix B`: https://tools.ietf.org/html/rfc6749#appendix-B
+        .. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3
+        .. _`Section 7.1`: https://tools.ietf.org/html/rfc6749#section-7.1
+        """
+        try:
+            self.validate_token_request(request)
+
+        # If the request fails due to a missing, invalid, or mismatching
+        # redirection URI, or if the client identifier is missing or invalid,
+        # the authorization server SHOULD inform the resource owner of the
+        # error and MUST NOT automatically redirect the user-agent to the
+        # invalid redirection URI.
+        except errors.FatalClientError as e:
+            log.debug('Fatal client error during validation of %r. %r.',
+                      request, e)
+            raise
+
+        # If the resource owner denies the access request or if the request
+        # fails for reasons other than a missing or invalid redirection URI,
+        # the authorization server informs the client by adding the following
+        # parameters to the fragment component of the redirection URI using the
+        # "application/x-www-form-urlencoded" format, per Appendix B:
+        # https://tools.ietf.org/html/rfc6749#appendix-B
+        except errors.OAuth2Error as e:
+            log.debug('Client error during validation of %r. %r.', request, e)
+            return {'Location': common.add_params_to_uri(request.redirect_uri, e.twotuples,
+                                                         fragment=True)}, None, 302
+
+        # In OIDC implicit flow it is possible to have a request_type that does not include the access_token!
+        # "id_token token" - return the access token and the id token
+        # "id_token" - don't return the access token
+        if "token" in request.response_type.split():
+            token = token_handler.create_token(request, refresh_token=False)
+        else:
+            token = {}
+
+        if request.state is not None:
+            token['state'] = request.state
+
+        for modifier in self._token_modifiers:
+            token = modifier(token, token_handler, request)
+
+        # In OIDC implicit flow it is possible to have a request_type that does
+        # not include the access_token! In this case there is no need to save a token.
+        if "token" in request.response_type.split():
+            self.request_validator.save_token(token, request)
+
+        return self.prepare_authorization_response(
+            request, token, {}, None, 302)
+
+    def validate_authorization_request(self, request):
+        """
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        """
+        return self.validate_token_request(request)
+
+    def validate_token_request(self, request):
+        """Check the token request for normal and fatal errors.
+
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+
+        This method is very similar to validate_authorization_request in
+        the AuthorizationCodeGrant but differ in a few subtle areas.
+
+        A normal error could be a missing response_type parameter or the client
+        attempting to access scope it is not allowed to ask authorization for.
+        Normal errors can safely be included in the redirection URI and
+        sent back to the client.
+
+        Fatal errors occur when the client_id or redirect_uri is invalid or
+        missing. These must be caught by the provider and handled, how this
+        is done is outside of the scope of OAuthLib but showing an error
+        page describing the issue is a good idea.
+        """
+
+        # First check for fatal errors
+
+        # If the request fails due to a missing, invalid, or mismatching
+        # redirection URI, or if the client identifier is missing or invalid,
+        # the authorization server SHOULD inform the resource owner of the
+        # error and MUST NOT automatically redirect the user-agent to the
+        # invalid redirection URI.
+
+        # First check duplicate parameters
+        for param in ('client_id', 'response_type', 'redirect_uri', 'scope', 'state'):
+            try:
+                duplicate_params = request.duplicate_params
+            except ValueError:
+                raise errors.InvalidRequestFatalError(description='Unable to parse query string', request=request)
+            if param in duplicate_params:
+                raise errors.InvalidRequestFatalError(description='Duplicate %s parameter.' % param, request=request)
+
+        # REQUIRED. The client identifier as described in Section 2.2.
+        # https://tools.ietf.org/html/rfc6749#section-2.2
+        if not request.client_id:
+            raise errors.MissingClientIdError(request=request)
+
+        if not self.request_validator.validate_client_id(request.client_id, request):
+            raise errors.InvalidClientIdError(request=request)
+
+        # OPTIONAL. As described in Section 3.1.2.
+        # https://tools.ietf.org/html/rfc6749#section-3.1.2
+        self._handle_redirects(request)
+
+        # Then check for normal errors.
+
+        request_info = self._run_custom_validators(request,
+                                                   self.custom_validators.all_pre)
+
+        # If the resource owner denies the access request or if the request
+        # fails for reasons other than a missing or invalid redirection URI,
+        # the authorization server informs the client by adding the following
+        # parameters to the fragment component of the redirection URI using the
+        # "application/x-www-form-urlencoded" format, per Appendix B.
+        # https://tools.ietf.org/html/rfc6749#appendix-B
+
+        # Note that the correct parameters to be added are automatically
+        # populated through the use of specific exceptions
+
+        # REQUIRED.
+        if request.response_type is None:
+            raise errors.MissingResponseTypeError(request=request)
+        # Value MUST be one of our registered types: "token" by default or if using OIDC "id_token" or "id_token token"
+        elif not set(request.response_type.split()).issubset(self.response_types):
+            raise errors.UnsupportedResponseTypeError(request=request)
+
+        log.debug('Validating use of response_type token for client %r (%r).',
+                  request.client_id, request.client)
+        if not self.request_validator.validate_response_type(request.client_id,
+                                                             request.response_type,
+                                                             request.client, request):
+
+            log.debug('Client %s is not authorized to use response_type %s.',
+                      request.client_id, request.response_type)
+            raise errors.UnauthorizedClientError(request=request)
+
+        # OPTIONAL. The scope of the access request as described by Section 3.3
+        # https://tools.ietf.org/html/rfc6749#section-3.3
+        self.validate_scopes(request)
+
+        request_info.update({
+            'client_id': request.client_id,
+            'redirect_uri': request.redirect_uri,
+            'response_type': request.response_type,
+            'state': request.state,
+            'request': request,
+        })
+
+        request_info = self._run_custom_validators(
+            request,
+            self.custom_validators.all_post,
+            request_info
+        )
+
+        return request.scopes, request_info
+
+    def _run_custom_validators(self,
+                               request,
+                               validations,
+                               request_info=None):
+        # Make a copy so we don't modify the existing request_info dict
+        request_info = {} if request_info is None else request_info.copy()
+        # For implicit grant, auth_validators and token_validators are
+        # basically equivalent since the token is returned from the
+        # authorization endpoint.
+        for validator in validations:
+            result = validator(request)
+            if result is not None:
+                request_info.update(result)
+        return request_info
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/grant_types/refresh_token.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/grant_types/refresh_token.py
new file mode 100644
index 00000000..ce33df0e
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/grant_types/refresh_token.py
@@ -0,0 +1,136 @@
+"""
+oauthlib.oauth2.rfc6749.grant_types
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+"""
+import json
+import logging
+
+from .. import errors, utils
+from .base import GrantTypeBase
+
+log = logging.getLogger(__name__)
+
+
+class RefreshTokenGrant(GrantTypeBase):
+
+    """`Refresh token grant`_
+
+    .. _`Refresh token grant`: https://tools.ietf.org/html/rfc6749#section-6
+    """
+
+    def __init__(self, request_validator=None,
+                 issue_new_refresh_tokens=True,
+                 **kwargs):
+        super().__init__(
+            request_validator,
+            issue_new_refresh_tokens=issue_new_refresh_tokens,
+            **kwargs)
+
+    def create_token_response(self, request, token_handler):
+        """Create a new access token from a refresh_token.
+
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :param token_handler: A token handler instance, for example of type
+                              oauthlib.oauth2.BearerToken.
+
+        If valid and authorized, the authorization server issues an access
+        token as described in `Section 5.1`_. If the request failed
+        verification or is invalid, the authorization server returns an error
+        response as described in `Section 5.2`_.
+
+        The authorization server MAY issue a new refresh token, in which case
+        the client MUST discard the old refresh token and replace it with the
+        new refresh token. The authorization server MAY revoke the old
+        refresh token after issuing a new refresh token to the client. If a
+        new refresh token is issued, the refresh token scope MUST be
+        identical to that of the refresh token included by the client in the
+        request.
+
+        .. _`Section 5.1`: https://tools.ietf.org/html/rfc6749#section-5.1
+        .. _`Section 5.2`: https://tools.ietf.org/html/rfc6749#section-5.2
+        """
+        headers = self._get_default_headers()
+        try:
+            log.debug('Validating refresh token request, %r.', request)
+            self.validate_token_request(request)
+        except errors.OAuth2Error as e:
+            log.debug('Client error in token request, %s.', e)
+            headers.update(e.headers)
+            return headers, e.json, e.status_code
+
+        token = token_handler.create_token(request,
+                                           refresh_token=self.issue_new_refresh_tokens)
+
+        for modifier in self._token_modifiers:
+            token = modifier(token, token_handler, request)
+
+        self.request_validator.save_token(token, request)
+
+        log.debug('Issuing new token to client id %r (%r), %r.',
+                  request.client_id, request.client, token)
+        headers.update(self._create_cors_headers(request))
+        return headers, json.dumps(token), 200
+
+    def validate_token_request(self, request):
+        """
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        """
+        # REQUIRED. Value MUST be set to "refresh_token".
+        if request.grant_type != 'refresh_token':
+            raise errors.UnsupportedGrantTypeError(request=request)
+
+        for validator in self.custom_validators.pre_token:
+            validator(request)
+
+        if request.refresh_token is None:
+            raise errors.InvalidRequestError(
+                description='Missing refresh token parameter.',
+                request=request)
+
+        # Because refresh tokens are typically long-lasting credentials used to
+        # request additional access tokens, the refresh token is bound to the
+        # client to which it was issued.  If the client type is confidential or
+        # the client was issued client credentials (or assigned other
+        # authentication requirements), the client MUST authenticate with the
+        # authorization server as described in Section 3.2.1.
+        # https://tools.ietf.org/html/rfc6749#section-3.2.1
+        if self.request_validator.client_authentication_required(request):
+            log.debug('Authenticating client, %r.', request)
+            if not self.request_validator.authenticate_client(request):
+                log.debug('Invalid client (%r), denying access.', request)
+                raise errors.InvalidClientError(request=request)
+        elif not self.request_validator.authenticate_client_id(request.client_id, request):
+            log.debug('Client authentication failed, %r.', request)
+            raise errors.InvalidClientError(request=request)
+
+        # Ensure client is authorized use of this grant type
+        self.validate_grant_type(request)
+
+        # REQUIRED. The refresh token issued to the client.
+        log.debug('Validating refresh token %s for client %r.',
+                  request.refresh_token, request.client)
+        if not self.request_validator.validate_refresh_token(
+                request.refresh_token, request.client, request):
+            log.debug('Invalid refresh token, %s, for client %r.',
+                      request.refresh_token, request.client)
+            raise errors.InvalidGrantError(request=request)
+
+        original_scopes = utils.scope_to_list(
+            self.request_validator.get_original_scopes(
+                request.refresh_token, request))
+
+        if request.scope:
+            request.scopes = utils.scope_to_list(request.scope)
+            if (not all(s in original_scopes for s in request.scopes)
+                and not self.request_validator.is_within_original_scope(
+                    request.scopes, request.refresh_token, request)):
+                log.debug('Refresh token %s lack requested scopes, %r.',
+                          request.refresh_token, request.scopes)
+                raise errors.InvalidScopeError(request=request)
+        else:
+            request.scopes = original_scopes
+
+        for validator in self.custom_validators.post_token:
+            validator(request)
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/grant_types/resource_owner_password_credentials.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/grant_types/resource_owner_password_credentials.py
new file mode 100644
index 00000000..4b0de5bf
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/grant_types/resource_owner_password_credentials.py
@@ -0,0 +1,199 @@
+"""
+oauthlib.oauth2.rfc6749.grant_types
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+"""
+import json
+import logging
+
+from .. import errors
+from .base import GrantTypeBase
+
+log = logging.getLogger(__name__)
+
+
+class ResourceOwnerPasswordCredentialsGrant(GrantTypeBase):
+
+    """`Resource Owner Password Credentials Grant`_
+
+    The resource owner password credentials grant type is suitable in
+    cases where the resource owner has a trust relationship with the
+    client, such as the device operating system or a highly privileged
+    application.  The authorization server should take special care when
+    enabling this grant type and only allow it when other flows are not
+    viable.
+
+    This grant type is suitable for clients capable of obtaining the
+    resource owner's credentials (username and password, typically using
+    an interactive form).  It is also used to migrate existing clients
+    using direct authentication schemes such as HTTP Basic or Digest
+    authentication to OAuth by converting the stored credentials to an
+    access token::
+
+            +----------+
+            | Resource |
+            |  Owner   |
+            |          |
+            +----------+
+                 v
+                 |    Resource Owner
+                (A) Password Credentials
+                 |
+                 v
+            +---------+                                  +---------------+
+            |         |>--(B)---- Resource Owner ------->|               |
+            |         |         Password Credentials     | Authorization |
+            | Client  |                                  |     Server    |
+            |         |<--(C)---- Access Token ---------<|               |
+            |         |    (w/ Optional Refresh Token)   |               |
+            +---------+                                  +---------------+
+
+    Figure 5: Resource Owner Password Credentials Flow
+
+    The flow illustrated in Figure 5 includes the following steps:
+
+    (A)  The resource owner provides the client with its username and
+            password.
+
+    (B)  The client requests an access token from the authorization
+            server's token endpoint by including the credentials received
+            from the resource owner.  When making the request, the client
+            authenticates with the authorization server.
+
+    (C)  The authorization server authenticates the client and validates
+            the resource owner credentials, and if valid, issues an access
+            token.
+
+    .. _`Resource Owner Password Credentials Grant`: https://tools.ietf.org/html/rfc6749#section-4.3
+    """
+
+    def create_token_response(self, request, token_handler):
+        """Return token or error in json format.
+
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :param token_handler: A token handler instance, for example of type
+                              oauthlib.oauth2.BearerToken.
+
+        If the access token request is valid and authorized, the
+        authorization server issues an access token and optional refresh
+        token as described in `Section 5.1`_.  If the request failed client
+        authentication or is invalid, the authorization server returns an
+        error response as described in `Section 5.2`_.
+
+        .. _`Section 5.1`: https://tools.ietf.org/html/rfc6749#section-5.1
+        .. _`Section 5.2`: https://tools.ietf.org/html/rfc6749#section-5.2
+        """
+        headers = self._get_default_headers()
+        try:
+            if self.request_validator.client_authentication_required(request):
+                log.debug('Authenticating client, %r.', request)
+                if not self.request_validator.authenticate_client(request):
+                    log.debug('Client authentication failed, %r.', request)
+                    raise errors.InvalidClientError(request=request)
+            elif not self.request_validator.authenticate_client_id(request.client_id, request):
+                log.debug('Client authentication failed, %r.', request)
+                raise errors.InvalidClientError(request=request)
+            log.debug('Validating access token request, %r.', request)
+            self.validate_token_request(request)
+        except errors.OAuth2Error as e:
+            log.debug('Client error in token request, %s.', e)
+            headers.update(e.headers)
+            return headers, e.json, e.status_code
+
+        token = token_handler.create_token(request, self.refresh_token)
+
+        for modifier in self._token_modifiers:
+            token = modifier(token)
+
+        self.request_validator.save_token(token, request)
+
+        log.debug('Issuing token %r to client id %r (%r) and username %s.',
+                  token, request.client_id, request.client, request.username)
+        return headers, json.dumps(token), 200
+
+    def validate_token_request(self, request):
+        """
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+
+        The client makes a request to the token endpoint by adding the
+        following parameters using the "application/x-www-form-urlencoded"
+        format per Appendix B with a character encoding of UTF-8 in the HTTP
+        request entity-body:
+
+        grant_type
+                REQUIRED.  Value MUST be set to "password".
+
+        username
+                REQUIRED.  The resource owner username.
+
+        password
+                REQUIRED.  The resource owner password.
+
+        scope
+                OPTIONAL.  The scope of the access request as described by
+                `Section 3.3`_.
+
+        If the client type is confidential or the client was issued client
+        credentials (or assigned other authentication requirements), the
+        client MUST authenticate with the authorization server as described
+        in `Section 3.2.1`_.
+
+        The authorization server MUST:
+
+        o  require client authentication for confidential clients or for any
+            client that was issued client credentials (or with other
+            authentication requirements),
+
+        o  authenticate the client if client authentication is included, and
+
+        o  validate the resource owner password credentials using its
+            existing password validation algorithm.
+
+        Since this access token request utilizes the resource owner's
+        password, the authorization server MUST protect the endpoint against
+        brute force attacks (e.g., using rate-limitation or generating
+        alerts).
+
+        .. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3
+        .. _`Section 3.2.1`: https://tools.ietf.org/html/rfc6749#section-3.2.1
+        """
+        for validator in self.custom_validators.pre_token:
+            validator(request)
+
+        for param in ('grant_type', 'username', 'password'):
+            if not getattr(request, param, None):
+                raise errors.InvalidRequestError(
+                    'Request is missing %s parameter.' % param, request=request)
+
+        for param in ('grant_type', 'username', 'password', 'scope'):
+            if param in request.duplicate_params:
+                raise errors.InvalidRequestError(description='Duplicate %s parameter.' % param, request=request)
+
+        # This error should rarely (if ever) occur if requests are routed to
+        # grant type handlers based on the grant_type parameter.
+        if not request.grant_type == 'password':
+            raise errors.UnsupportedGrantTypeError(request=request)
+
+        log.debug('Validating username %s.', request.username)
+        if not self.request_validator.validate_user(request.username,
+                                                    request.password, request.client, request):
+            raise errors.InvalidGrantError(
+                'Invalid credentials given.', request=request)
+        else:
+            if not hasattr(request.client, 'client_id'):
+                raise NotImplementedError(
+                    'Validate user must set the '
+                    'request.client.client_id attribute '
+                    'in authenticate_client.')
+        log.debug('Authorizing access to user %r.', request.user)
+
+        # Ensure client is authorized use of this grant type
+        self.validate_grant_type(request)
+
+        if request.client:
+            request.client_id = request.client_id or request.client.client_id
+        self.validate_scopes(request)
+
+        for validator in self.custom_validators.post_token:
+            validator(request)
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/parameters.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/parameters.py
new file mode 100644
index 00000000..8f6ce2c7
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/parameters.py
@@ -0,0 +1,471 @@
+"""
+oauthlib.oauth2.rfc6749.parameters
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This module contains methods related to `Section 4`_ of the OAuth 2 RFC.
+
+.. _`Section 4`: https://tools.ietf.org/html/rfc6749#section-4
+"""
+import json
+import os
+import time
+import urllib.parse as urlparse
+
+from oauthlib.common import add_params_to_qs, add_params_to_uri
+from oauthlib.signals import scope_changed
+
+from .errors import (
+    InsecureTransportError, MismatchingStateError, MissingCodeError,
+    MissingTokenError, MissingTokenTypeError, raise_from_error,
+)
+from .tokens import OAuth2Token
+from .utils import is_secure_transport, list_to_scope, scope_to_list
+
+
+def prepare_grant_uri(uri, client_id, response_type, redirect_uri=None,
+                      scope=None, state=None, code_challenge=None, code_challenge_method='plain', **kwargs):
+    """Prepare the authorization grant request URI.
+
+    The client constructs the request URI by adding the following
+    parameters to the query component of the authorization endpoint URI
+    using the ``application/x-www-form-urlencoded`` format as defined by
+    [`W3C.REC-html401-19991224`_]:
+
+    :param uri:
+    :param client_id: The client identifier as described in `Section 2.2`_.
+    :param response_type: To indicate which OAuth 2 grant/flow is required,
+                          "code" and "token".
+    :param redirect_uri: The client provided URI to redirect back to after
+                         authorization as described in `Section 3.1.2`_.
+    :param scope: The scope of the access request as described by
+                  `Section 3.3`_.
+    :param state: An opaque value used by the client to maintain
+                  state between the request and callback.  The authorization
+                  server includes this value when redirecting the user-agent
+                  back to the client.  The parameter SHOULD be used for
+                  preventing cross-site request forgery as described in
+                  `Section 10.12`_.
+    :param code_challenge: PKCE parameter. A challenge derived from the 
+                           code_verifier that is sent in the authorization 
+                           request, to be verified against later.
+    :param code_challenge_method: PKCE parameter. A method that was used to derive the 
+                                  code_challenge. Defaults to "plain" if not present in the request.
+    :param kwargs: Extra arguments to embed in the grant/authorization URL.
+
+    An example of an authorization code grant authorization URL:
+
+    .. code-block:: http
+
+        GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz
+            &code_challenge=kjasBS523KdkAILD2k78NdcJSk2k3KHG6&code_challenge_method=S256
+            &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
+        Host: server.example.com
+
+    .. _`W3C.REC-html401-19991224`: https://tools.ietf.org/html/rfc6749#ref-W3C.REC-html401-19991224
+    .. _`Section 2.2`: https://tools.ietf.org/html/rfc6749#section-2.2
+    .. _`Section 3.1.2`: https://tools.ietf.org/html/rfc6749#section-3.1.2
+    .. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3
+    .. _`section 10.12`: https://tools.ietf.org/html/rfc6749#section-10.12
+    """
+    if not is_secure_transport(uri):
+        raise InsecureTransportError()
+
+    params = [(('response_type', response_type)),
+              (('client_id', client_id))]
+
+    if redirect_uri:
+        params.append(('redirect_uri', redirect_uri))
+    if scope:
+        params.append(('scope', list_to_scope(scope)))
+    if state:
+        params.append(('state', state))
+    if code_challenge is not None:
+        params.append(('code_challenge', code_challenge))
+        params.append(('code_challenge_method', code_challenge_method))
+
+    for k in kwargs:
+        if kwargs[k]:
+            params.append((str(k), kwargs[k]))
+
+    return add_params_to_uri(uri, params)
+
+
+def prepare_token_request(grant_type, body='', include_client_id=True, code_verifier=None, **kwargs):
+    """Prepare the access token request.
+
+    The client makes a request to the token endpoint by adding the
+    following parameters using the ``application/x-www-form-urlencoded``
+    format in the HTTP request entity-body:
+
+    :param grant_type: To indicate grant type being used, i.e. "password",
+                       "authorization_code" or "client_credentials".
+
+    :param body: Existing request body (URL encoded string) to embed parameters
+                 into. This may contain extra parameters. Default ''.
+
+    :param include_client_id: `True` (default) to send the `client_id` in the
+                              body of the upstream request. This is required
+                              if the client is not authenticating with the
+                              authorization server as described in
+                              `Section 3.2.1`_.
+    :type include_client_id: Boolean
+
+    :param client_id: Unicode client identifier. Will only appear if
+                      `include_client_id` is True. *
+
+    :param client_secret: Unicode client secret. Will only appear if set to a
+                          value that is not `None`. Invoking this function with
+                          an empty string will send an empty `client_secret`
+                          value to the server. *
+
+    :param code: If using authorization_code grant, pass the previously
+                 obtained authorization code as the ``code`` argument. *
+
+    :param redirect_uri: If the "redirect_uri" parameter was included in the
+                         authorization request as described in
+                         `Section 4.1.1`_, and their values MUST be identical. *
+
+    :param code_verifier: PKCE parameter. A cryptographically random string that is used to correlate the
+                          authorization request to the token request.
+
+    :param kwargs: Extra arguments to embed in the request body.
+
+    Parameters marked with a `*` above are not explicit arguments in the
+    function signature, but are specially documented arguments for items
+    appearing in the generic `**kwargs` keyworded input.
+
+    An example of an authorization code token request body:
+
+    .. code-block:: http
+
+        grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
+        &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
+
+    .. _`Section 4.1.1`: https://tools.ietf.org/html/rfc6749#section-4.1.1
+    """
+    params = [('grant_type', grant_type)]
+
+    if 'scope' in kwargs:
+        kwargs['scope'] = list_to_scope(kwargs['scope'])
+
+    # pull the `client_id` out of the kwargs.
+    client_id = kwargs.pop('client_id', None)
+    if include_client_id:
+        if client_id is not None:
+            params.append(('client_id', client_id))
+
+    # use code_verifier if code_challenge was passed in the authorization request
+    if code_verifier is not None:
+        params.append(('code_verifier', code_verifier))
+
+    # the kwargs iteration below only supports including boolean truth (truthy)
+    # values, but some servers may require an empty string for `client_secret`
+    client_secret = kwargs.pop('client_secret', None)
+    if client_secret is not None:
+        params.append(('client_secret', client_secret))
+
+    # this handles: `code`, `redirect_uri`, and other undocumented params
+    for k in kwargs:
+        if kwargs[k]:
+            params.append((str(k), kwargs[k]))
+
+    return add_params_to_qs(body, params)
+
+
+def prepare_token_revocation_request(url, token, token_type_hint="access_token",
+        callback=None, body='', **kwargs):
+    """Prepare a token revocation request.
+
+    The client constructs the request by including the following parameters
+    using the ``application/x-www-form-urlencoded`` format in the HTTP request
+    entity-body:
+
+    :param token: REQUIRED.  The token that the client wants to get revoked.
+
+    :param token_type_hint: OPTIONAL.  A hint about the type of the token
+                            submitted for revocation. Clients MAY pass this
+                            parameter in order to help the authorization server
+                            to optimize the token lookup.  If the server is
+                            unable to locate the token using the given hint, it
+                            MUST extend its search across all of its supported
+                            token types.  An authorization server MAY ignore
+                            this parameter, particularly if it is able to detect
+                            the token type automatically.
+
+    This specification defines two values for `token_type_hint`:
+
+        * access_token: An access token as defined in [RFC6749],
+             `Section 1.4`_
+
+        * refresh_token: A refresh token as defined in [RFC6749],
+             `Section 1.5`_
+
+        Specific implementations, profiles, and extensions of this
+        specification MAY define other values for this parameter using the
+        registry defined in `Section 4.1.2`_.
+
+    .. _`Section 1.4`: https://tools.ietf.org/html/rfc6749#section-1.4
+    .. _`Section 1.5`: https://tools.ietf.org/html/rfc6749#section-1.5
+    .. _`Section 4.1.2`: https://tools.ietf.org/html/rfc7009#section-4.1.2
+
+    """
+    if not is_secure_transport(url):
+        raise InsecureTransportError()
+
+    params = [('token', token)]
+
+    if token_type_hint:
+        params.append(('token_type_hint', token_type_hint))
+
+    for k in kwargs:
+        if kwargs[k]:
+            params.append((str(k), kwargs[k]))
+
+    headers = {'Content-Type': 'application/x-www-form-urlencoded'}
+
+    if callback:
+        params.append(('callback', callback))
+        return add_params_to_uri(url, params), headers, body
+    else:
+        return url, headers, add_params_to_qs(body, params)
+
+
+def parse_authorization_code_response(uri, state=None):
+    """Parse authorization grant response URI into a dict.
+
+    If the resource owner grants the access request, the authorization
+    server issues an authorization code and delivers it to the client by
+    adding the following parameters to the query component of the
+    redirection URI using the ``application/x-www-form-urlencoded`` format:
+
+    **code**
+            REQUIRED.  The authorization code generated by the
+            authorization server.  The authorization code MUST expire
+            shortly after it is issued to mitigate the risk of leaks.  A
+            maximum authorization code lifetime of 10 minutes is
+            RECOMMENDED.  The client MUST NOT use the authorization code
+            more than once.  If an authorization code is used more than
+            once, the authorization server MUST deny the request and SHOULD
+            revoke (when possible) all tokens previously issued based on
+            that authorization code.  The authorization code is bound to
+            the client identifier and redirection URI.
+
+    **state**
+            REQUIRED if the "state" parameter was present in the client
+            authorization request.  The exact value received from the
+            client.
+
+    :param uri: The full redirect URL back to the client.
+    :param state: The state parameter from the authorization request.
+
+    For example, the authorization server redirects the user-agent by
+    sending the following HTTP response:
+
+    .. code-block:: http
+
+        HTTP/1.1 302 Found
+        Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA
+                &state=xyz
+
+    """
+    if not is_secure_transport(uri):
+        raise InsecureTransportError()
+
+    query = urlparse.urlparse(uri).query
+    params = dict(urlparse.parse_qsl(query))
+
+    if state and params.get('state', None) != state:
+        raise MismatchingStateError()
+
+    if 'error' in params:
+        raise_from_error(params.get('error'), params)
+
+    if not 'code' in params:
+        raise MissingCodeError("Missing code parameter in response.")
+
+    return params
+
+
+def parse_implicit_response(uri, state=None, scope=None):
+    """Parse the implicit token response URI into a dict.
+
+    If the resource owner grants the access request, the authorization
+    server issues an access token and delivers it to the client by adding
+    the following parameters to the fragment component of the redirection
+    URI using the ``application/x-www-form-urlencoded`` format:
+
+    **access_token**
+            REQUIRED.  The access token issued by the authorization server.
+
+    **token_type**
+            REQUIRED.  The type of the token issued as described in
+            Section 7.1.  Value is case insensitive.
+
+    **expires_in**
+            RECOMMENDED.  The lifetime in seconds of the access token.  For
+            example, the value "3600" denotes that the access token will
+            expire in one hour from the time the response was generated.
+            If omitted, the authorization server SHOULD provide the
+            expiration time via other means or document the default value.
+
+    **scope**
+            OPTIONAL, if identical to the scope requested by the client,
+            otherwise REQUIRED.  The scope of the access token as described
+            by Section 3.3.
+
+    **state**
+            REQUIRED if the "state" parameter was present in the client
+            authorization request.  The exact value received from the
+            client.
+
+    :param uri:
+    :param state:
+    :param scope:
+
+    Similar to the authorization code response, but with a full token provided
+    in the URL fragment:
+
+    .. code-block:: http
+
+        HTTP/1.1 302 Found
+        Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA
+                &state=xyz&token_type=example&expires_in=3600
+    """
+    if not is_secure_transport(uri):
+        raise InsecureTransportError()
+
+    fragment = urlparse.urlparse(uri).fragment
+    params = dict(urlparse.parse_qsl(fragment, keep_blank_values=True))
+
+    for key in ('expires_in',):
+        if key in params:  # cast things to int
+            params[key] = int(params[key])
+
+    if 'scope' in params:
+        params['scope'] = scope_to_list(params['scope'])
+
+    if 'expires_in' in params:
+        params['expires_at'] = time.time() + int(params['expires_in'])
+
+    if state and params.get('state', None) != state:
+        raise ValueError("Mismatching or missing state in params.")
+
+    params = OAuth2Token(params, old_scope=scope)
+    validate_token_parameters(params)
+    return params
+
+
+def parse_token_response(body, scope=None):
+    """Parse the JSON token response body into a dict.
+
+    The authorization server issues an access token and optional refresh
+    token, and constructs the response by adding the following parameters
+    to the entity body of the HTTP response with a 200 (OK) status code:
+
+    access_token
+            REQUIRED.  The access token issued by the authorization server.
+    token_type
+            REQUIRED.  The type of the token issued as described in
+            `Section 7.1`_.  Value is case insensitive.
+    expires_in
+            RECOMMENDED.  The lifetime in seconds of the access token.  For
+            example, the value "3600" denotes that the access token will
+            expire in one hour from the time the response was generated.
+            If omitted, the authorization server SHOULD provide the
+            expiration time via other means or document the default value.
+    refresh_token
+            OPTIONAL.  The refresh token which can be used to obtain new
+            access tokens using the same authorization grant as described
+            in `Section 6`_.
+    scope
+            OPTIONAL, if identical to the scope requested by the client,
+            otherwise REQUIRED.  The scope of the access token as described
+            by `Section 3.3`_.
+
+    The parameters are included in the entity body of the HTTP response
+    using the "application/json" media type as defined by [`RFC4627`_].  The
+    parameters are serialized into a JSON structure by adding each
+    parameter at the highest structure level.  Parameter names and string
+    values are included as JSON strings.  Numerical values are included
+    as JSON numbers.  The order of parameters does not matter and can
+    vary.
+
+    :param body: The full json encoded response body.
+    :param scope: The scope requested during authorization.
+
+    For example:
+
+    .. code-block:: http
+
+        HTTP/1.1 200 OK
+        Content-Type: application/json
+        Cache-Control: no-store
+        Pragma: no-cache
+
+        {
+            "access_token":"2YotnFZFEjr1zCsicMWpAA",
+            "token_type":"example",
+            "expires_in":3600,
+            "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
+            "example_parameter":"example_value"
+        }
+
+    .. _`Section 7.1`: https://tools.ietf.org/html/rfc6749#section-7.1
+    .. _`Section 6`: https://tools.ietf.org/html/rfc6749#section-6
+    .. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3
+    .. _`RFC4627`: https://tools.ietf.org/html/rfc4627
+    """
+    try:
+        params = json.loads(body)
+    except ValueError:
+
+        # Fall back to URL-encoded string, to support old implementations,
+        # including (at time of writing) Facebook. See:
+        #   https://github.com/oauthlib/oauthlib/issues/267
+
+        params = dict(urlparse.parse_qsl(body))
+        for key in ('expires_in',):
+            if key in params:  # cast things to int
+                params[key] = int(params[key])
+
+    if 'scope' in params:
+        params['scope'] = scope_to_list(params['scope'])
+
+    if 'expires_in' in params:
+        if params['expires_in'] is None:
+            params.pop('expires_in')
+        else:
+            params['expires_at'] = time.time() + int(params['expires_in'])
+
+    params = OAuth2Token(params, old_scope=scope)
+    validate_token_parameters(params)
+    return params
+
+
+def validate_token_parameters(params):
+    """Ensures token presence, token type, expiration and scope in params."""
+    if 'error' in params:
+        raise_from_error(params.get('error'), params)
+
+    if not 'access_token' in params:
+        raise MissingTokenError(description="Missing access token parameter.")
+
+    if not 'token_type' in params:
+        if os.environ.get('OAUTHLIB_STRICT_TOKEN_TYPE'):
+            raise MissingTokenTypeError()
+
+    # If the issued access token scope is different from the one requested by
+    # the client, the authorization server MUST include the "scope" response
+    # parameter to inform the client of the actual scope granted.
+    # https://tools.ietf.org/html/rfc6749#section-3.3
+    if params.scope_changed:
+        message = 'Scope has changed from "{old}" to "{new}".'.format(
+            old=params.old_scope, new=params.scope,
+        )
+        scope_changed.send(message=message, old=params.old_scopes, new=params.scopes)
+        if not os.environ.get('OAUTHLIB_RELAX_TOKEN_SCOPE', None):
+            w = Warning(message)
+            w.token = params
+            w.old_scope = params.old_scopes
+            w.new_scope = params.scopes
+            raise w
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/request_validator.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/request_validator.py
new file mode 100644
index 00000000..3910c0b9
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/request_validator.py
@@ -0,0 +1,680 @@
+"""
+oauthlib.oauth2.rfc6749.request_validator
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+"""
+import logging
+
+log = logging.getLogger(__name__)
+
+
+class RequestValidator:
+
+    def client_authentication_required(self, request, *args, **kwargs):
+        """Determine if client authentication is required for current request.
+
+        According to the rfc6749, client authentication is required in the following cases:
+            - Resource Owner Password Credentials Grant, when Client type is Confidential or when
+              Client was issued client credentials or whenever Client provided client
+              authentication, see `Section 4.3.2`_.
+            - Authorization Code Grant, when Client type is Confidential or when Client was issued
+              client credentials or whenever Client provided client authentication,
+              see `Section 4.1.3`_.
+            - Refresh Token Grant, when Client type is Confidential or when Client was issued
+              client credentials or whenever Client provided client authentication, see
+              `Section 6`_
+
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :rtype: True or False
+
+        Method is used by:
+            - Authorization Code Grant
+            - Resource Owner Password Credentials Grant
+            - Refresh Token Grant
+
+        .. _`Section 4.3.2`: https://tools.ietf.org/html/rfc6749#section-4.3.2
+        .. _`Section 4.1.3`: https://tools.ietf.org/html/rfc6749#section-4.1.3
+        .. _`Section 6`: https://tools.ietf.org/html/rfc6749#section-6
+        """
+        return True
+
+    def authenticate_client(self, request, *args, **kwargs):
+        """Authenticate client through means outside the OAuth 2 spec.
+
+        Means of authentication is negotiated beforehand and may for example
+        be `HTTP Basic Authentication Scheme`_ which utilizes the Authorization
+        header.
+
+        Headers may be accesses through request.headers and parameters found in
+        both body and query can be obtained by direct attribute access, i.e.
+        request.client_id for client_id in the URL query.
+		
+        The authentication process is required to contain the identification of
+        the client (i.e. search the database based on the client_id). In case the
+        client doesn't exist based on the received client_id, this method has to
+        return False and the HTTP response created by the library will contain
+        'invalid_client' message. 
+
+        After the client identification succeeds, this method needs to set the
+        client on the request, i.e. request.client = client. A client object's
+        class must contain the 'client_id' attribute and the 'client_id' must have
+        a value.
+
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :rtype: True or False
+
+        Method is used by:
+            - Authorization Code Grant
+            - Resource Owner Password Credentials Grant (may be disabled)
+            - Client Credentials Grant
+            - Refresh Token Grant
+
+        .. _`HTTP Basic Authentication Scheme`: https://tools.ietf.org/html/rfc1945#section-11.1
+        """
+        raise NotImplementedError('Subclasses must implement this method.')
+
+    def authenticate_client_id(self, client_id, request, *args, **kwargs):
+        """Ensure client_id belong to a non-confidential client.
+
+        A non-confidential client is one that is not required to authenticate
+        through other means, such as using HTTP Basic.
+
+        Note, while not strictly necessary it can often be very convenient
+        to set request.client to the client object associated with the
+        given client_id.
+
+        :param client_id: Unicode client identifier.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :rtype: True or False
+
+        Method is used by:
+            - Authorization Code Grant
+        """
+        raise NotImplementedError('Subclasses must implement this method.')
+
+    def confirm_redirect_uri(self, client_id, code, redirect_uri, client, request,
+                             *args, **kwargs):
+        """Ensure that the authorization process represented by this authorization
+        code began with this 'redirect_uri'.
+
+        If the client specifies a redirect_uri when obtaining code then that
+        redirect URI must be bound to the code and verified equal in this
+        method, according to RFC 6749 section 4.1.3.  Do not compare against
+        the client's allowed redirect URIs, but against the URI used when the
+        code was saved.
+
+        :param client_id: Unicode client identifier.
+        :param code: Unicode authorization_code.
+        :param redirect_uri: Unicode absolute URI.
+        :param client: Client object set by you, see ``.authenticate_client``.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :rtype: True or False
+
+        Method is used by:
+            - Authorization Code Grant (during token request)
+        """
+        raise NotImplementedError('Subclasses must implement this method.')
+
+    def get_default_redirect_uri(self, client_id, request, *args, **kwargs):
+        """Get the default redirect URI for the client.
+
+        :param client_id: Unicode client identifier.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :rtype: The default redirect URI for the client
+
+        Method is used by:
+            - Authorization Code Grant
+            - Implicit Grant
+        """
+        raise NotImplementedError('Subclasses must implement this method.')
+
+    def get_default_scopes(self, client_id, request, *args, **kwargs):
+        """Get the default scopes for the client.
+
+        :param client_id: Unicode client identifier.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :rtype: List of default scopes
+
+        Method is used by all core grant types:
+            - Authorization Code Grant
+            - Implicit Grant
+            - Resource Owner Password Credentials Grant
+            - Client Credentials grant
+        """
+        raise NotImplementedError('Subclasses must implement this method.')
+
+    def get_original_scopes(self, refresh_token, request, *args, **kwargs):
+        """Get the list of scopes associated with the refresh token.
+
+        :param refresh_token: Unicode refresh token.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :rtype: List of scopes.
+
+        Method is used by:
+            - Refresh token grant
+        """
+        raise NotImplementedError('Subclasses must implement this method.')
+
+    def is_within_original_scope(self, request_scopes, refresh_token, request, *args, **kwargs):
+        """Check if requested scopes are within a scope of the refresh token.
+
+        When access tokens are refreshed the scope of the new token
+        needs to be within the scope of the original token. This is
+        ensured by checking that all requested scopes strings are on
+        the list returned by the get_original_scopes. If this check
+        fails, is_within_original_scope is called. The method can be
+        used in situations where returning all valid scopes from the
+        get_original_scopes is not practical.
+
+        :param request_scopes: A list of scopes that were requested by client.
+        :param refresh_token: Unicode refresh_token.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :rtype: True or False
+
+        Method is used by:
+            - Refresh token grant
+        """
+        return False
+
+    def introspect_token(self, token, token_type_hint, request, *args, **kwargs):
+        """Introspect an access or refresh token.
+
+        Called once the introspect request is validated. This method should
+        verify the *token* and either return a dictionary with the list of
+        claims associated, or `None` in case the token is unknown.
+
+        Below the list of registered claims you should be interested in:
+
+        - scope : space-separated list of scopes
+        - client_id : client identifier
+        - username : human-readable identifier for the resource owner
+        - token_type : type of the token
+        - exp : integer timestamp indicating when this token will expire
+        - iat : integer timestamp indicating when this token was issued
+        - nbf : integer timestamp indicating when it can be "not-before" used
+        - sub : subject of the token - identifier of the resource owner
+        - aud : list of string identifiers representing the intended audience
+        - iss : string representing issuer of this token
+        - jti : string identifier for the token
+
+        Note that most of them are coming directly from JWT RFC. More details
+        can be found in `Introspect Claims`_ or `JWT Claims`_.
+
+        The implementation can use *token_type_hint* to improve lookup
+        efficiency, but must fallback to other types to be compliant with RFC.
+
+        The dict of claims is added to request.token after this method.
+
+        :param token: The token string.
+        :param token_type_hint: access_token or refresh_token.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+
+        Method is used by:
+            - Introspect Endpoint (all grants are compatible)
+
+        .. _`Introspect Claims`: https://tools.ietf.org/html/rfc7662#section-2.2
+        .. _`JWT Claims`: https://tools.ietf.org/html/rfc7519#section-4
+        """
+        raise NotImplementedError('Subclasses must implement this method.')
+
+    def invalidate_authorization_code(self, client_id, code, request, *args, **kwargs):
+        """Invalidate an authorization code after use.
+
+        :param client_id: Unicode client identifier.
+        :param code: The authorization code grant (request.code).
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+
+        Method is used by:
+            - Authorization Code Grant
+        """
+        raise NotImplementedError('Subclasses must implement this method.')
+
+    def revoke_token(self, token, token_type_hint, request, *args, **kwargs):
+        """Revoke an access or refresh token.
+
+        :param token: The token string.
+        :param token_type_hint: access_token or refresh_token.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+
+        Method is used by:
+            - Revocation Endpoint
+        """
+        raise NotImplementedError('Subclasses must implement this method.')
+
+    def rotate_refresh_token(self, request):
+        """Determine whether to rotate the refresh token. Default, yes.
+
+        When access tokens are refreshed the old refresh token can be kept
+        or replaced with a new one (rotated). Return True to rotate and
+        and False for keeping original.
+
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :rtype: True or False
+
+        Method is used by:
+            - Refresh Token Grant
+        """
+        return True
+
+    def save_authorization_code(self, client_id, code, request, *args, **kwargs):
+        """Persist the authorization_code.
+
+        The code should at minimum be stored with:
+            - the client_id (``client_id``)
+            - the redirect URI used (``request.redirect_uri``)
+            - a resource owner / user (``request.user``)
+            - the authorized scopes (``request.scopes``)
+
+        To support PKCE, you MUST associate the code with:
+            - Code Challenge (``request.code_challenge``) and
+            - Code Challenge Method (``request.code_challenge_method``)
+
+        To support OIDC, you MUST associate the code with:
+            - nonce, if present (``code["nonce"]``)
+
+        The ``code`` argument is actually a dictionary, containing at least a
+        ``code`` key with the actual authorization code:
+
+            ``{'code': 'sdf345jsdf0934f'}``
+
+        It may also have a ``claims`` parameter which, when present, will be a dict
+        deserialized from JSON as described at
+        http://openid.net/specs/openid-connect-core-1_0.html#ClaimsParameter
+        This value should be saved in this method and used again in ``.validate_code``.
+
+        :param client_id: Unicode client identifier.
+        :param code: A dict of the authorization code grant and, optionally, state.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+
+        Method is used by:
+            - Authorization Code Grant
+        """
+        raise NotImplementedError('Subclasses must implement this method.')
+
+    def save_token(self, token, request, *args, **kwargs):
+        """Persist the token with a token type specific method.
+
+        Currently, only save_bearer_token is supported.
+
+        :param token: A (Bearer) token dict.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        """
+        return self.save_bearer_token(token, request, *args, **kwargs)
+
+    def save_bearer_token(self, token, request, *args, **kwargs):
+        """Persist the Bearer token.
+
+        The Bearer token should at minimum be associated with:
+            - a client and it's client_id, if available
+            - a resource owner / user (request.user)
+            - authorized scopes (request.scopes)
+            - an expiration time
+            - a refresh token, if issued
+            - a claims document, if present in request.claims
+
+        The Bearer token dict may hold a number of items::
+
+            {
+                'token_type': 'Bearer',
+                'access_token': 'askfjh234as9sd8',
+                'expires_in': 3600,
+                'scope': 'string of space separated authorized scopes',
+                'refresh_token': '23sdf876234',  # if issued
+                'state': 'given_by_client',  # if supplied by client (implicit ONLY)
+            }
+
+        Note that while "scope" is a string-separated list of authorized scopes,
+        the original list is still available in request.scopes.
+
+        The token dict is passed as a reference so any changes made to the dictionary
+        will go back to the user.  If additional information must return to the client
+        user, and it is only possible to get this information after writing the token
+        to storage, it should be added to the token dictionary.  If the token
+        dictionary must be modified but the changes should not go back to the user,
+        a copy of the dictionary must be made before making the changes.
+
+        Also note that if an Authorization Code grant request included a valid claims
+        parameter (for OpenID Connect) then the request.claims property will contain
+        the claims dict, which should be saved for later use when generating the
+        id_token and/or UserInfo response content.
+
+        :param token: A Bearer token dict.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :rtype: The default redirect URI for the client
+
+        Method is used by all core grant types issuing Bearer tokens:
+            - Authorization Code Grant
+            - Implicit Grant
+            - Resource Owner Password Credentials Grant (might not associate a client)
+            - Client Credentials grant
+        """
+        raise NotImplementedError('Subclasses must implement this method.')
+
+    def validate_bearer_token(self, token, scopes, request):
+        """Ensure the Bearer token is valid and authorized access to scopes.
+
+        :param token: A string of random characters.
+        :param scopes: A list of scopes associated with the protected resource.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+
+        A key to OAuth 2 security and restricting impact of leaked tokens is
+        the short expiration time of tokens, *always ensure the token has not
+        expired!*.
+
+        Two different approaches to scope validation:
+
+            1) all(scopes). The token must be authorized access to all scopes
+                            associated with the resource. For example, the
+                            token has access to ``read-only`` and ``images``,
+                            thus the client can view images but not upload new.
+                            Allows for fine grained access control through
+                            combining various scopes.
+
+            2) any(scopes). The token must be authorized access to one of the
+                            scopes associated with the resource. For example,
+                            token has access to ``read-only-images``.
+                            Allows for fine grained, although arguably less
+                            convenient, access control.
+
+        A powerful way to use scopes would mimic UNIX ACLs and see a scope
+        as a group with certain privileges. For a restful API these might
+        map to HTTP verbs instead of read, write and execute.
+
+        Note, the request.user attribute can be set to the resource owner
+        associated with this token. Similarly the request.client and
+        request.scopes attribute can be set to associated client object
+        and authorized scopes. If you then use a decorator such as the
+        one provided for django these attributes will be made available
+        in all protected views as keyword arguments.
+
+        :param token: Unicode Bearer token
+        :param scopes: List of scopes (defined by you)
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :rtype: True or False
+
+        Method is indirectly used by all core Bearer token issuing grant types:
+            - Authorization Code Grant
+            - Implicit Grant
+            - Resource Owner Password Credentials Grant
+            - Client Credentials Grant
+        """
+        raise NotImplementedError('Subclasses must implement this method.')
+
+    def validate_client_id(self, client_id, request, *args, **kwargs):
+        """Ensure client_id belong to a valid and active client.
+
+        Note, while not strictly necessary it can often be very convenient
+        to set request.client to the client object associated with the
+        given client_id.
+
+        :param client_id: Unicode client identifier.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :rtype: True or False
+
+        Method is used by:
+            - Authorization Code Grant
+            - Implicit Grant
+        """
+        raise NotImplementedError('Subclasses must implement this method.')
+
+    def validate_code(self, client_id, code, client, request, *args, **kwargs):
+        """Verify that the authorization_code is valid and assigned to the given
+        client.
+
+        Before returning true, set the following based on the information stored
+        with the code in 'save_authorization_code':
+
+            - request.user
+            - request.scopes
+            - request.claims (if given)
+
+        OBS! The request.user attribute should be set to the resource owner
+        associated with this authorization code. Similarly request.scopes
+        must also be set.
+
+        The request.claims property, if it was given, should assigned a dict.
+
+        If PKCE is enabled (see 'is_pkce_required' and 'save_authorization_code')
+        you MUST set the following based on the information stored:
+
+            - request.code_challenge
+            - request.code_challenge_method
+
+        :param client_id: Unicode client identifier.
+        :param code: Unicode authorization code.
+        :param client: Client object set by you, see ``.authenticate_client``.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :rtype: True or False
+
+        Method is used by:
+            - Authorization Code Grant
+        """
+        raise NotImplementedError('Subclasses must implement this method.')
+
+    def validate_grant_type(self, client_id, grant_type, client, request, *args, **kwargs):
+        """Ensure client is authorized to use the grant_type requested.
+
+        :param client_id: Unicode client identifier.
+        :param grant_type: Unicode grant type, i.e. authorization_code, password.
+        :param client: Client object set by you, see ``.authenticate_client``.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :rtype: True or False
+
+        Method is used by:
+            - Authorization Code Grant
+            - Resource Owner Password Credentials Grant
+            - Client Credentials Grant
+            - Refresh Token Grant
+        """
+        raise NotImplementedError('Subclasses must implement this method.')
+
+    def validate_redirect_uri(self, client_id, redirect_uri, request, *args, **kwargs):
+        """Ensure client is authorized to redirect to the redirect_uri requested.
+
+        All clients should register the absolute URIs of all URIs they intend
+        to redirect to. The registration is outside of the scope of oauthlib.
+
+        :param client_id: Unicode client identifier.
+        :param redirect_uri: Unicode absolute URI.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :rtype: True or False
+
+        Method is used by:
+            - Authorization Code Grant
+            - Implicit Grant
+        """
+        raise NotImplementedError('Subclasses must implement this method.')
+
+    def validate_refresh_token(self, refresh_token, client, request, *args, **kwargs):
+        """Ensure the Bearer token is valid and authorized access to scopes.
+
+        OBS! The request.user attribute should be set to the resource owner
+        associated with this refresh token.
+
+        :param refresh_token: Unicode refresh token.
+        :param client: Client object set by you, see ``.authenticate_client``.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :rtype: True or False
+
+        Method is used by:
+            - Authorization Code Grant (indirectly by issuing refresh tokens)
+            - Resource Owner Password Credentials Grant (also indirectly)
+            - Refresh Token Grant
+        """
+        raise NotImplementedError('Subclasses must implement this method.')
+
+    def validate_response_type(self, client_id, response_type, client, request, *args, **kwargs):
+        """Ensure client is authorized to use the response_type requested.
+
+        :param client_id: Unicode client identifier.
+        :param response_type: Unicode response type, i.e. code, token.
+        :param client: Client object set by you, see ``.authenticate_client``.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :rtype: True or False
+
+        Method is used by:
+            - Authorization Code Grant
+            - Implicit Grant
+        """
+        raise NotImplementedError('Subclasses must implement this method.')
+
+    def validate_scopes(self, client_id, scopes, client, request, *args, **kwargs):
+        """Ensure the client is authorized access to requested scopes.
+
+        :param client_id: Unicode client identifier.
+        :param scopes: List of scopes (defined by you).
+        :param client: Client object set by you, see ``.authenticate_client``.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :rtype: True or False
+
+        Method is used by all core grant types:
+            - Authorization Code Grant
+            - Implicit Grant
+            - Resource Owner Password Credentials Grant
+            - Client Credentials Grant
+        """
+        raise NotImplementedError('Subclasses must implement this method.')
+
+    def validate_user(self, username, password, client, request, *args, **kwargs):
+        """Ensure the username and password is valid.
+
+        OBS! The validation should also set the user attribute of the request
+        to a valid resource owner, i.e. request.user = username or similar. If
+        not set you will be unable to associate a token with a user in the
+        persistence method used (commonly, save_bearer_token).
+
+        :param username: Unicode username.
+        :param password: Unicode password.
+        :param client: Client object set by you, see ``.authenticate_client``.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :rtype: True or False
+
+        Method is used by:
+            - Resource Owner Password Credentials Grant
+        """
+        raise NotImplementedError('Subclasses must implement this method.')
+
+    def is_pkce_required(self, client_id, request):
+        """Determine if current request requires PKCE. Default, False.
+        This is called for both "authorization" and "token" requests.
+
+        Override this method by ``return True`` to enable PKCE for everyone.
+        You might want to enable it only for public clients.
+        Note that PKCE can also be used in addition of a client authentication.
+
+        OAuth 2.0 public clients utilizing the Authorization Code Grant are
+        susceptible to the authorization code interception attack.  This
+        specification describes the attack as well as a technique to mitigate
+        against the threat through the use of Proof Key for Code Exchange
+        (PKCE, pronounced "pixy"). See `RFC7636`_.
+
+        :param client_id: Client identifier.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :rtype: True or False
+
+        Method is used by:
+            - Authorization Code Grant
+
+        .. _`RFC7636`: https://tools.ietf.org/html/rfc7636
+        """
+        return False
+
+    def get_code_challenge(self, code, request):
+        """Is called for every "token" requests.
+
+        When the server issues the authorization code in the authorization
+        response, it MUST associate the ``code_challenge`` and
+        ``code_challenge_method`` values with the authorization code so it can
+        be verified later.
+
+        Typically, the ``code_challenge`` and ``code_challenge_method`` values
+        are stored in encrypted form in the ``code`` itself but could
+        alternatively be stored on the server associated with the code.  The
+        server MUST NOT include the ``code_challenge`` value in client requests
+        in a form that other entities can extract.
+
+        Return the ``code_challenge`` associated to the code.
+        If ``None`` is returned, code is considered to not be associated to any
+        challenges.
+
+        :param code: Authorization code.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :rtype: code_challenge string
+
+        Method is used by:
+            - Authorization Code Grant - when PKCE is active
+
+        """
+        return None
+
+    def get_code_challenge_method(self, code, request):
+        """Is called during the "token" request processing, when a
+        ``code_verifier`` and a ``code_challenge`` has been provided.
+
+        See ``.get_code_challenge``.
+
+        Must return ``plain`` or ``S256``. You can return a custom value if you have
+        implemented your own ``AuthorizationCodeGrant`` class.
+
+        :param code: Authorization code.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :rtype: code_challenge_method string
+
+        Method is used by:
+            - Authorization Code Grant - when PKCE is active
+
+        """
+        raise NotImplementedError('Subclasses must implement this method.')
+
+    def is_origin_allowed(self, client_id, origin, request, *args, **kwargs):
+        """Indicate if the given origin is allowed to access the token endpoint
+        via Cross-Origin Resource Sharing (CORS).  CORS is used by browser-based
+        clients, such as Single-Page Applications, to perform the Authorization
+        Code Grant.
+
+        (Note:  If performing Authorization Code Grant via a public client such
+        as a browser, you should use PKCE as well.)
+
+        If this method returns true, the appropriate CORS headers will be added
+        to the response.  By default this method always returns False, meaning
+        CORS is disabled.
+
+        :param client_id: Unicode client identifier.
+        :param redirect_uri: Unicode origin.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :rtype: bool
+
+        Method is used by:
+            - Authorization Code Grant
+            - Refresh Token Grant
+
+        """
+        return False
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/tokens.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/tokens.py
new file mode 100644
index 00000000..0757d07e
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/tokens.py
@@ -0,0 +1,356 @@
+"""
+oauthlib.oauth2.rfc6749.tokens
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This module contains methods for adding two types of access tokens to requests.
+
+- Bearer https://tools.ietf.org/html/rfc6750
+- MAC https://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01
+"""
+import hashlib
+import hmac
+import warnings
+from binascii import b2a_base64
+from urllib.parse import urlparse
+
+from oauthlib import common
+from oauthlib.common import add_params_to_qs, add_params_to_uri
+
+from . import utils
+
+
+class OAuth2Token(dict):
+
+    def __init__(self, params, old_scope=None):
+        super().__init__(params)
+        self._new_scope = None
+        if 'scope' in params and params['scope']:
+            self._new_scope = set(utils.scope_to_list(params['scope']))
+        if old_scope is not None:
+            self._old_scope = set(utils.scope_to_list(old_scope))
+            if self._new_scope is None:
+                # the rfc says that if the scope hasn't changed, it's optional
+                # in params so set the new scope to the old scope
+                self._new_scope = self._old_scope
+        else:
+            self._old_scope = self._new_scope
+
+    @property
+    def scope_changed(self):
+        return self._new_scope != self._old_scope
+
+    @property
+    def old_scope(self):
+        return utils.list_to_scope(self._old_scope)
+
+    @property
+    def old_scopes(self):
+        return list(self._old_scope)
+
+    @property
+    def scope(self):
+        return utils.list_to_scope(self._new_scope)
+
+    @property
+    def scopes(self):
+        return list(self._new_scope)
+
+    @property
+    def missing_scopes(self):
+        return list(self._old_scope - self._new_scope)
+
+    @property
+    def additional_scopes(self):
+        return list(self._new_scope - self._old_scope)
+
+
+def prepare_mac_header(token, uri, key, http_method,
+                       nonce=None,
+                       headers=None,
+                       body=None,
+                       ext='',
+                       hash_algorithm='hmac-sha-1',
+                       issue_time=None,
+                       draft=0):
+    """Add an `MAC Access Authentication`_ signature to headers.
+
+    Unlike OAuth 1, this HMAC signature does not require inclusion of the
+    request payload/body, neither does it use a combination of client_secret
+    and token_secret but rather a mac_key provided together with the access
+    token.
+
+    Currently two algorithms are supported, "hmac-sha-1" and "hmac-sha-256",
+    `extension algorithms`_ are not supported.
+
+    Example MAC Authorization header, linebreaks added for clarity
+
+    Authorization: MAC id="h480djs93hd8",
+                       nonce="1336363200:dj83hs9s",
+                       mac="bhCQXTVyfj5cmA9uKkPFx1zeOXM="
+
+    .. _`MAC Access Authentication`: https://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01
+    .. _`extension algorithms`: https://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01#section-7.1
+
+    :param token:
+    :param uri: Request URI.
+    :param key: MAC given provided by token endpoint.
+    :param http_method: HTTP Request method.
+    :param nonce:
+    :param headers: Request headers as a dictionary.
+    :param body:
+    :param ext:
+    :param hash_algorithm: HMAC algorithm provided by token endpoint.
+    :param issue_time: Time when the MAC credentials were issued (datetime).
+    :param draft: MAC authentication specification version.
+    :return: headers dictionary with the authorization field added.
+    """
+    http_method = http_method.upper()
+    host, port = utils.host_from_uri(uri)
+
+    if hash_algorithm.lower() == 'hmac-sha-1':
+        h = hashlib.sha1
+    elif hash_algorithm.lower() == 'hmac-sha-256':
+        h = hashlib.sha256
+    else:
+        raise ValueError('unknown hash algorithm')
+
+    if draft == 0:
+        nonce = nonce or '{}:{}'.format(utils.generate_age(issue_time),
+                                          common.generate_nonce())
+    else:
+        ts = common.generate_timestamp()
+        nonce = common.generate_nonce()
+
+    sch, net, path, par, query, fra = urlparse(uri)
+
+    if query:
+        request_uri = path + '?' + query
+    else:
+        request_uri = path
+
+    # Hash the body/payload
+    if body is not None and draft == 0:
+        body = body.encode('utf-8')
+        bodyhash = b2a_base64(h(body).digest())[:-1].decode('utf-8')
+    else:
+        bodyhash = ''
+
+    # Create the normalized base string
+    base = []
+    if draft == 0:
+        base.append(nonce)
+    else:
+        base.append(ts)
+        base.append(nonce)
+    base.append(http_method.upper())
+    base.append(request_uri)
+    base.append(host)
+    base.append(port)
+    if draft == 0:
+        base.append(bodyhash)
+    base.append(ext or '')
+    base_string = '\n'.join(base) + '\n'
+
+    # hmac struggles with unicode strings - http://bugs.python.org/issue5285
+    if isinstance(key, str):
+        key = key.encode('utf-8')
+    sign = hmac.new(key, base_string.encode('utf-8'), h)
+    sign = b2a_base64(sign.digest())[:-1].decode('utf-8')
+
+    header = []
+    header.append('MAC id="%s"' % token)
+    if draft != 0:
+        header.append('ts="%s"' % ts)
+    header.append('nonce="%s"' % nonce)
+    if bodyhash:
+        header.append('bodyhash="%s"' % bodyhash)
+    if ext:
+        header.append('ext="%s"' % ext)
+    header.append('mac="%s"' % sign)
+
+    headers = headers or {}
+    headers['Authorization'] = ', '.join(header)
+    return headers
+
+
+def prepare_bearer_uri(token, uri):
+    """Add a `Bearer Token`_ to the request URI.
+    Not recommended, use only if client can't use authorization header or body.
+
+    http://www.example.com/path?access_token=h480djs93hd8
+
+    .. _`Bearer Token`: https://tools.ietf.org/html/rfc6750
+
+    :param token:
+    :param uri:
+    """
+    return add_params_to_uri(uri, [(('access_token', token))])
+
+
+def prepare_bearer_headers(token, headers=None):
+    """Add a `Bearer Token`_ to the request URI.
+    Recommended method of passing bearer tokens.
+
+    Authorization: Bearer h480djs93hd8
+
+    .. _`Bearer Token`: https://tools.ietf.org/html/rfc6750
+
+    :param token:
+    :param headers:
+    """
+    headers = headers or {}
+    headers['Authorization'] = 'Bearer %s' % token
+    return headers
+
+
+def prepare_bearer_body(token, body=''):
+    """Add a `Bearer Token`_ to the request body.
+
+    access_token=h480djs93hd8
+
+    .. _`Bearer Token`: https://tools.ietf.org/html/rfc6750
+
+    :param token:
+    :param body:
+    """
+    return add_params_to_qs(body, [(('access_token', token))])
+
+
+def random_token_generator(request, refresh_token=False):
+    """
+    :param request: OAuthlib request.
+    :type request: oauthlib.common.Request
+    :param refresh_token:
+    """
+    return common.generate_token()
+
+
+def signed_token_generator(private_pem, **kwargs):
+    """
+    :param private_pem:
+    """
+    def signed_token_generator(request):
+        request.claims = kwargs
+        return common.generate_signed_token(private_pem, request)
+
+    return signed_token_generator
+
+
+def get_token_from_header(request):
+    """
+    Helper function to extract a token from the request header.
+
+    :param request: OAuthlib request.
+    :type request: oauthlib.common.Request
+    :return: Return the token or None if the Authorization header is malformed.
+    """
+    token = None
+
+    if 'Authorization' in request.headers:
+        split_header = request.headers.get('Authorization').split()
+        if len(split_header) == 2 and split_header[0].lower() == 'bearer':
+            token = split_header[1]
+    else:
+        token = request.access_token
+
+    return token
+
+
+class TokenBase:
+    __slots__ = ()
+
+    def __call__(self, request, refresh_token=False):
+        raise NotImplementedError('Subclasses must implement this method.')
+
+    def validate_request(self, request):
+        """
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        """
+        raise NotImplementedError('Subclasses must implement this method.')
+
+    def estimate_type(self, request):
+        """
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        """
+        raise NotImplementedError('Subclasses must implement this method.')
+
+
+class BearerToken(TokenBase):
+    __slots__ = (
+        'request_validator', 'token_generator',
+        'refresh_token_generator', 'expires_in'
+    )
+
+    def __init__(self, request_validator=None, token_generator=None,
+                 expires_in=None, refresh_token_generator=None):
+        self.request_validator = request_validator
+        self.token_generator = token_generator or random_token_generator
+        self.refresh_token_generator = (
+            refresh_token_generator or self.token_generator
+        )
+        self.expires_in = expires_in or 3600
+
+    def create_token(self, request, refresh_token=False, **kwargs):
+        """
+        Create a BearerToken, by default without refresh token.
+
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :param refresh_token:
+        """
+        if "save_token" in kwargs:
+            warnings.warn("`save_token` has been deprecated, it was not called internally."
+                          "If you do, call `request_validator.save_token()` instead.",
+                          DeprecationWarning)
+
+        if callable(self.expires_in):
+            expires_in = self.expires_in(request)
+        else:
+            expires_in = self.expires_in
+
+        request.expires_in = expires_in
+
+        token = {
+            'access_token': self.token_generator(request),
+            'expires_in': expires_in,
+            'token_type': 'Bearer',
+        }
+
+        # If provided, include - this is optional in some cases https://tools.ietf.org/html/rfc6749#section-3.3 but
+        # there is currently no mechanism to coordinate issuing a token for only a subset of the requested scopes so
+        # all tokens issued are for the entire set of requested scopes.
+        if request.scopes is not None:
+            token['scope'] = ' '.join(request.scopes)
+
+        if refresh_token:
+            if (request.refresh_token and
+                    not self.request_validator.rotate_refresh_token(request)):
+                token['refresh_token'] = request.refresh_token
+            else:
+                token['refresh_token'] = self.refresh_token_generator(request)
+
+        token.update(request.extra_credentials or {})
+        return OAuth2Token(token)
+
+    def validate_request(self, request):
+        """
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        """
+        token = get_token_from_header(request)
+        return self.request_validator.validate_bearer_token(
+            token, request.scopes, request)
+
+    def estimate_type(self, request):
+        """
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        """
+        if request.headers.get('Authorization', '').split(' ')[0].lower() == 'bearer':
+            return 9
+        elif request.access_token is not None:
+            return 5
+        else:
+            return 0
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/utils.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/utils.py
new file mode 100644
index 00000000..7dc27b3d
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc6749/utils.py
@@ -0,0 +1,83 @@
+"""
+oauthlib.utils
+~~~~~~~~~~~~~~
+
+This module contains utility methods used by various parts of the OAuth 2 spec.
+"""
+import datetime
+import os
+from urllib.parse import quote, urlparse
+
+from oauthlib.common import urldecode
+
+
+def list_to_scope(scope):
+    """Convert a list of scopes to a space separated string."""
+    if isinstance(scope, str) or scope is None:
+        return scope
+    elif isinstance(scope, (set, tuple, list)):
+        return " ".join([str(s) for s in scope])
+    else:
+        raise ValueError("Invalid scope (%s), must be string, tuple, set, or list." % scope)
+
+
+def scope_to_list(scope):
+    """Convert a space separated string to a list of scopes."""
+    if isinstance(scope, (tuple, list, set)):
+        return [str(s) for s in scope]
+    elif scope is None:
+        return None
+    else:
+        return scope.strip().split(" ")
+
+
+def params_from_uri(uri):
+    params = dict(urldecode(urlparse(uri).query))
+    if 'scope' in params:
+        params['scope'] = scope_to_list(params['scope'])
+    return params
+
+
+def host_from_uri(uri):
+    """Extract hostname and port from URI.
+
+    Will use default port for HTTP and HTTPS if none is present in the URI.
+    """
+    default_ports = {
+        'HTTP': '80',
+        'HTTPS': '443',
+    }
+
+    sch, netloc, path, par, query, fra = urlparse(uri)
+    if ':' in netloc:
+        netloc, port = netloc.split(':', 1)
+    else:
+        port = default_ports.get(sch.upper())
+
+    return netloc, port
+
+
+def escape(u):
+    """Escape a string in an OAuth-compatible fashion.
+
+    TODO: verify whether this can in fact be used for OAuth 2
+
+    """
+    if not isinstance(u, str):
+        raise ValueError('Only unicode objects are escapable.')
+    return quote(u.encode('utf-8'), safe=b'~')
+
+
+def generate_age(issue_time):
+    """Generate a age parameter for MAC authentication draft 00."""
+    td = datetime.datetime.now() - issue_time
+    age = (td.microseconds + (td.seconds + td.days * 24 * 3600)
+           * 10 ** 6) / 10 ** 6
+    return str(age)
+
+
+def is_secure_transport(uri):
+    """Check if the uri is over ssl."""
+    if os.environ.get('OAUTHLIB_INSECURE_TRANSPORT'):
+        return True
+    return uri.lower().startswith('https://')
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc8628/__init__.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc8628/__init__.py
new file mode 100644
index 00000000..531929dc
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc8628/__init__.py
@@ -0,0 +1,10 @@
+"""
+oauthlib.oauth2.rfc8628
+~~~~~~~~~~~~~~~~~~~~~~~
+
+This module is an implementation of various logic needed
+for consuming and providing OAuth 2.0 Device Authorization RFC8628.
+"""
+import logging
+
+log = logging.getLogger(__name__)
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc8628/clients/__init__.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc8628/clients/__init__.py
new file mode 100644
index 00000000..130b52e3
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc8628/clients/__init__.py
@@ -0,0 +1,8 @@
+"""
+oauthlib.oauth2.rfc8628
+~~~~~~~~~~~~~~~~~~~~~~~
+
+This module is an implementation of various logic needed
+for consuming OAuth 2.0 Device Authorization RFC8628.
+"""
+from .device import DeviceClient
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc8628/clients/device.py b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc8628/clients/device.py
new file mode 100644
index 00000000..b9ba2150
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/oauth2/rfc8628/clients/device.py
@@ -0,0 +1,95 @@
+"""
+oauthlib.oauth2.rfc8628
+~~~~~~~~~~~~~~~~~~~~~~~
+
+This module is an implementation of various logic needed
+for consuming and providing OAuth 2.0 Device Authorization RFC8628.
+"""
+from oauthlib.common import add_params_to_uri
+from oauthlib.oauth2 import BackendApplicationClient, Client
+from oauthlib.oauth2.rfc6749.errors import InsecureTransportError
+from oauthlib.oauth2.rfc6749.parameters import prepare_token_request
+from oauthlib.oauth2.rfc6749.utils import is_secure_transport, list_to_scope
+
+
+class DeviceClient(Client):
+
+    """A public client utilizing the device authorization workflow.
+
+    The client can request an access token using a device code and
+    a public client id associated with the device code as defined
+    in RFC8628.
+
+    The device authorization grant type can be used to obtain both
+    access tokens and refresh tokens and is intended to be used in
+    a scenario where the device being authorized does not have a
+    user interface that is suitable for performing authentication.
+    """
+
+    grant_type = 'urn:ietf:params:oauth:grant-type:device_code'
+
+    def __init__(self, client_id, **kwargs):
+        super().__init__(client_id, **kwargs)
+        self.client_secret = kwargs.get('client_secret')
+
+    def prepare_request_uri(self, uri, scope=None, **kwargs):
+        if not is_secure_transport(uri):
+            raise InsecureTransportError()
+
+        scope = self.scope if scope is None else scope
+        params = [(('client_id', self.client_id)), (('grant_type', self.grant_type))]
+
+        if self.client_secret is not None:
+            params.append(('client_secret', self.client_secret))
+
+        if scope:
+            params.append(('scope', list_to_scope(scope)))
+
+        for k in kwargs:
+            if kwargs[k]:
+                params.append((str(k), kwargs[k]))
+
+        return add_params_to_uri(uri, params)
+
+    def prepare_request_body(self, device_code, body='', scope=None,
+                             include_client_id=False, **kwargs):
+        """Add device_code to request body
+
+        The client makes a request to the token endpoint by adding the
+        device_code as a parameter using the
+        "application/x-www-form-urlencoded" format to the HTTP request
+        body.
+
+        :param body: Existing request body (URL encoded string) to embed parameters
+                     into. This may contain extra parameters. Default ''.
+        :param scope:   The scope of the access request as described by
+                        `Section 3.3`_.
+
+        :param include_client_id: `True` to send the `client_id` in the
+                                  body of the upstream request. This is required
+                                  if the client is not authenticating with the
+                                  authorization server as described in
+                                  `Section 3.2.1`_. False otherwise (default).
+        :type include_client_id: Boolean
+
+        :param kwargs:  Extra credentials to include in the token request.
+
+        The prepared body will include all provided device_code as well as
+        the ``grant_type`` parameter set to
+        ``urn:ietf:params:oauth:grant-type:device_code``::
+
+            >>> from oauthlib.oauth2 import DeviceClient
+            >>> client = DeviceClient('your_id', 'your_code')
+            >>> client.prepare_request_body(scope=['hello', 'world'])
+            'grant_type=urn:ietf:params:oauth:grant-type:device_code&scope=hello+world'
+
+        .. _`Section 3.2.1`: https://datatracker.ietf.org/doc/html/rfc6749#section-3.2.1
+        .. _`Section 3.3`: https://datatracker.ietf.org/doc/html/rfc6749#section-3.3
+        .. _`Section 3.4`: https://datatracker.ietf.org/doc/html/rfc8628#section-3.4
+        """
+
+        kwargs['client_id'] = self.client_id
+        kwargs['include_client_id'] = include_client_id
+        scope = self.scope if scope is None else scope
+        return prepare_token_request(self.grant_type, body=body, device_code=device_code,
+                                     scope=scope, **kwargs)
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/openid/__init__.py b/.venv/lib/python3.12/site-packages/oauthlib/openid/__init__.py
new file mode 100644
index 00000000..e3174374
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/openid/__init__.py
@@ -0,0 +1,7 @@
+"""
+oauthlib.openid
+~~~~~~~~~~~~~~
+
+"""
+from .connect.core.endpoints import Server, UserInfoEndpoint
+from .connect.core.request_validator import RequestValidator
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/openid/connect/__init__.py b/.venv/lib/python3.12/site-packages/oauthlib/openid/connect/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/openid/connect/__init__.py
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/__init__.py b/.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/__init__.py
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/endpoints/__init__.py b/.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/endpoints/__init__.py
new file mode 100644
index 00000000..7017ff4f
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/endpoints/__init__.py
@@ -0,0 +1,9 @@
+"""
+oauthlib.oopenid.core
+~~~~~~~~~~~~~~~~~~~~~~~
+
+This module is an implementation of various logic needed
+for consuming and providing OpenID Connect
+"""
+from .pre_configured import Server
+from .userinfo import UserInfoEndpoint
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/endpoints/pre_configured.py b/.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/endpoints/pre_configured.py
new file mode 100644
index 00000000..8ce8bee6
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/endpoints/pre_configured.py
@@ -0,0 +1,97 @@
+"""
+oauthlib.openid.connect.core.endpoints.pre_configured
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This module is an implementation of various endpoints needed
+for providing OpenID Connect servers.
+"""
+from oauthlib.oauth2.rfc6749.endpoints import (
+    AuthorizationEndpoint, IntrospectEndpoint, ResourceEndpoint,
+    RevocationEndpoint, TokenEndpoint,
+)
+from oauthlib.oauth2.rfc6749.grant_types import (
+    AuthorizationCodeGrant as OAuth2AuthorizationCodeGrant,
+    ClientCredentialsGrant, ImplicitGrant as OAuth2ImplicitGrant,
+    RefreshTokenGrant, ResourceOwnerPasswordCredentialsGrant,
+)
+from oauthlib.oauth2.rfc6749.tokens import BearerToken
+
+from ..grant_types import AuthorizationCodeGrant, HybridGrant, ImplicitGrant
+from ..grant_types.dispatchers import (
+    AuthorizationCodeGrantDispatcher, AuthorizationTokenGrantDispatcher,
+    ImplicitTokenGrantDispatcher,
+)
+from ..tokens import JWTToken
+from .userinfo import UserInfoEndpoint
+
+
+class Server(AuthorizationEndpoint, IntrospectEndpoint, TokenEndpoint,
+             ResourceEndpoint, RevocationEndpoint, UserInfoEndpoint):
+
+    """An all-in-one endpoint featuring all four major grant types."""
+
+    def __init__(self, request_validator, token_expires_in=None,
+                 token_generator=None, refresh_token_generator=None,
+                 *args, **kwargs):
+        """Construct a new all-grants-in-one server.
+
+        :param request_validator: An implementation of
+                                  oauthlib.oauth2.RequestValidator.
+        :param token_expires_in: An int or a function to generate a token
+                                 expiration offset (in seconds) given a
+                                 oauthlib.common.Request object.
+        :param token_generator: A function to generate a token from a request.
+        :param refresh_token_generator: A function to generate a token from a
+                                        request for the refresh token.
+        :param kwargs: Extra parameters to pass to authorization-,
+                       token-, resource-, and revocation-endpoint constructors.
+        """
+        self.auth_grant = OAuth2AuthorizationCodeGrant(request_validator)
+        self.implicit_grant = OAuth2ImplicitGrant(request_validator)
+        self.password_grant = ResourceOwnerPasswordCredentialsGrant(
+            request_validator)
+        self.credentials_grant = ClientCredentialsGrant(request_validator)
+        self.refresh_grant = RefreshTokenGrant(request_validator)
+        self.openid_connect_auth = AuthorizationCodeGrant(request_validator)
+        self.openid_connect_implicit = ImplicitGrant(request_validator)
+        self.openid_connect_hybrid = HybridGrant(request_validator)
+
+        self.bearer = BearerToken(request_validator, token_generator,
+                             token_expires_in, refresh_token_generator)
+
+        self.jwt = JWTToken(request_validator, token_generator,
+                       token_expires_in, refresh_token_generator)
+
+        self.auth_grant_choice = AuthorizationCodeGrantDispatcher(default_grant=self.auth_grant, oidc_grant=self.openid_connect_auth)
+        self.implicit_grant_choice = ImplicitTokenGrantDispatcher(default_grant=self.implicit_grant, oidc_grant=self.openid_connect_implicit)
+
+        # See http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#Combinations for valid combinations
+        # internally our AuthorizationEndpoint will ensure they can appear in any order for any valid combination
+        AuthorizationEndpoint.__init__(self, default_response_type='code',
+                                       response_types={
+                                           'code': self.auth_grant_choice,
+                                           'token': self.implicit_grant_choice,
+                                           'id_token': self.openid_connect_implicit,
+                                           'id_token token': self.openid_connect_implicit,
+                                           'code token': self.openid_connect_hybrid,
+                                           'code id_token': self.openid_connect_hybrid,
+                                           'code id_token token': self.openid_connect_hybrid,
+                                           'none': self.auth_grant
+                                       },
+                                       default_token_type=self.bearer)
+
+        self.token_grant_choice = AuthorizationTokenGrantDispatcher(request_validator, default_grant=self.auth_grant, oidc_grant=self.openid_connect_auth)
+
+        TokenEndpoint.__init__(self, default_grant_type='authorization_code',
+                               grant_types={
+                                   'authorization_code': self.token_grant_choice,
+                                   'password': self.password_grant,
+                                   'client_credentials': self.credentials_grant,
+                                   'refresh_token': self.refresh_grant,
+                               },
+                               default_token_type=self.bearer)
+        ResourceEndpoint.__init__(self, default_token='Bearer',
+                                  token_types={'Bearer': self.bearer, 'JWT': self.jwt})
+        RevocationEndpoint.__init__(self, request_validator)
+        IntrospectEndpoint.__init__(self, request_validator)
+        UserInfoEndpoint.__init__(self, request_validator)
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/endpoints/userinfo.py b/.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/endpoints/userinfo.py
new file mode 100644
index 00000000..7aa2bbe9
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/endpoints/userinfo.py
@@ -0,0 +1,106 @@
+"""
+oauthlib.openid.connect.core.endpoints.userinfo
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This module is an implementation of userinfo endpoint.
+"""
+import json
+import logging
+
+from oauthlib.common import Request
+from oauthlib.oauth2.rfc6749 import errors
+from oauthlib.oauth2.rfc6749.endpoints.base import (
+    BaseEndpoint, catch_errors_and_unavailability,
+)
+from oauthlib.oauth2.rfc6749.tokens import BearerToken
+
+log = logging.getLogger(__name__)
+
+
+class UserInfoEndpoint(BaseEndpoint):
+    """Authorizes access to userinfo resource.
+    """
+    def __init__(self, request_validator):
+        self.bearer = BearerToken(request_validator, None, None, None)
+        self.request_validator = request_validator
+        BaseEndpoint.__init__(self)
+
+    @catch_errors_and_unavailability
+    def create_userinfo_response(self, uri, http_method='GET', body=None, headers=None):
+        """Validate BearerToken and return userinfo from RequestValidator
+
+        The UserInfo Endpoint MUST return a
+        content-type header to indicate which format is being returned. The
+        content-type of the HTTP response MUST be application/json if the
+        response body is a text JSON object; the response body SHOULD be encoded
+        using UTF-8.
+        """
+        request = Request(uri, http_method, body, headers)
+        request.scopes = ["openid"]
+        self.validate_userinfo_request(request)
+
+        claims = self.request_validator.get_userinfo_claims(request)
+        if claims is None:
+            log.error('Userinfo MUST have claims for %r.', request)
+            raise errors.ServerError(status_code=500)
+
+        if isinstance(claims, dict):
+            resp_headers = {
+                'Content-Type': 'application/json'
+            }
+            if "sub" not in claims:
+                log.error('Userinfo MUST have "sub" for %r.', request)
+                raise errors.ServerError(status_code=500)
+            body = json.dumps(claims)
+        elif isinstance(claims, str):
+            resp_headers = {
+                'Content-Type': 'application/jwt'
+            }
+            body = claims
+        else:
+            log.error('Userinfo return unknown response for %r.', request)
+            raise errors.ServerError(status_code=500)
+        log.debug('Userinfo access valid for %r.', request)
+        return resp_headers, body, 200
+
+    def validate_userinfo_request(self, request):
+        """Ensure the request is valid.
+
+        5.3.1.  UserInfo Request
+        The Client sends the UserInfo Request using either HTTP GET or HTTP
+        POST. The Access Token obtained from an OpenID Connect Authentication
+        Request MUST be sent as a Bearer Token, per `Section 2`_ of OAuth 2.0
+        Bearer Token Usage [RFC6750].
+
+        It is RECOMMENDED that the request use the HTTP GET method and the
+        Access Token be sent using the Authorization header field.
+
+        The following is a non-normative example of a UserInfo Request:
+
+        .. code-block:: http
+
+            GET /userinfo HTTP/1.1
+            Host: server.example.com
+            Authorization: Bearer SlAV32hkKG
+
+        5.3.3. UserInfo Error Response
+        When an error condition occurs, the UserInfo Endpoint returns an Error
+        Response as defined in `Section 3`_ of OAuth 2.0 Bearer Token Usage
+        [RFC6750]. (HTTP errors unrelated to RFC 6750 are returned to the User
+        Agent using the appropriate HTTP status code.)
+
+        The following is a non-normative example of a UserInfo Error Response:
+
+        .. code-block:: http
+
+            HTTP/1.1 401 Unauthorized
+            WWW-Authenticate: Bearer error="invalid_token",
+                error_description="The Access Token expired"
+
+        .. _`Section 2`: https://datatracker.ietf.org/doc/html/rfc6750#section-2
+        .. _`Section 3`: https://datatracker.ietf.org/doc/html/rfc6750#section-3
+        """
+        if not self.bearer.validate_request(request):
+            raise errors.InvalidTokenError()
+        if "openid" not in request.scopes:
+            raise errors.InsufficientScopeError()
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/exceptions.py b/.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/exceptions.py
new file mode 100644
index 00000000..099b84e2
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/exceptions.py
@@ -0,0 +1,149 @@
+"""
+oauthlib.oauth2.rfc6749.errors
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Error used both by OAuth 2 clients and providers to represent the spec
+defined error responses for all four core grant types.
+"""
+from oauthlib.oauth2.rfc6749.errors import FatalClientError, OAuth2Error
+
+
+class FatalOpenIDClientError(FatalClientError):
+    pass
+
+
+class OpenIDClientError(OAuth2Error):
+    pass
+
+
+class InteractionRequired(OpenIDClientError):
+    """
+    The Authorization Server requires End-User interaction to proceed.
+
+    This error MAY be returned when the prompt parameter value in the
+    Authentication Request is none, but the Authentication Request cannot be
+    completed without displaying a user interface for End-User interaction.
+    """
+    error = 'interaction_required'
+    status_code = 401
+
+
+class LoginRequired(OpenIDClientError):
+    """
+    The Authorization Server requires End-User authentication.
+
+    This error MAY be returned when the prompt parameter value in the
+    Authentication Request is none, but the Authentication Request cannot be
+    completed without displaying a user interface for End-User authentication.
+    """
+    error = 'login_required'
+    status_code = 401
+
+
+class AccountSelectionRequired(OpenIDClientError):
+    """
+    The End-User is REQUIRED to select a session at the Authorization Server.
+
+    The End-User MAY be authenticated at the Authorization Server with
+    different associated accounts, but the End-User did not select a session.
+    This error MAY be returned when the prompt parameter value in the
+    Authentication Request is none, but the Authentication Request cannot be
+    completed without displaying a user interface to prompt for a session to
+    use.
+    """
+    error = 'account_selection_required'
+
+
+class ConsentRequired(OpenIDClientError):
+    """
+    The Authorization Server requires End-User consent.
+
+    This error MAY be returned when the prompt parameter value in the
+    Authentication Request is none, but the Authentication Request cannot be
+    completed without displaying a user interface for End-User consent.
+    """
+    error = 'consent_required'
+    status_code = 401
+
+
+class InvalidRequestURI(OpenIDClientError):
+    """
+    The request_uri in the Authorization Request returns an error or
+    contains invalid data.
+    """
+    error = 'invalid_request_uri'
+    description = 'The request_uri in the Authorization Request returns an ' \
+                  'error or contains invalid data.'
+
+
+class InvalidRequestObject(OpenIDClientError):
+    """
+    The request parameter contains an invalid Request Object.
+    """
+    error = 'invalid_request_object'
+    description = 'The request parameter contains an invalid Request Object.'
+
+
+class RequestNotSupported(OpenIDClientError):
+    """
+    The OP does not support use of the request parameter.
+    """
+    error = 'request_not_supported'
+    description = 'The request parameter is not supported.'
+
+
+class RequestURINotSupported(OpenIDClientError):
+    """
+    The OP does not support use of the request_uri parameter.
+    """
+    error = 'request_uri_not_supported'
+    description = 'The request_uri parameter is not supported.'
+
+
+class RegistrationNotSupported(OpenIDClientError):
+    """
+    The OP does not support use of the registration parameter.
+    """
+    error = 'registration_not_supported'
+    description = 'The registration parameter is not supported.'
+
+
+class InvalidTokenError(OAuth2Error):
+    """
+    The access token provided is expired, revoked, malformed, or
+    invalid for other reasons.  The resource SHOULD respond with
+    the HTTP 401 (Unauthorized) status code.  The client MAY
+    request a new access token and retry the protected resource
+    request.
+    """
+    error = 'invalid_token'
+    status_code = 401
+    description = ("The access token provided is expired, revoked, malformed, "
+                   "or invalid for other reasons.")
+
+
+class InsufficientScopeError(OAuth2Error):
+    """
+    The request requires higher privileges than provided by the
+    access token.  The resource server SHOULD respond with the HTTP
+    403 (Forbidden) status code and MAY include the "scope"
+    attribute with the scope necessary to access the protected
+    resource.
+    """
+    error = 'insufficient_scope'
+    status_code = 403
+    description = ("The request requires higher privileges than provided by "
+                   "the access token.")
+
+
+def raise_from_error(error, params=None):
+    import inspect
+    import sys
+    kwargs = {
+        'description': params.get('error_description'),
+        'uri': params.get('error_uri'),
+        'state': params.get('state')
+    }
+    for _, cls in inspect.getmembers(sys.modules[__name__], inspect.isclass):
+        if cls.error == error:
+            raise cls(**kwargs)
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/grant_types/__init__.py b/.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/grant_types/__init__.py
new file mode 100644
index 00000000..8dad5f60
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/grant_types/__init__.py
@@ -0,0 +1,13 @@
+"""
+oauthlib.openid.connect.core.grant_types
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+"""
+from .authorization_code import AuthorizationCodeGrant
+from .base import GrantTypeBase
+from .dispatchers import (
+    AuthorizationCodeGrantDispatcher, AuthorizationTokenGrantDispatcher,
+    ImplicitTokenGrantDispatcher,
+)
+from .hybrid import HybridGrant
+from .implicit import ImplicitGrant
+from .refresh_token import RefreshTokenGrant
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/grant_types/authorization_code.py b/.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/grant_types/authorization_code.py
new file mode 100644
index 00000000..6b2dcc3b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/grant_types/authorization_code.py
@@ -0,0 +1,43 @@
+"""
+oauthlib.openid.connect.core.grant_types
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+"""
+import logging
+
+from oauthlib.oauth2.rfc6749.grant_types.authorization_code import (
+    AuthorizationCodeGrant as OAuth2AuthorizationCodeGrant,
+)
+
+from .base import GrantTypeBase
+
+log = logging.getLogger(__name__)
+
+
+class AuthorizationCodeGrant(GrantTypeBase):
+
+    def __init__(self, request_validator=None, **kwargs):
+        self.proxy_target = OAuth2AuthorizationCodeGrant(
+            request_validator=request_validator, **kwargs)
+        self.custom_validators.post_auth.append(
+            self.openid_authorization_validator)
+        self.register_token_modifier(self.add_id_token)
+
+    def add_id_token(self, token, token_handler, request):
+        """
+        Construct an initial version of id_token, and let the
+        request_validator sign or encrypt it.
+
+        The authorization_code version of this method is used to
+        retrieve the nonce accordingly to the code storage.
+        """
+        # Treat it as normal OAuth 2 auth code request if openid is not present
+        if not request.scopes or 'openid' not in request.scopes:
+            return token
+
+        nonce = self.request_validator.get_authorization_code_nonce(
+            request.client_id,
+            request.code,
+            request.redirect_uri,
+            request
+        )
+        return super().add_id_token(token, token_handler, request, nonce=nonce)
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/grant_types/base.py b/.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/grant_types/base.py
new file mode 100644
index 00000000..33411dad
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/grant_types/base.py
@@ -0,0 +1,326 @@
+import base64
+import hashlib
+import logging
+import time
+from json import loads
+
+from oauthlib.oauth2.rfc6749.errors import (
+    ConsentRequired, InvalidRequestError, LoginRequired,
+)
+
+log = logging.getLogger(__name__)
+
+
+class GrantTypeBase:
+
+    # Just proxy the majority of method calls through to the
+    # proxy_target grant type handler, which will usually be either
+    # the standard OAuth2 AuthCode or Implicit grant types.
+    def __getattr__(self, attr):
+        return getattr(self.proxy_target, attr)
+
+    def __setattr__(self, attr, value):
+        proxied_attrs = {'refresh_token', 'response_types'}
+        if attr in proxied_attrs:
+            setattr(self.proxy_target, attr, value)
+        else:
+            super(OpenIDConnectBase, self).__setattr__(attr, value)
+
+    def validate_authorization_request(self, request):
+        """Validates the OpenID Connect authorization request parameters.
+
+        :returns: (list of scopes, dict of request info)
+        """
+        return self.proxy_target.validate_authorization_request(request)
+
+    def _inflate_claims(self, request):
+        # this may be called multiple times in a single request so make sure we only de-serialize the claims once
+        if request.claims and not isinstance(request.claims, dict):
+            # specific claims are requested during the Authorization Request and may be requested for inclusion
+            # in either the id_token or the UserInfo endpoint response
+            # see http://openid.net/specs/openid-connect-core-1_0.html#ClaimsParameter
+            try:
+                request.claims = loads(request.claims)
+            except Exception as ex:
+                raise InvalidRequestError(description="Malformed claims parameter",
+                                          uri="http://openid.net/specs/openid-connect-core-1_0.html#ClaimsParameter")
+
+    def id_token_hash(self, value, hashfunc=hashlib.sha256):
+        """
+        Its value is the base64url encoding of the left-most half of the
+        hash of the octets of the ASCII representation of the access_token
+        value, where the hash algorithm used is the hash algorithm used in
+        the alg Header Parameter of the ID Token's JOSE Header.
+
+        For instance, if the alg is RS256, hash the access_token value
+        with SHA-256, then take the left-most 128 bits and
+        base64url-encode them.
+        For instance, if the alg is HS512, hash the code value with
+        SHA-512, then take the left-most 256 bits and base64url-encode
+        them. The c_hash value is a case-sensitive string.
+
+        Example of hash from OIDC specification (bound to a JWS using RS256):
+
+        code:
+        Qcb0Orv1zh30vL1MPRsbm-diHiMwcLyZvn1arpZv-Jxf_11jnpEX3Tgfvk
+
+        c_hash:
+        LDktKdoQak3Pk0cnXxCltA
+        """
+        digest = hashfunc(value.encode()).digest()
+        left_most = len(digest) // 2
+        return base64.urlsafe_b64encode(digest[:left_most]).decode().rstrip("=")
+
+    def add_id_token(self, token, token_handler, request, nonce=None):
+        """
+        Construct an initial version of id_token, and let the
+        request_validator sign or encrypt it.
+
+        The initial version can contain the fields below, accordingly
+        to the spec:
+        - aud
+        - iat
+        - nonce
+        - at_hash
+        - c_hash
+        """
+        # Treat it as normal OAuth 2 auth code request if openid is not present
+        if not request.scopes or 'openid' not in request.scopes:
+            return token
+
+        # Only add an id token on auth/token step if asked for.
+        if request.response_type and 'id_token' not in request.response_type:
+            return token
+
+        # Implementation mint its own id_token without help.
+        id_token = self.request_validator.get_id_token(token, token_handler, request)
+        if id_token:
+            token['id_token'] = id_token
+            return token
+
+        # Fallback for asking some help from oauthlib framework.
+        # Start with technicals fields bound to the specification.
+        id_token = {}
+        id_token['aud'] = request.client_id
+        id_token['iat'] = int(time.time())
+
+        # nonce is REQUIRED when response_type value is:
+        # - id_token token (Implicit)
+        # - id_token (Implicit)
+        # - code id_token (Hybrid)
+        # - code id_token token (Hybrid)
+        #
+        # nonce is OPTIONAL when response_type value is:
+        # - code (Authorization Code)
+        # - code token (Hybrid)
+        if nonce is not None:
+            id_token["nonce"] = nonce
+
+        # at_hash is REQUIRED when response_type value is:
+        # - id_token token (Implicit)
+        # - code id_token token (Hybrid)
+        #
+        # at_hash is OPTIONAL when:
+        # - code (Authorization code)
+        # - code id_token (Hybrid)
+        # - code token (Hybrid)
+        #
+        # at_hash MAY NOT be used when:
+        # - id_token (Implicit)
+        if "access_token" in token:
+            id_token["at_hash"] = self.id_token_hash(token["access_token"])
+
+        # c_hash is REQUIRED when response_type value is:
+        # - code id_token (Hybrid)
+        # - code id_token token (Hybrid)
+        #
+        # c_hash is OPTIONAL for others.
+        if "code" in token:
+            id_token["c_hash"] = self.id_token_hash(token["code"])
+
+        # Call request_validator to complete/sign/encrypt id_token
+        token['id_token'] = self.request_validator.finalize_id_token(id_token, token, token_handler, request)
+
+        return token
+
+    def openid_authorization_validator(self, request):
+        """Perform OpenID Connect specific authorization request validation.
+
+        nonce
+                OPTIONAL. String value used to associate a Client session with
+                an ID Token, and to mitigate replay attacks. The value is
+                passed through unmodified from the Authentication Request to
+                the ID Token. Sufficient entropy MUST be present in the nonce
+                values used to prevent attackers from guessing values
+
+        display
+                OPTIONAL. ASCII string value that specifies how the
+                Authorization Server displays the authentication and consent
+                user interface pages to the End-User. The defined values are:
+
+                    page - The Authorization Server SHOULD display the
+                    authentication and consent UI consistent with a full User
+                    Agent page view. If the display parameter is not specified,
+                    this is the default display mode.
+
+                    popup - The Authorization Server SHOULD display the
+                    authentication and consent UI consistent with a popup User
+                    Agent window. The popup User Agent window should be of an
+                    appropriate size for a login-focused dialog and should not
+                    obscure the entire window that it is popping up over.
+
+                    touch - The Authorization Server SHOULD display the
+                    authentication and consent UI consistent with a device that
+                    leverages a touch interface.
+
+                    wap - The Authorization Server SHOULD display the
+                    authentication and consent UI consistent with a "feature
+                    phone" type display.
+
+                The Authorization Server MAY also attempt to detect the
+                capabilities of the User Agent and present an appropriate
+                display.
+
+        prompt
+                OPTIONAL. Space delimited, case sensitive list of ASCII string
+                values that specifies whether the Authorization Server prompts
+                the End-User for reauthentication and consent. The defined
+                values are:
+
+                    none - The Authorization Server MUST NOT display any
+                    authentication or consent user interface pages. An error is
+                    returned if an End-User is not already authenticated or the
+                    Client does not have pre-configured consent for the
+                    requested Claims or does not fulfill other conditions for
+                    processing the request. The error code will typically be
+                    login_required, interaction_required, or another code
+                    defined in Section 3.1.2.6. This can be used as a method to
+                    check for existing authentication and/or consent.
+
+                    login - The Authorization Server SHOULD prompt the End-User
+                    for reauthentication. If it cannot reauthenticate the
+                    End-User, it MUST return an error, typically
+                    login_required.
+
+                    consent - The Authorization Server SHOULD prompt the
+                    End-User for consent before returning information to the
+                    Client. If it cannot obtain consent, it MUST return an
+                    error, typically consent_required.
+
+                    select_account - The Authorization Server SHOULD prompt the
+                    End-User to select a user account. This enables an End-User
+                    who has multiple accounts at the Authorization Server to
+                    select amongst the multiple accounts that they might have
+                    current sessions for. If it cannot obtain an account
+                    selection choice made by the End-User, it MUST return an
+                    error, typically account_selection_required.
+
+                The prompt parameter can be used by the Client to make sure
+                that the End-User is still present for the current session or
+                to bring attention to the request. If this parameter contains
+                none with any other value, an error is returned.
+
+        max_age
+                OPTIONAL. Maximum Authentication Age. Specifies the allowable
+                elapsed time in seconds since the last time the End-User was
+                actively authenticated by the OP. If the elapsed time is
+                greater than this value, the OP MUST attempt to actively
+                re-authenticate the End-User. (The max_age request parameter
+                corresponds to the OpenID 2.0 PAPE [OpenID.PAPE] max_auth_age
+                request parameter.) When max_age is used, the ID Token returned
+                MUST include an auth_time Claim Value.
+
+        ui_locales
+                OPTIONAL. End-User's preferred languages and scripts for the
+                user interface, represented as a space-separated list of BCP47
+                [RFC5646] language tag values, ordered by preference. For
+                instance, the value "fr-CA fr en" represents a preference for
+                French as spoken in Canada, then French (without a region
+                designation), followed by English (without a region
+                designation). An error SHOULD NOT result if some or all of the
+                requested locales are not supported by the OpenID Provider.
+
+        id_token_hint
+                OPTIONAL. ID Token previously issued by the Authorization
+                Server being passed as a hint about the End-User's current or
+                past authenticated session with the Client. If the End-User
+                identified by the ID Token is logged in or is logged in by the
+                request, then the Authorization Server returns a positive
+                response; otherwise, it SHOULD return an error, such as
+                login_required. When possible, an id_token_hint SHOULD be
+                present when prompt=none is used and an invalid_request error
+                MAY be returned if it is not; however, the server SHOULD
+                respond successfully when possible, even if it is not present.
+                The Authorization Server need not be listed as an audience of
+                the ID Token when it is used as an id_token_hint value. If the
+                ID Token received by the RP from the OP is encrypted, to use it
+                as an id_token_hint, the Client MUST decrypt the signed ID
+                Token contained within the encrypted ID Token. The Client MAY
+                re-encrypt the signed ID token to the Authentication Server
+                using a key that enables the server to decrypt the ID Token,
+                and use the re-encrypted ID token as the id_token_hint value.
+
+        login_hint
+                OPTIONAL. Hint to the Authorization Server about the login
+                identifier the End-User might use to log in (if necessary).
+                This hint can be used by an RP if it first asks the End-User
+                for their e-mail address (or other identifier) and then wants
+                to pass that value as a hint to the discovered authorization
+                service. It is RECOMMENDED that the hint value match the value
+                used for discovery. This value MAY also be a phone number in
+                the format specified for the phone_number Claim. The use of
+                this parameter is left to the OP's discretion.
+
+        acr_values
+                OPTIONAL. Requested Authentication Context Class Reference
+                values. Space-separated string that specifies the acr values
+                that the Authorization Server is being requested to use for
+                processing this Authentication Request, with the values
+                appearing in order of preference. The Authentication Context
+                Class satisfied by the authentication performed is returned as
+                the acr Claim Value, as specified in Section 2. The acr Claim
+                is requested as a Voluntary Claim by this parameter.
+        """
+
+        # Treat it as normal OAuth 2 auth code request if openid is not present
+        if not request.scopes or 'openid' not in request.scopes:
+            return {}
+
+        prompt = request.prompt if request.prompt else []
+        if hasattr(prompt, 'split'):
+            prompt = prompt.strip().split()
+        prompt = set(prompt)
+
+        if 'none' in prompt:
+
+            if len(prompt) > 1:
+                msg = "Prompt none is mutually exclusive with other values."
+                raise InvalidRequestError(request=request, description=msg)
+
+            if not self.request_validator.validate_silent_login(request):
+                raise LoginRequired(request=request)
+
+            if not self.request_validator.validate_silent_authorization(request):
+                raise ConsentRequired(request=request)
+
+        self._inflate_claims(request)
+
+        if not self.request_validator.validate_user_match(
+                request.id_token_hint, request.scopes, request.claims, request):
+            msg = "Session user does not match client supplied user."
+            raise LoginRequired(request=request, description=msg)
+
+        request_info = {
+            'display': request.display,
+            'nonce': request.nonce,
+            'prompt': prompt,
+            'ui_locales': request.ui_locales.split() if request.ui_locales else [],
+            'id_token_hint': request.id_token_hint,
+            'login_hint': request.login_hint,
+            'claims': request.claims
+        }
+
+        return request_info
+
+
+OpenIDConnectBase = GrantTypeBase
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/grant_types/dispatchers.py b/.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/grant_types/dispatchers.py
new file mode 100644
index 00000000..5aa7d469
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/grant_types/dispatchers.py
@@ -0,0 +1,101 @@
+import logging
+
+log = logging.getLogger(__name__)
+
+
+class Dispatcher:
+    default_grant = None
+    oidc_grant = None
+
+
+class AuthorizationCodeGrantDispatcher(Dispatcher):
+    """
+    This is an adapter class that will route simple Authorization Code
+    requests, those that have `response_type=code` and a scope including
+    `openid` to either the `default_grant` or the `oidc_grant` based on
+    the scopes requested.
+    """
+    def __init__(self, default_grant=None, oidc_grant=None):
+        self.default_grant = default_grant
+        self.oidc_grant = oidc_grant
+
+    def _handler_for_request(self, request):
+        handler = self.default_grant
+
+        if request.scopes and "openid" in request.scopes:
+            handler = self.oidc_grant
+
+        log.debug('Selecting handler for request %r.', handler)
+        return handler
+
+    def create_authorization_response(self, request, token_handler):
+        """Read scope and route to the designated handler."""
+        return self._handler_for_request(request).create_authorization_response(request, token_handler)
+
+    def validate_authorization_request(self, request):
+        """Read scope and route to the designated handler."""
+        return self._handler_for_request(request).validate_authorization_request(request)
+
+
+class ImplicitTokenGrantDispatcher(Dispatcher):
+    """
+    This is an adapter class that will route simple Authorization
+    requests, those that have `id_token` in `response_type` and a scope
+    including `openid` to either the `default_grant` or the `oidc_grant`
+    based on the scopes requested.
+    """
+    def __init__(self, default_grant=None, oidc_grant=None):
+        self.default_grant = default_grant
+        self.oidc_grant = oidc_grant
+
+    def _handler_for_request(self, request):
+        handler = self.default_grant
+
+        if request.scopes and "openid" in request.scopes and 'id_token' in request.response_type:
+            handler = self.oidc_grant
+
+        log.debug('Selecting handler for request %r.', handler)
+        return handler
+
+    def create_authorization_response(self, request, token_handler):
+        """Read scope and route to the designated handler."""
+        return self._handler_for_request(request).create_authorization_response(request, token_handler)
+
+    def validate_authorization_request(self, request):
+        """Read scope and route to the designated handler."""
+        return self._handler_for_request(request).validate_authorization_request(request)
+
+
+class AuthorizationTokenGrantDispatcher(Dispatcher):
+    """
+    This is an adapter class that will route simple Token requests, those that authorization_code have a scope
+    including 'openid' to either the default_grant or the oidc_grant based on the scopes requested.
+    """
+    def __init__(self, request_validator, default_grant=None, oidc_grant=None):
+        self.default_grant = default_grant
+        self.oidc_grant = oidc_grant
+        self.request_validator = request_validator
+
+    def _handler_for_request(self, request):
+        handler = self.default_grant
+        scopes = ()
+        parameters = dict(request.decoded_body)
+        client_id = parameters.get('client_id', None)
+        code = parameters.get('code', None)
+        redirect_uri = parameters.get('redirect_uri', None)
+
+        # If code is not present fallback to `default_grant` which will
+        # raise an error for the missing `code` in `create_token_response` step.
+        if code:
+            scopes = self.request_validator.get_authorization_code_scopes(client_id, code, redirect_uri, request)
+
+        if 'openid' in scopes:
+            handler = self.oidc_grant
+
+        log.debug('Selecting handler for request %r.', handler)
+        return handler
+
+    def create_token_response(self, request, token_handler):
+        """Read scope and route to the designated handler."""
+        handler = self._handler_for_request(request)
+        return handler.create_token_response(request, token_handler)
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/grant_types/hybrid.py b/.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/grant_types/hybrid.py
new file mode 100644
index 00000000..7cb0758b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/grant_types/hybrid.py
@@ -0,0 +1,63 @@
+"""
+oauthlib.openid.connect.core.grant_types
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+"""
+import logging
+
+from oauthlib.oauth2.rfc6749.errors import InvalidRequestError
+from oauthlib.oauth2.rfc6749.grant_types.authorization_code import (
+    AuthorizationCodeGrant as OAuth2AuthorizationCodeGrant,
+)
+
+from ..request_validator import RequestValidator
+from .base import GrantTypeBase
+
+log = logging.getLogger(__name__)
+
+
+class HybridGrant(GrantTypeBase):
+
+    def __init__(self, request_validator=None, **kwargs):
+        self.request_validator = request_validator or RequestValidator()
+
+        self.proxy_target = OAuth2AuthorizationCodeGrant(
+            request_validator=request_validator, **kwargs)
+        # All hybrid response types should be fragment-encoded.
+        self.proxy_target.default_response_mode = "fragment"
+        self.register_response_type('code id_token')
+        self.register_response_type('code token')
+        self.register_response_type('code id_token token')
+        self.custom_validators.post_auth.append(
+            self.openid_authorization_validator)
+        # Hybrid flows can return the id_token from the authorization
+        # endpoint as part of the 'code' response
+        self.register_code_modifier(self.add_token)
+        self.register_code_modifier(self.add_id_token)
+        self.register_token_modifier(self.add_id_token)
+
+    def add_id_token(self, token, token_handler, request):
+        return super().add_id_token(token, token_handler, request, nonce=request.nonce)
+
+    def openid_authorization_validator(self, request):
+        """Additional validation when following the Authorization Code flow.
+        """
+        request_info = super().openid_authorization_validator(request)
+        if not request_info:  # returns immediately if OAuth2.0
+            return request_info
+
+        # REQUIRED if the Response Type of the request is `code
+        # id_token` or `code id_token token` and OPTIONAL when the
+        # Response Type of the request is `code token`. It is a string
+        # value used to associate a Client session with an ID Token,
+        # and to mitigate replay attacks. The value is passed through
+        # unmodified from the Authentication Request to the ID
+        # Token. Sufficient entropy MUST be present in the `nonce`
+        # values used to prevent attackers from guessing values. For
+        # implementation notes, see Section 15.5.2.
+        if request.response_type in ["code id_token", "code id_token token"]:
+            if not request.nonce:
+                raise InvalidRequestError(
+                    request=request,
+                    description='Request is missing mandatory nonce parameter.'
+                )
+        return request_info
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/grant_types/implicit.py b/.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/grant_types/implicit.py
new file mode 100644
index 00000000..a4fe6049
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/grant_types/implicit.py
@@ -0,0 +1,51 @@
+"""
+oauthlib.openid.connect.core.grant_types
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+"""
+import logging
+
+from oauthlib.oauth2.rfc6749.errors import InvalidRequestError
+from oauthlib.oauth2.rfc6749.grant_types.implicit import (
+    ImplicitGrant as OAuth2ImplicitGrant,
+)
+
+from .base import GrantTypeBase
+
+log = logging.getLogger(__name__)
+
+
+class ImplicitGrant(GrantTypeBase):
+
+    def __init__(self, request_validator=None, **kwargs):
+        self.proxy_target = OAuth2ImplicitGrant(
+            request_validator=request_validator, **kwargs)
+        self.register_response_type('id_token')
+        self.register_response_type('id_token token')
+        self.custom_validators.post_auth.append(
+            self.openid_authorization_validator)
+        self.register_token_modifier(self.add_id_token)
+
+    def add_id_token(self, token, token_handler, request):
+        if 'state' not in token and request.state:
+            token['state'] = request.state
+        return super().add_id_token(token, token_handler, request, nonce=request.nonce)
+
+    def openid_authorization_validator(self, request):
+        """Additional validation when following the implicit flow.
+        """
+        request_info = super().openid_authorization_validator(request)
+        if not request_info:  # returns immediately if OAuth2.0
+            return request_info
+
+        # REQUIRED. String value used to associate a Client session with an ID
+        # Token, and to mitigate replay attacks. The value is passed through
+        # unmodified from the Authentication Request to the ID Token.
+        # Sufficient entropy MUST be present in the nonce values used to
+        # prevent attackers from guessing values. For implementation notes, see
+        # Section 15.5.2.
+        if not request.nonce:
+            raise InvalidRequestError(
+                request=request,
+                description='Request is missing mandatory nonce parameter.'
+            )
+        return request_info
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/grant_types/refresh_token.py b/.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/grant_types/refresh_token.py
new file mode 100644
index 00000000..43e4499c
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/grant_types/refresh_token.py
@@ -0,0 +1,34 @@
+"""
+oauthlib.openid.connect.core.grant_types
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+"""
+import logging
+
+from oauthlib.oauth2.rfc6749.grant_types.refresh_token import (
+    RefreshTokenGrant as OAuth2RefreshTokenGrant,
+)
+
+from .base import GrantTypeBase
+
+log = logging.getLogger(__name__)
+
+
+class RefreshTokenGrant(GrantTypeBase):
+
+    def __init__(self, request_validator=None, **kwargs):
+        self.proxy_target = OAuth2RefreshTokenGrant(
+            request_validator=request_validator, **kwargs)
+        self.register_token_modifier(self.add_id_token)
+
+    def add_id_token(self, token, token_handler, request):
+        """
+        Construct an initial version of id_token, and let the
+        request_validator sign or encrypt it.
+
+        The authorization_code version of this method is used to
+        retrieve the nonce accordingly to the code storage.
+        """
+        if not self.request_validator.refresh_id_token(request):
+            return token
+
+        return super().add_id_token(token, token_handler, request)
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/request_validator.py b/.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/request_validator.py
new file mode 100644
index 00000000..47c4cd94
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/request_validator.py
@@ -0,0 +1,320 @@
+"""
+oauthlib.openid.connect.core.request_validator
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+"""
+import logging
+
+from oauthlib.oauth2.rfc6749.request_validator import (
+    RequestValidator as OAuth2RequestValidator,
+)
+
+log = logging.getLogger(__name__)
+
+
+class RequestValidator(OAuth2RequestValidator):
+
+    def get_authorization_code_scopes(self, client_id, code, redirect_uri, request):
+        """ Extracts scopes from saved authorization code.
+
+        The scopes returned by this method is used to route token requests
+        based on scopes passed to Authorization Code requests.
+
+        With that the token endpoint knows when to include OpenIDConnect
+        id_token in token response only based on authorization code scopes.
+
+        Only code param should be sufficient to retrieve grant code from
+        any storage you are using, `client_id` and `redirect_uri` can have a
+        blank value `""` don't forget to check it before using those values
+        in a select query if a database is used.
+
+        :param client_id: Unicode client identifier
+        :param code: Unicode authorization code grant
+        :param redirect_uri: Unicode absolute URI
+        :return: A list of scope
+
+        Method is used by:
+            - Authorization Token Grant Dispatcher
+        """
+        raise NotImplementedError('Subclasses must implement this method.')
+
+    def get_authorization_code_nonce(self, client_id, code, redirect_uri, request):
+        """ Extracts nonce from saved authorization code.
+
+        If present in the Authentication Request, Authorization
+        Servers MUST include a nonce Claim in the ID Token with the
+        Claim Value being the nonce value sent in the Authentication
+        Request. Authorization Servers SHOULD perform no other
+        processing on nonce values used. The nonce value is a
+        case-sensitive string.
+
+        Only code param should be sufficient to retrieve grant code from
+        any storage you are using. However, `client_id` and `redirect_uri`
+        have been validated and can be used also.
+
+        :param client_id: Unicode client identifier
+        :param code: Unicode authorization code grant
+        :param redirect_uri: Unicode absolute URI
+        :return: Unicode nonce
+
+        Method is used by:
+            - Authorization Token Grant Dispatcher
+        """
+        raise NotImplementedError('Subclasses must implement this method.')
+
+    def get_jwt_bearer_token(self, token, token_handler, request):
+        """Get JWT Bearer token or OpenID Connect ID token
+
+        If using OpenID Connect this SHOULD call `oauthlib.oauth2.RequestValidator.get_id_token`
+
+        :param token: A Bearer token dict
+        :param token_handler: the token handler (BearerToken class)
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :return: The JWT Bearer token or OpenID Connect ID token (a JWS signed JWT)
+
+        Method is used by JWT Bearer and OpenID Connect tokens:
+            - JWTToken.create_token
+        """
+        raise NotImplementedError('Subclasses must implement this method.')
+
+    def get_id_token(self, token, token_handler, request):
+        """Get OpenID Connect ID token
+
+        This method is OPTIONAL and is NOT RECOMMENDED.
+        `finalize_id_token` SHOULD be implemented instead. However, if you
+        want a full control over the minting of the `id_token`, you
+        MAY want to override `get_id_token` instead of using
+        `finalize_id_token`.
+
+        In the OpenID Connect workflows when an ID Token is requested this method is called.
+        Subclasses should implement the construction, signing and optional encryption of the
+        ID Token as described in the OpenID Connect spec.
+
+        In addition to the standard OAuth2 request properties, the request may also contain
+        these OIDC specific properties which are useful to this method:
+
+            - nonce, if workflow is implicit or hybrid and it was provided
+            - claims, if provided to the original Authorization Code request
+
+        The token parameter is a dict which may contain an ``access_token`` entry, in which
+        case the resulting ID Token *should* include a calculated ``at_hash`` claim.
+
+        Similarly, when the request parameter has a ``code`` property defined, the ID Token
+        *should* include a calculated ``c_hash`` claim.
+
+        http://openid.net/specs/openid-connect-core-1_0.html (sections `3.1.3.6`_, `3.2.2.10`_, `3.3.2.11`_)
+
+        .. _`3.1.3.6`: http://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken
+        .. _`3.2.2.10`: http://openid.net/specs/openid-connect-core-1_0.html#ImplicitIDToken
+        .. _`3.3.2.11`: http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken
+
+        :param token: A Bearer token dict
+        :param token_handler: the token handler (BearerToken class)
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :return: The ID Token (a JWS signed JWT)
+        """
+        return None
+
+    def finalize_id_token(self, id_token, token, token_handler, request):
+        """Finalize OpenID Connect ID token & Sign or Encrypt.
+
+        In the OpenID Connect workflows when an ID Token is requested
+        this method is called.  Subclasses should implement the
+        construction, signing and optional encryption of the ID Token
+        as described in the OpenID Connect spec.
+
+        The `id_token` parameter is a dict containing a couple of OIDC
+        technical fields related to the specification. Prepopulated
+        attributes are:
+
+        - `aud`, equals to `request.client_id`.
+        - `iat`, equals to current time.
+        - `nonce`, if present, is equals to the `nonce` from the
+          authorization request.
+        - `at_hash`, hash of `access_token`, if relevant.
+        - `c_hash`, hash of `code`, if relevant.
+
+        This method MUST provide required fields as below:
+
+        - `iss`, REQUIRED. Issuer Identifier for the Issuer of the response.
+        - `sub`, REQUIRED. Subject Identifier
+        - `exp`, REQUIRED. Expiration time on or after which the ID
+          Token MUST NOT be accepted by the RP when performing
+          authentication with the OP.
+
+        Additionals claims must be added, note that `request.scope`
+        should be used to determine the list of claims.
+
+        More information can be found at `OpenID Connect Core#Claims`_
+
+        .. _`OpenID Connect Core#Claims`: https://openid.net/specs/openid-connect-core-1_0.html#Claims
+
+        :param id_token: A dict containing technical fields of id_token
+        :param token: A Bearer token dict
+        :param token_handler: the token handler (BearerToken class)
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :return: The ID Token (a JWS signed JWT or JWE encrypted JWT)
+        """
+        raise NotImplementedError('Subclasses must implement this method.')
+
+    def validate_jwt_bearer_token(self, token, scopes, request):
+        """Ensure the JWT Bearer token or OpenID Connect ID token are valids and authorized access to scopes.
+
+        If using OpenID Connect this SHOULD call `oauthlib.oauth2.RequestValidator.get_id_token`
+
+        If not using OpenID Connect this can `return None` to avoid 5xx rather 401/3 response.
+
+        OpenID connect core 1.0 describe how to validate an id_token:
+            - http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
+            - http://openid.net/specs/openid-connect-core-1_0.html#ImplicitIDTValidation
+            - http://openid.net/specs/openid-connect-core-1_0.html#HybridIDTValidation
+            - http://openid.net/specs/openid-connect-core-1_0.html#HybridIDTValidation2
+
+        :param token: Unicode Bearer token
+        :param scopes: List of scopes (defined by you)
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :rtype: True or False
+
+        Method is indirectly used by all core OpenID connect JWT token issuing grant types:
+            - Authorization Code Grant
+            - Implicit Grant
+            - Hybrid Grant
+        """
+        raise NotImplementedError('Subclasses must implement this method.')
+
+    def validate_id_token(self, token, scopes, request):
+        """Ensure the id token is valid and authorized access to scopes.
+
+        OpenID connect core 1.0 describe how to validate an id_token:
+            - http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
+            - http://openid.net/specs/openid-connect-core-1_0.html#ImplicitIDTValidation
+            - http://openid.net/specs/openid-connect-core-1_0.html#HybridIDTValidation
+            - http://openid.net/specs/openid-connect-core-1_0.html#HybridIDTValidation2
+
+        :param token: Unicode Bearer token
+        :param scopes: List of scopes (defined by you)
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :rtype: True or False
+
+        Method is indirectly used by all core OpenID connect JWT token issuing grant types:
+            - Authorization Code Grant
+            - Implicit Grant
+            - Hybrid Grant
+        """
+        raise NotImplementedError('Subclasses must implement this method.')
+
+    def validate_silent_authorization(self, request):
+        """Ensure the logged in user has authorized silent OpenID authorization.
+
+        Silent OpenID authorization allows access tokens and id tokens to be
+        granted to clients without any user prompt or interaction.
+
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :rtype: True or False
+
+        Method is used by:
+            - OpenIDConnectAuthCode
+            - OpenIDConnectImplicit
+            - OpenIDConnectHybrid
+        """
+        raise NotImplementedError('Subclasses must implement this method.')
+
+    def validate_silent_login(self, request):
+        """Ensure session user has authorized silent OpenID login.
+
+        If no user is logged in or has not authorized silent login, this
+        method should return False.
+
+        If the user is logged in but associated with multiple accounts and
+        not selected which one to link to the token then this method should
+        raise an oauthlib.oauth2.AccountSelectionRequired error.
+
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :rtype: True or False
+
+        Method is used by:
+            - OpenIDConnectAuthCode
+            - OpenIDConnectImplicit
+            - OpenIDConnectHybrid
+        """
+        raise NotImplementedError('Subclasses must implement this method.')
+
+    def validate_user_match(self, id_token_hint, scopes, claims, request):
+        """Ensure client supplied user id hint matches session user.
+
+        If the sub claim or id_token_hint is supplied then the session
+        user must match the given ID.
+
+        :param id_token_hint: User identifier string.
+        :param scopes: List of OAuth 2 scopes and OpenID claims (strings).
+        :param claims: OpenID Connect claims dict.
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :rtype: True or False
+
+        Method is used by:
+            - OpenIDConnectAuthCode
+            - OpenIDConnectImplicit
+            - OpenIDConnectHybrid
+        """
+        raise NotImplementedError('Subclasses must implement this method.')
+
+    def get_userinfo_claims(self, request):
+        """Return the UserInfo claims in JSON or Signed or Encrypted.
+
+        The UserInfo Claims MUST be returned as the members of a JSON object
+         unless a signed or encrypted response was requested during Client
+         Registration. The Claims defined in Section 5.1 can be returned, as can
+         additional Claims not specified there.
+
+        For privacy reasons, OpenID Providers MAY elect to not return values for
+        some requested Claims.
+
+        If a Claim is not returned, that Claim Name SHOULD be omitted from the
+        JSON object representing the Claims; it SHOULD NOT be present with a
+        null or empty string value.
+
+        The sub (subject) Claim MUST always be returned in the UserInfo
+        Response.
+
+        Upon receipt of the UserInfo Request, the UserInfo Endpoint MUST return
+        the JSON Serialization of the UserInfo Response as in Section 13.3 in
+        the HTTP response body unless a different format was specified during
+        Registration [OpenID.Registration].
+
+        If the UserInfo Response is signed and/or encrypted, then the Claims are
+        returned in a JWT and the content-type MUST be application/jwt. The
+        response MAY be encrypted without also being signed. If both signing and
+        encryption are requested, the response MUST be signed then encrypted,
+        with the result being a Nested JWT, as defined in [JWT].
+
+        If signed, the UserInfo Response SHOULD contain the Claims iss (issuer)
+        and aud (audience) as members. The iss value SHOULD be the OP's Issuer
+        Identifier URL. The aud value SHOULD be or include the RP's Client ID
+        value.
+
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :rtype: Claims as a dict OR JWT/JWS/JWE as a string
+
+        Method is used by:
+            UserInfoEndpoint
+        """
+
+    def refresh_id_token(self, request):
+        """Whether the id token should be refreshed. Default, True
+
+        :param request: OAuthlib request.
+        :type request: oauthlib.common.Request
+        :rtype: True or False
+
+        Method is used by:
+            RefreshTokenGrant
+        """
+        return True
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/tokens.py b/.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/tokens.py
new file mode 100644
index 00000000..936ab52e
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/openid/connect/core/tokens.py
@@ -0,0 +1,48 @@
+"""
+authlib.openid.connect.core.tokens
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This module contains methods for adding JWT tokens to requests.
+"""
+from oauthlib.oauth2.rfc6749.tokens import (
+    TokenBase, get_token_from_header, random_token_generator,
+)
+
+
+class JWTToken(TokenBase):
+    __slots__ = (
+        'request_validator', 'token_generator',
+        'refresh_token_generator', 'expires_in'
+    )
+
+    def __init__(self, request_validator=None, token_generator=None,
+                 expires_in=None, refresh_token_generator=None):
+        self.request_validator = request_validator
+        self.token_generator = token_generator or random_token_generator
+        self.refresh_token_generator = (
+            refresh_token_generator or self.token_generator
+        )
+        self.expires_in = expires_in or 3600
+
+    def create_token(self, request, refresh_token=False):
+        """Create a JWT Token, using requestvalidator method."""
+
+        if callable(self.expires_in):
+            expires_in = self.expires_in(request)
+        else:
+            expires_in = self.expires_in
+
+        request.expires_in = expires_in
+
+        return self.request_validator.get_jwt_bearer_token(None, None, request)
+
+    def validate_request(self, request):
+        token = get_token_from_header(request)
+        return self.request_validator.validate_jwt_bearer_token(
+            token, request.scopes, request)
+
+    def estimate_type(self, request):
+        token = get_token_from_header(request)
+        if token and token.startswith('ey') and token.count('.') in (2, 4):
+            return 10
+        return 0
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/signals.py b/.venv/lib/python3.12/site-packages/oauthlib/signals.py
new file mode 100644
index 00000000..8fd347a5
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/signals.py
@@ -0,0 +1,40 @@
+"""
+    Implements signals based on blinker if available, otherwise
+    falls silently back to a noop. Shamelessly stolen from flask.signals:
+    https://github.com/mitsuhiko/flask/blob/master/flask/signals.py
+"""
+signals_available = False
+try:
+    from blinker import Namespace
+    signals_available = True
+except ImportError:  # noqa
+    class Namespace:
+        def signal(self, name, doc=None):
+            return _FakeSignal(name, doc)
+
+    class _FakeSignal:
+        """If blinker is unavailable, create a fake class with the same
+        interface that allows sending of signals but will fail with an
+        error on anything else.  Instead of doing anything on send, it
+        will just ignore the arguments and do nothing instead.
+        """
+
+        def __init__(self, name, doc=None):
+            self.name = name
+            self.__doc__ = doc
+        def _fail(self, *args, **kwargs):
+            raise RuntimeError('signalling support is unavailable '
+                               'because the blinker library is '
+                               'not installed.')
+        send = lambda *a, **kw: None
+        connect = disconnect = has_receivers_for = receivers_for = \
+            temporarily_connected_to = connected_to = _fail
+        del _fail
+
+# The namespace for code signals.  If you are not oauthlib code, do
+# not put signals in here.  Create your own namespace instead.
+_signals = Namespace()
+
+
+# Core signals.
+scope_changed = _signals.signal('scope-changed')
diff --git a/.venv/lib/python3.12/site-packages/oauthlib/uri_validate.py b/.venv/lib/python3.12/site-packages/oauthlib/uri_validate.py
new file mode 100644
index 00000000..a6fe0fb2
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/oauthlib/uri_validate.py
@@ -0,0 +1,190 @@
+"""
+Regex for URIs
+
+These regex are directly derived from the collected ABNF in RFC3986
+(except for DIGIT, ALPHA and HEXDIG, defined by RFC2234).
+
+They should be processed with re.VERBOSE.
+
+Thanks Mark Nottingham for this code - https://gist.github.com/138549
+"""
+import re
+
+# basics
+
+DIGIT = r"[\x30-\x39]"
+
+ALPHA = r"[\x41-\x5A\x61-\x7A]"
+
+HEXDIG = r"[\x30-\x39A-Fa-f]"
+
+#   pct-encoded   = "%" HEXDIG HEXDIG
+pct_encoded = r" %% %(HEXDIG)s %(HEXDIG)s" % locals()
+
+#   unreserved    = ALPHA / DIGIT / "-" / "." / "_" / "~"
+unreserved = r"(?: %(ALPHA)s | %(DIGIT)s | \- | \. | _ | ~ )" % locals()
+
+# gen-delims    = ":" / "/" / "?" / "#" / "[" / "]" / "@"
+gen_delims = r"(?: : | / | \? | \# | \[ | \] | @ )"
+
+#   sub-delims    = "!" / "$" / "&" / "'" / "(" / ")"
+#                 / "*" / "+" / "," / ";" / "="
+sub_delims = r"""(?: ! | \$ | & | ' | \( | \) |
+                     \* | \+ | , | ; | = )"""
+
+#   pchar         = unreserved / pct-encoded / sub-delims / ":" / "@"
+pchar = r"(?: %(unreserved)s | %(pct_encoded)s | %(sub_delims)s | : | @ )" % locals(
+)
+
+#   reserved      = gen-delims / sub-delims
+reserved = r"(?: %(gen_delims)s | %(sub_delims)s )" % locals()
+
+
+# scheme
+
+#   scheme        = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
+scheme = r"%(ALPHA)s (?: %(ALPHA)s | %(DIGIT)s | \+ | \- | \. )*" % locals()
+
+
+# authority
+
+#   dec-octet     = DIGIT                 ; 0-9
+#                 / %x31-39 DIGIT         ; 10-99
+#                 / "1" 2DIGIT            ; 100-199
+#                 / "2" %x30-34 DIGIT     ; 200-249
+#                 / "25" %x30-35          ; 250-255
+dec_octet = r"""(?: %(DIGIT)s |
+                    [\x31-\x39] %(DIGIT)s |
+                    1 %(DIGIT)s{2} |
+                    2 [\x30-\x34] %(DIGIT)s |
+                    25 [\x30-\x35]
+                )
+""" % locals()
+
+#  IPv4address   = dec-octet "." dec-octet "." dec-octet "." dec-octet
+IPv4address = r"%(dec_octet)s \. %(dec_octet)s \. %(dec_octet)s \. %(dec_octet)s" % locals(
+)
+
+#   IPv6address
+IPv6address = r"([A-Fa-f0-9:]+[:$])[A-Fa-f0-9]{1,4}"
+
+#   IPvFuture     = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" )
+IPvFuture = r"v %(HEXDIG)s+ \. (?: %(unreserved)s | %(sub_delims)s | : )+" % locals()
+
+#   IP-literal    = "[" ( IPv6address / IPvFuture  ) "]"
+IP_literal = r"\[ (?: %(IPv6address)s | %(IPvFuture)s ) \]" % locals()
+
+#   reg-name      = *( unreserved / pct-encoded / sub-delims )
+reg_name = r"(?: %(unreserved)s | %(pct_encoded)s | %(sub_delims)s )*" % locals()
+
+#   userinfo      = *( unreserved / pct-encoded / sub-delims / ":" )
+userinfo = r"(?: %(unreserved)s | %(pct_encoded)s | %(sub_delims)s | : )" % locals(
+)
+
+#   host          = IP-literal / IPv4address / reg-name
+host = r"(?: %(IP_literal)s | %(IPv4address)s | %(reg_name)s )" % locals()
+
+#   port          = *DIGIT
+port = r"(?: %(DIGIT)s )*" % locals()
+
+#   authority     = [ userinfo "@" ] host [ ":" port ]
+authority = r"(?: %(userinfo)s @)? %(host)s (?: : %(port)s)?" % locals()
+
+# Path
+
+#   segment       = *pchar
+segment = r"%(pchar)s*" % locals()
+
+#   segment-nz    = 1*pchar
+segment_nz = r"%(pchar)s+" % locals()
+
+#   segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" )
+#                 ; non-zero-length segment without any colon ":"
+segment_nz_nc = r"(?: %(unreserved)s | %(pct_encoded)s | %(sub_delims)s | @ )+" % locals()
+
+#   path-abempty  = *( "/" segment )
+path_abempty = r"(?: / %(segment)s )*" % locals()
+
+#   path-absolute = "/" [ segment-nz *( "/" segment ) ]
+path_absolute = r"/ (?: %(segment_nz)s (?: / %(segment)s )* )?" % locals()
+
+#   path-noscheme = segment-nz-nc *( "/" segment )
+path_noscheme = r"%(segment_nz_nc)s (?: / %(segment)s )*" % locals()
+
+#   path-rootless = segment-nz *( "/" segment )
+path_rootless = r"%(segment_nz)s (?: / %(segment)s )*" % locals()
+
+#   path-empty    = 0<pchar>
+path_empty = r""  # FIXME
+
+#   path          = path-abempty    ; begins with "/" or is empty
+#                 / path-absolute   ; begins with "/" but not "//"
+#                 / path-noscheme   ; begins with a non-colon segment
+#                 / path-rootless   ; begins with a segment
+#                 / path-empty      ; zero characters
+path = r"""(?: %(path_abempty)s |
+               %(path_absolute)s |
+               %(path_noscheme)s |
+               %(path_rootless)s |
+               %(path_empty)s
+            )
+""" % locals()
+
+### Query and Fragment
+
+#   query         = *( pchar / "/" / "?" )
+query = r"(?: %(pchar)s | / | \? )*" % locals()
+
+#   fragment      = *( pchar / "/" / "?" )
+fragment = r"(?: %(pchar)s | / | \? )*" % locals()
+
+# URIs
+
+#   hier-part     = "//" authority path-abempty
+#                 / path-absolute
+#                 / path-rootless
+#                 / path-empty
+hier_part = r"""(?: (?: // %(authority)s %(path_abempty)s ) |
+                    %(path_absolute)s |
+                    %(path_rootless)s |
+                    %(path_empty)s
+                )
+""" % locals()
+
+#   relative-part = "//" authority path-abempty
+#                 / path-absolute
+#                 / path-noscheme
+#                 / path-empty
+relative_part = r"""(?: (?: // %(authority)s %(path_abempty)s ) |
+                        %(path_absolute)s |
+                        %(path_noscheme)s |
+                        %(path_empty)s
+                    )
+""" % locals()
+
+# relative-ref  = relative-part [ "?" query ] [ "#" fragment ]
+relative_ref = r"%(relative_part)s (?: \? %(query)s)? (?: \# %(fragment)s)?" % locals(
+)
+
+# URI           = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
+URI = r"^(?: %(scheme)s : %(hier_part)s (?: \? %(query)s )? (?: \# %(fragment)s )? )$" % locals(
+)
+
+#   URI-reference = URI / relative-ref
+URI_reference = r"^(?: %(URI)s | %(relative_ref)s )$" % locals()
+
+#   absolute-URI  = scheme ":" hier-part [ "?" query ]
+absolute_URI = r"^(?: %(scheme)s : %(hier_part)s (?: \? %(query)s )? )$" % locals(
+)
+
+
+def is_uri(uri):
+    return re.match(URI, uri, re.VERBOSE)
+
+
+def is_uri_reference(uri):
+    return re.match(URI_reference, uri, re.VERBOSE)
+
+
+def is_absolute_uri(uri):
+    return re.match(absolute_URI, uri, re.VERBOSE)