Coverage for bc/kwai-bc-identity/src/kwai_bc_identity/users/user_account.py: 96%

28 statements  

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

1"""Module that implements a user account entity.""" 

2 

3from dataclasses import dataclass, field, replace 

4from typing import ClassVar, Self, Type 

5 

6from kwai_core.domain.entity import DataclassEntity 

7from kwai_core.domain.value_objects.identifier import IntIdentifier 

8from kwai_core.domain.value_objects.password import Password 

9from kwai_core.domain.value_objects.timestamp import Timestamp 

10 

11from kwai_bc_identity.exceptions import NotAllowedException 

12from kwai_bc_identity.users.user import UserEntity 

13 

14 

15class UserAccountIdentifier(IntIdentifier): 

16 """Identifier for a user account.""" 

17 

18 

19@dataclass(kw_only=True, eq=False, slots=True, frozen=True) 

20class UserAccountEntity(DataclassEntity): 

21 """A user account entity. 

22 

23 Attributes: 

24 user: The associated user entity. 

25 password: The password of the user. 

26 logged_in: Whether the user is logged in. 

27 last_login: Timestamp of the last login. 

28 last_unsuccessful_login: Timestamp of the last unsuccessful login. 

29 revoked: Whether the user is revoked. 

30 admin: Whether the user is an administrator. 

31 """ 

32 

33 ID: ClassVar[Type] = UserAccountIdentifier 

34 

35 user: UserEntity 

36 password: Password 

37 logged_in: bool = False 

38 last_login: Timestamp = field(default_factory=Timestamp) 

39 last_unsuccessful_login: Timestamp = field(default_factory=Timestamp) 

40 revoked: bool = False 

41 

42 def login(self, password: str | None = None) -> Self: 

43 """Check if the given password is correct. 

44 

45 When login succeeds, last_login will be updated. 

46 When login fails, last_unsuccessful_login will be updated. 

47 Use None for password, when the user logs in using OIDC. 

48 

49 Args: 

50 password: The password. 

51 """ 

52 if password and not self.password.verify(password): 

53 return replace( 

54 self, last_unsuccessful_login=Timestamp.create_now(), logged_in=False 

55 ) 

56 

57 return replace(self, last_login=Timestamp.create_now(), logged_in=True) 

58 

59 def reset_password(self, password: Password) -> Self: 

60 """Reset the password of the user account. 

61 

62 Args: 

63 password: The new password. 

64 """ 

65 if self.revoked: 

66 raise NotAllowedException() 

67 

68 return replace( 

69 self, 

70 password=password, 

71 traceable_time=self.traceable_time.mark_for_update(), 

72 ) 

73 

74 def revoke(self) -> Self: 

75 """Revoke a user account.""" 

76 return replace( 

77 self, revoked=True, traceable_time=self.traceable_time.mark_for_update() 

78 ) 

79 

80 def enact(self) -> Self: 

81 """Reactivate a user account.""" 

82 return replace( 

83 self, revoked=False, traceable_time=self.traceable_time.mark_for_update() 

84 )