chore: update semantic contracts and git merge handling

This commit is contained in:
2026-03-16 20:34:28 +03:00
parent c53c3f77cc
commit 7e4124bc3f
19 changed files with 480 additions and 257 deletions

View File

@@ -1,5 +1,4 @@
# [DEF:backend.src.services.auth_service:Module]
#
# @COMPLEXITY: 5
# @SEMANTICS: auth, service, business-logic, login, jwt, adfs, jit-provisioning
# @PURPOSE: Orchestrates credential authentication and ADFS JIT user provisioning.
@@ -9,28 +8,29 @@
# @RELATION: [DEPENDS_ON] ->[backend.src.core.auth.jwt.create_access_token]
# @RELATION: [DEPENDS_ON] ->[backend.src.models.auth.User]
# @RELATION: [DEPENDS_ON] ->[backend.src.models.auth.Role]
#
# @INVARIANT: Authentication succeeds only for active users with valid credentials; issued sessions encode subject and scopes from assigned roles.
# @PRE: Core auth models and security utilities available.
# @POST: User identity verified and session tokens issued according to role scopes.
# @SIDE_EFFECT: Writes last login timestamps and JIT-provisions external users.
# @DATA_CONTRACT: [Credentials | ADFSClaims] -> [UserEntity | SessionToken]
# [SECTION: IMPORTS]
from typing import Dict, Any
from typing import Dict, Any, Optional, List
from datetime import datetime
from sqlalchemy.orm import Session
from ..models.auth import User, Role
from ..core.auth.repository import AuthRepository
from ..core.auth.security import verify_password
from ..core.auth.jwt import create_access_token
from ..core.auth.logger import log_security_event
from ..models.auth import User, Role
from ..core.logger import belief_scope
# [/SECTION]
# [DEF:AuthService:Class]
# @COMPLEXITY: 3
# @PURPOSE: Provides high-level authentication services.
class AuthService:
# [DEF:__init__:Function]
# [DEF:AuthService.__init__:Function]
# @COMPLEXITY: 1
# @PURPOSE: Initializes the authentication service with repository access over an active DB session.
# @PRE: db is a valid SQLAlchemy Session instance bound to the auth persistence context.
@@ -39,10 +39,11 @@ class AuthService:
# @DATA_CONTRACT: Input(Session) -> Model(AuthRepository)
# @PARAM: db (Session) - SQLAlchemy session.
def __init__(self, db: Session):
self.db = db
self.repo = AuthRepository(db)
# [/DEF:__init__:Function]
# [/DEF:AuthService.__init__:Function]
# [DEF:authenticate_user:Function]
# [DEF:AuthService.authenticate_user:Function]
# @COMPLEXITY: 3
# @PURPOSE: Validates credentials and account state for local username/password authentication.
# @PRE: username and password are non-empty credential inputs.
@@ -52,23 +53,24 @@ class AuthService:
# @PARAM: username (str) - The username.
# @PARAM: password (str) - The plain password.
# @RETURN: Optional[User] - The authenticated user or None.
def authenticate_user(self, username: str, password: str):
with belief_scope("AuthService.authenticate_user"):
def authenticate_user(self, username: str, password: str) -> Optional[User]:
with belief_scope("auth.authenticate_user"):
user = self.repo.get_user_by_username(username)
if not user:
if not user or not user.is_active:
return None
if not user.is_active:
return None
if not user.password_hash or not verify_password(password, user.password_hash):
if not verify_password(password, user.password_hash):
return None
self.repo.update_last_login(user)
# Update last login
user.last_login = datetime.utcnow()
self.db.commit()
self.db.refresh(user)
return user
# [/DEF:authenticate_user:Function]
# [/DEF:AuthService.authenticate_user:Function]
# [DEF:create_session:Function]
# [DEF:AuthService.create_session:Function]
# @COMPLEXITY: 3
# @PURPOSE: Issues an access token payload for an already authenticated user.
# @PRE: user is a valid User entity containing username and iterable roles with role.name values.
@@ -77,24 +79,16 @@ class AuthService:
# @DATA_CONTRACT: Input(User) -> Output(Dict[str, str]{access_token, token_type})
# @PARAM: user (User) - The authenticated user.
# @RETURN: Dict[str, str] - Session data.
def create_session(self, user) -> Dict[str, str]:
with belief_scope("AuthService.create_session"):
# Collect role names for scopes
scopes = [role.name for role in user.roles]
token_data = {
"sub": user.username,
"scopes": scopes
}
access_token = create_access_token(data=token_data)
return {
"access_token": access_token,
"token_type": "bearer"
}
# [/DEF:create_session:Function]
def create_session(self, user: User) -> Dict[str, str]:
with belief_scope("auth.create_session"):
roles = [role.name for role in user.roles]
access_token = create_access_token(
data={"sub": user.username, "scopes": roles}
)
return {"access_token": access_token, "token_type": "bearer"}
# [/DEF:AuthService.create_session:Function]
# [DEF:provision_adfs_user:Function]
# [DEF:AuthService.provision_adfs_user:Function]
# @COMPLEXITY: 3
# @PURPOSE: Performs ADFS Just-In-Time provisioning and role synchronization from AD group mappings.
# @PRE: user_info contains identity claims where at least one of 'upn' or 'email' is present; 'groups' may be absent.
@@ -104,32 +98,34 @@ class AuthService:
# @PARAM: user_info (Dict[str, Any]) - Claims from ADFS token.
# @RETURN: User - The provisioned user.
def provision_adfs_user(self, user_info: Dict[str, Any]) -> User:
with belief_scope("AuthService.provision_adfs_user"):
with belief_scope("auth.provision_adfs_user"):
username = user_info.get("upn") or user_info.get("email")
email = user_info.get("email")
ad_groups = user_info.get("groups", [])
groups = user_info.get("groups", [])
user = self.repo.get_user_by_username(username)
if not user:
user = User(
username=username,
email=email,
full_name=user_info.get("name"),
auth_source="ADFS",
is_active=True
is_active=True,
is_ad_user=True
)
self.repo.db.add(user)
# Update roles based on group mappings
from ..models.auth import ADGroupMapping
mapped_roles = self.repo.db.query(Role).join(ADGroupMapping).filter(
ADGroupMapping.ad_group.in_(ad_groups)
).all()
self.db.add(user)
log_security_event("USER_PROVISIONED", username, {"source": "ADFS"})
# Sync roles from AD groups
mapped_roles = self.repo.get_roles_by_ad_groups(groups)
user.roles = mapped_roles
self.repo.db.commit()
self.repo.db.refresh(user)
user.last_login = datetime.utcnow()
self.db.commit()
self.db.refresh(user)
return user
# [/DEF:provision_adfs_user:Function]
# [/DEF:AuthService.provision_adfs_user:Function]
# [/DEF:AuthService:Class]
# [/DEF:backend.src.services.auth_service:Module]