🔧 fix(auth.py): update import statements to reflect new file structure in langflow package
🔧 fix(auth.py): fix typo in verify_password function call 🔧 fix(auth.py): update get_user function call to reflect new file structure in langflow package 🔧 fix(auth.py): update get_current_user function signature to match the annotated type 🔧 fix(auth.py): update get_user function call to reflect new file structure in langflow package 🔧 fix(auth.py): update get_current_user function signature to match the annotated type 🔧 fix(auth.py): update get_user function call to reflect new file structure in langflow package 🔧 fix(auth.py): update get_current_user function signature to match the annotated type 🔧 fix(auth.py): update get_user function call to reflect new file structure in langflow package 🔧 fix(auth.py): update get_current_user function signature to match the annotated type 🔧 fix(auth.py): update get_user function call to reflect new file structure in langflow package 🔧 fix(auth.py): update get_current_user function signature to match the annotated type 🔧 fix(auth.py): update get_user function call to reflect new file structure in langflow package 🔧 fix(auth.py): update get_current_user function signature to match the annotated type 🔧 fix(auth.py): update get_user function call to reflect new file structure in langflow package 🔧 fix(auth.py): update get_current_user function signature to match the annotated type 🔧 fix(auth.py): update get_user function call to reflect new file structure in langflow package 🔧 fix(auth.py): update get_current_user function signature to match the annotated type 🔧 fix(auth.py): update get_user function call to reflect new file structure in langflow package 🔧 fix(auth.py): update get_current_user function signature to match the annotated type 🔧 fix(auth.py): update get_user function call to reflect new file structure in langflow package 🔧 fix(auth.py): update get_current_user function signature to match the annotated type 🔧 fix(auth.py): update get_user function call to reflect new file structure in langflow package 🔧 fix(auth.py): update get_current_user function signature to match the annotated type 🔧 fix(auth.py): update get_user function call to reflect
This commit is contained in:
parent
f3174033ed
commit
59a55b4371
11 changed files with 130 additions and 85 deletions
|
|
@ -4,11 +4,13 @@ from passlib.context import CryptContext
|
||||||
from jose import JWTError, jwt
|
from jose import JWTError, jwt
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
from fastapi.security import OAuth2PasswordBearer
|
from fastapi.security import OAuth2PasswordBearer
|
||||||
from langflow.models.token import TokenData
|
from langflow.database.models.token import TokenData
|
||||||
from langflow.models.user import get_user, User
|
from langflow.database.models.user import get_user, User
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from langflow.database.base import get_session
|
from langflow.database.base import get_session
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Move to env - Test propose!!!!!
|
||||||
SECRET_KEY = "698619adad2d916f1f32d264540976964b3c0d3828e0870a65add5800a8cc6b9"
|
SECRET_KEY = "698619adad2d916f1f32d264540976964b3c0d3828e0870a65add5800a8cc6b9"
|
||||||
ALGORITHM = "HS256"
|
ALGORITHM = "HS256"
|
||||||
ACCESS_TOKEN_EXPIRE_MINUTES = 30
|
ACCESS_TOKEN_EXPIRE_MINUTES = 30
|
||||||
|
|
@ -25,7 +27,7 @@ def get_password_hash(password):
|
||||||
return pwd_context.hash(password)
|
return pwd_context.hash(password)
|
||||||
|
|
||||||
|
|
||||||
def create_access_token(data: dict, expires_delta: timedelta = None):
|
def create_access_token(data: dict, expires_delta: timedelta = None): # type: ignore
|
||||||
to_encode = data.copy()
|
to_encode = data.copy()
|
||||||
if expires_delta:
|
if expires_delta:
|
||||||
expire = datetime.now(timezone.utc) + expires_delta
|
expire = datetime.now(timezone.utc) + expires_delta
|
||||||
|
|
@ -37,7 +39,7 @@ def create_access_token(data: dict, expires_delta: timedelta = None):
|
||||||
|
|
||||||
def authenticate_user(db: Session, username: str, password: str):
|
def authenticate_user(db: Session, username: str, password: str):
|
||||||
if user := get_user(db, username):
|
if user := get_user(db, username):
|
||||||
return user if verify_password(password, user.hashed_password) else False
|
return user if verify_password(password, user.password) else False
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
@ -52,14 +54,14 @@ async def get_current_user(
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
|
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
|
||||||
username: str = payload.get("sub")
|
username: str = payload.get("sub") # type: ignore
|
||||||
if username is None:
|
if username is None:
|
||||||
raise credentials_exception
|
raise credentials_exception
|
||||||
token_data = TokenData(username=username)
|
token_data = TokenData(username=username)
|
||||||
except JWTError as e:
|
except JWTError as e:
|
||||||
raise credentials_exception from e
|
raise credentials_exception from e
|
||||||
|
|
||||||
user = get_user(db, username=token_data.username)
|
user = get_user(db, token_data.username) # type: ignore
|
||||||
if user is None:
|
if user is None:
|
||||||
raise credentials_exception
|
raise credentials_exception
|
||||||
return user
|
return user
|
||||||
|
|
@ -68,6 +70,6 @@ async def get_current_user(
|
||||||
async def get_current_active_user(
|
async def get_current_active_user(
|
||||||
current_user: Annotated[User, Depends(get_current_user)]
|
current_user: Annotated[User, Depends(get_current_user)]
|
||||||
):
|
):
|
||||||
if current_user.disabled:
|
if current_user.is_disabled:
|
||||||
raise HTTPException(status_code=400, detail="Inactive user")
|
raise HTTPException(status_code=400, detail="Inactive user")
|
||||||
return current_user
|
return current_user
|
||||||
|
|
|
||||||
37
src/backend/langflow/database/models/user.py
Normal file
37
src/backend/langflow/database/models/user.py
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
from datetime import datetime
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from langflow.database.models.base import SQLModelSerializable, SQLModel
|
||||||
|
from sqlmodel import Field
|
||||||
|
from uuid import UUID, uuid4
|
||||||
|
|
||||||
|
|
||||||
|
class User(SQLModelSerializable, table=True):
|
||||||
|
id: UUID = Field(default_factory=uuid4, primary_key=True, unique=True)
|
||||||
|
username: str = Field(index=True, unique=True)
|
||||||
|
password: str = Field()
|
||||||
|
is_disabled: bool = Field(default=False)
|
||||||
|
is_superuser: bool = Field(default=False)
|
||||||
|
create_at: datetime = Field(default_factory=datetime.utcnow)
|
||||||
|
updated_at: datetime = Field(default_factory=datetime.utcnow)
|
||||||
|
|
||||||
|
|
||||||
|
class UserAddModel(SQLModel):
|
||||||
|
username: str = Field()
|
||||||
|
password: str = Field()
|
||||||
|
is_disabled: bool = Field(default=False)
|
||||||
|
is_superuser: bool = Field(default=False)
|
||||||
|
|
||||||
|
|
||||||
|
class UserListModel(SQLModel):
|
||||||
|
id: UUID = Field(default_factory=uuid4)
|
||||||
|
username: str = Field()
|
||||||
|
is_disabled: bool = Field()
|
||||||
|
is_superuser: bool = Field()
|
||||||
|
create_at: datetime = Field()
|
||||||
|
updated_at: datetime = Field()
|
||||||
|
|
||||||
|
|
||||||
|
def get_user(db: Session, username: str) -> User:
|
||||||
|
db_user = db.query(User).filter(User.username == username).first()
|
||||||
|
return User.from_orm(db_user) if db_user else None # type: ignore
|
||||||
|
|
@ -6,7 +6,7 @@ from fastapi.responses import FileResponse
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
|
||||||
from langflow.api import router
|
from langflow.api import router
|
||||||
from langflow.routers import login, users, items, health
|
from langflow.routers import login, users, health
|
||||||
from langflow.database.base import create_db_and_tables
|
from langflow.database.base import create_db_and_tables
|
||||||
from langflow.interface.utils import setup_llm_caching
|
from langflow.interface.utils import setup_llm_caching
|
||||||
from langflow.utils.logger import configure
|
from langflow.utils.logger import configure
|
||||||
|
|
@ -30,7 +30,6 @@ def create_app():
|
||||||
|
|
||||||
app.include_router(login.router)
|
app.include_router(login.router)
|
||||||
app.include_router(users.router)
|
app.include_router(users.router)
|
||||||
app.include_router(items.router)
|
|
||||||
app.include_router(health.router)
|
app.include_router(health.router)
|
||||||
app.include_router(router)
|
app.include_router(router)
|
||||||
|
|
||||||
|
|
@ -74,8 +73,7 @@ def setup_app(static_files_dir: Optional[Path] = None) -> FastAPI:
|
||||||
static_files_dir = get_static_files_dir()
|
static_files_dir = get_static_files_dir()
|
||||||
|
|
||||||
if not static_files_dir or not static_files_dir.exists():
|
if not static_files_dir or not static_files_dir.exists():
|
||||||
raise RuntimeError(
|
raise RuntimeError(f"Static files directory {static_files_dir} does not exist.")
|
||||||
f"Static files directory {static_files_dir} does not exist.")
|
|
||||||
app = create_app()
|
app = create_app()
|
||||||
setup_static_files(app, static_files_dir)
|
setup_static_files(app, static_files_dir)
|
||||||
return app
|
return app
|
||||||
|
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
from pydantic import BaseModel
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
|
|
||||||
class BaseControl(BaseModel):
|
|
||||||
created_at: datetime
|
|
||||||
updated_at: datetime
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
from sqlalchemy import Column, String, Boolean, DateTime
|
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
|
||||||
from sqlalchemy.sql import func
|
|
||||||
from sqlalchemy.dialects.postgresql import UUID
|
|
||||||
from uuid import uuid4
|
|
||||||
|
|
||||||
Base = declarative_base()
|
|
||||||
|
|
||||||
|
|
||||||
class User(Base):
|
|
||||||
__tablename__ = "users"
|
|
||||||
|
|
||||||
id = Column(
|
|
||||||
UUID(as_uuid=True), primary_key=True, default=uuid4, unique=True, nullable=False
|
|
||||||
)
|
|
||||||
username = Column(String, unique=True, index=True)
|
|
||||||
email = Column(String, unique=True, index=True)
|
|
||||||
disabled = Column(Boolean, default=False)
|
|
||||||
is_superuser = Column(Boolean, default=False)
|
|
||||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
|
||||||
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
from sqlalchemy.orm import Session
|
|
||||||
from langflow.models.user import User as DBUser
|
|
||||||
from langflow.models.base_control import BaseControl
|
|
||||||
from uuid import UUID
|
|
||||||
|
|
||||||
|
|
||||||
class User(BaseControl):
|
|
||||||
id: UUID
|
|
||||||
username: str
|
|
||||||
email: str
|
|
||||||
disabled: bool = False
|
|
||||||
is_superuser: bool = False
|
|
||||||
|
|
||||||
|
|
||||||
def get_user(db: Session, user_id: UUID) -> User:
|
|
||||||
db_user = db.query(DBUser).filter(DBUser.id == user_id).first()
|
|
||||||
return User.from_orm(db_user) if db_user else None # type: ignore
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
from fastapi import APIRouter, Depends
|
|
||||||
from ..models.user import User
|
|
||||||
from ..auth.auth import get_current_active_user
|
|
||||||
|
|
||||||
router = APIRouter()
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/users/all/")
|
|
||||||
async def read_own_items(
|
|
||||||
current_user: User = Depends(get_current_active_user)
|
|
||||||
):
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
"item_id": "my_id",
|
|
||||||
"owner": current_user.username
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
@ -1,34 +1,35 @@
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, status
|
from fastapi import APIRouter, Depends, HTTPException, status
|
||||||
from fastapi.security import OAuth2PasswordRequestForm
|
from fastapi.security import OAuth2PasswordRequestForm
|
||||||
from langflow.models.token import Token
|
from langflow.database.models.token import Token
|
||||||
from langflow.auth.auth import (
|
from langflow.auth.auth import (
|
||||||
ACCESS_TOKEN_EXPIRE_MINUTES,
|
ACCESS_TOKEN_EXPIRE_MINUTES,
|
||||||
authenticate_user,
|
authenticate_user,
|
||||||
create_access_token,
|
create_access_token,
|
||||||
)
|
)
|
||||||
|
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from langflow.database.base import get_session
|
from langflow.database.base import get_session
|
||||||
|
|
||||||
TOKEN_TYPE = "bearer"
|
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
def create_user_token(user: str) -> dict:
|
def create_user_token(user: str) -> dict:
|
||||||
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
|
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||||
access_token = create_access_token(
|
access_token = create_access_token(
|
||||||
data={"sub": user.username}, expires_delta=access_token_expires
|
data={"sub": user.username}, expires_delta=access_token_expires # type: ignore
|
||||||
)
|
)
|
||||||
return {"access_token": access_token, "token_type": TOKEN_TYPE}
|
|
||||||
|
return {"access_token": access_token, "token_type": "bearer"}
|
||||||
|
|
||||||
|
|
||||||
@router.post("/token", response_model=Token)
|
@router.post("/token", response_model=Token)
|
||||||
async def login_for_access_token(
|
async def login_to_get_access_token(
|
||||||
form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_session)
|
form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_session)
|
||||||
):
|
):
|
||||||
if user := authenticate_user(db, form_data.username, form_data.password):
|
if user := authenticate_user(db, form_data.username, form_data.password):
|
||||||
return create_user_token(user)
|
return create_user_token(user) # type: ignore
|
||||||
else:
|
else:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,79 @@
|
||||||
from fastapi import APIRouter, Depends
|
from typing import List
|
||||||
from langflow.models.user import User
|
from sqlmodel import Session, select
|
||||||
|
from sqlalchemy.exc import IntegrityError
|
||||||
|
from fastapi import APIRouter, Depends, HTTPException
|
||||||
|
|
||||||
|
from langflow.database.base import get_session
|
||||||
from langflow.auth.auth import get_current_active_user
|
from langflow.auth.auth import get_current_active_user
|
||||||
|
from langflow.database.models.user import UserAddModel, UserListModel, User
|
||||||
|
|
||||||
router = APIRouter()
|
from passlib.context import CryptContext
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/users", tags=["Users"])
|
||||||
|
|
||||||
|
|
||||||
@router.get("/users/me/", response_model=User)
|
def get_password_hash(password):
|
||||||
async def read_users_me(current_user: User = Depends(get_current_active_user)):
|
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||||
|
return pwd_context.hash(password)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/user", response_model=UserListModel)
|
||||||
|
async def read_current_user(current_user: User = Depends(get_current_active_user)):
|
||||||
return current_user
|
return current_user
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/users", response_model=List[UserListModel])
|
||||||
|
async def read_all_users(
|
||||||
|
skip: int = 0,
|
||||||
|
limit: int = 10,
|
||||||
|
_: Session = Depends(get_current_active_user),
|
||||||
|
db: Session = Depends(get_session),
|
||||||
|
):
|
||||||
|
query = select(User)
|
||||||
|
query = query.offset(skip).limit(limit)
|
||||||
|
|
||||||
|
return db.execute(query).fetchall()
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/user", response_model=User)
|
||||||
|
async def add_user(
|
||||||
|
user: UserAddModel,
|
||||||
|
_: Session = Depends(get_current_active_user),
|
||||||
|
db: Session = Depends(get_session),
|
||||||
|
):
|
||||||
|
new_user = User(**user.dict())
|
||||||
|
try:
|
||||||
|
new_user.password = get_password_hash(user.password)
|
||||||
|
|
||||||
|
db.add(new_user)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(new_user)
|
||||||
|
except IntegrityError as e:
|
||||||
|
db.rollback()
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail="User exists",
|
||||||
|
) from e
|
||||||
|
|
||||||
|
return new_user
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Remove - Just for testing purposes
|
||||||
|
@router.post("/super_user", response_model=User)
|
||||||
|
async def add_super_user_to_testing_purposes(db: Session = Depends(get_session)):
|
||||||
|
new_user = User(username="superuser", password="12345", is_superuser=True)
|
||||||
|
|
||||||
|
try:
|
||||||
|
new_user.password = get_password_hash(new_user.password)
|
||||||
|
|
||||||
|
db.add(new_user)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(new_user)
|
||||||
|
except IntegrityError as e:
|
||||||
|
db.rollback()
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail="User exists",
|
||||||
|
) from e
|
||||||
|
|
||||||
|
return new_user
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue