77
88from bloom_lims .api .v1 .dependencies import APIUser , require_admin
99from bloom_lims .auth .services .groups import GroupService
10- from bloom_lims .auth .services .user_api_tokens import UserAPITokenService
10+ from bloom_lims .auth .services .user_api_tokens import TokenCreateInput , UserAPITokenService
1111from bloom_lims .db import BLOOMdb3
1212
1313router = APIRouter (prefix = "/admin" , tags = ["Admin Auth" ])
@@ -17,6 +17,14 @@ class GroupMemberAddRequest(BaseModel):
1717 user_id : str = Field (..., min_length = 1 )
1818
1919
20+ class AdminIssueTokenRequest (BaseModel ):
21+ user_id : str = Field (..., min_length = 1 )
22+ token_name : str = Field (..., min_length = 3 , max_length = 120 )
23+ scope : str = Field (default = "internal_ro" )
24+ expires_in_days : int = Field (default = 30 , ge = 1 , le = 3650 )
25+ note : str | None = None
26+
27+
2028def _require_id (value : str | None , * , field_name : str ) -> str :
2129 normalized = str (value or "" ).strip ()
2230 if not normalized :
@@ -88,17 +96,35 @@ async def add_group_member(
8896 bdb = BLOOMdb3 (app_username = user .email )
8997 try :
9098 service = GroupService (bdb .session )
99+ existing = {
100+ str (member .user_id ): member
101+ for member in service .list_group_members (group_code = group_code )
102+ }.get (member_user_id )
103+
91104 member = service .add_user_to_group (
92105 group_code = group_code ,
93106 user_id = member_user_id ,
94107 added_by = actor_user_id ,
95108 )
109+
110+ if existing is None :
111+ result = "added"
112+ message = f"Added { member_user_id } to { group_code } "
113+ elif existing .is_active :
114+ result = "exists"
115+ message = f"{ member_user_id } is already active in { group_code } "
116+ else :
117+ result = "reactivated"
118+ message = f"Reactivated { member_user_id } membership in { group_code } "
119+
96120 return {
97121 "id" : str (member .id ),
98122 "group_id" : str (member .group_id ),
99123 "group_code" : member .group_code ,
100124 "user_id" : str (member .user_id ),
101125 "is_active" : member .is_active ,
126+ "result" : result ,
127+ "message" : message ,
102128 }
103129 except ValueError as exc :
104130 raise HTTPException (status_code = 404 , detail = str (exc )) from exc
@@ -164,6 +190,49 @@ async def list_admin_user_tokens(user: APIUser = Depends(require_admin)):
164190 bdb .close ()
165191
166192
193+ @router .post ("/user-tokens/issue" )
194+ async def issue_admin_user_token (
195+ payload : AdminIssueTokenRequest ,
196+ user : APIUser = Depends (require_admin ),
197+ ):
198+ actor_user_id = _require_id (user .user_id , field_name = "authenticated user_id" )
199+ owner_user_id = _require_id (payload .user_id , field_name = "user_id" )
200+ bdb = BLOOMdb3 (app_username = user .email )
201+ try :
202+ service = UserAPITokenService (bdb .session )
203+ service .groups .ensure_system_groups ()
204+ created = service .create_token (
205+ owner_user_id = owner_user_id ,
206+ actor_user_id = actor_user_id ,
207+ actor_roles = user .roles ,
208+ actor_groups = user .groups ,
209+ payload = TokenCreateInput (
210+ token_name = payload .token_name ,
211+ scope = payload .scope ,
212+ expires_in_days = payload .expires_in_days ,
213+ note = payload .note ,
214+ ),
215+ )
216+ return {
217+ "token" : {
218+ "token_id" : str (created .token .id ),
219+ "user_id" : str (created .token .user_id ),
220+ "token_name" : created .token .token_name ,
221+ "token_prefix" : created .token .token_prefix ,
222+ "scope" : created .token .scope ,
223+ "status" : created .revision .status ,
224+ "expires_at" : created .revision .expires_at .isoformat (),
225+ "created_at" : created .token .created_at .isoformat () if created .token .created_at else None ,
226+ },
227+ "plaintext_token" : created .plaintext_token ,
228+ "message" : "Store this token now; it will not be shown again." ,
229+ }
230+ except PermissionError as exc :
231+ raise HTTPException (status_code = 403 , detail = str (exc )) from exc
232+ finally :
233+ bdb .close ()
234+
235+
167236@router .delete ("/user-tokens/{token_id}" )
168237async def revoke_admin_user_token (
169238 token_id : str ,
@@ -224,4 +293,3 @@ async def get_admin_token_usage(
224293 }
225294 finally :
226295 bdb .close ()
227-
0 commit comments