From 4562165973285de0cf5d14b305e9fad858e9c8af Mon Sep 17 00:00:00 2001 From: IJtLJZ8Rm4Yr <90443055+IJtLJZ8Rm4Yr@users.noreply.github.com> Date: Wed, 22 Mar 2023 10:24:40 +0800 Subject: [PATCH 1/6] [auth] add email to JWT (#1672) --- ymir/backend/src/ymir_auth/auth/api/api_v1/endpoints/login.py | 1 + ymir/backend/src/ymir_auth/auth/schemas/token.py | 1 + ymir/backend/src/ymir_auth/auth/schemas/user.py | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ymir/backend/src/ymir_auth/auth/api/api_v1/endpoints/login.py b/ymir/backend/src/ymir_auth/auth/api/api_v1/endpoints/login.py index b35fccfcae..d59f8ea169 100644 --- a/ymir/backend/src/ymir_auth/auth/api/api_v1/endpoints/login.py +++ b/ymir/backend/src/ymir_auth/auth/api/api_v1/endpoints/login.py @@ -63,6 +63,7 @@ def login_access_token( access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) token_payload = { "id": user.id, + "email": user.email, "role": role.name, } token_payload["version"] = YMIR_VERSION diff --git a/ymir/backend/src/ymir_auth/auth/schemas/token.py b/ymir/backend/src/ymir_auth/auth/schemas/token.py index ddcf07ba88..b3f17b69c5 100644 --- a/ymir/backend/src/ymir_auth/auth/schemas/token.py +++ b/ymir/backend/src/ymir_auth/auth/schemas/token.py @@ -19,4 +19,5 @@ class TokenOut(Common): class TokenPayload(BaseModel): id: int role: str + email: str version: Optional[str] diff --git a/ymir/backend/src/ymir_auth/auth/schemas/user.py b/ymir/backend/src/ymir_auth/auth/schemas/user.py index c25b9d9f32..8ded768b93 100644 --- a/ymir/backend/src/ymir_auth/auth/schemas/user.py +++ b/ymir/backend/src/ymir_auth/auth/schemas/user.py @@ -45,7 +45,7 @@ class UserBase(BaseModel): class UserCreate(UserBase): username: Optional[constr(min_length=2, max_length=15, strip_whitespace=True)] = None phone: Optional[str] = None - password: str + password: constr(min_length=8, max_length=40, strip_whitespace=True) @validator("phone") def check_phone(cls, v: Optional[str]) -> Optional[str]: From db22164017a2342a203c15b6402456fe192b9c47 Mon Sep 17 00:00:00 2001 From: IJtLJZ8Rm4Yr <90443055+IJtLJZ8Rm4Yr@users.noreply.github.com> Date: Wed, 22 Mar 2023 15:13:48 +0800 Subject: [PATCH 2/6] [deploy] use native redis docker image (#1673) --- ymir/backend/src/ymir_app/deploy/redis/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ymir/backend/src/ymir_app/deploy/redis/Dockerfile b/ymir/backend/src/ymir_app/deploy/redis/Dockerfile index 68dd719d0e..00236a698b 100644 --- a/ymir/backend/src/ymir_app/deploy/redis/Dockerfile +++ b/ymir/backend/src/ymir_app/deploy/redis/Dockerfile @@ -1,3 +1,3 @@ -FROM redislabs/redisgraph +FROM redis:7.0.10-alpine3.17 COPY redis.conf /config/redis/redis.conf From f7bf1f28110524d34c0daba7b11bb1b7cf83b828 Mon Sep 17 00:00:00 2001 From: IJtLJZ8Rm4Yr <90443055+IJtLJZ8Rm4Yr@users.noreply.github.com> Date: Thu, 23 Mar 2023 14:24:56 +0800 Subject: [PATCH 3/6] [auth] use auth to protect backend api (#1675) * [auth] use auth to protect backend api * update jwt based app deps * add forwardAuth in traefik * fix tests * migrate to auth tables * do not set status code to 200 for auth --- .../src/ymir_app/app/api/api_v1/api.py | 2 - .../app/api/api_v1/endpoints/assets.py | 6 +- .../api/api_v1/endpoints/dataset_groups.py | 12 +- .../app/api/api_v1/endpoints/datasets.py | 26 +-- .../app/api/api_v1/endpoints/images.py | 16 +- .../app/api/api_v1/endpoints/inferences.py | 4 +- .../ymir_app/app/api/api_v1/endpoints/info.py | 4 +- .../app/api/api_v1/endpoints/iterations.py | 22 +-- .../app/api/api_v1/endpoints/keywords.py | 8 +- .../app/api/api_v1/endpoints/login.py | 120 ------------- .../app/api/api_v1/endpoints/model_groups.py | 12 +- .../app/api/api_v1/endpoints/model_stages.py | 6 +- .../app/api/api_v1/endpoints/models.py | 16 +- .../app/api/api_v1/endpoints/predictions.py | 10 +- .../app/api/api_v1/endpoints/projects.py | 16 +- .../app/api/api_v1/endpoints/stats.py | 4 +- .../app/api/api_v1/endpoints/tasks.py | 18 +- .../app/api/api_v1/endpoints/upload.py | 4 +- .../app/api/api_v1/endpoints/users.py | 169 +----------------- ymir/backend/src/ymir_app/app/api/deps.py | 80 +++------ ymir/backend/src/ymir_app/app/schemas/user.py | 11 ++ .../src/ymir_app/tests/api/test_login.py | 49 ----- ymir/backend/src/ymir_app/tests/conftest.py | 22 ++- .../src/ymir_app/tests/crud/test_user.py | 52 ------ .../backend/src/ymir_app/tests/utils/utils.py | 34 +--- .../4a002517f2a9_add_user_and_role_table.py | 107 ++++++----- .../auth/api/api_v1/endpoints/login.py | 6 +- ymir/backend/src/ymir_auth/auth/config.py | 2 +- .../backend/src/ymir_auth/auth/models/role.py | 2 +- .../backend/src/ymir_auth/auth/models/user.py | 2 +- ymir/gateway/dynamic-conf.yml | 17 ++ 31 files changed, 227 insertions(+), 632 deletions(-) delete mode 100644 ymir/backend/src/ymir_app/app/api/api_v1/endpoints/login.py delete mode 100644 ymir/backend/src/ymir_app/tests/api/test_login.py delete mode 100644 ymir/backend/src/ymir_app/tests/crud/test_user.py diff --git a/ymir/backend/src/ymir_app/app/api/api_v1/api.py b/ymir/backend/src/ymir_app/app/api/api_v1/api.py index 5f1d83afd1..2210060bb0 100644 --- a/ymir/backend/src/ymir_app/app/api/api_v1/api.py +++ b/ymir/backend/src/ymir_app/app/api/api_v1/api.py @@ -7,7 +7,6 @@ inferences, info, keywords, - login, models, roles, stats, @@ -24,7 +23,6 @@ api_router = APIRouter() -api_router.include_router(login.router, tags=["auth"]) api_router.include_router(users.router, prefix="/users", tags=["users"]) api_router.include_router(projects.router, prefix="/projects", tags=["projects"]) api_router.include_router(iterations.router, prefix="/iterations", tags=["iterations"]) diff --git a/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/assets.py b/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/assets.py index 6d38a1919e..c3a0c33d65 100644 --- a/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/assets.py +++ b/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/assets.py @@ -30,7 +30,7 @@ def list_assets( tags_str: Optional[str] = Query(None, example="big,small", alias="tags"), annotation_types_str: Optional[str] = Query(None, example="gt,pred", alias="annotation_types"), viz_client: VizClient = Depends(deps.get_viz_client), - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), user_labels: UserLabels = Depends(deps.get_user_labels), ) -> Any: """ @@ -66,7 +66,7 @@ def get_random_asset_id_of_dataset( data_type: AnnotationType, db: Session = Depends(deps.get_db), viz_client: VizClient = Depends(deps.get_viz_client), - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), user_labels: UserLabels = Depends(deps.get_user_labels), ) -> Any: """ @@ -108,7 +108,7 @@ def get_asset_info( asset_hash: str = Path(..., description="in asset hash format"), db: Session = Depends(deps.get_db), viz_client: VizClient = Depends(deps.get_viz_client), - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), user_labels: UserLabels = Depends(deps.get_user_labels), ) -> Any: """ diff --git a/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/dataset_groups.py b/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/dataset_groups.py index e95d061888..a28ff12c70 100644 --- a/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/dataset_groups.py +++ b/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/dataset_groups.py @@ -4,7 +4,7 @@ from fastapi.logger import logger from sqlalchemy.orm import Session -from app import crud, models, schemas +from app import crud, schemas from app.api import deps from app.api.errors.errors import ( DatasetGroupNotFound, @@ -18,7 +18,7 @@ @router.get("/", response_model=schemas.DatasetGroupPaginationOut) def list_dataset_groups( db: Session = Depends(deps.get_db), - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), name: str = Query(None, description="search by dataset group name"), project_id: int = Query(None), pagination: schemas.CommonPaginationParams = Depends(), @@ -37,7 +37,7 @@ def list_dataset_groups( def create_dataset_group( *, db: Session = Depends(deps.get_db), - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), obj_in: schemas.DatasetGroupCreate, controller_client: ControllerClient = Depends(deps.get_controller_client), ) -> Any: @@ -59,7 +59,7 @@ def get_dataset_group( *, db: Session = Depends(deps.get_db), group_id: int = Path(...), - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), ) -> Any: """ Get a dataset group detail @@ -79,7 +79,7 @@ def update_dataset_group( db: Session = Depends(deps.get_db), group_id: int = Path(...), obj_update: schemas.DatasetGroupUpdate, - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), ) -> Any: """ Change dataset group name @@ -103,7 +103,7 @@ def delete_dataset_group( *, db: Session = Depends(deps.get_db), group_id: int = Path(...), - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), ) -> Any: """ Delete dataset group diff --git a/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/datasets.py b/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/datasets.py index bcb1fa5698..f7fb582210 100644 --- a/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/datasets.py +++ b/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/datasets.py @@ -4,7 +4,7 @@ from fastapi.logger import logger from sqlalchemy.orm import Session -from app import crud, models, schemas +from app import crud, schemas from app.api import deps from app.api.errors.errors import ( DatasetGroupNotFound, @@ -35,7 +35,7 @@ def batch_get_datasets( dataset_ids: str = Query(..., example="1,2,3", alias="ids", min_length=1), require_ck: bool = Query(False, alias="ck"), require_hist: bool = Query(False, alias="hist"), - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), user_labels: UserLabels = Depends(deps.get_user_labels), ) -> Any: ids = list({int(i) for i in dataset_ids.split(",")}) @@ -62,7 +62,7 @@ def batch_update_datasets( *, db: Session = Depends(deps.get_db), dataset_ops: schemas.BatchOperations, - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), ) -> Any: if not dataset_ops.operations: raise MissingOperations() @@ -85,7 +85,7 @@ def batch_update_datasets( @router.get("/", response_model=schemas.DatasetPaginationOut) def list_datasets( db: Session = Depends(deps.get_db), - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), source: TaskType = Query(None, description="type of related task"), exclude_source: TaskType = Query(None, description="exclude type of related task"), project_id: int = Query(None), @@ -119,7 +119,7 @@ def list_datasets( @router.get("/public", response_model=schemas.DatasetPaginationOut) def get_public_datasets( db: Session = Depends(deps.get_db), - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), ) -> Any: """ Get all the public datasets @@ -140,7 +140,7 @@ def import_dataset( *, db: Session = Depends(deps.get_db), dataset_import: schemas.DatasetImport, - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), controller_client: ControllerClient = Depends(deps.get_controller_client), background_tasks: BackgroundTasks, ) -> Any: @@ -225,7 +225,7 @@ def delete_dataset( *, db: Session = Depends(deps.get_db), dataset_id: int = Path(..., example="12"), - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), ) -> Any: """ Delete dataset @@ -256,7 +256,7 @@ def update_dataset( db: Session = Depends(deps.get_db), dataset_id: int = Path(..., example="12"), dataset_update: schemas.DatasetUpdate, - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), ) -> Any: dataset = crud.dataset.get_by_user_and_id(db, user_id=current_user.id, id=dataset_id) if not dataset: @@ -276,7 +276,7 @@ def get_dataset( dataset_id: int = Path(..., example="12"), keywords_for_negative_info: str = Query(None, alias="keywords"), verbose_info: bool = Query(False, alias="verbose"), - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), viz_client: VizClient = Depends(deps.get_viz_client), user_labels: UserLabels = Depends(deps.get_user_labels), ) -> Any: @@ -323,7 +323,7 @@ def check_duplication( *, db: Session = Depends(deps.get_db), in_datasets: schemas.dataset.MultiDatasetsWithProjectID, - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), viz_client: VizClient = Depends(deps.get_viz_client), ) -> Any: """ @@ -342,7 +342,7 @@ def create_dataset_fusion_task( *, db: Session = Depends(deps.get_db), in_fusion: schemas.task.FusionParameter, - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), controller_client: ControllerClient = Depends(deps.get_controller_client), user_labels: UserLabels = Depends(deps.get_user_labels), ) -> Any: @@ -369,7 +369,7 @@ def merge_datasets( *, db: Session = Depends(deps.get_db), in_merge: schemas.task.FusionParameter, - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), controller_client: ControllerClient = Depends(deps.get_controller_client), user_labels: UserLabels = Depends(deps.get_user_labels), ) -> Any: @@ -392,7 +392,7 @@ def filter_dataset( *, db: Session = Depends(deps.get_db), in_filter: schemas.task.FusionParameter, - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), controller_client: ControllerClient = Depends(deps.get_controller_client), user_labels: UserLabels = Depends(deps.get_user_labels), ) -> Any: diff --git a/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/images.py b/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/images.py index 9c249e5b7e..29b6162d99 100644 --- a/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/images.py +++ b/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/images.py @@ -4,7 +4,7 @@ from fastapi.logger import logger from sqlalchemy.orm import Session -from app import crud, models, schemas +from app import crud, schemas from app.api import deps from app.api.errors.errors import ( DockerImageHavingRelationships, @@ -22,7 +22,7 @@ @router.get("/", response_model=schemas.DockerImagesOut) def list_docker_images( db: Session = Depends(deps.get_db), - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), name: str = Query(None), url: str = Query(None), state: DockerImageState = Query(None), @@ -52,7 +52,7 @@ def list_docker_images( def create_docker_image( *, db: Session = Depends(deps.get_db), - current_user: models.User = Depends(deps.get_current_active_admin), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_admin), docker_image_in: DockerImageCreate, controller_client: ControllerClient = Depends(deps.get_controller_client), background_tasks: BackgroundTasks, @@ -135,7 +135,7 @@ def get_docker_image( *, db: Session = Depends(deps.get_db), docker_image_id: int = Path(...), - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), ) -> Any: """ Get a single docker image @@ -157,7 +157,7 @@ def update_docker_image( db: Session = Depends(deps.get_db), docker_image_id: int = Path(...), docker_image_update: schemas.DockerImageUpdate, - current_user: models.User = Depends(deps.get_current_active_admin), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_admin), ) -> Any: """ Update docker image's name and description @@ -181,7 +181,7 @@ def delete_docker_image( *, db: Session = Depends(deps.get_db), docker_image_id: int = Path(...), - current_user: models.User = Depends(deps.get_current_active_admin), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_admin), ) -> Any: """ Delete docker image @@ -204,7 +204,7 @@ def link_docker_images( *, db: Session = Depends(deps.get_db), docker_image_id: int = Path(...), - current_user: models.User = Depends(deps.get_current_active_admin), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_admin), image_relationships: schemas.ImageRelationshipsCreate, ) -> Any: """ @@ -228,7 +228,7 @@ def get_related_images( *, db: Session = Depends(deps.get_db), docker_image_id: int = Path(...), - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), ) -> Any: """ Get all the related_images of given docker image diff --git a/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/inferences.py b/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/inferences.py index e9e4c2db6c..59518c1391 100644 --- a/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/inferences.py +++ b/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/inferences.py @@ -5,7 +5,7 @@ from fastapi.logger import logger from sqlalchemy.orm import Session -from app import crud, models, schemas +from app import crud, schemas from app.api import deps from app.api.errors.errors import ( FailedToCallInference, @@ -26,7 +26,7 @@ def call_inference( *, inference_in: schemas.InferenceCreate, db: Session = Depends(deps.get_db), - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), controller_client: ControllerClient = Depends(deps.get_controller_client), ) -> Any: """ diff --git a/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/info.py b/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/info.py index 352dbc823d..2e43313454 100644 --- a/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/info.py +++ b/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/info.py @@ -3,7 +3,7 @@ import grpc from fastapi import APIRouter, Depends -from app import models, schemas +from app import schemas from app.api import deps from app.api.errors.errors import FailedtoGetSysInfo from app.config import settings @@ -15,7 +15,7 @@ @router.get("/", response_model=schemas.SysInfoOut) def get_sys_info( *, - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), controller_client: ControllerClient = Depends(deps.get_controller_client), ) -> Any: """ diff --git a/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/iterations.py b/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/iterations.py index 260ea8adfb..3c8466c04e 100644 --- a/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/iterations.py +++ b/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/iterations.py @@ -4,7 +4,7 @@ from fastapi.logger import logger from sqlalchemy.orm import Session -from app import crud, models, schemas +from app import crud, schemas from app.api import deps from app.api.errors.errors import ( DuplicateIterationError, @@ -27,7 +27,7 @@ def create_iteration( *, db: Session = Depends(deps.get_db), - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), obj_in: schemas.IterationCreate, ) -> Any: """ @@ -51,7 +51,7 @@ def create_iteration( @router.get("/", response_model=schemas.IterationsOut) def list_iterations( db: Session = Depends(deps.get_db), - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), project_id: int = Query(...), ) -> Any: """ @@ -64,7 +64,7 @@ def list_iterations( @router.get("/{iteration_id}", response_model=schemas.IterationOut) def get_iteration( db: Session = Depends(deps.get_db), - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), project_id: int = Query(...), iteration_id: int = Path(...), ) -> Any: @@ -86,7 +86,7 @@ def update_iteration( db: Session = Depends(deps.get_db), iteration_id: int = Path(...), iteration_updates: schemas.IterationUpdate, - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), ) -> Any: """ Change iteration stage and update iteration context when necessary @@ -108,7 +108,7 @@ def update_iteration( @router.get("/{iteration_id}/mining_progress", response_model=schemas.iteration.IterationMiningProgressOut) def get_mining_progress_of_iteration( db: Session = Depends(deps.get_db), - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), project_id: int = Query(...), iteration_id: int = Path(...), user_labels: UserLabels = Depends(deps.get_user_labels), @@ -125,7 +125,7 @@ def list_iteration_steps( *, db: Session = Depends(deps.get_db), iteration_id: int = Path(...), - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), ) -> Any: iteration = crud.iteration.get_by_user_and_id(db, user_id=current_user.id, id=iteration_id) if not iteration: @@ -140,7 +140,7 @@ def get_iteration_step( db: Session = Depends(deps.get_db), iteration_id: int = Path(...), step_id: int = Path(...), - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), ) -> Any: iteration = crud.iteration.get_by_user_and_id(db, user_id=current_user.id, id=iteration_id) if not iteration: @@ -158,7 +158,7 @@ def bind_iteration_step( iteration_id: int = Path(...), step_id: int = Path(...), task_id: int = Query(...), - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), ) -> Any: """ start given step: @@ -188,7 +188,7 @@ def unbind_iteration_step( iteration_id: int = Path(...), step_id: int = Path(...), user_labels: UserLabels = Depends(deps.get_user_labels), - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), ) -> Any: iteration = crud.iteration.get_by_user_and_id(db, user_id=current_user.id, id=iteration_id) if not iteration: @@ -208,7 +208,7 @@ def finish_iteration_step( db: Session = Depends(deps.get_db), iteration_id: int = Path(...), step_id: int = Path(...), - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), ) -> Any: # make sure iteration belongs to user iteration = crud.iteration.get_by_user_and_id(db, user_id=current_user.id, id=iteration_id) diff --git a/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/keywords.py b/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/keywords.py index f6979375af..c8593861d5 100644 --- a/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/keywords.py +++ b/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/keywords.py @@ -3,7 +3,7 @@ from fastapi import APIRouter, Depends, Query from fastapi.logger import logger -from app import models +from app import schemas from app.api import deps from app.config import settings from app.schemas.keyword import ( @@ -27,7 +27,7 @@ response_model=KeywordsPaginationOut, ) def get_keywords( - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), controller_client: ControllerClient = Depends(deps.get_controller_client), user_labels: UserLabels = Depends(deps.get_user_labels), q: Optional[str] = Query(None, description="query keywords"), @@ -53,7 +53,7 @@ def get_keywords( def create_keywords( *, keywords_input: KeywordsInput, - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), controller_client: ControllerClient = Depends(deps.get_controller_client), cache: CacheClient = Depends(deps.get_cache), ) -> Any: @@ -88,7 +88,7 @@ def update_keyword_aliases( *, keyword: str, aliases_in: KeywordUpdate, - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), controller_client: ControllerClient = Depends(deps.get_controller_client), cache: CacheClient = Depends(deps.get_cache), ) -> Any: diff --git a/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/login.py b/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/login.py deleted file mode 100644 index 48cab98407..0000000000 --- a/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/login.py +++ /dev/null @@ -1,120 +0,0 @@ -from datetime import timedelta -from typing import Any - -from fastapi import APIRouter, Body, Depends -from fastapi.logger import logger -from fastapi.security import OAuth2PasswordRequestForm -from sqlalchemy.orm import Session - -from app import crud, schemas -from app.api import deps -from app.api.errors.errors import ( - FailedToSendEmail, - InactiveUser, - IncorrectEmailOrPassword, - InvalidToken, - NotEligibleRole, - UserNotFound, -) -from app.config import settings -from app.utils import security -from app.utils.email import send_reset_password_email -from common_utils.version import YMIR_VERSION - -router = APIRouter() - - -@router.post( - "/auth/token", - response_model=schemas.TokenOut, - responses={ - 400: {"description": "Incorrect email or password"}, - 403: {"description": "Inactive user"}, - }, -) -def login_access_token( - db: Session = Depends(deps.get_db), - form_data: OAuth2PasswordRequestForm = Depends(), -) -> Any: - """ - OAuth2 compatible token login, get an access token for future requests - """ - user = crud.user.authenticate(db, email=form_data.username, password=form_data.password) - if not user: - raise IncorrectEmailOrPassword() - if not crud.user.is_active(user): - raise InactiveUser() - highest_role = schemas.UserRole(user.role) - - # User can request specific roles - # choose the role of highest rank yet within limit - if form_data.scopes: - claimed_role = max(getattr(schemas.UserRole, scope) for scope in form_data.scopes) - else: - claimed_role = highest_role - - # make sure the claimed_role is valid - if claimed_role > highest_role: - raise NotEligibleRole() - - # grant user adequate role - role = min(claimed_role, highest_role) - - access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) - token_payload = { - "id": user.id, - "role": role.name, - } - token_payload["version"] = YMIR_VERSION - payload = { - "access_token": security.create_access_token(token_payload, expires_delta=access_token_expires), - "token_type": "bearer", - } - - # update last login time - crud.user.update_login_time(db, user=user) - - return {"result": payload, **payload} - - -@router.post("/password-recovery/{email}", response_model=schemas.Msg) -def recover_password(email: str, db: Session = Depends(deps.get_db)) -> Any: - """ - Recover password - """ - user = crud.user.get_by_email(db, email=email) - - if not user: - raise UserNotFound() - password_reset_token = security.generate_password_reset_token(email=email) - try: - send_reset_password_email(email_to=user.email, email=email, token=password_reset_token) - except Exception: - logger.exception("[reset password] Failed to send email. Please check configuration") - raise FailedToSendEmail() - - return {"message": "Password recovery email sent"} - - -@router.post("/reset-password/", response_model=schemas.Msg) -def reset_password( - token: str = Body(...), - new_password: str = Body(...), - db: Session = Depends(deps.get_db), -) -> Any: - """ - Reset password - """ - email = security.verify_password_reset_token(token) - if not email: - raise InvalidToken() - user = crud.user.get_by_email(db, email=email) - if not user: - raise UserNotFound() - elif crud.user.is_deleted(user): - raise InactiveUser() - hashed_password = security.get_password_hash(new_password) - user.hashed_password = hashed_password - db.add(user) - db.commit() - return {"message": "Password updated successfully"} diff --git a/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/model_groups.py b/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/model_groups.py index e3e263aac2..6549d5f1d0 100644 --- a/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/model_groups.py +++ b/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/model_groups.py @@ -4,7 +4,7 @@ from fastapi.logger import logger from sqlalchemy.orm import Session -from app import crud, models, schemas +from app import crud, schemas from app.api import deps from app.api.errors.errors import ModelGroupNotFound, DuplicateModelGroupError from app.utils.ymir_controller import ControllerClient @@ -15,7 +15,7 @@ @router.get("/", response_model=schemas.ModelGroupPaginationOut) def list_model_groups( db: Session = Depends(deps.get_db), - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), project_id: int = Query(None), name: str = Query(None, description="search by model's name"), pagination: schemas.CommonPaginationParams = Depends(), @@ -34,7 +34,7 @@ def list_model_groups( def create_model_group( *, db: Session = Depends(deps.get_db), - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), obj_in: schemas.ModelGroupCreate, controller_client: ControllerClient = Depends(deps.get_controller_client), ) -> Any: @@ -56,7 +56,7 @@ def get_model_group( *, db: Session = Depends(deps.get_db), group_id: int = Path(...), - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), ) -> Any: """ Get a model group detail @@ -76,7 +76,7 @@ def update_model_group( db: Session = Depends(deps.get_db), group_id: int = Path(...), obj_update: schemas.ModelGroupUpdate, - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), ) -> Any: """ Change model group name @@ -100,7 +100,7 @@ def delete_model_group( *, db: Session = Depends(deps.get_db), group_id: int = Path(...), - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), ) -> Any: """ Delete model group diff --git a/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/model_stages.py b/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/model_stages.py index 0cca19bb70..7c81a54b2c 100644 --- a/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/model_stages.py +++ b/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/model_stages.py @@ -3,7 +3,7 @@ from fastapi import APIRouter, Depends, Path, Query from sqlalchemy.orm import Session -from app import crud, models, schemas +from app import crud, schemas from app.api import deps from app.api.errors.errors import ModelStageNotFound @@ -17,7 +17,7 @@ def batch_get_models( db: Session = Depends(deps.get_db), model_stage_ids: str = Query(None, alias="ids"), - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), ) -> Any: ids = [int(i) for i in model_stage_ids.split(",")] stages = crud.model_stage.get_multi_by_user_and_ids(db, user_id=current_user.id, ids=ids) @@ -32,7 +32,7 @@ def get_model_stage( *, db: Session = Depends(deps.get_db), model_stage_id: int = Path(...), - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), ) -> Any: """ Get a model stage detail diff --git a/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/models.py b/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/models.py index c2b8134f11..fa6b350447 100644 --- a/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/models.py +++ b/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/models.py @@ -4,7 +4,7 @@ from fastapi.logger import logger from sqlalchemy.orm import Session -from app import crud, models, schemas +from app import crud, schemas from app.api import deps from app.api.errors.errors import ( ModelNotFound, @@ -25,7 +25,7 @@ def batch_get_models( db: Session = Depends(deps.get_db), model_ids: str = Query(None, alias="ids"), - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), ) -> Any: ids = [int(i) for i in model_ids.split(",")] models = crud.model.get_multi_by_user_and_ids(db, user_id=current_user.id, ids=ids) @@ -37,7 +37,7 @@ def batch_update_models( *, db: Session = Depends(deps.get_db), model_ops: schemas.BatchOperations, - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), ) -> Any: project = crud.project.get(db, model_ops.project_id) if not project: @@ -56,7 +56,7 @@ def batch_update_models( @router.get("/", response_model=schemas.ModelPaginationOut) def list_models( db: Session = Depends(deps.get_db), - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), source: TaskType = Query(None, description="type of related task"), state: ResultState = Query(None), project_id: int = Query(None), @@ -96,7 +96,7 @@ def import_model( *, db: Session = Depends(deps.get_db), model_import: schemas.ModelImport, - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), controller_client: ControllerClient = Depends(deps.get_controller_client), background_tasks: BackgroundTasks, ) -> Any: @@ -157,7 +157,7 @@ def delete_model( *, db: Session = Depends(deps.get_db), model_id: int = Path(..., example="12"), - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), ) -> Any: """ Delete model @@ -177,7 +177,7 @@ def update_model( db: Session = Depends(deps.get_db), model_id: int = Path(..., example="12"), model_update: schemas.ModelUpdate, - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), ) -> Any: model = crud.model.get_by_user_and_id(db, user_id=current_user.id, id=model_id) if not model: @@ -191,7 +191,7 @@ def update_model( def get_model( db: Session = Depends(deps.get_db), model_id: int = Path(..., example="12"), - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), ) -> Any: """ Get verbose information of specific model diff --git a/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/predictions.py b/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/predictions.py index 745aa58aa0..4a3bbfe1ba 100644 --- a/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/predictions.py +++ b/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/predictions.py @@ -3,7 +3,7 @@ from fastapi.logger import logger from sqlalchemy.orm import Session -from app import crud, models, schemas +from app import crud, schemas from app.utils.data import groupby from app.api import deps from app.api.errors.errors import ( @@ -26,7 +26,7 @@ def list_predictions( *, db: Session = Depends(deps.get_db), - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), project_id: int = Query(None), visible: bool = Query(True), pagination: schemas.CommonPaginationParams = Depends(), @@ -46,7 +46,7 @@ def list_predictions( def get_prediction( db: Session = Depends(deps.get_db), prediction_id: int = Path(..., example="12"), - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), viz_client: VizClient = Depends(deps.get_viz_client), user_labels: UserLabels = Depends(deps.get_user_labels), ) -> Any: @@ -77,7 +77,7 @@ def batch_update_predictions( *, db: Session = Depends(deps.get_db), prediction_ops: schemas.BatchOperations, - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), ) -> Any: if not prediction_ops.operations: raise MissingOperations() @@ -101,7 +101,7 @@ def batch_evaluate_datasets( *, db: Session = Depends(deps.get_db), in_evaluation: schemas.prediction.PredictionEvaluationCreate, - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), controller_client: ControllerClient = Depends(deps.get_controller_client), user_labels: UserLabels = Depends(deps.get_user_labels), ) -> Any: diff --git a/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/projects.py b/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/projects.py index d219850ed8..067caf4494 100644 --- a/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/projects.py +++ b/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/projects.py @@ -5,7 +5,7 @@ from fastapi.logger import logger from sqlalchemy.orm import Session -from app import crud, models, schemas +from app import crud, schemas from app.api import deps from app.api.errors.errors import ( ProjectNotFound, @@ -29,7 +29,7 @@ @router.get("/", response_model=schemas.ProjectPaginationOut) def list_projects( db: Session = Depends(deps.get_db), - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), name: str = Query(None), object_type: int = Query(None), pagination: schemas.CommonPaginationParams = Depends(), @@ -59,7 +59,7 @@ def list_projects( def create_sample_project( *, db: Session = Depends(deps.get_db), - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), user_labels: UserLabels = Depends(deps.get_user_labels), controller_client: ControllerClient = Depends(deps.get_controller_client), background_tasks: BackgroundTasks, @@ -120,7 +120,7 @@ def create_sample_project( def create_project( *, db: Session = Depends(deps.get_db), - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), project_in: schemas.ProjectCreate, controller_client: ControllerClient = Depends(deps.get_controller_client), user_labels: UserLabels = Depends(deps.get_user_labels), @@ -202,7 +202,7 @@ def get_project( *, db: Session = Depends(deps.get_db), project_id: int = Path(...), - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), ) -> Any: """ Get a project detail @@ -222,7 +222,7 @@ def update_project( db: Session = Depends(deps.get_db), project_id: int = Path(...), project_update: schemas.ProjectUpdate, - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), ) -> Any: """ Setting up a project @@ -252,7 +252,7 @@ def delete_project( db: Session = Depends(deps.get_db), project_id: int = Path(...), controller_client: ControllerClient = Depends(deps.get_controller_client), - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), ) -> Any: """ Delete project, and terminate all tasks @@ -286,7 +286,7 @@ def delete_project( def check_project_status( *, project_id: int = Path(...), - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), controller_client: ControllerClient = Depends(deps.get_controller_client), ) -> Any: """ diff --git a/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/stats.py b/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/stats.py index d33170d69e..0ca553df99 100644 --- a/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/stats.py +++ b/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/stats.py @@ -26,7 +26,7 @@ def recommend_keywords( dataset_ids: str = Query(None, description="recommend keywords based on given datasets"), limit: int = Query(10, description="limit the data point size"), db: Session = Depends(deps.get_db), - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), viz_client: VizClient = Depends(deps.get_viz_client), user_labels: UserLabels = Depends(deps.get_user_labels), ) -> Any: @@ -59,7 +59,7 @@ def recommend_keywords( def get_projects_count( precision: StatsPrecision = Query(..., description="day, week or month"), limit: int = Query(10, description="limit the data point size"), - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), viz_client: VizClient = Depends(deps.get_viz_client), ) -> Any: """ diff --git a/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/tasks.py b/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/tasks.py index 74f8eb1327..52a673fc8d 100644 --- a/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/tasks.py +++ b/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/tasks.py @@ -9,7 +9,7 @@ from requests.exceptions import ConnectionError, HTTPError, Timeout from sqlalchemy.orm import Session -from app import crud, models, schemas +from app import crud, schemas from app.api import deps from app.api.errors.errors import ( DuplicateTaskError, @@ -38,7 +38,7 @@ def batch_create_tasks( *, db: Session = Depends(deps.get_db), - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), user_labels: UserLabels = Depends(deps.get_user_labels), batch_tasks_in: schemas.BatchTasksCreate, ) -> Any: @@ -59,7 +59,7 @@ def batch_create_tasks( @router.get("/", response_model=schemas.TaskPaginationOut) def list_tasks( db: Session = Depends(deps.get_db), - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), name: str = Query(None, description="search by task name"), type_: TaskType = Query(None, alias="type"), state: TaskState = Query(None), @@ -89,7 +89,7 @@ def create_task( *, db: Session = Depends(deps.get_db), task_in: schemas.TaskCreate, - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), user_labels: UserLabels = Depends(deps.get_user_labels), ) -> Any: """ @@ -117,7 +117,7 @@ def delete_task( *, db: Session = Depends(deps.get_db), task_id: int = Path(..., example="12"), - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), ) -> Any: """ Delete task @@ -141,7 +141,7 @@ def delete_task( def get_task( db: Session = Depends(deps.get_db), task_id: int = Path(..., example="12"), - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), controller_client: ControllerClient = Depends(deps.get_controller_client), ) -> Any: """ @@ -163,7 +163,7 @@ def update_task_name( db: Session = Depends(deps.get_db), task_id: int = Path(..., example="12"), task_in: schemas.TaskUpdate, - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), ) -> Any: """ Update task name @@ -183,7 +183,7 @@ def terminate_task( db: Session = Depends(deps.get_db), task_id: int = Path(...), terminate_info: schemas.TaskTerminate, - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), controller_client: ControllerClient = Depends(deps.get_controller_client), ) -> Any: """ @@ -307,7 +307,7 @@ async def save_task_update_to_redis_stream(*, task_events: schemas.TaskMonitorEv def get_openpai_task( db: Session = Depends(deps.get_db), task_id: int = Path(..., example=12), - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), controller_client: ControllerClient = Depends(deps.get_controller_client), ) -> Any: """ diff --git a/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/upload.py b/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/upload.py index 88d9ded914..87f031c076 100644 --- a/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/upload.py +++ b/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/upload.py @@ -2,7 +2,7 @@ from fastapi import APIRouter, Depends, File, UploadFile -from app import models, schemas +from app import schemas from app.api import deps from app.utils.files import host_file @@ -16,7 +16,7 @@ def upload( *, file: UploadFile = File(...), - current_user: models.User = Depends(deps.get_current_active_user), + current_user: schemas.user.UserInfo = Depends(deps.get_current_active_user), ) -> Any: """ Upload a file, return an url that has access to it diff --git a/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/users.py b/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/users.py index 96d8e4b57b..daabc0dc07 100644 --- a/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/users.py +++ b/ymir/backend/src/ymir_app/app/api/api_v1/endpoints/users.py @@ -1,180 +1,17 @@ -from typing import Any, Optional +from typing import Any -from fastapi import APIRouter, Body, Depends, Query, Security -from fastapi.encoders import jsonable_encoder -from pydantic.networks import EmailStr -from sqlalchemy.orm import Session +from fastapi import APIRouter, Depends -from app import crud, models, schemas +from app import schemas from app.api import deps from app.api.errors.errors import ( - DuplicateUserNameError, - DuplicatePhoneError, - UserNotFound, FailedToCreateUser, ) -from app.constants.role import Roles from app.utils.ymir_controller import ControllerClient, gen_user_hash router = APIRouter() -@router.get( - "/", - response_model=schemas.UsersOut, -) -def list_users( - db: Session = Depends(deps.get_db), - offset: int = Query(None), - limit: int = Query(None), - state: Optional[schemas.UserState] = Query(None), - current_user: models.User = Security( - deps.get_current_active_super_admin, - ), -) -> Any: - """ - Get list of users, - pagination is supported by means of offset and limit - - UserState: - - registered = 1 - - active = 2 - - declined = 3 - - deactivated = 4 - """ - users, total = crud.user.get_multi_with_filter(db, offset=offset, limit=limit, state=state) - return {"result": {"total": total, "items": users}} - - -@router.post( - "/", - response_model=schemas.UserOut, - responses={400: {"description": "Username Already Exists"}}, -) -def create_user( - *, - db: Session = Depends(deps.get_db), - controller_client: ControllerClient = Depends(deps.get_controller_client), - password: str = Body(...), - email: EmailStr = Body(...), - phone: str = Body(None), - username: str = Body(None), -) -> Any: - """ - Register user - """ - if crud.user.get_by_email(db, email=email): - raise DuplicateUserNameError() - if phone and crud.user.get_by_phone(db, phone=phone): - raise DuplicatePhoneError() - - user_in = schemas.UserCreate(password=password, email=email, phone=phone, username=username) - user = crud.user.create(db, obj_in=user_in) - - try: - controller_client.create_user(user_id=user.id) - except ValueError: - raise FailedToCreateUser() - - return {"result": user} - - -@router.get("/me", response_model=schemas.UserOut) -def get_current_user( - current_user: models.User = Depends(deps.get_current_active_user), -) -> Any: - """ - Get verbose information about current user - """ - return {"result": current_user} - - -@router.patch( - "/me", - response_model=schemas.UserOut, - responses={404: {"description": "User Not Found"}}, -) -def update_myself( - db: Session = Depends(deps.get_db), - password: str = Body(None), - username: str = Body(None), - phone: str = Body(None), - avatar: str = Body(None), - current_user: models.User = Depends(deps.get_current_active_user), -) -> Any: - """ - Update current user's information - """ - current_user_info = jsonable_encoder(current_user) - user_in = schemas.UserUpdate(**current_user_info) - if password: - user_in.password = password - if username: - user_in.username = username - if phone is not None: - if crud.user.get_by_phone(db, phone=phone): - raise DuplicatePhoneError() - user_in.phone = phone - if avatar is not None: - user_in.avatar = avatar - user = crud.user.update(db, db_obj=current_user, obj_in=user_in) - return {"result": user} - - -@router.get( - "/{user_id}", - response_model=schemas.UserOut, - responses={404: {"description": "User Not Found"}}, -) -def get_user( - user_id: int, - db: Session = Depends(deps.get_db), - current_user: models.User = Security( - deps.get_current_active_user, - scopes=[Roles.ADMIN.name, Roles.SUPER_ADMIN.name], - ), -) -> Any: - """ - Query user information, - Admin permission (Admin and Super admin) is required - """ - user = crud.user.get(db, id=user_id) - if not user: - raise UserNotFound() - - return {"result": user} - - -@router.patch( - "/{user_id}", - response_model=schemas.UserOut, - responses={404: {"description": "User Not Found"}}, -) -def update_user_state( - user_id: int, - state: Optional[schemas.UserState] = Body(None), - role: Optional[schemas.UserRole] = Body(None), - is_deleted: Optional[bool] = Body(None), - db: Session = Depends(deps.get_db), - current_user: models.User = Security( - deps.get_current_active_super_admin, - ), -) -> Any: - """ - Change user state (activate, decline, deactivate, etc), - Super Admin permission is required - """ - user = crud.user.get(db, id=user_id) - if not user: - raise UserNotFound() - - if state is not None: - user = crud.user.update_state(db, user=user, state=state) - if role is not None: - user = crud.user.update_role(db, user=user, role=role) - return {"result": user} - - @router.post( "/controller", response_model=schemas.user.ControllerUserOut, diff --git a/ymir/backend/src/ymir_app/app/api/deps.py b/ymir/backend/src/ymir_app/app/api/deps.py index d37ebb9c0f..a296760dec 100644 --- a/ymir/backend/src/ymir_app/app/api/deps.py +++ b/ymir/backend/src/ymir_app/app/api/deps.py @@ -1,34 +1,24 @@ from typing import Generator -from fastapi import Depends, Security +from fastapi import Depends, Security, Header from fastapi.logger import logger -from fastapi.security import APIKeyHeader, OAuth2PasswordBearer, SecurityScopes -from jose import jwt -from pydantic import ValidationError -from sqlalchemy.orm import Session +from fastapi.security import APIKeyHeader -from app import crud, models, schemas +from app import schemas from app.api.errors.errors import ( - InactiveUser, InvalidScope, InvalidToken, UserNotFound, - SystemVersionConflict, ) from app.config import settings from app.constants.role import Roles from app.db.session import SessionLocal from app.utils import cache as ymir_cache -from app.utils import security, ymir_controller, ymir_viz +from app.utils import ymir_controller, ymir_viz from app.utils.security import verify_api_key from app.utils.ymir_controller import ControllerClient from common_utils.labels import UserLabels -from common_utils.version import YMIR_VERSION -reusable_oauth2 = OAuth2PasswordBearer( - tokenUrl=f"{settings.API_V1_STR}/auth/token", - scopes={role.name: role.description for role in [Roles.NORMAL, Roles.ADMIN, Roles.SUPER_ADMIN]}, -) API_KEY_NAME = "api-key" # use dash to compatible with Nginx api_key_header = APIKeyHeader(name=API_KEY_NAME, scheme_name="API key header", auto_error=False) @@ -44,7 +34,6 @@ def api_key_security(header_param: str = Security(api_key_header)) -> str: raise InvalidToken() -# OAuth2 approach def get_db() -> Generator: try: db = SessionLocal() @@ -53,57 +42,36 @@ def get_db() -> Generator: db.close() -def get_current_user( - security_scopes: SecurityScopes, - db: Session = Depends(get_db), - token: str = Depends(reusable_oauth2), -) -> models.User: - try: - payload = jwt.decode(token, settings.APP_SECRET_KEY, algorithms=[security.ALGORITHM]) - token_data = schemas.TokenPayload(**payload) - except (jwt.JWTError, ValidationError): - logger.exception("Invalid JWT token") - raise InvalidToken() - - if token_data.version != YMIR_VERSION: - raise SystemVersionConflict() - - user = crud.user.get(db, id=token_data.id) - if not user: +def get_user_info_from_x_headers( + x_user_id: str = Header(default=None), + x_user_role: str = Header(default=None), +) -> schemas.user.UserInfo: + if not x_user_id: raise UserNotFound() - - current_role = min( - getattr(schemas.UserRole, token_data.role), - schemas.UserRole(user.role), - ) - - if security_scopes.scopes and current_role.name not in security_scopes.scopes: - logger.error( - "Invalid JWT token scope: %s not in %s", - current_role.name, - security_scopes.scopes, - ) - raise InvalidScope() - return user + return schemas.user.UserInfo(id=x_user_id, role=x_user_role) def get_current_active_user( - current_user: models.User = Security(get_current_user, scopes=[]), -) -> models.User: - if not crud.user.is_active(current_user): - raise InactiveUser() + current_user: schemas.user.UserInfo = Depends(get_user_info_from_x_headers), +) -> schemas.user.UserInfo: + if current_user.role < Roles.NORMAL.rank: + raise InvalidScope() return current_user def get_current_active_admin( - current_user: models.User = Security(get_current_user, scopes=[Roles.ADMIN.name, Roles.SUPER_ADMIN.name]), -) -> models.User: + current_user: schemas.user.UserInfo = Depends(get_user_info_from_x_headers), +) -> schemas.user.UserInfo: + if current_user.role < Roles.ADMIN.rank: + raise InvalidScope() return current_user def get_current_active_super_admin( - current_user: models.User = Security(get_current_user, scopes=[Roles.SUPER_ADMIN.name]), -) -> models.User: + current_user: schemas.user.UserInfo = Depends(get_user_info_from_x_headers), +) -> schemas.user.UserInfo: + if current_user.role < Roles.SUPER_ADMIN.rank: + raise InvalidScope() return current_user @@ -124,7 +92,7 @@ def get_viz_client() -> Generator: def get_cache( - current_user: models.User = Depends(get_current_active_user), + current_user: schemas.user.UserInfo = Depends(get_current_active_user), ) -> Generator: try: cache_client = ymir_cache.CacheClient(redis_uri=settings.BACKEND_REDIS_URL, user_id=current_user.id) @@ -134,7 +102,7 @@ def get_cache( def get_user_labels( - current_user: models.User = Depends(get_current_active_user), + current_user: schemas.user.UserInfo = Depends(get_current_active_user), cache: ymir_cache.CacheClient = Depends(get_cache), controller_client: ControllerClient = Depends(get_controller_client), ) -> UserLabels: diff --git a/ymir/backend/src/ymir_app/app/schemas/user.py b/ymir/backend/src/ymir_app/app/schemas/user.py index 3faddc2302..9077c5e306 100644 --- a/ymir/backend/src/ymir_app/app/schemas/user.py +++ b/ymir/backend/src/ymir_app/app/schemas/user.py @@ -96,3 +96,14 @@ class ControllerUser(BaseModel): class ControllerUserOut(Common): result: ControllerUser + + +class UserInfo(BaseModel): + id: int + role: UserRole + + @validator("role", pre=True) + def default_role(cls, v: Optional[UserRole]) -> UserRole: + if v is None: + return UserRole.NORMAL + return v diff --git a/ymir/backend/src/ymir_app/tests/api/test_login.py b/ymir/backend/src/ymir_app/tests/api/test_login.py deleted file mode 100644 index 8dc3ea1e25..0000000000 --- a/ymir/backend/src/ymir_app/tests/api/test_login.py +++ /dev/null @@ -1,49 +0,0 @@ -from fastapi.testclient import TestClient - -from app.api.api_v1.endpoints import login as m -from app.api.errors.errors import InvalidToken -from app.config import settings -from app.utils.security import frontend_hash -from tests.utils.utils import random_email, random_lower_string - - -def test_get_access_token(client: TestClient) -> None: - login_data = { - "username": settings.FIRST_ADMIN, - "password": frontend_hash(settings.FIRST_ADMIN_PASSWORD), - } - r = client.post(f"{settings.API_V1_STR}/auth/token", data=login_data) - tokens = r.json() - assert r.status_code == 200 - assert "access_token" in tokens - assert tokens["access_token"] - - -class TestRecoverPassword: - def test_recover_password_user_not_found(self, client: TestClient): - email = random_email() - r = client.post(f"{settings.API_V1_STR}/password-recovery/{email}") - assert r.status_code == 404 - - def test_recover_password(self, client: TestClient, mocker): - email = random_email() - mock_crud = mocker.Mock() - mocker.patch.object(m, "crud", return_value=mock_crud) - - mock_user = mocker.Mock(email=email) - mock_crud.user.get_by_email.return_value = mock_user - mocker.patch.object(m, "send_reset_password_email") - - r = client.post(f"{settings.API_V1_STR}/password-recovery/{email}") - assert r.status_code == 200 - - -class TestResetPassword: - def test_reset_password_not_found(self, client: TestClient): - j = { - "token": random_lower_string(), - "new_password": random_lower_string(), - } - r = client.post(f"{settings.API_V1_STR}/reset-password/", json=j) - assert r.status_code == 401 - assert r.json()["code"] == InvalidToken.code diff --git a/ymir/backend/src/ymir_app/tests/conftest.py b/ymir/backend/src/ymir_app/tests/conftest.py index cab54f732e..70dd1abf4a 100644 --- a/ymir/backend/src/ymir_app/tests/conftest.py +++ b/ymir/backend/src/ymir_app/tests/conftest.py @@ -10,8 +10,8 @@ from app.config import settings from app.db.session import SessionLocal from app.main import app -from tests.utils.user import authentication_token_from_email, create_admin_user -from tests.utils.utils import get_admin_token_headers, get_super_admin_token_headers +from tests.utils.user import create_admin_user +from tests.utils.utils import get_normal_token_headers, get_admin_token_headers, get_super_admin_token_headers @pytest.fixture(scope="session") @@ -157,22 +157,20 @@ def api_key_headers(client: TestClient, db: Session) -> Dict[str, str]: @pytest.fixture(scope="module") -def normal_user_token_headers(client: TestClient, db: Session) -> Dict[str, str]: - return authentication_token_from_email(client=client, email=settings.EMAIL_TEST_USER, db=db) +def normal_user_token_headers() -> Dict[str, str]: + return get_normal_token_headers() @pytest.fixture(scope="module") -def admin_token_headers(client: TestClient) -> Dict[str, str]: - return get_admin_token_headers(client) +def admin_token_headers() -> Dict[str, str]: + return get_admin_token_headers() @pytest.fixture(scope="module") -def super_admin_token_headers(client: TestClient) -> Dict[str, str]: - return get_super_admin_token_headers(client) +def super_admin_token_headers() -> Dict[str, str]: + return get_super_admin_token_headers() @pytest.fixture() -def user_id(mocker, client: TestClient, normal_user_token_headers: Dict[str, str]): - r = client.get(f"{settings.API_V1_STR}/users/me", headers=normal_user_token_headers) - current_user = r.json()["result"] - return current_user["id"] +def user_id(normal_user_token_headers: Dict[str, str]): + return int(normal_user_token_headers["X-User-Id"]) diff --git a/ymir/backend/src/ymir_app/tests/crud/test_user.py b/ymir/backend/src/ymir_app/tests/crud/test_user.py deleted file mode 100644 index 4bab7f1db1..0000000000 --- a/ymir/backend/src/ymir_app/tests/crud/test_user.py +++ /dev/null @@ -1,52 +0,0 @@ -from fastapi.encoders import jsonable_encoder -from sqlalchemy.orm import Session - -from app import crud -from app.schemas.user import UserCreate -from tests.utils.utils import random_email, random_lower_string - - -def test_create_user(db: Session) -> None: - email = random_email() - password = random_lower_string() - user_in = UserCreate(email=email, password=password) - user = crud.user.create(db, obj_in=user_in) - assert user.email == email - assert hasattr(user, "hashed_password") - - -def test_authenticate_user(db: Session) -> None: - email = random_email() - password = random_lower_string() - user_in = UserCreate(email=email, password=password) - user = crud.user.create(db, obj_in=user_in) - authenticated_user = crud.user.authenticate(db, email=email, password=password) - assert authenticated_user - assert user.email == authenticated_user.email - - -def test_not_authenticate_user(db: Session) -> None: - email = random_email() - password = random_lower_string() - user = crud.user.authenticate(db, email=email, password=password) - assert user is None - - -def test_check_if_user_is_deleted_not_deleted(db: Session) -> None: - email = random_email() - password = random_lower_string() - user_in = UserCreate(email=email, password=password) - user = crud.user.create(db, obj_in=user_in) - is_deleted = crud.user.is_deleted(user) - assert is_deleted is False - - -def test_get_user(db: Session) -> None: - password = random_lower_string() - username = random_email() - user_in = UserCreate(email=username, password=password) - user = crud.user.create(db, obj_in=user_in) - user_2 = crud.user.get(db, id=user.id) - assert user_2 - assert user.email == user_2.email - assert jsonable_encoder(user) == jsonable_encoder(user_2) diff --git a/ymir/backend/src/ymir_app/tests/utils/utils.py b/ymir/backend/src/ymir_app/tests/utils/utils.py index a24ee44b5f..98baa16b00 100644 --- a/ymir/backend/src/ymir_app/tests/utils/utils.py +++ b/ymir/backend/src/ymir_app/tests/utils/utils.py @@ -2,11 +2,6 @@ import string from typing import Dict -from fastapi.testclient import TestClient - -from app.config import settings -from app.utils.security import frontend_hash - def random_lower_string(k: int = 32) -> str: return "".join(random.choices(string.ascii_lowercase, k=k)) @@ -25,27 +20,16 @@ def random_url() -> str: return f"https://www.{random_lower_string()}.com/{random_lower_string()}" -def get_admin_token_headers(client: TestClient) -> Dict[str, str]: - login_data = { - "username": settings.FIRST_ADMIN, - "password": frontend_hash(settings.FIRST_ADMIN_PASSWORD), - "scope": "ADMIN", - } - r = client.post(f"{settings.API_V1_STR}/auth/token", data=login_data) - tokens = r.json()["result"] - a_token = tokens["access_token"] - headers = {"Authorization": f"Bearer {a_token}"} +def get_normal_token_headers() -> Dict[str, str]: + headers = {"X-User-Id": "233", "X-User-Role": "1"} + return headers + + +def get_admin_token_headers() -> Dict[str, str]: + headers = {"X-User-Id": "233", "X-User-Role": "2"} return headers -def get_super_admin_token_headers(client: TestClient) -> Dict[str, str]: - login_data = { - "username": settings.FIRST_ADMIN, - "password": frontend_hash(settings.FIRST_ADMIN_PASSWORD), - "scope": "NORMAL ADMIN SUPER_ADMIN", - } - r = client.post(f"{settings.API_V1_STR}/auth/token", data=login_data) - tokens = r.json()["result"] - a_token = tokens["access_token"] - headers = {"Authorization": f"Bearer {a_token}"} +def get_super_admin_token_headers() -> Dict[str, str]: + headers = {"X-User-Id": "233", "X-User-Role": "3"} return headers diff --git a/ymir/backend/src/ymir_auth/alembic/versions/4a002517f2a9_add_user_and_role_table.py b/ymir/backend/src/ymir_auth/alembic/versions/4a002517f2a9_add_user_and_role_table.py index 1a5b41d66f..6794c78600 100644 --- a/ymir/backend/src/ymir_auth/alembic/versions/4a002517f2a9_add_user_and_role_table.py +++ b/ymir/backend/src/ymir_auth/alembic/versions/4a002517f2a9_add_user_and_role_table.py @@ -5,12 +5,9 @@ Create Date: 2023-03-06 16:53:22.469390 """ -import os from alembic import op import sqlalchemy as sa -from sqlalchemy import engine_from_config -from sqlalchemy.engine import reflection # revision identifiers, used by Alembic. @@ -20,74 +17,76 @@ depends_on = None -def table_exist(table, schema=None): - config = op.get_context().config - config = config.get_section(config.config_ini_section) - config["sqlalchemy.url"] = os.getenv("DATABASE_URI", "sqlite:///auth.db") - engine = engine_from_config(config, prefix="sqlalchemy.") - insp = reflection.Inspector.from_engine(engine) - return insp.has_table(table, schema) - - def upgrade(): # ### commands auto generated by Alembic - please adjust! ### - if not table_exist("role"): - op.create_table( - "role", - sa.Column("id", sa.Integer(), autoincrement=True, nullable=False), - sa.Column("name", sa.String(length=100), nullable=True), - sa.Column("description", sa.String(length=100), nullable=True), - sa.PrimaryKeyConstraint("id"), - ) - with op.batch_alter_table("role", schema=None) as batch_op: - batch_op.create_index(batch_op.f("ix_role_id"), ["id"], unique=False) - batch_op.create_index(batch_op.f("ix_role_name"), ["name"], unique=False) + role_table = op.create_table( + "api_role", + sa.Column("id", sa.Integer(), autoincrement=True, nullable=False), + sa.Column("name", sa.String(length=100), nullable=True), + sa.Column("description", sa.String(length=100), nullable=True), + sa.PrimaryKeyConstraint("id"), + ) + with op.batch_alter_table("api_role", schema=None) as batch_op: + batch_op.create_index(batch_op.f("ix_role_id"), ["id"], unique=False) + batch_op.create_index(batch_op.f("ix_role_name"), ["name"], unique=False) + + user_table = op.create_table( + "api_user", + sa.Column("id", sa.Integer(), autoincrement=True, nullable=False), + sa.Column("email", sa.String(length=100), nullable=False), + sa.Column("username", sa.String(length=100), nullable=True), + sa.Column("phone", sa.String(length=20), nullable=True), + sa.Column("avatar", sa.String(length=100), nullable=True), + sa.Column("hashed_password", sa.String(length=200), nullable=False), + sa.Column("state", sa.Integer(), nullable=True), + sa.Column("role", sa.Integer(), nullable=True), + sa.Column("organization", sa.String(length=100), nullable=True), + sa.Column("scene", sa.String(length=500), nullable=True), + sa.Column("uuid", sa.String(length=36), nullable=False), + sa.Column("is_deleted", sa.Boolean(), nullable=True), + sa.Column("last_login_datetime", sa.DateTime(), nullable=True), + sa.Column("create_datetime", sa.DateTime(), nullable=False), + sa.Column("update_datetime", sa.DateTime(), nullable=False), + sa.PrimaryKeyConstraint("id"), + ) + with op.batch_alter_table("api_user", schema=None) as batch_op: + batch_op.create_index(batch_op.f("ix_user_email"), ["email"], unique=True) + batch_op.create_index(batch_op.f("ix_user_id"), ["id"], unique=False) + batch_op.create_index(batch_op.f("ix_user_phone"), ["phone"], unique=True) + batch_op.create_index(batch_op.f("ix_user_role"), ["role"], unique=False) + batch_op.create_index(batch_op.f("ix_user_state"), ["state"], unique=False) + batch_op.create_index(batch_op.f("ix_user_username"), ["username"], unique=False) - if not table_exist("user"): - op.create_table( - "user", - sa.Column("id", sa.Integer(), autoincrement=True, nullable=False), - sa.Column("email", sa.String(length=100), nullable=False), - sa.Column("username", sa.String(length=100), nullable=True), - sa.Column("phone", sa.String(length=20), nullable=True), - sa.Column("avatar", sa.String(length=100), nullable=True), - sa.Column("hashed_password", sa.String(length=200), nullable=False), - sa.Column("state", sa.Integer(), nullable=True), - sa.Column("role", sa.Integer(), nullable=True), - sa.Column("organization", sa.String(length=100), nullable=True), - sa.Column("scene", sa.String(length=500), nullable=True), - sa.Column("uuid", sa.String(length=36), nullable=False), - sa.Column("is_deleted", sa.Boolean(), nullable=True), - sa.Column("last_login_datetime", sa.DateTime(), nullable=True), - sa.Column("create_datetime", sa.DateTime(), nullable=False), - sa.Column("update_datetime", sa.DateTime(), nullable=False), - sa.PrimaryKeyConstraint("id"), - ) - with op.batch_alter_table("user", schema=None) as batch_op: - batch_op.create_index(batch_op.f("ix_user_email"), ["email"], unique=True) - batch_op.create_index(batch_op.f("ix_user_id"), ["id"], unique=False) - batch_op.create_index(batch_op.f("ix_user_phone"), ["phone"], unique=True) - batch_op.create_index(batch_op.f("ix_user_role"), ["role"], unique=False) - batch_op.create_index(batch_op.f("ix_user_state"), ["state"], unique=False) - batch_op.create_index(batch_op.f("ix_user_username"), ["username"], unique=False) + conn = op.get_bind() + try: + roles = conn.execute("SELECT * FROM role").fetchall() + roles = [dict(i) for i in roles] + if roles: + op.bulk_insert(role_table, roles) + users = conn.execute("SELECT * FROM user").fetchall() + users = [dict(i) for i in users] + if users: + op.bulk_insert(user_table, users) + except Exception as e: + print("Could not migrate app users and roles to api_users and api_roles: %s" % e) # ### end Alembic commands ### def downgrade(): # ### commands auto generated by Alembic - please adjust! ### - with op.batch_alter_table("user", schema=None) as batch_op: + with op.batch_alter_table("api_user", schema=None) as batch_op: batch_op.drop_index(batch_op.f("ix_user_username")) batch_op.drop_index(batch_op.f("ix_user_state")) batch_op.drop_index(batch_op.f("ix_user_role")) batch_op.drop_index(batch_op.f("ix_user_phone")) batch_op.drop_index(batch_op.f("ix_user_id")) batch_op.drop_index(batch_op.f("ix_user_email")) + op.drop_table("api_user") - op.drop_table("user") - with op.batch_alter_table("role", schema=None) as batch_op: + with op.batch_alter_table("api_role", schema=None) as batch_op: batch_op.drop_index(batch_op.f("ix_role_name")) batch_op.drop_index(batch_op.f("ix_role_id")) - op.drop_table("role") + op.drop_table("api_role") # ### end Alembic commands ### diff --git a/ymir/backend/src/ymir_auth/auth/api/api_v1/endpoints/login.py b/ymir/backend/src/ymir_auth/auth/api/api_v1/endpoints/login.py index d59f8ea169..dacfdb3741 100644 --- a/ymir/backend/src/ymir_auth/auth/api/api_v1/endpoints/login.py +++ b/ymir/backend/src/ymir_auth/auth/api/api_v1/endpoints/login.py @@ -1,7 +1,7 @@ from datetime import timedelta from typing import Any -from fastapi import APIRouter, Body, Depends +from fastapi import APIRouter, Body, Depends, Response from fastapi.logger import logger from fastapi.security import OAuth2PasswordRequestForm from sqlalchemy.orm import Session @@ -78,13 +78,17 @@ def login_access_token( return {"result": payload, **payload} +@router.get("/auth/validate", response_model=schemas.Msg) @router.post("/auth/validate", response_model=schemas.Msg) def validate_token( + response: Response, current_user: models.User = Depends(deps.get_current_active_user), ) -> Any: """ Validate JWT """ + response.headers["X-User-Id"] = str(current_user.id) + response.headers["X-User-Role"] = str(current_user.role) return {"message": "ok"} diff --git a/ymir/backend/src/ymir_auth/auth/config.py b/ymir/backend/src/ymir_auth/auth/config.py index b08507cc34..03ad3b031f 100644 --- a/ymir/backend/src/ymir_auth/auth/config.py +++ b/ymir/backend/src/ymir_auth/auth/config.py @@ -23,7 +23,7 @@ class Settings(BaseSettings): FIRST_ADMIN: EmailStr = "admin@example.com" # type: ignore FIRST_ADMIN_PASSWORD: str = "change_this" - USE_200_EVERYWHERE: bool = True + USE_200_EVERYWHERE: bool = False APP_API_TIMEOUT: int = 30 APP_API_HOST: str = "backend:80" diff --git a/ymir/backend/src/ymir_auth/auth/models/role.py b/ymir/backend/src/ymir_auth/auth/models/role.py index 506ea0ce75..87a41777c6 100644 --- a/ymir/backend/src/ymir_auth/auth/models/role.py +++ b/ymir/backend/src/ymir_auth/auth/models/role.py @@ -5,7 +5,7 @@ class Role(Base): - __tablename__ = "role" + __tablename__ = "api_role" id = Column(Integer, primary_key=True, index=True, autoincrement=True) name = Column(String(settings.STRING_LEN_LIMIT), index=True) description = Column(String(settings.STRING_LEN_LIMIT)) diff --git a/ymir/backend/src/ymir_auth/auth/models/user.py b/ymir/backend/src/ymir_auth/auth/models/user.py index 0a33a73368..3fc5c88870 100644 --- a/ymir/backend/src/ymir_auth/auth/models/user.py +++ b/ymir/backend/src/ymir_auth/auth/models/user.py @@ -12,7 +12,7 @@ def generate_uuid() -> str: class User(Base): - __tablename__ = "user" + __tablename__ = "api_user" id = Column(Integer, primary_key=True, index=True, autoincrement=True) email = Column(String(100), unique=True, index=True, nullable=False) username = Column(String(100), index=True) diff --git a/ymir/gateway/dynamic-conf.yml b/ymir/gateway/dynamic-conf.yml index ed28b6e16f..739c12ce27 100644 --- a/ymir/gateway/dynamic-conf.yml +++ b/ymir/gateway/dynamic-conf.yml @@ -8,11 +8,20 @@ http: service: auth rule: PathPrefix(`/api/v1/auth`) || PathPrefix(`/api/v1/user`) || PathPrefix(`/api/v1/reset-password`) || PathPrefix(`/api/v1/password-recovery`) + doc_router: + entryPoints: + - web + middlewares: + - APIRateLimiter + service: backend + rule: PathPrefix(`/api/v1/openapi.json`) + backend_router: entryPoints: - web middlewares: - APIRateLimiter + - Authenticator service: backend rule: PathPrefix(`/api/v1/`) @@ -48,6 +57,7 @@ http: loadBalancer: servers: - url: http://backend:80/ + frontend: loadBalancer: servers: @@ -61,6 +71,13 @@ http: middlewares: + Authenticator: + forwardAuth: + address: http://backend:8088/api/v1/auth/validate + authResponseHeaders: + - "X-User-Id" + - "X-User-Role" + APIRateLimiter: rateLimit: average: 3 From 36c7141b016fdee3534fa1127165830fad2f3d35 Mon Sep 17 00:00:00 2001 From: IJtLJZ8Rm4Yr <90443055+IJtLJZ8Rm4Yr@users.noreply.github.com> Date: Thu, 23 Mar 2023 14:29:08 +0800 Subject: [PATCH 4/6] [auth] add name in JWT token (#1677) Co-authored-by: Fenrir <90444968+fenrir-z@users.noreply.github.com> --- ymir/backend/src/ymir_auth/auth/api/api_v1/endpoints/login.py | 1 + ymir/backend/src/ymir_auth/auth/schemas/token.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ymir/backend/src/ymir_auth/auth/api/api_v1/endpoints/login.py b/ymir/backend/src/ymir_auth/auth/api/api_v1/endpoints/login.py index dacfdb3741..172c38c03c 100644 --- a/ymir/backend/src/ymir_auth/auth/api/api_v1/endpoints/login.py +++ b/ymir/backend/src/ymir_auth/auth/api/api_v1/endpoints/login.py @@ -63,6 +63,7 @@ def login_access_token( access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) token_payload = { "id": user.id, + "name": user.username, "email": user.email, "role": role.name, } diff --git a/ymir/backend/src/ymir_auth/auth/schemas/token.py b/ymir/backend/src/ymir_auth/auth/schemas/token.py index b3f17b69c5..32d473d864 100644 --- a/ymir/backend/src/ymir_auth/auth/schemas/token.py +++ b/ymir/backend/src/ymir_auth/auth/schemas/token.py @@ -19,5 +19,6 @@ class TokenOut(Common): class TokenPayload(BaseModel): id: int role: str - email: str + name: Optional[str] + email: Optional[str] version: Optional[str] From 90f7752d42bc29a0de29203d3c178a5888c4e494 Mon Sep 17 00:00:00 2001 From: Phoenix <89957974+phoenix-xhuang@users.noreply.github.com> Date: Thu, 23 Mar 2023 15:18:02 +0800 Subject: [PATCH 5/6] meta update (#1) --- .github/CODEOWNERS | 4 ++- .github/ISSUE_TEMPLATE/bug_report.md | 32 --------------------- .github/ISSUE_TEMPLATE/issue-report.md | 10 +++---- .github/workflows/backend-pr.yml | 2 +- .github/workflows/command-pr.yml | 2 +- .github/workflows/command-publish.yml | 39 -------------------------- .github/workflows/exc-sdk-pr.yml | 2 +- .github/workflows/web-pr.yml | 2 +- 8 files changed, 11 insertions(+), 82 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md delete mode 100644 .github/workflows/command-publish.yml diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 7a814a99b2..bc88ebe3c5 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,3 +1,5 @@ -/ymir/command/ @IndustryEssentials/ymir-cmd +/ymir/command/ @IndustryEssentials/ymir-backend /ymir/backend/ @IndustryEssentials/ymir-backend +/ymir/gateway/ @IndustryEssentials/ymir-backend +/ymir/updater/ @IndustryEssentials/ymir-backend /ymir/web/ @IndustryEssentials/ymir-web diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index a4d909de2f..0000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: '' -labels: '' -assignees: '' - ---- - -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behavior: -1. -2. -3. -4. - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Screenshots** -If applicable, add screenshots to help explain your problem. - -**Environment (please complete the following information):** - - Server OS: [e.g. Ubuntu] - - Ymir Version [e.g. 0.5.0] - - Local Browser [e.g. chrome] - -**Additional context** -Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/issue-report.md b/.github/ISSUE_TEMPLATE/issue-report.md index cc90bb8fda..9f28fb6cd8 100644 --- a/.github/ISSUE_TEMPLATE/issue-report.md +++ b/.github/ISSUE_TEMPLATE/issue-report.md @@ -3,7 +3,7 @@ name: Issue report about: Create a report to help us improve title: '' labels: '' -assignees: Zhang-SJ930104 +assignees: DianeWu --- @@ -28,11 +28,9 @@ If applicable, add screenshots to help explain your problem. - Browser [e.g. chrome, safari] - Version [e.g. 22] -**Smartphone (please complete the following information):** - - Device: [e.g. iPhone6] - - OS: [e.g. iOS8.1] - - Browser [e.g. stock browser, safari] - - Version [e.g. 22] +**Logs: under ymir-workplace/ymir-data/logs + - Error Message: in controller/app/hel-log + - Trace stack **Additional context** Add any other context about the problem here. diff --git a/.github/workflows/backend-pr.yml b/.github/workflows/backend-pr.yml index 5228da79c5..974e933279 100644 --- a/.github/workflows/backend-pr.yml +++ b/.github/workflows/backend-pr.yml @@ -16,7 +16,7 @@ env: jobs: backend-ci: - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 - name: Set up Python 3.8 diff --git a/.github/workflows/command-pr.yml b/.github/workflows/command-pr.yml index 344f1f90a1..d24b3b3b4b 100644 --- a/.github/workflows/command-pr.yml +++ b/.github/workflows/command-pr.yml @@ -13,7 +13,7 @@ on: jobs: command-ci: - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/command-publish.yml b/.github/workflows/command-publish.yml deleted file mode 100644 index 6aa772ffdd..0000000000 --- a/.github/workflows/command-publish.yml +++ /dev/null @@ -1,39 +0,0 @@ -# This workflow will upload a Python Package using Twine when a release is created -# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries - -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - -name: Upload Python Package - -on: - push: - tags: - - release-[0-9]+.[0-9]+.[0-9]+ - -jobs: - command-publish: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.x' - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install setuptools wheel twine - working-directory: ymir/command - - name: Build and publish - env: - TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} - TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} - run: | - python setup.py sdist bdist_wheel - twine upload dist/* - working-directory: ymir/command diff --git a/.github/workflows/exc-sdk-pr.yml b/.github/workflows/exc-sdk-pr.yml index 77565748ea..6f75aab1c1 100644 --- a/.github/workflows/exc-sdk-pr.yml +++ b/.github/workflows/exc-sdk-pr.yml @@ -15,7 +15,7 @@ on: jobs: exc-sdk-ci: - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/web-pr.yml b/.github/workflows/web-pr.yml index 29665cda36..d838a473ec 100644 --- a/.github/workflows/web-pr.yml +++ b/.github/workflows/web-pr.yml @@ -14,7 +14,7 @@ on: jobs: web-ci: - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 From a805f9b8c2f976f89734a65dc15fa0c63f7c16bb Mon Sep 17 00:00:00 2001 From: IJtLJZ8Rm4Yr <90443055+IJtLJZ8Rm4Yr@users.noreply.github.com> Date: Thu, 23 Mar 2023 19:33:52 +0800 Subject: [PATCH 6/6] [auth] no need to convert status code to 200 for auth and fix migration issue (#3) * [auth] no need to convert status code to 200 for auth * fix migration conflict --- .../4a002517f2a9_add_user_and_role_table.py | 77 +--------------- ...7792259_add_api_user_and_api_role_table.py | 92 +++++++++++++++++++ .../src/ymir_auth/auth/api/errors/errors.py | 5 +- ymir/backend/src/ymir_auth/auth/config.py | 2 - 4 files changed, 98 insertions(+), 78 deletions(-) create mode 100644 ymir/backend/src/ymir_auth/alembic/versions/6b9037792259_add_api_user_and_api_role_table.py diff --git a/ymir/backend/src/ymir_auth/alembic/versions/4a002517f2a9_add_user_and_role_table.py b/ymir/backend/src/ymir_auth/alembic/versions/4a002517f2a9_add_user_and_role_table.py index 6794c78600..493db81334 100644 --- a/ymir/backend/src/ymir_auth/alembic/versions/4a002517f2a9_add_user_and_role_table.py +++ b/ymir/backend/src/ymir_auth/alembic/versions/4a002517f2a9_add_user_and_role_table.py @@ -6,10 +6,6 @@ """ -from alembic import op -import sqlalchemy as sa - - # revision identifiers, used by Alembic. revision = "4a002517f2a9" down_revision = None @@ -18,75 +14,10 @@ def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - role_table = op.create_table( - "api_role", - sa.Column("id", sa.Integer(), autoincrement=True, nullable=False), - sa.Column("name", sa.String(length=100), nullable=True), - sa.Column("description", sa.String(length=100), nullable=True), - sa.PrimaryKeyConstraint("id"), - ) - with op.batch_alter_table("api_role", schema=None) as batch_op: - batch_op.create_index(batch_op.f("ix_role_id"), ["id"], unique=False) - batch_op.create_index(batch_op.f("ix_role_name"), ["name"], unique=False) - - user_table = op.create_table( - "api_user", - sa.Column("id", sa.Integer(), autoincrement=True, nullable=False), - sa.Column("email", sa.String(length=100), nullable=False), - sa.Column("username", sa.String(length=100), nullable=True), - sa.Column("phone", sa.String(length=20), nullable=True), - sa.Column("avatar", sa.String(length=100), nullable=True), - sa.Column("hashed_password", sa.String(length=200), nullable=False), - sa.Column("state", sa.Integer(), nullable=True), - sa.Column("role", sa.Integer(), nullable=True), - sa.Column("organization", sa.String(length=100), nullable=True), - sa.Column("scene", sa.String(length=500), nullable=True), - sa.Column("uuid", sa.String(length=36), nullable=False), - sa.Column("is_deleted", sa.Boolean(), nullable=True), - sa.Column("last_login_datetime", sa.DateTime(), nullable=True), - sa.Column("create_datetime", sa.DateTime(), nullable=False), - sa.Column("update_datetime", sa.DateTime(), nullable=False), - sa.PrimaryKeyConstraint("id"), - ) - with op.batch_alter_table("api_user", schema=None) as batch_op: - batch_op.create_index(batch_op.f("ix_user_email"), ["email"], unique=True) - batch_op.create_index(batch_op.f("ix_user_id"), ["id"], unique=False) - batch_op.create_index(batch_op.f("ix_user_phone"), ["phone"], unique=True) - batch_op.create_index(batch_op.f("ix_user_role"), ["role"], unique=False) - batch_op.create_index(batch_op.f("ix_user_state"), ["state"], unique=False) - batch_op.create_index(batch_op.f("ix_user_username"), ["username"], unique=False) - - conn = op.get_bind() - try: - roles = conn.execute("SELECT * FROM role").fetchall() - roles = [dict(i) for i in roles] - if roles: - op.bulk_insert(role_table, roles) - users = conn.execute("SELECT * FROM user").fetchall() - users = [dict(i) for i in users] - if users: - op.bulk_insert(user_table, users) - except Exception as e: - print("Could not migrate app users and roles to api_users and api_roles: %s" % e) - - # ### end Alembic commands ### + # remove legacy migration script content + # just keep this version for compatibility + pass def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - with op.batch_alter_table("api_user", schema=None) as batch_op: - batch_op.drop_index(batch_op.f("ix_user_username")) - batch_op.drop_index(batch_op.f("ix_user_state")) - batch_op.drop_index(batch_op.f("ix_user_role")) - batch_op.drop_index(batch_op.f("ix_user_phone")) - batch_op.drop_index(batch_op.f("ix_user_id")) - batch_op.drop_index(batch_op.f("ix_user_email")) - op.drop_table("api_user") - - with op.batch_alter_table("api_role", schema=None) as batch_op: - batch_op.drop_index(batch_op.f("ix_role_name")) - batch_op.drop_index(batch_op.f("ix_role_id")) - - op.drop_table("api_role") - # ### end Alembic commands ### + pass diff --git a/ymir/backend/src/ymir_auth/alembic/versions/6b9037792259_add_api_user_and_api_role_table.py b/ymir/backend/src/ymir_auth/alembic/versions/6b9037792259_add_api_user_and_api_role_table.py new file mode 100644 index 0000000000..ba1a07a487 --- /dev/null +++ b/ymir/backend/src/ymir_auth/alembic/versions/6b9037792259_add_api_user_and_api_role_table.py @@ -0,0 +1,92 @@ +"""add user and role table + +Revision ID: 6b9037792259 +Revises: +Create Date: 2023-03-23 16:00:00.469390 + +""" + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "6b9037792259" +down_revision = "4a002517f2a9" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + role_table = op.create_table( + "api_role", + sa.Column("id", sa.Integer(), autoincrement=True, nullable=False), + sa.Column("name", sa.String(length=100), nullable=True), + sa.Column("description", sa.String(length=100), nullable=True), + sa.PrimaryKeyConstraint("id"), + ) + with op.batch_alter_table("api_role", schema=None) as batch_op: + batch_op.create_index(batch_op.f("ix_role_id"), ["id"], unique=False) + batch_op.create_index(batch_op.f("ix_role_name"), ["name"], unique=False) + + user_table = op.create_table( + "api_user", + sa.Column("id", sa.Integer(), autoincrement=True, nullable=False), + sa.Column("email", sa.String(length=100), nullable=False), + sa.Column("username", sa.String(length=100), nullable=True), + sa.Column("phone", sa.String(length=20), nullable=True), + sa.Column("avatar", sa.String(length=100), nullable=True), + sa.Column("hashed_password", sa.String(length=200), nullable=False), + sa.Column("state", sa.Integer(), nullable=True), + sa.Column("role", sa.Integer(), nullable=True), + sa.Column("organization", sa.String(length=100), nullable=True), + sa.Column("scene", sa.String(length=500), nullable=True), + sa.Column("uuid", sa.String(length=36), nullable=False), + sa.Column("is_deleted", sa.Boolean(), nullable=True), + sa.Column("last_login_datetime", sa.DateTime(), nullable=True), + sa.Column("create_datetime", sa.DateTime(), nullable=False), + sa.Column("update_datetime", sa.DateTime(), nullable=False), + sa.PrimaryKeyConstraint("id"), + ) + with op.batch_alter_table("api_user", schema=None) as batch_op: + batch_op.create_index(batch_op.f("ix_user_email"), ["email"], unique=True) + batch_op.create_index(batch_op.f("ix_user_id"), ["id"], unique=False) + batch_op.create_index(batch_op.f("ix_user_phone"), ["phone"], unique=True) + batch_op.create_index(batch_op.f("ix_user_role"), ["role"], unique=False) + batch_op.create_index(batch_op.f("ix_user_state"), ["state"], unique=False) + batch_op.create_index(batch_op.f("ix_user_username"), ["username"], unique=False) + + conn = op.get_bind() + try: + roles = conn.execute("SELECT * FROM role").fetchall() + roles = [dict(i) for i in roles] + if roles: + op.bulk_insert(role_table, roles) + users = conn.execute("SELECT * FROM user").fetchall() + users = [dict(i) for i in users] + if users: + op.bulk_insert(user_table, users) + except Exception as e: + print("Could not migrate app users and roles to api_users and api_roles: %s" % e) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table("api_user", schema=None) as batch_op: + batch_op.drop_index(batch_op.f("ix_user_username")) + batch_op.drop_index(batch_op.f("ix_user_state")) + batch_op.drop_index(batch_op.f("ix_user_role")) + batch_op.drop_index(batch_op.f("ix_user_phone")) + batch_op.drop_index(batch_op.f("ix_user_id")) + batch_op.drop_index(batch_op.f("ix_user_email")) + op.drop_table("api_user") + + with op.batch_alter_table("api_role", schema=None) as batch_op: + batch_op.drop_index(batch_op.f("ix_role_name")) + batch_op.drop_index(batch_op.f("ix_role_id")) + + op.drop_table("api_role") + # ### end Alembic commands ### diff --git a/ymir/backend/src/ymir_auth/auth/api/errors/errors.py b/ymir/backend/src/ymir_auth/auth/api/errors/errors.py index 8c652fc2fa..654c436278 100644 --- a/ymir/backend/src/ymir_auth/auth/api/errors/errors.py +++ b/ymir/backend/src/ymir_auth/auth/api/errors/errors.py @@ -7,7 +7,6 @@ from starlette.responses import JSONResponse from starlette.status import HTTP_422_UNPROCESSABLE_ENTITY -from auth.config import settings from .error_codes import APIErrorCode as error_codes @@ -20,7 +19,7 @@ async def http_error_handler(_: Request, exc: HTTPException) -> JSONResponse: "code": error_codes.UNKNOWN_ERROR, "message": "Unknown Error", } - return JSONResponse(detail, status_code=200 if settings.USE_200_EVERYWHERE else exc.status_code) + return JSONResponse(detail, status_code=exc.status_code) async def http422_error_handler( @@ -33,7 +32,7 @@ async def http422_error_handler( "message": "Invalid Request Format", "errors": exc.errors(), }, - status_code=200 if settings.USE_200_EVERYWHERE else HTTP_422_UNPROCESSABLE_ENTITY, + status_code=HTTP_422_UNPROCESSABLE_ENTITY, ) diff --git a/ymir/backend/src/ymir_auth/auth/config.py b/ymir/backend/src/ymir_auth/auth/config.py index 03ad3b031f..9137afa537 100644 --- a/ymir/backend/src/ymir_auth/auth/config.py +++ b/ymir/backend/src/ymir_auth/auth/config.py @@ -23,8 +23,6 @@ class Settings(BaseSettings): FIRST_ADMIN: EmailStr = "admin@example.com" # type: ignore FIRST_ADMIN_PASSWORD: str = "change_this" - USE_200_EVERYWHERE: bool = False - APP_API_TIMEOUT: int = 30 APP_API_HOST: str = "backend:80" APP_API_KEY: str = secrets.token_urlsafe(32)