sachin commited on
Commit
39b1c1e
·
1 Parent(s): 944b400
Files changed (2) hide show
  1. src/server/main.py +158 -95
  2. src/server/utils/crypto.py +13 -0
src/server/main.py CHANGED
@@ -20,6 +20,9 @@ from Crypto.Cipher import AES
20
  # Import from auth.py
21
  from utils.auth import get_current_user, get_current_user_with_admin, login, refresh_token, register, app_register, TokenResponse, Settings, LoginRequest, RegisterRequest, bearer_scheme
22
 
 
 
 
23
  # Assuming these are in your project structure
24
  from config.tts_config import SPEED, ResponseFormat, config as tts_config
25
  from config.logging_config import logger
@@ -114,6 +117,77 @@ class AudioProcessingResponse(BaseModel):
114
  class Config:
115
  schema_extra = {"example": {"result": "Processed audio output"}}
116
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
  # TTS Service Interface
118
  class TTSService(ABC):
119
  @abstractmethod
@@ -271,38 +345,14 @@ async def generate_audio(
271
  headers=headers
272
  )
273
 
274
- class ChatRequest(BaseModel):
275
- prompt: str = Field(..., description="Text prompt for chat (max 1000 characters)")
276
- src_lang: str = Field("kan_Knda", description="Source language code (default: Kannada)")
277
-
278
- @field_validator("prompt")
279
- def prompt_must_be_valid(cls, v):
280
- if len(v) > 1000:
281
- raise ValueError("Prompt cannot exceed 1000 characters")
282
- return v.strip()
283
-
284
- class Config:
285
- schema_extra = {
286
- "example": {
287
- "prompt": "Hello, how are you?",
288
- "src_lang": "kan_Knda"
289
- }
290
- }
291
-
292
- class ChatResponse(BaseModel):
293
- response: str = Field(..., description="Generated chat response")
294
-
295
- class Config:
296
- schema_extra = {"example": {"response": "Hi there, I'm doing great!"}}
297
-
298
  @app.post("/v1/chat",
299
  response_model=ChatResponse,
300
  summary="Chat with AI",
301
- description="Generate a chat response from a prompt in the specified language. Rate limited to 100 requests per minute per user. Requires authentication.",
302
  tags=["Chat"],
303
  responses={
304
  200: {"description": "Chat response", "model": ChatResponse},
305
- 400: {"description": "Invalid prompt"},
306
  401: {"description": "Unauthorized - Token required"},
307
  429: {"description": "Rate limit exceeded"},
308
  504: {"description": "Chat service timeout"}
@@ -311,17 +361,31 @@ class ChatResponse(BaseModel):
311
  async def chat(
312
  request: Request,
313
  chat_request: ChatRequest,
314
- credentials: HTTPAuthorizationCredentials = Depends(bearer_scheme)
 
315
  ):
316
  user_id = await get_current_user(credentials)
317
- if not chat_request.prompt:
 
 
 
 
 
 
 
 
 
 
318
  raise HTTPException(status_code=400, detail="Prompt cannot be empty")
319
- logger.info(f"Received prompt: {chat_request.prompt}, src_lang: {chat_request.src_lang}, user_id: {user_id}")
 
 
 
320
 
321
  try:
322
  external_url = "https://slabstech-dhwani-internal-api-server.hf.space/v1/chat"
323
  payload = {
324
- "prompt": chat_request.prompt,
325
  "src_lang": chat_request.src_lang,
326
  "tgt_lang": chat_request.src_lang
327
  }
@@ -405,22 +469,27 @@ async def process_audio(
405
  @app.post("/v1/transcribe/",
406
  response_model=TranscriptionResponse,
407
  summary="Transcribe Audio File",
408
- description="Transcribe an uploaded audio file into text in the specified language. Requires authentication.",
409
  tags=["Audio"],
410
  responses={
411
  200: {"description": "Transcription result", "model": TranscriptionResponse},
 
412
  401: {"description": "Unauthorized - Token required"},
413
  504: {"description": "Transcription service timeout"}
414
  })
415
  async def transcribe_audio(
416
- file: UploadFile = File(..., description="Audio file to transcribe"),
417
  language: str = Query(..., enum=["kannada", "hindi", "tamil"], description="Language of the audio"),
418
- credentials: HTTPAuthorizationCredentials = Depends(bearer_scheme)
 
419
  ):
420
  user_id = await get_current_user(credentials)
 
 
421
  start_time = time()
422
  try:
423
- file_content = await file.read()
 
424
  files = {"file": (file.filename, file_content, file.content_type)}
425
 
426
  external_url = f"{settings.external_asr_url}/transcribe/?language={language}"
@@ -436,7 +505,10 @@ async def transcribe_audio(
436
  logger.info(f"Transcription completed in {time() - start_time:.2f} seconds")
437
  return TranscriptionResponse(text=transcription)
438
 
 
 
439
  except requests.Timeout:
 
440
  raise HTTPException(status_code=504, detail="Transcription service timeout")
441
  except requests.RequestException as e:
442
  logger.error(f"Transcription request failed: {str(e)}")
@@ -480,48 +552,43 @@ async def chat_v2(
480
  logger.error(f"Chat_v2 processing failed: {str(e)}", exc_info=True)
481
  raise HTTPException(status_code=500, detail=f"An error occurred: {str(e)}")
482
 
483
- class TranslationRequest(BaseModel):
484
- sentences: List[str] = Field(..., description="List of sentences to translate")
485
- src_lang: str = Field(..., description="Source language code")
486
- tgt_lang: str = Field(..., description="Target language code")
487
-
488
- class Config:
489
- schema_extra = {
490
- "example": {
491
- "sentences": ["Hello", "How are you?"],
492
- "src_lang": "en",
493
- "tgt_lang": "kan_Knda"
494
- }
495
- }
496
-
497
- class TranslationResponse(BaseModel):
498
- translations: List[str] = Field(..., description="Translated sentences")
499
-
500
- class Config:
501
- schema_extra = {"example": {"translations": ["ನಮಸ್ಕಾರ", "ನೀವು ಹೇಗಿದ್ದೀರಿ?"]}}
502
-
503
  @app.post("/v1/translate",
504
  response_model=TranslationResponse,
505
  summary="Translate Text",
506
- description="Translate a list of sentences from source to target language. Requires authentication.",
507
  tags=["Translation"],
508
  responses={
509
  200: {"description": "Translation result", "model": TranslationResponse},
 
510
  401: {"description": "Unauthorized - Token required"},
511
  500: {"description": "Translation service error"},
512
  504: {"description": "Translation service timeout"}
513
  })
514
  async def translate(
515
  request: TranslationRequest,
516
- credentials: HTTPAuthorizationCredentials = Depends(bearer_scheme)
 
517
  ):
518
  user_id = await get_current_user(credentials)
519
- logger.info(f"Received translation request: {request.dict()}, user_id: {user_id}")
 
 
 
 
 
 
 
 
 
 
 
 
 
520
 
521
  external_url = f"https://slabstech-dhwani-internal-api-server.hf.space/translate?src_lang={request.src_lang}&tgt_lang={request.tgt_lang}"
522
 
523
  payload = {
524
- "sentences": request.sentences,
525
  "src_lang": request.src_lang,
526
  "tgt_lang": request.tgt_lang
527
  }
@@ -541,7 +608,7 @@ async def translate(
541
  response_data = response.json()
542
  translations = response_data.get("translations", [])
543
 
544
- if not translations or len(translations) != len(request.sentences):
545
  logger.warning(f"Unexpected response format: {response_data}")
546
  raise HTTPException(status_code=500, detail="Invalid response from translation service")
547
 
@@ -558,28 +625,14 @@ async def translate(
558
  logger.error(f"Invalid JSON response: {str(e)}")
559
  raise HTTPException(status_code=500, detail="Invalid response format from translation service")
560
 
561
- class VisualQueryRequest(BaseModel):
562
- query: str
563
- src_lang: str = "kan_Knda"
564
- tgt_lang: str = "kan_Knda"
565
-
566
- @field_validator("query")
567
- def query_must_be_valid(cls, v):
568
- if len(v) > 1000:
569
- raise ValueError("Query cannot exceed 1000 characters")
570
- return v.strip()
571
-
572
- class VisualQueryResponse(BaseModel):
573
- answer: str
574
-
575
  @app.post("/v1/visual_query",
576
  response_model=VisualQueryResponse,
577
  summary="Visual Query with Image",
578
- description="Process a visual query with an image and text question. Rate limited to 100 requests per minute per user. Requires authentication.",
579
  tags=["Chat"],
580
  responses={
581
  200: {"description": "Query response", "model": VisualQueryResponse},
582
- 400: {"description": "Invalid query"},
583
  401: {"description": "Unauthorized - Token required"},
584
  429: {"description": "Rate limit exceeded"},
585
  504: {"description": "Visual query service timeout"}
@@ -587,19 +640,40 @@ class VisualQueryResponse(BaseModel):
587
  @limiter.limit(settings.chat_rate_limit)
588
  async def visual_query(
589
  request: Request,
590
- query: str = Form(..., description="Text query for the visual content"),
591
- file: UploadFile = File(..., description="Image file to analyze"),
592
  src_lang: str = Query(default="kan_Knda", description="Source language code"),
593
  tgt_lang: str = Query(default="kan_Knda", description="Target language code"),
594
- credentials: HTTPAuthorizationCredentials = Depends(bearer_scheme)
 
595
  ):
596
  user_id = await get_current_user(credentials)
597
- if not query.strip():
 
 
 
 
 
 
 
 
 
 
598
  raise HTTPException(status_code=400, detail="Query cannot be empty")
 
 
 
 
 
 
 
 
 
 
599
 
600
  logger.info("Processing visual query request", extra={
601
  "endpoint": "/v1/visual_query",
602
- "query_length": len(query),
603
  "file_name": file.filename,
604
  "client_ip": get_remote_address(request),
605
  "user_id": user_id,
@@ -610,9 +684,8 @@ async def visual_query(
610
  external_url = f"https://slabstech-dhwani-internal-api-server.hf.space/v1/visual_query/?src_lang={src_lang}&tgt_lang={tgt_lang}"
611
 
612
  try:
613
- file_content = await file.read()
614
- files = {"file": (file.filename, file_content, file.content_type)}
615
- data = {"query": query}
616
 
617
  response = requests.post(
618
  external_url,
@@ -650,19 +723,9 @@ class SupportedLanguage(str, Enum):
650
  hindi = "hindi"
651
  tamil = "tamil"
652
 
653
- def decrypt_audio(encrypted_data: bytes, key: bytes) -> bytes:
654
- try:
655
- nonce, ciphertext = encrypted_data[:12], encrypted_data[12:]
656
- cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
657
- plaintext = cipher.decrypt_and_verify(ciphertext[:-16], ciphertext[-16:])
658
- return plaintext
659
- except Exception as e:
660
- logger.error(f"Audio decryption failed: {str(e)}")
661
- raise HTTPException(status_code=400, detail="Invalid encrypted audio")
662
-
663
  @app.post("/v1/speech_to_speech",
664
  summary="Speech-to-Speech Conversion",
665
- description="Convert input speech to processed speech by calling an external speech-to-speech API. Rate limited to 5 requests per minute per user. Requires authentication and X-Session-Key header.",
666
  tags=["Audio"],
667
  responses={
668
  200: {"description": "Audio stream", "content": {"audio/mp3": {"example": "Binary audio data"}}},
@@ -693,7 +756,7 @@ async def speech_to_speech(
693
 
694
  try:
695
  encrypted_content = await file.read()
696
- file_content = decrypt_audio(encrypted_content, session_key)
697
  files = {"file": (file.filename, file_content, file.content_type)}
698
  external_url = f"https://slabstech-dhwani-internal-api-server.hf.space/v1/speech_to_speech?language={language}"
699
 
 
20
  # Import from auth.py
21
  from utils.auth import get_current_user, get_current_user_with_admin, login, refresh_token, register, app_register, TokenResponse, Settings, LoginRequest, RegisterRequest, bearer_scheme
22
 
23
+ # Import decryption utility
24
+ from utils.crypto import decrypt_data
25
+
26
  # Assuming these are in your project structure
27
  from config.tts_config import SPEED, ResponseFormat, config as tts_config
28
  from config.logging_config import logger
 
117
  class Config:
118
  schema_extra = {"example": {"result": "Processed audio output"}}
119
 
120
+ class ChatRequest(BaseModel):
121
+ prompt: str = Field(..., description="Base64-encoded encrypted prompt (max 1000 characters after decryption)")
122
+ src_lang: str = Field("kan_Knda", description="Source language code (default: Kannada)")
123
+
124
+ @field_validator("prompt")
125
+ def prompt_must_be_valid(cls, v):
126
+ try:
127
+ base64.b64decode(v)
128
+ except Exception:
129
+ raise ValueError("Prompt must be valid base64-encoded data")
130
+ return v
131
+
132
+ class Config:
133
+ schema_extra = {
134
+ "example": {
135
+ "prompt": "base64_encoded_encrypted_prompt",
136
+ "src_lang": "kan_Knda"
137
+ }
138
+ }
139
+
140
+ class ChatResponse(BaseModel):
141
+ response: str = Field(..., description="Generated chat response")
142
+
143
+ class Config:
144
+ schema_extra = {"example": {"response": "Hi there, I'm doing great!"}}
145
+
146
+ class TranslationRequest(BaseModel):
147
+ sentences: List[str] = Field(..., description="List of base64-encoded encrypted sentences")
148
+ src_lang: str = Field(..., description="Source language code")
149
+ tgt_lang: str = Field(..., description="Target language code")
150
+
151
+ @field_validator("sentences")
152
+ def sentences_must_be_valid(cls, v):
153
+ for sentence in v:
154
+ try:
155
+ base64.b64decode(sentence)
156
+ except Exception:
157
+ raise ValueError("Each sentence must be valid base64-encoded data")
158
+ return v
159
+
160
+ class Config:
161
+ schema_extra = {
162
+ "example": {
163
+ "sentences": ["base64_encoded_encrypted_hello", "base64_encoded_encrypted_how_are_you"],
164
+ "src_lang": "en",
165
+ "tgt_lang": "kan_Knda"
166
+ }
167
+ }
168
+
169
+ class TranslationResponse(BaseModel):
170
+ translations: List[str] = Field(..., description="Translated sentences")
171
+
172
+ class Config:
173
+ schema_extra = {"example": {"translations": ["ನಮಸ್ಕಾರ", "ನೀವು ಹೇಗಿದ್ದೀರಿ?"]}}
174
+
175
+ class VisualQueryRequest(BaseModel):
176
+ query: str
177
+ src_lang: str = "kan_Knda"
178
+ tgt_lang: str = "kan_Knda"
179
+
180
+ @field_validator("query")
181
+ def query_must_be_valid(cls, v):
182
+ try:
183
+ base64.b64decode(v)
184
+ except Exception:
185
+ raise ValueError("Query must be valid base64-encoded data")
186
+ return v
187
+
188
+ class VisualQueryResponse(BaseModel):
189
+ answer: str
190
+
191
  # TTS Service Interface
192
  class TTSService(ABC):
193
  @abstractmethod
 
345
  headers=headers
346
  )
347
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
348
  @app.post("/v1/chat",
349
  response_model=ChatResponse,
350
  summary="Chat with AI",
351
+ description="Generate a chat response from an encrypted prompt in the specified language. Rate limited to 100 requests per minute per user. Requires authentication and X-Session-Key header.",
352
  tags=["Chat"],
353
  responses={
354
  200: {"description": "Chat response", "model": ChatResponse},
355
+ 400: {"description": "Invalid prompt or encrypted data"},
356
  401: {"description": "Unauthorized - Token required"},
357
  429: {"description": "Rate limit exceeded"},
358
  504: {"description": "Chat service timeout"}
 
361
  async def chat(
362
  request: Request,
363
  chat_request: ChatRequest,
364
+ credentials: HTTPAuthorizationCredentials = Depends(bearer_scheme),
365
+ x_session_key: str = Header(..., alias="X-Session-Key")
366
  ):
367
  user_id = await get_current_user(credentials)
368
+ session_key = base64.b64decode(x_session_key)
369
+
370
+ # Decrypt the prompt
371
+ try:
372
+ encrypted_prompt = base64.b64decode(chat_request.prompt)
373
+ decrypted_prompt = decrypt_data(encrypted_prompt, session_key).decode("utf-8")
374
+ except Exception as e:
375
+ logger.error(f"Prompt decryption failed: {str(e)}")
376
+ raise HTTPException(status_code=400, detail="Invalid encrypted prompt")
377
+
378
+ if not decrypted_prompt:
379
  raise HTTPException(status_code=400, detail="Prompt cannot be empty")
380
+ if len(decrypted_prompt) > 1000:
381
+ raise HTTPException(status_code=400, detail="Decrypted prompt cannot exceed 1000 characters")
382
+
383
+ logger.info(f"Received prompt: {decrypted_prompt}, src_lang: {chat_request.src_lang}, user_id: {user_id}")
384
 
385
  try:
386
  external_url = "https://slabstech-dhwani-internal-api-server.hf.space/v1/chat"
387
  payload = {
388
+ "prompt": decrypted_prompt,
389
  "src_lang": chat_request.src_lang,
390
  "tgt_lang": chat_request.src_lang
391
  }
 
469
  @app.post("/v1/transcribe/",
470
  response_model=TranscriptionResponse,
471
  summary="Transcribe Audio File",
472
+ description="Transcribe an encrypted audio file into text in the specified language. Requires authentication and X-Session-Key header.",
473
  tags=["Audio"],
474
  responses={
475
  200: {"description": "Transcription result", "model": TranscriptionResponse},
476
+ 400: {"description": "Invalid encrypted audio"},
477
  401: {"description": "Unauthorized - Token required"},
478
  504: {"description": "Transcription service timeout"}
479
  })
480
  async def transcribe_audio(
481
+ file: UploadFile = File(..., description="Encrypted audio file to transcribe"),
482
  language: str = Query(..., enum=["kannada", "hindi", "tamil"], description="Language of the audio"),
483
+ credentials: HTTPAuthorizationCredentials = Depends(bearer_scheme),
484
+ x_session_key: str = Header(..., alias="X-Session-Key")
485
  ):
486
  user_id = await get_current_user(credentials)
487
+ session_key = base64.b64decode(x_session_key)
488
+
489
  start_time = time()
490
  try:
491
+ encrypted_content = await file.read()
492
+ file_content = decrypt_data(encrypted_content, session_key)
493
  files = {"file": (file.filename, file_content, file.content_type)}
494
 
495
  external_url = f"{settings.external_asr_url}/transcribe/?language={language}"
 
505
  logger.info(f"Transcription completed in {time() - start_time:.2f} seconds")
506
  return TranscriptionResponse(text=transcription)
507
 
508
+ except HTTPException:
509
+ raise
510
  except requests.Timeout:
511
+ logger.error("Transcription service timed out")
512
  raise HTTPException(status_code=504, detail="Transcription service timeout")
513
  except requests.RequestException as e:
514
  logger.error(f"Transcription request failed: {str(e)}")
 
552
  logger.error(f"Chat_v2 processing failed: {str(e)}", exc_info=True)
553
  raise HTTPException(status_code=500, detail=f"An error occurred: {str(e)}")
554
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
555
  @app.post("/v1/translate",
556
  response_model=TranslationResponse,
557
  summary="Translate Text",
558
+ description="Translate a list of base64-encoded encrypted sentences from source to target language. Requires authentication and X-Session-Key header.",
559
  tags=["Translation"],
560
  responses={
561
  200: {"description": "Translation result", "model": TranslationResponse},
562
+ 400: {"description": "Invalid encrypted sentences"},
563
  401: {"description": "Unauthorized - Token required"},
564
  500: {"description": "Translation service error"},
565
  504: {"description": "Translation service timeout"}
566
  })
567
  async def translate(
568
  request: TranslationRequest,
569
+ credentials: HTTPAuthorizationCredentials = Depends(bearer_scheme),
570
+ x_session_key: str = Header(..., alias="X-Session-Key")
571
  ):
572
  user_id = await get_current_user(credentials)
573
+ session_key = base64.b64decode(x_session_key)
574
+
575
+ # Decrypt sentences
576
+ decrypted_sentences = []
577
+ for sentence in request.sentences:
578
+ try:
579
+ encrypted_sentence = base64.b64decode(sentence)
580
+ decrypted_sentence = decrypt_data(encrypted_sentence, session_key).decode("utf-8")
581
+ decrypted_sentences.append(decrypted_sentence)
582
+ except Exception as e:
583
+ logger.error(f"Sentence decryption failed: {str(e)}")
584
+ raise HTTPException(status_code=400, detail="Invalid encrypted sentence")
585
+
586
+ logger.info(f"Received translation request: {decrypted_sentences}, src_lang: {request.src_lang}, tgt_lang: {request.tgt_lang}, user_id: {user_id}")
587
 
588
  external_url = f"https://slabstech-dhwani-internal-api-server.hf.space/translate?src_lang={request.src_lang}&tgt_lang={request.tgt_lang}"
589
 
590
  payload = {
591
+ "sentences": decrypted_sentences,
592
  "src_lang": request.src_lang,
593
  "tgt_lang": request.tgt_lang
594
  }
 
608
  response_data = response.json()
609
  translations = response_data.get("translations", [])
610
 
611
+ if not translations or len(translations) != len(decrypted_sentences):
612
  logger.warning(f"Unexpected response format: {response_data}")
613
  raise HTTPException(status_code=500, detail="Invalid response from translation service")
614
 
 
625
  logger.error(f"Invalid JSON response: {str(e)}")
626
  raise HTTPException(status_code=500, detail="Invalid response format from translation service")
627
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
628
  @app.post("/v1/visual_query",
629
  response_model=VisualQueryResponse,
630
  summary="Visual Query with Image",
631
+ description="Process a visual query with an encrypted text query and encrypted image. Rate limited to 100 requests per minute per user. Requires authentication and X-Session-Key header.",
632
  tags=["Chat"],
633
  responses={
634
  200: {"description": "Query response", "model": VisualQueryResponse},
635
+ 400: {"description": "Invalid query or encrypted data"},
636
  401: {"description": "Unauthorized - Token required"},
637
  429: {"description": "Rate limit exceeded"},
638
  504: {"description": "Visual query service timeout"}
 
640
  @limiter.limit(settings.chat_rate_limit)
641
  async def visual_query(
642
  request: Request,
643
+ query: str = Form(..., description="Base64-encoded encrypted text query"),
644
+ file: UploadFile = File(..., description="Encrypted image file to analyze"),
645
  src_lang: str = Query(default="kan_Knda", description="Source language code"),
646
  tgt_lang: str = Query(default="kan_Knda", description="Target language code"),
647
+ credentials: HTTPAuthorizationCredentials = Depends(bearer_scheme),
648
+ x_session_key: str = Header(..., alias="X-Session-Key")
649
  ):
650
  user_id = await get_current_user(credentials)
651
+ session_key = base64.b64decode(x_session_key)
652
+
653
+ # Decrypt query
654
+ try:
655
+ encrypted_query = base64.b64decode(query)
656
+ decrypted_query = decrypt_data(encrypted_query, session_key).decode("utf-8")
657
+ except Exception as e:
658
+ logger.error(f"Query decryption failed: {str(e)}")
659
+ raise HTTPException(status_code=400, detail="Invalid encrypted query")
660
+
661
+ if not decrypted_query.strip():
662
  raise HTTPException(status_code=400, detail="Query cannot be empty")
663
+ if len(decrypted_query) > 1000:
664
+ raise HTTPException(status_code=400, detail="Decrypted query cannot exceed 1000 characters")
665
+
666
+ # Decrypt image
667
+ try:
668
+ encrypted_content = await file.read()
669
+ decrypted_content = decrypt_data(encrypted_content, session_key)
670
+ except Exception as e:
671
+ logger.error(f"Image decryption failed: {str(e)}")
672
+ raise HTTPException(status_code=400, detail="Invalid encrypted image")
673
 
674
  logger.info("Processing visual query request", extra={
675
  "endpoint": "/v1/visual_query",
676
+ "query_length": len(decrypted_query),
677
  "file_name": file.filename,
678
  "client_ip": get_remote_address(request),
679
  "user_id": user_id,
 
684
  external_url = f"https://slabstech-dhwani-internal-api-server.hf.space/v1/visual_query/?src_lang={src_lang}&tgt_lang={tgt_lang}"
685
 
686
  try:
687
+ files = {"file": (file.filename, decrypted_content, file.content_type)}
688
+ data = {"query": decrypted_query}
 
689
 
690
  response = requests.post(
691
  external_url,
 
723
  hindi = "hindi"
724
  tamil = "tamil"
725
 
 
 
 
 
 
 
 
 
 
 
726
  @app.post("/v1/speech_to_speech",
727
  summary="Speech-to-Speech Conversion",
728
+ description="Convert input encrypted speech to processed speech by calling an external speech-to-speech API. Rate limited to 5 requests per minute per user. Requires authentication and X-Session-Key header.",
729
  tags=["Audio"],
730
  responses={
731
  200: {"description": "Audio stream", "content": {"audio/mp3": {"example": "Binary audio data"}}},
 
756
 
757
  try:
758
  encrypted_content = await file.read()
759
+ file_content = decrypt_data(encrypted_content, session_key)
760
  files = {"file": (file.filename, file_content, file.content_type)}
761
  external_url = f"https://slabstech-dhwani-internal-api-server.hf.space/v1/speech_to_speech?language={language}"
762
 
src/server/utils/crypto.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from Crypto.Cipher import AES
2
+ from fastapi import HTTPException
3
+ from config.logging_config import logger
4
+
5
+ def decrypt_data(encrypted_data: bytes, key: bytes) -> bytes:
6
+ try:
7
+ nonce, ciphertext = encrypted_data[:12], encrypted_data[12:]
8
+ cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
9
+ plaintext = cipher.decrypt_and_verify(ciphertext[:-16], ciphertext[-16:])
10
+ return plaintext
11
+ except Exception as e:
12
+ logger.error(f"Decryption failed: {str(e)}")
13
+ raise HTTPException(status_code=400, detail="Invalid encrypted data")