commit 1e11c8ca5ea452a8869b9f2d69b7df13958a0137 Author: Waylon S. Walker Date: Fri Nov 21 12:50:29 2025 -0600 init diff --git a/justfile b/justfile new file mode 100644 index 0000000..a7e1dd6 --- /dev/null +++ b/justfile @@ -0,0 +1,21 @@ +start-auth: + ./main_auth.py & + +stop-auth: + pkill -f main_auth.py || true + +start-nginx: + docker run \ + --rm \ + -d \ + --name nginx \ + --network host \ + -v "$PWD/site":/usr/share/nginx/html:ro \ + -v "$PWD/nginx.conf":/etc/nginx/nginx.conf:ro \ + docker.io/library/nginx + +logs-nginx: + docker logs -f nginx + +stop-nginx: + docker stop nginx diff --git a/main_auth.py b/main_auth.py new file mode 100755 index 0000000..d98c564 --- /dev/null +++ b/main_auth.py @@ -0,0 +1,72 @@ +#!/usr/bin/env -S uv run --script +# /// script +# requires-python = ">=3.12" +# dependencies = [ +# "fastapi", +# "uvicorn[standard]", +# ] +# /// +from fastapi import FastAPI, Request, Response, HTTPException, Depends +from fastapi.responses import RedirectResponse, PlainTextResponse +from fastapi.staticfiles import StaticFiles +from fastapi.security import HTTPBasic, HTTPBasicCredentials +import secrets + +app = FastAPI() +security = HTTPBasic() + +USERS = { + "admin": {"password": "admin", "role": "admin"}, + "reader": {"password": "reader", "role": "reader"}, +} + +# Cookie format: session=username +def get_current_user(request: Request): + session = request.cookies.get("session") + if session and session in USERS: + return session + return None + +def get_current_role(user: str): + return USERS[user]["role"] + +@app.post("/login") +async def login(credentials: HTTPBasicCredentials = Depends(security)): + user = credentials.username + pwd = credentials.password + if user in USERS and secrets.compare_digest(USERS[user]['password'], pwd): + resp = Response("OK", status_code=200) + resp.set_cookie("session", user, httponly=True, samesite='lax', path="/") + # Ensure login response isn't cached + resp.headers["Cache-Control"] = "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0" + resp.headers["Pragma"] = "no-cache" + return resp + raise HTTPException(status_code=401, detail="Invalid credentials") + +@app.get("/logout") +def logout(): + resp = RedirectResponse("/") + resp.delete_cookie("session") + # Ensure logout response isn't cached + resp.headers["Cache-Control"] = "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0" + resp.headers["Pragma"] = "no-cache" + resp.headers["Expires"] = "Thu, 01 Jan 1970 00:00:00 GMT" + return resp + +@app.get("/authz") +def authz(request: Request): + session = request.cookies.get("session") + path = request.headers.get("X-Original-URI") + if not session or session not in USERS: + return Response("Not authenticated", status_code=401) + user_role = USERS[session]['role'] + # Only admin may access /admin + if path and path.startswith("/admin") and user_role != 'admin': + return Response("Forbidden", status_code=403) + # Everything else: allowed + return Response("OK", status_code=200) + +if __name__ == "__main__": + import uvicorn + app.mount("/static", StaticFiles(directory="static"), name="static") + uvicorn.run(app, host="localhost", port=5115) diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..e101ad5 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,90 @@ +worker_processes 1; +events { worker_connections 1024; } + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + server { + listen 8000; + server_name localhost; + + root /usr/share/nginx/html; + index index.html; + + # Custom error pages + error_page 403 /403/; + error_page 404 /404/; + + location / { + auth_request /authz; + error_page 401 = @login; # If not authed, redirect to login page + error_page 403 = @forbidden; # If forbidden, show custom 403 page + + # Disable all caching for demo purposes + add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0"; + add_header Pragma "no-cache"; + add_header Expires "Thu, 01 Jan 1970 00:00:00 GMT"; + + try_files $uri $uri/ @not_found; + } + location = /authz { + internal; + proxy_pass http://127.0.0.1:5115/authz; + proxy_set_header X-Original-URI $request_uri; + proxy_pass_request_body off; + proxy_set_header Content-Length ""; + } + location @login { + add_header Content-Type text/html; + return 302 http://localhost:8000/login/; + } + + location @forbidden { + add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0"; + add_header Pragma "no-cache"; + add_header Expires "Thu, 01 Jan 1970 00:00:00 GMT"; + rewrite ^.*$ /403/ last; + } + + + + location @not_found { + add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0"; + add_header Pragma "no-cache"; + add_header Expires "Thu, 01 Jan 1970 00:00:00 GMT"; + return 404; + } + + # Login page is public + location /login/ { + # Disable caching for login page too + add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0"; + add_header Pragma "no-cache"; + add_header Expires "Thu, 01 Jan 1970 00:00:00 GMT"; + + try_files $uri $uri/index.html =404; + } + + # Custom error pages are public and shouldn't be cached + location ~ ^/(403|404)/$ { + add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0"; + add_header Pragma "no-cache"; + add_header Expires "Thu, 01 Jan 1970 00:00:00 GMT"; + try_files $uri $uri/index.html =404; + } + # AJAX login: POST to FastAPI + location /login { + proxy_pass http://127.0.0.1:5115/login; + proxy_set_header Content-Type $content_type; + proxy_pass_request_body on; + } + location /logout { + proxy_pass http://127.0.0.1:5115/logout; + + # Ensure logout response isn't cached + add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0"; + add_header Pragma "no-cache"; + } + } +} diff --git a/site/403.html b/site/403.html new file mode 100644 index 0000000..8857c80 --- /dev/null +++ b/site/403.html @@ -0,0 +1,72 @@ + + + + + + 403 - Access Forbidden + + + +
+

403

+

Access Forbidden

+

+ You don't have permission to access this resource. + You may need to log in with appropriate credentials or your current user role doesn't have access to this page. +

+ +
+ + diff --git a/site/403/index.html b/site/403/index.html new file mode 100644 index 0000000..9f6bb16 --- /dev/null +++ b/site/403/index.html @@ -0,0 +1,33 @@ + + + + + + 403 Forbidden - nginx auth demo + + + +
+

403

+

Access Forbidden

+

Your current user role doesn't have access to this resource

+ +
+

🔒 nginx auth_request blocked this request

+

Try logging in with different credentials

+
+ + +
+ + diff --git a/site/404.html b/site/404.html new file mode 100644 index 0000000..b1e74a5 --- /dev/null +++ b/site/404.html @@ -0,0 +1,83 @@ + + + + + + 404 - Page Not Found + + + +
+

404

+

Page Not Found

+

+ The page you are looking for might have been removed, had its name changed, + or is temporarily unavailable. +

+
+ Available pages:
+ Home | + Admin Page | + Login +
+ +
+ + diff --git a/site/404/index.html b/site/404/index.html new file mode 100644 index 0000000..a221f5f --- /dev/null +++ b/site/404/index.html @@ -0,0 +1,30 @@ + + + + + + 404 Not Found - nginx auth demo + + + +
+

404

+

Page Not Found

+

The page you're looking for doesn't exist

+ +
+

Available pages:

+ +
+ +
+ Go Home + Login +
+
+ + diff --git a/site/admin.html b/site/admin.html new file mode 100644 index 0000000..2f12a6c --- /dev/null +++ b/site/admin.html @@ -0,0 +1,2 @@ +

Admin page

+

Only admins can see this page!

diff --git a/site/admin/index.html b/site/admin/index.html new file mode 100644 index 0000000..3d70ee8 --- /dev/null +++ b/site/admin/index.html @@ -0,0 +1,24 @@ + + + + + + Admin - nginx auth demo + + + +
+

Admin Page

+

Only admins can see this page!

+
+

🔒 This page is protected by nginx auth_request

+

Only users with "admin" role can access

+
+ +
+ Home + Logout +
+
+ + diff --git a/site/index.html b/site/index.html new file mode 100644 index 0000000..dbc85da --- /dev/null +++ b/site/index.html @@ -0,0 +1,27 @@ + + + + + + nginx auth demo + + + +
+

nginx auth demo

+

Everyone can see this page

+ + +
+ + diff --git a/site/login.html b/site/login.html new file mode 100644 index 0000000..5644fb8 --- /dev/null +++ b/site/login.html @@ -0,0 +1,63 @@ + + + + + Login + + + +

Login

+
+ + + + +
+ + + diff --git a/site/login/index.html b/site/login/index.html new file mode 100644 index 0000000..592dca2 --- /dev/null +++ b/site/login/index.html @@ -0,0 +1,62 @@ + + + + + + Login - nginx auth demo + + + +
+

Login

+

Enter your credentials

+ +
+
+ +
+
+ +
+ +
+ +
+

Demo credentials:

+

admin / admin (can access /admin)

+

reader / reader (cannot access /admin)

+
+ + Back to Home +
+ + + +