seaislee1209 ed67a27399 feat(team): 团管重置成员密码 — 新 API + 严格权限矩阵 + 成员管理页按钮
权限矩阵(plan §G,服务端硬校验,前端按钮只是 UX 不算数):
| 操作者          | 可重置                  | 不可重置             |
|----------------|------------------------|---------------------|
| 主管(owner=T)  | 同团队副管 + 成员       | 其他主管 / 自己      |
| 副管(admin=T)  | 同团队成员              | 副管 / 主管 / 自己   |

后端:
- 新 view team_reset_member_password_view (POST /api/v1/team/members/<id>/reset-password)
- permission IsTeamAdmin(覆盖主管+副管两种)+ 服务端逐层判断:
  1. 同团队?              (target.team_id != operator.team_id → 403)
  2. 不能改自己?           (id 相同 → 400)
  3. 主管密码须超管?       (target.is_team_owner → 403)
  4. 副管只有主管能改?     (target.is_team_admin && !operator.is_team_owner → 403)
  5. 走到这里都是合法 case → 生成 8 位随机密码(secrets+string)+ must_change_password=True
- log_admin_action audit 留痕(action=user_password_reset, after.reset_by=team_admin, operator=...)
- urls.py 加路由

前端:
- lib/api.ts teamApi.resetMemberPassword(memberId) → 返回 { new_password, ... }
- TeamMembersPage.tsx:
  - canResetPasswordFor(m) helper 同权限矩阵(主管→副管+成员、副管→成员、不能改自己)
  - 成员行 actions 加 "重置密码" 按钮(只在 canResetPasswordFor 为 true 时显示)
  - 点击 → window.confirm 二次确认 → API → 弹结果 modal 显示新密码 + 复制按钮
  - 结果 modal 用 monospace font 大字 + 浅灰底显示密码,带 ⚠ 提醒"关闭后无法再次查看"
  - showToast 反馈复制/失败

后端 6 项 curl 测试全通过:
- T1 主管→副管        200 ✓
- T2 主管→成员        200 ✓
- T3 主管→自己        400 "不能重置自己的密码" ✓
- T4 副管→主管        403 "主管须由超级管理员重置" ✓
- T5 副管→成员        200 ✓
- T6 副管→另一副管    403 "只有主管理员能重置副管理员密码" ✓

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 18:36:16 +08:00

86 lines
5.6 KiB
Python

from django.urls import path
from . import views
urlpatterns = [
# Media upload
path('media/upload', views.upload_media_view, name='media_upload'),
# Video generation
path('video/generate', views.video_generate_view, name='video_generate'),
path('video/tasks', views.video_tasks_list_view, name='video_tasks_list'),
path('video/tasks/<uuid:task_id>', views.video_task_detail_view, name='video_task_detail'),
path('video/tasks/<uuid:task_id>/favorite', views.video_task_toggle_favorite_view, name='video_task_toggle_favorite'),
# Public announcement
path('announcement', views.announcement_view, name='announcement'),
path('announcement/read', views.announcement_read_view, name='announcement_read'),
# ── Super Admin: Dashboard ──
path('admin/stats', views.admin_stats_view, name='admin_stats'),
# ── Super Admin: Team management ──
path('admin/teams', views.admin_teams_list_view, name='admin_teams_list'),
path('admin/teams/create', views.admin_team_create_view, name='admin_team_create'),
path('admin/teams/<int:team_id>', views.admin_team_detail_view, name='admin_team_detail'),
path('admin/teams/<int:team_id>/topup', views.admin_team_topup_view, name='admin_team_topup'),
path('admin/teams/<int:team_id>/set-pool', views.admin_team_set_pool_view, name='admin_team_set_pool'),
path('admin/teams/<int:team_id>/admin', views.admin_team_create_admin_view, name='admin_team_create_admin'),
path('admin/teams/<int:team_id>/members/<int:member_id>/role', views.admin_team_member_role_view, name='admin_team_member_role'),
# ── Super Admin: User management ──
path('admin/users', views.admin_users_list_view, name='admin_users_list'),
path('admin/users/create', views.admin_create_user_view, name='admin_create_user'),
path('admin/users/<int:user_id>', views.admin_user_detail_view, name='admin_user_detail'),
path('admin/users/<int:user_id>/quota', views.admin_user_quota_view, name='admin_user_quota'),
path('admin/users/<int:user_id>/status', views.admin_user_status_view, name='admin_user_status'),
path('admin/users/<int:user_id>/reset-password', views.admin_reset_password_view, name='admin_reset_password'),
# ── Super Admin: Records, Settings & Audit Logs ──
path('admin/records', views.admin_records_view, name='admin_records'),
path('admin/settings', views.admin_settings_view, name='admin_settings'),
path('admin/logs', views.admin_audit_logs_view, name='admin_audit_logs'),
# ── Super Admin: Login Records ──
path('admin/login-records', views.admin_login_records_view, name='admin_login_records'),
# ── Super Admin: Anomaly Detection ──
path('admin/anomalies', views.admin_login_anomalies_view, name='admin_login_anomalies'),
path('admin/test-feishu', views.admin_test_feishu_view, name='admin_test_feishu'),
path('admin/test-sms', views.admin_test_sms_view, name='admin_test_sms'),
path('admin/teams/<int:team_id>/auto-learn', views.admin_team_auto_learn_view, name='admin_team_auto_learn'),
path('admin/teams/<int:team_id>/apply-learned-regions', views.admin_team_apply_learned_regions_view, name='admin_team_apply_learned_regions'),
# ── Super Admin: Content Assets ──
path('admin/assets/overview', views.admin_assets_overview, name='admin_assets_overview'),
path('admin/assets/team/<int:team_id>/members', views.admin_assets_team_members, name='admin_assets_team_members'),
path('admin/assets/user/<int:user_id>/videos', views.admin_assets_user_videos, name='admin_assets_user_videos'),
# ── Team Admin: Team management ──
path('team/info', views.team_info_view, name='team_info'),
path('team/stats', views.team_stats_view, name='team_stats'),
path('team/members', views.team_members_list_view, name='team_members_list'),
path('team/members/create', views.team_member_create_view, name='team_member_create'),
path('team/members/<int:member_id>', views.team_member_detail_view, name='team_member_detail'),
path('team/members/<int:member_id>/quota', views.team_member_quota_view, name='team_member_quota'),
path('team/members/<int:member_id>/status', views.team_member_status_view, name='team_member_status'),
path('team/members/<int:member_id>/role', views.team_member_role_view, name='team_member_role'),
path('team/members/<int:member_id>/reset-password', views.team_reset_member_password_view, name='team_reset_member_password'),
# ── Team Admin: Consumption Records ──
path('team/records', views.team_records_view, name='team_records'),
# ── Team Admin: Content Assets ──
path('team/assets/overview', views.team_assets_overview, name='team_assets_overview'),
path('team/assets/member/<int:member_id>/videos', views.team_assets_member_videos, name='team_assets_member_videos'),
# ── Profile: User's own data ──
path('profile/overview', views.profile_overview_view, name='profile_overview'),
path('profile/records', views.profile_records_view, name='profile_records'),
# ── Assets API (Virtual Avatar Library) ──
path('assets/groups', views.asset_groups_view, name='asset_groups'),
path('assets/groups/<int:group_id>', views.asset_group_detail_view, name='asset_group_detail'),
path('assets/groups/<int:group_id>/assets', views.asset_group_add_asset_view, name='asset_group_add_asset'),
path('assets/<int:asset_id>', views.asset_update_view, name='asset_update'),
path('assets/<int:asset_id>/status', views.asset_poll_status_view, name='asset_poll_status'),
path('assets/search', views.asset_search_view, name='asset_search'),
]