diff --git a/src/backend/langflow/auth/__init__.py b/src/backend/langflow/auth/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/backend/langflow/auth/auth.py b/src/backend/langflow/auth/auth.py new file mode 100644 index 000000000..503b2bd5b --- /dev/null +++ b/src/backend/langflow/auth/auth.py @@ -0,0 +1,72 @@ +from typing import Annotated + +from fastapi import Depends, HTTPException, status +from passlib.context import CryptContext +from jose import JWTError, jwt +from datetime import datetime, timedelta, timezone +from fastapi.security import OAuth2PasswordBearer +from ..models.token import TokenData +from ..models.user import get_user, fake_users_db, User + + +SECRET_KEY = "your_secret_key" +ALGORITHM = "HS256" +ACCESS_TOKEN_EXPIRE_MINUTES = 30 + +pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") + + +def verify_password(plain_password, hashed_password): + return pwd_context.verify(plain_password, hashed_password) + + +def get_password_hash(password): + return pwd_context.hash(password) + + +def create_access_token(data: dict, expires_delta: timedelta = None): + to_encode = data.copy() + if expires_delta: + expire = datetime.now(timezone.utc) + expires_delta + else: + expire = datetime.now(timezone.utc) + timedelta(minutes=15) + to_encode["exp"] = expire + return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) + + +def authenticate_user(fake_db, username: str, password: str): + user = get_user(fake_db, username) + if not user: + return False + if not verify_password(password, user.hashed_password): + return False + return user + + +async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]): + credentials_exception = HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Could not validate credentials", + headers={"WWW-Authenticate": "Bearer"}, + ) + try: + payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) + username: str = payload.get("sub") + if username is None: + raise credentials_exception + token_data = TokenData(username=username) + except JWTError: + raise credentials_exception + user = get_user(fake_users_db, username=token_data.username) + if user is None: + raise credentials_exception + return user + + +async def get_current_active_user( + current_user: Annotated[User, Depends(get_current_user)] +): + if current_user.disabled: + raise HTTPException(status_code=400, detail="Inactive user") + return current_user diff --git a/src/backend/langflow/login.py b/src/backend/langflow/login.py deleted file mode 100644 index c2782be56..000000000 --- a/src/backend/langflow/login.py +++ /dev/null @@ -1,143 +0,0 @@ -from datetime import datetime, timedelta -from typing import Annotated - -from fastapi import Depends, FastAPI, HTTPException, status -from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm -from jose import JWTError, jwt -from passlib.context import CryptContext -from pydantic import BaseModel - -# to get a string like this run: -# openssl rand -hex 32 -SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7" -ALGORITHM = "HS256" -ACCESS_TOKEN_EXPIRE_MINUTES = 30 - - -fake_users_db = { - "johndoe": { - "username": "johndoe", - "full_name": "John Doe", - "email": "johndoe@example.com", - "hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW", - "disabled": False, - } -} - - -class Token(BaseModel): - access_token: str - token_type: str - - -class TokenData(BaseModel): - username: str | None = None - - -class User(BaseModel): - username: str - email: str | None = None - full_name: str | None = None - disabled: bool | None = None - - -class UserInDB(User): - hashed_password: str - - -pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") -oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") - - -def verify_password(plain_password, hashed_password): - return pwd_context.verify(plain_password, hashed_password) - - -def get_password_hash(password): - return pwd_context.hash(password) - - -def get_user(db, username: str): - if username in db: - user_dict = db[username] - return UserInDB(**user_dict) - - -def authenticate_user(fake_db, username: str, password: str): - user = get_user(fake_db, username) - if not user: - return False - if not verify_password(password, user.hashed_password): - return False - return user - - -def create_access_token(data: dict, expires_delta: timedelta | None = None): - to_encode = data.copy() - if expires_delta: - expire = datetime.utcnow() + expires_delta - else: - expire = datetime.utcnow() + timedelta(minutes=15) - to_encode.update({"exp": expire}) - encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) - return encoded_jwt - - -async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]): - credentials_exception = HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Could not validate credentials", - headers={"WWW-Authenticate": "Bearer"}, - ) - try: - payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) - username: str = payload.get("sub") - if username is None: - raise credentials_exception - token_data = TokenData(username=username) - except JWTError: - raise credentials_exception - user = get_user(fake_users_db, username=token_data.username) - if user is None: - raise credentials_exception - return user - - -async def get_current_active_user( - current_user: Annotated[User, Depends(get_current_user)] -): - if current_user.disabled: - raise HTTPException(status_code=400, detail="Inactive user") - return current_user - - -@app.post("/token", response_model=Token) -async def login_for_access_token( - form_data: Annotated[OAuth2PasswordRequestForm, Depends()] -): - user = authenticate_user(fake_users_db, form_data.username, form_data.password) - if not user: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Incorrect username or password", - headers={"WWW-Authenticate": "Bearer"}, - ) - access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) - access_token = create_access_token( - data={"sub": user.username}, expires_delta=access_token_expires - ) - return {"access_token": access_token, "token_type": "bearer"} - - -@app.get("/users/me/", response_model=User) -async def read_users_me( - current_user: Annotated[User, Depends(get_current_active_user)] -): - return current_user - - -@app.get("/users/me/items/") -async def read_own_items( - current_user: Annotated[User, Depends(get_current_active_user)] -): - return [{"item_id": "Foo", "owner": current_user.username}] \ No newline at end of file diff --git a/src/backend/langflow/main.py b/src/backend/langflow/main.py index 78ac1e75f..d546fd58e 100644 --- a/src/backend/langflow/main.py +++ b/src/backend/langflow/main.py @@ -2,160 +2,15 @@ from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from langflow.api import router +from langflow.routers import login, users, items, health from langflow.database.base import create_db_and_tables -from datetime import datetime, timedelta -from typing import Annotated - -from fastapi import Depends, HTTPException, status -from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm -from jose import JWTError, jwt -from passlib.context import CryptContext -from pydantic import BaseModel - -# to get a string like this run: -# openssl rand -hex 32 -SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7" -ALGORITHM = "HS256" -ACCESS_TOKEN_EXPIRE_MINUTES = 30 - - -fake_users_db = { - "johndoe": { - "username": "johndoe", - "full_name": "John Doe", - "email": "johndoe@example.com", - "hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW", - "disabled": False, - } -} - - -class Token(BaseModel): - access_token: str - token_type: str - - -class TokenData(BaseModel): - username: str | None = None - - -class User(BaseModel): - username: str - email: str | None = None - full_name: str | None = None - disabled: bool | None = None - - -class UserInDB(User): - hashed_password: str - - -pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") -oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") - - -def verify_password(plain_password, hashed_password): - return pwd_context.verify(plain_password, hashed_password) - - -def get_password_hash(password): - return pwd_context.hash(password) - - -def get_user(db, username: str): - if username in db: - user_dict = db[username] - return UserInDB(**user_dict) - - -def authenticate_user(fake_db, username: str, password: str): - user = get_user(fake_db, username) - if not user: - return False - if not verify_password(password, user.hashed_password): - return False - return user - - -def create_access_token(data: dict, expires_delta: timedelta | None = None): - to_encode = data.copy() - if expires_delta: - expire = datetime.utcnow() + expires_delta - else: - expire = datetime.utcnow() + timedelta(minutes=15) - to_encode.update({"exp": expire}) - encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) - return encoded_jwt - - -async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]): - credentials_exception = HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Could not validate credentials", - headers={"WWW-Authenticate": "Bearer"}, - ) - try: - payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) - username: str = payload.get("sub") - if username is None: - raise credentials_exception - token_data = TokenData(username=username) - except JWTError: - raise credentials_exception - user = get_user(fake_users_db, username=token_data.username) - if user is None: - raise credentials_exception - return user - - -async def get_current_active_user(current_user: Annotated[User, Depends(get_current_user)]): - if current_user.disabled: - raise HTTPException(status_code=400, detail="Inactive user") - return current_user - def create_app(): """Create the FastAPI app and include the router.""" app = FastAPI() - origins = [ - "*", - ] - - @app.post("/token", response_model=Token) - async def login_for_access_token(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]): - user = authenticate_user(fake_users_db, form_data.username, form_data.password) - if not user: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Incorrect username or password", - headers={"WWW-Authenticate": "Bearer"}, - ) - access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) - access_token = create_access_token( - data={"sub": user.username}, - expires_delta=access_token_expires - ) - return {"access_token": access_token, "token_type": "bearer"} - - - @app.get("/users/me/", response_model=User) - async def read_users_me( - current_user: Annotated[User, Depends(get_current_active_user)] - ): - return current_user - - - @app.get("/users/me/items/") - async def read_own_items( - current_user: Annotated[User, Depends(get_current_active_user)] - ): - return [{"item_id": "Foo", "owner": current_user.username}] - - @app.get("/health") - def get_health(): - return {"status": "OK"} + origins = ["*"] app.add_middleware( CORSMiddleware, @@ -165,7 +20,12 @@ def create_app(): allow_headers=["*"], ) + app.include_router(login.router) + app.include_router(users.router) + app.include_router(items.router) + app.include_router(health.router) app.include_router(router) + app.on_event("startup")(create_db_and_tables) return app diff --git a/src/backend/langflow/models/__init__.py b/src/backend/langflow/models/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/backend/langflow/models/token.py b/src/backend/langflow/models/token.py new file mode 100644 index 000000000..080286787 --- /dev/null +++ b/src/backend/langflow/models/token.py @@ -0,0 +1,10 @@ +from pydantic import BaseModel + + +class Token(BaseModel): + access_token: str + token_type: str + + +class TokenData(BaseModel): + username: str | None = None diff --git a/src/backend/langflow/models/user.py b/src/backend/langflow/models/user.py new file mode 100644 index 000000000..1023a6a65 --- /dev/null +++ b/src/backend/langflow/models/user.py @@ -0,0 +1,29 @@ +from pydantic import BaseModel + + +class User(BaseModel): + username: str + email: str | None = None + full_name: str | None = None + disabled: bool | None = None + + +class UserInDB(User): + hashed_password: str + + +fake_users_db = { + "johndoe": { + "username": "johndoe", + "full_name": "John Doe", + "email": "johndoe@example.com", + "hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW", + "disabled": False, + } +} + + +def get_user(db, username: str): + if username in db: + user_dict = db[username] + return UserInDB(**user_dict) diff --git a/src/backend/langflow/routers/__init__.py b/src/backend/langflow/routers/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/backend/langflow/routers/health.py b/src/backend/langflow/routers/health.py new file mode 100644 index 000000000..244ef001d --- /dev/null +++ b/src/backend/langflow/routers/health.py @@ -0,0 +1,8 @@ +from fastapi import APIRouter + +router = APIRouter() + + +@router.get("/health") +def get_health(): + return {"status": "OK"} diff --git a/src/backend/langflow/routers/items.py b/src/backend/langflow/routers/items.py new file mode 100644 index 000000000..e6d21340e --- /dev/null +++ b/src/backend/langflow/routers/items.py @@ -0,0 +1,12 @@ +from fastapi import APIRouter, Depends +from ..models.user import User +from ..auth.auth import get_current_active_user + +router = APIRouter() + + +@router.get("/users/me/items/") +async def read_own_items( + current_user: User = Depends(get_current_active_user) +): + return [{"item_id": "Foo", "owner": current_user.username}] diff --git a/src/backend/langflow/routers/login.py b/src/backend/langflow/routers/login.py new file mode 100644 index 000000000..eac2d57bb --- /dev/null +++ b/src/backend/langflow/routers/login.py @@ -0,0 +1,35 @@ +from fastapi import APIRouter, Depends, HTTPException, status +from fastapi.security import OAuth2PasswordRequestForm +from langflow.models.token import Token +from langflow.models.user import fake_users_db +from datetime import timedelta +from langflow.auth.auth import ( + ACCESS_TOKEN_EXPIRE_MINUTES, + authenticate_user, + create_access_token +) + +router = APIRouter() + + +@router.post("/token", response_model=Token) +async def login_for_access_token( + form_data: OAuth2PasswordRequestForm = Depends() +): + user = authenticate_user( + fake_users_db, + form_data.username, + form_data.password + ) + if not user: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Incorrect username or password", + headers={"WWW-Authenticate": "Bearer"}, + ) + access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) + access_token = create_access_token( + data={"sub": user.username}, + expires_delta=access_token_expires + ) + return {"access_token": access_token, "token_type": "bearer"} diff --git a/src/backend/langflow/routers/users.py b/src/backend/langflow/routers/users.py new file mode 100644 index 000000000..1a9184ec8 --- /dev/null +++ b/src/backend/langflow/routers/users.py @@ -0,0 +1,10 @@ +from fastapi import APIRouter, Depends +from langflow.models.user import User +from langflow.auth.auth import get_current_active_user + +router = APIRouter() + + +@router.get("/users/me/", response_model=User) +async def read_users_me(current_user: User = Depends(get_current_active_user)): + return current_user