Coverage for src/kwai/modules/identity/authenticate_user.py: 97%

32 statements  

« prev     ^ index     » next       coverage.py v7.6.10, created at 2024-01-01 00:00 +0000

1"""Module that implements the use case: authenticate user.""" 

2 

3from dataclasses import dataclass 

4 

5from kwai.core.domain.value_objects.email_address import EmailAddress 

6from kwai.core.domain.value_objects.timestamp import Timestamp 

7from kwai.modules.identity.exceptions import AuthenticationException 

8from kwai.modules.identity.tokens.access_token import AccessTokenEntity 

9from kwai.modules.identity.tokens.access_token_repository import AccessTokenRepository 

10from kwai.modules.identity.tokens.refresh_token import RefreshTokenEntity 

11from kwai.modules.identity.tokens.refresh_token_repository import RefreshTokenRepository 

12from kwai.modules.identity.tokens.token_identifier import TokenIdentifier 

13from kwai.modules.identity.users.user_account_repository import UserAccountRepository 

14 

15 

16@dataclass(kw_only=True, frozen=True) 

17class AuthenticateUserCommand: 

18 """Input for the (AuthenticateUser) use case. 

19 

20 Attributes: 

21 username: The email address of the user. 

22 password: The password of the user. 

23 access_token_expiry_minutes: Minutes before expiring the access token. 

24 Default is 2 hours. 

25 refresh_token_expiry_minutes: Minutes before expiring the refresh token. 

26 Default is 2 months. 

27 """ 

28 

29 username: str 

30 password: str 

31 access_token_expiry_minutes: int = 60 * 2 # 2 hours 

32 refresh_token_expiry_minutes: int = 60 * 24 * 60 # 2 months 

33 

34 

35class AuthenticateUser: 

36 """Use case to authenticate a user. 

37 

38 Attributes: 

39 _user_account_repo (UserAccountRepository): The repository for getting the 

40 user account. 

41 _access_token_repo (UserAccountRepository): The repository for creating the 

42 access token. 

43 _refresh_token_repo (UserAccountRepository): The repository for creating the 

44 refresh token. 

45 """ 

46 

47 def __init__( 

48 self, 

49 user_account_repo: UserAccountRepository, 

50 access_token_repo: AccessTokenRepository, 

51 refresh_token_repo: RefreshTokenRepository, 

52 ): 

53 self._user_account_repo = user_account_repo 

54 self._access_token_repo = access_token_repo 

55 self._refresh_token_repo = refresh_token_repo 

56 

57 async def execute(self, command: AuthenticateUserCommand) -> RefreshTokenEntity: 

58 """Execute the use case. 

59 

60 Args: 

61 command: The input for this use case. 

62 

63 Returns: 

64 RefreshTokenEntity: On success, a refresh token entity will be returned. 

65 

66 Raises: 

67 InvalidEmailException: Raised when the username 

68 contains an invalid email address. 

69 UserAccountNotFoundException: Raised when the user with the given email 

70 address doesn't exist. 

71 """ 

72 user_account = await self._user_account_repo.get_user_by_email( 

73 EmailAddress(command.username) 

74 ) 

75 if user_account.revoked: 

76 raise AuthenticationException("User account is revoked") 

77 

78 user_account = user_account.login(command.password) 

79 if not user_account.logged_in: 

80 # save the last unsuccessful login 

81 await self._user_account_repo.update(user_account) 

82 raise AuthenticationException("Invalid password") 

83 

84 # save the last successful login 

85 await self._user_account_repo.update(user_account) 

86 

87 access_token = await self._access_token_repo.create( 

88 AccessTokenEntity( 

89 identifier=TokenIdentifier.generate(), 

90 expiration=Timestamp.create_with_delta( 

91 minutes=command.access_token_expiry_minutes 

92 ), 

93 user_account=user_account, 

94 ) 

95 ) 

96 

97 return await self._refresh_token_repo.create( 

98 RefreshTokenEntity( 

99 identifier=TokenIdentifier.generate(), 

100 expiration=Timestamp.create_with_delta( 

101 minutes=command.refresh_token_expiry_minutes 

102 ), 

103 access_token=access_token, 

104 ) 

105 )