merge authentication
This commit is contained in:
commit
2aa33bb08b
23 changed files with 632 additions and 419 deletions
|
|
@ -1,3 +1,4 @@
|
|||
from uuid import UUID
|
||||
from typing import Annotated
|
||||
from jose import JWTError, jwt
|
||||
from sqlalchemy.orm import Session
|
||||
|
|
@ -7,8 +8,12 @@ from fastapi import Depends, HTTPException, status
|
|||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
from langflow.services.utils import get_session
|
||||
from langflow.database.models.token import TokenData
|
||||
from langflow.database.models.user import User, get_user_by_username
|
||||
from langflow.database.models.user import (
|
||||
User,
|
||||
get_user_by_id,
|
||||
get_user_by_username,
|
||||
update_user_last_login_at,
|
||||
)
|
||||
|
||||
|
||||
# TODO: Move to env - JUST FOR TEST!!!!!
|
||||
|
|
@ -21,6 +26,38 @@ pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
|||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="login")
|
||||
|
||||
|
||||
async def get_current_user(
|
||||
token: Annotated[str, Depends(oauth2_scheme)], db: Session = Depends(get_session)
|
||||
) -> User:
|
||||
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])
|
||||
user_id: UUID = payload.get("sub") # type: ignore
|
||||
token_type: str = payload.get("type") # type: ignore
|
||||
|
||||
if user_id is None or token_type:
|
||||
raise credentials_exception
|
||||
except JWTError as e:
|
||||
raise credentials_exception from e
|
||||
|
||||
user = get_user_by_id(db, user_id) # type: ignore
|
||||
if user is None:
|
||||
raise credentials_exception
|
||||
return user
|
||||
|
||||
|
||||
async def get_current_active_user(
|
||||
current_user: Annotated[User, Depends(get_current_user)]
|
||||
):
|
||||
if not current_user.is_active:
|
||||
raise HTTPException(status_code=400, detail="Inactive user")
|
||||
return current_user
|
||||
|
||||
|
||||
def verify_password(plain_password, hashed_password):
|
||||
return pwd_context.verify(plain_password, hashed_password)
|
||||
|
||||
|
|
@ -38,19 +75,22 @@ def create_token(data: dict, expires_delta: timedelta):
|
|||
return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
|
||||
|
||||
|
||||
def create_user_tokens(username: str) -> dict:
|
||||
def create_user_tokens(user_id: UUID, db: Session = Depends(get_session)) -> dict:
|
||||
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
access_token = create_token(
|
||||
data={"sub": username},
|
||||
data={"sub": str(user_id)},
|
||||
expires_delta=access_token_expires,
|
||||
)
|
||||
|
||||
refresh_token_expires = timedelta(minutes=REFRESH_TOKEN_EXPIRE_MINUTES)
|
||||
refresh_token = create_token(
|
||||
data={"sub": username, "type": "rf"},
|
||||
data={"sub": str(user_id), "type": "rf"},
|
||||
expires_delta=refresh_token_expires,
|
||||
)
|
||||
|
||||
# Update: last_login_at
|
||||
update_user_last_login_at(user_id, db)
|
||||
|
||||
return {
|
||||
"access_token": access_token,
|
||||
"refresh_token": refresh_token,
|
||||
|
|
@ -61,15 +101,15 @@ def create_user_tokens(username: str) -> dict:
|
|||
def create_refresh_token(refresh_token: str):
|
||||
try:
|
||||
payload = jwt.decode(refresh_token, SECRET_KEY, algorithms=[ALGORITHM])
|
||||
username: str = payload.get("sub") # type: ignore
|
||||
user_id: UUID = payload.get("sub") # type: ignore
|
||||
token_type: str = payload.get("type") # type: ignore
|
||||
|
||||
if username is None or token_type is None:
|
||||
if user_id is None or token_type is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid refresh token"
|
||||
)
|
||||
|
||||
return create_user_tokens(username)
|
||||
return create_user_tokens(user_id)
|
||||
|
||||
except JWTError as e:
|
||||
raise HTTPException(
|
||||
|
|
@ -78,41 +118,10 @@ def create_refresh_token(refresh_token: str):
|
|||
) from e
|
||||
|
||||
|
||||
def authenticate_user(db: Session, username: str, password: str):
|
||||
def authenticate_user(
|
||||
username: str, password: str, db: Session = Depends(get_session)
|
||||
) -> User | None:
|
||||
if user := get_user_by_username(db, username):
|
||||
return user if verify_password(password, user.password) else False
|
||||
return user if verify_password(password, user.password) else None
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
async def get_current_user(
|
||||
token: Annotated[str, Depends(oauth2_scheme)], db: Session = Depends(get_session)
|
||||
) -> User:
|
||||
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") # type: ignore
|
||||
token_type: str = payload.get("type") # type: ignore
|
||||
|
||||
if username is None or token_type:
|
||||
raise credentials_exception
|
||||
token_data = TokenData(username=username)
|
||||
except JWTError as e:
|
||||
raise credentials_exception from e
|
||||
|
||||
user = get_user_by_username(db, token_data.username) # type: ignore
|
||||
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.is_disabled:
|
||||
raise HTTPException(status_code=400, detail="Inactive user")
|
||||
return current_user
|
||||
return None
|
||||
|
|
|
|||
56
src/backend/langflow/components/toolkits/Metaphor.py
Normal file
56
src/backend/langflow/components/toolkits/Metaphor.py
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
from typing import List, Union
|
||||
from langflow import CustomComponent
|
||||
|
||||
from metaphor_python import Metaphor # type: ignore
|
||||
from langchain.tools import Tool
|
||||
from langchain.agents import tool
|
||||
from langchain.agents.agent_toolkits.base import BaseToolkit
|
||||
|
||||
|
||||
class MetaphorToolkit(CustomComponent):
|
||||
display_name: str = "Metaphor"
|
||||
description: str = "Metaphor Toolkit"
|
||||
documentation = (
|
||||
"https://python.langchain.com/docs/integrations/tools/metaphor_search"
|
||||
)
|
||||
beta = True
|
||||
# api key should be password = True
|
||||
field_config = {
|
||||
"metaphor_api_key": {"display_name": "Metaphor API Key", "password": True},
|
||||
"code": {"advanced": True},
|
||||
}
|
||||
|
||||
def build(
|
||||
self,
|
||||
metaphor_api_key: str,
|
||||
use_autoprompt: bool = True,
|
||||
search_num_results: int = 5,
|
||||
similar_num_results: int = 5,
|
||||
) -> Union[Tool, BaseToolkit]:
|
||||
# If documents, then we need to create a Vectara instance using .from_documents
|
||||
client = Metaphor(api_key=metaphor_api_key)
|
||||
|
||||
@tool
|
||||
def search(query: str):
|
||||
"""Call search engine with a query."""
|
||||
return client.search(
|
||||
query, use_autoprompt=use_autoprompt, num_results=search_num_results
|
||||
)
|
||||
|
||||
@tool
|
||||
def get_contents(ids: List[str]):
|
||||
"""Get contents of a webpage.
|
||||
|
||||
The ids passed in should be a list of ids as fetched from `search`.
|
||||
"""
|
||||
return client.get_contents(ids)
|
||||
|
||||
@tool
|
||||
def find_similar(url: str):
|
||||
"""Get search results similar to a given URL.
|
||||
|
||||
The url passed in should be a URL returned from `search`
|
||||
"""
|
||||
return client.find_similar(url, num_results=similar_num_results)
|
||||
|
||||
return [search, get_contents, find_similar] # type: ignore
|
||||
0
src/backend/langflow/components/toolkits/__init__.py
Normal file
0
src/backend/langflow/components/toolkits/__init__.py
Normal file
50
src/backend/langflow/components/vectorstores/Vectara.py
Normal file
50
src/backend/langflow/components/vectorstores/Vectara.py
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
from typing import Optional, Union
|
||||
from langflow import CustomComponent
|
||||
|
||||
from langchain.vectorstores import Vectara
|
||||
from langchain.schema import Document
|
||||
from langchain.vectorstores.base import VectorStore
|
||||
from langchain.schema import BaseRetriever
|
||||
from langchain.embeddings.base import Embeddings
|
||||
|
||||
|
||||
class VectaraComponent(CustomComponent):
|
||||
display_name: str = "Vectara"
|
||||
description: str = "Implementation of Vector Store using Vectara"
|
||||
documentation = (
|
||||
"https://python.langchain.com/docs/integrations/vectorstores/vectara"
|
||||
)
|
||||
beta = True
|
||||
# api key should be password = True
|
||||
field_config = {
|
||||
"vectara_customer_id": {"display_name": "Vectara Customer ID"},
|
||||
"vectara_corpus_id": {"display_name": "Vectara Corpus ID"},
|
||||
"vectara_api_key": {"display_name": "Vectara API Key", "password": True},
|
||||
"code": {"show": False},
|
||||
"documents": {"display_name": "Documents"},
|
||||
"embedding": {"display_name": "Embedding"},
|
||||
}
|
||||
|
||||
def build(
|
||||
self,
|
||||
vectara_customer_id: str,
|
||||
vectara_corpus_id: str,
|
||||
vectara_api_key: str,
|
||||
embedding: Optional[Embeddings] = None,
|
||||
documents: Optional[Document] = None,
|
||||
) -> Union[VectorStore, BaseRetriever]:
|
||||
# If documents, then we need to create a Vectara instance using .from_documents
|
||||
if documents is not None and embedding is not None:
|
||||
return Vectara.from_documents(
|
||||
documents=documents, # type: ignore
|
||||
vectara_customer_id=vectara_customer_id,
|
||||
vectara_corpus_id=vectara_corpus_id,
|
||||
vectara_api_key=vectara_api_key,
|
||||
embedding=embedding,
|
||||
)
|
||||
|
||||
return Vectara(
|
||||
vectara_customer_id=vectara_customer_id,
|
||||
vectara_corpus_id=vectara_corpus_id,
|
||||
vectara_api_key=vectara_api_key,
|
||||
)
|
||||
0
src/backend/langflow/components/vectorstores/__init__.py
Normal file
0
src/backend/langflow/components/vectorstores/__init__.py
Normal file
|
|
@ -5,7 +5,3 @@ class Token(BaseModel):
|
|||
access_token: str
|
||||
refresh_token: str
|
||||
token_type: str
|
||||
|
||||
|
||||
class TokenData(BaseModel):
|
||||
username: str | None = None
|
||||
|
|
|
|||
|
|
@ -1,41 +1,52 @@
|
|||
from datetime import datetime
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from langflow.services.database.models.base import SQLModelSerializable, SQLModel
|
||||
from sqlmodel import Field
|
||||
from uuid import UUID, uuid4
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional, List
|
||||
from sqlalchemy.orm import Session
|
||||
from datetime import timezone, datetime
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from fastapi import HTTPException, Depends
|
||||
|
||||
from langflow.services.utils import get_session
|
||||
from langflow.services.database.models.base import SQLModelSerializable, SQLModel
|
||||
|
||||
|
||||
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_active: 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)
|
||||
last_login_at: Optional[datetime] = Field()
|
||||
|
||||
|
||||
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_active: bool = Field()
|
||||
is_superuser: bool = Field()
|
||||
create_at: datetime = Field()
|
||||
updated_at: datetime = Field()
|
||||
last_login_at: Optional[datetime] = Field()
|
||||
|
||||
|
||||
class UserPatchModel(SQLModel):
|
||||
username: str = Field()
|
||||
is_disabled: bool = Field()
|
||||
is_superuser: bool = Field()
|
||||
username: Optional[str] = Field()
|
||||
is_active: Optional[bool] = Field()
|
||||
is_superuser: Optional[bool] = Field()
|
||||
last_login_at: Optional[datetime] = Field()
|
||||
|
||||
|
||||
class UsersResponse(BaseModel):
|
||||
total_count: int
|
||||
users: List[UserListModel]
|
||||
|
||||
|
||||
def get_user_by_username(db: Session, username: str) -> User:
|
||||
|
|
@ -46,3 +57,38 @@ def get_user_by_username(db: Session, username: str) -> User:
|
|||
def get_user_by_id(db: Session, id: UUID) -> User:
|
||||
db_user = db.query(User).filter(User.id == id).first()
|
||||
return User.from_orm(db_user) if db_user else None # type: ignore
|
||||
|
||||
|
||||
def update_user(
|
||||
user_id: UUID, user: UserPatchModel, db: Session = Depends(get_session)
|
||||
) -> User:
|
||||
user_db = get_user_by_username(db, user.username) # type: ignore
|
||||
if user_db and user_db.id != user_id:
|
||||
raise HTTPException(status_code=409, detail="Username already exists")
|
||||
|
||||
user_db = get_user_by_id(db, user_id)
|
||||
if not user_db:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
try:
|
||||
user_data = user.dict(exclude_unset=True)
|
||||
for key, value in user_data.items():
|
||||
setattr(user_db, key, value)
|
||||
|
||||
user_db.updated_at = datetime.now(timezone.utc)
|
||||
user_db = db.merge(user_db)
|
||||
db.commit()
|
||||
if db.identity_key(instance=user_db) is not None:
|
||||
db.refresh(user_db)
|
||||
|
||||
except IntegrityError as e:
|
||||
db.rollback()
|
||||
raise HTTPException(status_code=400, detail=str(e)) from e
|
||||
|
||||
return user_db
|
||||
|
||||
|
||||
def update_user_last_login_at(user_id: UUID, db: Session = Depends(get_session)):
|
||||
user_data = UserPatchModel(last_login_at=datetime.now(timezone.utc)) # type: ignore
|
||||
|
||||
return update_user(user_id, user_data, db)
|
||||
|
|
|
|||
|
|
@ -66,6 +66,9 @@ class Component(BaseModel):
|
|||
elif "beta" in item_name:
|
||||
template_config["beta"] = ast.literal_eval(item_value)
|
||||
|
||||
elif "documentation" in item_name:
|
||||
template_config["documentation"] = ast.literal_eval(item_value)
|
||||
|
||||
return template_config
|
||||
|
||||
def build(self, *args: Any, **kwargs: Any) -> Any:
|
||||
|
|
|
|||
|
|
@ -50,7 +50,9 @@ class CustomComponent(Component, extra=Extra.allow):
|
|||
reader = DirectoryReader("", False)
|
||||
|
||||
for type_hint in TYPE_HINT_LIST:
|
||||
if reader.is_type_hint_used_but_not_imported(type_hint, code):
|
||||
if reader._is_type_hint_used_in_args(
|
||||
"Optional", code
|
||||
) and not reader._is_type_hint_imported("Optional", code):
|
||||
error_detail = {
|
||||
"error": "Type hint Error",
|
||||
"traceback": f"Type hint '{type_hint}' is used but not imported in the code.",
|
||||
|
|
@ -93,9 +95,9 @@ class CustomComponent(Component, extra=Extra.allow):
|
|||
return build_method["args"]
|
||||
|
||||
@property
|
||||
def get_function_entrypoint_return_type(self) -> str:
|
||||
def get_function_entrypoint_return_type(self) -> List[str]:
|
||||
if not self.code:
|
||||
return ""
|
||||
return []
|
||||
tree = self.get_code_tree(self.code)
|
||||
|
||||
component_classes = [
|
||||
|
|
@ -104,7 +106,7 @@ class CustomComponent(Component, extra=Extra.allow):
|
|||
if self.code_class_base_inheritance in cls["bases"]
|
||||
]
|
||||
if not component_classes:
|
||||
return ""
|
||||
return []
|
||||
|
||||
# Assume the first Component class is the one we're interested in
|
||||
component_class = component_classes[0]
|
||||
|
|
@ -115,11 +117,21 @@ class CustomComponent(Component, extra=Extra.allow):
|
|||
]
|
||||
|
||||
if not build_methods:
|
||||
return ""
|
||||
return []
|
||||
|
||||
build_method = build_methods[0]
|
||||
return_type = build_method["return_type"]
|
||||
if not return_type:
|
||||
return []
|
||||
# If the return type is not a Union, then we just return it as a list
|
||||
if "Union" not in return_type:
|
||||
return [return_type] if return_type in self.return_type_valid_list else []
|
||||
|
||||
return build_method["return_type"]
|
||||
# If the return type is a Union, then we need to parse it
|
||||
return_type = return_type.replace("Union", "").replace("[", "").replace("]", "")
|
||||
return_type = return_type.split(",")
|
||||
return_type = [item.strip() for item in return_type]
|
||||
return [item for item in return_type if item in self.return_type_valid_list]
|
||||
|
||||
@property
|
||||
def get_main_class_name(self):
|
||||
|
|
|
|||
|
|
@ -152,15 +152,19 @@ class DirectoryReader:
|
|||
Check if a specific type hint is used in the
|
||||
function definitions within the given code.
|
||||
"""
|
||||
module = ast.parse(code)
|
||||
try:
|
||||
module = ast.parse(code)
|
||||
|
||||
for node in ast.walk(module):
|
||||
if isinstance(node, ast.FunctionDef):
|
||||
for arg in node.args.args:
|
||||
if self._is_type_hint_in_arg_annotation(
|
||||
arg.annotation, type_hint_name
|
||||
):
|
||||
return True
|
||||
for node in ast.walk(module):
|
||||
if isinstance(node, ast.FunctionDef):
|
||||
for arg in node.args.args:
|
||||
if self._is_type_hint_in_arg_annotation(
|
||||
arg.annotation, type_hint_name
|
||||
):
|
||||
return True
|
||||
except SyntaxError:
|
||||
# Returns False if the code is not valid Python
|
||||
return False
|
||||
return False
|
||||
|
||||
def _is_type_hint_in_arg_annotation(self, annotation, type_hint_name: str) -> bool:
|
||||
|
|
@ -204,8 +208,13 @@ class DirectoryReader:
|
|||
return False, "Syntax error"
|
||||
elif not self.validate_build(file_content):
|
||||
return False, "Missing build function"
|
||||
elif self.is_type_hint_used_but_not_imported("Optional", file_content):
|
||||
return False, "Type hint 'Optional' is used but not imported in the code."
|
||||
elif self._is_type_hint_used_in_args(
|
||||
"Optional", file_content
|
||||
) and not self._is_type_hint_imported("Optional", file_content):
|
||||
return (
|
||||
False,
|
||||
"Type hint 'Optional' is used but not imported in the code.",
|
||||
)
|
||||
else:
|
||||
if self.compress_code_field:
|
||||
file_content = str(StringCompressor(file_content).compress_string())
|
||||
|
|
|
|||
|
|
@ -116,9 +116,12 @@ def instantiate_based_on_type(class_object, base_type, node_type, params):
|
|||
|
||||
|
||||
def instantiate_custom_component(node_type, class_object, params):
|
||||
class_object = get_function_custom(params.pop("code"))
|
||||
# we need to make a copy of the params because we will be
|
||||
# modifying it
|
||||
params_copy = params.copy()
|
||||
class_object = get_function_custom(params_copy.pop("code"))
|
||||
custom_component = class_object()
|
||||
built_object = custom_component.build(**params)
|
||||
built_object = custom_component.build(**params_copy)
|
||||
return built_object, {"repr": custom_component.custom_repr()}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -130,8 +130,8 @@ def initialize_pinecone(class_object: Type[Pinecone], params: dict):
|
|||
|
||||
import pinecone # type: ignore
|
||||
|
||||
pinecone_api_key = params.get("pinecone_api_key")
|
||||
pinecone_env = params.get("pinecone_env")
|
||||
pinecone_api_key = params.pop("pinecone_api_key")
|
||||
pinecone_env = params.pop("pinecone_env")
|
||||
|
||||
if pinecone_api_key is None or pinecone_env is None:
|
||||
if os.getenv("PINECONE_API_KEY") is not None:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import ast
|
||||
import contextlib
|
||||
from typing import Any
|
||||
from typing import Any, List
|
||||
from langflow.api.utils import merge_nested_dicts_with_renaming
|
||||
from langflow.interface.agents.base import agent_creator
|
||||
from langflow.interface.chains.base import chain_creator
|
||||
|
|
@ -199,6 +199,9 @@ def update_attributes(frontend_node, template_config):
|
|||
if "beta" in template_config:
|
||||
frontend_node["beta"] = template_config["beta"]
|
||||
|
||||
if "documentation" in template_config:
|
||||
frontend_node["documentation"] = template_config["documentation"]
|
||||
|
||||
|
||||
def build_field_config(custom_component: CustomComponent):
|
||||
"""Build the field configuration for a custom component"""
|
||||
|
|
@ -257,26 +260,27 @@ def get_field_properties(extra_field):
|
|||
return field_name, field_type, field_value, field_required
|
||||
|
||||
|
||||
def add_base_classes(frontend_node, return_type):
|
||||
def add_base_classes(frontend_node, return_types: List[str]):
|
||||
"""Add base classes to the frontend node"""
|
||||
if return_type not in CUSTOM_COMPONENT_SUPPORTED_TYPES or return_type is None:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail={
|
||||
"error": (
|
||||
"Invalid return type should be one of: "
|
||||
f"{list(CUSTOM_COMPONENT_SUPPORTED_TYPES.keys())}"
|
||||
),
|
||||
"traceback": traceback.format_exc(),
|
||||
},
|
||||
)
|
||||
for return_type in return_types:
|
||||
if return_type not in CUSTOM_COMPONENT_SUPPORTED_TYPES or return_type is None:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail={
|
||||
"error": (
|
||||
"Invalid return type should be one of: "
|
||||
f"{list(CUSTOM_COMPONENT_SUPPORTED_TYPES.keys())}"
|
||||
),
|
||||
"traceback": traceback.format_exc(),
|
||||
},
|
||||
)
|
||||
|
||||
return_type_instance = CUSTOM_COMPONENT_SUPPORTED_TYPES.get(return_type)
|
||||
base_classes = get_base_classes(return_type_instance)
|
||||
return_type_instance = CUSTOM_COMPONENT_SUPPORTED_TYPES.get(return_type)
|
||||
base_classes = get_base_classes(return_type_instance)
|
||||
|
||||
for base_class in base_classes:
|
||||
if base_class not in CLASSES_TO_REMOVE:
|
||||
frontend_node.get("base_classes").append(base_class)
|
||||
for base_class in base_classes:
|
||||
if base_class not in CLASSES_TO_REMOVE:
|
||||
frontend_node.get("base_classes").append(base_class)
|
||||
|
||||
|
||||
def build_langchain_template_custom_component(custom_component: CustomComponent):
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ 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.database.models.token import Token
|
||||
from langflow.auth.auth import (
|
||||
|
|
@ -15,10 +16,12 @@ router = APIRouter()
|
|||
|
||||
@router.post("/login", response_model=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),
|
||||
# _: Session = Depends(get_current_active_user)
|
||||
):
|
||||
if user := authenticate_user(db, form_data.username, form_data.password):
|
||||
return create_user_tokens(user.username)
|
||||
if user := authenticate_user(form_data.username, form_data.password, db):
|
||||
return create_user_tokens(user_id=user.id, db=db)
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
from typing import List
|
||||
from uuid import UUID
|
||||
from datetime import timezone, datetime
|
||||
|
||||
from sqlalchemy import func
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
|
||||
from sqlmodel import Session, select
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
|
||||
from langflow.services.utils import get_session
|
||||
from langflow.auth.auth import get_current_active_user, get_password_hash
|
||||
|
|
@ -13,8 +13,8 @@ from langflow.database.models.user import (
|
|||
UserAddModel,
|
||||
UserListModel,
|
||||
UserPatchModel,
|
||||
get_user_by_id,
|
||||
get_user_by_username,
|
||||
UsersResponse,
|
||||
update_user,
|
||||
)
|
||||
|
||||
router = APIRouter(tags=["Login"])
|
||||
|
|
@ -23,7 +23,6 @@ router = APIRouter(tags=["Login"])
|
|||
@router.post("/user", response_model=UserListModel)
|
||||
def add_user(
|
||||
user: UserAddModel,
|
||||
_: Session = Depends(get_current_active_user),
|
||||
db: Session = Depends(get_session),
|
||||
) -> User:
|
||||
"""
|
||||
|
|
@ -32,6 +31,7 @@ def add_user(
|
|||
new_user = User(**user.dict())
|
||||
try:
|
||||
new_user.password = get_password_hash(user.password)
|
||||
|
||||
db.add(new_user)
|
||||
db.commit()
|
||||
db.refresh(new_user)
|
||||
|
|
@ -50,24 +50,30 @@ def read_current_user(current_user: User = Depends(get_current_active_user)) ->
|
|||
return current_user
|
||||
|
||||
|
||||
@router.get("/users", response_model=List[UserListModel])
|
||||
@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),
|
||||
) -> List[UserListModel]:
|
||||
) -> UsersResponse:
|
||||
"""
|
||||
Retrieve a list of users from the database with pagination.
|
||||
"""
|
||||
query = select(User).offset(skip).limit(limit)
|
||||
users = db.execute(query).fetchall()
|
||||
|
||||
return [UserListModel(**dict(user.User)) for user in users]
|
||||
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=[UserListModel(**dict(user.User)) for user in users],
|
||||
)
|
||||
|
||||
|
||||
@router.patch("/user/{user_id}", response_model=UserListModel)
|
||||
def update_user(
|
||||
def patch_user(
|
||||
user_id: UUID,
|
||||
user: UserPatchModel,
|
||||
_: Session = Depends(get_current_active_user),
|
||||
|
|
@ -76,29 +82,7 @@ def update_user(
|
|||
"""
|
||||
Update an existing user's data.
|
||||
"""
|
||||
user_db = get_user_by_username(db, user.username)
|
||||
if user_db and user_db.id != user_id:
|
||||
raise HTTPException(status_code=409, detail="Username already exists")
|
||||
|
||||
user_db = get_user_by_id(db, user_id)
|
||||
if not user_db:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
try:
|
||||
user_data = user.dict(exclude_unset=True)
|
||||
for key, value in user_data.items():
|
||||
setattr(user_db, key, value)
|
||||
|
||||
user_db.updated_at = datetime.now(timezone.utc)
|
||||
user_db = db.merge(user_db)
|
||||
db.commit()
|
||||
if db.identity_key(instance=user_db) is not None:
|
||||
db.refresh(user_db)
|
||||
except IntegrityError as e:
|
||||
db.rollback()
|
||||
raise HTTPException(status_code=400, detail=str(e)) from e
|
||||
|
||||
return user_db
|
||||
return update_user(user_id, user, db)
|
||||
|
||||
|
||||
@router.delete("/user/{user_id}")
|
||||
|
|
@ -129,7 +113,13 @@ def add_super_user_for_testing_purposes_delete_me_before_merge_into_dev(
|
|||
Add a superuser for testing purposes.
|
||||
(This should be removed in production)
|
||||
"""
|
||||
new_user = User(username="superuser", password="12345", is_superuser=True)
|
||||
new_user = User(
|
||||
username="superuser",
|
||||
password="12345",
|
||||
is_active=True,
|
||||
is_superuser=True,
|
||||
last_login_at=None,
|
||||
)
|
||||
|
||||
try:
|
||||
new_user.password = get_password_hash(new_user.password)
|
||||
|
|
|
|||
|
|
@ -34,19 +34,24 @@ export default function Header() {
|
|||
return (
|
||||
<div className="header-arrangement">
|
||||
<div className="header-start-display">
|
||||
<Link to="/">
|
||||
<span className="ml-4 text-2xl">⛓️</span>
|
||||
</Link>
|
||||
<Button
|
||||
onClick={() => {
|
||||
logout();
|
||||
navigate("/login");
|
||||
}}
|
||||
variant="outline"
|
||||
className=""
|
||||
>
|
||||
Sign out
|
||||
</Button>
|
||||
{tabId === "" || !tabId ? (
|
||||
<div className="ml-2">
|
||||
<a
|
||||
href="https://www.langflow.org/"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="header-waitlist-link-box"
|
||||
>
|
||||
<span className="pr-1 text-2xl">⛓️</span>
|
||||
<span>Join The Waitlist</span>
|
||||
</a>
|
||||
</div>
|
||||
) : (
|
||||
<Link to="/">
|
||||
<span className="ml-4 text-2xl">⛓️</span>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{flows.findIndex((f) => tabId === f.id) !== -1 && tabId !== "" && (
|
||||
<MenuBar flows={flows} tabId={tabId} />
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -495,6 +495,12 @@
|
|||
.header-github-link-box {
|
||||
@apply inline-flex h-9 items-center justify-center rounded-md border border-input px-3 pr-0 shadow-sm;
|
||||
}
|
||||
.header-waitlist-link-box {
|
||||
@apply inline-flex h-9 items-center justify-center rounded-md border border-input px-2 shadow-sm text-sm font-medium text-muted-foreground ring-offset-background disabled:pointer-events-none disabled:opacity-50 whitespace-nowrap;
|
||||
}
|
||||
.header-waitlist-link-box:hover {
|
||||
@apply hover:bg-accent hover:text-accent-foreground;
|
||||
}
|
||||
.header-github-link {
|
||||
@apply header-github-link-box text-sm font-medium text-muted-foreground ring-offset-background disabled:pointer-events-none disabled:opacity-50;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue