Added DB connection and started creating api calls for the pages

This commit is contained in:
gulimabr
2025-11-30 15:17:23 -03:00
parent b5381ae376
commit bbbe65067b
20 changed files with 1403 additions and 152 deletions

View File

@@ -1,8 +1,10 @@
from fastapi import HTTPException, status, Request
from keycloak.exceptions import KeycloakAuthenticationError, KeycloakPostError
from keycloak import KeycloakOpenID
from sqlalchemy.ext.asyncio import AsyncSession
from src.config import get_settings
from src.models import UserInfo
from src.repositories import UserRepository
import logging
logger = logging.getLogger(__name__)
@@ -21,9 +23,10 @@ def get_keycloak_openid():
class AuthService:
@staticmethod
def authenticate_user(keycode: str, request: Request) -> str:
def authenticate_user(keycode: str, request: Request) -> dict:
"""
Authenticate the user using Keycloak and return an access token.
Authenticate the user using Keycloak and return the full token response.
Returns the full token dict to allow access to the access_token.
"""
try:
# Use the same redirect_uri that was used in the login endpoint
@@ -46,7 +49,7 @@ class AuthService:
redirect_uri=redirect_uri,
)
logger.info("Token exchange successful")
return token["access_token"]
return token
except KeycloakAuthenticationError as exc:
logger.error(f"KeycloakAuthenticationError: {exc}")
raise HTTPException(
@@ -80,6 +83,7 @@ class AuthService:
status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token"
)
return UserInfo(
sub=user_info.get("sub"),
preferred_username=user_info["preferred_username"],
email=user_info.get("email"),
full_name=user_info.get("name"),
@@ -89,3 +93,64 @@ class AuthService:
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
) from exc
@staticmethod
def decode_token(token: str) -> dict:
"""
Decode the access token to extract claims without full verification.
Used to get the 'sub' claim for user provisioning.
"""
try:
keycloak_openid = get_keycloak_openid()
# Decode token - this validates the signature
token_info = keycloak_openid.decode_token(
token,
validate=True
)
return token_info
except Exception as exc:
logger.error(f"Error decoding token: {exc}")
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not decode token",
) from exc
class UserService:
"""Service for user-related operations."""
@staticmethod
async def provision_user_on_login(
token: str,
db: AsyncSession
) -> tuple[int, bool]:
"""
Provision a user in the database on first login (JIT provisioning).
Args:
token: The access token from Keycloak
db: Database session
Returns:
Tuple of (user_id, is_new_user)
"""
# Decode the token to get the 'sub' claim
token_info = AuthService.decode_token(token)
sub = token_info.get("sub")
if not sub:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Token does not contain 'sub' claim"
)
# Get or create the user
user_repo = UserRepository(db)
user, created = await user_repo.get_or_create_user(sub)
if created:
logger.info(f"New user provisioned: {sub} -> user_id: {user.id}")
else:
logger.debug(f"Existing user logged in: {sub} -> user_id: {user.id}")
return user.id, created