Coverage for app/api/routes/login.py: 83%

53 statements  

« prev     ^ index     » next       coverage.py v7.16.1, created at 2026-10-26 10:48 +0000

1from datetime import timedelta 

2from typing import Annotated, Any 

3 

4from fastapi import APIRouter, Depends, HTTPException 

5from fastapi.responses import HTMLResponse 

6from fastapi.security import OAuth2PasswordRequestForm 

7 

8from app import crud 

9from app.api.deps import CurrentUser, SessionDep, get_current_active_superuser 

10from app.core import security 

11from app.core.config import settings 

12from app.models import Message, NewPassword, Token, UserPublic, UserUpdate 

13from app.utils import ( 

14 generate_password_reset_token, 

15 generate_reset_password_email, 

16 send_email, 

17 verify_password_reset_token, 

18) 

19 

20router = APIRouter(tags=["login"]) 

21 

22 

23@router.post("/login/access-token") 

24def login_access_token( 

25 session: SessionDep, form_data: Annotated[OAuth2PasswordRequestForm, Depends()] 

26) -> Token: 

27 """ 

28 OAuth2 compatible token login, get an access token for future requests 

29 """ 

30 user = crud.authenticate( 1acidebfg

31 session=session, email=form_data.username, password=form_data.password 

32 ) 

33 if not user: 1acidebfg

34 raise HTTPException(status_code=400, detail="Incorrect email or password") 1i

35 elif not user.is_active: 1acdebfg

36 raise HTTPException(status_code=400, detail="Inactive user") 

37 access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) 1acdebfg

38 return Token( 1acdebfg

39 access_token=security.create_access_token( 

40 user.id, expires_delta=access_token_expires 

41 ) 

42 ) 

43 

44 

45@router.post("/login/test-token", response_model=UserPublic) 

46def test_token(current_user: CurrentUser) -> Any: 

47 """ 

48 Test access token 

49 """ 

50 return current_user 1l

51 

52 

53@router.post("/password-recovery/{email}") 

54def recover_password(email: str, session: SessionDep) -> Message: 

55 """ 

56 Password Recovery 

57 """ 

58 user = crud.get_user_by_email(session=session, email=email) 1hj

59 

60 # Always return the same response to prevent email enumeration attacks 

61 # Only send email if user actually exists 

62 if user: 1hj

63 password_reset_token = generate_password_reset_token(email=email) 1h

64 email_data = generate_reset_password_email( 1h

65 email_to=user.email, email=email, token=password_reset_token 

66 ) 

67 send_email( 

68 email_to=user.email, 

69 subject=email_data.subject, 

70 html_content=email_data.html_content, 

71 ) 

72 return Message( 1aj

73 message="If that email is registered, we sent a password recovery link" 

74 ) 

75 

76 

77@router.post("/reset-password/") 

78def reset_password(session: SessionDep, body: NewPassword) -> Message: 

79 """ 

80 Reset password 

81 """ 

82 email = verify_password_reset_token(token=body.token) 1bk

83 if not email: 1bk

84 raise HTTPException(status_code=400, detail="Invalid token") 1k

85 user = crud.get_user_by_email(session=session, email=email) 1b

86 if not user: 1b

87 # Don't reveal that the user doesn't exist - use same error as invalid token 

88 raise HTTPException(status_code=400, detail="Invalid token") 

89 elif not user.is_active: 1b

90 raise HTTPException(status_code=400, detail="Inactive user") 

91 user_in_update = UserUpdate(password=body.new_password) 1b

92 crud.update_user( 1b

93 session=session, 

94 db_user=user, 

95 user_in=user_in_update, 

96 ) 

97 return Message(message="Password updated successfully") 1b

98 

99 

100@router.post( 

101 "/password-recovery-html-content/{email}", 

102 dependencies=[Depends(get_current_active_superuser)], 

103 response_class=HTMLResponse, 

104) 

105def recover_password_html_content(email: str, session: SessionDep) -> Any: 

106 """ 

107 HTML Content for Password Recovery 

108 """ 

109 user = crud.get_user_by_email(session=session, email=email) 

110 

111 if not user: 

112 raise HTTPException( 

113 status_code=404, 

114 detail="The user with this username does not exist in the system.", 

115 ) 

116 password_reset_token = generate_password_reset_token(email=email) 

117 email_data = generate_reset_password_email( 

118 email_to=user.email, email=email, token=password_reset_token 

119 ) 

120 

121 return HTMLResponse( 

122 content=email_data.html_content, headers={"subject:": email_data.subject} 

123 )