🔄 chore(router.py): add users_router and api_key_router to the APIRouter to include the new routes for users and api keys
🔄 chore(__init__.py): add users_router and api_key_router to the __all__ list to include the new routes for users and api keys 🆕 feat(api_key.py): add new routes for retrieving, creating, and deleting API keys 🆕 feat(login.py): add new routes for user login, auto login, and token refresh 🆕 feat(schemas.py): add new schemas for API key response and users response 🆕 feat(users.py): add new routes for adding, reading, updating, and deleting users 🗑️ chore(health.py): remove health router as it is no longer needed 🔄 chore(utils.py): update import statements for User model and update_user_last_login_at function
This commit is contained in:
parent
1a51cc0848
commit
d9cbf17b1a
8 changed files with 27 additions and 21 deletions
|
|
@ -6,6 +6,8 @@ from langflow.api.v1 import (
|
|||
validate_router,
|
||||
flows_router,
|
||||
component_router,
|
||||
users_router,
|
||||
api_key_router,
|
||||
)
|
||||
|
||||
router = APIRouter(
|
||||
|
|
@ -16,3 +18,5 @@ router.include_router(endpoints_router)
|
|||
router.include_router(validate_router)
|
||||
router.include_router(component_router)
|
||||
router.include_router(flows_router)
|
||||
router.include_router(users_router)
|
||||
router.include_router(api_key_router)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ from langflow.api.v1.validate import router as validate_router
|
|||
from langflow.api.v1.chat import router as chat_router
|
||||
from langflow.api.v1.flows import router as flows_router
|
||||
from langflow.api.v1.components import router as component_router
|
||||
from langflow.api.v1.users import router as users_router
|
||||
from langflow.api.v1.api_key import router as api_key_router
|
||||
|
||||
__all__ = [
|
||||
"chat_router",
|
||||
|
|
@ -10,4 +12,6 @@ __all__ = [
|
|||
"component_router",
|
||||
"validate_router",
|
||||
"flows_router",
|
||||
"users_router",
|
||||
"api_key_router",
|
||||
]
|
||||
|
|
|
|||
49
src/backend/langflow/api/v1/api_key.py
Normal file
49
src/backend/langflow/api/v1/api_key.py
Normal file
|
|
@ -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",
|
||||
"api_key": "lf-...abcd",
|
||||
"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",
|
||||
"api_key": "lf-...abcd",
|
||||
"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",
|
||||
"api_key": "lf-...abcd",
|
||||
"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"}
|
||||
60
src/backend/langflow/api/v1/login.py
Normal file
60
src/backend/langflow/api/v1/login.py
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
from sqlalchemy.orm import Session
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from fastapi.security import OAuth2PasswordRequestForm
|
||||
|
||||
from langflow.services.utils import get_session
|
||||
from langflow.services.database.models import Token
|
||||
from langflow.services.auth.utils import (
|
||||
authenticate_user,
|
||||
create_user_tokens,
|
||||
create_refresh_token,
|
||||
create_user_longterm_token,
|
||||
)
|
||||
|
||||
from langflow.services.utils import get_settings_manager
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/login", response_model=Token)
|
||||
async def login_to_get_access_token(
|
||||
form_data: OAuth2PasswordRequestForm = Depends(),
|
||||
db: Session = Depends(get_session),
|
||||
# _: Session = Depends(get_current_active_user)
|
||||
):
|
||||
if user := authenticate_user(form_data.username, form_data.password, db):
|
||||
return create_user_tokens(user_id=user.id, db=db, update_last_login=True)
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Incorrect username or password",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
|
||||
@router.get("/auto_login")
|
||||
async def auto_login(db: Session = Depends(get_session)):
|
||||
settings_manager = get_settings_manager()
|
||||
|
||||
if settings_manager.auth_settings.AUTO_LOGIN:
|
||||
return create_user_longterm_token(db)
|
||||
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail={
|
||||
"message": "Auto login is disabled. Please enable it in the settings",
|
||||
"auto_login": False,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@router.post("/refresh")
|
||||
async def refresh_token(token: str):
|
||||
if token:
|
||||
return create_refresh_token(token)
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid refresh token",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
|
@ -3,6 +3,7 @@ from pathlib import Path
|
|||
from typing import Any, Dict, List, Optional, Union
|
||||
from langflow.services.database.models.api_key import ApiKeyRead
|
||||
from langflow.services.database.models.flow import FlowCreate, FlowRead
|
||||
from langflow.services.database.models.user import UserRead
|
||||
from pydantic import BaseModel, Field, validator
|
||||
import json
|
||||
|
||||
|
|
@ -141,3 +142,8 @@ class ApiKeyResponse(BaseModel):
|
|||
total_count: int
|
||||
user_id: str
|
||||
api_keys: List[ApiKeyRead]
|
||||
|
||||
|
||||
class UsersResponse(BaseModel):
|
||||
total_count: int
|
||||
users: List[UserRead]
|
||||
|
|
|
|||
134
src/backend/langflow/api/v1/users.py
Normal file
134
src/backend/langflow/api/v1/users.py
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
from uuid import UUID
|
||||
from langflow.api.v1.schemas import UsersResponse
|
||||
from langflow.services.database.models.user import (
|
||||
User,
|
||||
UserCreate,
|
||||
UserRead,
|
||||
UserUpdate,
|
||||
)
|
||||
|
||||
from sqlalchemy import func
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
|
||||
from sqlmodel import Session, select
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
|
||||
from langflow.services.utils import get_session
|
||||
from langflow.services.auth.utils import get_current_active_user, get_password_hash
|
||||
from langflow.services.database.models.user.utils import (
|
||||
update_user,
|
||||
)
|
||||
|
||||
router = APIRouter(tags=["Login"])
|
||||
|
||||
|
||||
@router.post("/user", response_model=UserRead)
|
||||
def add_user(
|
||||
user: UserCreate,
|
||||
db: Session = Depends(get_session),
|
||||
) -> User:
|
||||
"""
|
||||
Add a new user to the database.
|
||||
"""
|
||||
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
|
||||
|
||||
|
||||
@router.get("/user", response_model=UserRead)
|
||||
def read_current_user(current_user: User = Depends(get_current_active_user)) -> User:
|
||||
"""
|
||||
Retrieve the current user's data.
|
||||
"""
|
||||
return current_user
|
||||
|
||||
|
||||
@router.get("/users", response_model=UsersResponse)
|
||||
def read_all_users(
|
||||
skip: int = 0,
|
||||
limit: int = 10,
|
||||
_: Session = Depends(get_current_active_user),
|
||||
db: Session = Depends(get_session),
|
||||
) -> UsersResponse:
|
||||
"""
|
||||
Retrieve a list of users from the database with pagination.
|
||||
"""
|
||||
query = select(User).offset(skip).limit(limit)
|
||||
users = db.execute(query).fetchall()
|
||||
|
||||
count_query = select(func.count()).select_from(User) # type: ignore
|
||||
total_count = db.execute(count_query).scalar()
|
||||
|
||||
return UsersResponse(
|
||||
total_count=total_count, # type: ignore
|
||||
users=[UserRead(**dict(user.User)) for user in users],
|
||||
)
|
||||
|
||||
|
||||
@router.patch("/user/{user_id}", response_model=UserRead)
|
||||
def patch_user(
|
||||
user_id: UUID,
|
||||
user: UserUpdate,
|
||||
_: Session = Depends(get_current_active_user),
|
||||
db: Session = Depends(get_session),
|
||||
) -> User:
|
||||
"""
|
||||
Update an existing user's data.
|
||||
"""
|
||||
return update_user(user_id, user, db)
|
||||
|
||||
|
||||
@router.delete("/user/{user_id}")
|
||||
def delete_user(
|
||||
user_id: UUID,
|
||||
_: Session = Depends(get_current_active_user),
|
||||
db: Session = Depends(get_session),
|
||||
) -> dict:
|
||||
"""
|
||||
Delete a user from the database.
|
||||
"""
|
||||
user_db = db.query(User).filter(User.id == user_id).first()
|
||||
if not user_db:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
db.delete(user_db)
|
||||
db.commit()
|
||||
|
||||
return {"detail": "User deleted"}
|
||||
|
||||
|
||||
# TODO: REMOVE - Just for testing purposes
|
||||
@router.post("/super_user", response_model=User)
|
||||
def add_super_user_for_testing_purposes_delete_me_before_merge_into_dev(
|
||||
db: Session = Depends(get_session),
|
||||
) -> User:
|
||||
"""
|
||||
Add a superuser for testing purposes.
|
||||
(This should be removed in production)
|
||||
"""
|
||||
new_user = User(
|
||||
username="superuser",
|
||||
password=get_password_hash("12345"),
|
||||
is_active=True,
|
||||
is_superuser=True,
|
||||
last_login_at=None,
|
||||
)
|
||||
|
||||
try:
|
||||
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
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
from fastapi import APIRouter
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/health")
|
||||
def get_health():
|
||||
return {"status": "OK"}
|
||||
|
|
@ -4,8 +4,8 @@ from jose import JWTError, jwt
|
|||
from typing import Annotated
|
||||
from uuid import UUID
|
||||
from langflow.services.auth.service import AuthManager
|
||||
from langflow.services.database.models.user import (
|
||||
User,
|
||||
from langflow.services.database.models.user.user import User
|
||||
from langflow.services.database.models.user.utils import (
|
||||
get_user_by_id,
|
||||
get_user_by_username,
|
||||
update_user_last_login_at,
|
||||
|
|
|
|||
Loading…
Reference in a new issue