auth: add JWT email/password auth + Login UI; security: kms rotate helper; preview sync endpoint + UI
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
import os
|
||||
import time
|
||||
from fastapi import APIRouter, HTTPException, Depends, Request
|
||||
from fastapi.responses import JSONResponse
|
||||
from passlib.hash import bcrypt
|
||||
import jwt
|
||||
from . import models
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
JWT_SECRET = os.getenv('LIFERPG_JWT_SECRET', 'dev_jwt_secret_change')
|
||||
JWT_ALGO = 'HS256'
|
||||
JWT_EXP_SECONDS = 60 * 60 * 24 # 1 day
|
||||
|
||||
|
||||
def create_token(payload: dict) -> str:
|
||||
now = int(time.time())
|
||||
payload_out = {**payload, 'iat': now, 'exp': now + JWT_EXP_SECONDS}
|
||||
return jwt.encode(payload_out, JWT_SECRET, algorithm=JWT_ALGO)
|
||||
|
||||
|
||||
def decode_token(token: str) -> dict:
|
||||
try:
|
||||
return jwt.decode(token, JWT_SECRET, algorithms=[JWT_ALGO])
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
|
||||
@router.post('/signup')
|
||||
def signup(payload: dict):
|
||||
email = payload.get('email')
|
||||
password = payload.get('password')
|
||||
if not email or not password:
|
||||
raise HTTPException(status_code=400, detail='email and password required')
|
||||
db = models.SessionLocal()
|
||||
try:
|
||||
existing = db.query(models.User).filter_by(email=email).first()
|
||||
if existing:
|
||||
raise HTTPException(status_code=400, detail='email exists')
|
||||
user = models.User(email=email, password_hash=bcrypt.hash(password), display_name=payload.get('display_name'))
|
||||
db.add(user)
|
||||
db.commit()
|
||||
db.refresh(user)
|
||||
token = create_token({'sub': user.id})
|
||||
resp = JSONResponse({'id': user.id, 'email': user.email})
|
||||
resp.set_cookie('session', token, httponly=True, secure=False, samesite='lax')
|
||||
return resp
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@router.post('/login')
|
||||
def login(payload: dict):
|
||||
email = payload.get('email')
|
||||
password = payload.get('password')
|
||||
if not email or not password:
|
||||
raise HTTPException(status_code=400, detail='email and password required')
|
||||
db = models.SessionLocal()
|
||||
try:
|
||||
user = db.query(models.User).filter_by(email=email).first()
|
||||
if not user or not user.password_hash or not bcrypt.verify(password, user.password_hash):
|
||||
raise HTTPException(status_code=401, detail='invalid credentials')
|
||||
token = create_token({'sub': user.id})
|
||||
resp = JSONResponse({'id': user.id, 'email': user.email})
|
||||
resp.set_cookie('session', token, httponly=True, secure=False, samesite='lax')
|
||||
return resp
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@router.post('/logout')
|
||||
def logout():
|
||||
resp = JSONResponse({'ok': True})
|
||||
resp.delete_cookie('session')
|
||||
return resp
|
||||
|
||||
|
||||
def get_current_user(request: Request):
|
||||
token = request.cookies.get('session')
|
||||
if not token:
|
||||
raise HTTPException(status_code=401, detail='not authenticated')
|
||||
data = decode_token(token)
|
||||
uid = data.get('sub')
|
||||
if not uid:
|
||||
raise HTTPException(status_code=401, detail='invalid token')
|
||||
db = models.SessionLocal()
|
||||
try:
|
||||
user = db.query(models.User).filter_by(id=uid).first()
|
||||
if not user:
|
||||
raise HTTPException(status_code=401, detail='user not found')
|
||||
return user
|
||||
finally:
|
||||
db.close()
|
||||
Reference in New Issue
Block a user