refactor(auth): move JWT helpers to app.domains.auth.jwt, break auth circular import (P4.3 cont)
This commit is contained in:
parent
7bd204f9f5
commit
59d6f2f0f6
3 changed files with 71 additions and 58 deletions
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue