GT AI OS Community v2.0.33 - Add NVIDIA NIM and Nemotron agents
- Updated python_coding_microproject.csv to use NVIDIA NIM Kimi K2 - Updated kali_linux_shell_simulator.csv to use NVIDIA NIM Kimi K2 - Made more general-purpose (flexible targets, expanded tools) - Added nemotron-mini-agent.csv for fast local inference via Ollama - Added nemotron-agent.csv for advanced reasoning via Ollama - Added wiki page: Projects for NVIDIA NIMs and Nemotron
This commit is contained in:
185
apps/control-panel-backend/app/api/internal/sessions.py
Normal file
185
apps/control-panel-backend/app/api/internal/sessions.py
Normal file
@@ -0,0 +1,185 @@
|
||||
"""
|
||||
Internal API for service-to-service session validation
|
||||
|
||||
OWASP/NIST Compliant Session Management (Issue #264):
|
||||
- Server-side session state is the authoritative source of truth
|
||||
- Called by tenant-backend on every authenticated request
|
||||
- Returns session status, warning signals, and expiry information
|
||||
"""
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Header
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import Session as SyncSession
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
|
||||
from app.core.database import get_db, get_sync_db
|
||||
from app.services.session_service import SessionService
|
||||
from app.core.config import settings
|
||||
|
||||
router = APIRouter(prefix="/internal/sessions", tags=["Internal Sessions"])
|
||||
|
||||
|
||||
async def verify_service_auth(
|
||||
x_service_auth: str = Header(None),
|
||||
x_service_name: str = Header(None)
|
||||
) -> bool:
|
||||
"""Verify service-to-service authentication"""
|
||||
|
||||
if not x_service_auth or not x_service_name:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Service authentication required"
|
||||
)
|
||||
|
||||
# Verify service token (in production, use proper service mesh auth)
|
||||
expected_token = settings.SERVICE_AUTH_TOKEN or "internal-service-token"
|
||||
if x_service_auth != expected_token:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid service authentication"
|
||||
)
|
||||
|
||||
# Verify service is allowed
|
||||
allowed_services = ["resource-cluster", "tenant-backend"]
|
||||
if x_service_name not in allowed_services:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=f"Service {x_service_name} not authorized"
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class SessionValidateRequest(BaseModel):
|
||||
"""Request body for session validation"""
|
||||
session_token: str
|
||||
|
||||
|
||||
class SessionValidateResponse(BaseModel):
|
||||
"""Response for session validation"""
|
||||
is_valid: bool
|
||||
expiry_reason: Optional[str] = None # 'idle' or 'absolute' if expired
|
||||
seconds_remaining: Optional[int] = None # Seconds until expiry
|
||||
show_warning: bool = False # True if < 5 minutes remaining
|
||||
user_id: Optional[int] = None
|
||||
tenant_id: Optional[int] = None
|
||||
|
||||
|
||||
class SessionRevokeRequest(BaseModel):
|
||||
"""Request body for session revocation"""
|
||||
session_token: str
|
||||
reason: str = "logout"
|
||||
|
||||
|
||||
class SessionRevokeResponse(BaseModel):
|
||||
"""Response for session revocation"""
|
||||
success: bool
|
||||
|
||||
|
||||
class SessionRevokeAllRequest(BaseModel):
|
||||
"""Request body for revoking all user sessions"""
|
||||
user_id: int
|
||||
reason: str = "password_change"
|
||||
|
||||
|
||||
class SessionRevokeAllResponse(BaseModel):
|
||||
"""Response for revoking all user sessions"""
|
||||
sessions_revoked: int
|
||||
|
||||
|
||||
@router.post("/validate", response_model=SessionValidateResponse)
|
||||
def validate_session(
|
||||
request: SessionValidateRequest,
|
||||
db: SyncSession = Depends(get_sync_db),
|
||||
authorized: bool = Depends(verify_service_auth)
|
||||
):
|
||||
"""
|
||||
Validate a session and return status information.
|
||||
|
||||
Called by tenant-backend on every authenticated request.
|
||||
|
||||
Returns:
|
||||
- is_valid: Whether the session is currently valid
|
||||
- expiry_reason: 'idle' or 'absolute' if expired
|
||||
- seconds_remaining: Time until expiry (min of idle and absolute)
|
||||
- show_warning: True if warning should be shown (< 30 min until absolute timeout)
|
||||
- user_id, tenant_id: Session context if valid
|
||||
"""
|
||||
session_service = SessionService(db)
|
||||
|
||||
is_valid, expiry_reason, seconds_remaining, session_info = session_service.validate_session(
|
||||
request.session_token
|
||||
)
|
||||
|
||||
# If valid, update activity timestamp
|
||||
if is_valid:
|
||||
session_service.update_activity(request.session_token)
|
||||
|
||||
# Warning is based on ABSOLUTE timeout only (not idle)
|
||||
# because polling keeps idle from expiring when browser is open
|
||||
show_warning = False
|
||||
if is_valid and session_info:
|
||||
absolute_seconds = session_info.get('absolute_seconds_remaining')
|
||||
if absolute_seconds is not None:
|
||||
show_warning = session_service.should_show_warning(absolute_seconds)
|
||||
|
||||
return SessionValidateResponse(
|
||||
is_valid=is_valid,
|
||||
expiry_reason=expiry_reason,
|
||||
seconds_remaining=seconds_remaining,
|
||||
show_warning=show_warning,
|
||||
user_id=session_info.get('user_id') if session_info else None,
|
||||
tenant_id=session_info.get('tenant_id') if session_info else None
|
||||
)
|
||||
|
||||
|
||||
@router.post("/revoke", response_model=SessionRevokeResponse)
|
||||
def revoke_session(
|
||||
request: SessionRevokeRequest,
|
||||
db: SyncSession = Depends(get_sync_db),
|
||||
authorized: bool = Depends(verify_service_auth)
|
||||
):
|
||||
"""
|
||||
Revoke a session (e.g., on logout).
|
||||
|
||||
Called by tenant-backend or control-panel-backend when user logs out.
|
||||
"""
|
||||
session_service = SessionService(db)
|
||||
success = session_service.revoke_session(request.session_token, request.reason)
|
||||
|
||||
return SessionRevokeResponse(success=success)
|
||||
|
||||
|
||||
@router.post("/revoke-all", response_model=SessionRevokeAllResponse)
|
||||
def revoke_all_user_sessions(
|
||||
request: SessionRevokeAllRequest,
|
||||
db: SyncSession = Depends(get_sync_db),
|
||||
authorized: bool = Depends(verify_service_auth)
|
||||
):
|
||||
"""
|
||||
Revoke all sessions for a user.
|
||||
|
||||
Called on password change, account lockout, etc.
|
||||
"""
|
||||
session_service = SessionService(db)
|
||||
count = session_service.revoke_all_user_sessions(request.user_id, request.reason)
|
||||
|
||||
return SessionRevokeAllResponse(sessions_revoked=count)
|
||||
|
||||
|
||||
@router.post("/cleanup")
|
||||
def cleanup_expired_sessions(
|
||||
db: SyncSession = Depends(get_sync_db),
|
||||
authorized: bool = Depends(verify_service_auth)
|
||||
):
|
||||
"""
|
||||
Clean up expired sessions.
|
||||
|
||||
This endpoint can be called by a scheduled task to mark expired sessions
|
||||
as inactive. Not strictly required (validation does this anyway) but
|
||||
helps keep the database clean.
|
||||
"""
|
||||
session_service = SessionService(db)
|
||||
count = session_service.cleanup_expired_sessions()
|
||||
|
||||
return {"sessions_cleaned": count}
|
||||
Reference in New Issue
Block a user