FastAPI-UI-Auth 0.1.0__py3-none-any.whl → 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: FastAPI-UI-Auth
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: Python module to add username and password authentication to specific FastAPI routes
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -10,6 +10,7 @@ Requires-Dist: jinja2==3.1.*
10
10
  Requires-Dist: pydantic==2.11.*
11
11
  Requires-Dist: python-dotenv==1.1.*
12
12
  Provides-Extra: dev
13
+ Requires-Dist: websockets==15.0.*; extra == "dev"
13
14
  Requires-Dist: pre-commit==4.2.*; extra == "dev"
14
15
  Requires-Dist: uvicorn==0.34.*; extra == "dev"
15
16
  Dynamic: license-file
@@ -37,25 +38,31 @@ pip install "git+https://github.com/${repo}.git@${latest}"
37
38
  ## Usage
38
39
 
39
40
  ```python
40
- import fastapiauthenticator
41
+ import fastapiauthenticator as api
41
42
 
42
43
  from fastapi import FastAPI
43
44
 
44
45
  app = FastAPI()
45
46
 
46
-
47
47
  @app.get("/public")
48
- def public_route():
48
+ async def public_route():
49
49
  return {"message": "This is a public route"}
50
50
 
51
-
52
- def private_route():
51
+ async def private_route():
53
52
  return {"message": "This is a private route"}
54
53
 
55
-
56
- fastapiauthenticator.Authenticator(app=app, secure_function=private_route)
54
+ api.protect(
55
+ app=app,
56
+ params=api.Parameters(
57
+ path="/private",
58
+ function=private_route
59
+ )
60
+ )
57
61
  ```
58
62
 
63
+ > `FastAPIAuthenticator` supports both `APIRoute` and `APIWebSocketRoute` routes.<br>
64
+ > Refer [samples] directory for different use-cases.
65
+
59
66
  ## Coding Standards
60
67
  Docstring format: [`Google`][google-docs] <br>
61
68
  Styling conventions: [`PEP 8`][pep8] and [`isort`][isort]
@@ -96,6 +103,7 @@ Licensed under the [MIT License][license]
96
103
  [google-docs]: https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings
97
104
  [pep8]: https://www.python.org/dev/peps/pep-0008/
98
105
  [isort]: https://pycqa.github.io/isort/
106
+ [samples]: https://github.com/thevickypedia/FastAPIAuthenticator/tree/main/samples
99
107
 
100
108
  [label-pyversion]: https://img.shields.io/badge/python-3.11%20%7C%203.12-blue
101
109
  [label-platform]: https://img.shields.io/badge/Platform-Linux|macOS|Windows-1f425f.svg
@@ -1,16 +1,16 @@
1
- fastapi_ui_auth-0.1.0.dist-info/licenses/LICENSE,sha256=_sOIKJWdD2o1WwwDIwYB2qTP2nlSWqT5Tyg9jr1Xa4w,1070
1
+ fastapi_ui_auth-0.1.1.dist-info/licenses/LICENSE,sha256=_sOIKJWdD2o1WwwDIwYB2qTP2nlSWqT5Tyg9jr1Xa4w,1070
2
2
  fastapiauthenticator/__init__.py,sha256=H1tJUJp4FWweEu2JeMw5vmqjtMhMwR4kUd11ek8UmwQ,320
3
3
  fastapiauthenticator/endpoints.py,sha256=PF2qu6XQ3MQStFEprRYYxiS6Dl6ukYaMqEkSlO-F3Ls,2104
4
4
  fastapiauthenticator/enums.py,sha256=WO0eBv3l9HHr1I_ZXtAifCgdL-db_tZj9ka7jnjiS5k,547
5
5
  fastapiauthenticator/models.py,sha256=GxmQfSvg70OTsvswJ3QFq_lxq-Yz1fIfzW6x8d4Sj40,2726
6
6
  fastapiauthenticator/secure.py,sha256=ZOH6kT4BD56VqwaKdKocX7eSE8tqZcu-tK0QOmjY58k,1089
7
- fastapiauthenticator/service.py,sha256=1UCCUf7yaH11BHB2vlaNYehVn5YyWbGK6bnrN7wJlDM,6485
8
- fastapiauthenticator/utils.py,sha256=geO78AL-nqv4EANQzQaWI2mGkkZiLZY8wm2LH_EDye0,7145
9
- fastapiauthenticator/version.py,sha256=aOHawL1zuHMfBWKXqwUkXcW96oXLNCY-CXdHDqkz4g4,18
7
+ fastapiauthenticator/service.py,sha256=7E474kwPIDsbtZPMGJTSo0thKc2FP2n-MUogsH0ZrZI,6247
8
+ fastapiauthenticator/utils.py,sha256=Is7oaaHYsZ97aPmDWTcIqStW693HRIDUKtkGBdfyMVg,6731
9
+ fastapiauthenticator/version.py,sha256=I4WU5JcKZjfggPiNIoGDFxUDB05Ym1xFhbC3J_MjilA,18
10
10
  fastapiauthenticator/templates/index.html,sha256=mA2R6gk6lvibq_AmPgGHBFQijYtNUD7IIfeBSJWQrM4,9078
11
11
  fastapiauthenticator/templates/session.html,sha256=LUCvcEdQOjfIXjRZ2gPx2s5wyzNuCve4OMge0hXaBLM,3053
12
12
  fastapiauthenticator/templates/unauthorized.html,sha256=UZo1Jt64-CFfjwWTGicUMdHVWkYkXCJBRxvit4QTiQM,3015
13
- fastapi_ui_auth-0.1.0.dist-info/METADATA,sha256=taS1d_w3xCMVJB5ymIbhjlM7GpWIfCPEf8PyR9u4pRQ,2402
14
- fastapi_ui_auth-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
15
- fastapi_ui_auth-0.1.0.dist-info/top_level.txt,sha256=EpDRP7uLM0f-Vd5rUtLBh4MTMAnpXzw1pr0DSknW_Ds,21
16
- fastapi_ui_auth-0.1.0.dist-info/RECORD,,
13
+ fastapi_ui_auth-0.1.1.dist-info/METADATA,sha256=cmKRN9LBPNgKnm66iKXZaAkBasddHo2vs5ISjFw9HJE,2733
14
+ fastapi_ui_auth-0.1.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
15
+ fastapi_ui_auth-0.1.1.dist-info/top_level.txt,sha256=EpDRP7uLM0f-Vd5rUtLBh4MTMAnpXzw1pr0DSknW_Ds,21
16
+ fastapi_ui_auth-0.1.1.dist-info/RECORD,,
@@ -42,7 +42,7 @@ class Authenticator:
42
42
 
43
43
  Args:
44
44
  app: FastAPI application instance to which the authenticator will be added.
45
- params: Parameters for the secure routes, can be a single `Parameters` object or a list of `Parameters`.
45
+ params: Parameters for the secure routes can be a single `Parameters` object or a list of `Parameters`.
46
46
  timeout: Session timeout in seconds, default is 300 seconds (5 minutes).
47
47
  username: Username for authentication, can be set via environment variable 'USERNAME'.
48
48
  password: Password for authentication, can be set via environment variable 'PASSWORD'.
@@ -59,10 +59,6 @@ class Authenticator:
59
59
  elif isinstance(params, models.Parameters):
60
60
  self.params = [params]
61
61
 
62
- self.route_map: Dict[str, models.Parameters] = {
63
- param.path: param for param in self.params if param.route is APIRoute
64
- }
65
-
66
62
  models.fallback.path = fallback_path
67
63
  models.fallback.button = fallback_button
68
64
 
@@ -95,19 +91,18 @@ class Authenticator:
95
91
  Dict[str, str]:
96
92
  A dictionary containing the redirect URL to the secure path.
97
93
  """
98
- utils.verify_login(
94
+ session_token = utils.verify_login(
99
95
  authorization=authorization,
100
96
  request=request,
101
97
  env_username=self.username,
102
98
  env_password=self.password,
103
99
  )
104
- destination = request.cookies.get("X-Requested-By")
105
- if parameter := self.route_map.get(destination):
100
+ if destination := request.cookies.get("X-Requested-By"):
106
101
  LOGGER.info("Setting session timeout for %s seconds", self.timeout)
107
102
  # Set session_token cookie with a timeout, to be used for session validation when redirected
108
103
  response.set_cookie(
109
104
  key="session_token",
110
- value=models.ws_session.client_auth[request.client.host].get("token"),
105
+ value=session_token,
111
106
  httponly=True,
112
107
  samesite="strict",
113
108
  max_age=self.timeout,
@@ -118,7 +113,7 @@ class Authenticator:
118
113
  args=(request.client.host,),
119
114
  interval=self.timeout,
120
115
  ).start()
121
- return {"redirect_url": parameter.path}
116
+ return {"redirect_url": destination}
122
117
  raise HTTPException(
123
118
  status_code=status.HTTP_417_EXPECTATION_FAILED,
124
119
  detail="Unable to find secure route for the requested path.\n"
@@ -154,14 +149,14 @@ class Authenticator:
154
149
  secure_route = APIWebSocketRoute(
155
150
  path=param.path,
156
151
  endpoint=param.function,
157
- dependencies=[Depends(utils.session_check)],
152
+ dependencies=[Depends(utils.verify_session)],
158
153
  )
159
154
  else:
160
155
  secure_route = APIRoute(
161
156
  path=param.path,
162
157
  endpoint=param.function,
163
158
  methods=["GET"],
164
- dependencies=[Depends(utils.session_check)],
159
+ dependencies=[Depends(utils.verify_session)],
165
160
  )
166
161
  self.app.routes.append(secure_route)
167
162
  self.app.routes.extend([login_route, session_route, verify_route, error_route])
@@ -1,6 +1,6 @@
1
1
  import logging
2
2
  import secrets
3
- from typing import Dict, List, NoReturn, Union
3
+ from typing import List, NoReturn
4
4
 
5
5
  from fastapi import status
6
6
  from fastapi.exceptions import HTTPException
@@ -73,17 +73,12 @@ def raise_error(request: Request) -> NoReturn:
73
73
  )
74
74
 
75
75
 
76
- def extract_credentials(
77
- authorization: HTTPAuthorizationCredentials, host: str
78
- ) -> List[str]:
76
+ def extract_credentials(authorization: HTTPAuthorizationCredentials) -> List[str]:
79
77
  """Extract the credentials from ``Authorization`` headers and decode it before returning as a list of strings.
80
78
 
81
79
  Args:
82
80
  authorization: Authorization header from the request.
83
- host: Host header from the request.
84
81
  """
85
- if not authorization:
86
- raise_error(host)
87
82
  decoded_auth = secure.base64_decode(authorization.credentials)
88
83
  # convert hex to a string
89
84
  auth = secure.hex_decode(decoded_auth)
@@ -95,7 +90,7 @@ def verify_login(
95
90
  request: Request,
96
91
  env_username: str,
97
92
  env_password: str,
98
- ) -> Dict[str, Union[str, int]]:
93
+ ) -> str | NoReturn:
99
94
  """Verifies authentication and generates session token for each user.
100
95
 
101
96
  Args:
@@ -105,12 +100,13 @@ def verify_login(
105
100
  env_password: Environment variable for the password.
106
101
 
107
102
  Returns:
108
- Dict[str, str]:
109
- Returns a dictionary with the payload required to create the session token.
103
+ str:
104
+ Returns the session token.
110
105
  """
111
- username, signature, timestamp = extract_credentials(
112
- authorization, request.client.host
113
- )
106
+ if authorization:
107
+ username, signature, timestamp = extract_credentials(authorization)
108
+ else:
109
+ raise_error(request)
114
110
  if secrets.compare_digest(username, env_username):
115
111
  hex_user = secure.hex_encode(env_username)
116
112
  hex_pass = secure.hex_encode(env_password)
@@ -122,15 +118,14 @@ def verify_login(
122
118
  if secrets.compare_digest(signature, expected_signature):
123
119
  models.ws_session.invalid[request.client.host] = 0
124
120
  key = secrets.token_urlsafe(64)
125
- # fixme: By setting a path instead of timestamp, this can handle path specific sessions
126
- models.ws_session.client_auth[request.client.host] = dict(
127
- username=username, token=key, timestamp=int(timestamp)
128
- )
129
- return models.ws_session.client_auth[request.client.host]
121
+ models.ws_session.client_auth[request.client.host] = key
122
+ return key
130
123
  raise_error(request)
131
124
 
132
125
 
133
- def session_check(api_request: Request = None, api_websocket: WebSocket = None) -> None:
126
+ def verify_session(
127
+ api_request: Request = None, api_websocket: WebSocket = None
128
+ ) -> None:
134
129
  """Check if the session is still valid.
135
130
 
136
131
  Args:
@@ -150,9 +145,7 @@ def session_check(api_request: Request = None, api_websocket: WebSocket = None)
150
145
  detail="Request or WebSocket connection is required for session check.",
151
146
  )
152
147
  session_token = request.cookies.get("session_token")
153
- stored_token = models.ws_session.client_auth.get(request.client.host, {}).get(
154
- "token"
155
- )
148
+ stored_token = models.ws_session.client_auth.get(request.client.host)
156
149
  if (
157
150
  stored_token
158
151
  and session_token
@@ -1 +1 @@
1
- version = "0.1.0"
1
+ version = "0.1.1"