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

31 statements  

« prev     ^ index     » next       coverage.py v7.3.0, created at 2023-09-05 17:55 +0000

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

2from dataclasses import dataclass 

3from datetime import datetime, timedelta 

4 

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

6from kwai.modules.identity.exceptions import AuthenticationException 

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

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

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

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

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

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

13 

14 

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

16class AuthenticateUserCommand: 

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

18 

19 Attributes: 

20 username: The email address of the user. 

21 password: The password of the user. 

22 access_token_expiry_minutes: Minutes before expiring the access token. 

23 Default is 2 hours. 

24 refresh_token_expiry_minutes: Minutes before expiring the refresh token. 

25 Default is 2 months. 

26 """ 

27 

28 username: str 

29 password: str 

30 access_token_expiry_minutes: int = 60 * 2 # 2 hours 

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

32 

33 

34class AuthenticateUser: 

35 """Use case to authenticate a user. 

36 

37 Attributes: 

38 _user_account_repo (UserAccountRepository): The repository for getting the 

39 user account. 

40 _access_token_repo (UserAccountRepository): The repository for creating the 

41 access token. 

42 _refresh_token_repo (UserAccountRepository): The repository for creating the 

43 refresh token. 

44 """ 

45 

46 def __init__( 

47 self, 

48 user_account_repo: UserAccountRepository, 

49 access_token_repo: AccessTokenRepository, 

50 refresh_token_repo: RefreshTokenRepository, 

51 ): 

52 self._user_account_repo = user_account_repo 

53 self._access_token_repo = access_token_repo 

54 self._refresh_token_repo = refresh_token_repo 

55 

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

57 """Execute the use case. 

58 

59 Args: 

60 command: The input for this use case. 

61 

62 Returns: 

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

64 

65 Raises: 

66 InvalidEmailException: Raised when the username 

67 contains an invalid email address. 

68 UserAccountNotFoundException: Raised when the user with the given email 

69 address doesn't exist. 

70 """ 

71 user_account = await self._user_account_repo.get_user_by_email( 

72 EmailAddress(command.username) 

73 ) 

74 if user_account.revoked: 

75 raise AuthenticationException("User account is revoked") 

76 

77 if not user_account.login(command.password): 

78 await self._user_account_repo.update( 

79 user_account 

80 ) # save the last unsuccessful login 

81 raise AuthenticationException("Invalid password") 

82 

83 await self._user_account_repo.update( 

84 user_account 

85 ) # save the last successful login 

86 

87 access_token = await self._access_token_repo.create( 

88 AccessTokenEntity( 

89 identifier=TokenIdentifier.generate(), 

90 expiration=datetime.utcnow() 

91 + timedelta(minutes=command.access_token_expiry_minutes), 

92 user_account=user_account, 

93 ) 

94 ) 

95 

96 return await self._refresh_token_repo.create( 

97 RefreshTokenEntity( 

98 identifier=TokenIdentifier.generate(), 

99 expiration=datetime.utcnow() 

100 + timedelta(minutes=command.refresh_token_expiry_minutes), 

101 access_token=access_token, 

102 ) 

103 )