Functional Weave
Code in Python

auth.access-token@1.0.0

impl/python/read_access_token.py

2,640 bytes · the Python implementation · view raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

from typing import Any, Optional, Sequence

from .auth_access_token_types import AccessCheck
from .auth_bearer_token import parse_bearer_token  ← from auth.bearer-token ^1.0.0 · built alongside by fune
from .auth_jwt_decode_jwt import check_jwt_secret
from .auth_jwt_verify_jwt import verify_jwt
from .time_unix_to_iso import unix_to_iso  ← from time.unix-to-iso ^1.0.0 · built alongside by fune

#: The latest second time.unix-to-iso can write.
MAX_EXP = 253402300799


def access_denied(error: str, message: str) -> AccessCheck:
    """A failed check: every field but the reason is None."""
    return AccessCheck(
        ok=False, subject=None, token_version=None, jti=None, expires_at=None, claims=None, error=error, message=message
    )


def _whole(value: Any) -> Optional[int]:
    # A JSON 1.0 is a float in Python and 1 in JavaScript; both are whole.
    if isinstance(value, bool):
        return None
    if isinstance(value, int):
        return value
    if isinstance(value, float) and value.is_integer():
        return int(value)
    return None


def read_access_token(authorization: Optional[str], secret: Sequence[int], now: int, leeway_seconds: int) -> AccessCheck:
    """Who is making this request? The Bearer token from the Authorization
    header, verified, and required to carry the claims issue_access_token
    writes. A missing or bad token is an answer (401), never an exception."""
    check_jwt_secret(secret)
    if isinstance(now, bool) or not isinstance(now, int):
        raise TypeError("now must be a whole number of Unix seconds")
    if isinstance(leeway_seconds, bool) or not isinstance(leeway_seconds, int) or leeway_seconds < 0:
        raise ValueError("leewaySeconds must be a whole number, 0 or more")

    token = parse_bearer_token(authorization)
    if token is None:
        return access_denied("missing_token", "the request has no Bearer token")
    verified = verify_jwt(token, secret, now, leeway_seconds)
    if not verified.valid or verified.claims is None:
        return access_denied(verified.error or "malformed_token", verified.message or "the token is not a well-formed JWT")
    claims = verified.claims
    sub, jti = claims.get("sub"), claims.get("jti")
    ver, exp = _whole(claims.get("ver")), _whole(claims.get("exp"))
    if (
        not isinstance(sub, str) or len(sub) == 0
        or not isinstance(jti, str) or len(jti) == 0
        or ver is None or ver < 0
        or exp is None or exp < 0 or exp > MAX_EXP
    ):
        return access_denied("invalid_claims", "the token lacks the sub, jti, ver or exp this API issues")
    return AccessCheck(
        ok=True, subject=sub, token_version=ver, jti=jti, expires_at=unix_to_iso(exp), claims=claims, error=None, message=None
    )