diff --git a/CHANGELOG.md b/CHANGELOG.md index 15f7f71..2d48f86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.10.5] - 2026-01-07 + +### Fixed + +- **OIDC audience mismatch** - Accept tokens from multiple clients + - Changed `oidc_audience` (string) to `oidc_audiences` (list) + - Now accepts tokens with audience: `core-api`, `tatlock-ui`, or `tatlock` + - Fixes environment endpoint returning "default" user instead of authenticated username + ## [1.10.4] - 2026-01-07 ### Removed diff --git a/pyproject.toml b/pyproject.toml index d9385a9..11f8bb1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "core-api" -version = "1.10.4" +version = "1.10.5" description = "Core Code API - Infrastructure management and tools API" readme = "README.md" requires-python = ">=3.12" diff --git a/src/auth/oidc.py b/src/auth/oidc.py index f381c13..5108bf1 100644 --- a/src/auth/oidc.py +++ b/src/auth/oidc.py @@ -23,16 +23,16 @@ class OIDCConfig: # These will be set from environment variables in config.py self.enabled = False self.issuer = "" - self.audience = "" + self.audiences: list[str] = [] self.jwks_uri = "" - def configure(self, enabled: bool, issuer: str, audience: str): + def configure(self, enabled: bool, issuer: str, audiences: list[str]): """Configure OIDC settings""" self.enabled = enabled self.issuer = issuer - self.audience = audience + self.audiences = audiences self.jwks_uri = f"{issuer.rstrip('/')}/jwks/" - logger.info(f"OIDC configured: enabled={enabled}, issuer={issuer}") + logger.info(f"OIDC configured: enabled={enabled}, issuer={issuer}, audiences={audiences}") # Global OIDC config instance @@ -129,12 +129,12 @@ async def get_current_user( logger.warning(f"No matching key found for kid: {kid}") raise HTTPException(status_code=401, detail="Invalid token key") - # Verify and decode token + # Verify and decode token (accepts any of the configured audiences) payload = jwt.decode( token, rsa_key, algorithms=["RS256"], - audience=oidc_config.audience, + audience=oidc_config.audiences, issuer=oidc_config.issuer, ) diff --git a/src/config.py b/src/config.py index 041ca2d..7058d4d 100644 --- a/src/config.py +++ b/src/config.py @@ -84,7 +84,8 @@ class Settings(BaseSettings): # OIDC Authentication (Authentik) oidc_enabled: bool = False # Set to True to require authentication oidc_issuer: str = "https://auth.schweitz.net/application/o/core-api/" - oidc_audience: str = "core-api" + # Accept tokens from multiple clients (core-api, tatlock-ui, tatlock) + oidc_audiences: list[str] = ["core-api", "tatlock-ui", "tatlock"] # Authentik API (for token validation and user management) # Must use domain name (not IP) when AUTHENTIK_COOKIE_DOMAIN is set diff --git a/src/domains/auth/oidc.py b/src/domains/auth/oidc.py index 7e37b4a..a6bcd25 100644 --- a/src/domains/auth/oidc.py +++ b/src/domains/auth/oidc.py @@ -64,16 +64,16 @@ class OIDCConfig: # These will be set from environment variables in config.py self.enabled = False self.issuer = "" - self.audience = "" + self.audiences: list[str] = [] self.jwks_uri = "" - def configure(self, enabled: bool, issuer: str, audience: str): + def configure(self, enabled: bool, issuer: str, audiences: list[str]): """Configure OIDC settings""" self.enabled = enabled self.issuer = issuer - self.audience = audience + self.audiences = audiences self.jwks_uri = f"{issuer.rstrip('/')}/jwks/" - logger.info(f"OIDC configured: enabled={enabled}, issuer={issuer}") + logger.info(f"OIDC configured: enabled={enabled}, issuer={issuer}, audiences={audiences}") # Global OIDC config instance @@ -177,12 +177,12 @@ async def get_current_user( logger.warning(f"No matching key found for kid: {kid}") raise HTTPException(status_code=401, detail="Invalid token key") - # Verify and decode token + # Verify and decode token (accepts any of the configured audiences) payload = jwt.decode( token, rsa_key, algorithms=["RS256"], - audience=oidc_config.audience, + audience=oidc_config.audiences, issuer=oidc_config.issuer, ) diff --git a/src/shared/config.py b/src/shared/config.py index 719a21c..9c31e42 100644 --- a/src/shared/config.py +++ b/src/shared/config.py @@ -91,7 +91,8 @@ class Settings(BaseSettings): # OIDC Authentication (Authentik) oidc_enabled: bool = False oidc_issuer: str = "https://auth.schweitz.net/application/o/core-api/" - oidc_audience: str = "core-api" + # Accept tokens from multiple clients (core-api, tatlock-ui, tatlock) + oidc_audiences: list[str] = ["core-api", "tatlock-ui", "tatlock"] # Authentik API (for token validation and user management) authentik_url: str = "https://auth.schweitz.net" diff --git a/src/shared/security.py b/src/shared/security.py index 8f95b15..fe1f03d 100644 --- a/src/shared/security.py +++ b/src/shared/security.py @@ -24,13 +24,13 @@ def initialize_oidc(settings: Settings) -> None: auth_oidc_config.configure( enabled=settings.oidc_enabled, issuer=settings.oidc_issuer, - audience=settings.oidc_audience + audiences=settings.oidc_audiences ) domains_oidc_config.configure( enabled=settings.oidc_enabled, issuer=settings.oidc_issuer, - audience=settings.oidc_audience + audiences=settings.oidc_audiences ) if settings.oidc_enabled: