Allow ADMIN users to bulk deactivate and reactivate by role.

Platform ADMIN callers no longer hit 403 on these endpoints; bulk changes to platform users.role ADMIN remain restricted to SUPER_ADMIN, while team_members.team_role ADMIN is still eligible under path ADMIN.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Yared Yemane 2026-05-19 01:09:42 -07:00
parent ecad91d89e
commit 2f73b60122
4 changed files with 35 additions and 21 deletions

View File

@ -512,7 +512,7 @@ const docTemplate = `{
"Bearer": [] "Bearer": []
} }
], ],
"description": "Sets all platform users with the given users.role to DEACTIVATED (except the caller) and all team_members with the given team_role to inactive. The path role must match a valid learner role or team role string (e.g. STUDENT, INSTRUCTOR, ADMIN, CONTENT_MANAGER). SUPER_ADMIN cannot be bulk-deactivated. Empty body allowed; optionally pass exclude_team_member_id to skip one team_members row (e.g. yourself).", "description": "Sets all platform users with the given users.role to DEACTIVATED (except the caller) and all team_members with the given team_role to inactive. The path role must match a valid learner role or team role string (e.g. STUDENT, INSTRUCTOR, ADMIN, CONTENT_MANAGER). SUPER_ADMIN cannot be bulk-deactivated. ADMIN platform users must use SUPER_ADMIN to bulk change other platform ADMIN users (team_members with team_role ADMIN under path ADMIN remain allowed). Empty body allowed; optionally pass exclude_team_member_id to skip one team_members row (e.g. yourself).",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@ -522,7 +522,7 @@ const docTemplate = `{
"tags": [ "tags": [
"admin" "admin"
], ],
"summary": "Bulk deactivate accounts by role (SUPER_ADMIN only)", "summary": "Bulk deactivate accounts by role (SUPER_ADMIN or ADMIN platform users only)",
"parameters": [ "parameters": [
{ {
"type": "string", "type": "string",
@ -575,7 +575,7 @@ const docTemplate = `{
"Bearer": [] "Bearer": []
} }
], ],
"description": "Sets all platform users with the given role from DEACTIVATED to ACTIVE (except the caller) and all team_members with the given team_role from inactive to active. Path role must be a valid platform or team role. SUPER_ADMIN cannot be bulk changed. Matches only users currently DEACTIVATED and team rows currently inactive.", "description": "Sets all platform users with the given role from DEACTIVATED to ACTIVE (except the caller) and all team_members with the given team_role from inactive to active. Path role must be a valid platform or team role. SUPER_ADMIN cannot be bulk changed. ADMIN callers cannot bulk change other platform ADMIN users (team_members ADMIN under path ADMIN is allowed). Matches only users currently DEACTIVATED and team rows currently inactive.",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@ -585,7 +585,7 @@ const docTemplate = `{
"tags": [ "tags": [
"admin" "admin"
], ],
"summary": "Bulk reactivate accounts by role (SUPER_ADMIN only)", "summary": "Bulk reactivate accounts by role (SUPER_ADMIN or ADMIN platform users only)",
"parameters": [ "parameters": [
{ {
"type": "string", "type": "string",

View File

@ -504,7 +504,7 @@
"Bearer": [] "Bearer": []
} }
], ],
"description": "Sets all platform users with the given users.role to DEACTIVATED (except the caller) and all team_members with the given team_role to inactive. The path role must match a valid learner role or team role string (e.g. STUDENT, INSTRUCTOR, ADMIN, CONTENT_MANAGER). SUPER_ADMIN cannot be bulk-deactivated. Empty body allowed; optionally pass exclude_team_member_id to skip one team_members row (e.g. yourself).", "description": "Sets all platform users with the given users.role to DEACTIVATED (except the caller) and all team_members with the given team_role to inactive. The path role must match a valid learner role or team role string (e.g. STUDENT, INSTRUCTOR, ADMIN, CONTENT_MANAGER). SUPER_ADMIN cannot be bulk-deactivated. ADMIN platform users must use SUPER_ADMIN to bulk change other platform ADMIN users (team_members with team_role ADMIN under path ADMIN remain allowed). Empty body allowed; optionally pass exclude_team_member_id to skip one team_members row (e.g. yourself).",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@ -514,7 +514,7 @@
"tags": [ "tags": [
"admin" "admin"
], ],
"summary": "Bulk deactivate accounts by role (SUPER_ADMIN only)", "summary": "Bulk deactivate accounts by role (SUPER_ADMIN or ADMIN platform users only)",
"parameters": [ "parameters": [
{ {
"type": "string", "type": "string",
@ -567,7 +567,7 @@
"Bearer": [] "Bearer": []
} }
], ],
"description": "Sets all platform users with the given role from DEACTIVATED to ACTIVE (except the caller) and all team_members with the given team_role from inactive to active. Path role must be a valid platform or team role. SUPER_ADMIN cannot be bulk changed. Matches only users currently DEACTIVATED and team rows currently inactive.", "description": "Sets all platform users with the given role from DEACTIVATED to ACTIVE (except the caller) and all team_members with the given team_role from inactive to active. Path role must be a valid platform or team role. SUPER_ADMIN cannot be bulk changed. ADMIN callers cannot bulk change other platform ADMIN users (team_members ADMIN under path ADMIN is allowed). Matches only users currently DEACTIVATED and team rows currently inactive.",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@ -577,7 +577,7 @@
"tags": [ "tags": [
"admin" "admin"
], ],
"summary": "Bulk reactivate accounts by role (SUPER_ADMIN only)", "summary": "Bulk reactivate accounts by role (SUPER_ADMIN or ADMIN platform users only)",
"parameters": [ "parameters": [
{ {
"type": "string", "type": "string",

View File

@ -2881,6 +2881,8 @@ paths:
(except the caller) and all team_members with the given team_role to inactive. (except the caller) and all team_members with the given team_role to inactive.
The path role must match a valid learner role or team role string (e.g. STUDENT, The path role must match a valid learner role or team role string (e.g. STUDENT,
INSTRUCTOR, ADMIN, CONTENT_MANAGER). SUPER_ADMIN cannot be bulk-deactivated. INSTRUCTOR, ADMIN, CONTENT_MANAGER). SUPER_ADMIN cannot be bulk-deactivated.
ADMIN platform users must use SUPER_ADMIN to bulk change other platform ADMIN
users (team_members with team_role ADMIN under path ADMIN remain allowed).
Empty body allowed; optionally pass exclude_team_member_id to skip one team_members Empty body allowed; optionally pass exclude_team_member_id to skip one team_members
row (e.g. yourself). row (e.g. yourself).
parameters: parameters:
@ -2915,7 +2917,8 @@ paths:
$ref: '#/definitions/domain.ErrorResponse' $ref: '#/definitions/domain.ErrorResponse'
security: security:
- Bearer: [] - Bearer: []
summary: Bulk deactivate accounts by role (SUPER_ADMIN only) summary: Bulk deactivate accounts by role (SUPER_ADMIN or ADMIN platform users
only)
tags: tags:
- admin - admin
/api/v1/admin/roles/{role}/bulk-reactivate: /api/v1/admin/roles/{role}/bulk-reactivate:
@ -2925,8 +2928,9 @@ paths:
description: Sets all platform users with the given role from DEACTIVATED to description: Sets all platform users with the given role from DEACTIVATED to
ACTIVE (except the caller) and all team_members with the given team_role from ACTIVE (except the caller) and all team_members with the given team_role from
inactive to active. Path role must be a valid platform or team role. SUPER_ADMIN inactive to active. Path role must be a valid platform or team role. SUPER_ADMIN
cannot be bulk changed. Matches only users currently DEACTIVATED and team cannot be bulk changed. ADMIN callers cannot bulk change other platform ADMIN
rows currently inactive. users (team_members ADMIN under path ADMIN is allowed). Matches only users
currently DEACTIVATED and team rows currently inactive.
parameters: parameters:
- description: Role key (matches users.role and/or team_members.team_role) - description: Role key (matches users.role and/or team_members.team_role)
in: path in: path
@ -2959,7 +2963,8 @@ paths:
$ref: '#/definitions/domain.ErrorResponse' $ref: '#/definitions/domain.ErrorResponse'
security: security:
- Bearer: [] - Bearer: []
summary: Bulk reactivate accounts by role (SUPER_ADMIN only) summary: Bulk reactivate accounts by role (SUPER_ADMIN or ADMIN platform users
only)
tags: tags:
- admin - admin
/api/v1/admin/users/{user_id}/lms-learning-activity: /api/v1/admin/users/{user_id}/lms-learning-activity:

View File

@ -382,8 +382,8 @@ func (h *Handler) UpdateAdmin(c *fiber.Ctx) error {
} }
// BulkDeactivateAccountsByRole godoc // BulkDeactivateAccountsByRole godoc
// @Summary Bulk deactivate accounts by role (SUPER_ADMIN only) // @Summary Bulk deactivate accounts by role (SUPER_ADMIN or ADMIN platform users only)
// @Description Sets all platform users with the given users.role to DEACTIVATED (except the caller) and all team_members with the given team_role to inactive. The path role must match a valid learner role or team role string (e.g. STUDENT, INSTRUCTOR, ADMIN, CONTENT_MANAGER). SUPER_ADMIN cannot be bulk-deactivated. Empty body allowed; optionally pass exclude_team_member_id to skip one team_members row (e.g. yourself). // @Description Sets all platform users with the given users.role to DEACTIVATED (except the caller) and all team_members with the given team_role to inactive. The path role must match a valid learner role or team role string (e.g. STUDENT, INSTRUCTOR, ADMIN, CONTENT_MANAGER). SUPER_ADMIN cannot be bulk-deactivated. ADMIN platform users must use SUPER_ADMIN to bulk change other platform ADMIN users (team_members with team_role ADMIN under path ADMIN remain allowed). Empty body allowed; optionally pass exclude_team_member_id to skip one team_members row (e.g. yourself).
// @Tags admin // @Tags admin
// @Accept json // @Accept json
// @Produce json // @Produce json
@ -403,10 +403,10 @@ func (h *Handler) BulkDeactivateAccountsByRole(c *fiber.Ctx) error {
Error: "role not found in context", Error: "role not found in context",
}) })
} }
if callerRole != domain.RoleSuperAdmin { if callerRole != domain.RoleSuperAdmin && callerRole != domain.RoleAdmin {
return c.Status(fiber.StatusForbidden).JSON(domain.ErrorResponse{ return c.Status(fiber.StatusForbidden).JSON(domain.ErrorResponse{
Message: "Forbidden", Message: "Forbidden",
Error: "only SUPER_ADMIN platform users may bulk deactivate by role", Error: "only SUPER_ADMIN or ADMIN platform users may bulk deactivate by role",
}) })
} }
@ -431,7 +431,6 @@ func (h *Handler) BulkDeactivateAccountsByRole(c *fiber.Ctx) error {
Error: "SUPER_ADMIN cannot be bulk deactivated", Error: "SUPER_ADMIN cannot be bulk deactivated",
}) })
} }
validUserRole := domain.Role(roleKey).IsValid() validUserRole := domain.Role(roleKey).IsValid()
validTeamRole := domain.TeamRole(roleKey).IsValid() validTeamRole := domain.TeamRole(roleKey).IsValid()
if !validUserRole && !validTeamRole { if !validUserRole && !validTeamRole {
@ -441,6 +440,11 @@ func (h *Handler) BulkDeactivateAccountsByRole(c *fiber.Ctx) error {
}) })
} }
// Non-super-admins cannot bulk change other platform ADMIN users (same role); team_members ADMIN is still allowed.
if callerRole != domain.RoleSuperAdmin && roleKey == string(domain.RoleAdmin) {
validUserRole = false
}
var req domain.BulkAccountsByRoleRequest var req domain.BulkAccountsByRoleRequest
if len(c.Body()) > 0 { if len(c.Body()) > 0 {
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
@ -504,8 +508,8 @@ func (h *Handler) BulkDeactivateAccountsByRole(c *fiber.Ctx) error {
} }
// BulkReactivateAccountsByRole godoc // BulkReactivateAccountsByRole godoc
// @Summary Bulk reactivate accounts by role (SUPER_ADMIN only) // @Summary Bulk reactivate accounts by role (SUPER_ADMIN or ADMIN platform users only)
// @Description Sets all platform users with the given role from DEACTIVATED to ACTIVE (except the caller) and all team_members with the given team_role from inactive to active. Path role must be a valid platform or team role. SUPER_ADMIN cannot be bulk changed. Matches only users currently DEACTIVATED and team rows currently inactive. // @Description Sets all platform users with the given role from DEACTIVATED to ACTIVE (except the caller) and all team_members with the given team_role from inactive to active. Path role must be a valid platform or team role. SUPER_ADMIN cannot be bulk changed. ADMIN callers cannot bulk change other platform ADMIN users (team_members ADMIN under path ADMIN is allowed). Matches only users currently DEACTIVATED and team rows currently inactive.
// @Tags admin // @Tags admin
// @Accept json // @Accept json
// @Produce json // @Produce json
@ -525,10 +529,10 @@ func (h *Handler) BulkReactivateAccountsByRole(c *fiber.Ctx) error {
Error: "role not found in context", Error: "role not found in context",
}) })
} }
if callerRole != domain.RoleSuperAdmin { if callerRole != domain.RoleSuperAdmin && callerRole != domain.RoleAdmin {
return c.Status(fiber.StatusForbidden).JSON(domain.ErrorResponse{ return c.Status(fiber.StatusForbidden).JSON(domain.ErrorResponse{
Message: "Forbidden", Message: "Forbidden",
Error: "only SUPER_ADMIN platform users may bulk reactivate by role", Error: "only SUPER_ADMIN or ADMIN platform users may bulk reactivate by role",
}) })
} }
@ -563,6 +567,11 @@ func (h *Handler) BulkReactivateAccountsByRole(c *fiber.Ctx) error {
}) })
} }
// Non-super-admins cannot bulk change other platform ADMIN users; team_members ADMIN is still allowed.
if callerRole != domain.RoleSuperAdmin && roleKey == string(domain.RoleAdmin) {
validUserRole = false
}
var req domain.BulkAccountsByRoleRequest var req domain.BulkAccountsByRoleRequest
if len(c.Body()) > 0 { if len(c.Body()) > 0 {
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {