From 731fcb228805fca3b8663fdb2f0b7f4a6333d46b Mon Sep 17 00:00:00 2001 From: Ivan <45982459+ivntsng@users.noreply.github.com> Date: Mon, 25 Nov 2024 16:09:01 -0800 Subject: [PATCH] restrict kernel uploads to verified members and admins (#641) * restrict krec/kernel uploads to verified members and admins * Removed unused import * store/app/routers/krecs.py --- frontend/src/components/pages/Profile.tsx | 2 + frontend/src/gen/api.ts | 51 +++++++++++++++++++++-- store/app/model.py | 2 +- store/app/routers/artifacts.py | 6 +++ store/app/routers/teleop/webrtc.py | 13 ++++++ 5 files changed, 70 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/pages/Profile.tsx b/frontend/src/components/pages/Profile.tsx index 1e84181c..a434a5f4 100644 --- a/frontend/src/components/pages/Profile.tsx +++ b/frontend/src/components/pages/Profile.tsx @@ -176,6 +176,8 @@ export const RenderProfile = (props: RenderProfileProps) => { return "Moderator"; case permissions.includes("is_content_manager"): return "Content Manager"; + case permissions.includes("is_verified_member"): + return "Verified Member"; default: return "Member"; } diff --git a/frontend/src/gen/api.ts b/frontend/src/gen/api.ts index 38804aba..ff617f98 100644 --- a/frontend/src/gen/api.ts +++ b/frontend/src/gen/api.ts @@ -1276,6 +1276,26 @@ export interface paths { patch?: never; trace?: never; }; + "/teleop/rtc/check": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Check Auth + * @description Validates the user's API key and returns their user ID. + */ + get: operations["check_auth_teleop_rtc_check_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/krecs/upload": { parameters: { query?: never; @@ -1462,6 +1482,11 @@ export interface components { /** Details */ details: string; }; + /** CheckAuthResponse */ + CheckAuthResponse: { + /** User Id */ + user_id: string; + }; /** ClientIdResponse */ ClientIdResponse: { /** Client Id */ @@ -1851,7 +1876,7 @@ export interface components { /** Google Id */ google_id: string | null; /** Permissions */ - permissions: ("is_admin" | "is_mod" | "is_content_manager")[] | null; + permissions: ("is_admin" | "is_mod" | "is_content_manager" | "is_verified_member")[] | null; /** First Name */ first_name: string | null; /** Last Name */ @@ -2027,7 +2052,7 @@ export interface components { /** Username */ username: string; /** Permissions */ - permissions?: ("is_admin" | "is_mod" | "is_content_manager")[] | null; + permissions?: ("is_admin" | "is_mod" | "is_content_manager" | "is_verified_member")[] | null; /** Created At */ created_at?: number | null; /** Updated At */ @@ -2306,7 +2331,7 @@ export interface components { /** Username */ username: string; /** Permissions */ - permissions?: ("is_admin" | "is_mod" | "is_content_manager")[] | null; + permissions?: ("is_admin" | "is_mod" | "is_content_manager" | "is_verified_member")[] | null; /** Created At */ created_at: number; /** Updated At */ @@ -4620,6 +4645,26 @@ export interface operations { }; }; }; + check_auth_teleop_rtc_check_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CheckAuthResponse"]; + }; + }; + }; + }; create_krec_krecs_upload_post: { parameters: { query?: never; diff --git a/store/app/model.py b/store/app/model.py index 1625d163..4d3fa1f6 100644 --- a/store/app/model.py +++ b/store/app/model.py @@ -28,7 +28,7 @@ class StoreBaseModel(BaseModel): id: str -UserPermission = Literal["is_admin", "is_mod", "is_content_manager"] +UserPermission = Literal["is_admin", "is_mod", "is_content_manager", "is_verified_member"] class UserStripeConnect(BaseModel): diff --git a/store/app/routers/artifacts.py b/store/app/routers/artifacts.py index 2c2099fc..784b94b9 100644 --- a/store/app/routers/artifacts.py +++ b/store/app/routers/artifacts.py @@ -439,6 +439,12 @@ async def get_presigned_url( detail="Filename was not provided", ) + user_permissions = user.permissions or set() + if not ("is_admin" in user_permissions or "is_verified_member" in user_permissions): + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, detail="Only verified members and admins can upload kernel images" + ) + if not Path(filename).suffix.lower() == ".img": raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Only .img files are supported for kernel uploads" diff --git a/store/app/routers/teleop/webrtc.py b/store/app/routers/teleop/webrtc.py index e0c58f2f..7e50fc2d 100644 --- a/store/app/routers/teleop/webrtc.py +++ b/store/app/routers/teleop/webrtc.py @@ -7,6 +7,7 @@ from fastapi import APIRouter, Depends, WebSocket, WebSocketDisconnect from fastapi.responses import StreamingResponse +from pydantic import BaseModel from store.app.db import Crud from store.app.model import TeleopICECandidate, User @@ -75,3 +76,15 @@ async def poll_ice_candidates( content=ice_candidates_generator(user.id, robot_id, crud), media_type="text/event-stream", ) + + +class CheckAuthResponse(BaseModel): + user_id: str + + +@router.get("/check", response_model=CheckAuthResponse) +async def check_auth( + user: Annotated[User, Depends(get_session_user_with_write_permission)], +) -> CheckAuthResponse: + """Validates the user's API key and returns their user ID.""" + return CheckAuthResponse(user_id=user.id)