FastAPI-UI-Auth 0.1.0__py3-none-any.whl → 0.2.0__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.2.0.dist-info}/METADATA +44 -16
- fastapi_ui_auth-0.2.0.dist-info/RECORD +17 -0
- fastapi_ui_auth-0.2.0.dist-info/top_level.txt +1 -0
- uiauth/__init__.py +6 -0
- {fastapiauthenticator → uiauth}/endpoints.py +2 -2
- uiauth/logger.py +13 -0
- {fastapiauthenticator → uiauth}/models.py +53 -2
- {fastapiauthenticator → uiauth}/service.py +25 -26
- {fastapiauthenticator → uiauth}/templates/index.html +1 -1
- {fastapiauthenticator → uiauth}/templates/session.html +3 -3
- {fastapiauthenticator → uiauth}/templates/unauthorized.html +3 -3
- {fastapiauthenticator → uiauth}/utils.py +26 -40
- uiauth/version.py +1 -0
- fastapi_ui_auth-0.1.0.dist-info/RECORD +0 -16
- fastapi_ui_auth-0.1.0.dist-info/top_level.txt +0 -1
- fastapiauthenticator/__init__.py +0 -6
- fastapiauthenticator/version.py +0 -1
- {fastapi_ui_auth-0.1.0.dist-info → fastapi_ui_auth-0.2.0.dist-info}/WHEEL +0 -0
- {fastapi_ui_auth-0.1.0.dist-info → fastapi_ui_auth-0.2.0.dist-info}/licenses/LICENSE +0 -0
- {fastapiauthenticator → uiauth}/enums.py +0 -0
- {fastapiauthenticator → uiauth}/secure.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: FastAPI-UI-Auth
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
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,11 +10,12 @@ 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
|
|
16
17
|
|
|
17
|
-
#
|
|
18
|
+
# FastAPIUIAuth
|
|
18
19
|
|
|
19
20
|
Python module to add username and password authentication to specific FastAPI routes
|
|
20
21
|
|
|
@@ -24,38 +25,48 @@ Python module to add username and password authentication to specific FastAPI ro
|
|
|
24
25
|
|
|
25
26
|
![Platform][label-platform]
|
|
26
27
|
|
|
27
|
-
|
|
28
|
+
**Deployments**
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
repo="thevickypedia/FastAPIAuthenticator"
|
|
30
|
+
[![pypi][label-actions-pypi]][gha_pypi]
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
[![Pypi][label-pypi]][pypi]
|
|
33
|
+
[![Pypi-format][label-pypi-format]][pypi-files]
|
|
34
|
+
[![Pypi-status][label-pypi-status]][pypi]
|
|
35
|
+
|
|
36
|
+
## Installation
|
|
33
37
|
|
|
34
|
-
|
|
38
|
+
```shell
|
|
39
|
+
pip install FastAPI-UI-Auth
|
|
35
40
|
```
|
|
36
41
|
|
|
37
42
|
## Usage
|
|
38
43
|
|
|
39
44
|
```python
|
|
40
|
-
import
|
|
45
|
+
import uiauth
|
|
41
46
|
|
|
42
47
|
from fastapi import FastAPI
|
|
43
48
|
|
|
44
49
|
app = FastAPI()
|
|
45
50
|
|
|
46
|
-
|
|
47
51
|
@app.get("/public")
|
|
48
|
-
def public_route():
|
|
52
|
+
async def public_route():
|
|
49
53
|
return {"message": "This is a public route"}
|
|
50
54
|
|
|
51
|
-
|
|
52
|
-
def private_route():
|
|
55
|
+
async def private_route():
|
|
53
56
|
return {"message": "This is a private route"}
|
|
54
57
|
|
|
55
|
-
|
|
56
|
-
|
|
58
|
+
uiauth.protect(
|
|
59
|
+
app=app,
|
|
60
|
+
params=uiauth.Parameters(
|
|
61
|
+
path="/private",
|
|
62
|
+
function=private_route
|
|
63
|
+
)
|
|
64
|
+
)
|
|
57
65
|
```
|
|
58
66
|
|
|
67
|
+
> `FastAPI-UI-Auth` supports both `APIRoute` and `APIWebSocketRoute` routes.<br>
|
|
68
|
+
> Refer [samples] directory for different use-cases.
|
|
69
|
+
|
|
59
70
|
## Coding Standards
|
|
60
71
|
Docstring format: [`Google`][google-docs] <br>
|
|
61
72
|
Styling conventions: [`PEP 8`][pep8] and [`isort`][isort]
|
|
@@ -83,6 +94,11 @@ python -m pip install sphinx==5.1.1 pre-commit recommonmark
|
|
|
83
94
|
pre-commit run --all-files
|
|
84
95
|
```
|
|
85
96
|
|
|
97
|
+
## Pypi Package
|
|
98
|
+
[![pypi-module][label-pypi-package]][pypi-repo]
|
|
99
|
+
|
|
100
|
+
[https://pypi.org/project/FastAPI-UI-Auth/][pypi]
|
|
101
|
+
|
|
86
102
|
## License & copyright
|
|
87
103
|
|
|
88
104
|
© Vignesh Rao
|
|
@@ -92,11 +108,23 @@ Licensed under the [MIT License][license]
|
|
|
92
108
|
[//]: # (Labels)
|
|
93
109
|
|
|
94
110
|
[3.11]: https://docs.python.org/3/whatsnew/3.11.html
|
|
95
|
-
[license]: https://github.com/thevickypedia/
|
|
111
|
+
[license]: https://github.com/thevickypedia/FastAPI-UI-Auth/blob/main/LICENSE
|
|
96
112
|
[google-docs]: https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings
|
|
97
113
|
[pep8]: https://www.python.org/dev/peps/pep-0008/
|
|
98
114
|
[isort]: https://pycqa.github.io/isort/
|
|
115
|
+
[samples]: https://github.com/thevickypedia/FastAPI-UI-Auth/tree/main/samples
|
|
99
116
|
|
|
117
|
+
[label-actions-pypi]: https://github.com/thevickypedia/FastAPI-UI-Auth/actions/workflows/python-publish.yml/badge.svg
|
|
118
|
+
[label-pypi]: https://img.shields.io/pypi/v/FastAPI-UI-Auth
|
|
119
|
+
[label-pypi-format]: https://img.shields.io/pypi/format/FastAPI-UI-Auth
|
|
120
|
+
[label-pypi-status]: https://img.shields.io/pypi/status/FastAPI_UI_Auth
|
|
121
|
+
[label-pypi-package]: https://img.shields.io/badge/Pypi%20Package-FastAPI_UI_Auth-blue?style=for-the-badge&logo=Python
|
|
100
122
|
[label-pyversion]: https://img.shields.io/badge/python-3.11%20%7C%203.12-blue
|
|
101
123
|
[label-platform]: https://img.shields.io/badge/Platform-Linux|macOS|Windows-1f425f.svg
|
|
102
|
-
[release-notes]: https://github.com/thevickypedia/
|
|
124
|
+
[release-notes]: https://github.com/thevickypedia/FastAPI-UI-Auth/blob/main/release_notes.rst
|
|
125
|
+
|
|
126
|
+
[gha_pypi]: https://github.com/thevickypedia/FastAPI-UI-Auth/actions/workflows/python-publish.yml
|
|
127
|
+
|
|
128
|
+
[pypi]: https://pypi.org/project/FastAPI-UI-Auth
|
|
129
|
+
[pypi-files]: https://pypi.org/project/FastAPI-UI-Auth/#files
|
|
130
|
+
[pypi-repo]: https://packaging.python.org/tutorials/packaging-projects/
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
fastapi_ui_auth-0.2.0.dist-info/licenses/LICENSE,sha256=_sOIKJWdD2o1WwwDIwYB2qTP2nlSWqT5Tyg9jr1Xa4w,1070
|
|
2
|
+
uiauth/__init__.py,sha256=s8r2Z0O9w3cuw7GcmRTOWY0NZC0KJXzBS9QsG9wUWsk,264
|
|
3
|
+
uiauth/endpoints.py,sha256=4RtabYwlNSHAqpU89zfRH8RD_ATH1AAf-lJKg4HA1RQ,2076
|
|
4
|
+
uiauth/enums.py,sha256=WO0eBv3l9HHr1I_ZXtAifCgdL-db_tZj9ka7jnjiS5k,547
|
|
5
|
+
uiauth/logger.py,sha256=z67PBMs4zWOfy-Gfm_41dj5Uulm-ChvZxB_jmYKKXeI,391
|
|
6
|
+
uiauth/models.py,sha256=lcJyy99c-VeSeUj3LahXisQZ4g3wRqdGtBVdL1oyaZI,4255
|
|
7
|
+
uiauth/secure.py,sha256=ZOH6kT4BD56VqwaKdKocX7eSE8tqZcu-tK0QOmjY58k,1089
|
|
8
|
+
uiauth/service.py,sha256=6CN3Rg8m3f83sJuLdswVzwkxkwkjptHgWa5uugxIccE,6460
|
|
9
|
+
uiauth/utils.py,sha256=DzXqxLpKHUDy1bxffg1cw0izqxcgmnCybSytywiPgbQ,6625
|
|
10
|
+
uiauth/version.py,sha256=XQVhijSHeIVMVzY2S4fY9BKxV2XHSTr9VVHsYltGPvQ,18
|
|
11
|
+
uiauth/templates/index.html,sha256=8vbONgCdhBmwe12ITeuBSjwwjp309kDS9cu2lRrrG88,9080
|
|
12
|
+
uiauth/templates/session.html,sha256=EL4gajOED3IcOnrALMiJ2SzJl2at8GFfruTuExhgOVI,3040
|
|
13
|
+
uiauth/templates/unauthorized.html,sha256=ahv78zLM04_Lu83LdX0Ua_toKeP5JZkYsTCWCrfCvHA,3002
|
|
14
|
+
fastapi_ui_auth-0.2.0.dist-info/METADATA,sha256=L10c3aWSYOwNnAQIcbVO4SzBVH7Xa6DhueQViFlmW_o,3553
|
|
15
|
+
fastapi_ui_auth-0.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
16
|
+
fastapi_ui_auth-0.2.0.dist-info/top_level.txt,sha256=ra3nGTbDTgQ7eChlkngJ7xGXhSCeFTWMvb_b6q8uPVA,7
|
|
17
|
+
fastapi_ui_auth-0.2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
uiauth
|
uiauth/__init__.py
ADDED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
from fastapi.requests import Request
|
|
2
2
|
from fastapi.responses import HTMLResponse
|
|
3
3
|
|
|
4
|
-
from
|
|
5
|
-
from
|
|
4
|
+
from uiauth import enums, models, utils
|
|
5
|
+
from uiauth.version import version
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
def session(request: Request) -> HTMLResponse:
|
uiauth/logger.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Default logger for FastAPI-UI-Auth package."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
CUSTOM_LOGGER = logging.getLogger(__name__)
|
|
7
|
+
CUSTOM_LOGGER.setLevel(logging.DEBUG)
|
|
8
|
+
CONSOLE_HANDLER = logging.StreamHandler(sys.stdout)
|
|
9
|
+
CONSOLE_FORMATTER = logging.Formatter(
|
|
10
|
+
fmt="%(levelname)-9s %(message)s",
|
|
11
|
+
)
|
|
12
|
+
CONSOLE_HANDLER.setFormatter(fmt=CONSOLE_FORMATTER)
|
|
13
|
+
CUSTOM_LOGGER.addHandler(hdlr=CONSOLE_HANDLER)
|
|
@@ -1,15 +1,66 @@
|
|
|
1
|
+
import os
|
|
1
2
|
import pathlib
|
|
2
3
|
from typing import Callable, Dict, List, Optional, Type
|
|
3
4
|
|
|
4
5
|
from fastapi.routing import APIRoute, APIWebSocketRoute
|
|
5
6
|
from fastapi.templating import Jinja2Templates
|
|
6
|
-
from pydantic import BaseModel, Field
|
|
7
|
+
from pydantic import BaseModel, Field, ValidationInfo, field_validator
|
|
7
8
|
|
|
8
|
-
from
|
|
9
|
+
from uiauth.enums import APIMethods
|
|
9
10
|
|
|
10
11
|
templates = Jinja2Templates(directory=pathlib.Path(__file__).parent / "templates")
|
|
11
12
|
|
|
12
13
|
|
|
14
|
+
def get_env(keys: List[str], default: Optional[str] = None) -> Optional[str]:
|
|
15
|
+
"""Get environment variable value.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
keys: List of environment variable names to check.
|
|
19
|
+
default: Default value if the environment variable is not set.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
Value of the environment variable or default value.
|
|
23
|
+
"""
|
|
24
|
+
for key in keys:
|
|
25
|
+
if value := os.getenv(key):
|
|
26
|
+
return value
|
|
27
|
+
if value := os.getenv(key.upper()):
|
|
28
|
+
return value
|
|
29
|
+
if value := os.getenv(key.lower()):
|
|
30
|
+
return value
|
|
31
|
+
return default
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class EnvConfig(BaseModel):
|
|
35
|
+
"""Configuration for environment variables."""
|
|
36
|
+
|
|
37
|
+
username: str
|
|
38
|
+
password: str
|
|
39
|
+
|
|
40
|
+
# noinspection PyMethodParameters
|
|
41
|
+
@field_validator("username", "password", mode="before")
|
|
42
|
+
def load_user(cls, key: str, field: ValidationInfo) -> str | None:
|
|
43
|
+
"""Load environment variables into the configuration.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
key: Environment variable key to check.
|
|
47
|
+
field: Field information for validation.
|
|
48
|
+
|
|
49
|
+
See Also:
|
|
50
|
+
- This method checks if the environment variable is set and returns its value.
|
|
51
|
+
- If the key is not set, it attempts to get the value from the environment using a helper function.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
str | None:
|
|
55
|
+
Value of the environment variable or None if not set.
|
|
56
|
+
"""
|
|
57
|
+
if not key:
|
|
58
|
+
return get_env([field.field_name, field.field_name[:4]])
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
env = EnvConfig
|
|
62
|
+
|
|
63
|
+
|
|
13
64
|
class Parameters(BaseModel):
|
|
14
65
|
"""Parameters for the Authenticator class.
|
|
15
66
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
import os
|
|
3
2
|
from threading import Timer
|
|
4
3
|
from typing import Dict, List
|
|
5
4
|
|
|
@@ -13,18 +12,17 @@ from fastapi.responses import Response
|
|
|
13
12
|
from fastapi.routing import APIRoute, APIWebSocketRoute
|
|
14
13
|
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
|
15
14
|
|
|
16
|
-
from
|
|
15
|
+
from uiauth import endpoints, enums, logger, models, utils
|
|
17
16
|
|
|
18
17
|
dotenv.load_dotenv(dotenv_path=dotenv.find_dotenv(), override=True)
|
|
19
|
-
LOGGER = logging.getLogger("uvicorn.default")
|
|
20
18
|
BEARER_AUTH = HTTPBearer()
|
|
21
19
|
|
|
22
20
|
|
|
23
21
|
# noinspection PyDefaultArgument
|
|
24
|
-
class
|
|
25
|
-
"""
|
|
22
|
+
class FastAPIUIAuth:
|
|
23
|
+
"""FastAPIUIAuth is a FastAPI integration that provides authentication for secure routes.
|
|
26
24
|
|
|
27
|
-
>>>
|
|
25
|
+
>>> FastAPIUIAuth
|
|
28
26
|
|
|
29
27
|
"""
|
|
30
28
|
|
|
@@ -33,23 +31,25 @@ class Authenticator:
|
|
|
33
31
|
app: FastAPI,
|
|
34
32
|
params: models.Parameters | List[models.Parameters],
|
|
35
33
|
timeout: int = 300,
|
|
36
|
-
username: str =
|
|
37
|
-
password: str =
|
|
34
|
+
username: str = None,
|
|
35
|
+
password: str = None,
|
|
38
36
|
fallback_button: str = models.fallback.button,
|
|
39
37
|
fallback_path: str = models.fallback.path,
|
|
38
|
+
custom_logger: logging.Logger = None,
|
|
40
39
|
):
|
|
41
40
|
"""Initialize the APIAuthenticator with the FastAPI app and secure function.
|
|
42
41
|
|
|
43
42
|
Args:
|
|
44
43
|
app: FastAPI application instance to which the authenticator will be added.
|
|
45
|
-
params: Parameters for the secure routes
|
|
44
|
+
params: Parameters for the secure routes can be a single `Parameters` object or a list of `Parameters`.
|
|
46
45
|
timeout: Session timeout in seconds, default is 300 seconds (5 minutes).
|
|
47
46
|
username: Username for authentication, can be set via environment variable 'USERNAME'.
|
|
48
47
|
password: Password for authentication, can be set via environment variable 'PASSWORD'.
|
|
49
48
|
fallback_button: Title for the fallback button, defaults to "LOGIN".
|
|
50
49
|
fallback_path: Fallback path to redirect to in case of session timeout or invalid session.
|
|
50
|
+
custom_logger: Custom logger instance, defaults to the custom logger.
|
|
51
51
|
"""
|
|
52
|
-
|
|
52
|
+
models.env = models.EnvConfig(username=username, password=password)
|
|
53
53
|
assert fallback_path.startswith("/"), "Fallback path must start with '/'"
|
|
54
54
|
|
|
55
55
|
self.app = app
|
|
@@ -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
|
|
|
@@ -72,11 +68,15 @@ class Authenticator:
|
|
|
72
68
|
handler=utils.redirect_exception_handler,
|
|
73
69
|
)
|
|
74
70
|
|
|
75
|
-
|
|
76
|
-
|
|
71
|
+
if custom_logger:
|
|
72
|
+
assert isinstance(
|
|
73
|
+
custom_logger, logging.Logger
|
|
74
|
+
), "Custom logger must be an instance of logging.Logger"
|
|
75
|
+
logger.CUSTOM_LOGGER = custom_logger
|
|
77
76
|
self.timeout = timeout
|
|
78
77
|
|
|
79
78
|
self._secure()
|
|
79
|
+
logger.CUSTOM_LOGGER.debug("Endpoints registered: %s", len(self.params))
|
|
80
80
|
|
|
81
81
|
def _verify_auth(
|
|
82
82
|
self,
|
|
@@ -95,19 +95,18 @@ class Authenticator:
|
|
|
95
95
|
Dict[str, str]:
|
|
96
96
|
A dictionary containing the redirect URL to the secure path.
|
|
97
97
|
"""
|
|
98
|
-
utils.verify_login(
|
|
98
|
+
session_token = utils.verify_login(
|
|
99
99
|
authorization=authorization,
|
|
100
100
|
request=request,
|
|
101
|
-
env_username=self.username,
|
|
102
|
-
env_password=self.password,
|
|
103
101
|
)
|
|
104
|
-
destination
|
|
105
|
-
|
|
106
|
-
|
|
102
|
+
if destination := request.cookies.get("X-Requested-By"):
|
|
103
|
+
logger.CUSTOM_LOGGER.info(
|
|
104
|
+
"Setting session timeout for %s seconds", self.timeout
|
|
105
|
+
)
|
|
107
106
|
# Set session_token cookie with a timeout, to be used for session validation when redirected
|
|
108
107
|
response.set_cookie(
|
|
109
108
|
key="session_token",
|
|
110
|
-
value=
|
|
109
|
+
value=session_token,
|
|
111
110
|
httponly=True,
|
|
112
111
|
samesite="strict",
|
|
113
112
|
max_age=self.timeout,
|
|
@@ -118,7 +117,7 @@ class Authenticator:
|
|
|
118
117
|
args=(request.client.host,),
|
|
119
118
|
interval=self.timeout,
|
|
120
119
|
).start()
|
|
121
|
-
return {"redirect_url":
|
|
120
|
+
return {"redirect_url": destination}
|
|
122
121
|
raise HTTPException(
|
|
123
122
|
status_code=status.HTTP_417_EXPECTATION_FAILED,
|
|
124
123
|
detail="Unable to find secure route for the requested path.\n"
|
|
@@ -154,14 +153,14 @@ class Authenticator:
|
|
|
154
153
|
secure_route = APIWebSocketRoute(
|
|
155
154
|
path=param.path,
|
|
156
155
|
endpoint=param.function,
|
|
157
|
-
dependencies=[Depends(utils.
|
|
156
|
+
dependencies=[Depends(utils.verify_session)],
|
|
158
157
|
)
|
|
159
158
|
else:
|
|
160
159
|
secure_route = APIRoute(
|
|
161
160
|
path=param.path,
|
|
162
161
|
endpoint=param.function,
|
|
163
162
|
methods=["GET"],
|
|
164
|
-
dependencies=[Depends(utils.
|
|
163
|
+
dependencies=[Depends(utils.verify_session)],
|
|
165
164
|
)
|
|
166
165
|
self.app.routes.append(secure_route)
|
|
167
166
|
self.app.routes.extend([login_route, session_route, verify_route, error_route])
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<html lang="en">
|
|
4
4
|
<head>
|
|
5
5
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
|
6
|
-
<title>FastAPI
|
|
6
|
+
<title>FastAPI UI Authentication</title>
|
|
7
7
|
<meta property="og:type" content="Authenticator">
|
|
8
8
|
<meta name="keywords" content="Python, fastapi, JavaScript, HTML, CSS">
|
|
9
9
|
<meta name="author" content="Vignesh Rao">
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<html lang="en">
|
|
3
3
|
<head>
|
|
4
4
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
|
5
|
-
<title>FastAPI
|
|
5
|
+
<title>FastAPI UI Authentication</title>
|
|
6
6
|
<meta property="og:type" content="Authenticator">
|
|
7
7
|
<meta name="keywords" content="Python, fastapi, JavaScript, HTML, CSS">
|
|
8
8
|
<meta name="author" content="Vignesh Rao">
|
|
@@ -79,8 +79,8 @@
|
|
|
79
79
|
</body>
|
|
80
80
|
<footer>
|
|
81
81
|
<div class="footer">
|
|
82
|
-
|
|
83
|
-
<a href="https://github.com/thevickypedia/
|
|
82
|
+
FastAPI-UI-Auth - {{ version }}<br>
|
|
83
|
+
<a href="https://github.com/thevickypedia/FastAPI-UI-Auth">https://github.com/thevickypedia/FastAPI-UI-Auth</a>
|
|
84
84
|
</div>
|
|
85
85
|
</footer>
|
|
86
86
|
</html>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<html lang="en">
|
|
3
3
|
<head>
|
|
4
4
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
|
5
|
-
<title>FastAPI
|
|
5
|
+
<title>FastAPI UI Authentication</title>
|
|
6
6
|
<meta property="og:type" content="Authenticator">
|
|
7
7
|
<meta name="keywords" content="Python, fastapi, JavaScript, HTML, CSS">
|
|
8
8
|
<meta name="author" content="Vignesh Rao">
|
|
@@ -79,8 +79,8 @@
|
|
|
79
79
|
</body>
|
|
80
80
|
<footer>
|
|
81
81
|
<div class="footer">
|
|
82
|
-
|
|
83
|
-
<a href="https://github.com/thevickypedia/
|
|
82
|
+
FastAPI-UI-Auth - {{ version }}<br>
|
|
83
|
+
<a href="https://github.com/thevickypedia/FastAPI-UI-Auth">https://github.com/thevickypedia/FastAPI-UI-Auth</a>
|
|
84
84
|
</div>
|
|
85
85
|
</footer>
|
|
86
86
|
</html>
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import logging
|
|
2
1
|
import secrets
|
|
3
|
-
from typing import
|
|
2
|
+
from typing import List, NoReturn
|
|
4
3
|
|
|
5
4
|
from fastapi import status
|
|
6
5
|
from fastapi.exceptions import HTTPException
|
|
@@ -9,9 +8,7 @@ from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse
|
|
|
9
8
|
from fastapi.security import HTTPAuthorizationCredentials
|
|
10
9
|
from fastapi.websockets import WebSocket
|
|
11
10
|
|
|
12
|
-
from
|
|
13
|
-
|
|
14
|
-
LOGGER = logging.getLogger("uvicorn.default")
|
|
11
|
+
from uiauth import enums, logger, models, secure
|
|
15
12
|
|
|
16
13
|
|
|
17
14
|
def failed_auth_counter(request: Request) -> None:
|
|
@@ -62,7 +59,7 @@ def raise_error(request: Request) -> NoReturn:
|
|
|
62
59
|
request: Request object containing client information.
|
|
63
60
|
"""
|
|
64
61
|
failed_auth_counter(request)
|
|
65
|
-
|
|
62
|
+
logger.CUSTOM_LOGGER.error(
|
|
66
63
|
"Incorrect username or password: %d",
|
|
67
64
|
models.ws_session.invalid[request.client.host],
|
|
68
65
|
)
|
|
@@ -73,17 +70,12 @@ def raise_error(request: Request) -> NoReturn:
|
|
|
73
70
|
)
|
|
74
71
|
|
|
75
72
|
|
|
76
|
-
def extract_credentials(
|
|
77
|
-
authorization: HTTPAuthorizationCredentials, host: str
|
|
78
|
-
) -> List[str]:
|
|
73
|
+
def extract_credentials(authorization: HTTPAuthorizationCredentials) -> List[str]:
|
|
79
74
|
"""Extract the credentials from ``Authorization`` headers and decode it before returning as a list of strings.
|
|
80
75
|
|
|
81
76
|
Args:
|
|
82
77
|
authorization: Authorization header from the request.
|
|
83
|
-
host: Host header from the request.
|
|
84
78
|
"""
|
|
85
|
-
if not authorization:
|
|
86
|
-
raise_error(host)
|
|
87
79
|
decoded_auth = secure.base64_decode(authorization.credentials)
|
|
88
80
|
# convert hex to a string
|
|
89
81
|
auth = secure.hex_decode(decoded_auth)
|
|
@@ -93,44 +85,40 @@ def extract_credentials(
|
|
|
93
85
|
def verify_login(
|
|
94
86
|
authorization: HTTPAuthorizationCredentials,
|
|
95
87
|
request: Request,
|
|
96
|
-
|
|
97
|
-
env_password: str,
|
|
98
|
-
) -> Dict[str, Union[str, int]]:
|
|
88
|
+
) -> str | NoReturn:
|
|
99
89
|
"""Verifies authentication and generates session token for each user.
|
|
100
90
|
|
|
101
91
|
Args:
|
|
102
92
|
authorization: Authorization header from the request.
|
|
103
93
|
request: Request object containing client information.
|
|
104
|
-
env_username: Environment variable for the username.
|
|
105
|
-
env_password: Environment variable for the password.
|
|
106
94
|
|
|
107
95
|
Returns:
|
|
108
|
-
|
|
109
|
-
Returns
|
|
96
|
+
str:
|
|
97
|
+
Returns the session token.
|
|
110
98
|
"""
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
)
|
|
114
|
-
if secrets.compare_digest(username, env_username):
|
|
115
|
-
hex_user = secure.hex_encode(env_username)
|
|
116
|
-
hex_pass = secure.hex_encode(env_password)
|
|
99
|
+
if authorization:
|
|
100
|
+
username, signature, timestamp = extract_credentials(authorization)
|
|
117
101
|
else:
|
|
118
|
-
|
|
102
|
+
raise_error(request)
|
|
103
|
+
if secrets.compare_digest(username, models.env.username):
|
|
104
|
+
hex_user = secure.hex_encode(models.env.username)
|
|
105
|
+
hex_pass = secure.hex_encode(models.env.password)
|
|
106
|
+
else:
|
|
107
|
+
logger.CUSTOM_LOGGER.warning("User '%s' not allowed", models.env.username)
|
|
119
108
|
raise_error(request)
|
|
120
109
|
message = f"{hex_user}{hex_pass}{timestamp}"
|
|
121
110
|
expected_signature = secure.calculate_hash(message)
|
|
122
111
|
if secrets.compare_digest(signature, expected_signature):
|
|
123
112
|
models.ws_session.invalid[request.client.host] = 0
|
|
124
113
|
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]
|
|
114
|
+
models.ws_session.client_auth[request.client.host] = key
|
|
115
|
+
return key
|
|
130
116
|
raise_error(request)
|
|
131
117
|
|
|
132
118
|
|
|
133
|
-
def
|
|
119
|
+
def verify_session(
|
|
120
|
+
api_request: Request = None, api_websocket: WebSocket = None
|
|
121
|
+
) -> None:
|
|
134
122
|
"""Check if the session is still valid.
|
|
135
123
|
|
|
136
124
|
Args:
|
|
@@ -150,18 +138,16 @@ def session_check(api_request: Request = None, api_websocket: WebSocket = None)
|
|
|
150
138
|
detail="Request or WebSocket connection is required for session check.",
|
|
151
139
|
)
|
|
152
140
|
session_token = request.cookies.get("session_token")
|
|
153
|
-
stored_token = models.ws_session.client_auth.get(request.client.host
|
|
154
|
-
"token"
|
|
155
|
-
)
|
|
141
|
+
stored_token = models.ws_session.client_auth.get(request.client.host)
|
|
156
142
|
if (
|
|
157
143
|
stored_token
|
|
158
144
|
and session_token
|
|
159
145
|
and secrets.compare_digest(session_token, stored_token)
|
|
160
146
|
):
|
|
161
|
-
|
|
147
|
+
logger.CUSTOM_LOGGER.info("Session is valid for host: %s", request.client.host)
|
|
162
148
|
return
|
|
163
149
|
elif not session_token:
|
|
164
|
-
|
|
150
|
+
logger.CUSTOM_LOGGER.warning(
|
|
165
151
|
"Session is invalid or expired for host: %s", request.client.host
|
|
166
152
|
)
|
|
167
153
|
raise models.RedirectException(
|
|
@@ -169,7 +155,7 @@ def session_check(api_request: Request = None, api_websocket: WebSocket = None)
|
|
|
169
155
|
destination=enums.APIEndpoints.fastapi_login,
|
|
170
156
|
)
|
|
171
157
|
else:
|
|
172
|
-
|
|
158
|
+
logger.CUSTOM_LOGGER.warning(
|
|
173
159
|
"Session token mismatch for host: %s. Expected: %s, Received: %s",
|
|
174
160
|
request.client.host,
|
|
175
161
|
stored_token,
|
|
@@ -205,6 +191,6 @@ def clear_session(host: str) -> None:
|
|
|
205
191
|
"""
|
|
206
192
|
if models.ws_session.client_auth.get(host):
|
|
207
193
|
models.ws_session.client_auth.pop(host)
|
|
208
|
-
|
|
194
|
+
logger.CUSTOM_LOGGER.info("Session cleared for host: %s", host)
|
|
209
195
|
else:
|
|
210
|
-
|
|
196
|
+
logger.CUSTOM_LOGGER.warning("No session found for host: %s", host)
|
uiauth/version.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
version = "0.2.0"
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
fastapi_ui_auth-0.1.0.dist-info/licenses/LICENSE,sha256=_sOIKJWdD2o1WwwDIwYB2qTP2nlSWqT5Tyg9jr1Xa4w,1070
|
|
2
|
-
fastapiauthenticator/__init__.py,sha256=H1tJUJp4FWweEu2JeMw5vmqjtMhMwR4kUd11ek8UmwQ,320
|
|
3
|
-
fastapiauthenticator/endpoints.py,sha256=PF2qu6XQ3MQStFEprRYYxiS6Dl6ukYaMqEkSlO-F3Ls,2104
|
|
4
|
-
fastapiauthenticator/enums.py,sha256=WO0eBv3l9HHr1I_ZXtAifCgdL-db_tZj9ka7jnjiS5k,547
|
|
5
|
-
fastapiauthenticator/models.py,sha256=GxmQfSvg70OTsvswJ3QFq_lxq-Yz1fIfzW6x8d4Sj40,2726
|
|
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
|
|
10
|
-
fastapiauthenticator/templates/index.html,sha256=mA2R6gk6lvibq_AmPgGHBFQijYtNUD7IIfeBSJWQrM4,9078
|
|
11
|
-
fastapiauthenticator/templates/session.html,sha256=LUCvcEdQOjfIXjRZ2gPx2s5wyzNuCve4OMge0hXaBLM,3053
|
|
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,,
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
fastapiauthenticator
|
fastapiauthenticator/__init__.py
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
from fastapiauthenticator.enums import APIEndpoints, APIMethods # noqa: F401,E402
|
|
2
|
-
from fastapiauthenticator.models import Parameters # noqa: F401,E402
|
|
3
|
-
from fastapiauthenticator.service import Authenticator # noqa: F401,E402
|
|
4
|
-
from fastapiauthenticator.version import version # noqa: F401,E402
|
|
5
|
-
|
|
6
|
-
protect = Authenticator
|
fastapiauthenticator/version.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
version = "0.1.0"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|