From 4e6bb7569ac39940c34ea9234cee52512e0cdff5 Mon Sep 17 00:00:00 2001 From: petrCher <88943157+petrCher@users.noreply.github.com> Date: Sat, 9 May 2026 11:44:34 +0300 Subject: [PATCH 01/15] sqladmin --- auth_backend/admin/admin.py | 23 +++++++++++++++++++++++ auth_backend/routes/base.py | 13 +++++++++++++ requirements.txt | 1 + 3 files changed, 37 insertions(+) create mode 100644 auth_backend/admin/admin.py diff --git a/auth_backend/admin/admin.py b/auth_backend/admin/admin.py new file mode 100644 index 00000000..fafe3614 --- /dev/null +++ b/auth_backend/admin/admin.py @@ -0,0 +1,23 @@ +from sqladmin import ModelView + +from auth_backend.models.db import Scope, Group, User + + +class ScopeAdmin(ModelView, model=Scope): + name = "Scope" + name_plural = "Scopes" + column_list = ["id", "name", "comment", "is_deleted", "create_ts"] + column_searchable_list = ["name", "comment"] + + +class GroupAdmin(ModelView, model=Group): + name = "Group" + name_plural = "Groups" + column_list = ["id", "name", "parent_id", "is_deleted", "create_ts"] + column_searchable_list = ["name"] + + +class UserAdmin(ModelView, model=User): + name = "User" + name_plural = "Users" + column_list = ["id", "is_deleted", "create_ts"] \ No newline at end of file diff --git a/auth_backend/routes/base.py b/auth_backend/routes/base.py index efff10ac..f8adbd94 100644 --- a/auth_backend/routes/base.py +++ b/auth_backend/routes/base.py @@ -4,6 +4,10 @@ from fastapi_sqlalchemy import DBSessionMiddleware from starlette.middleware.cors import CORSMiddleware +from sqladmin import Admin +from sqlalchemy import create_engine +from auth_backend.admin.admin import ScopeAdmin, GroupAdmin, UserAdmin + from auth_backend import __version__ from auth_backend.auth_method import AuthPluginMeta from auth_backend.kafka.kafka import get_kafka_producer @@ -23,6 +27,9 @@ async def lifespan(app: FastAPI): settings = get_settings() + +engine = create_engine(str(settings.DB_DSN)) + app = FastAPI( title='Сервис аутентификации и авторизации', description=( @@ -37,6 +44,8 @@ async def lifespan(app: FastAPI): lifespan=lifespan, ) +admin=Admin(app, engine=engine, title='Admin panel') + app.add_middleware( DBSessionMiddleware, db_url=str(settings.DB_DSN), @@ -51,6 +60,10 @@ async def lifespan(app: FastAPI): allow_headers=settings.CORS_ALLOW_HEADERS, ) +admin.add_view(GroupAdmin) +admin.add_view(ScopeAdmin) +admin.add_view(UserAdmin) + app.include_router(groups_router) app.include_router(scopes_router) app.include_router(user_router) diff --git a/requirements.txt b/requirements.txt index 5e444fe2..b5fc6c41 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,6 +16,7 @@ confluent-kafka event-schema-profcomff aiocache python-multipart +sqladmin[full] # Google Auth Method google-api-python-client From 9d1c1e928e7a6acb89fc1f9e43de01f6faf7b1db Mon Sep 17 00:00:00 2001 From: petrCher <88943157+petrCher@users.noreply.github.com> Date: Sat, 9 May 2026 12:09:19 +0300 Subject: [PATCH 02/15] sqladmin columns updated --- auth_backend/__init__.py | 1 - auth_backend/__main__.py | 1 - auth_backend/admin/admin.py | 8 ++++---- auth_backend/auth_method/__init__.py | 1 - auth_backend/auth_method/base.py | 1 - auth_backend/auth_method/oauth.py | 1 - auth_backend/auth_method/outer.py | 1 - auth_backend/auth_plugins/__init__.py | 1 - auth_backend/auth_plugins/airflow.py | 1 - auth_backend/auth_plugins/authentic.py | 1 - auth_backend/auth_plugins/coder.py | 1 - auth_backend/auth_plugins/email.py | 1 - auth_backend/auth_plugins/github.py | 1 - auth_backend/auth_plugins/google.py | 1 - auth_backend/auth_plugins/keycloak.py | 1 - auth_backend/auth_plugins/lkmsu.py | 1 - auth_backend/auth_plugins/mailu.py | 1 - auth_backend/auth_plugins/postgres.py | 1 - auth_backend/auth_plugins/telegram.py | 1 - auth_backend/auth_plugins/vk.py | 1 - auth_backend/auth_plugins/yandex.py | 1 - auth_backend/cli/process.py | 1 - auth_backend/kafka/kafka.py | 1 - auth_backend/models/__init__.py | 1 - auth_backend/models/db.py | 1 - auth_backend/routes/__init__.py | 1 - auth_backend/routes/base.py | 7 +++---- auth_backend/routes/groups.py | 1 - auth_backend/routes/oidc.py | 1 - auth_backend/routes/scopes.py | 1 - auth_backend/routes/user.py | 1 - auth_backend/routes/user_session.py | 1 - auth_backend/utils/jwt.py | 1 - auth_backend/utils/security.py | 1 - auth_backend/utils/smtp.py | 1 - auth_backend/utils/user_session_basics.py | 1 - auth_backend/utils/user_session_control.py | 1 - migrations/env.py | 1 - migrations/versions/2d29fc132e89_fix_base_users_group.py | 1 - migrations/versions/586cf0e784e5_scopes.py | 1 - migrations/versions/5d71a2a2405d_verified_user_group.py | 1 - migrations/versions/6c27352ee53b_unique_scopes.py | 1 - .../versions/6dffd8e42152_193_add_unbounded_sessions.py | 1 - migrations/versions/7c1cd7ceacd8_dynamic_option_model.py | 1 - migrations/versions/b07c0ca33c2b_groups.py | 1 - migrations/versions/bd7ac9cbdfc8_init.py | 1 - migrations/versions/bda218c91211_user_session_ts.py | 1 - .../versions/c03c6b509881_rollback_unique_scopes.py | 1 - .../versions/dcb89e72d446_session_security_scopes.py | 1 - migrations/versions/fa4691ad1054_email_delay.py | 1 - tests/test_routes/test_change_email.py | 1 - tests/test_routes/test_change_password.py | 1 - tests/test_routes/test_delete_last_auth_method.py | 1 - tests/test_routes/test_email_message_delay.py | 1 - tests/test_routes/test_login.py | 1 - tests/test_routes/test_logout.py | 1 - tests/test_routes/test_oidc.py | 1 - tests/test_routes/test_registration.py | 1 - tests/test_routes/test_user_sessions.py | 1 - tests/test_unit/test_jwt.py | 1 - tests/test_unit/test_update_user.py | 1 - 61 files changed, 7 insertions(+), 67 deletions(-) diff --git a/auth_backend/__init__.py b/auth_backend/__init__.py index fa9c0989..ebcaeac8 100644 --- a/auth_backend/__init__.py +++ b/auth_backend/__init__.py @@ -1,4 +1,3 @@ import os - __version__ = os.getenv('APP_VERSION', 'dev') diff --git a/auth_backend/__main__.py b/auth_backend/__main__.py index 6d8bd9fa..1cd7a947 100644 --- a/auth_backend/__main__.py +++ b/auth_backend/__main__.py @@ -1,5 +1,4 @@ from auth_backend.cli.process import process - if __name__ == '__main__': process() diff --git a/auth_backend/admin/admin.py b/auth_backend/admin/admin.py index fafe3614..d4fbee8e 100644 --- a/auth_backend/admin/admin.py +++ b/auth_backend/admin/admin.py @@ -1,23 +1,23 @@ from sqladmin import ModelView -from auth_backend.models.db import Scope, Group, User +from auth_backend.models.db import Group, Scope, User class ScopeAdmin(ModelView, model=Scope): name = "Scope" name_plural = "Scopes" - column_list = ["id", "name", "comment", "is_deleted", "create_ts"] + column_list = ["id", "name", "comment", "is_deleted"] column_searchable_list = ["name", "comment"] class GroupAdmin(ModelView, model=Group): name = "Group" name_plural = "Groups" - column_list = ["id", "name", "parent_id", "is_deleted", "create_ts"] + column_list = ["id", "name", "scopes", "users", "parent_id", "is_deleted"] column_searchable_list = ["name"] class UserAdmin(ModelView, model=User): name = "User" name_plural = "Users" - column_list = ["id", "is_deleted", "create_ts"] \ No newline at end of file + column_list = ["id", "scopes", "groups"] diff --git a/auth_backend/auth_method/__init__.py b/auth_backend/auth_method/__init__.py index fe1ccccf..142640d7 100644 --- a/auth_backend/auth_method/__init__.py +++ b/auth_backend/auth_method/__init__.py @@ -5,7 +5,6 @@ from .session import Session from .userdata_mixin import UserdataMixin - __all__ = [ "Session", "AUTH_METHODS", diff --git a/auth_backend/auth_method/base.py b/auth_backend/auth_method/base.py index 6dc58aa7..d204e534 100644 --- a/auth_backend/auth_method/base.py +++ b/auth_backend/auth_method/base.py @@ -12,7 +12,6 @@ from auth_backend.models.db import AuthMethod, User, UserSession from auth_backend.settings import get_settings - logger = logging.getLogger(__name__) settings = get_settings() diff --git a/auth_backend/auth_method/oauth.py b/auth_backend/auth_method/oauth.py index 961e2767..9adabfbf 100644 --- a/auth_backend/auth_method/oauth.py +++ b/auth_backend/auth_method/oauth.py @@ -15,7 +15,6 @@ from .method_mixins import LoginableMixin, RegistrableMixin from .userdata_mixin import UserdataMixin - logger = logging.getLogger(__name__) diff --git a/auth_backend/auth_method/outer.py b/auth_backend/auth_method/outer.py index 26b1ab02..e2ba485b 100644 --- a/auth_backend/auth_method/outer.py +++ b/auth_backend/auth_method/outer.py @@ -12,7 +12,6 @@ from auth_backend.models.db import AuthMethod, UserSession from auth_backend.utils.security import UnionAuth - logger = logging.getLogger(__name__) diff --git a/auth_backend/auth_plugins/__init__.py b/auth_backend/auth_plugins/__init__.py index 480835a1..0b648632 100644 --- a/auth_backend/auth_plugins/__init__.py +++ b/auth_backend/auth_plugins/__init__.py @@ -16,7 +16,6 @@ from .vk import VkAuth from .yandex import YandexAuth - __all__ = [ "AUTH_METHODS", "AuthPluginMeta", diff --git a/auth_backend/auth_plugins/airflow.py b/auth_backend/auth_plugins/airflow.py index 6988011f..be723cf3 100644 --- a/auth_backend/auth_plugins/airflow.py +++ b/auth_backend/auth_plugins/airflow.py @@ -6,7 +6,6 @@ from auth_backend.auth_method.outer import ConnectionIssue, OuterAuthMeta from auth_backend.settings import Settings - logger = logging.getLogger(__name__) diff --git a/auth_backend/auth_plugins/authentic.py b/auth_backend/auth_plugins/authentic.py index 003c81bd..d19724dd 100644 --- a/auth_backend/auth_plugins/authentic.py +++ b/auth_backend/auth_plugins/authentic.py @@ -20,7 +20,6 @@ from auth_backend.settings import Settings from auth_backend.utils.security import UnionAuth - AUTH_METHOD_ID_PARAM_NAME = 'user_id' logger = logging.getLogger(__name__) diff --git a/auth_backend/auth_plugins/coder.py b/auth_backend/auth_plugins/coder.py index 2c1ecef2..5a29cd5e 100644 --- a/auth_backend/auth_plugins/coder.py +++ b/auth_backend/auth_plugins/coder.py @@ -6,7 +6,6 @@ from auth_backend.auth_method.outer import ConnectionIssue, OuterAuthMeta from auth_backend.settings import Settings - logger = logging.getLogger(__name__) diff --git a/auth_backend/auth_plugins/email.py b/auth_backend/auth_plugins/email.py index abf0b3c2..799188aa 100644 --- a/auth_backend/auth_plugins/email.py +++ b/auth_backend/auth_plugins/email.py @@ -22,7 +22,6 @@ from auth_backend.utils.smtp import SendEmailMessage from auth_backend.utils.string import random_string - settings = get_settings() logger = logging.getLogger(__name__) diff --git a/auth_backend/auth_plugins/github.py b/auth_backend/auth_plugins/github.py index 126168a5..5e82525c 100644 --- a/auth_backend/auth_plugins/github.py +++ b/auth_backend/auth_plugins/github.py @@ -18,7 +18,6 @@ from auth_backend.settings import Settings from auth_backend.utils.security import UnionAuth - logger = logging.getLogger(__name__) diff --git a/auth_backend/auth_plugins/google.py b/auth_backend/auth_plugins/google.py index 686eaa3d..a5e97e24 100644 --- a/auth_backend/auth_plugins/google.py +++ b/auth_backend/auth_plugins/google.py @@ -20,7 +20,6 @@ from auth_backend.settings import Settings from auth_backend.utils.security import UnionAuth - logger = logging.getLogger(__name__) diff --git a/auth_backend/auth_plugins/keycloak.py b/auth_backend/auth_plugins/keycloak.py index 6759f8c9..81a2e5a3 100644 --- a/auth_backend/auth_plugins/keycloak.py +++ b/auth_backend/auth_plugins/keycloak.py @@ -18,7 +18,6 @@ from auth_backend.settings import Settings from auth_backend.utils.security import UnionAuth - logger = logging.getLogger(__name__) diff --git a/auth_backend/auth_plugins/lkmsu.py b/auth_backend/auth_plugins/lkmsu.py index 4b170daa..bc9b9950 100644 --- a/auth_backend/auth_plugins/lkmsu.py +++ b/auth_backend/auth_plugins/lkmsu.py @@ -20,7 +20,6 @@ from auth_backend.utils.security import UnionAuth from auth_backend.utils.string import concantenate_strings - logger = logging.getLogger(__name__) diff --git a/auth_backend/auth_plugins/mailu.py b/auth_backend/auth_plugins/mailu.py index cd1eab58..7f88f613 100644 --- a/auth_backend/auth_plugins/mailu.py +++ b/auth_backend/auth_plugins/mailu.py @@ -6,7 +6,6 @@ from auth_backend.auth_method.outer import ConnectionIssue, OuterAuthMeta from auth_backend.settings import Settings - logger = logging.getLogger(__name__) diff --git a/auth_backend/auth_plugins/postgres.py b/auth_backend/auth_plugins/postgres.py index 94def5ba..12f5873a 100644 --- a/auth_backend/auth_plugins/postgres.py +++ b/auth_backend/auth_plugins/postgres.py @@ -10,7 +10,6 @@ from auth_backend.auth_method import OuterAuthMeta from auth_backend.settings import Settings - logger = logging.getLogger(__name__) diff --git a/auth_backend/auth_plugins/telegram.py b/auth_backend/auth_plugins/telegram.py index ad45be0b..a421eecb 100644 --- a/auth_backend/auth_plugins/telegram.py +++ b/auth_backend/auth_plugins/telegram.py @@ -20,7 +20,6 @@ from auth_backend.utils.security import UnionAuth from auth_backend.utils.string import concantenate_strings - logger = logging.getLogger(__name__) diff --git a/auth_backend/auth_plugins/vk.py b/auth_backend/auth_plugins/vk.py index 7bb2bdd8..5d06e6f3 100644 --- a/auth_backend/auth_plugins/vk.py +++ b/auth_backend/auth_plugins/vk.py @@ -20,7 +20,6 @@ from ..schemas.types.scopes import Scope - logger = logging.getLogger(__name__) diff --git a/auth_backend/auth_plugins/yandex.py b/auth_backend/auth_plugins/yandex.py index 96f1239f..d1d81753 100644 --- a/auth_backend/auth_plugins/yandex.py +++ b/auth_backend/auth_plugins/yandex.py @@ -19,7 +19,6 @@ from auth_backend.utils.security import UnionAuth from auth_backend.utils.string import concantenate_strings - logger = logging.getLogger(__name__) diff --git a/auth_backend/cli/process.py b/auth_backend/cli/process.py index 1c8357d7..0db371ff 100644 --- a/auth_backend/cli/process.py +++ b/auth_backend/cli/process.py @@ -11,7 +11,6 @@ from .user import create_user from .user_group import create_user_group - settings = get_settings() engine = create_engine(str(settings.DB_DSN)) Session = sessionmaker(bind=engine) diff --git a/auth_backend/kafka/kafka.py b/auth_backend/kafka/kafka.py index 7437e9e3..e11c1c90 100644 --- a/auth_backend/kafka/kafka.py +++ b/auth_backend/kafka/kafka.py @@ -9,7 +9,6 @@ from auth_backend.kafka.kafkameta import KafkaMeta from auth_backend.settings import get_settings - log = logging.getLogger(__name__) diff --git a/auth_backend/models/__init__.py b/auth_backend/models/__init__.py index 0531fd11..6d382725 100644 --- a/auth_backend/models/__init__.py +++ b/auth_backend/models/__init__.py @@ -2,5 +2,4 @@ from .db import AuthMethod, User, UserSession from .dynamic_settings import DynamicOption - __all__ = ["Base", "User", "UserSession", "AuthMethod", "DynamicOption"] diff --git a/auth_backend/models/db.py b/auth_backend/models/db.py index 3cab9a2e..41b8ac37 100644 --- a/auth_backend/models/db.py +++ b/auth_backend/models/db.py @@ -15,7 +15,6 @@ from auth_backend.settings import get_settings from auth_backend.utils.user_session_basics import session_expires_date - settings = get_settings() logger = logging.getLogger(__name__) diff --git a/auth_backend/routes/__init__.py b/auth_backend/routes/__init__.py index 5e4fcb10..e53b88c8 100644 --- a/auth_backend/routes/__init__.py +++ b/auth_backend/routes/__init__.py @@ -1,5 +1,4 @@ from . import exc_handlers from .base import app - __all__ = ["app", "exc_handlers"] diff --git a/auth_backend/routes/base.py b/auth_backend/routes/base.py index f8adbd94..3d5056b2 100644 --- a/auth_backend/routes/base.py +++ b/auth_backend/routes/base.py @@ -2,13 +2,12 @@ from fastapi import FastAPI from fastapi_sqlalchemy import DBSessionMiddleware -from starlette.middleware.cors import CORSMiddleware - from sqladmin import Admin from sqlalchemy import create_engine -from auth_backend.admin.admin import ScopeAdmin, GroupAdmin, UserAdmin +from starlette.middleware.cors import CORSMiddleware from auth_backend import __version__ +from auth_backend.admin.admin import GroupAdmin, ScopeAdmin, UserAdmin from auth_backend.auth_method import AuthPluginMeta from auth_backend.kafka.kafka import get_kafka_producer from auth_backend.settings import get_settings @@ -44,7 +43,7 @@ async def lifespan(app: FastAPI): lifespan=lifespan, ) -admin=Admin(app, engine=engine, title='Admin panel') +admin = Admin(app, engine=engine, title='Admin panel') app.add_middleware( DBSessionMiddleware, diff --git a/auth_backend/routes/groups.py b/auth_backend/routes/groups.py index a9f9f429..ae1d9966 100644 --- a/auth_backend/routes/groups.py +++ b/auth_backend/routes/groups.py @@ -10,7 +10,6 @@ from auth_backend.schemas.models import Group, GroupGet, GroupPatch, GroupPost, GroupsGet from auth_backend.utils.security import UnionAuth - groups = APIRouter(prefix="/group", tags=["Groups"]) diff --git a/auth_backend/routes/oidc.py b/auth_backend/routes/oidc.py index 5ec56dc0..fda459c7 100644 --- a/auth_backend/routes/oidc.py +++ b/auth_backend/routes/oidc.py @@ -13,7 +13,6 @@ from auth_backend.utils.jwt import create_jwks from auth_backend.utils.oidc_token import OidcGrantType, token_by_client_credentials, token_by_refresh_token - settings = get_settings() router = APIRouter(prefix="/openid", tags=["OpenID"]) logger = logging.getLogger(__name__) diff --git a/auth_backend/routes/scopes.py b/auth_backend/routes/scopes.py index 4091290f..81039eb4 100644 --- a/auth_backend/routes/scopes.py +++ b/auth_backend/routes/scopes.py @@ -8,7 +8,6 @@ from auth_backend.schemas.models import ScopeGet, ScopePatch, ScopePost from auth_backend.utils.security import UnionAuth - scopes = APIRouter(prefix="/scope", tags=["Scopes"]) diff --git a/auth_backend/routes/user.py b/auth_backend/routes/user.py index 02c52940..0fc55999 100644 --- a/auth_backend/routes/user.py +++ b/auth_backend/routes/user.py @@ -23,7 +23,6 @@ ) from auth_backend.utils.security import UnionAuth - logger = logging.getLogger(__name__) user = APIRouter(prefix="/user", tags=["User"]) diff --git a/auth_backend/routes/user_session.py b/auth_backend/routes/user_session.py index 37927f73..ef844ff9 100644 --- a/auth_backend/routes/user_session.py +++ b/auth_backend/routes/user_session.py @@ -26,7 +26,6 @@ from auth_backend.utils import user_session_control from auth_backend.utils.security import UnionAuth - user_session = APIRouter(prefix="", tags=["User session"]) logger = logging.getLogger(__name__) diff --git a/auth_backend/utils/jwt.py b/auth_backend/utils/jwt.py index 9221910f..dc7bf9c4 100644 --- a/auth_backend/utils/jwt.py +++ b/auth_backend/utils/jwt.py @@ -12,7 +12,6 @@ from auth_backend.settings import get_settings - settings = get_settings() diff --git a/auth_backend/utils/security.py b/auth_backend/utils/security.py index aa3eb103..135a0892 100644 --- a/auth_backend/utils/security.py +++ b/auth_backend/utils/security.py @@ -12,7 +12,6 @@ from auth_backend.utils.user_session_basics import session_expires_date from auth_backend.utils.user_session_control import SESSION_UPDATE_SCOPE - settings = get_settings() diff --git a/auth_backend/utils/smtp.py b/auth_backend/utils/smtp.py index 5d9a305d..febd494f 100644 --- a/auth_backend/utils/smtp.py +++ b/auth_backend/utils/smtp.py @@ -12,7 +12,6 @@ from auth_backend.models.db import UserMessageDelay from auth_backend.settings import Settings, get_settings - logger = logging.getLogger(__name__) diff --git a/auth_backend/utils/user_session_basics.py b/auth_backend/utils/user_session_basics.py index 9bfb938d..b3136cb7 100644 --- a/auth_backend/utils/user_session_basics.py +++ b/auth_backend/utils/user_session_basics.py @@ -2,7 +2,6 @@ from auth_backend.settings import get_settings - settings = get_settings() diff --git a/auth_backend/utils/user_session_control.py b/auth_backend/utils/user_session_control.py index 4f3b5255..b03be2e2 100644 --- a/auth_backend/utils/user_session_control.py +++ b/auth_backend/utils/user_session_control.py @@ -13,7 +13,6 @@ from auth_backend.utils.string import random_string from auth_backend.utils.user_session_basics import session_expires_date - SESSION_UPDATE_SCOPE = 'auth.session.update' settings = get_settings() diff --git a/migrations/env.py b/migrations/env.py index 6fd58532..4316dfb2 100644 --- a/migrations/env.py +++ b/migrations/env.py @@ -6,7 +6,6 @@ from auth_backend.models.base import Base from auth_backend.settings import get_settings - # this is the Alembic Config object, which provides # access to the values within the .ini file in use. config = context.config diff --git a/migrations/versions/2d29fc132e89_fix_base_users_group.py b/migrations/versions/2d29fc132e89_fix_base_users_group.py index a6841202..851e50c3 100644 --- a/migrations/versions/2d29fc132e89_fix_base_users_group.py +++ b/migrations/versions/2d29fc132e89_fix_base_users_group.py @@ -12,7 +12,6 @@ from auth_backend.models.db import Group, User - # revision identifiers, used by Alembic. revision = '2d29fc132e89' down_revision = 'dcb89e72d446' diff --git a/migrations/versions/586cf0e784e5_scopes.py b/migrations/versions/586cf0e784e5_scopes.py index d3d65185..e4b8c0c9 100644 --- a/migrations/versions/586cf0e784e5_scopes.py +++ b/migrations/versions/586cf0e784e5_scopes.py @@ -9,7 +9,6 @@ import sqlalchemy as sa from alembic import op - # revision identifiers, used by Alembic. revision = '586cf0e784e5' down_revision = 'b07c0ca33c2b' diff --git a/migrations/versions/5d71a2a2405d_verified_user_group.py b/migrations/versions/5d71a2a2405d_verified_user_group.py index f80b69ab..74162cbf 100644 --- a/migrations/versions/5d71a2a2405d_verified_user_group.py +++ b/migrations/versions/5d71a2a2405d_verified_user_group.py @@ -9,7 +9,6 @@ import sqlalchemy as sa from alembic import op - # revision identifiers, used by Alembic. revision = '5d71a2a2405d' down_revision = '2d29fc132e89' diff --git a/migrations/versions/6c27352ee53b_unique_scopes.py b/migrations/versions/6c27352ee53b_unique_scopes.py index b3626953..972cf24c 100644 --- a/migrations/versions/6c27352ee53b_unique_scopes.py +++ b/migrations/versions/6c27352ee53b_unique_scopes.py @@ -7,7 +7,6 @@ from alembic import op - revision = '6c27352ee53b' down_revision = '586cf0e784e5' branch_labels = None diff --git a/migrations/versions/6dffd8e42152_193_add_unbounded_sessions.py b/migrations/versions/6dffd8e42152_193_add_unbounded_sessions.py index d2940e86..0c8d5d3c 100644 --- a/migrations/versions/6dffd8e42152_193_add_unbounded_sessions.py +++ b/migrations/versions/6dffd8e42152_193_add_unbounded_sessions.py @@ -9,7 +9,6 @@ import sqlalchemy as sa from alembic import op - # revision identifiers, used by Alembic. revision = '6dffd8e42152' down_revision = '2d29fc132e89' diff --git a/migrations/versions/7c1cd7ceacd8_dynamic_option_model.py b/migrations/versions/7c1cd7ceacd8_dynamic_option_model.py index 466c8e7f..96a9bb75 100644 --- a/migrations/versions/7c1cd7ceacd8_dynamic_option_model.py +++ b/migrations/versions/7c1cd7ceacd8_dynamic_option_model.py @@ -12,7 +12,6 @@ from auth_backend.models.db import Group, Scope, User - # revision identifiers, used by Alembic. revision = '7c1cd7ceacd8' down_revision = 'bda218c91211' diff --git a/migrations/versions/b07c0ca33c2b_groups.py b/migrations/versions/b07c0ca33c2b_groups.py index ecf93169..efccc85e 100644 --- a/migrations/versions/b07c0ca33c2b_groups.py +++ b/migrations/versions/b07c0ca33c2b_groups.py @@ -10,7 +10,6 @@ from alembic import op from sqlalchemy.dialects import postgresql - # revision identifiers, used by Alembic. revision = 'b07c0ca33c2b' down_revision = 'bd7ac9cbdfc8' diff --git a/migrations/versions/bd7ac9cbdfc8_init.py b/migrations/versions/bd7ac9cbdfc8_init.py index 74a6285d..9cc09b55 100644 --- a/migrations/versions/bd7ac9cbdfc8_init.py +++ b/migrations/versions/bd7ac9cbdfc8_init.py @@ -3,7 +3,6 @@ import sqlalchemy as sa from alembic import op - revision = 'bd7ac9cbdfc8' down_revision = None branch_labels = None diff --git a/migrations/versions/bda218c91211_user_session_ts.py b/migrations/versions/bda218c91211_user_session_ts.py index 564b3441..d69ec9d5 100644 --- a/migrations/versions/bda218c91211_user_session_ts.py +++ b/migrations/versions/bda218c91211_user_session_ts.py @@ -9,7 +9,6 @@ import sqlalchemy as sa from alembic import op - # revision identifiers, used by Alembic. revision = 'bda218c91211' down_revision = 'fa4691ad1054' diff --git a/migrations/versions/c03c6b509881_rollback_unique_scopes.py b/migrations/versions/c03c6b509881_rollback_unique_scopes.py index 8d3ac576..3c5b9098 100644 --- a/migrations/versions/c03c6b509881_rollback_unique_scopes.py +++ b/migrations/versions/c03c6b509881_rollback_unique_scopes.py @@ -8,7 +8,6 @@ from alembic import op - revision = 'c03c6b509881' down_revision = '6c27352ee53b' branch_labels = None diff --git a/migrations/versions/dcb89e72d446_session_security_scopes.py b/migrations/versions/dcb89e72d446_session_security_scopes.py index 033b8bea..fe6f050a 100644 --- a/migrations/versions/dcb89e72d446_session_security_scopes.py +++ b/migrations/versions/dcb89e72d446_session_security_scopes.py @@ -9,7 +9,6 @@ from alembic import op from sqlalchemy.sql import text - # revision identifiers, used by Alembic. revision = 'dcb89e72d446' down_revision = '7c1cd7ceacd8' diff --git a/migrations/versions/fa4691ad1054_email_delay.py b/migrations/versions/fa4691ad1054_email_delay.py index 90dd32c0..b34233f6 100644 --- a/migrations/versions/fa4691ad1054_email_delay.py +++ b/migrations/versions/fa4691ad1054_email_delay.py @@ -9,7 +9,6 @@ import sqlalchemy as sa from alembic import op - # revision identifiers, used by Alembic. revision = 'fa4691ad1054' down_revision = 'c03c6b509881' diff --git a/tests/test_routes/test_change_email.py b/tests/test_routes/test_change_email.py index b15e59e9..4e239807 100644 --- a/tests/test_routes/test_change_email.py +++ b/tests/test_routes/test_change_email.py @@ -6,7 +6,6 @@ from auth_backend.models.db import AuthMethod, UserSession - url = "/email/reset/email" diff --git a/tests/test_routes/test_change_password.py b/tests/test_routes/test_change_password.py index da6925a8..df03504f 100644 --- a/tests/test_routes/test_change_password.py +++ b/tests/test_routes/test_change_password.py @@ -5,7 +5,6 @@ from auth_backend.auth_plugins.email import Email from auth_backend.models.db import AuthMethod - url = "/email/reset/password" diff --git a/tests/test_routes/test_delete_last_auth_method.py b/tests/test_routes/test_delete_last_auth_method.py index a3e3836c..fe38781e 100644 --- a/tests/test_routes/test_delete_last_auth_method.py +++ b/tests/test_routes/test_delete_last_auth_method.py @@ -4,7 +4,6 @@ from auth_backend.exceptions import LastAuthMethodDelete from auth_backend.settings import get_settings - pytest_plugins = ('pytest_asyncio',) settings = get_settings() diff --git a/tests/test_routes/test_email_message_delay.py b/tests/test_routes/test_email_message_delay.py index ebc9b9de..9d6cd3a0 100644 --- a/tests/test_routes/test_email_message_delay.py +++ b/tests/test_routes/test_email_message_delay.py @@ -5,7 +5,6 @@ from auth_backend.models.db import AuthMethod, User from auth_backend.settings import get_settings - settings = get_settings() diff --git a/tests/test_routes/test_login.py b/tests/test_routes/test_login.py index 111e8242..c4e4fcce 100644 --- a/tests/test_routes/test_login.py +++ b/tests/test_routes/test_login.py @@ -6,7 +6,6 @@ from auth_backend.models.db import AuthMethod, Group, GroupScope, Scope, User, UserGroup, UserSession - url = "/email/login" diff --git a/tests/test_routes/test_logout.py b/tests/test_routes/test_logout.py index ae99a68c..9a979a34 100644 --- a/tests/test_routes/test_logout.py +++ b/tests/test_routes/test_logout.py @@ -6,7 +6,6 @@ from auth_backend.models.db import AuthMethod, User, UserSession - url = "/logout" diff --git a/tests/test_routes/test_oidc.py b/tests/test_routes/test_oidc.py index 3e9519ef..2037308d 100644 --- a/tests/test_routes/test_oidc.py +++ b/tests/test_routes/test_oidc.py @@ -7,7 +7,6 @@ from auth_backend.models.db import AuthMethod from auth_backend.settings import get_settings - settings = get_settings() diff --git a/tests/test_routes/test_registration.py b/tests/test_routes/test_registration.py index 6326c366..169f44a6 100644 --- a/tests/test_routes/test_registration.py +++ b/tests/test_routes/test_registration.py @@ -8,7 +8,6 @@ from auth_backend.models.db import AuthMethod, User, UserSession - url = "/email/registration" diff --git a/tests/test_routes/test_user_sessions.py b/tests/test_routes/test_user_sessions.py index 8fc934cb..09f4c2af 100644 --- a/tests/test_routes/test_user_sessions.py +++ b/tests/test_routes/test_user_sessions.py @@ -7,7 +7,6 @@ from auth_backend.models.db import Group, GroupScope, Scope, UserGroup, UserSession, UserSessionScope - logger = logging.getLogger(__name__) diff --git a/tests/test_unit/test_jwt.py b/tests/test_unit/test_jwt.py index 714b5cf7..8e694fc6 100644 --- a/tests/test_unit/test_jwt.py +++ b/tests/test_unit/test_jwt.py @@ -5,7 +5,6 @@ from auth_backend.settings import get_settings from auth_backend.utils.jwt import create_jwks, decode_jwt, generate_jwt - settings = get_settings() diff --git a/tests/test_unit/test_update_user.py b/tests/test_unit/test_update_user.py index a2e3ebe9..66501601 100644 --- a/tests/test_unit/test_update_user.py +++ b/tests/test_unit/test_update_user.py @@ -5,7 +5,6 @@ from auth_backend.auth_method import AUTH_METHODS, AuthPluginMeta - if TYPE_CHECKING: from unittest.mock import _patch_default_new as patch_type From 9051921fbafcf07d53c52b7b90c7b2dcc01a7f1d Mon Sep 17 00:00:00 2001 From: petrCher <88943157+petrCher@users.noreply.github.com> Date: Sat, 9 May 2026 12:15:32 +0300 Subject: [PATCH 03/15] black format --- .github/workflows/checks.yml | 2 +- Makefile | 2 +- auth_backend/__init__.py | 1 + auth_backend/__main__.py | 1 + auth_backend/auth_method/__init__.py | 1 + auth_backend/auth_method/base.py | 1 + auth_backend/auth_method/oauth.py | 1 + auth_backend/auth_method/outer.py | 1 + auth_backend/auth_plugins/__init__.py | 1 + auth_backend/auth_plugins/airflow.py | 1 + auth_backend/auth_plugins/authentic.py | 1 + auth_backend/auth_plugins/coder.py | 1 + auth_backend/auth_plugins/email.py | 1 + auth_backend/auth_plugins/github.py | 1 + auth_backend/auth_plugins/google.py | 1 + auth_backend/auth_plugins/keycloak.py | 1 + auth_backend/auth_plugins/lkmsu.py | 1 + auth_backend/auth_plugins/mailu.py | 1 + auth_backend/auth_plugins/postgres.py | 1 + auth_backend/auth_plugins/telegram.py | 1 + auth_backend/auth_plugins/vk.py | 1 + auth_backend/auth_plugins/yandex.py | 1 + auth_backend/cli/process.py | 1 + auth_backend/kafka/kafka.py | 1 + auth_backend/models/__init__.py | 1 + auth_backend/models/db.py | 1 + auth_backend/routes/__init__.py | 1 + auth_backend/routes/groups.py | 1 + auth_backend/routes/oidc.py | 1 + auth_backend/routes/scopes.py | 1 + auth_backend/routes/user.py | 1 + auth_backend/routes/user_session.py | 1 + auth_backend/utils/jwt.py | 1 + auth_backend/utils/security.py | 1 + auth_backend/utils/smtp.py | 1 + auth_backend/utils/user_session_basics.py | 1 + auth_backend/utils/user_session_control.py | 1 + migrations/env.py | 1 + migrations/versions/2d29fc132e89_fix_base_users_group.py | 1 + migrations/versions/586cf0e784e5_scopes.py | 1 + migrations/versions/5d71a2a2405d_verified_user_group.py | 1 + migrations/versions/6c27352ee53b_unique_scopes.py | 1 + migrations/versions/6dffd8e42152_193_add_unbounded_sessions.py | 1 + migrations/versions/7c1cd7ceacd8_dynamic_option_model.py | 1 + migrations/versions/b07c0ca33c2b_groups.py | 1 + migrations/versions/bd7ac9cbdfc8_init.py | 1 + migrations/versions/bda218c91211_user_session_ts.py | 1 + migrations/versions/c03c6b509881_rollback_unique_scopes.py | 1 + migrations/versions/dcb89e72d446_session_security_scopes.py | 1 + migrations/versions/fa4691ad1054_email_delay.py | 1 + requirements.dev.txt | 2 +- tests/test_routes/test_change_email.py | 1 + tests/test_routes/test_change_password.py | 1 + tests/test_routes/test_delete_last_auth_method.py | 1 + tests/test_routes/test_email_message_delay.py | 1 + tests/test_routes/test_login.py | 1 + tests/test_routes/test_logout.py | 1 + tests/test_routes/test_oidc.py | 1 + tests/test_routes/test_registration.py | 1 + tests/test_routes/test_user_sessions.py | 1 + tests/test_unit/test_jwt.py | 1 + tests/test_unit/test_update_user.py | 1 + 62 files changed, 62 insertions(+), 3 deletions(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index cbd53189..6ae1a676 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -70,7 +70,7 @@ jobs: - uses: isort/isort-action@master with: requirementsFiles: "requirements.txt requirements.dev.txt" - - uses: psf/black@stable + - uses: psf/black@23.11.0 - name: Comment if linting failed if: failure() uses: thollander/actions-comment-pull-request@v2 diff --git a/Makefile b/Makefile index e754ba75..29edbd08 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ run: source ./venv/bin/activate && uvicorn --reload --log-config logging_dev.conf auth_backend.routes.base:app configure: venv - source ./venv/bin/activate && pip install -r requirements.dev.txt -r requirements.txt + source ./venv/bin/activate && pip install --force-reinstall -r requirements.dev.txt -r requirements.txt venv: python3.11 -m venv venv diff --git a/auth_backend/__init__.py b/auth_backend/__init__.py index ebcaeac8..fa9c0989 100644 --- a/auth_backend/__init__.py +++ b/auth_backend/__init__.py @@ -1,3 +1,4 @@ import os + __version__ = os.getenv('APP_VERSION', 'dev') diff --git a/auth_backend/__main__.py b/auth_backend/__main__.py index 1cd7a947..6d8bd9fa 100644 --- a/auth_backend/__main__.py +++ b/auth_backend/__main__.py @@ -1,4 +1,5 @@ from auth_backend.cli.process import process + if __name__ == '__main__': process() diff --git a/auth_backend/auth_method/__init__.py b/auth_backend/auth_method/__init__.py index 142640d7..fe1ccccf 100644 --- a/auth_backend/auth_method/__init__.py +++ b/auth_backend/auth_method/__init__.py @@ -5,6 +5,7 @@ from .session import Session from .userdata_mixin import UserdataMixin + __all__ = [ "Session", "AUTH_METHODS", diff --git a/auth_backend/auth_method/base.py b/auth_backend/auth_method/base.py index d204e534..6dc58aa7 100644 --- a/auth_backend/auth_method/base.py +++ b/auth_backend/auth_method/base.py @@ -12,6 +12,7 @@ from auth_backend.models.db import AuthMethod, User, UserSession from auth_backend.settings import get_settings + logger = logging.getLogger(__name__) settings = get_settings() diff --git a/auth_backend/auth_method/oauth.py b/auth_backend/auth_method/oauth.py index 9adabfbf..961e2767 100644 --- a/auth_backend/auth_method/oauth.py +++ b/auth_backend/auth_method/oauth.py @@ -15,6 +15,7 @@ from .method_mixins import LoginableMixin, RegistrableMixin from .userdata_mixin import UserdataMixin + logger = logging.getLogger(__name__) diff --git a/auth_backend/auth_method/outer.py b/auth_backend/auth_method/outer.py index e2ba485b..26b1ab02 100644 --- a/auth_backend/auth_method/outer.py +++ b/auth_backend/auth_method/outer.py @@ -12,6 +12,7 @@ from auth_backend.models.db import AuthMethod, UserSession from auth_backend.utils.security import UnionAuth + logger = logging.getLogger(__name__) diff --git a/auth_backend/auth_plugins/__init__.py b/auth_backend/auth_plugins/__init__.py index 0b648632..480835a1 100644 --- a/auth_backend/auth_plugins/__init__.py +++ b/auth_backend/auth_plugins/__init__.py @@ -16,6 +16,7 @@ from .vk import VkAuth from .yandex import YandexAuth + __all__ = [ "AUTH_METHODS", "AuthPluginMeta", diff --git a/auth_backend/auth_plugins/airflow.py b/auth_backend/auth_plugins/airflow.py index be723cf3..6988011f 100644 --- a/auth_backend/auth_plugins/airflow.py +++ b/auth_backend/auth_plugins/airflow.py @@ -6,6 +6,7 @@ from auth_backend.auth_method.outer import ConnectionIssue, OuterAuthMeta from auth_backend.settings import Settings + logger = logging.getLogger(__name__) diff --git a/auth_backend/auth_plugins/authentic.py b/auth_backend/auth_plugins/authentic.py index d19724dd..003c81bd 100644 --- a/auth_backend/auth_plugins/authentic.py +++ b/auth_backend/auth_plugins/authentic.py @@ -20,6 +20,7 @@ from auth_backend.settings import Settings from auth_backend.utils.security import UnionAuth + AUTH_METHOD_ID_PARAM_NAME = 'user_id' logger = logging.getLogger(__name__) diff --git a/auth_backend/auth_plugins/coder.py b/auth_backend/auth_plugins/coder.py index 5a29cd5e..2c1ecef2 100644 --- a/auth_backend/auth_plugins/coder.py +++ b/auth_backend/auth_plugins/coder.py @@ -6,6 +6,7 @@ from auth_backend.auth_method.outer import ConnectionIssue, OuterAuthMeta from auth_backend.settings import Settings + logger = logging.getLogger(__name__) diff --git a/auth_backend/auth_plugins/email.py b/auth_backend/auth_plugins/email.py index 799188aa..abf0b3c2 100644 --- a/auth_backend/auth_plugins/email.py +++ b/auth_backend/auth_plugins/email.py @@ -22,6 +22,7 @@ from auth_backend.utils.smtp import SendEmailMessage from auth_backend.utils.string import random_string + settings = get_settings() logger = logging.getLogger(__name__) diff --git a/auth_backend/auth_plugins/github.py b/auth_backend/auth_plugins/github.py index 5e82525c..126168a5 100644 --- a/auth_backend/auth_plugins/github.py +++ b/auth_backend/auth_plugins/github.py @@ -18,6 +18,7 @@ from auth_backend.settings import Settings from auth_backend.utils.security import UnionAuth + logger = logging.getLogger(__name__) diff --git a/auth_backend/auth_plugins/google.py b/auth_backend/auth_plugins/google.py index a5e97e24..686eaa3d 100644 --- a/auth_backend/auth_plugins/google.py +++ b/auth_backend/auth_plugins/google.py @@ -20,6 +20,7 @@ from auth_backend.settings import Settings from auth_backend.utils.security import UnionAuth + logger = logging.getLogger(__name__) diff --git a/auth_backend/auth_plugins/keycloak.py b/auth_backend/auth_plugins/keycloak.py index 81a2e5a3..6759f8c9 100644 --- a/auth_backend/auth_plugins/keycloak.py +++ b/auth_backend/auth_plugins/keycloak.py @@ -18,6 +18,7 @@ from auth_backend.settings import Settings from auth_backend.utils.security import UnionAuth + logger = logging.getLogger(__name__) diff --git a/auth_backend/auth_plugins/lkmsu.py b/auth_backend/auth_plugins/lkmsu.py index bc9b9950..4b170daa 100644 --- a/auth_backend/auth_plugins/lkmsu.py +++ b/auth_backend/auth_plugins/lkmsu.py @@ -20,6 +20,7 @@ from auth_backend.utils.security import UnionAuth from auth_backend.utils.string import concantenate_strings + logger = logging.getLogger(__name__) diff --git a/auth_backend/auth_plugins/mailu.py b/auth_backend/auth_plugins/mailu.py index 7f88f613..cd1eab58 100644 --- a/auth_backend/auth_plugins/mailu.py +++ b/auth_backend/auth_plugins/mailu.py @@ -6,6 +6,7 @@ from auth_backend.auth_method.outer import ConnectionIssue, OuterAuthMeta from auth_backend.settings import Settings + logger = logging.getLogger(__name__) diff --git a/auth_backend/auth_plugins/postgres.py b/auth_backend/auth_plugins/postgres.py index 12f5873a..94def5ba 100644 --- a/auth_backend/auth_plugins/postgres.py +++ b/auth_backend/auth_plugins/postgres.py @@ -10,6 +10,7 @@ from auth_backend.auth_method import OuterAuthMeta from auth_backend.settings import Settings + logger = logging.getLogger(__name__) diff --git a/auth_backend/auth_plugins/telegram.py b/auth_backend/auth_plugins/telegram.py index a421eecb..ad45be0b 100644 --- a/auth_backend/auth_plugins/telegram.py +++ b/auth_backend/auth_plugins/telegram.py @@ -20,6 +20,7 @@ from auth_backend.utils.security import UnionAuth from auth_backend.utils.string import concantenate_strings + logger = logging.getLogger(__name__) diff --git a/auth_backend/auth_plugins/vk.py b/auth_backend/auth_plugins/vk.py index 5d06e6f3..7bb2bdd8 100644 --- a/auth_backend/auth_plugins/vk.py +++ b/auth_backend/auth_plugins/vk.py @@ -20,6 +20,7 @@ from ..schemas.types.scopes import Scope + logger = logging.getLogger(__name__) diff --git a/auth_backend/auth_plugins/yandex.py b/auth_backend/auth_plugins/yandex.py index d1d81753..96f1239f 100644 --- a/auth_backend/auth_plugins/yandex.py +++ b/auth_backend/auth_plugins/yandex.py @@ -19,6 +19,7 @@ from auth_backend.utils.security import UnionAuth from auth_backend.utils.string import concantenate_strings + logger = logging.getLogger(__name__) diff --git a/auth_backend/cli/process.py b/auth_backend/cli/process.py index 0db371ff..1c8357d7 100644 --- a/auth_backend/cli/process.py +++ b/auth_backend/cli/process.py @@ -11,6 +11,7 @@ from .user import create_user from .user_group import create_user_group + settings = get_settings() engine = create_engine(str(settings.DB_DSN)) Session = sessionmaker(bind=engine) diff --git a/auth_backend/kafka/kafka.py b/auth_backend/kafka/kafka.py index e11c1c90..7437e9e3 100644 --- a/auth_backend/kafka/kafka.py +++ b/auth_backend/kafka/kafka.py @@ -9,6 +9,7 @@ from auth_backend.kafka.kafkameta import KafkaMeta from auth_backend.settings import get_settings + log = logging.getLogger(__name__) diff --git a/auth_backend/models/__init__.py b/auth_backend/models/__init__.py index 6d382725..0531fd11 100644 --- a/auth_backend/models/__init__.py +++ b/auth_backend/models/__init__.py @@ -2,4 +2,5 @@ from .db import AuthMethod, User, UserSession from .dynamic_settings import DynamicOption + __all__ = ["Base", "User", "UserSession", "AuthMethod", "DynamicOption"] diff --git a/auth_backend/models/db.py b/auth_backend/models/db.py index 41b8ac37..3cab9a2e 100644 --- a/auth_backend/models/db.py +++ b/auth_backend/models/db.py @@ -15,6 +15,7 @@ from auth_backend.settings import get_settings from auth_backend.utils.user_session_basics import session_expires_date + settings = get_settings() logger = logging.getLogger(__name__) diff --git a/auth_backend/routes/__init__.py b/auth_backend/routes/__init__.py index e53b88c8..5e4fcb10 100644 --- a/auth_backend/routes/__init__.py +++ b/auth_backend/routes/__init__.py @@ -1,4 +1,5 @@ from . import exc_handlers from .base import app + __all__ = ["app", "exc_handlers"] diff --git a/auth_backend/routes/groups.py b/auth_backend/routes/groups.py index ae1d9966..a9f9f429 100644 --- a/auth_backend/routes/groups.py +++ b/auth_backend/routes/groups.py @@ -10,6 +10,7 @@ from auth_backend.schemas.models import Group, GroupGet, GroupPatch, GroupPost, GroupsGet from auth_backend.utils.security import UnionAuth + groups = APIRouter(prefix="/group", tags=["Groups"]) diff --git a/auth_backend/routes/oidc.py b/auth_backend/routes/oidc.py index fda459c7..5ec56dc0 100644 --- a/auth_backend/routes/oidc.py +++ b/auth_backend/routes/oidc.py @@ -13,6 +13,7 @@ from auth_backend.utils.jwt import create_jwks from auth_backend.utils.oidc_token import OidcGrantType, token_by_client_credentials, token_by_refresh_token + settings = get_settings() router = APIRouter(prefix="/openid", tags=["OpenID"]) logger = logging.getLogger(__name__) diff --git a/auth_backend/routes/scopes.py b/auth_backend/routes/scopes.py index 81039eb4..4091290f 100644 --- a/auth_backend/routes/scopes.py +++ b/auth_backend/routes/scopes.py @@ -8,6 +8,7 @@ from auth_backend.schemas.models import ScopeGet, ScopePatch, ScopePost from auth_backend.utils.security import UnionAuth + scopes = APIRouter(prefix="/scope", tags=["Scopes"]) diff --git a/auth_backend/routes/user.py b/auth_backend/routes/user.py index 0fc55999..02c52940 100644 --- a/auth_backend/routes/user.py +++ b/auth_backend/routes/user.py @@ -23,6 +23,7 @@ ) from auth_backend.utils.security import UnionAuth + logger = logging.getLogger(__name__) user = APIRouter(prefix="/user", tags=["User"]) diff --git a/auth_backend/routes/user_session.py b/auth_backend/routes/user_session.py index ef844ff9..37927f73 100644 --- a/auth_backend/routes/user_session.py +++ b/auth_backend/routes/user_session.py @@ -26,6 +26,7 @@ from auth_backend.utils import user_session_control from auth_backend.utils.security import UnionAuth + user_session = APIRouter(prefix="", tags=["User session"]) logger = logging.getLogger(__name__) diff --git a/auth_backend/utils/jwt.py b/auth_backend/utils/jwt.py index dc7bf9c4..9221910f 100644 --- a/auth_backend/utils/jwt.py +++ b/auth_backend/utils/jwt.py @@ -12,6 +12,7 @@ from auth_backend.settings import get_settings + settings = get_settings() diff --git a/auth_backend/utils/security.py b/auth_backend/utils/security.py index 135a0892..aa3eb103 100644 --- a/auth_backend/utils/security.py +++ b/auth_backend/utils/security.py @@ -12,6 +12,7 @@ from auth_backend.utils.user_session_basics import session_expires_date from auth_backend.utils.user_session_control import SESSION_UPDATE_SCOPE + settings = get_settings() diff --git a/auth_backend/utils/smtp.py b/auth_backend/utils/smtp.py index febd494f..5d9a305d 100644 --- a/auth_backend/utils/smtp.py +++ b/auth_backend/utils/smtp.py @@ -12,6 +12,7 @@ from auth_backend.models.db import UserMessageDelay from auth_backend.settings import Settings, get_settings + logger = logging.getLogger(__name__) diff --git a/auth_backend/utils/user_session_basics.py b/auth_backend/utils/user_session_basics.py index b3136cb7..9bfb938d 100644 --- a/auth_backend/utils/user_session_basics.py +++ b/auth_backend/utils/user_session_basics.py @@ -2,6 +2,7 @@ from auth_backend.settings import get_settings + settings = get_settings() diff --git a/auth_backend/utils/user_session_control.py b/auth_backend/utils/user_session_control.py index b03be2e2..4f3b5255 100644 --- a/auth_backend/utils/user_session_control.py +++ b/auth_backend/utils/user_session_control.py @@ -13,6 +13,7 @@ from auth_backend.utils.string import random_string from auth_backend.utils.user_session_basics import session_expires_date + SESSION_UPDATE_SCOPE = 'auth.session.update' settings = get_settings() diff --git a/migrations/env.py b/migrations/env.py index 4316dfb2..6fd58532 100644 --- a/migrations/env.py +++ b/migrations/env.py @@ -6,6 +6,7 @@ from auth_backend.models.base import Base from auth_backend.settings import get_settings + # this is the Alembic Config object, which provides # access to the values within the .ini file in use. config = context.config diff --git a/migrations/versions/2d29fc132e89_fix_base_users_group.py b/migrations/versions/2d29fc132e89_fix_base_users_group.py index 851e50c3..a6841202 100644 --- a/migrations/versions/2d29fc132e89_fix_base_users_group.py +++ b/migrations/versions/2d29fc132e89_fix_base_users_group.py @@ -12,6 +12,7 @@ from auth_backend.models.db import Group, User + # revision identifiers, used by Alembic. revision = '2d29fc132e89' down_revision = 'dcb89e72d446' diff --git a/migrations/versions/586cf0e784e5_scopes.py b/migrations/versions/586cf0e784e5_scopes.py index e4b8c0c9..d3d65185 100644 --- a/migrations/versions/586cf0e784e5_scopes.py +++ b/migrations/versions/586cf0e784e5_scopes.py @@ -9,6 +9,7 @@ import sqlalchemy as sa from alembic import op + # revision identifiers, used by Alembic. revision = '586cf0e784e5' down_revision = 'b07c0ca33c2b' diff --git a/migrations/versions/5d71a2a2405d_verified_user_group.py b/migrations/versions/5d71a2a2405d_verified_user_group.py index 74162cbf..f80b69ab 100644 --- a/migrations/versions/5d71a2a2405d_verified_user_group.py +++ b/migrations/versions/5d71a2a2405d_verified_user_group.py @@ -9,6 +9,7 @@ import sqlalchemy as sa from alembic import op + # revision identifiers, used by Alembic. revision = '5d71a2a2405d' down_revision = '2d29fc132e89' diff --git a/migrations/versions/6c27352ee53b_unique_scopes.py b/migrations/versions/6c27352ee53b_unique_scopes.py index 972cf24c..b3626953 100644 --- a/migrations/versions/6c27352ee53b_unique_scopes.py +++ b/migrations/versions/6c27352ee53b_unique_scopes.py @@ -7,6 +7,7 @@ from alembic import op + revision = '6c27352ee53b' down_revision = '586cf0e784e5' branch_labels = None diff --git a/migrations/versions/6dffd8e42152_193_add_unbounded_sessions.py b/migrations/versions/6dffd8e42152_193_add_unbounded_sessions.py index 0c8d5d3c..d2940e86 100644 --- a/migrations/versions/6dffd8e42152_193_add_unbounded_sessions.py +++ b/migrations/versions/6dffd8e42152_193_add_unbounded_sessions.py @@ -9,6 +9,7 @@ import sqlalchemy as sa from alembic import op + # revision identifiers, used by Alembic. revision = '6dffd8e42152' down_revision = '2d29fc132e89' diff --git a/migrations/versions/7c1cd7ceacd8_dynamic_option_model.py b/migrations/versions/7c1cd7ceacd8_dynamic_option_model.py index 96a9bb75..466c8e7f 100644 --- a/migrations/versions/7c1cd7ceacd8_dynamic_option_model.py +++ b/migrations/versions/7c1cd7ceacd8_dynamic_option_model.py @@ -12,6 +12,7 @@ from auth_backend.models.db import Group, Scope, User + # revision identifiers, used by Alembic. revision = '7c1cd7ceacd8' down_revision = 'bda218c91211' diff --git a/migrations/versions/b07c0ca33c2b_groups.py b/migrations/versions/b07c0ca33c2b_groups.py index efccc85e..ecf93169 100644 --- a/migrations/versions/b07c0ca33c2b_groups.py +++ b/migrations/versions/b07c0ca33c2b_groups.py @@ -10,6 +10,7 @@ from alembic import op from sqlalchemy.dialects import postgresql + # revision identifiers, used by Alembic. revision = 'b07c0ca33c2b' down_revision = 'bd7ac9cbdfc8' diff --git a/migrations/versions/bd7ac9cbdfc8_init.py b/migrations/versions/bd7ac9cbdfc8_init.py index 9cc09b55..74a6285d 100644 --- a/migrations/versions/bd7ac9cbdfc8_init.py +++ b/migrations/versions/bd7ac9cbdfc8_init.py @@ -3,6 +3,7 @@ import sqlalchemy as sa from alembic import op + revision = 'bd7ac9cbdfc8' down_revision = None branch_labels = None diff --git a/migrations/versions/bda218c91211_user_session_ts.py b/migrations/versions/bda218c91211_user_session_ts.py index d69ec9d5..564b3441 100644 --- a/migrations/versions/bda218c91211_user_session_ts.py +++ b/migrations/versions/bda218c91211_user_session_ts.py @@ -9,6 +9,7 @@ import sqlalchemy as sa from alembic import op + # revision identifiers, used by Alembic. revision = 'bda218c91211' down_revision = 'fa4691ad1054' diff --git a/migrations/versions/c03c6b509881_rollback_unique_scopes.py b/migrations/versions/c03c6b509881_rollback_unique_scopes.py index 3c5b9098..8d3ac576 100644 --- a/migrations/versions/c03c6b509881_rollback_unique_scopes.py +++ b/migrations/versions/c03c6b509881_rollback_unique_scopes.py @@ -8,6 +8,7 @@ from alembic import op + revision = 'c03c6b509881' down_revision = '6c27352ee53b' branch_labels = None diff --git a/migrations/versions/dcb89e72d446_session_security_scopes.py b/migrations/versions/dcb89e72d446_session_security_scopes.py index fe6f050a..033b8bea 100644 --- a/migrations/versions/dcb89e72d446_session_security_scopes.py +++ b/migrations/versions/dcb89e72d446_session_security_scopes.py @@ -9,6 +9,7 @@ from alembic import op from sqlalchemy.sql import text + # revision identifiers, used by Alembic. revision = 'dcb89e72d446' down_revision = '7c1cd7ceacd8' diff --git a/migrations/versions/fa4691ad1054_email_delay.py b/migrations/versions/fa4691ad1054_email_delay.py index b34233f6..90dd32c0 100644 --- a/migrations/versions/fa4691ad1054_email_delay.py +++ b/migrations/versions/fa4691ad1054_email_delay.py @@ -9,6 +9,7 @@ import sqlalchemy as sa from alembic import op + # revision identifiers, used by Alembic. revision = 'fa4691ad1054' down_revision = 'c03c6b509881' diff --git a/requirements.dev.txt b/requirements.dev.txt index 0436cc34..4827b7dc 100644 --- a/requirements.dev.txt +++ b/requirements.dev.txt @@ -3,6 +3,6 @@ pytest-cov requests httpx flake8 -black +black==23.11.0 autoflake isort diff --git a/tests/test_routes/test_change_email.py b/tests/test_routes/test_change_email.py index 4e239807..b15e59e9 100644 --- a/tests/test_routes/test_change_email.py +++ b/tests/test_routes/test_change_email.py @@ -6,6 +6,7 @@ from auth_backend.models.db import AuthMethod, UserSession + url = "/email/reset/email" diff --git a/tests/test_routes/test_change_password.py b/tests/test_routes/test_change_password.py index df03504f..da6925a8 100644 --- a/tests/test_routes/test_change_password.py +++ b/tests/test_routes/test_change_password.py @@ -5,6 +5,7 @@ from auth_backend.auth_plugins.email import Email from auth_backend.models.db import AuthMethod + url = "/email/reset/password" diff --git a/tests/test_routes/test_delete_last_auth_method.py b/tests/test_routes/test_delete_last_auth_method.py index fe38781e..a3e3836c 100644 --- a/tests/test_routes/test_delete_last_auth_method.py +++ b/tests/test_routes/test_delete_last_auth_method.py @@ -4,6 +4,7 @@ from auth_backend.exceptions import LastAuthMethodDelete from auth_backend.settings import get_settings + pytest_plugins = ('pytest_asyncio',) settings = get_settings() diff --git a/tests/test_routes/test_email_message_delay.py b/tests/test_routes/test_email_message_delay.py index 9d6cd3a0..ebc9b9de 100644 --- a/tests/test_routes/test_email_message_delay.py +++ b/tests/test_routes/test_email_message_delay.py @@ -5,6 +5,7 @@ from auth_backend.models.db import AuthMethod, User from auth_backend.settings import get_settings + settings = get_settings() diff --git a/tests/test_routes/test_login.py b/tests/test_routes/test_login.py index c4e4fcce..111e8242 100644 --- a/tests/test_routes/test_login.py +++ b/tests/test_routes/test_login.py @@ -6,6 +6,7 @@ from auth_backend.models.db import AuthMethod, Group, GroupScope, Scope, User, UserGroup, UserSession + url = "/email/login" diff --git a/tests/test_routes/test_logout.py b/tests/test_routes/test_logout.py index 9a979a34..ae99a68c 100644 --- a/tests/test_routes/test_logout.py +++ b/tests/test_routes/test_logout.py @@ -6,6 +6,7 @@ from auth_backend.models.db import AuthMethod, User, UserSession + url = "/logout" diff --git a/tests/test_routes/test_oidc.py b/tests/test_routes/test_oidc.py index 2037308d..3e9519ef 100644 --- a/tests/test_routes/test_oidc.py +++ b/tests/test_routes/test_oidc.py @@ -7,6 +7,7 @@ from auth_backend.models.db import AuthMethod from auth_backend.settings import get_settings + settings = get_settings() diff --git a/tests/test_routes/test_registration.py b/tests/test_routes/test_registration.py index 169f44a6..6326c366 100644 --- a/tests/test_routes/test_registration.py +++ b/tests/test_routes/test_registration.py @@ -8,6 +8,7 @@ from auth_backend.models.db import AuthMethod, User, UserSession + url = "/email/registration" diff --git a/tests/test_routes/test_user_sessions.py b/tests/test_routes/test_user_sessions.py index 09f4c2af..8fc934cb 100644 --- a/tests/test_routes/test_user_sessions.py +++ b/tests/test_routes/test_user_sessions.py @@ -7,6 +7,7 @@ from auth_backend.models.db import Group, GroupScope, Scope, UserGroup, UserSession, UserSessionScope + logger = logging.getLogger(__name__) diff --git a/tests/test_unit/test_jwt.py b/tests/test_unit/test_jwt.py index 8e694fc6..714b5cf7 100644 --- a/tests/test_unit/test_jwt.py +++ b/tests/test_unit/test_jwt.py @@ -5,6 +5,7 @@ from auth_backend.settings import get_settings from auth_backend.utils.jwt import create_jwks, decode_jwt, generate_jwt + settings = get_settings() diff --git a/tests/test_unit/test_update_user.py b/tests/test_unit/test_update_user.py index 66501601..a2e3ebe9 100644 --- a/tests/test_unit/test_update_user.py +++ b/tests/test_unit/test_update_user.py @@ -5,6 +5,7 @@ from auth_backend.auth_method import AUTH_METHODS, AuthPluginMeta + if TYPE_CHECKING: from unittest.mock import _patch_default_new as patch_type From ca80e482fd417dd6c097ffb876a1d598272ed2e0 Mon Sep 17 00:00:00 2001 From: petrCher <88943157+petrCher@users.noreply.github.com> Date: Sat, 9 May 2026 12:16:21 +0300 Subject: [PATCH 04/15] small fix --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 29edbd08..e754ba75 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ run: source ./venv/bin/activate && uvicorn --reload --log-config logging_dev.conf auth_backend.routes.base:app configure: venv - source ./venv/bin/activate && pip install --force-reinstall -r requirements.dev.txt -r requirements.txt + source ./venv/bin/activate && pip install -r requirements.dev.txt -r requirements.txt venv: python3.11 -m venv venv From 10691748011207b2238051424fdfc74e4e9b2ef6 Mon Sep 17 00:00:00 2001 From: petrCher <88943157+petrCher@users.noreply.github.com> Date: Sun, 10 May 2026 15:23:37 +0300 Subject: [PATCH 05/15] formatting sqladmin --- auth_backend/admin/admin.py | 40 ++++++++++++++++++++++++++++++++++++- auth_backend/admin/auth.py | 26 ++++++++++++++++++++++++ auth_backend/models/db.py | 9 +++++++++ auth_backend/routes/base.py | 5 ++++- auth_backend/settings.py | 3 +++ 5 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 auth_backend/admin/auth.py diff --git a/auth_backend/admin/admin.py b/auth_backend/admin/admin.py index d4fbee8e..6e586188 100644 --- a/auth_backend/admin/admin.py +++ b/auth_backend/admin/admin.py @@ -7,17 +7,55 @@ class ScopeAdmin(ModelView, model=Scope): name = "Scope" name_plural = "Scopes" column_list = ["id", "name", "comment", "is_deleted"] - column_searchable_list = ["name", "comment"] + column_details_list = [ + "id", + "name", + "comment", + "group", + "create_ts", + "update_ts", + "is_deleted", + ] + column_searchable_list = ["id", "name"] + column_sortable_list = ["id", "name", "is_deleted"] + column_default_sort = [("id", False)] + form_excluded_columns = ["create_ts", "update_ts", "groups", "user_sessions", "is_deleted"] class GroupAdmin(ModelView, model=Group): name = "Group" name_plural = "Groups" column_list = ["id", "name", "scopes", "users", "parent_id", "is_deleted"] + column_details_list = [ + "id", + "name", + "parent_id", + "scopes", + "users", + "create_ts", + "update_ts", + "is_deleted", + ] column_searchable_list = ["name"] + column_sortable_list = ["id", "name", "parent_id", "is_deleted"] + column_default_sort = [("id", False)] + form_excluded_columns = ["create_ts", "update_ts", "is_deleted"] class UserAdmin(ModelView, model=User): name = "User" name_plural = "Users" column_list = ["id", "scopes", "groups"] + column_details_list = ["id", "groups", "scopes", "is_deleted"] + column_searchable_list = ["id"] + column_sortable_list = ["id", "is_deleted"] + form_include_pk = False + form_columns = ["groups"] + can_create = False + can_delete = False + column_formatters = { + "scopes": lambda m, a: ", ".join(s.name for s in m.scopes), + } + column_formatters_detail = { + "scopes": lambda m, a: ", ".join(s.name for s in (m.scopes or set())), + } diff --git a/auth_backend/admin/auth.py b/auth_backend/admin/auth.py new file mode 100644 index 00000000..a09de6be --- /dev/null +++ b/auth_backend/admin/auth.py @@ -0,0 +1,26 @@ +from fastapi import Request +from sqladmin.authentication import AuthenticationBackend + +from auth_backend.settings import get_settings + + +settings = get_settings() + + +class AdminAuth(AuthenticationBackend): + async def login(self, request: Request) -> bool: + form = await request.form() + username = form.get("username") + password = form.get("password") + if username == settings.ADMIN_LOGIN and password == settings.ADMIN_PASSWORD: + request.session["user"] = username + return True + return False + + async def logout(self, request: Request) -> bool: + request.session.clear() + return True + + async def authenticate(self, request: Request) -> bool: + user = request.session.get("user") + return user is not None diff --git a/auth_backend/models/db.py b/auth_backend/models/db.py index 3cab9a2e..3727d0cc 100644 --- a/auth_backend/models/db.py +++ b/auth_backend/models/db.py @@ -43,6 +43,9 @@ class User(BaseDbModel): secondaryjoin="and_(Group.id==UserGroup.group_id, not_(Group.is_deleted))", ) + def __str__(self): + return str(self.id) + @classmethod def create(cls, *, session: Session, **kwargs) -> User: user: User = super().create(session=session, **kwargs) @@ -114,6 +117,9 @@ class Group(BaseDbModel): secondaryjoin="and_(Scope.id==GroupScope.scope_id, not_(Scope.is_deleted))", ) + def __str__(self): + return self.name + @hybrid_property def indirect_scopes(self) -> set[Scope]: _indirect_scopes = set() @@ -205,6 +211,9 @@ class Scope(BaseDbModel): secondaryjoin="(UserSession.id==UserSessionScope.user_session_id)", ) + def __str__(self): + return self.name + @classmethod def create(cls, *, session: Session, **kwargs) -> Scope: scope: Scope = super().create(session=session, **kwargs) diff --git a/auth_backend/routes/base.py b/auth_backend/routes/base.py index 3d5056b2..2e1f9285 100644 --- a/auth_backend/routes/base.py +++ b/auth_backend/routes/base.py @@ -8,6 +8,7 @@ from auth_backend import __version__ from auth_backend.admin.admin import GroupAdmin, ScopeAdmin, UserAdmin +from auth_backend.admin.auth import AdminAuth from auth_backend.auth_method import AuthPluginMeta from auth_backend.kafka.kafka import get_kafka_producer from auth_backend.settings import get_settings @@ -43,7 +44,9 @@ async def lifespan(app: FastAPI): lifespan=lifespan, ) -admin = Admin(app, engine=engine, title='Admin panel') +admin = Admin( + app, engine=engine, title='Admin panel', authentication_backend=AdminAuth(secret_key=settings.ADMIN_SECRET_KEY) +) app.add_middleware( DBSessionMiddleware, diff --git a/auth_backend/settings.py b/auth_backend/settings.py index 04e6d619..caa4b356 100644 --- a/auth_backend/settings.py +++ b/auth_backend/settings.py @@ -20,6 +20,9 @@ class Settings(BaseSettings): KAFKA_TIMEOUT: int = 2 KAFKA_LOGIN: str | None = None KAFKA_PASSWORD: str | None = None + ADMIN_SECRET_KEY: str | None = None + ADMIN_LOGIN: str = "admin" + ADMIN_PASSWORD: str = "admin" ROOT_PATH: str = '/' + os.getenv('APP_NAME', '') From afbf7cbdf89e614297164e4784823965148aa15b Mon Sep 17 00:00:00 2001 From: petrCher <88943157+petrCher@users.noreply.github.com> Date: Sun, 10 May 2026 15:33:27 +0300 Subject: [PATCH 06/15] small fix --- auth_backend/routes/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/auth_backend/routes/base.py b/auth_backend/routes/base.py index 2e1f9285..2f4793f4 100644 --- a/auth_backend/routes/base.py +++ b/auth_backend/routes/base.py @@ -28,7 +28,7 @@ async def lifespan(app: FastAPI): settings = get_settings() -engine = create_engine(str(settings.DB_DSN)) +engine = create_engine(str(settings.DB_DSN), pool_pre_ping=True) app = FastAPI( title='Сервис аутентификации и авторизации', @@ -45,7 +45,7 @@ async def lifespan(app: FastAPI): ) admin = Admin( - app, engine=engine, title='Admin panel', authentication_backend=AdminAuth(secret_key=settings.ADMIN_SECRET_KEY) + app, engine=engine, title='Auth admin panel', authentication_backend=AdminAuth(secret_key=settings.ADMIN_SECRET_KEY) ) app.add_middleware( From 45af67211e688a4d24c556c78c98aec42d8aea30 Mon Sep 17 00:00:00 2001 From: petrCher <88943157+petrCher@users.noreply.github.com> Date: Sun, 10 May 2026 17:18:33 +0300 Subject: [PATCH 07/15] user logic added for sqladmin --- auth_backend/admin/admin.py | 7 +++++++ auth_backend/routes/user.py | 40 +++++++++++++++++++------------------ 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/auth_backend/admin/admin.py b/auth_backend/admin/admin.py index 6e586188..7355ef94 100644 --- a/auth_backend/admin/admin.py +++ b/auth_backend/admin/admin.py @@ -1,6 +1,7 @@ from sqladmin import ModelView from auth_backend.models.db import Group, Scope, User +from auth_backend.routes.user import patch_user_groups class ScopeAdmin(ModelView, model=Scope): @@ -20,6 +21,7 @@ class ScopeAdmin(ModelView, model=Scope): column_sortable_list = ["id", "name", "is_deleted"] column_default_sort = [("id", False)] form_excluded_columns = ["create_ts", "update_ts", "groups", "user_sessions", "is_deleted"] + can_create = False # I don't know how to use UnionAuth there to get user_id that is required class GroupAdmin(ModelView, model=Group): @@ -59,3 +61,8 @@ class UserAdmin(ModelView, model=User): column_formatters_detail = { "scopes": lambda m, a: ", ".join(s.name for s in (m.scopes or set())), } + + async def on_model_change(self, data: dict, model: User, is_created: bool, request) -> None: + group_ids = [int(group) for group in (data.pop("groups") or [])] + with self.session_maker(expire_on_commit=False) as session: + patch_user_groups(model.id, group_ids, session) diff --git a/auth_backend/routes/user.py b/auth_backend/routes/user.py index 02c52940..1e1ae254 100644 --- a/auth_backend/routes/user.py +++ b/auth_backend/routes/user.py @@ -110,34 +110,36 @@ async def get_users( return UsersGet(**result).model_dump(exclude_unset=True) -@user.patch("/{user_id}", response_model=UserModel) -async def patch_user( - user_id: int, - user_inp: UserPatch, - _: UserSession = Depends(UnionAuth(scopes=["auth.user.update"], allow_none=False, auto_error=True)), -) -> UserInfo: - """ - Scopes: `["auth.user.update"]` - """ - user = User.get(user_id, session=db.session) +def patch_user_groups(user_id: int, group_ids: list[int], session) -> None: + user = User.get(user_id, session=session) groups = set() - for group_id in user_inp.groups: - group = Group.get(group_id, session=db.session) + for group_id in group_ids: + group = Group.get(group_id, session=session) groups.add(group) user_groups = set(user.groups) new_groups = groups - user_groups to_delete_groups = user_groups - groups for group in new_groups: - UserGroup.create(session=db.session, user_id=user_id, group_id=group.id) + UserGroup.create(session=session, user_id=user_id, group_id=group.id) for group in to_delete_groups: user_group: UserGroup = ( - UserGroup.query(session=db.session) - .filter(UserGroup.user_id == user_id, UserGroup.group_id == group.id) - .one() + UserGroup.query(session=session).filter(UserGroup.user_id == user_id, UserGroup.group_id == group.id).one() ) - UserGroup.delete(user_group.id, session=db.session) - db.session.commit() - return UserModel.model_validate(user) + UserGroup.delete(user_group.id, session=session) + session.commit() + + +@user.patch("/{user_id}", response_model=UserModel) +async def patch_user( + user_id: int, + user_inp: UserPatch, + _: UserSession = Depends(UnionAuth(scopes=["auth.user.update"], allow_none=False, auto_error=True)), +) -> UserInfo: + """ + Scopes: `["auth.user.update"]` + """ + patch_user_groups(user_id, user_inp.groups, db.session) + return UserModel.model_validate(User.get(user_id, session=db.session)) @user.delete("/{user_id}", response_model=None) From 6570f0261147fd10d5be2528860a1e6557c8d198 Mon Sep 17 00:00:00 2001 From: petrCher <88943157+petrCher@users.noreply.github.com> Date: Sun, 10 May 2026 17:51:25 +0300 Subject: [PATCH 08/15] soft deletes added --- auth_backend/admin/admin.py | 44 ++++++++++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/auth_backend/admin/admin.py b/auth_backend/admin/admin.py index 7355ef94..ae934a39 100644 --- a/auth_backend/admin/admin.py +++ b/auth_backend/admin/admin.py @@ -1,4 +1,7 @@ from sqladmin import ModelView +from sqlalchemy import func, select +from sqlalchemy.sql.expression import Select +from starlette.requests import Request from auth_backend.models.db import Group, Scope, User from auth_backend.routes.user import patch_user_groups @@ -7,7 +10,7 @@ class ScopeAdmin(ModelView, model=Scope): name = "Scope" name_plural = "Scopes" - column_list = ["id", "name", "comment", "is_deleted"] + column_list = ["id", "name", "comment"] column_details_list = [ "id", "name", @@ -18,16 +21,34 @@ class ScopeAdmin(ModelView, model=Scope): "is_deleted", ] column_searchable_list = ["id", "name"] - column_sortable_list = ["id", "name", "is_deleted"] + column_sortable_list = ["id", "name"] column_default_sort = [("id", False)] form_excluded_columns = ["create_ts", "update_ts", "groups", "user_sessions", "is_deleted"] can_create = False # I don't know how to use UnionAuth there to get user_id that is required + def list_query(self, request: Request) -> Select: + return select(Scope).where(Scope.is_deleted == False) + + def count_query(self, request: Request) -> Select: + return select(func.count(Scope.id)).where(Scope.is_deleted == False) + + async def update_model(self, request, pk, data): + with self.session_maker(expire_on_commit=False) as session: + scope_data = {k: v for k, v in data.items() if v is not None} + obj = Scope.update(int(pk), **scope_data, session=session) + session.commit() + return obj + + async def delete_model(self, request, pk): + with self.session_maker(expire_on_commit=False) as session: + Scope.delete(session=session, id=int(pk)) + session.commit() + class GroupAdmin(ModelView, model=Group): name = "Group" name_plural = "Groups" - column_list = ["id", "name", "scopes", "users", "parent_id", "is_deleted"] + column_list = ["id", "name", "scopes", "users", "parent_id"] column_details_list = [ "id", "name", @@ -43,6 +64,12 @@ class GroupAdmin(ModelView, model=Group): column_default_sort = [("id", False)] form_excluded_columns = ["create_ts", "update_ts", "is_deleted"] + def list_query(self, request: Request) -> Select: + return select(Group).where(Group.is_deleted == False) + + def count_query(self, request: Request) -> Select: + return select(func.count(Group.id)).where(Group.is_deleted == False) + class UserAdmin(ModelView, model=User): name = "User" @@ -62,7 +89,14 @@ class UserAdmin(ModelView, model=User): "scopes": lambda m, a: ", ".join(s.name for s in (m.scopes or set())), } - async def on_model_change(self, data: dict, model: User, is_created: bool, request) -> None: + def list_query(self, request: Request) -> Select: + return select(User).where(User.is_deleted == False) + + def count_query(self, request: Request) -> Select: + return select(func.count(User.id)).where(User.is_deleted == False) + + async def update_model(self, request, pk, data): group_ids = [int(group) for group in (data.pop("groups") or [])] with self.session_maker(expire_on_commit=False) as session: - patch_user_groups(model.id, group_ids, session) + patch_user_groups(int(pk), group_ids, session) + return User.get(int(pk), session=session) From c4cdd6b3af6f7727ba20ad07b1daf2ec34a7c47c Mon Sep 17 00:00:00 2001 From: petrCher <88943157+petrCher@users.noreply.github.com> Date: Sun, 10 May 2026 18:27:34 +0300 Subject: [PATCH 09/15] logic for group sqladmin --- auth_backend/admin/admin.py | 16 +++++++++++ auth_backend/routes/groups.py | 53 ++++++++++++++++++++--------------- 2 files changed, 47 insertions(+), 22 deletions(-) diff --git a/auth_backend/admin/admin.py b/auth_backend/admin/admin.py index ae934a39..08d916c9 100644 --- a/auth_backend/admin/admin.py +++ b/auth_backend/admin/admin.py @@ -4,7 +4,9 @@ from starlette.requests import Request from auth_backend.models.db import Group, Scope, User +from auth_backend.routes.groups import create_group_logic, delete_group_id from auth_backend.routes.user import patch_user_groups +from auth_backend.schemas.models import GroupPost class ScopeAdmin(ModelView, model=Scope): @@ -70,6 +72,20 @@ def list_query(self, request: Request) -> Select: def count_query(self, request: Request) -> Select: return select(func.count(Group.id)).where(Group.is_deleted == False) + # add logic with deleted scopes showed + async def insert_model(self, request, data): + scope_ids = [int(s) for s in (data.pop("scopes", None) or [])] + parent_id = int(data["parent_id"]) if data.get("parent_id") else None + group_inp = GroupPost(name=data["name"], parent_id=parent_id, scopes=scope_ids) + with self.session_maker(expire_on_commit=False) as session: + result = create_group_logic(group_inp, session) + return Group.get(result["id"], session=session) + + # add update group + async def delete_model(self, request, pk): + with self.session_maker(expire_on_commit=False) as session: + delete_group_id(int(pk), session) + class UserAdmin(ModelView, model=User): name = "User" diff --git a/auth_backend/routes/groups.py b/auth_backend/routes/groups.py index a9f9f429..82559eaf 100644 --- a/auth_backend/routes/groups.py +++ b/auth_backend/routes/groups.py @@ -37,17 +37,10 @@ async def get_group( return GroupGet(**result).model_dump(exclude_unset=True) -@groups.post("", response_model=Group) -async def create_group( - group_inp: GroupPost, - _: UserSession = Depends(UnionAuth(scopes=["auth.group.create"], allow_none=False, auto_error=True)), -) -> dict[str, str | int]: - """ - Scopes: `["auth.group.create"]` - """ - if group_inp.parent_id and not db.session.query(DbGroup).get(group_inp.parent_id): +def create_group_logic(group_inp: GroupPost, session) -> dict: + if group_inp.parent_id and not session.query(DbGroup).get(group_inp.parent_id): raise ObjectNotFound(Group, group_inp.parent_id) - if DbGroup.query(session=db.session).filter(DbGroup.name == group_inp.name).one_or_none(): + if DbGroup.query(session=session).filter(DbGroup.name == group_inp.name).one_or_none(): raise HTTPException( status_code=409, detail=StatusResponseModel( @@ -57,14 +50,26 @@ async def create_group( scopes = set() if group_inp.scopes: for _scope_id in group_inp.scopes: - scopes.add(Scope.get(session=db.session, id=_scope_id)) + scopes.add(Scope.get(session=session, id=_scope_id)) result = {} - group = DbGroup.create(session=db.session, name=group_inp.name, parent_id=group_inp.parent_id) - db.session.flush() + group = DbGroup.create(session=session, name=group_inp.name, parent_id=group_inp.parent_id) + session.flush() result = result | {"name": group.name, "id": group.id, "parent_id": group.parent_id} for scope in scopes: - GroupScope.create(session=db.session, group_id=group.id, scope_id=scope.id) - db.session.commit() + GroupScope.create(session=session, group_id=group.id, scope_id=scope.id) + session.commit() + return result + + +@groups.post("", response_model=Group) +async def create_group( + group_inp: GroupPost, + _: UserSession = Depends(UnionAuth(scopes=["auth.group.create"], allow_none=False, auto_error=True)), +) -> dict[str, str | int]: + """ + Scopes: `["auth.group.create"]` + """ + result = create_group_logic(group_inp, db.session) return Group(**result).model_dump(exclude_unset=True) @@ -101,13 +106,7 @@ async def patch_group( return Group.model_validate(group) -@groups.delete("/{id}", response_model=None) -async def delete_group( - id: int, _: UserSession = Depends(UnionAuth(scopes=["auth.scope.delete"], allow_none=False, auto_error=True)) -) -> None: - """ - Scopes: `["auth.scope.delete"]` - """ +def delete_group_id(id: int, session) -> None: group: DbGroup = DbGroup.get(id, session=db.session) if child := group.child: for children in child: @@ -115,6 +114,16 @@ async def delete_group( db.session.flush() DbGroup.delete(id, session=db.session) db.session.commit() + + +@groups.delete("/{id}", response_model=None) +async def delete_group( + id: int, _: UserSession = Depends(UnionAuth(scopes=["auth.scope.delete"], allow_none=False, auto_error=True)) +) -> None: + """ + Scopes: `["auth.scope.delete"]` + """ + delete_group_id(id, db.session) return None From 10c45f2719885a403a830032efb8bedc28b25bf2 Mon Sep 17 00:00:00 2001 From: petrCher <88943157+petrCher@users.noreply.github.com> Date: Sun, 10 May 2026 23:40:34 +0300 Subject: [PATCH 10/15] patch group logic and filtering --- auth_backend/admin/admin.py | 21 ++++++++++++++---- auth_backend/admin/filter.py | 20 +++++++++++++++++ auth_backend/routes/groups.py | 41 ++++++++++++++++++++--------------- 3 files changed, 60 insertions(+), 22 deletions(-) create mode 100644 auth_backend/admin/filter.py diff --git a/auth_backend/admin/admin.py b/auth_backend/admin/admin.py index 08d916c9..6bb51f36 100644 --- a/auth_backend/admin/admin.py +++ b/auth_backend/admin/admin.py @@ -3,10 +3,11 @@ from sqlalchemy.sql.expression import Select from starlette.requests import Request +from auth_backend.admin.filter import FilteredModelConverter from auth_backend.models.db import Group, Scope, User -from auth_backend.routes.groups import create_group_logic, delete_group_id +from auth_backend.routes.groups import create_group_logic, delete_group_id, patch_group_logic from auth_backend.routes.user import patch_user_groups -from auth_backend.schemas.models import GroupPost +from auth_backend.schemas.models import GroupPatch, GroupPost class ScopeAdmin(ModelView, model=Scope): @@ -27,6 +28,7 @@ class ScopeAdmin(ModelView, model=Scope): column_default_sort = [("id", False)] form_excluded_columns = ["create_ts", "update_ts", "groups", "user_sessions", "is_deleted"] can_create = False # I don't know how to use UnionAuth there to get user_id that is required + form_converter = FilteredModelConverter def list_query(self, request: Request) -> Select: return select(Scope).where(Scope.is_deleted == False) @@ -65,6 +67,7 @@ class GroupAdmin(ModelView, model=Group): column_sortable_list = ["id", "name", "parent_id", "is_deleted"] column_default_sort = [("id", False)] form_excluded_columns = ["create_ts", "update_ts", "is_deleted"] + form_converter = FilteredModelConverter def list_query(self, request: Request) -> Select: return select(Group).where(Group.is_deleted == False) @@ -72,7 +75,6 @@ def list_query(self, request: Request) -> Select: def count_query(self, request: Request) -> Select: return select(func.count(Group.id)).where(Group.is_deleted == False) - # add logic with deleted scopes showed async def insert_model(self, request, data): scope_ids = [int(s) for s in (data.pop("scopes", None) or [])] parent_id = int(data["parent_id"]) if data.get("parent_id") else None @@ -81,7 +83,17 @@ async def insert_model(self, request, data): result = create_group_logic(group_inp, session) return Group.get(result["id"], session=session) - # add update group + async def update_model(self, request, pk, data): + scope_ids = [int(s) for s in (data.pop("scopes", None) or [])] + parent_id = int(data["parent_id"]) if data.get("parent_id") else None + group_inp = GroupPatch( + name=data.get("name"), + parent_id=parent_id, + scopes=scope_ids, + ) + with self.session_maker(expire_on_commit=False) as session: + return patch_group_logic(int(pk), group_inp, session) + async def delete_model(self, request, pk): with self.session_maker(expire_on_commit=False) as session: delete_group_id(int(pk), session) @@ -104,6 +116,7 @@ class UserAdmin(ModelView, model=User): column_formatters_detail = { "scopes": lambda m, a: ", ".join(s.name for s in (m.scopes or set())), } + form_converter = FilteredModelConverter def list_query(self, request: Request) -> Select: return select(User).where(User.is_deleted == False) diff --git a/auth_backend/admin/filter.py b/auth_backend/admin/filter.py new file mode 100644 index 00000000..1d853617 --- /dev/null +++ b/auth_backend/admin/filter.py @@ -0,0 +1,20 @@ +import anyio +from sqladmin.forms import ModelConverter +from sqladmin.helpers import is_async_session_maker +from sqlalchemy import select + + +class FilteredModelConverter(ModelConverter): + async def _prepare_select_options(self, prop, session_maker): + target_model = prop.mapper.class_ + stmt = select(target_model) + if hasattr(target_model, "is_deleted"): + stmt = stmt.where(target_model.is_deleted == False) + if is_async_session_maker(session_maker): + async with session_maker() as session: + objects = await session.execute(stmt) + return [(str(self._get_identifier_value(obj)), str(obj)) for obj in objects.scalars().unique().all()] + else: + with session_maker() as session: + objects = await anyio.to_thread.run_sync(session.execute, stmt) + return [(str(self._get_identifier_value(obj)), str(obj)) for obj in objects.scalars().unique().all()] diff --git a/auth_backend/routes/groups.py b/auth_backend/routes/groups.py index 82559eaf..5ff507ad 100644 --- a/auth_backend/routes/groups.py +++ b/auth_backend/routes/groups.py @@ -73,47 +73,52 @@ async def create_group( return Group(**result).model_dump(exclude_unset=True) -@groups.patch("/{id}", response_model=Group) -async def patch_group( - id: int, - group_inp: GroupPatch, - _: UserSession = Depends(UnionAuth(scopes=["auth.group.update"], allow_none=False, auto_error=True)), -) -> Group: - """ - Scopes: `["auth.group.update"]` - """ +def patch_group_logic(id: int, group_inp: GroupPatch, session) -> DbGroup: if ( - exists_check := DbGroup.query(session=db.session) + exists_check := DbGroup.query(session=session) .filter(DbGroup.name == group_inp.name, DbGroup.id != id) .one_or_none() ): raise AlreadyExists(Group, exists_check.id) - group = DbGroup.get(id, session=db.session) + group = DbGroup.get(id, session=session) if group_inp.parent_id in (row.id for row in group.child): raise HTTPException( status_code=400, detail=StatusResponseModel(status="Error", message="Cycle detected", ru="Найден цикл").model_dump(), ) result = Group.model_validate( - DbGroup.update(id, session=db.session, **group_inp.model_dump(exclude_unset=True, exclude={"scopes"})) + DbGroup.update(id, session=session, **group_inp.model_dump(exclude_unset=True, exclude={"scopes"})) ).model_dump(exclude_unset=True) scopes = set() if group_inp.scopes is not None: for _scope_id in group_inp.scopes: - scopes.add(Scope.get(session=db.session, id=_scope_id)) + scopes.add(Scope.get(session=session, id=_scope_id)) group.scopes = scopes - db.session.commit() + session.commit() + return group + + +@groups.patch("/{id}", response_model=Group) +async def patch_group( + id: int, + group_inp: GroupPatch, + _: UserSession = Depends(UnionAuth(scopes=["auth.group.update"], allow_none=False, auto_error=True)), +) -> Group: + """ + Scopes: `["auth.group.update"]` + """ + group = patch_group_logic(id, group_inp, db.session) return Group.model_validate(group) def delete_group_id(id: int, session) -> None: - group: DbGroup = DbGroup.get(id, session=db.session) + group: DbGroup = DbGroup.get(id, session=session) if child := group.child: for children in child: children.parent_id = group.parent_id - db.session.flush() - DbGroup.delete(id, session=db.session) - db.session.commit() + session.flush() + DbGroup.delete(id, session=session) + session.commit() @groups.delete("/{id}", response_model=None) From f721c9d3118b03849a3fa5b633a4a58f2b004217 Mon Sep 17 00:00:00 2001 From: petrCher <88943157+petrCher@users.noreply.github.com> Date: Mon, 11 May 2026 00:22:35 +0300 Subject: [PATCH 11/15] minor format fixes --- auth_backend/admin/admin.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/auth_backend/admin/admin.py b/auth_backend/admin/admin.py index 6bb51f36..aa529127 100644 --- a/auth_backend/admin/admin.py +++ b/auth_backend/admin/admin.py @@ -18,9 +18,7 @@ class ScopeAdmin(ModelView, model=Scope): "id", "name", "comment", - "group", - "create_ts", - "update_ts", + "creator_id", "is_deleted", ] column_searchable_list = ["id", "name"] @@ -66,7 +64,7 @@ class GroupAdmin(ModelView, model=Group): column_searchable_list = ["name"] column_sortable_list = ["id", "name", "parent_id", "is_deleted"] column_default_sort = [("id", False)] - form_excluded_columns = ["create_ts", "update_ts", "is_deleted"] + form_excluded_columns = ["child", "users", "create_ts", "update_ts", "is_deleted"] form_converter = FilteredModelConverter def list_query(self, request: Request) -> Select: From ea58ec5944393eeab41866901c0d8690a9fb2bea Mon Sep 17 00:00:00 2001 From: petrCher <88943157+petrCher@users.noreply.github.com> Date: Mon, 11 May 2026 14:43:37 +0300 Subject: [PATCH 12/15] docs + default admin key --- auth_backend/admin/filter.py | 4 ++++ auth_backend/settings.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/auth_backend/admin/filter.py b/auth_backend/admin/filter.py index 1d853617..f8f8dbe4 100644 --- a/auth_backend/admin/filter.py +++ b/auth_backend/admin/filter.py @@ -5,6 +5,10 @@ class FilteredModelConverter(ModelConverter): + """ + A custom ModelConverter that filters out deleted objects from select options in form with create/update. + """ + async def _prepare_select_options(self, prop, session_maker): target_model = prop.mapper.class_ stmt = select(target_model) diff --git a/auth_backend/settings.py b/auth_backend/settings.py index caa4b356..4d653a8b 100644 --- a/auth_backend/settings.py +++ b/auth_backend/settings.py @@ -20,7 +20,7 @@ class Settings(BaseSettings): KAFKA_TIMEOUT: int = 2 KAFKA_LOGIN: str | None = None KAFKA_PASSWORD: str | None = None - ADMIN_SECRET_KEY: str | None = None + ADMIN_SECRET_KEY: str = "default" ADMIN_LOGIN: str = "admin" ADMIN_PASSWORD: str = "admin" From a0479995dae7aff1017af7a4110863e8d8609f66 Mon Sep 17 00:00:00 2001 From: petrCher <88943157+petrCher@users.noreply.github.com> Date: Mon, 11 May 2026 16:12:07 +0300 Subject: [PATCH 13/15] black stable --- .github/workflows/checks.yml | 2 +- requirements.dev.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 6ae1a676..cbd53189 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -70,7 +70,7 @@ jobs: - uses: isort/isort-action@master with: requirementsFiles: "requirements.txt requirements.dev.txt" - - uses: psf/black@23.11.0 + - uses: psf/black@stable - name: Comment if linting failed if: failure() uses: thollander/actions-comment-pull-request@v2 diff --git a/requirements.dev.txt b/requirements.dev.txt index 4827b7dc..0436cc34 100644 --- a/requirements.dev.txt +++ b/requirements.dev.txt @@ -3,6 +3,6 @@ pytest-cov requests httpx flake8 -black==23.11.0 +black autoflake isort From 0b5ef20a6e3ddd0be1ba944dcee1e7ec24eedf8c Mon Sep 17 00:00:00 2001 From: petrCher <88943157+petrCher@users.noreply.github.com> Date: Mon, 11 May 2026 21:24:23 +0300 Subject: [PATCH 14/15] auth logic --- auth_backend/admin/auth.py | 41 +++++++++++++++++++++++++++++--------- auth_backend/settings.py | 1 + requirements.txt | 1 + 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/auth_backend/admin/auth.py b/auth_backend/admin/auth.py index a09de6be..6b4f3b8e 100644 --- a/auth_backend/admin/auth.py +++ b/auth_backend/admin/auth.py @@ -2,25 +2,48 @@ from sqladmin.authentication import AuthenticationBackend from auth_backend.settings import get_settings - +from auth_backend.utils.security import UnionAuth +from auth_lib.methods import AuthLib settings = get_settings() - class AdminAuth(AuthenticationBackend): + async def login(self, request: Request) -> bool: form = await request.form() username = form.get("username") - password = form.get("password") - if username == settings.ADMIN_LOGIN and password == settings.ADMIN_PASSWORD: - request.session["user"] = username + token = form.get("password") + if username != settings.ADMIN_LOGIN: + return False + valid = await self._is_valid_token(token) + if valid: + request.session["token"] = token return True - return False + else: + return False + + async def authenticate(self, request: Request) -> bool: + token = request.session.get("token") + if not token: + return False + return await self._is_valid_token(token) async def logout(self, request: Request) -> bool: request.session.clear() return True - async def authenticate(self, request: Request) -> bool: - user = request.session.get("user") - return user is not None + @staticmethod + async def _is_valid_token(token: str) -> bool: + try: + result = AuthLib(auth_url=settings.AUTH_URL).check_token(token) + if not result: + return False + session_scopes = { + scope["name"].lower() for scope in result.get("session_scopes", []) + } + required_scopes = "auth.sqladmin.admin" + if required_scopes not in session_scopes: + return False + return True + except Exception: + return False \ No newline at end of file diff --git a/auth_backend/settings.py b/auth_backend/settings.py index 4d653a8b..847d0948 100644 --- a/auth_backend/settings.py +++ b/auth_backend/settings.py @@ -23,6 +23,7 @@ class Settings(BaseSettings): ADMIN_SECRET_KEY: str = "default" ADMIN_LOGIN: str = "admin" ADMIN_PASSWORD: str = "admin" + AUTH_URL: str = "https://api.test.profcomff.com/auth/" ROOT_PATH: str = '/' + os.getenv('APP_NAME', '') diff --git a/requirements.txt b/requirements.txt index b5fc6c41..83d1d2c8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,6 +17,7 @@ event-schema-profcomff aiocache python-multipart sqladmin[full] +auth-lib-profcomff[fastapi] # Google Auth Method google-api-python-client From 8f7631be595fdd90021a3ffcf3fd5d334a35bbab Mon Sep 17 00:00:00 2001 From: petrCher <88943157+petrCher@users.noreply.github.com> Date: Mon, 11 May 2026 23:53:24 +0300 Subject: [PATCH 15/15] authorization correct logic added to sqladmin --- auth_backend/admin/admin.py | 11 +++++++++-- auth_backend/admin/auth.py | 26 ++++++++++++++------------ auth_backend/routes/scopes.py | 25 ++++++++++++++----------- 3 files changed, 37 insertions(+), 25 deletions(-) diff --git a/auth_backend/admin/admin.py b/auth_backend/admin/admin.py index aa529127..7d200c42 100644 --- a/auth_backend/admin/admin.py +++ b/auth_backend/admin/admin.py @@ -6,8 +6,9 @@ from auth_backend.admin.filter import FilteredModelConverter from auth_backend.models.db import Group, Scope, User from auth_backend.routes.groups import create_group_logic, delete_group_id, patch_group_logic +from auth_backend.routes.scopes import create_scope_logic from auth_backend.routes.user import patch_user_groups -from auth_backend.schemas.models import GroupPatch, GroupPost +from auth_backend.schemas.models import GroupPatch, GroupPost, ScopePost class ScopeAdmin(ModelView, model=Scope): @@ -25,7 +26,6 @@ class ScopeAdmin(ModelView, model=Scope): column_sortable_list = ["id", "name"] column_default_sort = [("id", False)] form_excluded_columns = ["create_ts", "update_ts", "groups", "user_sessions", "is_deleted"] - can_create = False # I don't know how to use UnionAuth there to get user_id that is required form_converter = FilteredModelConverter def list_query(self, request: Request) -> Select: @@ -33,6 +33,13 @@ def list_query(self, request: Request) -> Select: def count_query(self, request: Request) -> Select: return select(func.count(Scope.id)).where(Scope.is_deleted == False) + + async def insert_model(self, request: Request, data: dict): + user_id = request.session.get("user_id") + scope_inp = ScopePost(**data) + with self.session_maker(expire_on_commit=False) as session: + obj = create_scope_logic(scope_inp, session, user_id) + return Scope.get(obj.id, session=session) async def update_model(self, request, pk, data): with self.session_maker(expire_on_commit=False) as session: diff --git a/auth_backend/admin/auth.py b/auth_backend/admin/auth.py index 6b4f3b8e..b5bb6ee9 100644 --- a/auth_backend/admin/auth.py +++ b/auth_backend/admin/auth.py @@ -1,9 +1,10 @@ +from auth_lib.methods import AuthLib from fastapi import Request from sqladmin.authentication import AuthenticationBackend from auth_backend.settings import get_settings -from auth_backend.utils.security import UnionAuth -from auth_lib.methods import AuthLib +from typing import Any + settings = get_settings() @@ -16,34 +17,35 @@ async def login(self, request: Request) -> bool: if username != settings.ADMIN_LOGIN: return False valid = await self._is_valid_token(token) - if valid: - request.session["token"] = token - return True - else: + if valid is None: return False + request.session["token"] = token + request.session["user_id"] = valid.get("id") + return True async def authenticate(self, request: Request) -> bool: token = request.session.get("token") if not token: return False - return await self._is_valid_token(token) + userdata = await self._is_valid_token(token) + return userdata is not None async def logout(self, request: Request) -> bool: request.session.clear() return True @staticmethod - async def _is_valid_token(token: str) -> bool: + async def _is_valid_token(token: str) -> dict[str, Any] | None: try: result = AuthLib(auth_url=settings.AUTH_URL).check_token(token) if not result: - return False + return None session_scopes = { scope["name"].lower() for scope in result.get("session_scopes", []) } required_scopes = "auth.sqladmin.admin" if required_scopes not in session_scopes: - return False - return True + return None + return result except Exception: - return False \ No newline at end of file + return None \ No newline at end of file diff --git a/auth_backend/routes/scopes.py b/auth_backend/routes/scopes.py index 4091290f..5c3bf409 100644 --- a/auth_backend/routes/scopes.py +++ b/auth_backend/routes/scopes.py @@ -11,6 +11,18 @@ scopes = APIRouter(prefix="/scope", tags=["Scopes"]) +def create_scope_logic(scope: ScopePost, session, creator_id) -> dict: + if Scope.query(session=session).filter(func.lower(Scope.name) == scope.name.lower()).all(): + raise HTTPException( + status_code=409, + detail=StatusResponseModel(status="Error", message="Already exists", ru="Уже существует").model_dump(), + ) + scope.name = scope.name.lower() + retval = ScopeGet.model_validate( + Scope.create(**scope.model_dump(), creator_id=creator_id, session=session) + ) + session.commit() + return retval @scopes.post("", response_model=ScopeGet) async def create_scope( @@ -20,17 +32,8 @@ async def create_scope( """ Scopes: `["auth.scope.create"]` """ - if Scope.query(session=db.session).filter(func.lower(Scope.name) == scope.name.lower()).all(): - raise HTTPException( - status_code=409, - detail=StatusResponseModel(status="Error", message="Already exists", ru="Уже существует").model_dump(), - ) - scope.name = scope.name.lower() - retval = ScopeGet.model_validate( - Scope.create(**scope.model_dump(), creator_id=user_session.user_id, session=db.session) - ) - db.session.commit() - return retval + retval = create_scope_logic(scope, db.session, user_session.user_id) + return ScopeGet.model_validate(retval) @scopes.get("/{id}", response_model=ScopeGet)