From 3cd8aff96ee71ba3675913c50e4421b7aebd357d Mon Sep 17 00:00:00 2001 From: gustavoschaedler Date: Tue, 15 Aug 2023 20:32:53 +0100 Subject: [PATCH 1/4] =?UTF-8?q?=F0=9F=90=9B=20fix(auth.py):=20change=20cre?= =?UTF-8?q?ate=5Fuser=5Flongterm=5Ftoken=20to=20use=20create=5Fsuper=5Fuse?= =?UTF-8?q?r=20to=20ensure=20super=20user=20exists=20before=20creating=20t?= =?UTF-8?q?oken=20=E2=9C=A8=20feat(auth.py):=20add=20create=5Fsuper=5Fuser?= =?UTF-8?q?=20function=20to=20create=20super=20user=20if=20it=20doesn't=20?= =?UTF-8?q?exist=20=F0=9F=90=9B=20fix(login.py):=20remove=20hardcoded=20us?= =?UTF-8?q?er=5Fid=20and=20use=20create=5Fuser=5Flongterm=5Ftoken=20withou?= =?UTF-8?q?t=20arguments=20=F0=9F=90=9B=20fix(users.py):=20remove=20redund?= =?UTF-8?q?ant=20password=20hashing=20in=20add=5Fsuper=5Fuser=5Ffor=5Ftest?= =?UTF-8?q?ing=5Fpurposes=5Fdelete=5Fme=5Fbefore=5Fmerge=5Finto=5Fdev=20?= =?UTF-8?q?=F0=9F=90=9B=20fix(base.py):=20change=20AUTO=5FLOGIN=20default?= =?UTF-8?q?=20value=20to=20False=20and=20add=20FIRST=5FSUPERUSER=20and=20F?= =?UTF-8?q?IRST=5FSUPERUSER=5FPASSWORD=20settings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/langflow/auth/auth.py | 34 +++++++++++++++---- src/backend/langflow/routers/login.py | 4 +-- src/backend/langflow/routers/users.py | 3 +- .../langflow/services/settings/base.py | 5 ++- 4 files changed, 34 insertions(+), 12 deletions(-) diff --git a/src/backend/langflow/auth/auth.py b/src/backend/langflow/auth/auth.py index b9e8dba3a..28d242342 100644 --- a/src/backend/langflow/auth/auth.py +++ b/src/backend/langflow/auth/auth.py @@ -83,18 +83,40 @@ def create_token(data: dict, expires_delta: timedelta): ) -def create_user_longterm_token( - user_id: UUID, db: Session = Depends(get_session), update_last_login: bool = False -) -> dict: +def create_super_user(db: Session = Depends(get_session)) -> User: + settings_manager = get_settings_manager() + + super_user = get_user_by_username(db, settings_manager.settings.FIRST_SUPERUSER) + + if not super_user: + super_user = User( + username=settings_manager.settings.FIRST_SUPERUSER, + password=get_password_hash( + settings_manager.settings.FIRST_SUPERUSER_PASSWORD + ), + is_superuser=True, + is_active=True, + last_login_at=None, + ) + + db.add(super_user) + db.commit() + db.refresh(super_user) + + return super_user + + +def create_user_longterm_token(db: Session = Depends(get_session)) -> dict: + super_user = create_super_user(db) + access_token_expires_longterm = timedelta(days=365) access_token = create_token( - data={"sub": str(user_id)}, + data={"sub": str(super_user.id)}, expires_delta=access_token_expires_longterm, ) # Update: last_login_at - if update_last_login: - update_user_last_login_at(user_id, db) + update_user_last_login_at(super_user.id, db) return { "access_token": access_token, diff --git a/src/backend/langflow/routers/login.py b/src/backend/langflow/routers/login.py index 7d114473d..de255a0d5 100644 --- a/src/backend/langflow/routers/login.py +++ b/src/backend/langflow/routers/login.py @@ -1,4 +1,3 @@ -from uuid import UUID from sqlalchemy.orm import Session from fastapi import APIRouter, Depends, HTTPException, status from fastapi.security import OAuth2PasswordRequestForm @@ -38,8 +37,7 @@ async def auto_login(db: Session = Depends(get_session)): settings_manager = get_settings_manager() if settings_manager.settings.AUTO_LOGIN: - user_id = UUID("3fa85f64-5717-4562-b3fc-2c963f66afa6") - return create_user_longterm_token(user_id, db) + return create_user_longterm_token(db) raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, diff --git a/src/backend/langflow/routers/users.py b/src/backend/langflow/routers/users.py index da738a5cd..04972c976 100644 --- a/src/backend/langflow/routers/users.py +++ b/src/backend/langflow/routers/users.py @@ -115,14 +115,13 @@ def add_super_user_for_testing_purposes_delete_me_before_merge_into_dev( """ new_user = User( username="superuser", - password="12345", + password=get_password_hash("12345"), is_active=True, is_superuser=True, last_login_at=None, ) try: - new_user.password = get_password_hash(new_user.password) db.add(new_user) db.commit() db.refresh(new_user) diff --git a/src/backend/langflow/services/settings/base.py b/src/backend/langflow/services/settings/base.py index d8b3f3ad6..d99f0f8b5 100644 --- a/src/backend/langflow/services/settings/base.py +++ b/src/backend/langflow/services/settings/base.py @@ -41,9 +41,12 @@ class Settings(BaseSettings): ALGORITHM: str = "HS256" ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 REFRESH_TOKEN_EXPIRE_MINUTES: int = 70 + # If AUTO_LOGIN = True # > The application does not request login and logs in automatically as a super user. - AUTO_LOGIN: bool = True + AUTO_LOGIN: bool = False + FIRST_SUPERUSER: str = "superuser" + FIRST_SUPERUSER_PASSWORD: str = "12345" @validator("DATABASE_URL", pre=True) def set_database_url(cls, value): From effbddbcb094215d3d84ebdbacc874c2c216ecb7 Mon Sep 17 00:00:00 2001 From: gustavoschaedler Date: Tue, 15 Aug 2023 20:56:10 +0100 Subject: [PATCH 2/4] =?UTF-8?q?=F0=9F=90=9B=20fix(base.py):=20set=20AUTO?= =?UTF-8?q?=5FLOGIN=20to=20True=20to=20enable=20automatic=20login=20as=20a?= =?UTF-8?q?=20super=20user=20=F0=9F=94=A7=20chore(base.py):=20improve=20re?= =?UTF-8?q?adability=20by=20formatting=20logger.debug=20statements?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/langflow/services/settings/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/langflow/services/settings/base.py b/src/backend/langflow/services/settings/base.py index d99f0f8b5..f1b012f4d 100644 --- a/src/backend/langflow/services/settings/base.py +++ b/src/backend/langflow/services/settings/base.py @@ -44,7 +44,7 @@ class Settings(BaseSettings): # If AUTO_LOGIN = True # > The application does not request login and logs in automatically as a super user. - AUTO_LOGIN: bool = False + AUTO_LOGIN: bool = True FIRST_SUPERUSER: str = "superuser" FIRST_SUPERUSER_PASSWORD: str = "12345" From 91fcf33506ca227a80d1a305e7a9699ce37f7bc5 Mon Sep 17 00:00:00 2001 From: gustavoschaedler Date: Tue, 15 Aug 2023 23:47:50 +0100 Subject: [PATCH 3/4] =?UTF-8?q?=F0=9F=90=9B=20fix(auth.py):=20import=20get?= =?UTF-8?q?=5Fsession=20from=20correct=20module=20to=20fix=20import=20erro?= =?UTF-8?q?r=20=E2=9C=A8=20feat(auth.py):=20add=20support=20for=20creating?= =?UTF-8?q?=20user=20API=20key=20and=20getting=20user=20ID=20from=20token?= =?UTF-8?q?=20=F0=9F=90=9B=20fix(base.py):=20fix=20typo=20in=20API=5FKEY?= =?UTF-8?q?=5FSECRET=5FKEY=20variable=20name=20=F0=9F=90=9B=20fix(base.py)?= =?UTF-8?q?:=20fix=20typo=20in=20FIRST=5FSUPERUSER=20and=20FIRST=5FSUPERUS?= =?UTF-8?q?ER=5FPASSWORD=20variable=20names=20=F0=9F=90=9B=20fix(base.py):?= =?UTF-8?q?=20fix=20indentation=20in=20load=5Fsettings=5Ffrom=5Fyaml=20fun?= =?UTF-8?q?ction?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/langflow/auth/auth.py | 20 +++++++++++++++++-- .../langflow/services/settings/base.py | 10 ++++++++-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/backend/langflow/auth/auth.py b/src/backend/langflow/auth/auth.py index 28d242342..9d4f12862 100644 --- a/src/backend/langflow/auth/auth.py +++ b/src/backend/langflow/auth/auth.py @@ -7,9 +7,8 @@ from fastapi.security import OAuth2PasswordBearer from fastapi import Depends, HTTPException, status from datetime import datetime, timedelta, timezone -from langflow.services.utils import get_settings_manager +from langflow.services.utils import get_settings_manager, get_session -from langflow.services.utils import get_session from langflow.database.models.user import ( User, get_user_by_id, @@ -125,6 +124,23 @@ def create_user_longterm_token(db: Session = Depends(get_session)) -> dict: } +def create_user_api_key(user_id: UUID) -> dict: + access_token = create_token( + data={"sub": str(user_id), "role": "api_key"}, + expires_delta=timedelta(days=365 * 2), + ) + + return {"api_key": access_token} + + +def get_user_id_from_token(token: str) -> UUID: + try: + user_id = jwt.get_unverified_claims(token)["sub"] + return UUID(user_id) + except (KeyError, JWTError, ValueError): + return UUID(int=0) + + def create_user_tokens( user_id: UUID, db: Session = Depends(get_session), update_last_login: bool = False ) -> dict: diff --git a/src/backend/langflow/services/settings/base.py b/src/backend/langflow/services/settings/base.py index f1b012f4d..ec976bade 100644 --- a/src/backend/langflow/services/settings/base.py +++ b/src/backend/langflow/services/settings/base.py @@ -42,11 +42,17 @@ class Settings(BaseSettings): ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 REFRESH_TOKEN_EXPIRE_MINUTES: int = 70 + # API Key to execute /process endpoint + API_KEY_SECRET_KEY: Optional[ + str + ] = "b82818e0ad4ff76615c5721ee21004b07d84cd9b87ba4d9cb42374da134b841a" + API_KEY_ALGORITHM: str = "HS256" + # If AUTO_LOGIN = True # > The application does not request login and logs in automatically as a super user. AUTO_LOGIN: bool = True - FIRST_SUPERUSER: str = "superuser" - FIRST_SUPERUSER_PASSWORD: str = "12345" + FIRST_SUPERUSER: str = "langflow" + FIRST_SUPERUSER_PASSWORD: str = "langflow" @validator("DATABASE_URL", pre=True) def set_database_url(cls, value): From 4eeb9449cb607c26688c6c2a63f5b53cb648ce01 Mon Sep 17 00:00:00 2001 From: gustavoschaedler Date: Wed, 16 Aug 2023 20:20:18 +0100 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=94=A7=20fix(main.py):=20reformat=20i?= =?UTF-8?q?mport=20statements=20to=20improve=20readability=20and=20maintai?= =?UTF-8?q?nability=20=E2=9C=A8=20feat(main.py):=20add=20support=20for=20A?= =?UTF-8?q?PI=20key=20routes=20to=20enable=20API=20key=20management=20func?= =?UTF-8?q?tionality=20=F0=9F=93=9D=20docs(api=5Fkey.py):=20add=20API=20ke?= =?UTF-8?q?y=20routes=20for=20retrieving,=20creating,=20and=20deleting=20A?= =?UTF-8?q?PI=20keys?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/langflow/main.py | 3 +- src/backend/langflow/routers/api_key.py | 49 +++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 src/backend/langflow/routers/api_key.py diff --git a/src/backend/langflow/main.py b/src/backend/langflow/main.py index b63caff24..7045ec99d 100644 --- a/src/backend/langflow/main.py +++ b/src/backend/langflow/main.py @@ -6,7 +6,7 @@ from fastapi.responses import FileResponse from fastapi.staticfiles import StaticFiles from langflow.api import router -from langflow.routers import login, users, health +from langflow.routers import api_key, login, users, health from langflow.interface.utils import setup_llm_caching from langflow.services.database.utils import initialize_database @@ -32,6 +32,7 @@ def create_app(): ) app.include_router(login.router) + app.include_router(api_key.router) app.include_router(users.router) app.include_router(health.router) diff --git a/src/backend/langflow/routers/api_key.py b/src/backend/langflow/routers/api_key.py new file mode 100644 index 000000000..7cc712962 --- /dev/null +++ b/src/backend/langflow/routers/api_key.py @@ -0,0 +1,49 @@ + + +from fastapi import APIRouter + + + +router = APIRouter(tags=["APIKey"]) + + +@router.get("/api_key/{user_id}") +def get_api_key(user_id: str): + return { + "total_count": 3, + "user_id": user_id, + "api_keys": [ + { + "id": "4425707e-cce4-4d1b-a54e-bd2632064657", + "name": "my api_key name - 01", + "created_at": "2023-08-15T19:28:40.019613", + "last_used_at": "2023-08-16T18:38:20.875210", + }, + { + "id": "6fb7282b-9f2e-4efe-9bda-0c3d8f899473", + "name": "my api_key name - 02", + "created_at": "2023-08-15T19:41:30.077942", + "last_used_at": "2023-08-15T19:45:32.067899", + }, + { + "id": "c55f3b32-4920-42b6-a5cd-698b4251806e", + "name": "my api_key name - 03", + "created_at": "2023-08-15T20:29:40.577808", + "last_used_at": "2023-08-15T20:29:40.577816", + }, + ], + } + + +@router.post("/api_key/{user_id}") +def create_api_key(user_id: str): + return { + "user_id": user_id, + "name": "my api-key 01", + "api_key": "lf-eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1YTBmODM1ZS0yMTQxLTQ2YWItYmQ4NS0yMWEzMjQ1MTE2ZDAiLCJleHAiOjE2OTIyMTUwMTN9.c_s0ZPRtjSI9yUrhi8ACIwyXf0feRLYfaeIZEbRVKQg", + } + + +@router.delete("/api_key/{api_key_id}") +def delete_api_key(api_key_id: str): + return {"detail": "API Key deleted"}