added production user role and made it default for new MSAL users - production can access everything EXCEPT user management - that's only for admin
This commit is contained in:
parent
665b49c3f1
commit
aefd559e68
12 changed files with 61 additions and 41 deletions
|
|
@ -278,10 +278,10 @@ async def admin_reset_password(
|
|||
|
||||
@router.get("/stats", response_model=AdminStatsResponse)
|
||||
async def get_admin_stats(
|
||||
current_user: User = Depends(require_roles(UserRole.ADMIN)),
|
||||
current_user: User = Depends(require_roles(UserRole.PRODUCTION, UserRole.ADMIN)),
|
||||
db: AsyncIOMotorDatabase = Depends(get_database),
|
||||
):
|
||||
"""Get system statistics (admin only)"""
|
||||
"""Get system statistics (production/admin only)"""
|
||||
# Get user count
|
||||
total_users = await db.users.count_documents({"is_active": True})
|
||||
|
||||
|
|
@ -336,10 +336,10 @@ async def get_admin_stats(
|
|||
|
||||
@router.get("/health/detailed")
|
||||
async def detailed_health_check(
|
||||
current_user: User = Depends(require_roles(UserRole.ADMIN, UserRole.REVIEWER)),
|
||||
current_user: User = Depends(require_roles(UserRole.REVIEWER, UserRole.PRODUCTION, UserRole.ADMIN)),
|
||||
db: AsyncIOMotorDatabase = Depends(get_database),
|
||||
):
|
||||
"""Detailed health check with system component status (admin/reviewer only)"""
|
||||
"""Detailed health check with system component status (reviewer/production/admin only)"""
|
||||
health_status = {
|
||||
"status": "healthy",
|
||||
"timestamp": datetime.utcnow().isoformat(),
|
||||
|
|
@ -405,10 +405,10 @@ async def detailed_health_check(
|
|||
@router.get("/jobs/stats")
|
||||
async def get_job_statistics(
|
||||
days: int = Query(7, ge=1, le=90),
|
||||
current_user: User = Depends(require_roles(UserRole.ADMIN, UserRole.REVIEWER)),
|
||||
current_user: User = Depends(require_roles(UserRole.REVIEWER, UserRole.PRODUCTION, UserRole.ADMIN)),
|
||||
db: AsyncIOMotorDatabase = Depends(get_database),
|
||||
):
|
||||
"""Get job processing statistics (admin/reviewer only)"""
|
||||
"""Get job processing statistics (reviewer/production/admin only)"""
|
||||
since_date = datetime.utcnow() - timedelta(days=days)
|
||||
|
||||
# Jobs created in period
|
||||
|
|
@ -534,10 +534,10 @@ async def get_audit_logs(
|
|||
days: int = Query(7, ge=1, le=90),
|
||||
page: int = Query(1, ge=1),
|
||||
size: int = Query(50, ge=1, le=200),
|
||||
current_user: User = Depends(require_roles(UserRole.ADMIN)),
|
||||
current_user: User = Depends(require_roles(UserRole.PRODUCTION, UserRole.ADMIN)),
|
||||
db: AsyncIOMotorDatabase = Depends(get_database),
|
||||
):
|
||||
"""Get audit logs with filtering (admin only)"""
|
||||
"""Get audit logs with filtering (production/admin only)"""
|
||||
query = {
|
||||
"when": {"$gte": datetime.utcnow() - timedelta(days=days)}
|
||||
}
|
||||
|
|
@ -572,10 +572,10 @@ async def get_audit_logs(
|
|||
@router.post("/maintenance/reprocess-job/{job_id}")
|
||||
async def reprocess_job(
|
||||
job_id: str,
|
||||
current_user: User = Depends(require_roles(UserRole.ADMIN)),
|
||||
current_user: User = Depends(require_roles(UserRole.PRODUCTION, UserRole.ADMIN)),
|
||||
db: AsyncIOMotorDatabase = Depends(get_database),
|
||||
):
|
||||
"""Force reprocessing of a job (admin emergency function)"""
|
||||
"""Force reprocessing of a job (production/admin emergency function)"""
|
||||
# Check if job exists
|
||||
job_doc = await db.jobs.find_one({"_id": job_id})
|
||||
if not job_doc:
|
||||
|
|
@ -626,11 +626,11 @@ async def reprocess_job(
|
|||
|
||||
|
||||
@router.get("/audit-logs", response_model=AuditLogResponse)
|
||||
async def get_audit_logs(
|
||||
async def get_audit_logs_detailed(
|
||||
# Time range
|
||||
start_date: Optional[datetime] = Query(None, description="Start date for audit logs"),
|
||||
end_date: Optional[datetime] = Query(None, description="End date for audit logs"),
|
||||
|
||||
|
||||
# Filters
|
||||
action: Optional[str] = Query(None, description="Filter by action type"),
|
||||
severity: Optional[str] = Query(None, description="Filter by severity level"),
|
||||
|
|
@ -638,22 +638,22 @@ async def get_audit_logs(
|
|||
resource_type: Optional[str] = Query(None, description="Filter by resource type"),
|
||||
resource_id: Optional[str] = Query(None, description="Filter by resource ID"),
|
||||
success: Optional[bool] = Query(None, description="Filter by success status"),
|
||||
|
||||
|
||||
# Search
|
||||
search: Optional[str] = Query(None, description="Search in description and details"),
|
||||
|
||||
|
||||
# Pagination
|
||||
page: int = Query(1, ge=1, description="Page number"),
|
||||
size: int = Query(50, ge=1, le=500, description="Page size"),
|
||||
|
||||
|
||||
# Sorting
|
||||
sort_by: str = Query("timestamp", description="Field to sort by"),
|
||||
sort_order: int = Query(-1, ge=-1, le=1, description="Sort order (-1 desc, 1 asc)"),
|
||||
|
||||
current_user: User = Depends(require_roles(UserRole.ADMIN)),
|
||||
|
||||
current_user: User = Depends(require_roles(UserRole.PRODUCTION, UserRole.ADMIN)),
|
||||
request: Request = None,
|
||||
):
|
||||
"""Get audit logs with filtering and pagination (admin only)"""
|
||||
"""Get audit logs with filtering and pagination (production/admin only)"""
|
||||
|
||||
# Log audit log access
|
||||
await audit_logger.log_action(
|
||||
|
|
@ -698,10 +698,10 @@ async def get_audit_logs(
|
|||
async def get_user_audit_logs(
|
||||
user_id: str,
|
||||
days: int = Query(30, ge=1, le=365, description="Number of days to look back"),
|
||||
current_user: User = Depends(require_roles(UserRole.ADMIN)),
|
||||
current_user: User = Depends(require_roles(UserRole.PRODUCTION, UserRole.ADMIN)),
|
||||
request: Request = None,
|
||||
):
|
||||
"""Get audit logs for a specific user (admin only)"""
|
||||
"""Get audit logs for a specific user (production/admin only)"""
|
||||
|
||||
# Validate user_id
|
||||
try:
|
||||
|
|
@ -730,10 +730,10 @@ async def get_user_audit_logs(
|
|||
@router.get("/audit-logs/security")
|
||||
async def get_security_events(
|
||||
hours: int = Query(24, ge=1, le=168, description="Number of hours to look back"),
|
||||
current_user: User = Depends(require_roles(UserRole.ADMIN)),
|
||||
current_user: User = Depends(require_roles(UserRole.PRODUCTION, UserRole.ADMIN)),
|
||||
request: Request = None,
|
||||
):
|
||||
"""Get recent security events (admin only)"""
|
||||
"""Get recent security events (production/admin only)"""
|
||||
|
||||
# Log access to security events
|
||||
await audit_logger.log_action(
|
||||
|
|
|
|||
|
|
@ -166,7 +166,7 @@ async def microsoft_login(
|
|||
"email": user_info.email,
|
||||
"full_name": user_info.name,
|
||||
"hashed_password": None, # No password for Microsoft users
|
||||
"role": UserRole.CLIENT.value,
|
||||
"role": UserRole.PRODUCTION.value,
|
||||
"auth_provider": AuthProvider.MICROSOFT.value,
|
||||
"is_active": True,
|
||||
"created_at": datetime.utcnow(),
|
||||
|
|
|
|||
|
|
@ -161,10 +161,10 @@ async def create_job(
|
|||
@router.delete("/bulk", response_model=BulkDeleteResponse)
|
||||
async def bulk_delete_jobs(
|
||||
request: BulkDeleteRequest,
|
||||
current_user: User = Depends(require_roles(UserRole.ADMIN)),
|
||||
current_user: User = Depends(require_roles(UserRole.PRODUCTION, UserRole.ADMIN)),
|
||||
db: AsyncIOMotorDatabase = Depends(get_database),
|
||||
):
|
||||
"""Bulk delete jobs (admin only)"""
|
||||
"""Bulk delete jobs (production/admin only)"""
|
||||
job_ids = request.job_ids
|
||||
logger.info(f"Bulk deleting {len(job_ids)} jobs requested by {current_user.email}")
|
||||
|
||||
|
|
@ -339,7 +339,7 @@ async def get_job(
|
|||
async def approve_english(
|
||||
job_id: str,
|
||||
request: ApproveEnglishRequest,
|
||||
current_user: User = Depends(require_roles(UserRole.REVIEWER, UserRole.ADMIN)),
|
||||
current_user: User = Depends(require_roles(UserRole.REVIEWER, UserRole.PRODUCTION, UserRole.ADMIN)),
|
||||
db: AsyncIOMotorDatabase = Depends(get_database),
|
||||
):
|
||||
result = await db.jobs.find_one_and_update(
|
||||
|
|
@ -394,7 +394,7 @@ async def approve_english(
|
|||
async def reject_job(
|
||||
job_id: str,
|
||||
request: RejectJobRequest,
|
||||
current_user: User = Depends(require_roles(UserRole.REVIEWER, UserRole.ADMIN)),
|
||||
current_user: User = Depends(require_roles(UserRole.REVIEWER, UserRole.PRODUCTION, UserRole.ADMIN)),
|
||||
db: AsyncIOMotorDatabase = Depends(get_database),
|
||||
):
|
||||
result = await db.jobs.find_one_and_update(
|
||||
|
|
@ -441,7 +441,7 @@ async def reject_job(
|
|||
async def complete_job(
|
||||
job_id: str,
|
||||
request: CompleteJobRequest,
|
||||
current_user: User = Depends(require_roles(UserRole.REVIEWER, UserRole.ADMIN)),
|
||||
current_user: User = Depends(require_roles(UserRole.REVIEWER, UserRole.PRODUCTION, UserRole.ADMIN)),
|
||||
db: AsyncIOMotorDatabase = Depends(get_database),
|
||||
):
|
||||
# Get job for validation
|
||||
|
|
@ -518,7 +518,7 @@ async def complete_job(
|
|||
async def reject_final_review(
|
||||
job_id: str,
|
||||
request: RejectJobRequest,
|
||||
current_user: User = Depends(require_roles(UserRole.REVIEWER, UserRole.ADMIN)),
|
||||
current_user: User = Depends(require_roles(UserRole.REVIEWER, UserRole.PRODUCTION, UserRole.ADMIN)),
|
||||
db: AsyncIOMotorDatabase = Depends(get_database),
|
||||
):
|
||||
result = await db.jobs.find_one_and_update(
|
||||
|
|
@ -706,7 +706,7 @@ async def get_job_vtt_content(
|
|||
async def update_job_vtt_content(
|
||||
job_id: str,
|
||||
request: VttUpdateRequest,
|
||||
current_user: User = Depends(require_roles(UserRole.REVIEWER, UserRole.ADMIN)),
|
||||
current_user: User = Depends(require_roles(UserRole.REVIEWER, UserRole.PRODUCTION, UserRole.ADMIN)),
|
||||
db: AsyncIOMotorDatabase = Depends(get_database),
|
||||
):
|
||||
"""Update VTT content for a job"""
|
||||
|
|
@ -800,7 +800,7 @@ async def update_job_vtt_content(
|
|||
async def adjust_vtt_timing(
|
||||
job_id: str,
|
||||
request: VttTimingAdjustRequest,
|
||||
current_user: User = Depends(require_roles(UserRole.REVIEWER, UserRole.ADMIN)),
|
||||
current_user: User = Depends(require_roles(UserRole.REVIEWER, UserRole.PRODUCTION, UserRole.ADMIN)),
|
||||
db: AsyncIOMotorDatabase = Depends(get_database),
|
||||
):
|
||||
"""Adjust timing of VTT content by a specified offset"""
|
||||
|
|
@ -1041,7 +1041,7 @@ async def _delete_job_gcs_assets(job_id: str, job_doc: dict):
|
|||
@router.get("/{job_id}/validate", response_model=AssetValidationResponse)
|
||||
async def validate_job_assets(
|
||||
job_id: str,
|
||||
current_user: User = Depends(require_roles(UserRole.REVIEWER, UserRole.ADMIN)),
|
||||
current_user: User = Depends(require_roles(UserRole.REVIEWER, UserRole.PRODUCTION, UserRole.ADMIN)),
|
||||
db: AsyncIOMotorDatabase = Depends(get_database),
|
||||
):
|
||||
"""Validate job assets before completion"""
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ PyObjectId = Annotated[str, BeforeValidator(validate_object_id)]
|
|||
class UserRole(str, Enum):
|
||||
CLIENT = "client"
|
||||
REVIEWER = "reviewer"
|
||||
PRODUCTION = "production"
|
||||
ADMIN = "admin"
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ async def create_test_users():
|
|||
"hashed_password": pwd_context.hash("admin"),
|
||||
"full_name": "Admin User",
|
||||
"role": UserRole.ADMIN.value,
|
||||
"auth_provider": "local",
|
||||
"is_active": True,
|
||||
"created_at": datetime.utcnow(),
|
||||
"updated_at": datetime.utcnow(),
|
||||
|
|
@ -44,6 +45,18 @@ async def create_test_users():
|
|||
"hashed_password": pwd_context.hash("reviewer"),
|
||||
"full_name": "Reviewer User",
|
||||
"role": UserRole.REVIEWER.value,
|
||||
"auth_provider": "local",
|
||||
"is_active": True,
|
||||
"created_at": datetime.utcnow(),
|
||||
"updated_at": datetime.utcnow(),
|
||||
},
|
||||
{
|
||||
"_id": "production-001",
|
||||
"email": "production@example.com",
|
||||
"hashed_password": pwd_context.hash("production"),
|
||||
"full_name": "Production User",
|
||||
"role": UserRole.PRODUCTION.value,
|
||||
"auth_provider": "local",
|
||||
"is_active": True,
|
||||
"created_at": datetime.utcnow(),
|
||||
"updated_at": datetime.utcnow(),
|
||||
|
|
@ -54,6 +67,7 @@ async def create_test_users():
|
|||
"hashed_password": pwd_context.hash("client123"),
|
||||
"full_name": "Client User",
|
||||
"role": UserRole.CLIENT.value,
|
||||
"auth_provider": "local",
|
||||
"is_active": True,
|
||||
"created_at": datetime.utcnow(),
|
||||
"updated_at": datetime.utcnow(),
|
||||
|
|
|
|||
|
|
@ -67,28 +67,28 @@ function AppContent() {
|
|||
} />
|
||||
<Route path="/admin/qc" element={
|
||||
<AuthenticatedRoute>
|
||||
<RoleGate allowedRoles={['reviewer', 'admin']}>
|
||||
<RoleGate allowedRoles={['reviewer', 'production', 'admin']}>
|
||||
<QCList />
|
||||
</RoleGate>
|
||||
</AuthenticatedRoute>
|
||||
} />
|
||||
<Route path="/admin/qc/:id" element={
|
||||
<AuthenticatedRoute>
|
||||
<RoleGate allowedRoles={['reviewer', 'admin']}>
|
||||
<RoleGate allowedRoles={['reviewer', 'production', 'admin']}>
|
||||
<QCDetail />
|
||||
</RoleGate>
|
||||
</AuthenticatedRoute>
|
||||
} />
|
||||
<Route path="/admin/final" element={
|
||||
<AuthenticatedRoute>
|
||||
<RoleGate allowedRoles={['reviewer', 'admin']}>
|
||||
<RoleGate allowedRoles={['reviewer', 'production', 'admin']}>
|
||||
<FinalList />
|
||||
</RoleGate>
|
||||
</AuthenticatedRoute>
|
||||
} />
|
||||
<Route path="/admin/final/:id" element={
|
||||
<AuthenticatedRoute>
|
||||
<RoleGate allowedRoles={['reviewer', 'admin']}>
|
||||
<RoleGate allowedRoles={['reviewer', 'production', 'admin']}>
|
||||
<FinalDetail />
|
||||
</RoleGate>
|
||||
</AuthenticatedRoute>
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ export function Navbar({ onMobileMenuClick }: NavbarProps) {
|
|||
<NotificationMenu />
|
||||
|
||||
{/* Quick Actions */}
|
||||
{['client', 'admin'].includes(user?.role || '') && (
|
||||
{['client', 'production', 'admin'].includes(user?.role || '') && (
|
||||
<Link
|
||||
to="/jobs/new"
|
||||
className="inline-flex items-center px-4 py-2 bg-gradient-to-r from-blue-500 to-blue-600 text-white text-sm font-medium rounded-lg hover:from-blue-600 hover:to-blue-700 transition-all duration-200 shadow-sm hover:shadow-md"
|
||||
|
|
|
|||
|
|
@ -32,19 +32,19 @@ export function Sidebar({ onMobileClose }: SidebarProps) {
|
|||
label: 'Upload Video',
|
||||
href: '/jobs/new',
|
||||
icon: '📤',
|
||||
roles: ['client'],
|
||||
roles: ['client', 'production', 'admin'],
|
||||
},
|
||||
{
|
||||
label: 'QC Review',
|
||||
href: '/admin/qc',
|
||||
icon: '🔍',
|
||||
roles: ['reviewer', 'admin'],
|
||||
roles: ['reviewer', 'production', 'admin'],
|
||||
},
|
||||
{
|
||||
label: 'Final Review',
|
||||
href: '/admin/final',
|
||||
icon: '✅',
|
||||
roles: ['reviewer', 'admin'],
|
||||
roles: ['reviewer', 'production', 'admin'],
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -242,6 +242,7 @@ export function Login() {
|
|||
<p className="font-medium">Demo Credentials:</p>
|
||||
<div className="bg-gray-50 rounded-lg p-3 space-y-1 text-xs">
|
||||
<p><strong>Admin:</strong> admin@example.com / admin</p>
|
||||
<p><strong>Production:</strong> production@example.com / production</p>
|
||||
<p><strong>Reviewer:</strong> reviewer@example.com / reviewer</p>
|
||||
</div>
|
||||
<p className="text-xs">
|
||||
|
|
|
|||
|
|
@ -164,6 +164,7 @@ export function UserDetail() {
|
|||
>
|
||||
<option value="client">Client</option>
|
||||
<option value="reviewer">Reviewer</option>
|
||||
<option value="production">Production</option>
|
||||
<option value="admin">Admin</option>
|
||||
</select>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -118,6 +118,7 @@ export function UserList() {
|
|||
>
|
||||
<option value="">All Roles</option>
|
||||
<option value="admin">Admin</option>
|
||||
<option value="production">Production</option>
|
||||
<option value="reviewer">Reviewer</option>
|
||||
<option value="client">Client</option>
|
||||
</select>
|
||||
|
|
@ -201,6 +202,7 @@ export function UserList() {
|
|||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium capitalize ${
|
||||
user.role === 'admin' ? 'bg-purple-100 text-purple-800' :
|
||||
user.role === 'production' ? 'bg-orange-100 text-orange-800' :
|
||||
user.role === 'reviewer' ? 'bg-blue-100 text-blue-800' :
|
||||
'bg-green-100 text-green-800'
|
||||
}`}>
|
||||
|
|
@ -425,6 +427,7 @@ function CreateUserModal({ onClose, onSuccess }: { onClose: () => void; onSucces
|
|||
>
|
||||
<option value="client">Client</option>
|
||||
<option value="reviewer">Reviewer</option>
|
||||
<option value="production">Production</option>
|
||||
<option value="admin">Admin</option>
|
||||
</select>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export type JobStatus =
|
|||
| "pending_final_review"
|
||||
| "completed";
|
||||
|
||||
export type UserRole = "client" | "reviewer" | "admin";
|
||||
export type UserRole = "client" | "reviewer" | "production" | "admin";
|
||||
export type AuthProvider = "local" | "microsoft";
|
||||
|
||||
export interface User {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue