Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 15 additions & 13 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,25 @@ PYTHON := python
CONFIGS_DIG := config
TOML_CONFIG_MANAGER := $(CONFIGS_DIG)/toml_config_manager.py

.PHONY: guard-APP_ENV
guard-APP_ENV:
@if [ -z "$$APP_ENV" ]; then \
echo "APP_ENV is not set. Set APP_ENV before running this command."; \
exit 1; \
fi

.PHONY: env dotenv
env:
@echo APP_ENV=$(APP_ENV)

dotenv:
@$(PYTHON) $(TOML_CONFIG_MANAGER) ${APP_ENV}
dotenv: guard-APP_ENV
@$(PYTHON) $(TOML_CONFIG_MANAGER) $(APP_ENV)

# Docker compose
DOCKER_COMPOSE := docker compose
DOCKER_COMPOSE_PRUNE := scripts/makefile/docker_prune.sh

.PHONY: guard-APP_ENV up.db up.db-echo up up.echo down down.total logs.db shell.db prune
guard-APP_ENV:
ifndef APP_ENV
$(error "APP_ENV is not set. Set APP_ENV before running this command.")
endif

.PHONY: up.db up.db-echo up up.echo down down.total logs.db shell.db prune
up.db: guard-APP_ENV
@echo "APP_ENV=$(APP_ENV)"
@cd $(CONFIGS_DIG)/$(APP_ENV) && $(DOCKER_COMPOSE) --env-file .env.$(APP_ENV) up -d web_app_db_pg --build
Expand All @@ -28,11 +30,11 @@ up.db-echo: guard-APP_ENV
@echo "APP_ENV=$(APP_ENV)"
@cd $(CONFIGS_DIG)/$(APP_ENV) && $(DOCKER_COMPOSE) --env-file .env.$(APP_ENV) up web_app_db_pg --build

up:
up: guard-APP_ENV
@echo "APP_ENV=$(APP_ENV)"
@cd $(CONFIGS_DIG)/$(APP_ENV) && $(DOCKER_COMPOSE) --env-file .env.$(APP_ENV) up -d --build

up.echo:
up.echo: guard-APP_ENV
@echo "APP_ENV=$(APP_ENV)"
@cd $(CONFIGS_DIG)/$(APP_ENV) && $(DOCKER_COMPOSE) --env-file .env.$(APP_ENV) up --build

Expand All @@ -42,10 +44,10 @@ down: guard-APP_ENV
down.total: guard-APP_ENV
@cd $(CONFIGS_DIG)/$(APP_ENV) && $(DOCKER_COMPOSE) --env-file .env.$(APP_ENV) down -v

logs.db:
logs.db: guard-APP_ENV
@cd $(CONFIGS_DIG)/$(APP_ENV) && $(DOCKER_COMPOSE) --env-file .env.$(APP_ENV) logs -f web_app_db_pg

shell.db:
shell.db: guard-APP_ENV
@cd $(CONFIGS_DIG)/$(APP_ENV) && $(DOCKER_COMPOSE) --env-file .env.$(APP_ENV) exec web_app_db_pg sh

prune:
Expand Down Expand Up @@ -90,4 +92,4 @@ tree: pycache-del

# Dishka
plot-data:
python $(DISHKA_PLOT_DATA)
@$(PYTHON) $(DISHKA_PLOT_DATA)
7 changes: 1 addition & 6 deletions src/app/application/common/services/current_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,18 @@ def __init__(
self._identity_provider = identity_provider
self._user_command_gateway = user_command_gateway
self._access_revoker = access_revoker
self._cached_current_user: User | None = None

async def get_current_user(self) -> User:
"""
:raises AuthenticationError:
:raises DataMapperError:
:raises AuthorizationError:
"""
if self._cached_current_user is not None:
return self._cached_current_user

current_user_id = await self._identity_provider.get_current_user_id()
user: User | None = await self._user_command_gateway.read_by_id(current_user_id)
if user is None:
if user is None or not user.is_active:
log.warning("%s ID: %s.", AUTHZ_NO_CURRENT_USER, current_user_id)
await self._access_revoker.remove_all_user_access(current_user_id)
raise AuthorizationError(AUTHZ_NOT_AUTHORIZED)

self._cached_current_user = user
return user
2 changes: 1 addition & 1 deletion src/app/infrastructure/auth/adapters/access_revoker.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ async def remove_all_user_access(self, user_id: UserId) -> None:
"""
:raises DataMapperError:
"""
await self._auth_session_service.invalidate_all_sessions_for_user(user_id)
await self._auth_session_service.terminate_all_sessions_for_user(user_id)
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ async def commit(self) -> None:
"""
try:
await self._session.commit()
log.debug("%s. Auth session.", DB_COMMIT_DONE)
log.debug("%s Auth session.", DB_COMMIT_DONE)

except SQLAlchemyError as error:
raise DataMapperError(f"{DB_QUERY_FAILED} {DB_COMMIT_FAILED}") from error
3 changes: 2 additions & 1 deletion src/app/infrastructure/auth/handlers/log_in.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ async def execute(self, request_data: LogInRequest) -> None:
:raises DataMapperError:
:raises DomainFieldError:
:raises UserNotFoundByUsernameError:
:raises AuthenticationError:
"""
log.info("Log in: started. Username: '%s'.", request_data.username)

Expand All @@ -83,7 +84,7 @@ async def execute(self, request_data: LogInRequest) -> None:
if not user.is_active:
raise AuthenticationError(AUTH_ACCOUNT_INACTIVE)

await self._auth_session_service.create_session(user.id_)
await self._auth_session_service.issue_session(user.id_)

log.info(
"Log in: done. User, ID: '%s', username '%s', role '%s'.",
Expand Down
2 changes: 1 addition & 1 deletion src/app/infrastructure/auth/handlers/log_out.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,6 @@ async def execute(self) -> None:

log.info("Log out: user identified. User ID: '%s'.", current_user.id_)

await self._auth_session_service.invalidate_current_session()
await self._auth_session_service.terminate_current_session()

log.info("Log out: done. User ID: '%s'.", current_user.id_)
127 changes: 63 additions & 64 deletions src/app/infrastructure/auth/session/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,11 @@ def __init__(
self._auth_session_timer = auth_session_timer
self._cached_auth_session: AuthSession | None = None

async def create_session(self, user_id: UserId) -> None:
async def issue_session(self, user_id: UserId) -> None:
"""
:raises AuthenticationError:
"""
log.debug("Create auth session: started. User ID: '%s'.", user_id.value)
log.debug("Issue auth session: started. User ID: '%s'.", user_id.value)

auth_session_id: str = self._auth_session_id_generator()
expiration: datetime = self._auth_session_timer.auth_session_expiration
Expand All @@ -68,7 +68,7 @@ async def create_session(self, user_id: UserId) -> None:
self._auth_session_transport.deliver(auth_session)

log.debug(
"Create auth session: done. User ID: '%s', Auth session id: '%s'.",
"Issue auth session: done. User ID: '%s', Auth session ID: '%s'.",
user_id.value,
auth_session.id_,
)
Expand All @@ -79,111 +79,113 @@ async def get_authenticated_user_id(self) -> UserId:
"""
log.debug("Get authenticated user ID: started.")

raw_auth_session = await self._load_current_session()
raw_auth_session = await self._get_current_auth_session()
valid_auth_session = await self._validate_and_extend_session(raw_auth_session)
self._cached_auth_session = valid_auth_session

log.debug(
"Get authenticated user ID: done. Auth session ID: %s. User ID: %s.",
"Get authenticated user ID: done. Auth session ID: '%s'. User ID: '%s'.",
valid_auth_session.id_,
valid_auth_session.user_id.value,
)
return valid_auth_session.user_id

async def invalidate_current_session(self) -> None:
log.debug("Invalidate current session: started. Auth session ID: unknown.")
async def terminate_current_session(self) -> None:
log.debug("Terminate current session: started. Auth session ID: unknown.")

auth_session_id: str | None = self._auth_session_transport.extract_id()
if auth_session_id is None:
log.warning(
"Invalidate current session failed: partially failed. "
"Session ID can't be extracted from transport. "
"Auth session can't be identified.",
auth_session_id: str | None
if self._cached_auth_session is not None:
auth_session_id = self._cached_auth_session.id_
log.debug(
"Terminate current session: using ID from cache. "
"Auth session ID: '%s'.",
auth_session_id,
)
else:
auth_session_id = self._auth_session_transport.extract_id()
if auth_session_id is None:
log.warning(
"Terminate current session failed: partially failed. "
"Session ID can't be extracted from transport. "
"Auth session can't be identified.",
)
return
log.debug(
"Terminate current session: using ID from transport. "
"Auth session ID: '%s'.",
auth_session_id,
)
return

log.debug(
"Invalidate current session: in progress. Auth session id: %s.",
auth_session_id,
)

self._auth_session_transport.remove_current()

auth_session: AuthSession | None = None
try:
auth_session = await self._auth_session_gateway.read_by_id(auth_session_id)

except DataMapperError as error:
log.error("%s: '%s'", AUTH_SESSION_EXTRACTION_FAILED, error)

if auth_session is None:
log.warning(
"Invalidate current session failed: partially failed. "
"Session ID was removed from transport, "
"but auth session was not found in storage.",
)
return

try:
await self._auth_session_gateway.delete(auth_session.id_)
await self._auth_session_gateway.delete(auth_session_id)
await self._auth_transaction_manager.commit()
log.debug(
"Terminate current session: done (transport cleared, storage deleted). "
"Auth session ID: '%s'.",
auth_session_id,
)

except DataMapperError:
log.warning(
(
"Invalidate current session failed: partially failed. "
"Session ID was removed from transport, "
"but auth session was not deleted from storage. "
"Auth session ID: '%s'."
),
auth_session.id_,
"Terminate current session: partially failed "
"(transport cleared, storage delete failed). "
"Auth session ID: '%s'.",
auth_session_id,
)

async def invalidate_all_sessions_for_user(self, user_id: UserId) -> None:
self._cached_auth_session = None

async def terminate_all_sessions_for_user(self, user_id: UserId) -> None:
"""
:raises DataMapperError:
"""
log.debug(
"Invalidate all sessions for user: started. User id: '%s'.",
"Terminate all sessions for user: started. User ID: '%s'.",
user_id.value,
)

await self._auth_session_gateway.delete_all_for_user(user_id)
await self._auth_transaction_manager.commit()

if self._cached_auth_session and self._cached_auth_session.user_id == user_id:
self._auth_session_transport.remove_current()
self._cached_auth_session = None

log.debug(
"Invalidate all sessions for user: done. User id: '%s'.",
"Terminate all sessions for user: done. User ID: '%s'.",
user_id.value,
)

async def _load_current_session(self) -> AuthSession:
async def _get_current_auth_session(self) -> AuthSession:
"""
:raises AuthenticationError:
"""
log.debug("Load current auth session: started. Auth session id: unknown.")
log.debug("Get current auth session: started. Auth session ID: unknown.")

if self._cached_auth_session is not None:
cached_auth_session = self._cached_auth_session
log.debug(
"Load current auth session: done (from cache). Auth session id: %s.",
cached_auth_session.id_,
"Get current auth session: done (from cache). Auth session ID: '%s'.",
self._cached_auth_session.id_,
)
return cached_auth_session
return self._cached_auth_session

auth_session_id: str | None = self._auth_session_transport.extract_id()
if auth_session_id is None:
log.debug(AUTH_SESSION_NOT_FOUND)
raise AuthenticationError(AUTH_NOT_AUTHENTICATED)

log.debug(
"Load current auth session: in progress. Auth session id: %s.",
"Get current auth session: reading from storage. Auth session ID: '%s'.",
auth_session_id,
)

try:
auth_session: (
AuthSession | None
) = await self._auth_session_gateway.read_by_id(
auth_session_id,
)
) = await self._auth_session_gateway.read_by_id(auth_session_id)

except DataMapperError as error:
log.error("%s: '%s'", AUTH_SESSION_EXTRACTION_FAILED, error)
raise AuthenticationError(AUTH_NOT_AUTHENTICATED) from error
Expand All @@ -192,11 +194,8 @@ async def _load_current_session(self) -> AuthSession:
log.debug(AUTH_SESSION_NOT_FOUND)
raise AuthenticationError(AUTH_NOT_AUTHENTICATED)

self._cached_auth_session = auth_session

log.debug(
"Load current auth session: done. Auth session id: %s.",
auth_session.id_,
"Get current auth session: done. Auth session ID: '%s'.", auth_session.id_
)
return auth_session

Expand All @@ -208,7 +207,7 @@ async def _validate_and_extend_session(
:raises AuthenticationError:
"""
log.debug(
"Validate and extend auth session: started. Auth session id: %s.",
"Validate and extend auth session: started. Auth session ID: '%s'.",
auth_session.id_,
)

Expand All @@ -223,7 +222,7 @@ async def _validate_and_extend_session(
):
log.debug(
"Validate and extend auth session: validated without extension. "
"Auth session id: %s.",
"Auth session ID: '%s'.",
auth_session.id_,
)
return auth_session
Expand All @@ -242,10 +241,10 @@ async def _validate_and_extend_session(

self._auth_session_transport.deliver(auth_session)

self._cached_auth_session = auth_session

log.debug(
"Validate and extend auth session: done. Auth session id: %s.",
"Validate and extend auth session: done. "
"Auth session ID: '%s'. New expiration: '%s'.",
auth_session.id_,
auth_session.expiration.isoformat(),
)
return auth_session
8 changes: 4 additions & 4 deletions src/app/infrastructure/persistence_sqla/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ async def get_main_async_session(
async with async_session_factory() as session:
log.debug("Main async session started.")
yield cast(MainAsyncSession, session)
log.debug("Closing async session.")
log.debug("Closing Main async session.")
log.debug("Main async session closed.")


Expand All @@ -69,7 +69,7 @@ async def get_auth_async_session(
"""Provides UoW (AsyncSession) for the auth context."""
log.debug("Starting Auth async session...")
async with async_session_factory() as session:
log.debug("Async session started for Auth.")
log.debug("Auth async session started.")
yield cast(AuthAsyncSession, session)
log.debug("Closing async session.")
log.debug("Async session closed for Auth.")
log.debug("Closing Auth async session.")
log.debug("Auth async session closed.")
Loading