[Multi-User Part 4]: Authenticate using API Tokens (#513)

###  New
- Use API keys to authenticate from Desktop, Obsidian, Emacs clients
- Create API, UI on web app config page to CRUD API Keys
- Create user API keys table and functions to CRUD them in Database

### 🧪 Improve
- Default to better search model, [gte-small](https://huggingface.co/thenlper/gte-small), to improve search quality
- Only load chat model to GPU if enough space, throw error on load failure
- Show encoding progress, truncate headings to max chars supported
- Add instruction to create db in Django DB setup Readme

### ⚙️ Fix
- Fix error handling when configure offline chat via Web UI
- Do not warn in anon mode about Google OAuth env vars not being set
- Fix path to load static files when server started from project root
This commit is contained in:
Debanjum
2023-10-26 12:33:03 -07:00
committed by GitHub
parent 4b6ec248a6
commit 9acc722f7f
36 changed files with 692 additions and 564 deletions

View File

@@ -28,6 +28,7 @@ from khoj.utils import state, fs_syncer
from khoj.routers.indexer import configure_content
from khoj.processor.org_mode.org_to_jsonl import OrgToJsonl
from database.models import (
KhojApiUser,
LocalOrgConfig,
LocalMarkdownConfig,
LocalPlaintextConfig,
@@ -76,13 +77,26 @@ def default_user2():
if KhojUser.objects.filter(username="default").exists():
return KhojUser.objects.get(username="default")
return UserFactory(
return KhojUser.objects.create(
username="default",
email="default@example.com",
password="default",
)
@pytest.mark.django_db
@pytest.fixture
def api_user(default_user):
if KhojApiUser.objects.filter(user=default_user).exists():
return KhojApiUser.objects.get(user=default_user)
return KhojApiUser.objects.create(
user=default_user,
name="api-key",
token="kk-secret",
)
@pytest.fixture(scope="session")
def search_models(search_config: SearchConfig):
search_models = SearchModels()
@@ -176,7 +190,7 @@ def chat_client(search_config: SearchConfig, default_user2: KhojUser):
if os.getenv("OPENAI_API_KEY"):
OpenAIProcessorConversationConfigFactory(user=default_user2)
state.anonymous_mode = True
state.anonymous_mode = False
app = FastAPI()
@@ -219,7 +233,7 @@ def fastapi_app():
def client(
content_config: ContentConfig,
search_config: SearchConfig,
default_user: KhojUser,
api_user: KhojApiUser,
):
state.config.content_type = content_config
state.config.search_type = search_config
@@ -231,7 +245,7 @@ def client(
OrgToJsonl,
get_sample_data("org"),
regenerate=False,
user=default_user,
user=api_user.user,
)
state.content_index.image = image_search.setup(
content_config.image, state.search_models.image_search, regenerate=False
@@ -240,11 +254,11 @@ def client(
PlaintextToJsonl,
get_sample_data("plaintext"),
regenerate=False,
user=default_user,
user=api_user.user,
)
ConversationProcessorConfigFactory(user=default_user)
state.anonymous_mode = True
ConversationProcessorConfigFactory(user=api_user.user)
state.anonymous_mode = False
configure_routes(app)
configure_middleware(app)
@@ -253,13 +267,8 @@ def client(
@pytest.fixture(scope="function")
def client_offline_chat(
search_config: SearchConfig,
content_config: ContentConfig,
default_user2: KhojUser,
):
def client_offline_chat(search_config: SearchConfig, default_user2: KhojUser):
# Initialize app state
state.config.content_type = md_content_config
state.config.search_type = search_config
state.SearchType = configure_search_types(state.config)
@@ -269,9 +278,6 @@ def client_offline_chat(
user=default_user2,
)
# Index Markdown Content for Search
state.search_models.image_search = image_search.initialize_model(search_config.image)
all_files = fs_syncer.collect_files(user=default_user2)
configure_content(
state.content_index, state.config.content_type, all_files, state.search_models, user=default_user2
@@ -283,6 +289,8 @@ def client_offline_chat(
state.anonymous_mode = True
app = FastAPI()
configure_routes(app)
configure_middleware(app)
app.mount("/static", StaticFiles(directory=web_directory), name="static")