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:
phil 2025-01-10 17:33:10 +01:00
parent 170e663ee8
commit 57681d91fe
12 changed files with 146 additions and 49 deletions

View file

@ -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
View 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>
```

View 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

View file

@ -0,0 +1,3 @@
[Network]
NetworkName=oidc-fastapi-test
Label=app=oidc-fastapi-test

View 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

View file

@ -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"

View file

@ -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)

View file

@ -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,
) )

View file

@ -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;

View file

@ -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
View file

@ -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"