ParisNeo commited on
Commit
6984dd1
·
1 Parent(s): 96fab98

Added servers protection using an API key to restrict access to only authenticated entities.

Browse files
README.md CHANGED
@@ -1025,6 +1025,7 @@ Each server has its own specific configuration options:
1025
  | --max-embed-tokens | 8192 | Maximum embedding token size |
1026
  | --input-file | ./book.txt | Initial input file |
1027
  | --log-level | INFO | Logging level |
 
1028
 
1029
  #### Ollama Server Options
1030
 
@@ -1042,6 +1043,7 @@ Each server has its own specific configuration options:
1042
  | --max-embed-tokens | 8192 | Maximum embedding token size |
1043
  | --input-file | ./book.txt | Initial input file |
1044
  | --log-level | INFO | Logging level |
 
1045
 
1046
  #### OpenAI Server Options
1047
 
@@ -1056,6 +1058,7 @@ Each server has its own specific configuration options:
1056
  | --max-embed-tokens | 8192 | Maximum embedding token size |
1057
  | --input-dir | ./inputs | Input directory for documents |
1058
  | --log-level | INFO | Logging level |
 
1059
 
1060
  #### OpenAI AZURE Server Options
1061
 
@@ -1071,8 +1074,10 @@ Each server has its own specific configuration options:
1071
  | --input-dir | ./inputs | Input directory for documents |
1072
  | --enable-cache | True | Enable response cache |
1073
  | --log-level | INFO | Logging level |
 
1074
 
1075
 
 
1076
  ### Example Usage
1077
 
1078
  #### LoLLMs RAG Server
 
1025
  | --max-embed-tokens | 8192 | Maximum embedding token size |
1026
  | --input-file | ./book.txt | Initial input file |
1027
  | --log-level | INFO | Logging level |
1028
+ | --key | none | Access Key to protect the lightrag service |
1029
 
1030
  #### Ollama Server Options
1031
 
 
1043
  | --max-embed-tokens | 8192 | Maximum embedding token size |
1044
  | --input-file | ./book.txt | Initial input file |
1045
  | --log-level | INFO | Logging level |
1046
+ | --key | none | Access Key to protect the lightrag service |
1047
 
1048
  #### OpenAI Server Options
1049
 
 
1058
  | --max-embed-tokens | 8192 | Maximum embedding token size |
1059
  | --input-dir | ./inputs | Input directory for documents |
1060
  | --log-level | INFO | Logging level |
1061
+ | --key | none | Access Key to protect the lightrag service |
1062
 
1063
  #### OpenAI AZURE Server Options
1064
 
 
1074
  | --input-dir | ./inputs | Input directory for documents |
1075
  | --enable-cache | True | Enable response cache |
1076
  | --log-level | INFO | Logging level |
1077
+ | --key | none | Access Key to protect the lightrag service |
1078
 
1079
 
1080
+ For protecting the server using an authentication key, you can also use an environment variable named `LIGHTRAG_API_KEY`.
1081
  ### Example Usage
1082
 
1083
  #### LoLLMs RAG Server
lightrag/api/azure_openai_lightrag_server.py CHANGED
@@ -20,6 +20,19 @@ from dotenv import load_dotenv
20
  import inspect
21
  import json
22
  from fastapi.responses import StreamingResponse
 
 
 
 
 
 
 
 
 
 
 
 
 
23
 
24
  load_dotenv()
25
 
@@ -93,6 +106,9 @@ def parse_args():
93
  help="Logging level (default: INFO)",
94
  )
95
 
 
 
 
96
  return parser.parse_args()
97
 
98
 
@@ -154,6 +170,31 @@ class InsertResponse(BaseModel):
154
  message: str
155
  document_count: int
156
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
157
 
158
  async def get_embedding_dim(embedding_model: str) -> int:
159
  """Get embedding dimensions for the specified model"""
@@ -168,12 +209,30 @@ def create_app(args):
168
  format="%(levelname)s:%(message)s", level=getattr(logging, args.log_level)
169
  )
170
 
171
- # Initialize FastAPI app
 
 
 
 
172
  app = FastAPI(
173
  title="LightRAG API",
174
- description="API for querying text using LightRAG with OpenAI integration",
 
 
 
 
 
 
 
 
 
 
 
175
  )
176
 
 
 
 
177
  # Create working directory if it doesn't exist
178
  Path(args.working_dir).mkdir(parents=True, exist_ok=True)
179
 
@@ -239,7 +298,7 @@ def create_app(args):
239
  except Exception as e:
240
  logging.error(f"Error during startup indexing: {str(e)}")
241
 
242
- @app.post("/documents/scan")
243
  async def scan_for_new_documents():
244
  """Manually trigger scanning for new documents"""
245
  try:
@@ -264,7 +323,7 @@ def create_app(args):
264
  except Exception as e:
265
  raise HTTPException(status_code=500, detail=str(e))
266
 
267
- @app.post("/resetcache")
268
  async def reset_cache():
269
  """Manually reset cache"""
270
  try:
@@ -276,7 +335,7 @@ def create_app(args):
276
  except Exception as e:
277
  raise HTTPException(status_code=500, detail=str(e))
278
 
279
- @app.post("/documents/upload")
280
  async def upload_to_input_dir(file: UploadFile = File(...)):
281
  """Upload a file to the input directory"""
282
  try:
@@ -304,7 +363,7 @@ def create_app(args):
304
  except Exception as e:
305
  raise HTTPException(status_code=500, detail=str(e))
306
 
307
- @app.post("/query", response_model=QueryResponse)
308
  async def query_text(request: QueryRequest):
309
  try:
310
  response = await rag.aquery(
@@ -319,7 +378,7 @@ def create_app(args):
319
  except Exception as e:
320
  raise HTTPException(status_code=500, detail=str(e))
321
 
322
- @app.post("/query/stream")
323
  async def query_text_stream(request: QueryRequest):
324
  try:
325
  response = await rag.aquery(
@@ -345,7 +404,7 @@ def create_app(args):
345
  except Exception as e:
346
  raise HTTPException(status_code=500, detail=str(e))
347
 
348
- @app.post("/documents/text", response_model=InsertResponse)
349
  async def insert_text(request: InsertTextRequest):
350
  try:
351
  await rag.ainsert(request.text)
@@ -357,7 +416,7 @@ def create_app(args):
357
  except Exception as e:
358
  raise HTTPException(status_code=500, detail=str(e))
359
 
360
- @app.post("/documents/file", response_model=InsertResponse)
361
  async def insert_file(file: UploadFile = File(...), description: str = Form(None)):
362
  try:
363
  content = await file.read()
@@ -381,7 +440,7 @@ def create_app(args):
381
  except Exception as e:
382
  raise HTTPException(status_code=500, detail=str(e))
383
 
384
- @app.post("/documents/batch", response_model=InsertResponse)
385
  async def insert_batch(files: List[UploadFile] = File(...)):
386
  try:
387
  inserted_count = 0
@@ -411,7 +470,7 @@ def create_app(args):
411
  except Exception as e:
412
  raise HTTPException(status_code=500, detail=str(e))
413
 
414
- @app.delete("/documents", response_model=InsertResponse)
415
  async def clear_documents():
416
  try:
417
  rag.text_chunks = []
@@ -425,7 +484,7 @@ def create_app(args):
425
  except Exception as e:
426
  raise HTTPException(status_code=500, detail=str(e))
427
 
428
- @app.get("/health")
429
  async def get_status():
430
  """Get current system status"""
431
  return {
 
20
  import inspect
21
  import json
22
  from fastapi.responses import StreamingResponse
23
+ from fastapi import FastAPI, HTTPException
24
+ import os
25
+ from typing import Optional
26
+
27
+ from fastapi import FastAPI, Depends, HTTPException, Security
28
+ from fastapi.security import APIKeyHeader
29
+ import os
30
+ import argparse
31
+ from typing import Optional
32
+ from fastapi.middleware.cors import CORSMiddleware
33
+
34
+ from starlette.status import HTTP_403_FORBIDDEN
35
+ from fastapi import HTTPException
36
 
37
  load_dotenv()
38
 
 
106
  help="Logging level (default: INFO)",
107
  )
108
 
109
+ parser.add_argument('--key', type=str, help='API key for authentication. This protects lightrag server against unauthorized access', default=None)
110
+
111
+
112
  return parser.parse_args()
113
 
114
 
 
170
  message: str
171
  document_count: int
172
 
173
+ def get_api_key_dependency(api_key: Optional[str]):
174
+ if not api_key:
175
+ # If no API key is configured, return a dummy dependency that always succeeds
176
+ async def no_auth():
177
+ return None
178
+ return no_auth
179
+
180
+ # If API key is configured, use proper authentication
181
+ api_key_header = APIKeyHeader(name="X-API-Key", auto_error=False)
182
+
183
+ async def api_key_auth(api_key_header_value: str | None = Security(api_key_header)):
184
+ if not api_key_header_value:
185
+ raise HTTPException(
186
+ status_code=HTTP_403_FORBIDDEN,
187
+ detail="API Key required"
188
+ )
189
+ if api_key_header_value != api_key:
190
+ raise HTTPException(
191
+ status_code=HTTP_403_FORBIDDEN,
192
+ detail="Invalid API Key"
193
+ )
194
+ return api_key_header_value
195
+
196
+ return api_key_auth
197
+
198
 
199
  async def get_embedding_dim(embedding_model: str) -> int:
200
  """Get embedding dimensions for the specified model"""
 
209
  format="%(levelname)s:%(message)s", level=getattr(logging, args.log_level)
210
  )
211
 
212
+
213
+ # Check if API key is provided either through env var or args
214
+ api_key = os.getenv("LIGHTRAG_API_KEY") or args.key
215
+
216
+ # Initialize FastAPI
217
  app = FastAPI(
218
  title="LightRAG API",
219
+ description="API for querying text using LightRAG with separate storage and input directories"+"(With authentication)" if api_key else "",
220
+ version="1.0.0",
221
+ openapi_tags=[{"name": "api"}]
222
+ )
223
+
224
+ # Add CORS middleware
225
+ app.add_middleware(
226
+ CORSMiddleware,
227
+ allow_origins=["*"],
228
+ allow_credentials=True,
229
+ allow_methods=["*"],
230
+ allow_headers=["*"],
231
  )
232
 
233
+ # Create the optional API key dependency
234
+ optional_api_key = get_api_key_dependency(api_key)
235
+
236
  # Create working directory if it doesn't exist
237
  Path(args.working_dir).mkdir(parents=True, exist_ok=True)
238
 
 
298
  except Exception as e:
299
  logging.error(f"Error during startup indexing: {str(e)}")
300
 
301
+ @app.post("/documents/scan", dependencies=[Depends(optional_api_key)])
302
  async def scan_for_new_documents():
303
  """Manually trigger scanning for new documents"""
304
  try:
 
323
  except Exception as e:
324
  raise HTTPException(status_code=500, detail=str(e))
325
 
326
+ @app.post("/resetcache", dependencies=[Depends(optional_api_key)])
327
  async def reset_cache():
328
  """Manually reset cache"""
329
  try:
 
335
  except Exception as e:
336
  raise HTTPException(status_code=500, detail=str(e))
337
 
338
+ @app.post("/documents/upload", dependencies=[Depends(optional_api_key)])
339
  async def upload_to_input_dir(file: UploadFile = File(...)):
340
  """Upload a file to the input directory"""
341
  try:
 
363
  except Exception as e:
364
  raise HTTPException(status_code=500, detail=str(e))
365
 
366
+ @app.post("/query", response_model=QueryResponse, dependencies=[Depends(optional_api_key)])
367
  async def query_text(request: QueryRequest):
368
  try:
369
  response = await rag.aquery(
 
378
  except Exception as e:
379
  raise HTTPException(status_code=500, detail=str(e))
380
 
381
+ @app.post("/query/stream", dependencies=[Depends(optional_api_key)])
382
  async def query_text_stream(request: QueryRequest):
383
  try:
384
  response = await rag.aquery(
 
404
  except Exception as e:
405
  raise HTTPException(status_code=500, detail=str(e))
406
 
407
+ @app.post("/documents/text", response_model=InsertResponse, dependencies=[Depends(optional_api_key)])
408
  async def insert_text(request: InsertTextRequest):
409
  try:
410
  await rag.ainsert(request.text)
 
416
  except Exception as e:
417
  raise HTTPException(status_code=500, detail=str(e))
418
 
419
+ @app.post("/documents/file", response_model=InsertResponse, dependencies=[Depends(optional_api_key)])
420
  async def insert_file(file: UploadFile = File(...), description: str = Form(None)):
421
  try:
422
  content = await file.read()
 
440
  except Exception as e:
441
  raise HTTPException(status_code=500, detail=str(e))
442
 
443
+ @app.post("/documents/batch", response_model=InsertResponse, dependencies=[Depends(optional_api_key)])
444
  async def insert_batch(files: List[UploadFile] = File(...)):
445
  try:
446
  inserted_count = 0
 
470
  except Exception as e:
471
  raise HTTPException(status_code=500, detail=str(e))
472
 
473
+ @app.delete("/documents", response_model=InsertResponse, dependencies=[Depends(optional_api_key)])
474
  async def clear_documents():
475
  try:
476
  rag.text_chunks = []
 
484
  except Exception as e:
485
  raise HTTPException(status_code=500, detail=str(e))
486
 
487
+ @app.get("/health", dependencies=[Depends(optional_api_key)])
488
  async def get_status():
489
  """Get current system status"""
490
  return {
lightrag/api/lollms_lightrag_server.py CHANGED
@@ -11,7 +11,19 @@ from pathlib import Path
11
  import shutil
12
  import aiofiles
13
  from ascii_colors import trace_exception
 
 
 
14
 
 
 
 
 
 
 
 
 
 
15
 
16
  def parse_args():
17
  parser = argparse.ArgumentParser(
@@ -86,6 +98,9 @@ def parse_args():
86
  help="Logging level (default: INFO)",
87
  )
88
 
 
 
 
89
  return parser.parse_args()
90
 
91
 
@@ -147,6 +162,31 @@ class InsertResponse(BaseModel):
147
  message: str
148
  document_count: int
149
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
 
151
  def create_app(args):
152
  # Setup logging
@@ -154,11 +194,28 @@ def create_app(args):
154
  format="%(levelname)s:%(message)s", level=getattr(logging, args.log_level)
155
  )
156
 
157
- # Initialize FastAPI app
 
 
 
158
  app = FastAPI(
159
  title="LightRAG API",
160
- description="API for querying text using LightRAG with separate storage and input directories",
 
 
161
  )
 
 
 
 
 
 
 
 
 
 
 
 
162
 
163
  # Create working directory if it doesn't exist
164
  Path(args.working_dir).mkdir(parents=True, exist_ok=True)
@@ -209,7 +266,7 @@ def create_app(args):
209
  except Exception as e:
210
  logging.error(f"Error during startup indexing: {str(e)}")
211
 
212
- @app.post("/documents/scan")
213
  async def scan_for_new_documents():
214
  """Manually trigger scanning for new documents"""
215
  try:
@@ -234,7 +291,7 @@ def create_app(args):
234
  except Exception as e:
235
  raise HTTPException(status_code=500, detail=str(e))
236
 
237
- @app.post("/documents/upload")
238
  async def upload_to_input_dir(file: UploadFile = File(...)):
239
  """Upload a file to the input directory"""
240
  try:
@@ -262,7 +319,7 @@ def create_app(args):
262
  except Exception as e:
263
  raise HTTPException(status_code=500, detail=str(e))
264
 
265
- @app.post("/query", response_model=QueryResponse)
266
  async def query_text(request: QueryRequest):
267
  try:
268
  response = await rag.aquery(
@@ -284,7 +341,7 @@ def create_app(args):
284
  except Exception as e:
285
  raise HTTPException(status_code=500, detail=str(e))
286
 
287
- @app.post("/query/stream")
288
  async def query_text_stream(request: QueryRequest):
289
  try:
290
  response = rag.query(
@@ -304,7 +361,7 @@ def create_app(args):
304
  except Exception as e:
305
  raise HTTPException(status_code=500, detail=str(e))
306
 
307
- @app.post("/documents/text", response_model=InsertResponse)
308
  async def insert_text(request: InsertTextRequest):
309
  try:
310
  rag.insert(request.text)
@@ -316,7 +373,7 @@ def create_app(args):
316
  except Exception as e:
317
  raise HTTPException(status_code=500, detail=str(e))
318
 
319
- @app.post("/documents/file", response_model=InsertResponse)
320
  async def insert_file(file: UploadFile = File(...), description: str = Form(None)):
321
  try:
322
  content = await file.read()
@@ -340,7 +397,7 @@ def create_app(args):
340
  except Exception as e:
341
  raise HTTPException(status_code=500, detail=str(e))
342
 
343
- @app.post("/documents/batch", response_model=InsertResponse)
344
  async def insert_batch(files: List[UploadFile] = File(...)):
345
  try:
346
  inserted_count = 0
@@ -370,7 +427,7 @@ def create_app(args):
370
  except Exception as e:
371
  raise HTTPException(status_code=500, detail=str(e))
372
 
373
- @app.delete("/documents", response_model=InsertResponse)
374
  async def clear_documents():
375
  try:
376
  rag.text_chunks = []
@@ -384,7 +441,7 @@ def create_app(args):
384
  except Exception as e:
385
  raise HTTPException(status_code=500, detail=str(e))
386
 
387
- @app.get("/health")
388
  async def get_status():
389
  """Get current system status"""
390
  return {
 
11
  import shutil
12
  import aiofiles
13
  from ascii_colors import trace_exception
14
+ from fastapi import FastAPI, HTTPException
15
+ import os
16
+ from typing import Optional
17
 
18
+ from fastapi import FastAPI, Depends, HTTPException, Security
19
+ from fastapi.security import APIKeyHeader
20
+ import os
21
+ import argparse
22
+ from typing import Optional
23
+ from fastapi.middleware.cors import CORSMiddleware
24
+
25
+ from starlette.status import HTTP_403_FORBIDDEN
26
+ from fastapi import HTTPException
27
 
28
  def parse_args():
29
  parser = argparse.ArgumentParser(
 
98
  help="Logging level (default: INFO)",
99
  )
100
 
101
+ parser.add_argument('--key', type=str, help='API key for authentication. This protects lightrag server against unauthorized access', default=None)
102
+
103
+
104
  return parser.parse_args()
105
 
106
 
 
162
  message: str
163
  document_count: int
164
 
165
+ def get_api_key_dependency(api_key: Optional[str]):
166
+ if not api_key:
167
+ # If no API key is configured, return a dummy dependency that always succeeds
168
+ async def no_auth():
169
+ return None
170
+ return no_auth
171
+
172
+ # If API key is configured, use proper authentication
173
+ api_key_header = APIKeyHeader(name="X-API-Key", auto_error=False)
174
+
175
+ async def api_key_auth(api_key_header_value: str | None = Security(api_key_header)):
176
+ if not api_key_header_value:
177
+ raise HTTPException(
178
+ status_code=HTTP_403_FORBIDDEN,
179
+ detail="API Key required"
180
+ )
181
+ if api_key_header_value != api_key:
182
+ raise HTTPException(
183
+ status_code=HTTP_403_FORBIDDEN,
184
+ detail="Invalid API Key"
185
+ )
186
+ return api_key_header_value
187
+
188
+ return api_key_auth
189
+
190
 
191
  def create_app(args):
192
  # Setup logging
 
194
  format="%(levelname)s:%(message)s", level=getattr(logging, args.log_level)
195
  )
196
 
197
+ # Check if API key is provided either through env var or args
198
+ api_key = os.getenv("LIGHTRAG_API_KEY") or args.key
199
+
200
+ # Initialize FastAPI
201
  app = FastAPI(
202
  title="LightRAG API",
203
+ description="API for querying text using LightRAG with separate storage and input directories"+"(With authentication)" if api_key else "",
204
+ version="1.0.0",
205
+ openapi_tags=[{"name": "api"}]
206
  )
207
+
208
+ # Add CORS middleware
209
+ app.add_middleware(
210
+ CORSMiddleware,
211
+ allow_origins=["*"],
212
+ allow_credentials=True,
213
+ allow_methods=["*"],
214
+ allow_headers=["*"],
215
+ )
216
+
217
+ # Create the optional API key dependency
218
+ optional_api_key = get_api_key_dependency(api_key)
219
 
220
  # Create working directory if it doesn't exist
221
  Path(args.working_dir).mkdir(parents=True, exist_ok=True)
 
266
  except Exception as e:
267
  logging.error(f"Error during startup indexing: {str(e)}")
268
 
269
+ @app.post("/documents/scan", dependencies=[Depends(optional_api_key)])
270
  async def scan_for_new_documents():
271
  """Manually trigger scanning for new documents"""
272
  try:
 
291
  except Exception as e:
292
  raise HTTPException(status_code=500, detail=str(e))
293
 
294
+ @app.post("/documents/upload", dependencies=[Depends(optional_api_key)])
295
  async def upload_to_input_dir(file: UploadFile = File(...)):
296
  """Upload a file to the input directory"""
297
  try:
 
319
  except Exception as e:
320
  raise HTTPException(status_code=500, detail=str(e))
321
 
322
+ @app.post("/query", response_model=QueryResponse, dependencies=[Depends(optional_api_key)])
323
  async def query_text(request: QueryRequest):
324
  try:
325
  response = await rag.aquery(
 
341
  except Exception as e:
342
  raise HTTPException(status_code=500, detail=str(e))
343
 
344
+ @app.post("/query/stream", dependencies=[Depends(optional_api_key)])
345
  async def query_text_stream(request: QueryRequest):
346
  try:
347
  response = rag.query(
 
361
  except Exception as e:
362
  raise HTTPException(status_code=500, detail=str(e))
363
 
364
+ @app.post("/documents/text", response_model=InsertResponse, dependencies=[Depends(optional_api_key)])
365
  async def insert_text(request: InsertTextRequest):
366
  try:
367
  rag.insert(request.text)
 
373
  except Exception as e:
374
  raise HTTPException(status_code=500, detail=str(e))
375
 
376
+ @app.post("/documents/file", response_model=InsertResponse, dependencies=[Depends(optional_api_key)])
377
  async def insert_file(file: UploadFile = File(...), description: str = Form(None)):
378
  try:
379
  content = await file.read()
 
397
  except Exception as e:
398
  raise HTTPException(status_code=500, detail=str(e))
399
 
400
+ @app.post("/documents/batch", response_model=InsertResponse, dependencies=[Depends(optional_api_key)])
401
  async def insert_batch(files: List[UploadFile] = File(...)):
402
  try:
403
  inserted_count = 0
 
427
  except Exception as e:
428
  raise HTTPException(status_code=500, detail=str(e))
429
 
430
+ @app.delete("/documents", response_model=InsertResponse, dependencies=[Depends(optional_api_key)])
431
  async def clear_documents():
432
  try:
433
  rag.text_chunks = []
 
441
  except Exception as e:
442
  raise HTTPException(status_code=500, detail=str(e))
443
 
444
+ @app.get("/health", dependencies=[Depends(optional_api_key)])
445
  async def get_status():
446
  """Get current system status"""
447
  return {
lightrag/api/ollama_lightrag_server.py CHANGED
@@ -11,6 +11,19 @@ from pathlib import Path
11
  import shutil
12
  import aiofiles
13
  from ascii_colors import trace_exception
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
 
16
  def parse_args():
@@ -85,6 +98,7 @@ def parse_args():
85
  choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
86
  help="Logging level (default: INFO)",
87
  )
 
88
 
89
  return parser.parse_args()
90
 
@@ -147,6 +161,31 @@ class InsertResponse(BaseModel):
147
  message: str
148
  document_count: int
149
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
 
151
  def create_app(args):
152
  # Setup logging
@@ -154,12 +193,30 @@ def create_app(args):
154
  format="%(levelname)s:%(message)s", level=getattr(logging, args.log_level)
155
  )
156
 
157
- # Initialize FastAPI app
 
 
 
158
  app = FastAPI(
159
  title="LightRAG API",
160
- description="API for querying text using LightRAG with separate storage and input directories",
 
 
 
 
 
 
 
 
 
 
 
161
  )
162
 
 
 
 
 
163
  # Create working directory if it doesn't exist
164
  Path(args.working_dir).mkdir(parents=True, exist_ok=True)
165
 
@@ -209,7 +266,7 @@ def create_app(args):
209
  except Exception as e:
210
  logging.error(f"Error during startup indexing: {str(e)}")
211
 
212
- @app.post("/documents/scan")
213
  async def scan_for_new_documents():
214
  """Manually trigger scanning for new documents"""
215
  try:
@@ -234,7 +291,7 @@ def create_app(args):
234
  except Exception as e:
235
  raise HTTPException(status_code=500, detail=str(e))
236
 
237
- @app.post("/documents/upload")
238
  async def upload_to_input_dir(file: UploadFile = File(...)):
239
  """Upload a file to the input directory"""
240
  try:
@@ -262,7 +319,7 @@ def create_app(args):
262
  except Exception as e:
263
  raise HTTPException(status_code=500, detail=str(e))
264
 
265
- @app.post("/query", response_model=QueryResponse)
266
  async def query_text(request: QueryRequest):
267
  try:
268
  response = await rag.aquery(
@@ -284,7 +341,7 @@ def create_app(args):
284
  except Exception as e:
285
  raise HTTPException(status_code=500, detail=str(e))
286
 
287
- @app.post("/query/stream")
288
  async def query_text_stream(request: QueryRequest):
289
  try:
290
  response = rag.query(
@@ -304,7 +361,7 @@ def create_app(args):
304
  except Exception as e:
305
  raise HTTPException(status_code=500, detail=str(e))
306
 
307
- @app.post("/documents/text", response_model=InsertResponse)
308
  async def insert_text(request: InsertTextRequest):
309
  try:
310
  await rag.ainsert(request.text)
@@ -316,7 +373,7 @@ def create_app(args):
316
  except Exception as e:
317
  raise HTTPException(status_code=500, detail=str(e))
318
 
319
- @app.post("/documents/file", response_model=InsertResponse)
320
  async def insert_file(file: UploadFile = File(...), description: str = Form(None)):
321
  try:
322
  content = await file.read()
@@ -340,7 +397,7 @@ def create_app(args):
340
  except Exception as e:
341
  raise HTTPException(status_code=500, detail=str(e))
342
 
343
- @app.post("/documents/batch", response_model=InsertResponse)
344
  async def insert_batch(files: List[UploadFile] = File(...)):
345
  try:
346
  inserted_count = 0
@@ -370,7 +427,7 @@ def create_app(args):
370
  except Exception as e:
371
  raise HTTPException(status_code=500, detail=str(e))
372
 
373
- @app.delete("/documents", response_model=InsertResponse)
374
  async def clear_documents():
375
  try:
376
  rag.text_chunks = []
@@ -384,7 +441,7 @@ def create_app(args):
384
  except Exception as e:
385
  raise HTTPException(status_code=500, detail=str(e))
386
 
387
- @app.get("/health")
388
  async def get_status():
389
  """Get current system status"""
390
  return {
 
11
  import shutil
12
  import aiofiles
13
  from ascii_colors import trace_exception
14
+ from fastapi import FastAPI, HTTPException
15
+ import os
16
+ from typing import Optional
17
+
18
+ from fastapi import FastAPI, Depends, HTTPException, Security
19
+ from fastapi.security import APIKeyHeader
20
+ import os
21
+ import argparse
22
+ from typing import Optional
23
+ from fastapi.middleware.cors import CORSMiddleware
24
+
25
+ from starlette.status import HTTP_403_FORBIDDEN
26
+ from fastapi import HTTPException
27
 
28
 
29
  def parse_args():
 
98
  choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
99
  help="Logging level (default: INFO)",
100
  )
101
+ parser.add_argument('--key', type=str, help='API key for authentication. This protects lightrag server against unauthorized access', default=None)
102
 
103
  return parser.parse_args()
104
 
 
161
  message: str
162
  document_count: int
163
 
164
+ def get_api_key_dependency(api_key: Optional[str]):
165
+ if not api_key:
166
+ # If no API key is configured, return a dummy dependency that always succeeds
167
+ async def no_auth():
168
+ return None
169
+ return no_auth
170
+
171
+ # If API key is configured, use proper authentication
172
+ api_key_header = APIKeyHeader(name="X-API-Key", auto_error=False)
173
+
174
+ async def api_key_auth(api_key_header_value: str | None = Security(api_key_header)):
175
+ if not api_key_header_value:
176
+ raise HTTPException(
177
+ status_code=HTTP_403_FORBIDDEN,
178
+ detail="API Key required"
179
+ )
180
+ if api_key_header_value != api_key:
181
+ raise HTTPException(
182
+ status_code=HTTP_403_FORBIDDEN,
183
+ detail="Invalid API Key"
184
+ )
185
+ return api_key_header_value
186
+
187
+ return api_key_auth
188
+
189
 
190
  def create_app(args):
191
  # Setup logging
 
193
  format="%(levelname)s:%(message)s", level=getattr(logging, args.log_level)
194
  )
195
 
196
+ # Check if API key is provided either through env var or args
197
+ api_key = os.getenv("LIGHTRAG_API_KEY") or args.key
198
+
199
+ # Initialize FastAPI
200
  app = FastAPI(
201
  title="LightRAG API",
202
+ description="API for querying text using LightRAG with separate storage and input directories"+"(With authentication)" if api_key else "",
203
+ version="1.0.0",
204
+ openapi_tags=[{"name": "api"}]
205
+ )
206
+
207
+ # Add CORS middleware
208
+ app.add_middleware(
209
+ CORSMiddleware,
210
+ allow_origins=["*"],
211
+ allow_credentials=True,
212
+ allow_methods=["*"],
213
+ allow_headers=["*"],
214
  )
215
 
216
+ # Create the optional API key dependency
217
+ optional_api_key = get_api_key_dependency(api_key)
218
+
219
+
220
  # Create working directory if it doesn't exist
221
  Path(args.working_dir).mkdir(parents=True, exist_ok=True)
222
 
 
266
  except Exception as e:
267
  logging.error(f"Error during startup indexing: {str(e)}")
268
 
269
+ @app.post("/documents/scan", dependencies=[Depends(optional_api_key)])
270
  async def scan_for_new_documents():
271
  """Manually trigger scanning for new documents"""
272
  try:
 
291
  except Exception as e:
292
  raise HTTPException(status_code=500, detail=str(e))
293
 
294
+ @app.post("/documents/upload", dependencies=[Depends(optional_api_key)])
295
  async def upload_to_input_dir(file: UploadFile = File(...)):
296
  """Upload a file to the input directory"""
297
  try:
 
319
  except Exception as e:
320
  raise HTTPException(status_code=500, detail=str(e))
321
 
322
+ @app.post("/query", response_model=QueryResponse, dependencies=[Depends(optional_api_key)])
323
  async def query_text(request: QueryRequest):
324
  try:
325
  response = await rag.aquery(
 
341
  except Exception as e:
342
  raise HTTPException(status_code=500, detail=str(e))
343
 
344
+ @app.post("/query/stream", dependencies=[Depends(optional_api_key)])
345
  async def query_text_stream(request: QueryRequest):
346
  try:
347
  response = rag.query(
 
361
  except Exception as e:
362
  raise HTTPException(status_code=500, detail=str(e))
363
 
364
+ @app.post("/documents/text", response_model=InsertResponse, dependencies=[Depends(optional_api_key)])
365
  async def insert_text(request: InsertTextRequest):
366
  try:
367
  await rag.ainsert(request.text)
 
373
  except Exception as e:
374
  raise HTTPException(status_code=500, detail=str(e))
375
 
376
+ @app.post("/documents/file", response_model=InsertResponse, dependencies=[Depends(optional_api_key)])
377
  async def insert_file(file: UploadFile = File(...), description: str = Form(None)):
378
  try:
379
  content = await file.read()
 
397
  except Exception as e:
398
  raise HTTPException(status_code=500, detail=str(e))
399
 
400
+ @app.post("/documents/batch", response_model=InsertResponse, dependencies=[Depends(optional_api_key)])
401
  async def insert_batch(files: List[UploadFile] = File(...)):
402
  try:
403
  inserted_count = 0
 
427
  except Exception as e:
428
  raise HTTPException(status_code=500, detail=str(e))
429
 
430
+ @app.delete("/documents", response_model=InsertResponse, dependencies=[Depends(optional_api_key)])
431
  async def clear_documents():
432
  try:
433
  rag.text_chunks = []
 
441
  except Exception as e:
442
  raise HTTPException(status_code=500, detail=str(e))
443
 
444
+ @app.get("/health", dependencies=[Depends(optional_api_key)])
445
  async def get_status():
446
  """Get current system status"""
447
  return {
lightrag/api/openai_lightrag_server.py CHANGED
@@ -14,6 +14,20 @@ import aiofiles
14
  from ascii_colors import trace_exception
15
  import nest_asyncio
16
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  # Apply nest_asyncio to solve event loop issues
18
  nest_asyncio.apply()
19
 
@@ -75,6 +89,9 @@ def parse_args():
75
  help="Logging level (default: INFO)",
76
  )
77
 
 
 
 
78
  return parser.parse_args()
79
 
80
 
@@ -136,6 +153,31 @@ class InsertResponse(BaseModel):
136
  message: str
137
  document_count: int
138
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
139
 
140
  async def get_embedding_dim(embedding_model: str) -> int:
141
  """Get embedding dimensions for the specified model"""
@@ -150,10 +192,37 @@ def create_app(args):
150
  format="%(levelname)s:%(message)s", level=getattr(logging, args.log_level)
151
  )
152
 
153
- # Initialize FastAPI app
 
 
 
 
154
  app = FastAPI(
155
  title="LightRAG API",
156
- description="API for querying text using LightRAG with OpenAI integration",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
157
  )
158
 
159
  # Create working directory if it doesn't exist
@@ -213,7 +282,7 @@ def create_app(args):
213
  except Exception as e:
214
  logging.error(f"Error during startup indexing: {str(e)}")
215
 
216
- @app.post("/documents/scan")
217
  async def scan_for_new_documents():
218
  """Manually trigger scanning for new documents"""
219
  try:
@@ -238,7 +307,7 @@ def create_app(args):
238
  except Exception as e:
239
  raise HTTPException(status_code=500, detail=str(e))
240
 
241
- @app.post("/documents/upload")
242
  async def upload_to_input_dir(file: UploadFile = File(...)):
243
  """Upload a file to the input directory"""
244
  try:
@@ -266,7 +335,7 @@ def create_app(args):
266
  except Exception as e:
267
  raise HTTPException(status_code=500, detail=str(e))
268
 
269
- @app.post("/query", response_model=QueryResponse)
270
  async def query_text(request: QueryRequest):
271
  try:
272
  response = await rag.aquery(
@@ -288,7 +357,7 @@ def create_app(args):
288
  except Exception as e:
289
  raise HTTPException(status_code=500, detail=str(e))
290
 
291
- @app.post("/query/stream")
292
  async def query_text_stream(request: QueryRequest):
293
  try:
294
  response = rag.query(
@@ -308,7 +377,7 @@ def create_app(args):
308
  except Exception as e:
309
  raise HTTPException(status_code=500, detail=str(e))
310
 
311
- @app.post("/documents/text", response_model=InsertResponse)
312
  async def insert_text(request: InsertTextRequest):
313
  try:
314
  rag.insert(request.text)
@@ -320,7 +389,7 @@ def create_app(args):
320
  except Exception as e:
321
  raise HTTPException(status_code=500, detail=str(e))
322
 
323
- @app.post("/documents/file", response_model=InsertResponse)
324
  async def insert_file(file: UploadFile = File(...), description: str = Form(None)):
325
  try:
326
  content = await file.read()
@@ -344,7 +413,7 @@ def create_app(args):
344
  except Exception as e:
345
  raise HTTPException(status_code=500, detail=str(e))
346
 
347
- @app.post("/documents/batch", response_model=InsertResponse)
348
  async def insert_batch(files: List[UploadFile] = File(...)):
349
  try:
350
  inserted_count = 0
@@ -374,7 +443,7 @@ def create_app(args):
374
  except Exception as e:
375
  raise HTTPException(status_code=500, detail=str(e))
376
 
377
- @app.delete("/documents", response_model=InsertResponse)
378
  async def clear_documents():
379
  try:
380
  rag.text_chunks = []
@@ -388,7 +457,7 @@ def create_app(args):
388
  except Exception as e:
389
  raise HTTPException(status_code=500, detail=str(e))
390
 
391
- @app.get("/health")
392
  async def get_status():
393
  """Get current system status"""
394
  return {
 
14
  from ascii_colors import trace_exception
15
  import nest_asyncio
16
 
17
+ from fastapi import FastAPI, HTTPException
18
+ import os
19
+ from typing import Optional
20
+
21
+ from fastapi import FastAPI, Depends, HTTPException, Security
22
+ from fastapi.security import APIKeyHeader
23
+ import os
24
+ import argparse
25
+ from typing import Optional
26
+ from fastapi.middleware.cors import CORSMiddleware
27
+
28
+ from starlette.status import HTTP_403_FORBIDDEN
29
+ from fastapi import HTTPException
30
+
31
  # Apply nest_asyncio to solve event loop issues
32
  nest_asyncio.apply()
33
 
 
89
  help="Logging level (default: INFO)",
90
  )
91
 
92
+ parser.add_argument('--key', type=str, help='API key for authentication. This protects lightrag server against unauthorized access', default=None)
93
+
94
+
95
  return parser.parse_args()
96
 
97
 
 
153
  message: str
154
  document_count: int
155
 
156
+ def get_api_key_dependency(api_key: Optional[str]):
157
+ if not api_key:
158
+ # If no API key is configured, return a dummy dependency that always succeeds
159
+ async def no_auth():
160
+ return None
161
+ return no_auth
162
+
163
+ # If API key is configured, use proper authentication
164
+ api_key_header = APIKeyHeader(name="X-API-Key", auto_error=False)
165
+
166
+ async def api_key_auth(api_key_header_value: str | None = Security(api_key_header)):
167
+ if not api_key_header_value:
168
+ raise HTTPException(
169
+ status_code=HTTP_403_FORBIDDEN,
170
+ detail="API Key required"
171
+ )
172
+ if api_key_header_value != api_key:
173
+ raise HTTPException(
174
+ status_code=HTTP_403_FORBIDDEN,
175
+ detail="Invalid API Key"
176
+ )
177
+ return api_key_header_value
178
+
179
+ return api_key_auth
180
+
181
 
182
  async def get_embedding_dim(embedding_model: str) -> int:
183
  """Get embedding dimensions for the specified model"""
 
192
  format="%(levelname)s:%(message)s", level=getattr(logging, args.log_level)
193
  )
194
 
195
+
196
+ # Check if API key is provided either through env var or args
197
+ api_key = os.getenv("LIGHTRAG_API_KEY") or args.key
198
+
199
+ # Initialize FastAPI
200
  app = FastAPI(
201
  title="LightRAG API",
202
+ description="API for querying text using LightRAG with separate storage and input directories"+"(With authentication)" if api_key else "",
203
+ version="1.0.0",
204
+ openapi_tags=[{"name": "api"}]
205
+ )
206
+
207
+ # Add CORS middleware
208
+ app.add_middleware(
209
+ CORSMiddleware,
210
+ allow_origins=["*"],
211
+ allow_credentials=True,
212
+ allow_methods=["*"],
213
+ allow_headers=["*"],
214
+ )
215
+
216
+ # Create the optional API key dependency
217
+ optional_api_key = get_api_key_dependency(api_key)
218
+
219
+ # Add CORS middleware
220
+ app.add_middleware(
221
+ CORSMiddleware,
222
+ allow_origins=["*"],
223
+ allow_credentials=True,
224
+ allow_methods=["*"],
225
+ allow_headers=["*"],
226
  )
227
 
228
  # Create working directory if it doesn't exist
 
282
  except Exception as e:
283
  logging.error(f"Error during startup indexing: {str(e)}")
284
 
285
+ @app.post("/documents/scan", dependencies=[Depends(optional_api_key)])
286
  async def scan_for_new_documents():
287
  """Manually trigger scanning for new documents"""
288
  try:
 
307
  except Exception as e:
308
  raise HTTPException(status_code=500, detail=str(e))
309
 
310
+ @app.post("/documents/upload", dependencies=[Depends(optional_api_key)])
311
  async def upload_to_input_dir(file: UploadFile = File(...)):
312
  """Upload a file to the input directory"""
313
  try:
 
335
  except Exception as e:
336
  raise HTTPException(status_code=500, detail=str(e))
337
 
338
+ @app.post("/query", response_model=QueryResponse, dependencies=[Depends(optional_api_key)])
339
  async def query_text(request: QueryRequest):
340
  try:
341
  response = await rag.aquery(
 
357
  except Exception as e:
358
  raise HTTPException(status_code=500, detail=str(e))
359
 
360
+ @app.post("/query/stream", dependencies=[Depends(optional_api_key)])
361
  async def query_text_stream(request: QueryRequest):
362
  try:
363
  response = rag.query(
 
377
  except Exception as e:
378
  raise HTTPException(status_code=500, detail=str(e))
379
 
380
+ @app.post("/documents/text", response_model=InsertResponse, dependencies=[Depends(optional_api_key)])
381
  async def insert_text(request: InsertTextRequest):
382
  try:
383
  rag.insert(request.text)
 
389
  except Exception as e:
390
  raise HTTPException(status_code=500, detail=str(e))
391
 
392
+ @app.post("/documents/file", response_model=InsertResponse, dependencies=[Depends(optional_api_key)])
393
  async def insert_file(file: UploadFile = File(...), description: str = Form(None)):
394
  try:
395
  content = await file.read()
 
413
  except Exception as e:
414
  raise HTTPException(status_code=500, detail=str(e))
415
 
416
+ @app.post("/documents/batch", response_model=InsertResponse, dependencies=[Depends(optional_api_key)])
417
  async def insert_batch(files: List[UploadFile] = File(...)):
418
  try:
419
  inserted_count = 0
 
443
  except Exception as e:
444
  raise HTTPException(status_code=500, detail=str(e))
445
 
446
+ @app.delete("/documents", response_model=InsertResponse, dependencies=[Depends(optional_api_key)])
447
  async def clear_documents():
448
  try:
449
  rag.text_chunks = []
 
457
  except Exception as e:
458
  raise HTTPException(status_code=500, detail=str(e))
459
 
460
+ @app.get("/health", dependencies=[Depends(optional_api_key)])
461
  async def get_status():
462
  """Get current system status"""
463
  return {