Push V1 app
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import secrets as secrets_mod
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from auth import (
|
||||
create_access_token,
|
||||
get_current_user,
|
||||
hash_password,
|
||||
require_admin,
|
||||
verify_password,
|
||||
)
|
||||
from database import get_db
|
||||
from models import Invitation, User
|
||||
from ratelimit import change_password_limiter, login_limiter, rate_limit, register_limiter
|
||||
from schemas import (
|
||||
InvitationCreate,
|
||||
InvitationResponse,
|
||||
PasswordChange,
|
||||
TokenResponse,
|
||||
UserCreate,
|
||||
UserLogin,
|
||||
UserResponse,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/api/auth", tags=["auth"])
|
||||
|
||||
INVITATION_EXPIRY_DAYS = 7
|
||||
|
||||
|
||||
@router.post("/register", response_model=TokenResponse)
|
||||
@rate_limit(register_limiter)
|
||||
def register(request: Request, data: UserCreate, db: Session = Depends(get_db)):
|
||||
invite = (
|
||||
db.query(Invitation)
|
||||
.filter(
|
||||
Invitation.token == data.invite_token,
|
||||
Invitation.used_by.is_(None),
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if not invite:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Invitation invalide ou déjà utilisée",
|
||||
)
|
||||
|
||||
if invite.expires_at and invite.expires_at < datetime.utcnow():
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Invitation expirée",
|
||||
)
|
||||
|
||||
if db.query(User).filter(
|
||||
(User.username == data.username) | (User.email == data.email)
|
||||
).first():
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Un compte avec ces informations existe déjà",
|
||||
)
|
||||
|
||||
user = User(
|
||||
username=data.username,
|
||||
email=data.email,
|
||||
hashed_password=hash_password(data.password),
|
||||
)
|
||||
db.add(user)
|
||||
db.flush()
|
||||
|
||||
invite.used_by = user.id
|
||||
invite.used_at = datetime.utcnow()
|
||||
db.flush()
|
||||
|
||||
db.commit()
|
||||
db.refresh(user)
|
||||
|
||||
token = create_access_token({"sub": str(user.id), "token_version": user.token_version})
|
||||
return TokenResponse(
|
||||
access_token=token,
|
||||
user=UserResponse.model_validate(user),
|
||||
)
|
||||
|
||||
|
||||
@router.post("/login", response_model=TokenResponse)
|
||||
@rate_limit(login_limiter)
|
||||
def login(request: Request, data: UserLogin, db: Session = Depends(get_db)):
|
||||
user = db.query(User).filter(User.username == data.username).first()
|
||||
if not user or not verify_password(data.password, user.hashed_password):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Identifiants incorrects",
|
||||
)
|
||||
|
||||
token = create_access_token({"sub": str(user.id), "token_version": user.token_version})
|
||||
return TokenResponse(
|
||||
access_token=token,
|
||||
user=UserResponse.model_validate(user),
|
||||
)
|
||||
|
||||
|
||||
@router.get("/me", response_model=UserResponse)
|
||||
def me(user: User = Depends(get_current_user)):
|
||||
return UserResponse.model_validate(user)
|
||||
|
||||
|
||||
@router.post("/change-password")
|
||||
@rate_limit(change_password_limiter)
|
||||
def change_password(
|
||||
request: Request,
|
||||
data: PasswordChange,
|
||||
user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
if not verify_password(data.current_password, user.hashed_password):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Mot de passe actuel incorrect",
|
||||
)
|
||||
user.hashed_password = hash_password(data.new_password)
|
||||
user.token_version += 1
|
||||
db.commit()
|
||||
return {"detail": "Mot de passe modifié"}
|
||||
|
||||
|
||||
@router.post("/logout")
|
||||
def logout(user: User = Depends(get_current_user), db: Session = Depends(get_db)):
|
||||
user.token_version += 1
|
||||
db.commit()
|
||||
return {"detail": "Déconnexion réussie"}
|
||||
|
||||
|
||||
@router.post("/invitations", response_model=InvitationResponse)
|
||||
def create_invitation(
|
||||
admin: User = Depends(require_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
invitation = Invitation(
|
||||
token=secrets_mod.token_urlsafe(32),
|
||||
created_by=admin.id,
|
||||
expires_at=datetime.utcnow() + timedelta(days=INVITATION_EXPIRY_DAYS),
|
||||
)
|
||||
db.add(invitation)
|
||||
db.commit()
|
||||
db.refresh(invitation)
|
||||
return InvitationResponse.model_validate(invitation)
|
||||
|
||||
|
||||
@router.get("/invitations", response_model=list[InvitationResponse])
|
||||
def list_invitations(
|
||||
admin: User = Depends(require_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
invitations = (
|
||||
db.query(Invitation)
|
||||
.filter(Invitation.created_by == admin.id)
|
||||
.order_by(Invitation.created_at.desc())
|
||||
.all()
|
||||
)
|
||||
return [InvitationResponse.model_validate(i) for i in invitations]
|
||||
Reference in New Issue
Block a user