Run container with uvicorn, move templates for packaging, add systemd config for container deployment, add OIDC_TEST_SETTINGS_FILE env var for setting, misc fixes
This commit is contained in:
parent
170e663ee8
commit
57681d91fe
12 changed files with 146 additions and 49 deletions
|
@ -7,8 +7,18 @@ COPY . /src
|
||||||
# Sync the project into a new environment, using the frozen lockfile
|
# Sync the project into a new environment, using the frozen lockfile
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
|
|
||||||
RUN uv sync --frozen --no-cache && uv pip install --system .
|
RUN uv pip install --system .
|
||||||
|
|
||||||
#ENV PATH="/src/.venv/bin:$PATH"
|
# Possible to run with:
|
||||||
|
#CMD ["oidc-test", "--port", "80"]
|
||||||
CMD ["oidc-test", "--port", "80"]
|
#CMD ["fastapi", "run", "src/oidc_test/main.py", "--port", "8873", "--root-path", "/oidc-test"]
|
||||||
|
#
|
||||||
|
# Running uvicorn is most flexible:
|
||||||
|
CMD [ \
|
||||||
|
"uvicorn", \
|
||||||
|
"oidc_test.main:app", \
|
||||||
|
"--host", "0.0.0.0", \
|
||||||
|
"--port", "80", \
|
||||||
|
"--forwarded-allow-ips", "*", \
|
||||||
|
"--root-path", "/oidc-test" \
|
||||||
|
]
|
||||||
|
|
41
deployment/README.md
Normal file
41
deployment/README.md
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
# Deployment / installation
|
||||||
|
|
||||||
|
This documentation covers the specifics of Oidc-fastapi-test deployment, but
|
||||||
|
it does no cover application configuration details,
|
||||||
|
the set up of a public facing web server handling dns domain / https security.
|
||||||
|
|
||||||
|
*Oidc-fastapi-test* is shipped as containers for easy and effective installation,
|
||||||
|
and packages to deploy them for different environments.
|
||||||
|
|
||||||
|
There is 1 containers:
|
||||||
|
|
||||||
|
- oidc-fast-test (Python server)
|
||||||
|
|
||||||
|
Commands below assume that they are run from their respective directories
|
||||||
|
(`deployment/systemd`, `deployment/kubernetes`, ...).
|
||||||
|
|
||||||
|
## Systemd
|
||||||
|
|
||||||
|
With the help of *podman*, systemd can handle starting/restarting services, etc,
|
||||||
|
even with a non-privileged user.
|
||||||
|
|
||||||
|
The 1 container is in the same *pod*, as it is meant primarily for a lightweight
|
||||||
|
production environment, but this can be easily adapted.
|
||||||
|
|
||||||
|
The `deployment/systemd` directory contains files used for running
|
||||||
|
*Oidc-fastapi-test* with Systemd.
|
||||||
|
Podman is used to facilitate the creation of the relevant services.
|
||||||
|
|
||||||
|
### Deployment
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cp -r * $HOME/.config/containers/systemd/
|
||||||
|
systemctl --user daemon-reload
|
||||||
|
systemctl --user start oidc-fastapi-test-pod.service
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that starting on system boot requires the user to be enabled accordingly with:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo loginctl enable-linger <username>
|
||||||
|
```
|
11
deployment/systemd/oidc-fastapi-test.container
Normal file
11
deployment/systemd/oidc-fastapi-test.container
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
[Container]
|
||||||
|
ContainerName=oidc-fastapi-test
|
||||||
|
Image=code.philo.ydns.eu/philorg/oidc-fastapi-test:latest
|
||||||
|
Pod=oidc-fastapi-test.pod
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Restart=always
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
|
|
3
deployment/systemd/oidc-fastapi-test.network
Normal file
3
deployment/systemd/oidc-fastapi-test.network
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
[Network]
|
||||||
|
NetworkName=oidc-fastapi-test
|
||||||
|
Label=app=oidc-fastapi-test
|
11
deployment/systemd/oidc-fastapi-test.pod
Normal file
11
deployment/systemd/oidc-fastapi-test.pod
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
[Pod]
|
||||||
|
PodName=oidc-fastapi-test
|
||||||
|
Network=oidc-fastapi-test.network
|
||||||
|
PublishPort=8873:80
|
||||||
|
|
||||||
|
[Unit]
|
||||||
|
After=podman-user-wait-network-online.service
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
[project]
|
[project]
|
||||||
name = "fastapi-oidc-test"
|
name = "oidc-fastapi-test"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = "Add your description here"
|
description = "Add your description here"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
|
"""
|
||||||
|
Test of OpenId Connect & OAuth2 with FastAPI
|
||||||
|
"""
|
||||||
|
|
||||||
from typing import Annotated
|
from typing import Annotated
|
||||||
|
from pathlib import Path
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import logging
|
import logging
|
||||||
from urllib.parse import urlencode
|
from urllib.parse import urlencode
|
||||||
|
@ -30,7 +35,7 @@ from .database import db
|
||||||
# logging.basicConfig(level=logging.INFO)
|
# logging.basicConfig(level=logging.INFO)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
templates = Jinja2Templates("src/templates")
|
templates = Jinja2Templates(Path(__file__).parent / "templates")
|
||||||
|
|
||||||
|
|
||||||
app = FastAPI(
|
app = FastAPI(
|
||||||
|
@ -79,6 +84,7 @@ async def login(request: Request, oidc_provider_id: str) -> RedirectResponse:
|
||||||
auth token that only we can decode and contains userinfo,
|
auth token that only we can decode and contains userinfo,
|
||||||
and a redirect to our own /auth/{oidc_provider_id} url
|
and a redirect to our own /auth/{oidc_provider_id} url
|
||||||
"""
|
"""
|
||||||
|
breakpoint()
|
||||||
redirect_uri = request.url_for("auth", oidc_provider_id=oidc_provider_id)
|
redirect_uri = request.url_for("auth", oidc_provider_id=oidc_provider_id)
|
||||||
try:
|
try:
|
||||||
provider_: StarletteOAuth2App = getattr(authlib_oauth, oidc_provider_id)
|
provider_: StarletteOAuth2App = getattr(authlib_oauth, oidc_provider_id)
|
||||||
|
@ -120,12 +126,13 @@ async def auth(request: Request, oidc_provider_id: str) -> RedirectResponse:
|
||||||
user = await db.add_user(sub, user_info=userinfo, oidc_provider=oidc_provider)
|
user = await db.add_user(sub, user_info=userinfo, oidc_provider=oidc_provider)
|
||||||
request.session["token"] = userinfo["sub"]
|
request.session["token"] = userinfo["sub"]
|
||||||
await db.add_token(token, user)
|
await db.add_token(token, user)
|
||||||
return RedirectResponse(url="/")
|
return RedirectResponse(url=request.url_for("home"))
|
||||||
else:
|
else:
|
||||||
# Not sure if it's correct to redirect to plain login
|
# Not sure if it's correct to redirect to plain login
|
||||||
# if no userinfo is provided
|
# if no userinfo is provided
|
||||||
redirect_uri = request.url_for("login", oidc_provider_id=oidc_provider_id)
|
return RedirectResponse(
|
||||||
return RedirectResponse(url=redirect_uri)
|
url=request.url_for("login", oidc_provider_id=oidc_provider_id)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/non-compliant-logout")
|
@app.get("/non-compliant-logout")
|
||||||
|
@ -267,7 +274,7 @@ def main():
|
||||||
import sys
|
import sys
|
||||||
from importlib.metadata import version
|
from importlib.metadata import version
|
||||||
|
|
||||||
print(version("sms_handler"))
|
print(version("oidc-fastapi-test"))
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
run(app, host=args.host, port=args.port)
|
run(app, host=args.host, port=args.port)
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
|
from os import environ
|
||||||
import string
|
import string
|
||||||
import random
|
import random
|
||||||
from typing import Type, Tuple
|
from typing import Type, Tuple
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from pydantic import BaseModel, computed_field
|
from pydantic import BaseModel, computed_field
|
||||||
from pydantic_settings import (
|
from pydantic_settings import (
|
||||||
|
@ -56,7 +58,16 @@ class Settings(BaseSettings):
|
||||||
init_settings,
|
init_settings,
|
||||||
env_settings,
|
env_settings,
|
||||||
file_secret_settings,
|
file_secret_settings,
|
||||||
YamlConfigSettingsSource(settings_cls, "settings.yaml"),
|
YamlConfigSettingsSource(
|
||||||
|
settings_cls,
|
||||||
|
Path(
|
||||||
|
Path(
|
||||||
|
environ.get(
|
||||||
|
"OIDC_TEST_SETTINGS_FILE", Path.cwd() / "settings.yaml"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
dotenv_settings,
|
dotenv_settings,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,9 @@
|
||||||
h1 {
|
h1 {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
.content {
|
.content {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
|
@ -46,7 +46,7 @@
|
||||||
<a href="protected-by-foorole-or-barrole">Auth + foorole or barrole protected content</a>
|
<a href="protected-by-foorole-or-barrole">Auth + foorole or barrole protected content</a>
|
||||||
<a href="protected-by-barrole">Auth + barrole protected content</a>
|
<a href="protected-by-barrole">Auth + barrole protected content</a>
|
||||||
<a href="protected-by-foorole-and-barrole">Auth + foorole and barrole protected content</a>
|
<a href="protected-by-foorole-and-barrole">Auth + foorole and barrole protected content</a>
|
||||||
<a href="fast_api_depends">Using FastAPI Depends</a>
|
<a href="fast_api_depends" class="hidden">Using FastAPI Depends</a>
|
||||||
<a href="other">Other</a>
|
<a href="other">Other</a>
|
||||||
</div>
|
</div>
|
||||||
{% if user_info_details %}
|
{% if user_info_details %}
|
74
uv.lock
generated
74
uv.lock
generated
|
@ -283,43 +283,6 @@ standard = [
|
||||||
{ name = "uvicorn", extra = ["standard"] },
|
{ name = "uvicorn", extra = ["standard"] },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "fastapi-oidc-test"
|
|
||||||
version = "0.1.0"
|
|
||||||
source = { editable = "." }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "authlib" },
|
|
||||||
{ name = "cachetools" },
|
|
||||||
{ name = "fastapi", extra = ["standard"] },
|
|
||||||
{ name = "itsdangerous" },
|
|
||||||
{ name = "passlib", extra = ["bcrypt"] },
|
|
||||||
{ name = "pydantic-settings" },
|
|
||||||
{ name = "python-jose", extra = ["cryptography"] },
|
|
||||||
{ name = "requests" },
|
|
||||||
{ name = "sqlmodel" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.dev-dependencies]
|
|
||||||
dev = [
|
|
||||||
{ name = "ipdb" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.metadata]
|
|
||||||
requires-dist = [
|
|
||||||
{ name = "authlib", specifier = ">=1.4.0" },
|
|
||||||
{ name = "cachetools", specifier = ">=5.5.0" },
|
|
||||||
{ name = "fastapi", extras = ["standard"], specifier = ">=0.115.6" },
|
|
||||||
{ name = "itsdangerous", specifier = ">=2.2.0" },
|
|
||||||
{ name = "passlib", extras = ["bcrypt"], specifier = ">=1.7.4" },
|
|
||||||
{ name = "pydantic-settings", specifier = ">=2.7.1" },
|
|
||||||
{ name = "python-jose", extras = ["cryptography"], specifier = ">=3.3.0" },
|
|
||||||
{ name = "requests", specifier = ">=2.32.3" },
|
|
||||||
{ name = "sqlmodel", specifier = ">=0.0.22" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.metadata.requires-dev]
|
|
||||||
dev = [{ name = "ipdb", specifier = ">=0.13.13" }]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "h11"
|
name = "h11"
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
|
@ -508,6 +471,43 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 },
|
{ url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "oidc-fastapi-test"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = { editable = "." }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "authlib" },
|
||||||
|
{ name = "cachetools" },
|
||||||
|
{ name = "fastapi", extra = ["standard"] },
|
||||||
|
{ name = "itsdangerous" },
|
||||||
|
{ name = "passlib", extra = ["bcrypt"] },
|
||||||
|
{ name = "pydantic-settings" },
|
||||||
|
{ name = "python-jose", extra = ["cryptography"] },
|
||||||
|
{ name = "requests" },
|
||||||
|
{ name = "sqlmodel" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dev-dependencies]
|
||||||
|
dev = [
|
||||||
|
{ name = "ipdb" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.metadata]
|
||||||
|
requires-dist = [
|
||||||
|
{ name = "authlib", specifier = ">=1.4.0" },
|
||||||
|
{ name = "cachetools", specifier = ">=5.5.0" },
|
||||||
|
{ name = "fastapi", extras = ["standard"], specifier = ">=0.115.6" },
|
||||||
|
{ name = "itsdangerous", specifier = ">=2.2.0" },
|
||||||
|
{ name = "passlib", extras = ["bcrypt"], specifier = ">=1.7.4" },
|
||||||
|
{ name = "pydantic-settings", specifier = ">=2.7.1" },
|
||||||
|
{ name = "python-jose", extras = ["cryptography"], specifier = ">=3.3.0" },
|
||||||
|
{ name = "requests", specifier = ">=2.32.3" },
|
||||||
|
{ name = "sqlmodel", specifier = ">=0.0.22" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.metadata.requires-dev]
|
||||||
|
dev = [{ name = "ipdb", specifier = ">=0.13.13" }]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parso"
|
name = "parso"
|
||||||
version = "0.8.4"
|
version = "0.8.4"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue