Added DB connection and started creating api calls for the pages
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user