appsmith-api / api /auth.py
Kakashiix26's picture
auth role re-export from auth.roles
8bd4797 verified
Raw
History Blame Contribute Delete
5.39 kB
"""
Auth endpoints — signup, login, Google SSO, me.
Mirrored from NourishBot/routes/auth.py; adapted to CodyBuddy conventions.
"""
import os
from fastapi import APIRouter, Depends, HTTPException, status
from pydantic import BaseModel, EmailStr
from sqlalchemy.orm import Session
from auth.dependencies import get_current_user
from auth.roles import SUPER_ADMIN_EMAILS, ensure_role as _ensure_role, resolve_role as _resolve_role
from auth.utils import create_access_token, hash_password, verify_password
from db.database import get_db
from db.models import User
router = APIRouter(prefix="/auth", tags=["auth"])
# Role logic (allowlist) now lives in auth.roles so get_current_user can apply it
# on every authenticated request — re-exported here for the auth endpoints.
__all__ = ["router", "SUPER_ADMIN_EMAILS"]
class SignupRequest(BaseModel):
email: EmailStr
password: str
name: str | None = None
class LoginRequest(BaseModel):
email: EmailStr
password: str
class GoogleAuthRequest(BaseModel):
id_token: str
class GitHubStatus(BaseModel):
connected: bool = False
login: str | None = None
def _github_status(user: User) -> GitHubStatus:
"""Derive GitHub connection status defensively. NEVER returns the token.
ponytail: guard against MagicMock/non-str attrs (test fixtures) — only a real,
non-empty encrypted string counts as connected.
"""
enc = getattr(user, "github_token_enc", None)
login = getattr(user, "github_login", None)
connected = isinstance(enc, str) and bool(enc)
return GitHubStatus(connected=connected, login=login if (connected and isinstance(login, str)) else None)
class UserResponse(BaseModel):
id: str
email: str
name: str | None
role: str = "user"
github: GitHubStatus = GitHubStatus()
model_config = {"from_attributes": True}
class AuthResponse(BaseModel):
token: str
user: UserResponse
@router.post("/signup", response_model=AuthResponse, status_code=status.HTTP_201_CREATED)
def signup(payload: SignupRequest, db: Session = Depends(get_db)) -> AuthResponse:
if len(payload.password) < 8:
raise HTTPException(status_code=400, detail="Password must be at least 8 characters")
if db.query(User).filter(User.email == payload.email).first():
raise HTTPException(status_code=409, detail="Email already registered")
user = User(
email=payload.email,
name=payload.name or payload.email.split("@")[0],
hashed_password=hash_password(payload.password),
)
db.add(user)
db.commit()
db.refresh(user)
role = _ensure_role(user, db)
token = create_access_token(str(user.id), user.email)
return AuthResponse(token=token, user=UserResponse(id=str(user.id), email=user.email, name=user.name, role=role, github=_github_status(user)))
@router.post("/login", response_model=AuthResponse)
def login(payload: LoginRequest, db: Session = Depends(get_db)) -> AuthResponse:
user = db.query(User).filter(User.email == payload.email).first()
if not user or not user.hashed_password:
raise HTTPException(status_code=401, detail="Invalid credentials")
if not verify_password(payload.password, user.hashed_password):
raise HTTPException(status_code=401, detail="Invalid credentials")
role = _ensure_role(user, db)
token = create_access_token(str(user.id), user.email)
return AuthResponse(token=token, user=UserResponse(id=str(user.id), email=user.email, name=user.name, role=role, github=_github_status(user)))
@router.post("/google", response_model=AuthResponse)
def google_auth(payload: GoogleAuthRequest, db: Session = Depends(get_db)) -> AuthResponse:
google_client_id = os.environ.get("GOOGLE_CLIENT_ID", "")
if not google_client_id:
raise HTTPException(status_code=503, detail="Google sign-in not configured")
from google.oauth2 import id_token as google_id_token
from google.auth.transport import requests as google_requests
try:
idinfo = google_id_token.verify_oauth2_token(
payload.id_token,
google_requests.Request(),
google_client_id,
)
except ValueError as exc:
raise HTTPException(status_code=401, detail=f"Invalid Google token: {exc}")
google_id = idinfo["sub"]
email = idinfo["email"]
name = idinfo.get("name", email.split("@")[0])
# ponytail: upsert by google_id first, then email, then create
user = db.query(User).filter(User.google_id == google_id).first()
if not user:
user = db.query(User).filter(User.email == email).first()
if user:
user.google_id = google_id
else:
user = User(email=email, name=name, google_id=google_id)
db.add(user)
db.commit()
db.refresh(user)
role = _ensure_role(user, db)
token = create_access_token(str(user.id), user.email)
return AuthResponse(token=token, user=UserResponse(id=str(user.id), email=user.email, name=user.name, role=role, github=_github_status(user)))
@router.get("/me", response_model=UserResponse)
def me(current_user: User = Depends(get_current_user)) -> UserResponse:
return UserResponse(
id=str(current_user.id), email=current_user.email,
name=current_user.name, role=_resolve_role(current_user.email),
github=_github_status(current_user),
)