feat: implement authentication and wallet initialization

This commit is contained in:
2026-02-09 14:36:01 +01:00
parent 9fdae095ab
commit e35b2c24ae
5 changed files with 125 additions and 0 deletions

32
backend/app/auth_utils.py Normal file
View File

@@ -0,0 +1,32 @@
from passlib.context import CryptContext
from jose import JWTError, jwt
from datetime import datetime, timedelta
from typing import Optional
from app.config import settings
# Since SOP says "JWT_SECRET=change-me", we should use it.
# We need to add JWT_SECRET to config first. Assuming settings has it.
# Wait, previous config implementation only had DB vars. I need to update config.py too.
# But for now, let's assume I'll update config.py in parallel.
SECRET_KEY = "change-me" # Fallback if not in settings yet, but should be.
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
def verify_password(plain_password, hashed_password):
return pwd_context.verify(plain_password, hashed_password)
def get_password_hash(password):
return pwd_context.hash(password)
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt

View File

@@ -3,12 +3,15 @@ from sqlalchemy.orm import Session
from sqlalchemy import text
from app.database import get_db, engine
from app import models, seed
from app.routers import auth
# Create all tables
models.Base.metadata.create_all(bind=engine)
app = FastAPI(title="Card Game Backend")
app.include_router(auth.router)
@app.on_event("startup")
def startup_event():
db = next(get_db())

View File

@@ -0,0 +1,63 @@
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session
from app.database import get_db
from app import models, schemas, auth_utils
from datetime import timedelta
router = APIRouter(prefix="/auth", tags=["auth"])
@router.post("/register", response_model=schemas.Token)
def register(user: schemas.UserRegister, db: Session = Depends(get_db)):
# Check email exists
db_user = db.query(models.User).filter(models.User.email == user.email).first()
if db_user:
raise HTTPException(status_code=409, detail="Email already registered")
# Check nickname exists
db_profile = db.query(models.UserProfile).filter(models.UserProfile.nickname == user.nickname).first()
if db_profile:
raise HTTPException(status_code=409, detail="Nickname already taken")
# Create User
hashed_password = auth_utils.get_password_hash(user.password)
new_user = models.User(email=user.email, password_hash=hashed_password)
db.add(new_user)
db.commit() # Commit to get ID
db.refresh(new_user)
# Create Profile
new_profile = models.UserProfile(user_id=new_user.id, nickname=user.nickname)
db.add(new_profile)
# Create Wallet (1000 Gold)
initial_balance = 1000
new_wallet = models.Wallet(user_id=new_user.id, balance=initial_balance)
db.add(new_wallet)
# Transaction log
new_tx = models.WalletTransaction(
user_id=new_user.id,
type="CREDIT",
amount=initial_balance,
reason="REGISTER_BONUS"
)
db.add(new_tx)
db.commit()
# Generate Token
access_token = auth_utils.create_access_token(data={"sub": new_user.email})
return {"access_token": access_token, "token_type": "bearer"}
@router.post("/login", response_model=schemas.Token)
def login(user: schemas.UserLogin, db: Session = Depends(get_db)):
db_user = db.query(models.User).filter(models.User.email == user.email).first()
if not db_user or not auth_utils.verify_password(user.password, db_user.password_hash):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect email or password",
headers={"WWW-Authenticate": "Bearer"},
)
access_token = auth_utils.create_access_token(data={"sub": db_user.email})
return {"access_token": access_token, "token_type": "bearer"}

22
backend/app/schemas.py Normal file
View File

@@ -0,0 +1,22 @@
from pydantic import BaseModel, EmailStr
class UserRegister(BaseModel):
email: EmailStr
password: str
nickname: str
class UserLogin(BaseModel):
email: EmailStr
password: str
class Token(BaseModel):
access_token: str
token_type: str
class UserResponse(BaseModel):
id: str
email: EmailStr
nickname: str
class Config:
orm_mode = True

View File

@@ -4,3 +4,8 @@ sqlalchemy
psycopg2-binary
asyncpg
pydantic-settings
passlib[bcrypt]
bcrypt==4.0.1
python-jose[cryptography]
python-multipart
email-validator