Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dd0997679f | ||
|
|
e2226cd923 | ||
|
|
7bf3c76a1b |
@@ -47,7 +47,7 @@ When changes are ready for deployment:
|
|||||||
5. **CI/CD triggers automatically**:
|
5. **CI/CD triggers automatically**:
|
||||||
- Gitea CI builds Docker image on new tag
|
- Gitea CI builds Docker image on new tag
|
||||||
- Watchtower pulls and deploys to production
|
- Watchtower pulls and deploys to production
|
||||||
- Verify deployment: `curl http://192.168.86.149:8000/health`
|
- Verify deployment: `curl http://192.168.86.149:8083/health`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,23 @@ 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/),
|
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).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [1.9.2] - 2026-01-04
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- `/auth/users/me` endpoint now supports both NPM forward auth headers AND JWT Bearer tokens
|
||||||
|
- Added `get_current_user_or_forward_auth()` combined auth dependency
|
||||||
|
- Fixes web authentication where NPM passes `X-authentik-*` headers instead of JWT
|
||||||
|
- Mobile/native clients continue to use JWT Bearer tokens as before
|
||||||
|
|
||||||
|
## [1.9.1] - 2026-01-03
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- CORS configuration now uses explicit origins instead of `"*"`
|
||||||
|
- When `allow_credentials=True`, wildcard origins are rejected by browsers
|
||||||
|
- Added `home.schweitz.net`, `tatlock.schweitz.net`, and localhost origins
|
||||||
|
|
||||||
## [1.9.0] - 2026-01-03
|
## [1.9.0] - 2026-01-03
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "core-api"
|
name = "core-api"
|
||||||
version = "1.9.0"
|
version = "1.9.2"
|
||||||
description = "Core Code API - Infrastructure management and tools API"
|
description = "Core Code API - Infrastructure management and tools API"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ from src.domains.auth.schemas import (
|
|||||||
ApiKeysListResponse,
|
ApiKeysListResponse,
|
||||||
)
|
)
|
||||||
from src.domains.auth.service import AuthService
|
from src.domains.auth.service import AuthService
|
||||||
from src.domains.auth.oidc import get_current_user
|
from src.domains.auth.oidc import get_current_user, get_current_user_or_forward_auth
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
@@ -326,7 +326,7 @@ class AuthController(BaseController):
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
async def get_current_user_profile(
|
async def get_current_user_profile(
|
||||||
user_claims: dict = Depends(get_current_user),
|
user_claims: dict = Depends(get_current_user_or_forward_auth),
|
||||||
session: AsyncSession = Depends(get_async_session),
|
session: AsyncSession = Depends(get_async_session),
|
||||||
) -> UserProfileResponse:
|
) -> UserProfileResponse:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -396,6 +396,56 @@ async def get_forward_auth_admin(
|
|||||||
return user
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
async def get_current_user_or_forward_auth(
|
||||||
|
request: Request,
|
||||||
|
credentials: Optional[HTTPAuthorizationCredentials] = Security(security)
|
||||||
|
) -> Dict:
|
||||||
|
"""
|
||||||
|
Combined auth: Try forward auth headers first, then JWT Bearer token.
|
||||||
|
|
||||||
|
Supports both:
|
||||||
|
- Web clients via NPM forward auth (X-authentik-* headers from proxy)
|
||||||
|
- Mobile/native clients via OIDC JWT Bearer tokens
|
||||||
|
|
||||||
|
This is the preferred dependency for /auth/users/me and similar endpoints
|
||||||
|
that need to work with both web (cookie-based via NPM) and mobile (token-based).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request: FastAPI request object containing headers
|
||||||
|
credentials: HTTP Bearer token from Authorization header
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
User claims dictionary with at minimum: sub, email, name, groups, auth_method
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
HTTPException 401: If neither forward auth headers nor valid JWT provided
|
||||||
|
"""
|
||||||
|
# 1. Try forward auth headers first (web via NPM)
|
||||||
|
username = request.headers.get("x-authentik-username")
|
||||||
|
email = request.headers.get("x-authentik-email")
|
||||||
|
|
||||||
|
if username and email:
|
||||||
|
# Forward auth headers present - use them
|
||||||
|
groups = request.headers.get("x-authentik-groups", "")
|
||||||
|
name = request.headers.get("x-authentik-name", username)
|
||||||
|
uid = request.headers.get("x-authentik-uid")
|
||||||
|
|
||||||
|
user_info = {
|
||||||
|
"sub": uid, # Use authentik UID as subject (for user lookup)
|
||||||
|
"email": email,
|
||||||
|
"preferred_username": username,
|
||||||
|
"name": name,
|
||||||
|
"groups": [g.strip() for g in groups.split(",")] if groups else [],
|
||||||
|
"auth_method": "forward_auth"
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(f"Authenticated via forward auth: {email}")
|
||||||
|
return user_info
|
||||||
|
|
||||||
|
# 2. Fall back to JWT Bearer token (mobile/native)
|
||||||
|
return await get_current_user(credentials)
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# Permission-Based Access Control
|
# Permission-Based Access Control
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|||||||
@@ -36,8 +36,15 @@ class Settings(BaseSettings):
|
|||||||
host: str = "0.0.0.0"
|
host: str = "0.0.0.0"
|
||||||
port: int = 8083
|
port: int = 8083
|
||||||
|
|
||||||
# CORS
|
# CORS - Note: When cors_credentials is True, cannot use "*" for origins
|
||||||
cors_origins: list[str] = ["*"]
|
# Set CORS_ORIGINS env var to override (comma-separated list)
|
||||||
|
cors_origins: list[str] = [
|
||||||
|
"https://home.schweitz.net",
|
||||||
|
"https://tatlock.schweitz.net",
|
||||||
|
"http://localhost:8080",
|
||||||
|
"http://localhost:3000",
|
||||||
|
"http://127.0.0.1:8080",
|
||||||
|
]
|
||||||
cors_credentials: bool = True
|
cors_credentials: bool = True
|
||||||
cors_methods: list[str] = ["*"]
|
cors_methods: list[str] = ["*"]
|
||||||
cors_headers: list[str] = ["*"]
|
cors_headers: list[str] = ["*"]
|
||||||
|
|||||||
Reference in New Issue
Block a user