Coverage for nexios\auth\base.py: 76%

1 from __future__ import annotations

2 import typing

3 from typing_extensions import Annotated, Doc

4 from nexios.http import Request, Response

5

6

7 class AuthenticationError(Exception):

8 """

9 Exception raised when authentication fails.

10

11 This error is triggered when an authentication backend determines that

12 the provided credentials are invalid or the authentication process

13 encounters an unexpected issue.

14 """

15

16 pass

17

18

19 class AuthCredentials:

20 def __init__(self, scopes: typing.Optional[typing.Sequence[str]] = None):

21 self.scopes = [] if scopes is None else list(scopes)

22

23

24 class AuthenticationBackend:

25 """

26 Base class for authentication backends in Nexios.

27

28 Authentication backends are responsible for verifying user credentials

29 and returning an authenticated user instance if authentication is successful.

30

31 Subclasses must override `authenticate()` to implement custom authentication logic.

32 """

33

34 async def authenticate(

35 self,

36 req: Annotated[

37 Request, Doc("The incoming HTTP request containing authentication details.")

38 ],

39 res: Annotated[

40 Response,

41 Doc("The HTTP response object that may be modified during authentication."),

42 ],

43 ) -> Annotated[

44 typing.Any,

45 Doc("Returns an authenticated user instance or raises an AuthenticationError."),

46 ]:

47 """

48 Authenticates a user based on the request.

49

50 Subclasses must implement this method to verify authentication credentials

51 (e.g., headers, cookies, or tokens) and return an authenticated user instance.

52

53 Args:

54 req (Request): The HTTP request object.

55 res (Response): The HTTP response object.

56

57 Returns:

58 Any: An authenticated user object if authentication succeeds.

59

60 Raises:

61 AuthenticationError: If authentication fails.

62 """

63 raise NotImplementedError()

64

65

66 class BaseUser:

67 """

68 Abstract base class for user objects.

69

70 This class defines the minimum required properties for user objects,

71 including authentication status, display name, and identity.

72

73 Subclasses should override these properties to provide meaningful values.

74 """

75

76 @property

77 def is_authenticated(

78 self,

79 ) -> Annotated[bool, Doc("Indicates whether the user is authenticated.")]:

80 """

81 Checks if the user is authenticated.

82

83 This property should be overridden in subclasses to return `True` for

84 authenticated users and `False` for unauthenticated users.

85

86 Returns:

87 bool: `True` if the user is authenticated, otherwise `False`.

88 """

89 raise NotImplementedError()

90

91 @property

92 def display_name(

93 self,

94 ) -> Annotated[str, Doc("The name to be displayed for the user.")]:

95 """

96 Retrieves the display name of the user.

97

98 This property should be overridden to return a human-readable

99 name for authenticated users or an empty string for unauthenticated users.

100

101 Returns:

102 str: The display name of the user.

103 """

104 raise NotImplementedError()

105

106 @property

107 def identity(

108 self,

109 ) -> Annotated[

110 str, Doc("A unique identifier for the user, such as a username or ID.")

111 ]:

112 """

113 Retrieves the unique identity of the user.

114

115 This property should be overridden to return a unique identifier

116 (e.g., username, email, or user ID).

117

118 Returns:

119 str: The unique identifier of the user.

120 """

121 raise NotImplementedError()

122

123

124 class SimpleUser(BaseUser):

125 """

126 A basic implementation of an authenticated user.

127

128 This class represents a simple authenticated user with a username.

129 """

130

131 def __init__(

132 self, username: Annotated[str, Doc("The username of the authenticated user.")]

133 ) -> None:

134 """

135 Initializes a simple authenticated user.

136

137 Args:

138 username (str): The username of the user.

139 """

140 self.username = username

141

142 @property

143 def is_authenticated(

144 self,

145 ) -> Annotated[

146 bool, Doc("Always returns `True` since this user is authenticated.")

147 ]:

148 """

149 Indicates that the user is authenticated.

150

151 Returns:

152 bool: Always `True` since this represents an authenticated user.

153 """

154 return True

155

156 @property

157 def display_name(

158 self,

159 ) -> Annotated[str, Doc("Returns the username as the display name.")]:

160 """

161 Retrieves the display name of the user.

162

163 Returns:

164 str: The username of the authenticated user.

165 """

166 return self.username

167

168

169 class UnauthenticatedUser(BaseUser):

170 """

171 Represents an unauthenticated user.

172

173 This class is used to represent users who have not logged in.

174 """

175

176 @property

177 def is_authenticated(

178 self,

179 ) -> Annotated[

180 bool, Doc("Always returns `False` since this user is unauthenticated.")

181 ]:

182 """

183 Indicates that the user is not authenticated.

184

185 Returns:

186 bool: Always `False` since this represents an unauthenticated user.

187 """

188 return False

189

190 @property

191 def display_name(

192 self,

193 ) -> Annotated[

194 str,

195 Doc(

196 "Returns an empty string since unauthenticated users have no display name."

197 ),

198 ]:

199 """

200 Retrieves the display name of the user.

201

202 Since unauthenticated users do not have a valid username, this

203 method returns an empty string.

204

205 Returns:

206 str: An empty string.

207 """

208 return ""