mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-06-24 21:38:09 +02:00
feat(auth): add PAT fail-closed bootstrap allowlist
This commit is contained in:
parent
49b5247210
commit
2315b2f344
4 changed files with 14 additions and 18 deletions
|
|
@ -56,7 +56,7 @@ from app.routes import router as crud_router
|
|||
from app.routes.auth_routes import router as auth_router
|
||||
from app.schemas import UserCreate, UserRead, UserUpdate
|
||||
from app.session_events import register_session_hooks
|
||||
from app.users import SECRET, auth_backend, fastapi_users, get_auth_context
|
||||
from app.users import SECRET, allow_any_principal, auth_backend, fastapi_users
|
||||
from app.utils.perf import log_system_snapshot
|
||||
|
||||
_error_logger = logging.getLogger("surfsense.errors")
|
||||
|
|
@ -1033,7 +1033,7 @@ async def readiness_check():
|
|||
|
||||
@app.get("/verify-token")
|
||||
async def authenticated_route(
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
auth: AuthContext = Depends(allow_any_principal),
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
return {"message": "Token is valid", "method": auth.method}
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ from app.services.obsidian_plugin_indexer import (
|
|||
upsert_note,
|
||||
)
|
||||
from app.tasks.celery_tasks.obsidian_tasks import index_obsidian_attachment_task
|
||||
from app.users import get_auth_context
|
||||
from app.users import allow_any_principal, get_auth_context
|
||||
from app.utils.rbac import check_search_space_access
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -255,7 +255,7 @@ async def _ensure_search_space_access(
|
|||
|
||||
@router.get("/health", response_model=HealthResponse)
|
||||
async def obsidian_health(
|
||||
_auth: AuthContext = Depends(get_auth_context),
|
||||
_auth: AuthContext = Depends(allow_any_principal),
|
||||
) -> HealthResponse:
|
||||
"""Return the API contract handshake; plugin caches it per onload."""
|
||||
return HealthResponse(
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ from app.db import (
|
|||
SearchSpace,
|
||||
SearchSpaceMembership,
|
||||
SearchSpaceRole,
|
||||
User,
|
||||
get_async_session,
|
||||
get_default_roles_config,
|
||||
)
|
||||
|
|
@ -22,7 +21,7 @@ from app.schemas import (
|
|||
SearchSpaceUpdate,
|
||||
SearchSpaceWithStats,
|
||||
)
|
||||
from app.users import get_auth_context
|
||||
from app.users import allow_any_principal, get_auth_context, require_session_context
|
||||
from app.utils.rbac import check_permission, check_search_space_access
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -76,7 +75,7 @@ async def create_default_roles_and_membership(
|
|||
async def create_search_space(
|
||||
search_space: SearchSpaceCreate,
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
auth: AuthContext = Depends(require_session_context),
|
||||
):
|
||||
user = auth.user
|
||||
try:
|
||||
|
|
@ -111,7 +110,7 @@ async def read_search_spaces(
|
|||
limit: int = 200,
|
||||
owned_only: bool = False,
|
||||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
auth: AuthContext = Depends(allow_any_principal),
|
||||
):
|
||||
user = auth.user
|
||||
"""
|
||||
|
|
@ -209,7 +208,6 @@ async def read_search_space(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""
|
||||
Get a specific search space by ID.
|
||||
Requires SETTINGS_VIEW permission or membership.
|
||||
|
|
@ -243,7 +241,6 @@ async def update_search_space(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""
|
||||
Update a search space.
|
||||
Requires SETTINGS_UPDATE permission.
|
||||
|
|
@ -289,7 +286,6 @@ async def update_search_space_api_access(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""
|
||||
Toggle programmatic API/PAT access for a search space.
|
||||
Requires API_ACCESS_MANAGE permission.
|
||||
|
|
@ -373,7 +369,6 @@ async def delete_search_space(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""
|
||||
Delete a search space.
|
||||
Requires SETTINGS_DELETE permission (only owners have this by default).
|
||||
|
|
|
|||
|
|
@ -349,15 +349,16 @@ async def get_auth_context(
|
|||
return AuthContext.session(user)
|
||||
|
||||
|
||||
async def current_active_user(
|
||||
async def allow_any_principal(
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
) -> User:
|
||||
"""Compatibility wrapper for identity-only routes.
|
||||
) -> AuthContext:
|
||||
"""Allow either session or PAT principals for bootstrap probes only.
|
||||
|
||||
Do not use this for space-scoped authorization or session-grade account
|
||||
actions. Those should depend on get_auth_context or require_session_context.
|
||||
Routes using this dependency intentionally have no search-space gate.
|
||||
Adding a new call site is a security decision and must be covered by
|
||||
the fail-closed PAT allowlist test.
|
||||
"""
|
||||
return auth.user
|
||||
return auth
|
||||
|
||||
|
||||
async def require_session_context(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue