All tweaks added
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from fastapi.security import OAuth2PasswordRequestForm
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
import httpx
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.core.security import verify_password, create_access_token, get_current_user
|
||||
from app.core.config import settings
|
||||
from app.models.user import User
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
async def _get_pocketid_config(db: AsyncSession):
|
||||
"""Get PocketID config from DB (admin user) falling back to env vars."""
|
||||
result = await db.execute(select(User).where(User.is_admin == True).limit(1))
|
||||
admin = result.scalar_one_or_none()
|
||||
issuer = (admin and admin.pocketid_issuer) or settings.pocketid_issuer
|
||||
client_id = (admin and admin.pocketid_client_id) or settings.pocketid_client_id
|
||||
client_secret = (admin and admin.pocketid_client_secret) or settings.pocketid_client_secret
|
||||
return issuer, client_id, client_secret
|
||||
|
||||
|
||||
class Token(BaseModel):
|
||||
access_token: str
|
||||
token_type: str
|
||||
user_id: int
|
||||
username: str
|
||||
is_admin: bool
|
||||
|
||||
|
||||
class UserOut(BaseModel):
|
||||
id: int
|
||||
username: str
|
||||
email: Optional[str]
|
||||
is_admin: bool
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
@router.post("/token", response_model=Token)
|
||||
async def login(
|
||||
form_data: OAuth2PasswordRequestForm = Depends(),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
result = await db.execute(select(User).where(User.username == form_data.username))
|
||||
user = result.scalar_one_or_none()
|
||||
if not user or not user.hashed_password:
|
||||
raise HTTPException(status_code=400, detail="Invalid credentials")
|
||||
if not verify_password(form_data.password, user.hashed_password):
|
||||
raise HTTPException(status_code=400, detail="Invalid credentials")
|
||||
token = create_access_token({"sub": str(user.id)})
|
||||
return Token(access_token=token, token_type="bearer",
|
||||
user_id=user.id, username=user.username, is_admin=user.is_admin)
|
||||
|
||||
|
||||
@router.get("/me", response_model=UserOut)
|
||||
async def get_me(current_user: User = Depends(get_current_user)):
|
||||
return current_user
|
||||
|
||||
|
||||
@router.get("/pocketid/available")
|
||||
async def pocketid_available(db: AsyncSession = Depends(get_db)):
|
||||
issuer, client_id, _ = await _get_pocketid_config(db)
|
||||
return {"available": bool(issuer and client_id)}
|
||||
|
||||
|
||||
@router.get("/pocketid/login-url")
|
||||
async def pocketid_login_url(db: AsyncSession = Depends(get_db)):
|
||||
issuer, client_id, _ = await _get_pocketid_config(db)
|
||||
if not issuer or not client_id:
|
||||
raise HTTPException(status_code=404, detail="PocketID not configured")
|
||||
from urllib.parse import urlencode
|
||||
params = {
|
||||
"client_id": client_id,
|
||||
"redirect_uri": "/api/auth/pocketid/callback",
|
||||
"response_type": "code",
|
||||
"scope": "openid profile email",
|
||||
}
|
||||
return {"url": f"{issuer}/authorize?{urlencode(params)}"}
|
||||
|
||||
|
||||
@router.get("/pocketid/callback")
|
||||
async def pocketid_callback(code: str, db: AsyncSession = Depends(get_db)):
|
||||
issuer, client_id, client_secret = await _get_pocketid_config(db)
|
||||
if not issuer:
|
||||
raise HTTPException(status_code=404, detail="PocketID not configured")
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
resp = await client.post(
|
||||
f"{issuer}/token",
|
||||
data={"grant_type": "authorization_code", "code": code,
|
||||
"redirect_uri": "/api/auth/pocketid/callback",
|
||||
"client_id": client_id, "client_secret": client_secret},
|
||||
)
|
||||
if resp.status_code != 200:
|
||||
raise HTTPException(status_code=400, detail="Token exchange failed")
|
||||
tokens = resp.json()
|
||||
userinfo_resp = await client.get(
|
||||
f"{issuer}/userinfo",
|
||||
headers={"Authorization": f"Bearer {tokens['access_token']}"},
|
||||
)
|
||||
userinfo = userinfo_resp.json()
|
||||
|
||||
sub = userinfo.get("sub")
|
||||
email = userinfo.get("email")
|
||||
preferred_username = userinfo.get("preferred_username") or email
|
||||
|
||||
result = await db.execute(select(User).where(User.pocketid_sub == sub))
|
||||
user = result.scalar_one_or_none()
|
||||
if not user:
|
||||
user = User(username=preferred_username, email=email, pocketid_sub=sub)
|
||||
db.add(user)
|
||||
await db.flush()
|
||||
|
||||
token = create_access_token({"sub": str(user.id)})
|
||||
from fastapi.responses import RedirectResponse
|
||||
return RedirectResponse(url=f"/?token={token}")
|
||||
Reference in New Issue
Block a user