diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..5ffb9d5 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,16 @@ +repos: + - repo: https://github.com/astral-sh/uv-pre-commit + # uv version. + rev: 0.9.8 + hooks: + - id: uv-lock + - id: uv-export + + - repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.14.4 + hooks: + # Run the linter. + - id: ruff-check + # Run the formatter. + - id: ruff-format diff --git a/pyproject.toml b/pyproject.toml index 97d7290..5e6b611 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ dependencies = [ "gunicorn>=23.0.0", "python-dotenv>=1.2.1", "requests>=2.32.5", - "authlib==1.3.0", + "authlib>=1.3.0", "flask-caching>=2.3.1", ] diff --git a/server.py b/server.py index 50fadb4..4407fc2 100644 --- a/server.py +++ b/server.py @@ -18,7 +18,6 @@ from datetime import datetime import dotenv from authlib.integrations.flask_client import OAuth from flask_caching import Cache -from werkzeug.middleware.proxy_fix import ProxyFix dotenv.load_dotenv() @@ -27,22 +26,23 @@ app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_prefix=1) app.secret_key = os.getenv("APP_SECRET_KEY", os.urandom(24)) # Cache Configuration -cache = Cache(app, config={'CACHE_TYPE': 'SimpleCache', 'CACHE_DEFAULT_TIMEOUT': 300}) +cache = Cache(app, config={"CACHE_TYPE": "SimpleCache", "CACHE_DEFAULT_TIMEOUT": 300}) # OAuth Configuration oauth = OAuth(app) oauth.register( - name='authentik', - server_metadata_url=os.getenv('AUTHENTIK_METADATA_URL'), - client_id=os.getenv('AUTHENTIK_CLIENT_ID'), - client_secret=os.getenv('AUTHENTIK_CLIENT_SECRET'), + name="authentik", + server_metadata_url=os.getenv("AUTHENTIK_METADATA_URL"), + client_id=os.getenv("AUTHENTIK_CLIENT_ID"), + client_secret=os.getenv("AUTHENTIK_CLIENT_SECRET"), client_kwargs={ - 'scope': 'openid profile email', - } + "scope": "openid profile email", + }, ) + def load_services(): - with open('services.json', 'r') as f: + with open("services.json", "r") as f: return json.load(f) @@ -78,6 +78,7 @@ def send_assets(path): return render_template("404.html"), 404 + @app.route("/services//.png") @cache.cached(timeout=3600, query_string=True) def service_images(category: str, service: str): @@ -88,10 +89,14 @@ def service_images(category: str, service: str): if "icon" in svc: # If the icon isn't a URL, try to serve it from the filesystem if not svc["icon"].startswith("http"): - icon_path = os.path.join("templates/assets/img/services", svc["icon"]) + icon_path = os.path.join( + "templates/assets/img/services", svc["icon"] + ) if os.path.isfile(icon_path): return make_response( - open(icon_path, "rb").read(), 200, {"Content-Type": "image/png"} + open(icon_path, "rb").read(), + 200, + {"Content-Type": "image/png"}, ) else: print(f"Icon file not found for {service} at {icon_path}") @@ -102,18 +107,20 @@ def service_images(category: str, service: str): req = requests.get(svc["icon"], timeout=5) if req.status_code == 200: return make_response( - req.content, 200, {"Content-Type": req.headers["Content-Type"]} + req.content, + 200, + {"Content-Type": req.headers["Content-Type"]}, ) except Exception as e: print(f"Failed to fetch icon for {service}: {e}") - - + # Read default favicon into memory to allow caching (pickling) with open("templates/assets/img/favicon.png", "rb") as f: return make_response(f.read(), 200, {"Content-Type": "image/png"}) return render_template("404.html"), 404 + # region Special routes @app.route("/favicon.png") def faviconPNG(): @@ -137,11 +144,13 @@ def wellknown(path): def index(): # Get current time in the format "dd MMM YYYY hh:mm AM/PM" current_datetime = datetime.now().strftime("%d %b %Y %I:%M %p") - + services = load_services() - user = session.get('user') - - return render_template("index.html", datetime=current_datetime, services=services, user=user) + user = session.get("user") + + return render_template( + "index.html", datetime=current_datetime, services=services, user=user + ) @app.route("/") @@ -205,34 +214,38 @@ def not_found(e): # endregion # region Auth routes -@app.route('/login') + +@app.route("/login") def login(): - redirect_uri = url_for('auth_callback', _external=True) + redirect_uri = url_for("auth_callback", _external=True) return oauth.authentik.authorize_redirect(redirect_uri) # type: ignore -@app.route('/auth/callback') +@app.route("/auth/callback") def auth_callback(): - token = oauth.authentik.authorize_access_token() # type: ignore - user = token.get('userinfo') + token = oauth.authentik.authorize_access_token() # type: ignore + user = token.get("userinfo") if user: - session['user'] = user - return redirect(url_for('index')) + session["user"] = user + return redirect(url_for("index")) -@app.route('/logout') +@app.route("/logout") def logout(): - session.pop('user', None) - return redirect(url_for('index')) + session.pop("user", None) + return redirect(url_for("index")) + # endregion # region Error handling + @app.errorhandler(InternalServerError) def handle_internal_server_error(e: InternalServerError): return render_template("500.html", message=e.original_exception), 500 + # endregion diff --git a/templates/404.html b/templates/404.html index acea536..e539207 100644 --- a/templates/404.html +++ b/templates/404.html @@ -4,7 +4,7 @@ - Nathan.Woodburn/ + Woodburn/ diff --git a/templates/500.html b/templates/500.html index edb9146..9d30986 100644 --- a/templates/500.html +++ b/templates/500.html @@ -4,7 +4,7 @@ - Nathan.Woodburn/ + Woodburn/ diff --git a/uv.lock b/uv.lock index 7817393..5197d3c 100644 --- a/uv.lock +++ b/uv.lock @@ -555,7 +555,7 @@ dev = [ [package.metadata] requires-dist = [ - { name = "authlib", specifier = "==1.3.0" }, + { name = "authlib", specifier = ">=1.3.0" }, { name = "flask", specifier = ">=3.1.2" }, { name = "flask-caching", specifier = ">=2.3.1" }, { name = "gunicorn", specifier = ">=23.0.0" },