zrb 1.0.0b8__py3-none-any.whl → 1.0.0b10__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.
- zrb/__main__.py +3 -0
- zrb/builtin/project/add/fastapp/fastapp_task.py +1 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/.coveragerc +11 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/.gitignore +4 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/column/add_column_task.py +4 -4
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/config.py +5 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/add_entity_task.py +108 -1
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/add_entity_util.py +67 -4
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/module/my_module/service/my_entity/my_entity_service.py +5 -5
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/schema/my_entity.py +1 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/test/my_module/my_entity/test_create_my_entity.py +53 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/test/my_module/my_entity/test_delete_my_entity.py +62 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/test/my_module/my_entity/test_read_my_entity.py +65 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/app_template/test/my_module/my_entity/test_update_my_entity.py +61 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/template/gateway_subroute.py +57 -13
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/input.py +8 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/add_module_util.py +2 -2
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/gateway/subroute/my_module.py +6 -1
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/module_task_definition.py +10 -6
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/task.py +65 -14
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/task_util.py +106 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/util.py +6 -86
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/base_db_repository.py +27 -11
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/base_service.py +140 -51
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/error.py +15 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/util/parser.py +1 -1
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/config.py +22 -4
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/client/auth_client.py +21 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/migration/versions/3093c7336477_add_auth_tables.py +106 -61
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/migration/versions/8ed025bcc845_create_permissions.py +69 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/migration_metadata.py +3 -4
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/route.py +15 -14
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/permission/permission_service.py +4 -4
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/role/repository/role_db_repository.py +24 -5
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/role/role_service.py +14 -12
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/repository/user_db_repository.py +134 -97
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/repository/user_repository.py +28 -11
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/user_service.py +215 -13
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/user_service_factory.py +30 -2
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/subroute/auth.py +216 -41
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/util/auth.py +57 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/requirements.txt +7 -1
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/permission.py +2 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/role.py +13 -12
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/user.py +64 -12
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/_util/access_token.py +19 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/auth/permission/test_create_permission.py +59 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/auth/permission/test_delete_permission.py +68 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/auth/permission/test_read_permission.py +71 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/auth/permission/test_update_permission.py +66 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/auth/test_user_session.py +195 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/test_health_and_readiness.py +28 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/test_homepage.py +17 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test/test_not_found_error.py +16 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/test.sh +7 -0
- zrb/task/base_task.py +10 -10
- zrb/task/cmd_task.py +2 -5
- zrb/util/cmd/command.py +39 -48
- zrb/util/codemod/modification_mode.py +3 -0
- zrb/util/codemod/modify_class.py +58 -0
- zrb/util/codemod/modify_class_parent.py +68 -0
- zrb/util/codemod/modify_class_property.py +128 -0
- zrb/util/codemod/modify_dict.py +75 -0
- zrb/util/codemod/modify_function.py +65 -0
- zrb/util/codemod/modify_function_call.py +68 -0
- zrb/util/codemod/modify_method.py +88 -0
- zrb/util/codemod/{prepend_code_to_module.py → modify_module.py} +2 -3
- zrb/util/file.py +3 -2
- {zrb-1.0.0b8.dist-info → zrb-1.0.0b10.dist-info}/METADATA +2 -1
- {zrb-1.0.0b8.dist-info → zrb-1.0.0b10.dist-info}/RECORD +72 -55
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/migrate.py +0 -3
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/session.py +0 -48
- zrb/util/codemod/append_code_to_class.py +0 -35
- zrb/util/codemod/append_code_to_function.py +0 -38
- zrb/util/codemod/append_code_to_method.py +0 -55
- zrb/util/codemod/append_key_to_dict.py +0 -51
- zrb/util/codemod/append_param_to_function_call.py +0 -39
- zrb/util/codemod/prepend_parent_to_class.py +0 -38
- zrb/util/codemod/prepend_property_to_class.py +0 -55
- {zrb-1.0.0b8.dist-info → zrb-1.0.0b10.dist-info}/WHEEL +0 -0
- {zrb-1.0.0b8.dist-info → zrb-1.0.0b10.dist-info}/entry_points.txt +0 -0
zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/gateway/subroute/auth.py
CHANGED
@@ -1,5 +1,14 @@
|
|
1
|
-
from
|
1
|
+
from typing import Annotated
|
2
|
+
|
3
|
+
from fastapi import Depends, FastAPI, Response
|
4
|
+
from fastapi.security import OAuth2PasswordRequestForm
|
5
|
+
from my_app_name.common.error import ForbiddenError
|
2
6
|
from my_app_name.module.auth.client.auth_client_factory import auth_client
|
7
|
+
from my_app_name.module.gateway.util.auth import (
|
8
|
+
get_current_user,
|
9
|
+
set_user_session_cookie,
|
10
|
+
unset_user_session_cookie,
|
11
|
+
)
|
3
12
|
from my_app_name.schema.permission import (
|
4
13
|
MultiplePermissionResponse,
|
5
14
|
PermissionCreate,
|
@@ -13,202 +22,368 @@ from my_app_name.schema.role import (
|
|
13
22
|
RoleUpdateWithPermissions,
|
14
23
|
)
|
15
24
|
from my_app_name.schema.user import (
|
25
|
+
AuthUserResponse,
|
16
26
|
MultipleUserResponse,
|
17
27
|
UserCreateWithRoles,
|
28
|
+
UserCredentials,
|
18
29
|
UserResponse,
|
30
|
+
UserSessionResponse,
|
19
31
|
UserUpdateWithRoles,
|
20
32
|
)
|
21
33
|
|
22
34
|
|
23
35
|
def serve_auth_route(app: FastAPI):
|
24
36
|
|
37
|
+
@app.post("/api/v1/user-sessions", response_model=UserSessionResponse)
|
38
|
+
async def create_user_session(
|
39
|
+
response: Response, form_data: Annotated[OAuth2PasswordRequestForm, Depends()]
|
40
|
+
) -> UserSessionResponse:
|
41
|
+
user_session = await auth_client.create_user_session(
|
42
|
+
UserCredentials(
|
43
|
+
username=form_data.username,
|
44
|
+
password=form_data.password,
|
45
|
+
)
|
46
|
+
)
|
47
|
+
set_user_session_cookie(response, user_session)
|
48
|
+
return user_session
|
49
|
+
|
50
|
+
@app.put("/api/v1/user-sessions", response_model=UserSessionResponse)
|
51
|
+
async def update_user_session(
|
52
|
+
response: Response, refresh_token: str
|
53
|
+
) -> UserSessionResponse:
|
54
|
+
user_session = await auth_client.update_user_session(refresh_token)
|
55
|
+
set_user_session_cookie(response, user_session)
|
56
|
+
return user_session
|
57
|
+
|
58
|
+
@app.delete("/api/v1/user-sessions", response_model=UserSessionResponse)
|
59
|
+
async def delete_user_session(
|
60
|
+
response: Response, refresh_token: str
|
61
|
+
) -> UserSessionResponse:
|
62
|
+
user_session = await auth_client.delete_user_session(refresh_token)
|
63
|
+
unset_user_session_cookie(response)
|
64
|
+
return user_session
|
65
|
+
|
25
66
|
# Permission routes
|
26
67
|
|
27
68
|
@app.get("/api/v1/permissions", response_model=MultiplePermissionResponse)
|
28
69
|
async def get_permissions(
|
70
|
+
current_user: Annotated[AuthUserResponse, Depends(get_current_user)],
|
29
71
|
page: int = 1,
|
30
72
|
page_size: int = 10,
|
31
73
|
sort: str | None = None,
|
32
74
|
filter: str | None = None,
|
33
75
|
) -> MultiplePermissionResponse:
|
76
|
+
if not current_user.has_permission("permission:read"):
|
77
|
+
raise ForbiddenError("Access denied")
|
34
78
|
return await auth_client.get_permissions(
|
35
79
|
page=page, page_size=page_size, sort=sort, filter=filter
|
36
80
|
)
|
37
81
|
|
38
82
|
@app.get("/api/v1/permissions/{permission_id}", response_model=PermissionResponse)
|
39
|
-
async def get_permission_by_id(
|
83
|
+
async def get_permission_by_id(
|
84
|
+
current_user: Annotated[AuthUserResponse, Depends(get_current_user)],
|
85
|
+
permission_id: str,
|
86
|
+
) -> PermissionResponse:
|
87
|
+
if not current_user.has_permission("permission:read"):
|
88
|
+
raise ForbiddenError("Access denied")
|
40
89
|
return await auth_client.get_permission_by_id(permission_id)
|
41
90
|
|
42
91
|
@app.post(
|
43
92
|
"/api/v1/permissions/bulk",
|
44
93
|
response_model=list[PermissionResponse],
|
45
94
|
)
|
46
|
-
async def create_permission_bulk(
|
95
|
+
async def create_permission_bulk(
|
96
|
+
current_user: Annotated[AuthUserResponse, Depends(get_current_user)],
|
97
|
+
data: list[PermissionCreate],
|
98
|
+
) -> list[PermissionResponse]:
|
99
|
+
if not current_user.has_permission("permission:create"):
|
100
|
+
raise ForbiddenError("Access denied")
|
47
101
|
return await auth_client.create_permission_bulk(
|
48
|
-
[row.with_audit(created_by=
|
102
|
+
[row.with_audit(created_by=current_user.id) for row in data]
|
49
103
|
)
|
50
104
|
|
51
105
|
@app.post(
|
52
106
|
"/api/v1/permissions",
|
53
107
|
response_model=PermissionResponse,
|
54
108
|
)
|
55
|
-
async def create_permission(
|
56
|
-
|
109
|
+
async def create_permission(
|
110
|
+
current_user: Annotated[AuthUserResponse, Depends(get_current_user)],
|
111
|
+
data: PermissionCreate,
|
112
|
+
) -> PermissionResponse:
|
113
|
+
if not current_user.has_permission("permission:create"):
|
114
|
+
raise ForbiddenError("Access denied")
|
115
|
+
return await auth_client.create_permission(
|
116
|
+
data.with_audit(created_by=current_user.id)
|
117
|
+
)
|
57
118
|
|
58
119
|
@app.put(
|
59
120
|
"/api/v1/permissions/bulk",
|
60
121
|
response_model=list[PermissionResponse],
|
61
122
|
)
|
62
|
-
async def update_permission_bulk(
|
123
|
+
async def update_permission_bulk(
|
124
|
+
current_user: Annotated[AuthUserResponse, Depends(get_current_user)],
|
125
|
+
permission_ids: list[str],
|
126
|
+
data: PermissionUpdate,
|
127
|
+
) -> list[PermissionResponse]:
|
128
|
+
if not current_user.has_permission("permission:update"):
|
129
|
+
raise ForbiddenError("Access denied")
|
63
130
|
return await auth_client.update_permission_bulk(
|
64
|
-
permission_ids, data.with_audit(updated_by=
|
131
|
+
permission_ids, data.with_audit(updated_by=current_user.id)
|
65
132
|
)
|
66
133
|
|
67
134
|
@app.put(
|
68
135
|
"/api/v1/permissions/{permission_id}",
|
69
136
|
response_model=PermissionResponse,
|
70
137
|
)
|
71
|
-
async def update_permission(
|
72
|
-
|
138
|
+
async def update_permission(
|
139
|
+
current_user: Annotated[AuthUserResponse, Depends(get_current_user)],
|
140
|
+
permission_id: str,
|
141
|
+
data: PermissionUpdate,
|
142
|
+
) -> PermissionResponse:
|
143
|
+
if not current_user.has_permission("permission:update"):
|
144
|
+
raise ForbiddenError("Access denied")
|
145
|
+
return await auth_client.update_permission(
|
146
|
+
permission_id, data.with_audit(updated_by=current_user.id)
|
147
|
+
)
|
73
148
|
|
74
149
|
@app.delete(
|
75
150
|
"/api/v1/permissions/bulk",
|
76
151
|
response_model=list[PermissionResponse],
|
77
152
|
)
|
78
|
-
async def delete_permission_bulk(
|
153
|
+
async def delete_permission_bulk(
|
154
|
+
current_user: Annotated[AuthUserResponse, Depends(get_current_user)],
|
155
|
+
permission_ids: list[str],
|
156
|
+
) -> list[PermissionResponse]:
|
157
|
+
if not current_user.has_permission("permission:delete"):
|
158
|
+
raise ForbiddenError("Access denied")
|
79
159
|
return await auth_client.delete_permission_bulk(
|
80
|
-
permission_ids, deleted_by=
|
160
|
+
permission_ids, deleted_by=current_user.id
|
81
161
|
)
|
82
162
|
|
83
163
|
@app.delete(
|
84
164
|
"/api/v1/permissions/{permission_id}",
|
85
165
|
response_model=PermissionResponse,
|
86
166
|
)
|
87
|
-
async def delete_permission(
|
88
|
-
|
167
|
+
async def delete_permission(
|
168
|
+
current_user: Annotated[AuthUserResponse, Depends(get_current_user)],
|
169
|
+
permission_id: str,
|
170
|
+
) -> PermissionResponse:
|
171
|
+
if not current_user.has_permission("permission:delete"):
|
172
|
+
raise ForbiddenError("Access denied")
|
173
|
+
return await auth_client.delete_permission(
|
174
|
+
permission_id, deleted_by=current_user.id
|
175
|
+
)
|
89
176
|
|
90
177
|
# Role routes
|
91
178
|
|
92
179
|
@app.get("/api/v1/roles", response_model=MultipleRoleResponse)
|
93
180
|
async def get_roles(
|
181
|
+
current_user: Annotated[AuthUserResponse, Depends(get_current_user)],
|
94
182
|
page: int = 1,
|
95
183
|
page_size: int = 10,
|
96
184
|
sort: str | None = None,
|
97
185
|
filter: str | None = None,
|
98
186
|
) -> MultipleRoleResponse:
|
187
|
+
if not current_user.has_permission("role:read"):
|
188
|
+
raise ForbiddenError("Access denied")
|
99
189
|
return await auth_client.get_roles(
|
100
190
|
page=page, page_size=page_size, sort=sort, filter=filter
|
101
191
|
)
|
102
192
|
|
103
193
|
@app.get("/api/v1/roles/{role_id}", response_model=RoleResponse)
|
104
|
-
async def get_role_by_id(
|
194
|
+
async def get_role_by_id(
|
195
|
+
current_user: Annotated[AuthUserResponse, Depends(get_current_user)],
|
196
|
+
role_id: str,
|
197
|
+
) -> RoleResponse:
|
198
|
+
if not current_user.has_permission("role:read"):
|
199
|
+
raise ForbiddenError("Access denied")
|
105
200
|
return await auth_client.get_role_by_id(role_id)
|
106
201
|
|
107
202
|
@app.post(
|
108
203
|
"/api/v1/roles/bulk",
|
109
204
|
response_model=list[RoleResponse],
|
110
205
|
)
|
111
|
-
async def create_role_bulk(
|
206
|
+
async def create_role_bulk(
|
207
|
+
current_user: Annotated[AuthUserResponse, Depends(get_current_user)],
|
208
|
+
data: list[RoleCreateWithPermissions],
|
209
|
+
) -> list[RoleResponse]:
|
210
|
+
if not current_user.has_permission("role:create"):
|
211
|
+
raise ForbiddenError("Access denied")
|
112
212
|
return await auth_client.create_role_bulk(
|
113
|
-
[row.with_audit(created_by=
|
213
|
+
[row.with_audit(created_by=current_user.id) for row in data]
|
114
214
|
)
|
115
215
|
|
116
216
|
@app.post(
|
117
217
|
"/api/v1/roles",
|
118
218
|
response_model=RoleResponse,
|
119
219
|
)
|
120
|
-
async def create_role(
|
121
|
-
|
220
|
+
async def create_role(
|
221
|
+
current_user: Annotated[AuthUserResponse, Depends(get_current_user)],
|
222
|
+
data: RoleCreateWithPermissions,
|
223
|
+
) -> RoleResponse:
|
224
|
+
if not current_user.has_permission("role:create"):
|
225
|
+
raise ForbiddenError("Access denied")
|
226
|
+
return await auth_client.create_role(
|
227
|
+
data.with_audit(created_by=current_user.id)
|
228
|
+
)
|
122
229
|
|
123
230
|
@app.put(
|
124
231
|
"/api/v1/roles/bulk",
|
125
232
|
response_model=list[RoleResponse],
|
126
233
|
)
|
127
|
-
async def update_role_bulk(
|
234
|
+
async def update_role_bulk(
|
235
|
+
current_user: Annotated[AuthUserResponse, Depends(get_current_user)],
|
236
|
+
role_ids: list[str],
|
237
|
+
data: RoleUpdateWithPermissions,
|
238
|
+
) -> list[RoleResponse]:
|
239
|
+
if not current_user.has_permission("role:update"):
|
240
|
+
raise ForbiddenError("Access denied")
|
128
241
|
return await auth_client.update_role_bulk(
|
129
|
-
role_ids, data.with_audit(updated_by=
|
242
|
+
role_ids, data.with_audit(updated_by=current_user.id)
|
130
243
|
)
|
131
244
|
|
132
245
|
@app.put(
|
133
246
|
"/api/v1/roles/{role_id}",
|
134
247
|
response_model=RoleResponse,
|
135
248
|
)
|
136
|
-
async def update_role(
|
137
|
-
|
249
|
+
async def update_role(
|
250
|
+
current_user: Annotated[AuthUserResponse, Depends(get_current_user)],
|
251
|
+
role_id: str,
|
252
|
+
data: RoleUpdateWithPermissions,
|
253
|
+
) -> RoleResponse:
|
254
|
+
if not current_user.has_permission("role:update"):
|
255
|
+
raise ForbiddenError("Access denied")
|
256
|
+
return await auth_client.update_role(
|
257
|
+
role_id, data.with_audit(updated_by=current_user.id)
|
258
|
+
)
|
138
259
|
|
139
260
|
@app.delete(
|
140
261
|
"/api/v1/roles/bulk",
|
141
262
|
response_model=list[RoleResponse],
|
142
263
|
)
|
143
|
-
async def delete_role_bulk(
|
144
|
-
|
264
|
+
async def delete_role_bulk(
|
265
|
+
current_user: Annotated[AuthUserResponse, Depends(get_current_user)],
|
266
|
+
role_ids: list[str],
|
267
|
+
) -> list[RoleResponse]:
|
268
|
+
if not current_user.has_permission("role:delete"):
|
269
|
+
raise ForbiddenError("Access denied")
|
270
|
+
return await auth_client.delete_role_bulk(role_ids, deleted_by=current_user.id)
|
145
271
|
|
146
272
|
@app.delete(
|
147
273
|
"/api/v1/roles/{role_id}",
|
148
274
|
response_model=RoleResponse,
|
149
275
|
)
|
150
|
-
async def delete_role(
|
151
|
-
|
276
|
+
async def delete_role(
|
277
|
+
current_user: Annotated[AuthUserResponse, Depends(get_current_user)],
|
278
|
+
role_id: str,
|
279
|
+
) -> RoleResponse:
|
280
|
+
if not current_user.has_permission("role:delete"):
|
281
|
+
raise ForbiddenError("Access denied")
|
282
|
+
return await auth_client.delete_role(role_id, deleted_by=current_user.id)
|
152
283
|
|
153
284
|
# User routes
|
154
285
|
|
155
286
|
@app.get("/api/v1/users", response_model=MultipleUserResponse)
|
156
287
|
async def get_users(
|
288
|
+
current_user: Annotated[AuthUserResponse, Depends(get_current_user)],
|
157
289
|
page: int = 1,
|
158
290
|
page_size: int = 10,
|
159
291
|
sort: str | None = None,
|
160
292
|
filter: str | None = None,
|
161
293
|
) -> MultipleUserResponse:
|
294
|
+
if not current_user.has_permission("user:read"):
|
295
|
+
raise ForbiddenError("Access denied")
|
162
296
|
return await auth_client.get_users(
|
163
297
|
page=page, page_size=page_size, sort=sort, filter=filter
|
164
298
|
)
|
165
299
|
|
166
300
|
@app.get("/api/v1/users/{user_id}", response_model=UserResponse)
|
167
|
-
async def get_user_by_id(
|
301
|
+
async def get_user_by_id(
|
302
|
+
current_user: Annotated[AuthUserResponse, Depends(get_current_user)],
|
303
|
+
user_id: str,
|
304
|
+
) -> UserResponse:
|
305
|
+
if not current_user.has_permission("user:read"):
|
306
|
+
raise ForbiddenError("Access denied")
|
168
307
|
return await auth_client.get_user_by_id(user_id)
|
169
308
|
|
170
309
|
@app.post(
|
171
310
|
"/api/v1/users/bulk",
|
172
311
|
response_model=list[UserResponse],
|
173
312
|
)
|
174
|
-
async def create_user_bulk(
|
175
|
-
|
176
|
-
|
313
|
+
async def create_user_bulk(
|
314
|
+
current_user: Annotated[AuthUserResponse, Depends(get_current_user)],
|
315
|
+
data: list[UserCreateWithRoles],
|
316
|
+
) -> list[UserResponse]:
|
317
|
+
if not current_user.has_permission("user:create"):
|
318
|
+
raise ForbiddenError("Access denied")
|
319
|
+
return await auth_client.create_user_bulk(
|
320
|
+
[row.with_audit(created_by=current_user.id) for row in data]
|
177
321
|
)
|
178
322
|
|
179
323
|
@app.post(
|
180
324
|
"/api/v1/users",
|
181
325
|
response_model=UserResponse,
|
182
326
|
)
|
183
|
-
async def create_user(
|
184
|
-
|
327
|
+
async def create_user(
|
328
|
+
current_user: Annotated[AuthUserResponse, Depends(get_current_user)],
|
329
|
+
data: UserCreateWithRoles,
|
330
|
+
) -> UserResponse:
|
331
|
+
if not current_user.has_permission("user:create"):
|
332
|
+
raise ForbiddenError("Access denied")
|
333
|
+
return await auth_client.create_user(
|
334
|
+
data.with_audit(created_by=current_user.id)
|
335
|
+
)
|
185
336
|
|
186
337
|
@app.put(
|
187
338
|
"/api/v1/users/bulk",
|
188
339
|
response_model=list[UserResponse],
|
189
340
|
)
|
190
|
-
async def update_user_bulk(
|
341
|
+
async def update_user_bulk(
|
342
|
+
current_user: Annotated[AuthUserResponse, Depends(get_current_user)],
|
343
|
+
user_ids: list[str],
|
344
|
+
data: UserUpdateWithRoles,
|
345
|
+
) -> list[UserResponse]:
|
346
|
+
if not current_user.has_permission("user:update"):
|
347
|
+
raise ForbiddenError("Access denied")
|
191
348
|
return await auth_client.update_user_bulk(
|
192
|
-
user_ids, data.with_audit(updated_by=
|
349
|
+
user_ids, data.with_audit(updated_by=current_user.id)
|
193
350
|
)
|
194
351
|
|
195
352
|
@app.put(
|
196
353
|
"/api/v1/users/{user_id}",
|
197
354
|
response_model=UserResponse,
|
198
355
|
)
|
199
|
-
async def update_user(
|
200
|
-
|
356
|
+
async def update_user(
|
357
|
+
current_user: Annotated[AuthUserResponse, Depends(get_current_user)],
|
358
|
+
user_id: str,
|
359
|
+
data: UserUpdateWithRoles,
|
360
|
+
) -> UserResponse:
|
361
|
+
if not current_user.has_permission("user:update"):
|
362
|
+
raise ForbiddenError("Access denied")
|
363
|
+
return await auth_client.update_user(
|
364
|
+
user_id, data.with_audit(updated_by=current_user.id)
|
365
|
+
)
|
201
366
|
|
202
367
|
@app.delete(
|
203
368
|
"/api/v1/users/bulk",
|
204
369
|
response_model=list[UserResponse],
|
205
370
|
)
|
206
|
-
async def delete_user_bulk(
|
207
|
-
|
371
|
+
async def delete_user_bulk(
|
372
|
+
current_user: Annotated[AuthUserResponse, Depends(get_current_user)],
|
373
|
+
user_ids: list[str],
|
374
|
+
) -> list[UserResponse]:
|
375
|
+
if not current_user.has_permission("user:delete"):
|
376
|
+
raise ForbiddenError("Access denied")
|
377
|
+
return await auth_client.delete_user_bulk(user_ids, deleted_by=current_user.id)
|
208
378
|
|
209
379
|
@app.delete(
|
210
380
|
"/api/v1/users/{user_id}",
|
211
381
|
response_model=UserResponse,
|
212
382
|
)
|
213
|
-
async def delete_user(
|
214
|
-
|
383
|
+
async def delete_user(
|
384
|
+
current_user: Annotated[AuthUserResponse, Depends(get_current_user)],
|
385
|
+
user_id: str,
|
386
|
+
) -> UserResponse:
|
387
|
+
if not current_user.has_permission("user:delete"):
|
388
|
+
raise ForbiddenError("Access denied")
|
389
|
+
return await auth_client.delete_user(user_id, deleted_by=current_user.id)
|
@@ -0,0 +1,57 @@
|
|
1
|
+
import datetime
|
2
|
+
|
3
|
+
from fastapi import Depends, Request, Response
|
4
|
+
from fastapi.security import OAuth2PasswordBearer
|
5
|
+
from my_app_name.config import (
|
6
|
+
APP_AUTH_ACCESS_TOKEN_COOKIE_NAME,
|
7
|
+
APP_AUTH_ACCESS_TOKEN_EXPIRE_MINUTES,
|
8
|
+
APP_AUTH_REFRESH_TOKEN_COOKIE_NAME,
|
9
|
+
APP_AUTH_REFRESH_TOKEN_EXPIRE_MINUTES,
|
10
|
+
)
|
11
|
+
from my_app_name.module.auth.client.auth_client_factory import auth_client
|
12
|
+
from my_app_name.schema.user import AuthUserResponse, UserSessionResponse
|
13
|
+
from typing_extensions import Annotated
|
14
|
+
|
15
|
+
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/user-sessions", auto_error=False)
|
16
|
+
|
17
|
+
|
18
|
+
async def get_current_user(
|
19
|
+
request: Request,
|
20
|
+
response: Response,
|
21
|
+
bearer_access_token: Annotated[str, Depends(oauth2_scheme)],
|
22
|
+
) -> AuthUserResponse:
|
23
|
+
# Bearer token exists
|
24
|
+
if bearer_access_token is not None and bearer_access_token != "":
|
25
|
+
return await auth_client.get_current_user(bearer_access_token)
|
26
|
+
cookie_access_token = request.cookies.get(APP_AUTH_ACCESS_TOKEN_COOKIE_NAME)
|
27
|
+
# Cookie exists
|
28
|
+
if cookie_access_token is not None and cookie_access_token != "":
|
29
|
+
cookie_user = await auth_client.get_current_user(cookie_access_token)
|
30
|
+
if cookie_user.is_guest:
|
31
|
+
# If user is guest, the cookie is not needed
|
32
|
+
unset_user_session_cookie(response)
|
33
|
+
return cookie_user
|
34
|
+
# No bearer token or cookie
|
35
|
+
return await auth_client.get_current_user("")
|
36
|
+
|
37
|
+
|
38
|
+
def set_user_session_cookie(response: Response, user_session: UserSessionResponse):
|
39
|
+
response.set_cookie(
|
40
|
+
key=APP_AUTH_ACCESS_TOKEN_COOKIE_NAME,
|
41
|
+
value=user_session.access_token,
|
42
|
+
httponly=True,
|
43
|
+
max_age=60 * APP_AUTH_ACCESS_TOKEN_EXPIRE_MINUTES,
|
44
|
+
expires=user_session.access_token_expired_at.astimezone(datetime.timezone.utc),
|
45
|
+
)
|
46
|
+
response.set_cookie(
|
47
|
+
key=APP_AUTH_REFRESH_TOKEN_COOKIE_NAME,
|
48
|
+
value=user_session.refresh_token,
|
49
|
+
httponly=True,
|
50
|
+
max_age=60 * APP_AUTH_REFRESH_TOKEN_EXPIRE_MINUTES,
|
51
|
+
expires=user_session.refresh_token_expired_at.astimezone(datetime.timezone.utc),
|
52
|
+
)
|
53
|
+
|
54
|
+
|
55
|
+
def unset_user_session_cookie(response: Response):
|
56
|
+
response.delete_cookie(APP_AUTH_ACCESS_TOKEN_COOKIE_NAME)
|
57
|
+
response.delete_cookie(APP_AUTH_REFRESH_TOKEN_COOKIE_NAME)
|
@@ -34,6 +34,7 @@ class PermissionUpdateWithAudit(PermissionUpdate):
|
|
34
34
|
|
35
35
|
class PermissionResponse(PermissionBase):
|
36
36
|
id: str
|
37
|
+
description: str
|
37
38
|
|
38
39
|
|
39
40
|
class MultiplePermissionResponse(BaseModel):
|
@@ -42,6 +43,7 @@ class MultiplePermissionResponse(BaseModel):
|
|
42
43
|
|
43
44
|
|
44
45
|
class Permission(SQLModel, table=True):
|
46
|
+
__tablename__ = "permissions"
|
45
47
|
id: str = Field(default_factory=lambda: ulid.new().str, primary_key=True)
|
46
48
|
created_at: datetime.datetime | None = Field(index=True)
|
47
49
|
created_by: str | None = Field(index=True)
|
@@ -1,7 +1,6 @@
|
|
1
1
|
import datetime
|
2
2
|
|
3
3
|
import ulid
|
4
|
-
from my_app_name.schema.permission import Permission
|
5
4
|
from pydantic import BaseModel
|
6
5
|
from sqlmodel import Field, SQLModel
|
7
6
|
|
@@ -22,7 +21,7 @@ class RoleCreateWithAudit(RoleCreate):
|
|
22
21
|
|
23
22
|
|
24
23
|
class RoleCreateWithPermissions(RoleCreate):
|
25
|
-
|
24
|
+
permission_names: list[str] | None = None
|
26
25
|
|
27
26
|
def with_audit(self, created_by: str) -> "RoleCreateWithPermissionsAndAudit":
|
28
27
|
return RoleCreateWithPermissionsAndAudit(
|
@@ -37,14 +36,14 @@ class RoleCreateWithPermissionsAndAudit(RoleCreateWithPermissions):
|
|
37
36
|
data = {
|
38
37
|
key: val
|
39
38
|
for key, val in self.model_dump().items()
|
40
|
-
if key != "
|
39
|
+
if key != "permission_names"
|
41
40
|
}
|
42
41
|
return RoleCreateWithAudit(**data)
|
43
42
|
|
44
|
-
def
|
45
|
-
if self.
|
43
|
+
def get_permission_names(self) -> list[str]:
|
44
|
+
if self.permission_names is None:
|
46
45
|
return []
|
47
|
-
return self.
|
46
|
+
return self.permission_names
|
48
47
|
|
49
48
|
|
50
49
|
class RoleUpdate(SQLModel):
|
@@ -60,7 +59,7 @@ class RoleUpdateWithAudit(RoleUpdate):
|
|
60
59
|
|
61
60
|
|
62
61
|
class RoleUpdateWithPermissions(RoleUpdate):
|
63
|
-
|
62
|
+
permission_names: list[str] | None = None
|
64
63
|
|
65
64
|
def with_audit(self, updated_by: str) -> "RoleUpdateWithPermissionsAndAudit":
|
66
65
|
return RoleUpdateWithPermissionsAndAudit(
|
@@ -75,19 +74,19 @@ class RoleUpdateWithPermissionsAndAudit(RoleUpdateWithPermissions):
|
|
75
74
|
data = {
|
76
75
|
key: val
|
77
76
|
for key, val in self.model_dump().items()
|
78
|
-
if key != "
|
77
|
+
if key != "permission_names"
|
79
78
|
}
|
80
79
|
return RoleUpdateWithAudit(**data)
|
81
80
|
|
82
|
-
def
|
83
|
-
if self.
|
81
|
+
def get_permission_names(self) -> list[str]:
|
82
|
+
if self.permission_names is None:
|
84
83
|
return []
|
85
|
-
return self.
|
84
|
+
return self.permission_names
|
86
85
|
|
87
86
|
|
88
87
|
class RoleResponse(RoleBase):
|
89
88
|
id: str
|
90
|
-
|
89
|
+
permission_names: list[str]
|
91
90
|
|
92
91
|
|
93
92
|
class MultipleRoleResponse(BaseModel):
|
@@ -96,6 +95,7 @@ class MultipleRoleResponse(BaseModel):
|
|
96
95
|
|
97
96
|
|
98
97
|
class Role(SQLModel, table=True):
|
98
|
+
__tablename__ = "roles"
|
99
99
|
id: str = Field(default_factory=lambda: ulid.new().str, primary_key=True)
|
100
100
|
created_at: datetime.datetime | None = Field(index=True)
|
101
101
|
created_by: str | None = Field(index=True)
|
@@ -106,6 +106,7 @@ class Role(SQLModel, table=True):
|
|
106
106
|
|
107
107
|
|
108
108
|
class RolePermission(SQLModel, table=True):
|
109
|
+
__tablename__ = "role_permissions"
|
109
110
|
id: str = Field(default_factory=lambda: ulid.new().str, primary_key=True)
|
110
111
|
role_id: str = Field(index=True)
|
111
112
|
permission_id: str = Field(index=True)
|