feat(v1.1.8): admin HTTP — /apps/{id}/users + /invitations
users_admin_api router merged into the guarded admin tree at
/api/v1/admin/apps/{id_or_slug}/users/* + /invitations/*.
Endpoints (capability gates applied via the service layer):
GET /users AppUsersRead
GET /users/{user_id} AppUsersRead
POST /users AppUsersWrite
PATCH /users/{user_id} AppUsersWrite
DELETE /users/{user_id} AppUsersWrite (admins satisfy implicitly)
POST /users/{user_id}/reset-password AppUsersAdmin -> one-shot token
POST /users/{user_id}/revoke-sessions AppUsersAdmin
GET /invitations AppUsersAdmin
POST /invitations AppUsersAdmin
DELETE /invitations/{invite_id} AppUsersAdmin
DTOs never include password_hash, session tokens, or unrotated
reset tokens. The reset-password endpoint returns the raw one-shot
token exactly once in the response body so an admin can paste it
into a manual reset link (TTL 1h by default).
Synth_cx() factors the admin-side cx construction into one place
(marked) so the SDK and admin code paths share the service's authz
fan-out. Admin-mediated trait methods (admin_create_invitation,
admin_reset_password_token, admin_revoke_all_sessions,
list_invitations, revoke_invitation) take &Principal directly,
not the synthesized cx.
UsersServiceImpl: removed the NOT_YET_IMPL constant + unused
auth alias (every method has a real implementation now). Added
Serialize+Deserialize on the AppUser / Invitation shared DTOs so
the admin HTTP layer doesn't need a parallel set of types.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -282,7 +282,7 @@ pub async fn build_app(
|
||||
pubsub,
|
||||
secrets,
|
||||
email,
|
||||
users,
|
||||
users.clone(),
|
||||
);
|
||||
let engine = Arc::new(Engine::new(Limits::default(), services));
|
||||
|
||||
@@ -448,9 +448,14 @@ pub async fn build_app(
|
||||
};
|
||||
let app_members_state = AppMembersState {
|
||||
apps: apps_state.apps.clone(),
|
||||
users: auth.users,
|
||||
users: auth.users.clone(),
|
||||
members,
|
||||
authz,
|
||||
authz: authz.clone(),
|
||||
};
|
||||
let app_users_admin_state = picloud_manager_core::AppUsersState {
|
||||
apps: apps_state.apps.clone(),
|
||||
authz: authz.clone(),
|
||||
users: users.clone(),
|
||||
};
|
||||
let api_keys_state = ApiKeysState { keys: auth.keys };
|
||||
|
||||
@@ -466,6 +471,7 @@ pub async fn build_app(
|
||||
.merge(admins_router(admins_state))
|
||||
.merge(apps_router(apps_state))
|
||||
.merge(app_members_router(app_members_state))
|
||||
.merge(picloud_manager_core::app_users_router(app_users_admin_state))
|
||||
.merge(api_keys_router(api_keys_state))
|
||||
.merge(triggers_router(triggers_state))
|
||||
.merge(files_admin_router(files_admin_state))
|
||||
|
||||
Reference in New Issue
Block a user