diff --git a/Containerfile b/Containerfile index dfd3160..1d0db97 100644 --- a/Containerfile +++ b/Containerfile @@ -7,8 +7,18 @@ COPY . /src # Sync the project into a new environment, using the frozen lockfile WORKDIR /src -RUN uv sync --frozen --no-cache && uv pip install --system . +RUN uv pip install --system . -#ENV PATH="/src/.venv/bin:$PATH" - -CMD ["oidc-test", "--port", "80"] +# Possible to run with: +#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" \ + ] diff --git a/deployment/README.md b/deployment/README.md new file mode 100644 index 0000000..4469588 --- /dev/null +++ b/deployment/README.md @@ -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 +``` diff --git a/deployment/systemd/oidc-fastapi-test.container b/deployment/systemd/oidc-fastapi-test.container new file mode 100644 index 0000000..f16d11d --- /dev/null +++ b/deployment/systemd/oidc-fastapi-test.container @@ -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 + diff --git a/deployment/systemd/oidc-fastapi-test.network b/deployment/systemd/oidc-fastapi-test.network new file mode 100644 index 0000000..2aa7cd7 --- /dev/null +++ b/deployment/systemd/oidc-fastapi-test.network @@ -0,0 +1,3 @@ +[Network] +NetworkName=oidc-fastapi-test +Label=app=oidc-fastapi-test diff --git a/deployment/systemd/oidc-fastapi-test.pod b/deployment/systemd/oidc-fastapi-test.pod new file mode 100644 index 0000000..df1571f --- /dev/null +++ b/deployment/systemd/oidc-fastapi-test.pod @@ -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 + diff --git a/pyproject.toml b/pyproject.toml index ca7b5eb..2ca396d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [project] -name = "fastapi-oidc-test" +name = "oidc-fastapi-test" version = "0.1.0" description = "Add your description here" readme = "README.md" diff --git a/src/oidc_test/main.py b/src/oidc_test/main.py index f2876bd..c5639f5 100644 --- a/src/oidc_test/main.py +++ b/src/oidc_test/main.py @@ -1,4 +1,9 @@ +""" +Test of OpenId Connect & OAuth2 with FastAPI +""" + from typing import Annotated +from pathlib import Path from datetime import datetime import logging from urllib.parse import urlencode @@ -30,7 +35,7 @@ from .database import db # logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) -templates = Jinja2Templates("src/templates") +templates = Jinja2Templates(Path(__file__).parent / "templates") 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, and a redirect to our own /auth/{oidc_provider_id} url """ + breakpoint() redirect_uri = request.url_for("auth", oidc_provider_id=oidc_provider_id) try: 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) request.session["token"] = userinfo["sub"] await db.add_token(token, user) - return RedirectResponse(url="/") + return RedirectResponse(url=request.url_for("home")) else: # Not sure if it's correct to redirect to plain login # if no userinfo is provided - redirect_uri = request.url_for("login", oidc_provider_id=oidc_provider_id) - return RedirectResponse(url=redirect_uri) + return RedirectResponse( + url=request.url_for("login", oidc_provider_id=oidc_provider_id) + ) @app.get("/non-compliant-logout") @@ -267,7 +274,7 @@ def main(): import sys from importlib.metadata import version - print(version("sms_handler")) + print(version("oidc-fastapi-test")) sys.exit(0) run(app, host=args.host, port=args.port) diff --git a/src/oidc_test/settings.py b/src/oidc_test/settings.py index c6050d7..fee105e 100644 --- a/src/oidc_test/settings.py +++ b/src/oidc_test/settings.py @@ -1,6 +1,8 @@ +from os import environ import string import random from typing import Type, Tuple +from pathlib import Path from pydantic import BaseModel, computed_field from pydantic_settings import ( @@ -56,7 +58,16 @@ class Settings(BaseSettings): init_settings, env_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, ) diff --git a/src/templates/base.html b/src/oidc_test/templates/base.html similarity index 98% rename from src/templates/base.html rename to src/oidc_test/templates/base.html index 774533c..d0934a0 100644 --- a/src/templates/base.html +++ b/src/oidc_test/templates/base.html @@ -9,6 +9,9 @@ h1 { text-align: center; } + .hidden { + display: none; + } .content { width: 100%; display: flex; diff --git a/src/templates/home.html b/src/oidc_test/templates/home.html similarity index 96% rename from src/templates/home.html rename to src/oidc_test/templates/home.html index c70a5fa..32b0ebe 100644 --- a/src/templates/home.html +++ b/src/oidc_test/templates/home.html @@ -46,7 +46,7 @@ Auth + foorole or barrole protected content Auth + barrole protected content Auth + foorole and barrole protected content - Using FastAPI Depends + Other {% if user_info_details %} diff --git a/src/templates/non_compliant_logout.html b/src/oidc_test/templates/non_compliant_logout.html similarity index 100% rename from src/templates/non_compliant_logout.html rename to src/oidc_test/templates/non_compliant_logout.html diff --git a/uv.lock b/uv.lock index fed951a..6c04ded 100644 --- a/uv.lock +++ b/uv.lock @@ -283,43 +283,6 @@ 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]] name = "h11" 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 }, ] +[[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]] name = "parso" version = "0.8.4"