refactor(auth): move JWT helpers to app.domains.auth.jwt, break auth circular import (P4.3 cont)

This commit is contained in:
Crypto Rug Munch 2026-07-07 04:56:23 +07:00
parent 7bd204f9f5
commit 59d6f2f0f6
3 changed files with 71 additions and 58 deletions

View file

@ -14,10 +14,18 @@ from typing import Any
import bcrypt
from fastapi import APIRouter, HTTPException, Request
from jose import JWTError, jwt
from pydantic import BaseModel, Field
from app.core.redis import get_redis
from app.domains.auth.jwt import (
JWT_ALGORITHM,
JWT_EXPIRY_DAYS,
JWT_SECRET,
_create_jwt,
_derive_user_id,
_verify_jwt,
generate_nonce,
)
logger = logging.getLogger(__name__)
@ -95,9 +103,6 @@ def _verify_backup_code(code: str, hashed: str) -> bool:
# ── Config ──
JWT_SECRET = os.getenv("JWT_SECRET", "rmi-jwt-secret-change-me")
JWT_ALGORITHM = "HS256"
JWT_EXPIRY_DAYS = 7
def hash_password(password: str) -> str:
@ -113,7 +118,7 @@ def verify_password(password: str, hashed: str) -> bool:
return False
# ── Redis User Store ──
# ── Storage (Redis-backed user store) ──
def _get_user(user_id: str) -> dict[str, Any] | None:
"""Fetch user record by id from Redis hash rmi:users."""
r = get_redis()
@ -159,45 +164,6 @@ def _is_valid_email(email: str) -> bool:
return re.match(pattern, email) is not None
def _create_jwt(user_id: str, email: str, tier: str = "FREE", role: str = "USER", wallet: str | None = None) -> str:
now = datetime.utcnow()
payload = {
"sub": user_id,
"email": email,
"tier": tier,
"role": role,
"iat": now,
"exp": now + timedelta(days=JWT_EXPIRY_DAYS),
}
if wallet:
payload["wallet"] = wallet
return jwt.encode(payload, JWT_SECRET, algorithm=JWT_ALGORITHM)
def _verify_jwt(token: str) -> dict[str, Any] | None:
try:
payload = jwt.decode(token, JWT_SECRET, algorithms=[JWT_ALGORITHM])
return {
"id": payload["sub"],
"email": payload["email"],
"tier": payload.get("tier", "FREE"),
"role": payload.get("role", "USER"),
"wallet": payload.get("wallet"),
}
except JWTError:
return None
def _derive_user_id(email: str) -> str:
import hashlib
return hashlib.sha256(email.lower().encode()).hexdigest()[:32]
def generate_nonce() -> str:
return secrets.token_urlsafe(16)
# ── Storage (Redis-backed user store) ──
class EmailLoginRequest(BaseModel):
email: str
@ -438,7 +404,7 @@ async def wallet_nonce(req: WalletNonceRequest):
@router.post("/wallet/verify", response_model=WalletAuthResponse)
async def wallet_verify(req: WalletVerifyRequest):
"""Verify wallet signature and create session."""
from app.auth_wallet import get_or_create_wallet_user, verify_wallet_signature
from app.domains.auth.wallet import get_or_create_wallet_user, verify_wallet_signature
if not verify_wallet_signature(req.message, req.signature, req.address):
raise HTTPException(status_code=401, detail="Invalid signature")

View file

@ -1,18 +1,65 @@
"""JWT helpers - re-exports from app.auth for cleaner import paths.
"""JWT helpers.
Phase 3B of AUDIT-2026-Q3.md.
Public API:
- JWT_SECRET, JWT_ALGORITHM, JWT_EXPIRY_DAYS
- _create_jwt, _verify_jwt
- generate_nonce
- generate_nonce, _derive_user_id
"""
from app.auth import ( # noqa: F401
JWT_ALGORITHM,
JWT_EXPIRY_DAYS,
JWT_SECRET,
_create_jwt,
_derive_user_id,
_verify_jwt,
generate_nonce,
)
from __future__ import annotations
import hashlib
import os
import secrets
from datetime import datetime, timedelta
from typing import Any
from jose import JWTError, jwt
JWT_SECRET = os.getenv("JWT_SECRET", "rmi-jwt-secret-change-me")
JWT_ALGORITHM = "HS256"
JWT_EXPIRY_DAYS = 7
def _create_jwt(
user_id: str,
email: str,
tier: str = "FREE",
role: str = "USER",
wallet: str | None = None,
) -> str:
now = datetime.utcnow()
payload = {
"sub": user_id,
"email": email,
"tier": tier,
"role": role,
"iat": now,
"exp": now + timedelta(days=JWT_EXPIRY_DAYS),
}
if wallet:
payload["wallet"] = wallet
return jwt.encode(payload, JWT_SECRET, algorithm=JWT_ALGORITHM)
def _verify_jwt(token: str) -> dict[str, Any] | None:
try:
payload = jwt.decode(token, JWT_SECRET, algorithms=[JWT_ALGORITHM])
return {
"id": payload["sub"],
"email": payload["email"],
"tier": payload.get("tier", "FREE"),
"role": payload.get("role", "USER"),
"wallet": payload.get("wallet"),
}
except JWTError:
return None
def _derive_user_id(email: str) -> str:
return hashlib.sha256(email.lower().encode()).hexdigest()[:32]
def generate_nonce() -> str:
return secrets.token_urlsafe(16)

View file

@ -118,7 +118,7 @@ async def get_or_create_wallet_user(address: str, chain: str = "base") -> dict[s
r.hset("rmi:users", user_id, json.dumps(user))
# Generate JWT token
from app.auth import _create_jwt
from app.domains.auth.jwt import _create_jwt
# Use email if available, otherwise derive from address
email = user.get("email") or f"{address.lower()}@wallet.rmi"
@ -145,7 +145,7 @@ async def verify_auth_token(token: str) -> dict[str, Any] | None:
Returns:
Dict with user info if valid, None if invalid/expired
"""
from app.auth import _verify_jwt
from app.domains.auth.jwt import _verify_jwt
try:
user = _verify_jwt(token)