Files
ProxMenux/AppImage/scripts/simple_jwt.py
2026-03-15 20:00:10 +01:00

137 lines
3.9 KiB
Python

"""
Simple JWT Implementation
A minimal JWT implementation using only Python standard library.
Supports HS256 algorithm without requiring cryptography or PyJWT.
This ensures compatibility across all Python versions and systems.
"""
import hmac
import hashlib
import base64
import json
import time
from typing import Optional, Dict, Any
class ExpiredSignatureError(Exception):
"""Token has expired"""
pass
class InvalidTokenError(Exception):
"""Token is invalid"""
pass
def _base64url_encode(data: bytes) -> str:
"""Encode bytes to base64url string (no padding)"""
return base64.urlsafe_b64encode(data).rstrip(b'=').decode('utf-8')
def _base64url_decode(data: str) -> bytes:
"""Decode base64url string to bytes"""
# Add padding if needed
padding = 4 - len(data) % 4
if padding != 4:
data += '=' * padding
return base64.urlsafe_b64decode(data.encode('utf-8'))
def encode(payload: Dict[str, Any], secret: str, algorithm: str = "HS256") -> str:
"""
Encode a payload into a JWT token.
Args:
payload: Dictionary containing the claims
secret: Secret key for signing
algorithm: Algorithm to use (only HS256 supported)
Returns:
JWT token string
"""
if algorithm != "HS256":
raise ValueError(f"Algorithm {algorithm} not supported. Only HS256 is available.")
# Header
header = {"typ": "JWT", "alg": "HS256"}
header_b64 = _base64url_encode(json.dumps(header, separators=(',', ':')).encode('utf-8'))
# Payload
payload_b64 = _base64url_encode(json.dumps(payload, separators=(',', ':')).encode('utf-8'))
# Signature
message = f"{header_b64}.{payload_b64}"
signature = hmac.new(
secret.encode('utf-8'),
message.encode('utf-8'),
hashlib.sha256
).digest()
signature_b64 = _base64url_encode(signature)
return f"{header_b64}.{payload_b64}.{signature_b64}"
def decode(token: str, secret: str, algorithms: list = None) -> Dict[str, Any]:
"""
Decode and verify a JWT token.
Args:
token: JWT token string
secret: Secret key for verification
algorithms: List of allowed algorithms (ignored, only HS256 supported)
Returns:
Decoded payload dictionary
Raises:
InvalidTokenError: If token is malformed or signature is invalid
ExpiredSignatureError: If token has expired
"""
try:
parts = token.split('.')
if len(parts) != 3:
raise InvalidTokenError("Token must have 3 parts")
header_b64, payload_b64, signature_b64 = parts
# Verify signature
message = f"{header_b64}.{payload_b64}"
expected_signature = hmac.new(
secret.encode('utf-8'),
message.encode('utf-8'),
hashlib.sha256
).digest()
actual_signature = _base64url_decode(signature_b64)
if not hmac.compare_digest(expected_signature, actual_signature):
raise InvalidTokenError("Signature verification failed")
# Decode payload
payload = json.loads(_base64url_decode(payload_b64).decode('utf-8'))
# Check expiration
if 'exp' in payload:
if time.time() > payload['exp']:
raise ExpiredSignatureError("Token has expired")
return payload
except (ValueError, KeyError, json.JSONDecodeError) as e:
raise InvalidTokenError(f"Invalid token format: {e}")
# Compatibility aliases for PyJWT interface
class PyJWTCompat:
"""Compatibility class to mimic PyJWT interface"""
ExpiredSignatureError = ExpiredSignatureError
InvalidTokenError = InvalidTokenError
@staticmethod
def encode(payload, secret, algorithm="HS256"):
return encode(payload, secret, algorithm)
@staticmethod
def decode(token, secret, algorithms=None):
return decode(token, secret, algorithms)