165 lines
4.5 KiB
Python
165 lines
4.5 KiB
Python
"""
|
|
Wallet Authentication Helpers
|
|
=============================
|
|
Wallet signature verification and user creation.
|
|
"""
|
|
|
|
import logging
|
|
import os
|
|
from datetime import datetime
|
|
from typing import Any
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def verify_wallet_signature(message: str, signature: str, address: str) -> bool:
|
|
"""
|
|
Verify a wallet signature.
|
|
|
|
NOTE: This is a validation stub. In production, use a proper signing library
|
|
(e.g., web3.py for Ethereum, @solana/web3.js for Solana) to verify signatures.
|
|
|
|
For now, returns True if all fields are non-empty (basic validation).
|
|
"""
|
|
if not message or not signature or not address:
|
|
return False
|
|
|
|
# Ensure address format looks valid (basic check)
|
|
if len(address) < 20:
|
|
return False
|
|
|
|
# Basic signature length check
|
|
if len(signature) < 60: # Typical sig is 65 hex chars for EVM
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
def decode_signature(signature: str) -> tuple:
|
|
"""
|
|
Decode a wallet signature into r, s, v components.
|
|
|
|
Returns (r_hex, s_hex, v_int) for signature verification.
|
|
"""
|
|
import binascii
|
|
|
|
sig_bytes = binascii.unhexlify(signature.replace("0x", ""))
|
|
|
|
r = sig_bytes[:32].hex()
|
|
s = sig_bytes[32:64].hex()
|
|
v = sig_bytes[64]
|
|
|
|
return r, s, v
|
|
|
|
|
|
async def get_or_create_wallet_user(address: str, chain: str = "base") -> dict[str, Any]:
|
|
"""
|
|
Get or create a user based on wallet address.
|
|
|
|
Returns dict with:
|
|
- access_token
|
|
- refresh_token
|
|
- id
|
|
- email
|
|
- display_name
|
|
- tier
|
|
- role
|
|
- created_at
|
|
"""
|
|
import hashlib
|
|
import json
|
|
|
|
# Derive user_id from wallet address
|
|
user_id = hashlib.sha256(address.lower().encode()).hexdigest()[:32]
|
|
|
|
# Try to load existing user
|
|
r = None
|
|
try:
|
|
import redis
|
|
|
|
r = redis.Redis(
|
|
host=os.getenv("REDIS_HOST", "localhost"),
|
|
port=int(os.getenv("REDIS_PORT", "6379")),
|
|
password=os.getenv("REDIS_PASSWORD", ""),
|
|
decode_responses=True,
|
|
)
|
|
except Exception as e:
|
|
logger.warning(f"Redis not available for wallet user lookup: {e}")
|
|
|
|
user = None
|
|
if r:
|
|
data = r.hget("rmi:wallet_users", address.lower())
|
|
if data:
|
|
user = json.loads(data)
|
|
|
|
# Create new user if doesn't exist
|
|
if not user:
|
|
# Generate fake email for wallet users (no email required for wallet auth)
|
|
email = f"{address.lower()}@wallet.rmi"
|
|
display_name = f"Wallet User {address[2:8].upper()}"
|
|
|
|
user = {
|
|
"id": user_id,
|
|
"address": address.lower(),
|
|
"email": email,
|
|
"display_name": display_name,
|
|
"chain": chain,
|
|
"tier": "FREE",
|
|
"role": "USER",
|
|
"created_at": datetime.utcnow().isoformat(),
|
|
"xp": 0,
|
|
"level": 1,
|
|
"badges": [],
|
|
"scans_remaining": 5,
|
|
"scans_used": 0,
|
|
}
|
|
|
|
if r:
|
|
r.hset("rmi:wallet_users", address.lower(), json.dumps(user))
|
|
# Also store in main users hash
|
|
r.hset("rmi:users", user_id, json.dumps(user))
|
|
|
|
# Generate JWT token
|
|
from app.auth import _create_jwt
|
|
|
|
# Use email if available, otherwise derive from address
|
|
email = user.get("email") or f"{address.lower()}@wallet.rmi"
|
|
token = _create_jwt(user_id, email, user.get("tier", "FREE"), user.get("role", "USER"), address)
|
|
|
|
return {
|
|
"access_token": token,
|
|
"refresh_token": token,
|
|
"id": user_id,
|
|
"email": email,
|
|
"display_name": user.get("display_name", display_name),
|
|
"tier": user.get("tier", "FREE"),
|
|
"role": user.get("role", "USER"),
|
|
"created_at": user.get("created_at"),
|
|
"address": address,
|
|
"chain": chain,
|
|
}
|
|
|
|
|
|
async def verify_auth_token(token: str) -> dict[str, Any] | None:
|
|
"""
|
|
Verify a JWT token and return user info.
|
|
|
|
Returns:
|
|
Dict with user info if valid, None if invalid/expired
|
|
"""
|
|
from app.auth import _verify_jwt
|
|
|
|
try:
|
|
user = _verify_jwt(token)
|
|
if user:
|
|
# Return user info in expected format
|
|
return {
|
|
"id": user.get("user_id"),
|
|
"email": user.get("email"),
|
|
"address": user.get("wallet_address"),
|
|
"tier": user.get("tier", "FREE"),
|
|
"role": user.get("role", "USER"),
|
|
}
|
|
except Exception as e:
|
|
logger.debug(f"Token verification failed: {e}")
|
|
return None
|