From cb05adeb48ed1948419af5f707141553cfe00dc7 Mon Sep 17 00:00:00 2001 From: Michael Genson <71845777+michael-genson@users.noreply.github.com> Date: Wed, 29 Jan 2025 13:52:12 -0600 Subject: [PATCH] fix: Remove API Tokens from User APIs (#4985) --- frontend/lib/api/types/user.ts | 7 ++++++- mealie/routes/users/api_tokens.py | 10 ++++++++-- mealie/schema/user/__init__.py | 2 ++ mealie/schema/user/user.py | 7 ++++++- .../user_tests/test_user_api_token.py | 19 +++++++++++++++++++ 5 files changed, 41 insertions(+), 4 deletions(-) diff --git a/frontend/lib/api/types/user.ts b/frontend/lib/api/types/user.ts index 855f64e7b..a29586b3a 100644 --- a/frontend/lib/api/types/user.ts +++ b/frontend/lib/api/types/user.ts @@ -93,6 +93,12 @@ export interface GroupSummary { slug: string; preferences?: ReadGroupPreferences | null; } +export interface LongLiveTokenCreateResponse { + name: string; + id: number; + createdAt?: string | null; + token: string; +} export interface LongLiveTokenIn { name: string; integrationId?: string; @@ -130,7 +136,6 @@ export interface PrivateUser { lockedAt?: string | null; } export interface LongLiveTokenOut { - token: string; name: string; id: number; createdAt?: string | null; diff --git a/mealie/routes/users/api_tokens.py b/mealie/routes/users/api_tokens.py index 8b8f31766..63c814db0 100644 --- a/mealie/routes/users/api_tokens.py +++ b/mealie/routes/users/api_tokens.py @@ -5,14 +5,20 @@ from fastapi import HTTPException, status from mealie.core.security import create_access_token from mealie.routes._base import BaseUserController, controller from mealie.routes._base.routers import UserAPIRouter -from mealie.schema.user import CreateToken, DeleteTokenResponse, LongLiveTokenIn, LongLiveTokenInDB, LongLiveTokenOut +from mealie.schema.user import ( + CreateToken, + DeleteTokenResponse, + LongLiveTokenCreateResponse, + LongLiveTokenIn, + LongLiveTokenInDB, +) router = UserAPIRouter(prefix="/users", tags=["Users: Tokens"]) @controller(router) class UserApiTokensController(BaseUserController): - @router.post("/api-tokens", status_code=status.HTTP_201_CREATED, response_model=LongLiveTokenOut) + @router.post("/api-tokens", status_code=status.HTTP_201_CREATED, response_model=LongLiveTokenCreateResponse) def create_api_token( self, token_params: LongLiveTokenIn, diff --git a/mealie/schema/user/__init__.py b/mealie/schema/user/__init__.py index 8dce0c179..76db2ec95 100644 --- a/mealie/schema/user/__init__.py +++ b/mealie/schema/user/__init__.py @@ -10,6 +10,7 @@ from .user import ( GroupInDB, GroupPagination, GroupSummary, + LongLiveTokenCreateResponse, LongLiveTokenIn, LongLiveTokenInDB, LongLiveTokenOut, @@ -57,6 +58,7 @@ __all__ = [ "GroupInDB", "GroupPagination", "GroupSummary", + "LongLiveTokenCreateResponse", "LongLiveTokenIn", "LongLiveTokenInDB", "LongLiveTokenOut", diff --git a/mealie/schema/user/user.py b/mealie/schema/user/user.py index f4e1228b4..b05c5e8f0 100644 --- a/mealie/schema/user/user.py +++ b/mealie/schema/user/user.py @@ -31,7 +31,6 @@ class LongLiveTokenIn(MealieModel): class LongLiveTokenOut(MealieModel): - token: str name: str id: int created_at: datetime | None = None @@ -42,6 +41,12 @@ class LongLiveTokenOut(MealieModel): return [joinedload(LongLiveToken.user)] +class LongLiveTokenCreateResponse(LongLiveTokenOut): + """Should ONLY be used when creating a new token, as the token field is sensitive""" + + token: str + + class CreateToken(LongLiveTokenIn): user_id: UUID4 token: str diff --git a/tests/integration_tests/user_tests/test_user_api_token.py b/tests/integration_tests/user_tests/test_user_api_token.py index 1fb03695c..44635addb 100644 --- a/tests/integration_tests/user_tests/test_user_api_token.py +++ b/tests/integration_tests/user_tests/test_user_api_token.py @@ -17,6 +17,25 @@ def long_live_token(api_client: TestClient, admin_token): def test_api_token_creation(api_client: TestClient, admin_token): response = api_client.post(api_routes.users_api_tokens, json={"name": "Test API Token"}, headers=admin_token) assert response.status_code == 201 + assert response.json()["token"] + + +def test_api_token_private(api_client: TestClient, admin_token): + response = api_client.post(api_routes.users_api_tokens, json={"name": "Test API Token"}, headers=admin_token) + assert response.status_code == 201 + + response = api_client.get(api_routes.users, headers=admin_token, params={"perPage": -1}) + assert response.status_code == 200 + for user in response.json()["items"]: + for user_token in user["tokens"] or []: + assert "token" not in user_token + + response = api_client.get(api_routes.users_self, headers=admin_token) + assert response.status_code == 200 + response_json = response.json() + assert response_json["tokens"] + for user_token in response_json["tokens"]: + assert "token" not in user_token def test_use_token(api_client: TestClient, long_live_token):