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

38 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-05-21 20:31 +0100

1from __future__ import annotations 

2import typing 

3from typing_extensions import Annotated, Doc 

4from nexios.http import Request, Response 

5 

6 

7class 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 

19class AuthCredentials: 

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

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

22 

23 

24class 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 

66class 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 

124class 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 

169class 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 ""