feat(auth): add TOTP 2FA setup and login verification
Some checks failed
CI / test (push) Failing after 21s

- add user twofa fields and migration

- add 2FA setup/enable/disable endpoints

- enforce OTP on login when 2FA enabled

- add web login OTP field and settings UI
This commit is contained in:
2026-03-08 11:43:51 +03:00
parent e685a38be6
commit 27d3340a37
12 changed files with 287 additions and 7 deletions

View File

@@ -14,9 +14,13 @@ from app.auth.schemas import (
ResetPasswordRequest,
TokenResponse,
SessionRead,
TwoFactorCodeRequest,
TwoFactorSetupRead,
VerifyEmailRequest,
)
from app.auth.service import (
disable_twofa,
enable_twofa,
get_current_user,
get_email_sender,
get_request_metadata,
@@ -29,6 +33,7 @@ from app.auth.service import (
request_password_reset,
resend_verification_email,
reset_password,
setup_twofa,
verify_email,
)
from app.database.session import get_db
@@ -155,3 +160,32 @@ async def revoke_session(jti: str, current_user: User = Depends(get_current_user
@router.delete("/sessions", status_code=status.HTTP_204_NO_CONTENT)
async def revoke_all_sessions(current_user: User = Depends(get_current_user)) -> None:
await revoke_all_user_sessions(user_id=current_user.id)
@router.post("/2fa/setup", response_model=TwoFactorSetupRead)
async def setup_2fa(
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
) -> TwoFactorSetupRead:
secret, otpauth_url = await setup_twofa(db, current_user)
return TwoFactorSetupRead(secret=secret, otpauth_url=otpauth_url)
@router.post("/2fa/enable", response_model=MessageResponse)
async def enable_2fa(
payload: TwoFactorCodeRequest,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
) -> MessageResponse:
await enable_twofa(db, current_user, code=payload.code)
return MessageResponse(message="2FA enabled")
@router.post("/2fa/disable", response_model=MessageResponse)
async def disable_2fa(
payload: TwoFactorCodeRequest,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
) -> MessageResponse:
await disable_twofa(db, current_user, code=payload.code)
return MessageResponse(message="2FA disabled")