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
« 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."""
3from dataclasses import dataclass
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
16@dataclass(kw_only=True, frozen=True)
17class AuthenticateUserCommand:
18 """Input for the (AuthenticateUser) use case.
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 """
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
35class AuthenticateUser:
36 """Use case to authenticate a user.
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 """
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
57 async def execute(self, command: AuthenticateUserCommand) -> RefreshTokenEntity:
58 """Execute the use case.
60 Args:
61 command: The input for this use case.
63 Returns:
64 RefreshTokenEntity: On success, a refresh token entity will be returned.
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")
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")
84 # save the last successful login
85 await self._user_account_repo.update(user_account)
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 )
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 )