apexauthlib 0.1.9__py3-none-any.whl → 0.1.11__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.
@@ -1,4 +1,7 @@
1
+ from __future__ import annotations
2
+
1
3
  from dataclasses import dataclass, field
4
+ from enum import Enum
2
5
  from typing import Any, Generic, TypeVar
3
6
  from uuid import uuid4
4
7
 
@@ -48,3 +51,30 @@ class ServiceUserInfo(Generic[ItemT]):
48
51
  user: User
49
52
  is_service_admin: bool
50
53
  metadata: ItemT
54
+
55
+
56
+ @dataclass(frozen=True)
57
+ class ServicePermission:
58
+ name: str
59
+ label: str
60
+ description: str
61
+ type: FieldType
62
+ default: str | int | bool
63
+
64
+ values: list[str] | list[int] = field(default_factory=list)
65
+ id: str = field(default_factory=lambda: str(uuid4()))
66
+
67
+
68
+ class FieldType(str, Enum):
69
+ string = "string"
70
+ integer = "integer"
71
+ boolean = "boolean"
72
+
73
+ def valid_type(self) -> type:
74
+ if self.value == FieldType.string:
75
+ return str
76
+ if self.value == FieldType.integer:
77
+ return int
78
+ if self.value == FieldType.boolean:
79
+ return bool
80
+ raise ValueError(f"Invalid type {self.value}")
@@ -7,7 +7,8 @@ from apexdevkit.formatter import DataclassFormatter, Formatter
7
7
  from apexdevkit.http import FluentHttp, JsonDict
8
8
 
9
9
  from apexauthlib.entities import User
10
- from apexauthlib.entities.auth import ItemT, ServiceUserInfo
10
+ from apexauthlib.entities.auth import ItemT, ServicePermission, ServiceUserInfo
11
+ from apexauthlib.integration.formatter import ServicePermissionFormatter
11
12
 
12
13
 
13
14
  @dataclass(frozen=True)
@@ -118,6 +119,59 @@ class AuthApi(Generic[ItemT]):
118
119
  metadata=self.formatter.load(raw_user["metadata"]),
119
120
  )
120
121
 
122
+ def update_permissions(
123
+ self, permissions: list[ServicePermission]
124
+ ) -> list[ServicePermission]:
125
+ existing = {
126
+ permission.name: permission
127
+ for permission in self._retrieve_existing_permissions()
128
+ }
129
+ new = {new_permission.name: new_permission for new_permission in permissions}
130
+
131
+ for current, current_permission in existing.items():
132
+ if current not in new.keys():
133
+ self._delete_permission(current_permission.id)
134
+
135
+ for current, current_permission in new.items():
136
+ if current not in existing.keys():
137
+ self._create_permission(current_permission)
138
+
139
+ return self._retrieve_existing_permissions()
140
+
141
+ def _retrieve_existing_permissions(self) -> list[ServicePermission]:
142
+ existing = list(
143
+ JsonDict(
144
+ (
145
+ self.http.with_header("Authorization", f"Bearer {self.token}")
146
+ .get()
147
+ .on_endpoint(f"/services/{self.service_name}/permissions")
148
+ .on_failure(raises=RuntimeError)
149
+ .json()
150
+ )
151
+ )["data"]["permissions"]
152
+ )
153
+
154
+ return [ServicePermissionFormatter().load(item) for item in existing]
155
+
156
+ def _delete_permission(self, permission_id: str) -> None:
157
+ (
158
+ self.http.with_header("Authorization", f"Bearer {self.token}")
159
+ .delete()
160
+ .on_endpoint(f"/services/{self.service_name}/permissions/{permission_id}")
161
+ .on_failure(raises=RuntimeError)
162
+ .json()
163
+ )
164
+
165
+ def _create_permission(self, permission: ServicePermission) -> None:
166
+ (
167
+ self.http.with_header("Authorization", f"Bearer {self.token}")
168
+ .with_json(JsonDict(ServicePermissionFormatter().dump(permission)))
169
+ .post()
170
+ .on_endpoint(f"/services/{self.service_name}/permissions")
171
+ .on_failure(raises=RuntimeError)
172
+ .json()
173
+ )
174
+
121
175
 
122
176
  @dataclass
123
177
  class AuthCodeApi:
@@ -0,0 +1,58 @@
1
+ from dataclasses import dataclass
2
+ from typing import Any, Mapping
3
+
4
+ from apexauthlib.entities.auth import FieldType, ServicePermission
5
+
6
+
7
+ @dataclass(frozen=True)
8
+ class ServicePermissionFormatter:
9
+ def dump(self, permission: ServicePermission) -> Mapping[str, Any]:
10
+ raw = {
11
+ "name": permission.name,
12
+ "description": permission.description,
13
+ "label": permission.label,
14
+ "type": permission.type.value,
15
+ "values": permission.values,
16
+ "default": permission.default,
17
+ }
18
+
19
+ if permission.id.isdigit():
20
+ raw["id"] = permission.id
21
+
22
+ return raw
23
+
24
+ def load(self, raw: Mapping[str, Any]) -> ServicePermission:
25
+ permission_type = FieldType[str(raw["type"])]
26
+
27
+ return ServicePermission(
28
+ id=str(raw["id"]),
29
+ name=str(raw["name"]),
30
+ label=str(raw["label"]),
31
+ description=str(raw["description"]),
32
+ type=permission_type,
33
+ values=self._load_combos(permission_type, raw),
34
+ default=self._load_default(permission_type, raw),
35
+ )
36
+
37
+ def _load_combos(
38
+ self, permission_type: FieldType, raw: Mapping[str, Any]
39
+ ) -> list[str] | list[int]:
40
+ if permission_type == FieldType.boolean:
41
+ return []
42
+ if permission_type == FieldType.string:
43
+ return [str(item) for item in list(raw["values"])]
44
+ if permission_type == FieldType.integer:
45
+ return [int(item) for item in list(raw["values"])]
46
+
47
+ raise ValueError(f"Unexpected type: {permission_type}")
48
+
49
+ def _load_default(
50
+ self, permission_type: FieldType, raw: Mapping[str, Any]
51
+ ) -> str | int | bool:
52
+ if permission_type == FieldType.string:
53
+ return str(raw["default"])
54
+ if permission_type == FieldType.integer:
55
+ return int(raw["default"])
56
+ if permission_type == FieldType.boolean:
57
+ return bool(raw["default"])
58
+ raise ValueError(f"Unexpected type: {permission_type}")
@@ -0,0 +1,132 @@
1
+ Metadata-Version: 2.4
2
+ Name: apexauthlib
3
+ Version: 0.1.11
4
+ Summary: Apex authorization library for services
5
+ License-File: LICENSE
6
+ Author: Apex Dev
7
+ Author-email: dev@apex.ge
8
+ Requires-Python: >=3.11
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.12
11
+ Classifier: Programming Language :: Python :: 3.13
12
+ Classifier: Programming Language :: Python :: 3.14
13
+ Requires-Dist: apexdevkit
14
+ Requires-Dist: fastapi (==0.120.*)
15
+ Requires-Dist: httpx
16
+ Requires-Dist: uvicorn
17
+ Description-Content-Type: text/markdown
18
+
19
+ # apexauthlib
20
+
21
+ Private Python library for integrating backend services with the company auth-service.
22
+
23
+ This library provides:
24
+ - **Entities** used across services (user, service, client, service metadata, permissions).
25
+ - A **FastAPI router** (`auth_api`) with login endpoints and dependency helpers that inject the current user + metadata from JWT.
26
+ - A small **HTTP client wrapper** (`AuthApiProvider` / `AuthApi`) for services to:
27
+ - obtain a token (password login or OAuth code exchange),
28
+ - fetch current user and service-scoped metadata,
29
+ - list users for a service,
30
+ - and register/update service permissions.
31
+
32
+ > Notes
33
+ > - This documentation is best used along with the documentation for auth-service.
34
+ > - This is a **private library**. If you change it, you should push to GitHub and then update downstream usage by running `make install` wherever it’s consumed (per team workflow).
35
+
36
+ ---
37
+
38
+ ## Concepts at a glance
39
+
40
+ ### Users
41
+ The `User` entity includes:
42
+ - `hashed_password`: **must never expose an actual password** through APIs (except in create/update requests handled elsewhere).
43
+ - `is_admin`: indicates whether the user is a “superadmin”.
44
+
45
+ ### Service-scoped auth model
46
+ The auth model used by services is service-centric:
47
+ - Services have a name (`service_name`) and service admins.
48
+ - Each service can define **permissions** (schema) and **per-user metadata** values that should align with those permissions.
49
+
50
+ See: [`docs/concepts.md`](docs/concepts.md)
51
+
52
+ ---
53
+
54
+ ## Usage pattern (high-level)
55
+
56
+ ### 1) Create an `AuthApiProvider`
57
+
58
+ Example (from service code):
59
+
60
+ ```python
61
+ provider = AuthApiProvider[HaccpPermissions](
62
+ http=FluentHttp(
63
+ Httpx.Builder().with_url(AUTH_SERVICE_API).build(),
64
+ ),
65
+ service_name=SERVICE_NAME,
66
+ formatter=DataclassFormatter(HaccpPermissions),
67
+ )
68
+ ```
69
+
70
+ ### 2) Register permissions (service schema)
71
+
72
+ Example:
73
+
74
+ ```python
75
+ provider.for_token(
76
+ provider.login(ADMIN_USERNAME, ADMIN_PASSWORD)
77
+ ).update_permissions(
78
+ [
79
+ ServicePermission(
80
+ # ...
81
+ )
82
+ ]
83
+ )
84
+ ```
85
+
86
+ ### 3) Add FastAPI auth routes + dependencies
87
+
88
+ Example:
89
+
90
+ ```python
91
+ FastApiBuilder()
92
+ .with_title(TITLE)
93
+ .with_description(DESCRIPTION)
94
+ .with_version(VERSION)
95
+ .with_route(auth_api)
96
+ .with_dependency(
97
+ auth=provider,
98
+ auth_code=AuthCodeApi(
99
+ http=FluentHttp(
100
+ Httpx.Builder()
101
+ .with_url(AUTH_SERVICE_API)
102
+ .build(),
103
+ ),
104
+ client_id=CLIENT_ID,
105
+ client_secret=CLIENT_SECRET,
106
+ ),
107
+ )
108
+ .build()
109
+ ```
110
+
111
+ See: [`docs/fastapi.md`](docs/fastapi.md)
112
+
113
+ ## Known Limitations
114
+
115
+ ### Permission sync is name-based only
116
+
117
+ AuthApi.update_permissions() compares permissions by name and only creates/deletes.
118
+ If a permission keeps the same name but changes type, default, label, etc.,
119
+ it currently won’t be updated automatically.
120
+
121
+ ### Admin password dependency for permission registration
122
+
123
+ The example uses provider.login(ADMIN_USERNAME, ADMIN_PASSWORD) to obtain a token for updating permissions.
124
+ If the admin password changes, downstream services must update their configuration, or a more proper flow should
125
+ be introduced (e.g., service-to-service auth or code-based admin auth).
126
+
127
+ ### Refresh tokens are not currently handled in this library
128
+
129
+ Current flow assumes a bearer access token is provided/used. Supporting refresh tokens would enable
130
+ longer-lived sessions with automatic token renewal.
131
+
132
+
@@ -0,0 +1,12 @@
1
+ apexauthlib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ apexauthlib/entities/__init__.py,sha256=KghMo6a2QWdPzWlMqdGAKOaVvRmTCOIVhXtIHrX7A_M,149
3
+ apexauthlib/entities/auth.py,sha256=AY34GnOhVhu0WCrpp0LoJ__r6ou-hORFiVExvCyCzmE,1566
4
+ apexauthlib/fastapi/__init__.py,sha256=G77C3ZIqjsXdh4fiH5FZyF_hyzVtS3waJJGa_TBGMWE,103
5
+ apexauthlib/fastapi/auth.py,sha256=hkvG2BougydElkYbEpwsuSgRoLOLANe6DbVSoa-zBRM,3917
6
+ apexauthlib/integration/__init__.py,sha256=f2lGbyoGct4kpZ2CUTExHhtQHs-1YR_xanvrj9Y4GiI,87
7
+ apexauthlib/integration/api.py,sha256=iSjaq6FrLOvnC86Y4DtV8O2fNqy09Tt5HjyAp707C4I,6478
8
+ apexauthlib/integration/formatter.py,sha256=DyVRMWYkZErZBeP_f4aYdSbe9g9zck-x2ONFPxwVNfY,2037
9
+ apexauthlib-0.1.11.dist-info/METADATA,sha256=2iH1MMTn76woy7R_XuungNbggbxFuhkuBv8bBf00mik,3922
10
+ apexauthlib-0.1.11.dist-info/WHEEL,sha256=kJCRJT_g0adfAJzTx2GUMmS80rTJIVHRCfG0DQgLq3o,88
11
+ apexauthlib-0.1.11.dist-info/licenses/LICENSE,sha256=iai0ILQTDgUXV1cIXl0UzSeOdFpMFK3shn5aqnz_Uro,1065
12
+ apexauthlib-0.1.11.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 2.1.3
2
+ Generator: poetry-core 2.3.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,19 +0,0 @@
1
- Metadata-Version: 2.3
2
- Name: apexauthlib
3
- Version: 0.1.9
4
- Summary: Apex authorization library for services
5
- Author: Apex Dev
6
- Author-email: dev@apex.ge
7
- Requires-Python: >=3.11
8
- Classifier: Programming Language :: Python :: 3
9
- Classifier: Programming Language :: Python :: 3.12
10
- Classifier: Programming Language :: Python :: 3.13
11
- Requires-Dist: apexdevkit
12
- Requires-Dist: fastapi
13
- Requires-Dist: httpx
14
- Requires-Dist: uvicorn
15
- Description-Content-Type: text/markdown
16
-
17
- # apexauthlib
18
- Central authorization library for apex services
19
-
@@ -1,11 +0,0 @@
1
- apexauthlib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- apexauthlib/entities/__init__.py,sha256=KghMo6a2QWdPzWlMqdGAKOaVvRmTCOIVhXtIHrX7A_M,149
3
- apexauthlib/entities/auth.py,sha256=Zyme1bgQEW1SHLeHSjTaY8E4yd12EIZOEOE3cICZOAo,843
4
- apexauthlib/fastapi/__init__.py,sha256=G77C3ZIqjsXdh4fiH5FZyF_hyzVtS3waJJGa_TBGMWE,103
5
- apexauthlib/fastapi/auth.py,sha256=hkvG2BougydElkYbEpwsuSgRoLOLANe6DbVSoa-zBRM,3917
6
- apexauthlib/integration/__init__.py,sha256=f2lGbyoGct4kpZ2CUTExHhtQHs-1YR_xanvrj9Y4GiI,87
7
- apexauthlib/integration/api.py,sha256=c94T9Z7IPM0K1zKDjex6fr9TNOtE3ZLHnjT2x8kOodY,4372
8
- apexauthlib-0.1.9.dist-info/LICENSE,sha256=iai0ILQTDgUXV1cIXl0UzSeOdFpMFK3shn5aqnz_Uro,1065
9
- apexauthlib-0.1.9.dist-info/METADATA,sha256=jTSqo4KFSZGv2Ew58Dm52xQq6myqcAp7vbfTS667C64,518
10
- apexauthlib-0.1.9.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
11
- apexauthlib-0.1.9.dist-info/RECORD,,