Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
import json
from typing import Any, Mapping, Sequence
from .auth_jwt_decode_jwt import check_jwt_secret ← decodeJwt, another function of this group · built into the same file, even by a slim install
from .crypto_hmac_sha256 import hmac_sha256 ← from crypto.hmac-sha256 ^1.0.0 · built alongside by fune
from .encoding_base64_base64_url_encode import base64_url_encode
from .encoding_utf8_utf8_encode import utf8_encode
#: base64url of {"alg":"HS256","typ":"JWT"}: the header is fixed.
HEADER = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"
MAX_DEPTH = 32
MAX_SAFE = 9007199254740991
def _canonical(value: Any, depth: int) -> Any:
"""The value with every number made a whole int, checked the way the other
languages check it; json.dumps then writes it with sorted keys."""
if value is None or isinstance(value, (bool, str)):
return value
if isinstance(value, (int, float)):
if isinstance(value, float) and not value.is_integer():
raise ValueError("claims may only hold whole numbers from -(2^53 - 1) to 2^53 - 1")
if abs(value) > MAX_SAFE:
raise ValueError("claims may only hold whole numbers from -(2^53 - 1) to 2^53 - 1")
return int(value)
if isinstance(value, (list, tuple)):
if depth > MAX_DEPTH:
raise ValueError("claims are nested more than 32 deep")
return [_canonical(item, depth + 1) for item in value]
if isinstance(value, Mapping):
if depth > MAX_DEPTH:
raise ValueError("claims are nested more than 32 deep")
out = {}
for key, item in value.items():
if not isinstance(key, str):
raise TypeError("claims must hold only JSON values")
out[key] = _canonical(item, depth + 1)
return out
raise TypeError("claims must hold only JSON values")
def sign_jwt(claims: Mapping[str, Any], secret: Sequence[int]) -> str:
"""An HS256 JWT for the claims: header {"alg":"HS256","typ":"JWT"}, the
claims as canonical JSON (no spaces, keys sorted by code point, UTF-8
rather than \\u escapes), and the HMAC-SHA256 of the two."""
check_jwt_secret(secret)
if not isinstance(claims, Mapping):
raise TypeError("claims must be a JSON object")
payload = json.dumps(_canonical(claims, 1), sort_keys=True, separators=(",", ":"), ensure_ascii=False, allow_nan=False)
signing_input = HEADER + "." + base64_url_encode(utf8_encode(payload))
return signing_input + "." + base64_url_encode(hmac_sha256(secret, utf8_encode(signing_input)))