pkg-auth 3.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pkg_auth/__init__.py +15 -0
- pkg_auth/admin/__init__.py +35 -0
- pkg_auth/admin/cli.py +87 -0
- pkg_auth/admin/client.py +401 -0
- pkg_auth/admin/env.py +74 -0
- pkg_auth/admin/helpers.py +113 -0
- pkg_auth/admin/provision_client.py +86 -0
- pkg_auth/admin/settings.py +33 -0
- pkg_auth/authentication/__init__.py +33 -0
- pkg_auth/authentication/adapters/__init__.py +1 -0
- pkg_auth/authentication/adapters/keycloak/__init__.py +6 -0
- pkg_auth/authentication/adapters/keycloak/jwt_decoder.py +105 -0
- pkg_auth/authentication/application/__init__.py +1 -0
- pkg_auth/authentication/application/use_cases/__init__.py +1 -0
- pkg_auth/authentication/application/use_cases/authenticate.py +91 -0
- pkg_auth/authentication/domain/__init__.py +1 -0
- pkg_auth/authentication/domain/entities.py +50 -0
- pkg_auth/authentication/domain/exceptions.py +18 -0
- pkg_auth/authentication/domain/ports.py +26 -0
- pkg_auth/authentication/domain/value_objects.py +42 -0
- pkg_auth/authorization/__init__.py +117 -0
- pkg_auth/authorization/adapters/__init__.py +1 -0
- pkg_auth/authorization/adapters/cache/__init__.py +32 -0
- pkg_auth/authorization/adapters/cache/decorators.py +181 -0
- pkg_auth/authorization/adapters/cache/memory.py +61 -0
- pkg_auth/authorization/adapters/cache/protocol.py +36 -0
- pkg_auth/authorization/adapters/cache/redis.py +60 -0
- pkg_auth/authorization/adapters/django_orm/__init__.py +37 -0
- pkg_auth/authorization/adapters/django_orm/apps.py +24 -0
- pkg_auth/authorization/adapters/django_orm/mixins.py +142 -0
- pkg_auth/authorization/adapters/django_orm/models.py +226 -0
- pkg_auth/authorization/adapters/django_orm/repositories/__init__.py +20 -0
- pkg_auth/authorization/adapters/django_orm/repositories/membership.py +118 -0
- pkg_auth/authorization/adapters/django_orm/repositories/organization.py +73 -0
- pkg_auth/authorization/adapters/django_orm/repositories/organization_service.py +71 -0
- pkg_auth/authorization/adapters/django_orm/repositories/permission_catalog.py +102 -0
- pkg_auth/authorization/adapters/django_orm/repositories/role.py +120 -0
- pkg_auth/authorization/adapters/django_orm/repositories/service.py +60 -0
- pkg_auth/authorization/adapters/django_orm/repositories/user.py +77 -0
- pkg_auth/authorization/adapters/sqlalchemy/__init__.py +90 -0
- pkg_auth/authorization/adapters/sqlalchemy/base.py +55 -0
- pkg_auth/authorization/adapters/sqlalchemy/migrations/__init__.py +1 -0
- pkg_auth/authorization/adapters/sqlalchemy/migrations/versions/20260410_0001_initial_schema.py +293 -0
- pkg_auth/authorization/adapters/sqlalchemy/migrations/versions/20260412_0002_add_permission_is_platform.py +39 -0
- pkg_auth/authorization/adapters/sqlalchemy/migrations/versions/20260620_0003_permission_visibility.py +65 -0
- pkg_auth/authorization/adapters/sqlalchemy/migrations/versions/20260620_0004_permission_description_jsonb.py +52 -0
- pkg_auth/authorization/adapters/sqlalchemy/migrations/versions/20260620_0005_services_tables.py +116 -0
- pkg_auth/authorization/adapters/sqlalchemy/migrations/versions/__init__.py +1 -0
- pkg_auth/authorization/adapters/sqlalchemy/mixins.py +187 -0
- pkg_auth/authorization/adapters/sqlalchemy/models.py +268 -0
- pkg_auth/authorization/adapters/sqlalchemy/repositories/__init__.py +16 -0
- pkg_auth/authorization/adapters/sqlalchemy/repositories/membership.py +146 -0
- pkg_auth/authorization/adapters/sqlalchemy/repositories/organization.py +97 -0
- pkg_auth/authorization/adapters/sqlalchemy/repositories/organization_service.py +106 -0
- pkg_auth/authorization/adapters/sqlalchemy/repositories/permission_catalog.py +127 -0
- pkg_auth/authorization/adapters/sqlalchemy/repositories/role.py +171 -0
- pkg_auth/authorization/adapters/sqlalchemy/repositories/service.py +93 -0
- pkg_auth/authorization/adapters/sqlalchemy/repositories/user.py +74 -0
- pkg_auth/authorization/application/__init__.py +1 -0
- pkg_auth/authorization/application/use_cases/__init__.py +1 -0
- pkg_auth/authorization/application/use_cases/_helpers.py +82 -0
- pkg_auth/authorization/application/use_cases/check_permission.py +21 -0
- pkg_auth/authorization/application/use_cases/create_organization.py +41 -0
- pkg_auth/authorization/application/use_cases/create_role.py +69 -0
- pkg_auth/authorization/application/use_cases/delete_membership.py +21 -0
- pkg_auth/authorization/application/use_cases/delete_organization.py +21 -0
- pkg_auth/authorization/application/use_cases/delete_role.py +23 -0
- pkg_auth/authorization/application/use_cases/list_user_organizations.py +21 -0
- pkg_auth/authorization/application/use_cases/provision_default_services.py +38 -0
- pkg_auth/authorization/application/use_cases/register_permission_catalog.py +122 -0
- pkg_auth/authorization/application/use_cases/resolve_auth_context.py +70 -0
- pkg_auth/authorization/application/use_cases/resolve_user_from_jwt.py +34 -0
- pkg_auth/authorization/application/use_cases/set_organization_service.py +50 -0
- pkg_auth/authorization/application/use_cases/sync_permission_catalog.py +86 -0
- pkg_auth/authorization/application/use_cases/sync_service_catalog.py +91 -0
- pkg_auth/authorization/application/use_cases/sync_user_from_jwt.py +32 -0
- pkg_auth/authorization/application/use_cases/update_organization.py +31 -0
- pkg_auth/authorization/application/use_cases/update_role.py +61 -0
- pkg_auth/authorization/application/use_cases/upsert_membership.py +54 -0
- pkg_auth/authorization/cli/__init__.py +1 -0
- pkg_auth/authorization/cli/sync_catalog.py +180 -0
- pkg_auth/authorization/cli/sync_services.py +151 -0
- pkg_auth/authorization/config.py +21 -0
- pkg_auth/authorization/domain/__init__.py +1 -0
- pkg_auth/authorization/domain/entities.py +192 -0
- pkg_auth/authorization/domain/exceptions.py +68 -0
- pkg_auth/authorization/domain/ports.py +217 -0
- pkg_auth/authorization/domain/value_objects.py +208 -0
- pkg_auth/authorization/platform.py +47 -0
- pkg_auth/integrations/__init__.py +0 -0
- pkg_auth/integrations/django/__init__.py +32 -0
- pkg_auth/integrations/django/apps.py +10 -0
- pkg_auth/integrations/django/auth_context_middleware.py +105 -0
- pkg_auth/integrations/django/decorators.py +74 -0
- pkg_auth/integrations/django/install.py +136 -0
- pkg_auth/integrations/django/middleware.py +63 -0
- pkg_auth/integrations/fastapi/__init__.py +26 -0
- pkg_auth/integrations/fastapi/auth_context_dep.py +150 -0
- pkg_auth/integrations/fastapi/auth_factory.py +84 -0
- pkg_auth/integrations/fastapi/decorators.py +55 -0
- pkg_auth/integrations/fastapi/errors.py +72 -0
- pkg_auth/integrations/fastapi/identity_dep.py +41 -0
- pkg_auth/integrations/strawberry/__init__.py +20 -0
- pkg_auth/integrations/strawberry/auth.py +137 -0
- pkg_auth/integrations/strawberry/permissions.py +56 -0
- pkg_auth-3.0.0.dist-info/METADATA +147 -0
- pkg_auth-3.0.0.dist-info/RECORD +110 -0
- pkg_auth-3.0.0.dist-info/WHEEL +5 -0
- pkg_auth-3.0.0.dist-info/entry_points.txt +4 -0
- pkg_auth-3.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Token extraction helpers and identity dependency for FastAPI."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from fastapi import HTTPException, Request, status
|
|
7
|
+
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
|
8
|
+
|
|
9
|
+
DEFAULT_COOKIE_NAME = "access_token"
|
|
10
|
+
|
|
11
|
+
bearer_scheme = HTTPBearer(auto_error=False)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def extract_token_from_request(
|
|
15
|
+
request: Request,
|
|
16
|
+
credentials: Optional[HTTPAuthorizationCredentials] = None,
|
|
17
|
+
cookie_name: str = DEFAULT_COOKIE_NAME,
|
|
18
|
+
) -> str:
|
|
19
|
+
"""Extract a bearer token from the Authorization header or a cookie.
|
|
20
|
+
|
|
21
|
+
Raises ``HTTPException(401)`` if no token is found.
|
|
22
|
+
"""
|
|
23
|
+
if credentials is not None:
|
|
24
|
+
token = (credentials.credentials or "").strip()
|
|
25
|
+
if token:
|
|
26
|
+
return token
|
|
27
|
+
|
|
28
|
+
auth_header = request.headers.get("Authorization")
|
|
29
|
+
if auth_header and auth_header.startswith("Bearer "):
|
|
30
|
+
token = auth_header.removeprefix("Bearer ").strip()
|
|
31
|
+
if token:
|
|
32
|
+
return token
|
|
33
|
+
|
|
34
|
+
cookie_token = request.cookies.get(cookie_name)
|
|
35
|
+
if cookie_token:
|
|
36
|
+
return cookie_token
|
|
37
|
+
|
|
38
|
+
raise HTTPException(
|
|
39
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
40
|
+
detail="Not authenticated",
|
|
41
|
+
)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Strawberry GraphQL integration for pkg_auth (identity + ACL)."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
try:
|
|
5
|
+
import strawberry # noqa: F401
|
|
6
|
+
except ImportError as exc: # pragma: no cover
|
|
7
|
+
raise ImportError(
|
|
8
|
+
"pkg_auth.integrations.strawberry requires strawberry-graphql. "
|
|
9
|
+
"Install with: pip install pkg-auth[strawberry]"
|
|
10
|
+
) from exc
|
|
11
|
+
|
|
12
|
+
from .auth import StrawberryContext, make_context_getter
|
|
13
|
+
from .permissions import IsAuthenticated, RequirePermission
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"StrawberryContext",
|
|
17
|
+
"make_context_getter",
|
|
18
|
+
"IsAuthenticated",
|
|
19
|
+
"RequirePermission",
|
|
20
|
+
]
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""Strawberry context getter producing ``(IdentityContext, AuthContext)``."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from typing import Awaitable, Callable
|
|
6
|
+
from uuid import UUID
|
|
7
|
+
|
|
8
|
+
from starlette.requests import Request
|
|
9
|
+
|
|
10
|
+
from ...authentication import (
|
|
11
|
+
AuthenticateTokenUseCase,
|
|
12
|
+
AuthenticationError,
|
|
13
|
+
IdentityContext,
|
|
14
|
+
InvalidTokenError,
|
|
15
|
+
TokenExpiredError,
|
|
16
|
+
)
|
|
17
|
+
from ...authorization import (
|
|
18
|
+
AuthContext,
|
|
19
|
+
NotAMember,
|
|
20
|
+
OrgId,
|
|
21
|
+
UserNotProvisioned,
|
|
22
|
+
)
|
|
23
|
+
from ...authorization.application.use_cases.resolve_auth_context import (
|
|
24
|
+
ResolveAuthContextUseCase,
|
|
25
|
+
)
|
|
26
|
+
from ...authorization.application.use_cases.resolve_user_from_jwt import (
|
|
27
|
+
ResolveUserFromJwtUseCase,
|
|
28
|
+
)
|
|
29
|
+
from ...authorization.application.use_cases.sync_user_from_jwt import (
|
|
30
|
+
SyncUserFromJwtUseCase,
|
|
31
|
+
)
|
|
32
|
+
from ...authorization.domain.entities import User
|
|
33
|
+
from ...authorization.domain.ports import OrganizationRepository
|
|
34
|
+
|
|
35
|
+
DEFAULT_HEADER_NAME = "X-Organization-Id"
|
|
36
|
+
DEFAULT_COOKIE_NAME = "access_token"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass(slots=True)
|
|
40
|
+
class StrawberryContext:
|
|
41
|
+
"""Context object exposed to every Strawberry resolver via ``info.context``.
|
|
42
|
+
|
|
43
|
+
Carries the request, the validated identity (``None`` for anonymous
|
|
44
|
+
queries), and the per-organization authorization context (``None``
|
|
45
|
+
if no ``X-Organization-Id`` header was provided).
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
request: Request
|
|
49
|
+
identity: IdentityContext | None = None
|
|
50
|
+
auth_context: AuthContext | None = None
|
|
51
|
+
extra: dict[str, object] = field(default_factory=dict)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _extract_token(request: Request, cookie_name: str) -> str | None:
|
|
55
|
+
auth_header = request.headers.get("Authorization")
|
|
56
|
+
if auth_header and auth_header.startswith("Bearer "):
|
|
57
|
+
token = auth_header.removeprefix("Bearer ").strip()
|
|
58
|
+
if token:
|
|
59
|
+
return token
|
|
60
|
+
return request.cookies.get(cookie_name)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def make_context_getter(
|
|
64
|
+
*,
|
|
65
|
+
authenticate_use_case: AuthenticateTokenUseCase,
|
|
66
|
+
resolve_use_case: ResolveAuthContextUseCase,
|
|
67
|
+
organization_repo: OrganizationRepository,
|
|
68
|
+
sync_user_use_case: SyncUserFromJwtUseCase | None = None,
|
|
69
|
+
resolve_user_use_case: ResolveUserFromJwtUseCase | None = None,
|
|
70
|
+
header_name: str = DEFAULT_HEADER_NAME,
|
|
71
|
+
cookie_name: str = DEFAULT_COOKIE_NAME,
|
|
72
|
+
) -> Callable[[Request], Awaitable[StrawberryContext]]:
|
|
73
|
+
"""Build an async ``context_getter`` for ``strawberry.fastapi.GraphQLRouter``.
|
|
74
|
+
|
|
75
|
+
Exactly one of ``sync_user_use_case`` (Mode A — source-of-truth) or
|
|
76
|
+
``resolve_user_use_case`` (Mode B — consumer) must be supplied.
|
|
77
|
+
|
|
78
|
+
The returned function is permissive: token errors, missing headers,
|
|
79
|
+
and ``UserNotProvisioned`` degrade the context fields to ``None``
|
|
80
|
+
rather than raising. Permission classes (``IsAuthenticated``,
|
|
81
|
+
``RequirePermission``) are responsible for rejecting under-privileged
|
|
82
|
+
queries.
|
|
83
|
+
"""
|
|
84
|
+
if (sync_user_use_case is None) == (resolve_user_use_case is None):
|
|
85
|
+
raise ValueError(
|
|
86
|
+
"make_context_getter: pass exactly one of "
|
|
87
|
+
"sync_user_use_case (Mode A) or resolve_user_use_case (Mode B)."
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
async def _context_getter(request: Request) -> StrawberryContext:
|
|
91
|
+
ctx = StrawberryContext(request=request)
|
|
92
|
+
|
|
93
|
+
token = _extract_token(request, cookie_name)
|
|
94
|
+
if token is not None:
|
|
95
|
+
try:
|
|
96
|
+
ctx.identity = authenticate_use_case.execute(token)
|
|
97
|
+
except (TokenExpiredError, InvalidTokenError, AuthenticationError):
|
|
98
|
+
ctx.identity = None
|
|
99
|
+
|
|
100
|
+
if ctx.identity is None:
|
|
101
|
+
return ctx
|
|
102
|
+
|
|
103
|
+
raw = request.headers.get(header_name)
|
|
104
|
+
if raw is None:
|
|
105
|
+
return ctx
|
|
106
|
+
|
|
107
|
+
user: User
|
|
108
|
+
try:
|
|
109
|
+
if sync_user_use_case is not None:
|
|
110
|
+
user = await sync_user_use_case.execute(
|
|
111
|
+
sub=ctx.identity.subject_str,
|
|
112
|
+
email=ctx.identity.email_str or "",
|
|
113
|
+
full_name=ctx.identity.full_name,
|
|
114
|
+
)
|
|
115
|
+
else:
|
|
116
|
+
assert resolve_user_use_case is not None
|
|
117
|
+
user = await resolve_user_use_case.execute(
|
|
118
|
+
sub=ctx.identity.subject_str,
|
|
119
|
+
)
|
|
120
|
+
except UserNotProvisioned:
|
|
121
|
+
return ctx
|
|
122
|
+
|
|
123
|
+
try:
|
|
124
|
+
org = await organization_repo.get(OrgId(UUID(raw)))
|
|
125
|
+
except ValueError:
|
|
126
|
+
org = await organization_repo.get_by_slug(raw)
|
|
127
|
+
if org is None:
|
|
128
|
+
return ctx
|
|
129
|
+
|
|
130
|
+
try:
|
|
131
|
+
ctx.auth_context = await resolve_use_case.execute(user.id, org.id)
|
|
132
|
+
except NotAMember:
|
|
133
|
+
ctx.auth_context = None
|
|
134
|
+
|
|
135
|
+
return ctx
|
|
136
|
+
|
|
137
|
+
return _context_getter
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Strawberry permission classes built on ``StrawberryContext``."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from strawberry.permission import BasePermission
|
|
7
|
+
from strawberry.types import Info
|
|
8
|
+
|
|
9
|
+
from .auth import StrawberryContext
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class IsAuthenticated(BasePermission):
|
|
13
|
+
"""Permission class: require a valid identity (any organization or none)."""
|
|
14
|
+
|
|
15
|
+
message = "Authentication required"
|
|
16
|
+
|
|
17
|
+
async def has_permission(
|
|
18
|
+
self, source: Any, info: Info, **kwargs: Any
|
|
19
|
+
) -> bool:
|
|
20
|
+
ctx: StrawberryContext = info.context
|
|
21
|
+
return ctx.identity is not None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class RequirePermission(BasePermission):
|
|
25
|
+
"""Permission class: require a specific perm in the active org context.
|
|
26
|
+
|
|
27
|
+
Usage::
|
|
28
|
+
|
|
29
|
+
@strawberry.type
|
|
30
|
+
class Query:
|
|
31
|
+
@strawberry.field(
|
|
32
|
+
permission_classes=[RequirePermission("course:view")],
|
|
33
|
+
)
|
|
34
|
+
async def course(self, id: strawberry.ID) -> Course: ...
|
|
35
|
+
|
|
36
|
+
Returns ``False`` (and a meaningful ``message``) when:
|
|
37
|
+
- the request has no identity → "Authentication required"
|
|
38
|
+
- the request has identity but no auth_context → "Missing X-Organization-Id"
|
|
39
|
+
- the role does not grant ``perm`` → "Permission denied: <perm>"
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(self, perm: str) -> None:
|
|
43
|
+
self.perm = perm
|
|
44
|
+
self.message = f"Permission denied: {perm}"
|
|
45
|
+
|
|
46
|
+
async def has_permission(
|
|
47
|
+
self, source: Any, info: Info, **kwargs: Any
|
|
48
|
+
) -> bool:
|
|
49
|
+
ctx: StrawberryContext = info.context
|
|
50
|
+
if ctx.identity is None:
|
|
51
|
+
self.message = "Authentication required"
|
|
52
|
+
return False
|
|
53
|
+
if ctx.auth_context is None:
|
|
54
|
+
self.message = "Missing X-Organization-Id header"
|
|
55
|
+
return False
|
|
56
|
+
return ctx.auth_context.has(self.perm)
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pkg-auth
|
|
3
|
+
Version: 3.0.0
|
|
4
|
+
Summary: Clean-architecture auth core for multiple Python frameworks
|
|
5
|
+
Author-email: Fritill <info@fritill.ae>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/fritill-team/fri_pkg_auth
|
|
8
|
+
Project-URL: Repository, https://github.com/fritill-team/fri_pkg_auth
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: httpx>=0.28.1
|
|
12
|
+
Requires-Dist: pyjwt[crypto]>=2.10.1
|
|
13
|
+
Requires-Dist: requests>=2.32.3
|
|
14
|
+
Provides-Extra: dev
|
|
15
|
+
Requires-Dist: pytest<9.0.0,>=8.0.0; extra == "dev"
|
|
16
|
+
Requires-Dist: pytest-asyncio<1.0.0,>=0.23.0; extra == "dev"
|
|
17
|
+
Requires-Dist: mypy<2.0.0,>=1.11.0; extra == "dev"
|
|
18
|
+
Requires-Dist: types-requests<3.0.0.0,>=2.32.0.0; extra == "dev"
|
|
19
|
+
Requires-Dist: types-PyJWT<2.0.0,>=1.7.0; extra == "dev"
|
|
20
|
+
Requires-Dist: testcontainers[postgres,redis]>=4.0; extra == "dev"
|
|
21
|
+
Provides-Extra: acl-sqlalchemy
|
|
22
|
+
Requires-Dist: sqlalchemy<3.0,>=2.0; extra == "acl-sqlalchemy"
|
|
23
|
+
Requires-Dist: asyncpg>=0.29; extra == "acl-sqlalchemy"
|
|
24
|
+
Requires-Dist: alembic>=1.13; extra == "acl-sqlalchemy"
|
|
25
|
+
Provides-Extra: acl-django
|
|
26
|
+
Requires-Dist: django>=4.2; extra == "acl-django"
|
|
27
|
+
Requires-Dist: psycopg[binary]>=3.1; extra == "acl-django"
|
|
28
|
+
Provides-Extra: cache-redis
|
|
29
|
+
Requires-Dist: redis>=5.0; extra == "cache-redis"
|
|
30
|
+
Provides-Extra: fastapi
|
|
31
|
+
Requires-Dist: fastapi>=0.115; extra == "fastapi"
|
|
32
|
+
Requires-Dist: starlette>=0.37; extra == "fastapi"
|
|
33
|
+
Provides-Extra: django
|
|
34
|
+
Requires-Dist: django>=4.2; extra == "django"
|
|
35
|
+
Provides-Extra: strawberry
|
|
36
|
+
Requires-Dist: strawberry-graphql>=0.255; extra == "strawberry"
|
|
37
|
+
Provides-Extra: all
|
|
38
|
+
Requires-Dist: django>=6.0; extra == "all"
|
|
39
|
+
Requires-Dist: fastapi>=0.115; extra == "all"
|
|
40
|
+
Requires-Dist: starlette>=0.37; extra == "all"
|
|
41
|
+
Requires-Dist: django>=4.2; extra == "all"
|
|
42
|
+
Requires-Dist: psycopg[binary]>=3.1; extra == "all"
|
|
43
|
+
Requires-Dist: strawberry-graphql>=0.255; extra == "all"
|
|
44
|
+
Requires-Dist: sqlalchemy<3.0,>=2.0; extra == "all"
|
|
45
|
+
Requires-Dist: asyncpg>=0.29; extra == "all"
|
|
46
|
+
Requires-Dist: alembic>=1.13; extra == "all"
|
|
47
|
+
Requires-Dist: redis>=5.0; extra == "all"
|
|
48
|
+
|
|
49
|
+
# pkg-auth
|
|
50
|
+
|
|
51
|
+
Clean-architecture **identity + ACL** for multi-framework Python services. Handles JWT authentication (via Keycloak) and database-backed authorization (users, organizations, roles, permissions, memberships) in a single package with first-class support for **FastAPI**, **Django**, and **Strawberry GraphQL**.
|
|
52
|
+
|
|
53
|
+
> **v1.0 is a breaking change from v0.x.** The old claim-based authorization model (`AccessContext`, `AccessRights`, `require_permissions`) is replaced by a real ACL database. See [`docs/MIGRATION_v1.md`](docs/MIGRATION_v1.md) for the upgrade guide.
|
|
54
|
+
|
|
55
|
+
## Install
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# Core (identity only — no DB deps)
|
|
59
|
+
pip install pkg-auth
|
|
60
|
+
|
|
61
|
+
# With ACL + FastAPI (most common for itqadem services)
|
|
62
|
+
pip install pkg-auth[acl-sqlalchemy,fastapi]
|
|
63
|
+
|
|
64
|
+
# With ACL + Django
|
|
65
|
+
pip install pkg-auth[acl-django,django]
|
|
66
|
+
|
|
67
|
+
# With optional Redis cache
|
|
68
|
+
pip install pkg-auth[cache-redis]
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Quickstart (FastAPI)
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from fastapi import Depends, FastAPI
|
|
75
|
+
from pkg_auth.authentication import IdentityContext
|
|
76
|
+
from pkg_auth.authorization import AuthContext
|
|
77
|
+
from pkg_auth.integrations.fastapi import (
|
|
78
|
+
create_authentication,
|
|
79
|
+
make_get_auth_context,
|
|
80
|
+
require_permission,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# --- Wire authentication + authorization ---
|
|
84
|
+
|
|
85
|
+
auth = create_authentication(
|
|
86
|
+
keycloak_base_url="https://auth.example.com",
|
|
87
|
+
realm="itqadem",
|
|
88
|
+
audience="courses-service",
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# Mode B (consumer — the common case): pass resolve_user_use_case.
|
|
92
|
+
# Mode A (source-of-truth): pass sync_user_use_case instead. Exactly
|
|
93
|
+
# one of the two is required; passing both raises ValueError.
|
|
94
|
+
get_auth_context = make_get_auth_context(
|
|
95
|
+
get_identity=auth.get_identity,
|
|
96
|
+
resolve_user_use_case=resolve_user, # or: sync_user_use_case=sync_user (Mode A)
|
|
97
|
+
resolve_use_case=resolve,
|
|
98
|
+
organization_repo=org_repo,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
app = FastAPI()
|
|
102
|
+
|
|
103
|
+
# --- Use in routes ---
|
|
104
|
+
|
|
105
|
+
@app.get("/courses/{id}")
|
|
106
|
+
async def get_course(
|
|
107
|
+
id: str,
|
|
108
|
+
bundle: tuple[IdentityContext, AuthContext] = Depends(
|
|
109
|
+
require_permission("course:view", get_auth_context=get_auth_context)
|
|
110
|
+
),
|
|
111
|
+
):
|
|
112
|
+
identity, auth_ctx = bundle
|
|
113
|
+
return {"course_id": id, "role": str(auth_ctx.role_name)}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
See [`examples/itqadem_courses_app`](examples/itqadem_courses_app) for a complete working example.
|
|
117
|
+
|
|
118
|
+
## Architecture
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
pkg_auth/
|
|
122
|
+
authentication/ JWT validation → IdentityContext (identity only)
|
|
123
|
+
authorization/ Full ACL (users, orgs, roles, perms, memberships)
|
|
124
|
+
domain/ Pure entities, ports (Protocol), exceptions
|
|
125
|
+
application/use_cases/ Business logic (13 use cases)
|
|
126
|
+
adapters/
|
|
127
|
+
sqlalchemy/ Canonical schema + Alembic migration + repos
|
|
128
|
+
django_orm/ Mirror models (managed=False) + repos
|
|
129
|
+
cache/ InMemoryTTLCache / RedisCache + decorator
|
|
130
|
+
integrations/
|
|
131
|
+
fastapi/ Deps + require_permission + exception handlers
|
|
132
|
+
django/ Middleware + decorators
|
|
133
|
+
strawberry/ Context getter + permission classes
|
|
134
|
+
admin/ Keycloak admin client (user provisioning)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
**Layering rules**: domain has zero external imports; application imports only domain; adapters import their framework; integrations import everything.
|
|
138
|
+
|
|
139
|
+
## Documentation
|
|
140
|
+
|
|
141
|
+
- [Authorization model](docs/Authorization.md) — schema, permission catalog, roles, memberships
|
|
142
|
+
- [Caching](docs/Caching.md) — InMemoryTTLCache, RedisCache, invalidation contract
|
|
143
|
+
- [FastAPI Integration](docs/FastAPI.md)
|
|
144
|
+
- [Django Integration](docs/Django.md)
|
|
145
|
+
- [Strawberry Integration](docs/Strawberry.md)
|
|
146
|
+
- [Keycloak Admin](docs/Keycloak-Admin.md)
|
|
147
|
+
- [Migration from v0.x](docs/MIGRATION_v1.md)
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
pkg_auth/__init__.py,sha256=Ncdh3MFgxrBDZ_mm0tvRwEtd6NCRdGUrdroUYZ1Rojg,501
|
|
2
|
+
pkg_auth/admin/__init__.py,sha256=3rhV9qexqMKgBmtlz5EeTsjh4Big1M0n0gTMbO8i56M,1053
|
|
3
|
+
pkg_auth/admin/cli.py,sha256=Zqa9arZSDWOgnnC5gIyUOZpsebaplwkZdJOlybm3HUI,2654
|
|
4
|
+
pkg_auth/admin/client.py,sha256=Zv-T0Z9mtD-OKi0KlWqe4rWHMXUIo7rE3OpxjPgUnCM,15640
|
|
5
|
+
pkg_auth/admin/env.py,sha256=y5ojrPwdbsTIpa8TKE1PNT_qbMCIUjbjjIDGwgOJEhI,2456
|
|
6
|
+
pkg_auth/admin/helpers.py,sha256=Zqq01HZChHcQsaDsWOIznqLRnC2rDoxKzabahonT0dE,3659
|
|
7
|
+
pkg_auth/admin/provision_client.py,sha256=tHqTXNCwD3Q-VqdUgEB6hbyxGYs8EEH15rtGtaAyk3g,2874
|
|
8
|
+
pkg_auth/admin/settings.py,sha256=qFQGl12zgt0O5wfAhXlRJ-uWxFelm-AR7keXRQnkJbE,907
|
|
9
|
+
pkg_auth/authentication/__init__.py,sha256=tC11N5chlpilu9keLJCPqciHhJswMEsfpfOsSNAoeCE,1003
|
|
10
|
+
pkg_auth/authentication/adapters/__init__.py,sha256=zT2tSG-CunLGX5-B_nH1VwUbiah78dXP4jjUmotstyE,46
|
|
11
|
+
pkg_auth/authentication/adapters/keycloak/__init__.py,sha256=jT6EGZ2hJvIubi_lDZP383gbV7-mJwRu4tgjE7e51Kw,144
|
|
12
|
+
pkg_auth/authentication/adapters/keycloak/jwt_decoder.py,sha256=ntsA6Rl5RkQxJaFn8uS53qsEKD-tP784wTE9pMIpSOc,3278
|
|
13
|
+
pkg_auth/authentication/application/__init__.py,sha256=Hds19u1CgNfI00uL9fkYmWF2O71IjIcy4VamMnbSjQ4,52
|
|
14
|
+
pkg_auth/authentication/application/use_cases/__init__.py,sha256=MHUxj-iDbSDzjFHgUgy6PAV6fy2-GJjyeWqGYuZRAX4,32
|
|
15
|
+
pkg_auth/authentication/application/use_cases/authenticate.py,sha256=ahXhfwpdCDNrAMNLYRdJPoGYRYkFdnYG_bKX3FYK9mE,3262
|
|
16
|
+
pkg_auth/authentication/domain/__init__.py,sha256=Lv-UUicaWH5_wTUk3Wq2rHy2ndWSSv6CPkk-kORfk4w,80
|
|
17
|
+
pkg_auth/authentication/domain/entities.py,sha256=hXsoN7kmrEXrDb8DOCkv0HGnHfPwedydc6Y7xtBsmBQ,1650
|
|
18
|
+
pkg_auth/authentication/domain/exceptions.py,sha256=u6Fd6pQfSU3ese0BIs7v6DsyZT7D0xnm24rXgxfJ2ak,486
|
|
19
|
+
pkg_auth/authentication/domain/ports.py,sha256=G3a2Et9QYkSW3muSgflb1hdJDHO6OV4dO9RmlVL_i7Y,785
|
|
20
|
+
pkg_auth/authentication/domain/value_objects.py,sha256=aiMEoD8n8Ij1FBL8txXqYzfcvHctlI4ONUIPBchnBxc,936
|
|
21
|
+
pkg_auth/authorization/__init__.py,sha256=Q79hWDxqH9yjupKzZBchJ9ONR99Pk9m6TyMkixhamFs,2941
|
|
22
|
+
pkg_auth/authorization/config.py,sha256=AGEYfwh19cVZ64yQXTL-_FBzfG2RCngRhCOH-QArKcM,662
|
|
23
|
+
pkg_auth/authorization/platform.py,sha256=_emk0vDEGjBtKlbHs0TLokLj4HLqMSHlVZ2zztkP-ek,2034
|
|
24
|
+
pkg_auth/authorization/adapters/__init__.py,sha256=5HHRX7dlZqqgtoJ0UoMqIbE9ayESEvb0DpdbczzDLWo,77
|
|
25
|
+
pkg_auth/authorization/adapters/cache/__init__.py,sha256=8T8HA1albF1m_aMKtb6_ueXgW37KAM2vBsb1StRdJ3w,966
|
|
26
|
+
pkg_auth/authorization/adapters/cache/decorators.py,sha256=9a1H9G9o6cWA5YwfGcZAKrMCreG01zS8IFsNzwYMHPA,6115
|
|
27
|
+
pkg_auth/authorization/adapters/cache/memory.py,sha256=L6JjCJlN-JVwpGmzt2a3Y536HuH0ylkgIYfE23u-vRE,2060
|
|
28
|
+
pkg_auth/authorization/adapters/cache/protocol.py,sha256=qgBMuBiIuLEfgW6JL9QyafuZlWfYVOyv5n7nl5ry7gs,1159
|
|
29
|
+
pkg_auth/authorization/adapters/cache/redis.py,sha256=OmKKmR5q0nLAh0GklFCqz8_NZ8OwIqjU928GNT3gCL8,1879
|
|
30
|
+
pkg_auth/authorization/adapters/django_orm/__init__.py,sha256=K0yp8LPavqds8wIcn3XpHr5BOocCrirsLKCUC05gd04,1419
|
|
31
|
+
pkg_auth/authorization/adapters/django_orm/apps.py,sha256=POt9oMuHd6ZhyzwOuFTsVOjaOq4DNeLsZrzoSV8Of2o,990
|
|
32
|
+
pkg_auth/authorization/adapters/django_orm/mixins.py,sha256=OVxrD38eQlgjOWoZ8GOCgUPHuJB460cg9Q_kAqLWfx8,4810
|
|
33
|
+
pkg_auth/authorization/adapters/django_orm/models.py,sha256=tPfu7UXEMuGueQsEiruMivX8ZPCW9E7Xahjpy1VEU94,6633
|
|
34
|
+
pkg_auth/authorization/adapters/django_orm/repositories/__init__.py,sha256=StguAKeqZXOuVDhmzm6dfeN7wp8swz74O6Kd7idz-F8,720
|
|
35
|
+
pkg_auth/authorization/adapters/django_orm/repositories/membership.py,sha256=r0dJ9Jk56dFtvNmJz-41qgXsoeS8Y5-fDIwXirGwnrg,3839
|
|
36
|
+
pkg_auth/authorization/adapters/django_orm/repositories/organization.py,sha256=9MLUugCqsae4spQpPQ-cQ-brmKv99DlDC0aTNQgfZRo,2631
|
|
37
|
+
pkg_auth/authorization/adapters/django_orm/repositories/organization_service.py,sha256=N-OLsHVGyy8_Fx92cGAheqnGFHjeecMTN096zpJrujM,2376
|
|
38
|
+
pkg_auth/authorization/adapters/django_orm/repositories/permission_catalog.py,sha256=Tl6u-jKVVXrbODFBsLo-nYGbsJBVg_rqL5dn6miPo-E,3231
|
|
39
|
+
pkg_auth/authorization/adapters/django_orm/repositories/role.py,sha256=pXdsKG004M_26g-FXMeAoM_c5pEKPOaQeJQxaF-6FEY,3896
|
|
40
|
+
pkg_auth/authorization/adapters/django_orm/repositories/service.py,sha256=EfIxJAEvvnlbTfvq4ZLrT_DgK5MDv7-gBa9L_x69fQI,2101
|
|
41
|
+
pkg_auth/authorization/adapters/django_orm/repositories/user.py,sha256=mSw3TU3cIfzvp8devH4zaDZxwjXitCBfV6wG0h5rGUQ,2467
|
|
42
|
+
pkg_auth/authorization/adapters/sqlalchemy/__init__.py,sha256=YrYsBwOCp-XxTlwmd_wsD43DAybvklUXGbJQhrk86C4,2621
|
|
43
|
+
pkg_auth/authorization/adapters/sqlalchemy/base.py,sha256=e3-OeqT_lSNMFzsJMhcfusKjahUVEfCjZL-m0VWAdi8,1988
|
|
44
|
+
pkg_auth/authorization/adapters/sqlalchemy/mixins.py,sha256=CSf9Uo2POfQq72nprHlsSUAe4mW-E7kw8EZxQAe4AWc,6324
|
|
45
|
+
pkg_auth/authorization/adapters/sqlalchemy/models.py,sha256=5ywCymfzzL6qhzFDRLvY5w9IYK3tKcApy8oHQm-Jjbk,8003
|
|
46
|
+
pkg_auth/authorization/adapters/sqlalchemy/migrations/__init__.py,sha256=vPLhCk568lV86IasOo4rEbHaXETwTN8gxYA1dhVLNwg,45
|
|
47
|
+
pkg_auth/authorization/adapters/sqlalchemy/migrations/versions/20260410_0001_initial_schema.py,sha256=f3xZGEqRLdtwzW1V83MQAa2JSL7AJ8-ezml5dXlA0WE,10029
|
|
48
|
+
pkg_auth/authorization/adapters/sqlalchemy/migrations/versions/20260412_0002_add_permission_is_platform.py,sha256=ykew9uG3vQvxMyH6TTTnuS6hnySMbWeJ_h9rIvYpVRU,1056
|
|
49
|
+
pkg_auth/authorization/adapters/sqlalchemy/migrations/versions/20260620_0003_permission_visibility.py,sha256=s2J8X-RIHQM8wSATmclBl0aHUv9bMceSVbabt9hJF1Q,1874
|
|
50
|
+
pkg_auth/authorization/adapters/sqlalchemy/migrations/versions/20260620_0004_permission_description_jsonb.py,sha256=HojPzi79eDc0NMTqOrBIzxBl8wVr_BOX4jVFFodWOpk,1578
|
|
51
|
+
pkg_auth/authorization/adapters/sqlalchemy/migrations/versions/20260620_0005_services_tables.py,sha256=CSDVMKpAYIRtsikPNQow6APQtbx_LWbGs2YSQ3zVsuw,3440
|
|
52
|
+
pkg_auth/authorization/adapters/sqlalchemy/migrations/versions/__init__.py,sha256=Lc0QV0ti8nqP6Z0nCsWRDmK5TEWaeJRz9DR88VbcyY4,73
|
|
53
|
+
pkg_auth/authorization/adapters/sqlalchemy/repositories/__init__.py,sha256=duKdKkcbZn2gIqOy5vYiXQuuSVUtgcN_6tswSpMMVuM,571
|
|
54
|
+
pkg_auth/authorization/adapters/sqlalchemy/repositories/membership.py,sha256=LgXpvvw099VioEFJQSpgYgKVk5vOQCcaeM44VPtI5Po,5143
|
|
55
|
+
pkg_auth/authorization/adapters/sqlalchemy/repositories/organization.py,sha256=Gw5WUuuWoa3XMN7TVhd1aTVMshT-T5fFWwx2R3ZHtD4,3556
|
|
56
|
+
pkg_auth/authorization/adapters/sqlalchemy/repositories/organization_service.py,sha256=huNY2Q9Jg0Dy1tBO7cSUNWTflHXuOV4VVuVil7d4j4w,3731
|
|
57
|
+
pkg_auth/authorization/adapters/sqlalchemy/repositories/permission_catalog.py,sha256=ECTdXKaWvuK7VGQgS8Ms8VXehzotpue9aLf5a-XXRgw,4489
|
|
58
|
+
pkg_auth/authorization/adapters/sqlalchemy/repositories/role.py,sha256=jxS6DCBfLo4jtf65Y3YI81_0Ex_3ob3YAFZzougofW4,5925
|
|
59
|
+
pkg_auth/authorization/adapters/sqlalchemy/repositories/service.py,sha256=vP5fSoEC1eT26ZwsjcUkd2CFlReDj9pIp_Va5VrnTz4,3534
|
|
60
|
+
pkg_auth/authorization/adapters/sqlalchemy/repositories/user.py,sha256=04tS9cdKAgj4MEoOBuxYFepNd1NMtSCEsbmAmrlKmes,2430
|
|
61
|
+
pkg_auth/authorization/application/__init__.py,sha256=clRG0-N0xMg7AONQR4_sMGHFnqjPYefzO0qezvcDaZ0,51
|
|
62
|
+
pkg_auth/authorization/application/use_cases/__init__.py,sha256=0Hh6G-ojeIPJ5coQRp-16vhAOlkofUPflzEX6xDPZBw,31
|
|
63
|
+
pkg_auth/authorization/application/use_cases/_helpers.py,sha256=e0NehT0LNEXx7wc2re4wWQjW9EqRe3j5ho6M8fCXTHI,2786
|
|
64
|
+
pkg_auth/authorization/application/use_cases/check_permission.py,sha256=GrFA7UgIeUFZfolptcEAaIlO74xkvl1IKMXNTWrNucU,699
|
|
65
|
+
pkg_auth/authorization/application/use_cases/create_organization.py,sha256=GBGnDe8g4T0ZIQqaaO4aRrC9ZARx_pis5P_4kOet3-0,1570
|
|
66
|
+
pkg_auth/authorization/application/use_cases/create_role.py,sha256=Y5X-KNQCDibb2mTXAj3U4rNQE0vf-p_ILeQVE5mgmvk,2333
|
|
67
|
+
pkg_auth/authorization/application/use_cases/delete_membership.py,sha256=_m_XXCT7k3bfrZj49zbJVLztwU880HmdiN2iOBdUQhw,613
|
|
68
|
+
pkg_auth/authorization/application/use_cases/delete_organization.py,sha256=SVWqCdUWOJ6-bX135gB_aqMpCeZL-NhFvxRY81-KwU8,621
|
|
69
|
+
pkg_auth/authorization/application/use_cases/delete_role.py,sha256=aWdeE19dkqorvqEMR3JnmG76cb-VyI2FQd7xTXstFFQ,715
|
|
70
|
+
pkg_auth/authorization/application/use_cases/list_user_organizations.py,sha256=ycfsKh5Iof7RbmYS81EyU1IyUsH9SlFWGwHgVVS3lOI,626
|
|
71
|
+
pkg_auth/authorization/application/use_cases/provision_default_services.py,sha256=o27wgHvouvJ7_y6nuZnZ2ik3fQ1t410hKijWZJPfU1g,1369
|
|
72
|
+
pkg_auth/authorization/application/use_cases/register_permission_catalog.py,sha256=V7woKRAyKfHWV_TkfwL_G5rYtT6_AaI-adVR12dhwQU,4369
|
|
73
|
+
pkg_auth/authorization/application/use_cases/resolve_auth_context.py,sha256=rPRnZaV7lKo5YABiv-MC419PdR6U740qN3P_3bfADY0,2841
|
|
74
|
+
pkg_auth/authorization/application/use_cases/resolve_user_from_jwt.py,sha256=kjBr0Qwwy0ObLcoH6CsBuYXoGh8Ae3a9CULqJ64AhUo,1242
|
|
75
|
+
pkg_auth/authorization/application/use_cases/set_organization_service.py,sha256=XQeb-TAIoIK80bMuS6EW5pBLl45z2CIbR5Yhg_nkvqk,1725
|
|
76
|
+
pkg_auth/authorization/application/use_cases/sync_permission_catalog.py,sha256=wNSaBxPgB9TIaoW1eB9xXZkptF_dV28BjvNEc6m7Oaw,2789
|
|
77
|
+
pkg_auth/authorization/application/use_cases/sync_service_catalog.py,sha256=Sqr--6wpDCo-joFvMz7Hubm3b15ceyjas743pcEslmw,2893
|
|
78
|
+
pkg_auth/authorization/application/use_cases/sync_user_from_jwt.py,sha256=eGy78u3PGyFmT3FbQxZETyqmn2-uVSmcwi1frZq010w,871
|
|
79
|
+
pkg_auth/authorization/application/use_cases/update_organization.py,sha256=N2yYeW2GgVdg2mNbvhP4m6rHocmYupyU8_d4w9Z3_QI,967
|
|
80
|
+
pkg_auth/authorization/application/use_cases/update_role.py,sha256=KbDQRo2NrOceOR5ikqGn1qlE1goHVaJbjwj17Ut0X-c,2087
|
|
81
|
+
pkg_auth/authorization/application/use_cases/upsert_membership.py,sha256=HkCXI0nEy1INsex5wou_6JvWNey6sDv7Hr6-nCqPLII,1559
|
|
82
|
+
pkg_auth/authorization/cli/__init__.py,sha256=yegbFIXOk-DLPjGQJuQtTfL_PTmC2TZglAnsLQZ1b78,53
|
|
83
|
+
pkg_auth/authorization/cli/sync_catalog.py,sha256=JJvJ907aSe41VLAEgP2Cup1dkZ6QBS0r3rYxAyjWCRc,5820
|
|
84
|
+
pkg_auth/authorization/cli/sync_services.py,sha256=HG60WlruD5aiXt3QSLmHQG0Cyts-1HmVYvVI_UIEaQI,4964
|
|
85
|
+
pkg_auth/authorization/domain/__init__.py,sha256=iCXr9ZV7LqKZr0pNcyiggcPPGr7T_-Eg4UY0qnywQlo,79
|
|
86
|
+
pkg_auth/authorization/domain/entities.py,sha256=ImjlwLaxnz0ptWj0zWe1c20pKf8n3JLk5m8Gj6r07pQ,6249
|
|
87
|
+
pkg_auth/authorization/domain/exceptions.py,sha256=abJucoInylc5EGyInYsLrIbp90UHg24NliJvdv90PXo,2145
|
|
88
|
+
pkg_auth/authorization/domain/ports.py,sha256=tNt1j7gQ7wL4UtoBsfbgKl2C78no7qvwbPFwwLFlYlw,7327
|
|
89
|
+
pkg_auth/authorization/domain/value_objects.py,sha256=HSDUCticFuLl-gIA2xtLAR4LSO6uKfTGLsnaR0bkI_0,6693
|
|
90
|
+
pkg_auth/integrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
91
|
+
pkg_auth/integrations/django/__init__.py,sha256=ViunP2eC1KUCbYlsagKpfpcOJEKg99SCwyDdVoQGfe4,1093
|
|
92
|
+
pkg_auth/integrations/django/apps.py,sha256=vNXF6vqstShEpXtDTA4HMGK4FdYOiO5gNx65nPgH_7Q,292
|
|
93
|
+
pkg_auth/integrations/django/auth_context_middleware.py,sha256=iHXfUtATAUmECC6a2Pua6rjvqs81Ke5m9pZprD5v_4s,3863
|
|
94
|
+
pkg_auth/integrations/django/decorators.py,sha256=LU7OcVzQrezzDS6sGIqXZTzhFJOepQVGeibAUT1n5hU,2320
|
|
95
|
+
pkg_auth/integrations/django/install.py,sha256=DpSwSZ40DwNe6rAHYl0JCK2Q-u2ZlrAIvyLXIBS0cf4,5076
|
|
96
|
+
pkg_auth/integrations/django/middleware.py,sha256=tUNRnFkHxsvHkqtgr4hH17OUOTX0uBhGsJ0PiexIERQ,2268
|
|
97
|
+
pkg_auth/integrations/fastapi/__init__.py,sha256=4SBK9gZdFh9ZXhu1dy7swvqB7-G6VAxPblHQ5rnQqs4,814
|
|
98
|
+
pkg_auth/integrations/fastapi/auth_context_dep.py,sha256=T3384N7Ex5fNyBUWCf_o9Sv6_NcXz-eJV2x9BBBMWnU,5733
|
|
99
|
+
pkg_auth/integrations/fastapi/auth_factory.py,sha256=G8enX_hisGItbLBkSd9dMXp4cTxazuEzHIy5E_IG0Hc,2804
|
|
100
|
+
pkg_auth/integrations/fastapi/decorators.py,sha256=vgME0__QrZwWtFTsyve6vL3NxMp9pGANxN4OEobIHeo,1653
|
|
101
|
+
pkg_auth/integrations/fastapi/errors.py,sha256=mhcpBYrkmrwuaHoiRvBIiA73Eps9EbV1em1E8e7kXTw,2383
|
|
102
|
+
pkg_auth/integrations/fastapi/identity_dep.py,sha256=9pHY9GEIT6jo0XEtbP8FsP-_0LHZ5YZaEbxJj9JDh-A,1218
|
|
103
|
+
pkg_auth/integrations/strawberry/__init__.py,sha256=3nOveRw8rOGQsiCaKvWO4Vtfxt6Ft23QuqJN_6HHzEQ,593
|
|
104
|
+
pkg_auth/integrations/strawberry/auth.py,sha256=DE3wPKa1i1sfyIJUzMg2fnyKQnWIBu76290y0jkX2Z4,4642
|
|
105
|
+
pkg_auth/integrations/strawberry/permissions.py,sha256=MmD8_RuQz7MUk1j_bmVBWY2XL4J6uuWSZRaR0ztGvnI,1806
|
|
106
|
+
pkg_auth-3.0.0.dist-info/METADATA,sha256=eYMYwFJNjLqJ9tjR2_qGuePfJufHTzfLs4SxPYo95p4,5699
|
|
107
|
+
pkg_auth-3.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
108
|
+
pkg_auth-3.0.0.dist-info/entry_points.txt,sha256=cbN-pxVE5BuYAb5LVfurD84I9S1gBe9_Zdd2jGnvtyY,205
|
|
109
|
+
pkg_auth-3.0.0.dist-info/top_level.txt,sha256=xEdLnVj1Gbyv7q5dJSiWASWs-soG3RKfSuP2HHdcUNY,9
|
|
110
|
+
pkg_auth-3.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pkg_auth
|