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.
- {fastapi_ui_auth-0.1.0.dist-info → fastapi_ui_auth-0.1.1.dist-info}/METADATA +16 -8
- {fastapi_ui_auth-0.1.0.dist-info → fastapi_ui_auth-0.1.1.dist-info}/RECORD +8 -8
- fastapiauthenticator/service.py +7 -12
- fastapiauthenticator/utils.py +15 -22
- fastapiauthenticator/version.py +1 -1
- {fastapi_ui_auth-0.1.0.dist-info → fastapi_ui_auth-0.1.1.dist-info}/WHEEL +0 -0
- {fastapi_ui_auth-0.1.0.dist-info → fastapi_ui_auth-0.1.1.dist-info}/licenses/LICENSE +0 -0
- {fastapi_ui_auth-0.1.0.dist-info → fastapi_ui_auth-0.1.1.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: FastAPI-UI-Auth
|
|
3
|
-
Version: 0.1.
|
|
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
|
-
|
|
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.
|
|
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=
|
|
8
|
-
fastapiauthenticator/utils.py,sha256=
|
|
9
|
-
fastapiauthenticator/version.py,sha256=
|
|
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.
|
|
14
|
-
fastapi_ui_auth-0.1.
|
|
15
|
-
fastapi_ui_auth-0.1.
|
|
16
|
-
fastapi_ui_auth-0.1.
|
|
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,,
|
fastapiauthenticator/service.py
CHANGED
|
@@ -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
|
|
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
|
|
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=
|
|
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":
|
|
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.
|
|
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.
|
|
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])
|
fastapiauthenticator/utils.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import secrets
|
|
3
|
-
from typing import
|
|
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
|
-
) ->
|
|
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
|
-
|
|
109
|
-
Returns
|
|
103
|
+
str:
|
|
104
|
+
Returns the session token.
|
|
110
105
|
"""
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
126
|
-
|
|
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
|
|
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
|
|
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
|
fastapiauthenticator/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
version = "0.1.
|
|
1
|
+
version = "0.1.1"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|