Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
sachin
commited on
Commit
·
56859f5
1
Parent(s):
c8002a8
add-encrity
Browse files- src/server/main.py +40 -23
- src/server/utils/auth.py +58 -23
src/server/main.py
CHANGED
@@ -5,7 +5,7 @@ from typing import List, Optional
|
|
5 |
from abc import ABC, abstractmethod
|
6 |
|
7 |
import uvicorn
|
8 |
-
from fastapi import Depends, FastAPI, File, HTTPException, Query, Request, UploadFile, Form
|
9 |
from fastapi.middleware.cors import CORSMiddleware
|
10 |
from fastapi.responses import JSONResponse, RedirectResponse, StreamingResponse
|
11 |
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
@@ -14,6 +14,8 @@ from slowapi import Limiter
|
|
14 |
from slowapi.util import get_remote_address
|
15 |
import requests
|
16 |
from PIL import Image
|
|
|
|
|
17 |
|
18 |
# Import from auth.py
|
19 |
from utils.auth import get_current_user, get_current_user_with_admin, login, refresh_token, register, app_register, TokenResponse, Settings, LoginRequest, RegisterRequest, bearer_scheme
|
@@ -58,7 +60,7 @@ async def get_user_id_for_rate_limit(request: Request):
|
|
58 |
user_id = await get_current_user(credentials)
|
59 |
return user_id
|
60 |
except Exception:
|
61 |
-
return get_remote_address(request)
|
62 |
|
63 |
limiter = Limiter(key_func=get_user_id_for_rate_limit)
|
64 |
|
@@ -155,14 +157,18 @@ async def home():
|
|
155 |
@app.post("/v1/token",
|
156 |
response_model=TokenResponse,
|
157 |
summary="User Login",
|
158 |
-
description="Authenticate a user with
|
159 |
tags=["Authentication"],
|
160 |
responses={
|
161 |
200: {"description": "Successful login", "model": TokenResponse},
|
162 |
-
|
|
|
163 |
})
|
164 |
-
async def token(
|
165 |
-
|
|
|
|
|
|
|
166 |
|
167 |
@app.post("/v1/refresh",
|
168 |
response_model=TokenResponse,
|
@@ -195,20 +201,21 @@ async def register_user(
|
|
195 |
@app.post("/v1/app/register",
|
196 |
response_model=TokenResponse,
|
197 |
summary="Register New App User",
|
198 |
-
description="Create a new user account for the mobile app using an email and device token. Returns an access token and refresh token. Rate limited to 5 requests per minute per IP.",
|
199 |
tags=["Authentication"],
|
200 |
responses={
|
201 |
200: {"description": "User registered successfully", "model": TokenResponse},
|
202 |
-
400: {"description": "Email already registered"},
|
203 |
429: {"description": "Rate limit exceeded"}
|
204 |
})
|
205 |
@limiter.limit(settings.speech_rate_limit)
|
206 |
async def app_register_user(
|
207 |
request: Request,
|
208 |
-
register_request: RegisterRequest
|
|
|
209 |
):
|
210 |
-
logger.info(f"App registration attempt
|
211 |
-
return await app_register(register_request)
|
212 |
|
213 |
@app.post("/v1/audio/speech",
|
214 |
summary="Generate Speech from Text",
|
@@ -551,8 +558,8 @@ async def translate(
|
|
551 |
|
552 |
class VisualQueryRequest(BaseModel):
|
553 |
query: str
|
554 |
-
src_lang: str = "kan_Knda"
|
555 |
-
tgt_lang: str = "kan_Knda"
|
556 |
|
557 |
@field_validator("query")
|
558 |
def query_must_be_valid(cls, v):
|
@@ -633,25 +640,31 @@ async def visual_query(
|
|
633 |
except ValueError as e:
|
634 |
logger.error(f"Invalid JSON response: {str(e)}")
|
635 |
raise HTTPException(status_code=500, detail="Invalid response format from visual query service")
|
636 |
-
|
637 |
-
# Ensure these imports are at the top with other imports
|
638 |
-
from fastapi.responses import StreamingResponse
|
639 |
from enum import Enum
|
640 |
|
641 |
-
# Define supported languages for validation
|
642 |
class SupportedLanguage(str, Enum):
|
643 |
kannada = "kannada"
|
644 |
hindi = "hindi"
|
645 |
tamil = "tamil"
|
646 |
|
647 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
648 |
@app.post("/v1/speech_to_speech",
|
649 |
summary="Speech-to-Speech Conversion",
|
650 |
-
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.",
|
651 |
tags=["Audio"],
|
652 |
responses={
|
653 |
200: {"description": "Audio stream", "content": {"audio/mp3": {"example": "Binary audio data"}}},
|
654 |
-
400: {"description": "Invalid input"},
|
655 |
401: {"description": "Unauthorized - Token required"},
|
656 |
429: {"description": "Rate limit exceeded"},
|
657 |
504: {"description": "External API timeout"},
|
@@ -660,11 +673,14 @@ class SupportedLanguage(str, Enum):
|
|
660 |
@limiter.limit(settings.speech_rate_limit)
|
661 |
async def speech_to_speech(
|
662 |
request: Request,
|
663 |
-
file: UploadFile = File(..., description="
|
664 |
language: SupportedLanguage = Query(..., description="Language of the audio (kannada, hindi, tamil)"),
|
665 |
-
credentials: HTTPAuthorizationCredentials = Depends(bearer_scheme)
|
|
|
666 |
) -> StreamingResponse:
|
667 |
user_id = await get_current_user(credentials)
|
|
|
|
|
668 |
logger.info("Processing speech-to-speech request", extra={
|
669 |
"endpoint": "/v1/speech_to_speech",
|
670 |
"audio_filename": file.filename,
|
@@ -674,7 +690,8 @@ async def speech_to_speech(
|
|
674 |
})
|
675 |
|
676 |
try:
|
677 |
-
|
|
|
678 |
files = {"file": (file.filename, file_content, file.content_type)}
|
679 |
external_url = f"https://slabstech-dhwani-internal-api-server.hf.space/v1/speech_to_speech?language={language}"
|
680 |
|
|
|
5 |
from abc import ABC, abstractmethod
|
6 |
|
7 |
import uvicorn
|
8 |
+
from fastapi import Depends, FastAPI, File, HTTPException, Query, Request, UploadFile, Form, Header
|
9 |
from fastapi.middleware.cors import CORSMiddleware
|
10 |
from fastapi.responses import JSONResponse, RedirectResponse, StreamingResponse
|
11 |
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
|
|
14 |
from slowapi.util import get_remote_address
|
15 |
import requests
|
16 |
from PIL import Image
|
17 |
+
import base64
|
18 |
+
from Crypto.Cipher import AES
|
19 |
|
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
|
|
|
60 |
user_id = await get_current_user(credentials)
|
61 |
return user_id
|
62 |
except Exception:
|
63 |
+
return get_remote_address(request)
|
64 |
|
65 |
limiter = Limiter(key_func=get_user_id_for_rate_limit)
|
66 |
|
|
|
157 |
@app.post("/v1/token",
|
158 |
response_model=TokenResponse,
|
159 |
summary="User Login",
|
160 |
+
description="Authenticate a user with encrypted email and device token to obtain an access token and refresh token. Requires X-Session-Key header.",
|
161 |
tags=["Authentication"],
|
162 |
responses={
|
163 |
200: {"description": "Successful login", "model": TokenResponse},
|
164 |
+
400: {"description": "Invalid encrypted data"},
|
165 |
+
401: {"description": "Invalid email or device token"}
|
166 |
})
|
167 |
+
async def token(
|
168 |
+
login_request: LoginRequest,
|
169 |
+
x_session_key: str = Header(..., alias="X-Session-Key")
|
170 |
+
):
|
171 |
+
return await login(login_request, x_session_key)
|
172 |
|
173 |
@app.post("/v1/refresh",
|
174 |
response_model=TokenResponse,
|
|
|
201 |
@app.post("/v1/app/register",
|
202 |
response_model=TokenResponse,
|
203 |
summary="Register New App User",
|
204 |
+
description="Create a new user account for the mobile app using an encrypted email and device token. Returns an access token and refresh token. Rate limited to 5 requests per minute per IP. Requires X-Session-Key header.",
|
205 |
tags=["Authentication"],
|
206 |
responses={
|
207 |
200: {"description": "User registered successfully", "model": TokenResponse},
|
208 |
+
400: {"description": "Email already registered or invalid encrypted data"},
|
209 |
429: {"description": "Rate limit exceeded"}
|
210 |
})
|
211 |
@limiter.limit(settings.speech_rate_limit)
|
212 |
async def app_register_user(
|
213 |
request: Request,
|
214 |
+
register_request: RegisterRequest,
|
215 |
+
x_session_key: str = Header(..., alias="X-Session-Key")
|
216 |
):
|
217 |
+
logger.info(f"App registration attempt")
|
218 |
+
return await app_register(register_request, x_session_key)
|
219 |
|
220 |
@app.post("/v1/audio/speech",
|
221 |
summary="Generate Speech from Text",
|
|
|
558 |
|
559 |
class VisualQueryRequest(BaseModel):
|
560 |
query: str
|
561 |
+
src_lang: str = "kan_Knda"
|
562 |
+
tgt_lang: str = "kan_Knda"
|
563 |
|
564 |
@field_validator("query")
|
565 |
def query_must_be_valid(cls, v):
|
|
|
640 |
except ValueError as e:
|
641 |
logger.error(f"Invalid JSON response: {str(e)}")
|
642 |
raise HTTPException(status_code=500, detail="Invalid response format from visual query service")
|
643 |
+
|
|
|
|
|
644 |
from enum import Enum
|
645 |
|
|
|
646 |
class SupportedLanguage(str, Enum):
|
647 |
kannada = "kannada"
|
648 |
hindi = "hindi"
|
649 |
tamil = "tamil"
|
650 |
|
651 |
+
def decrypt_audio(encrypted_data: bytes, key: bytes) -> bytes:
|
652 |
+
try:
|
653 |
+
nonce, ciphertext = encrypted_data[:12], encrypted_data[12:]
|
654 |
+
cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
|
655 |
+
plaintext = cipher.decrypt_and_verify(ciphertext[:-16], ciphertext[-16:])
|
656 |
+
return plaintext
|
657 |
+
except Exception as e:
|
658 |
+
logger.error(f"Audio decryption failed: {str(e)}")
|
659 |
+
raise HTTPException(status_code=400, detail="Invalid encrypted audio")
|
660 |
+
|
661 |
@app.post("/v1/speech_to_speech",
|
662 |
summary="Speech-to-Speech Conversion",
|
663 |
+
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.",
|
664 |
tags=["Audio"],
|
665 |
responses={
|
666 |
200: {"description": "Audio stream", "content": {"audio/mp3": {"example": "Binary audio data"}}},
|
667 |
+
400: {"description": "Invalid input or encrypted audio"},
|
668 |
401: {"description": "Unauthorized - Token required"},
|
669 |
429: {"description": "Rate limit exceeded"},
|
670 |
504: {"description": "External API timeout"},
|
|
|
673 |
@limiter.limit(settings.speech_rate_limit)
|
674 |
async def speech_to_speech(
|
675 |
request: Request,
|
676 |
+
file: UploadFile = File(..., description="Encrypted audio file to process"),
|
677 |
language: SupportedLanguage = Query(..., description="Language of the audio (kannada, hindi, tamil)"),
|
678 |
+
credentials: HTTPAuthorizationCredentials = Depends(bearer_scheme),
|
679 |
+
x_session_key: str = Header(..., alias="X-Session-Key")
|
680 |
) -> StreamingResponse:
|
681 |
user_id = await get_current_user(credentials)
|
682 |
+
session_key = base64.b64decode(x_session_key)
|
683 |
+
|
684 |
logger.info("Processing speech-to-speech request", extra={
|
685 |
"endpoint": "/v1/speech_to_speech",
|
686 |
"audio_filename": file.filename,
|
|
|
690 |
})
|
691 |
|
692 |
try:
|
693 |
+
encrypted_content = await file.read()
|
694 |
+
file_content = decrypt_audio(encrypted_content, session_key)
|
695 |
files = {"file": (file.filename, file_content, file.content_type)}
|
696 |
external_url = f"https://slabstech-dhwani-internal-api-server.hf.space/v1/speech_to_speech?language={language}"
|
697 |
|
src/server/utils/auth.py
CHANGED
@@ -10,6 +10,9 @@ from sqlalchemy.ext.declarative import declarative_base
|
|
10 |
from sqlalchemy.orm import sessionmaker
|
11 |
from passlib.context import CryptContext
|
12 |
import os
|
|
|
|
|
|
|
13 |
|
14 |
# SQLite database setup with Hugging Face persistent storage
|
15 |
DATABASE_PATH = "/data/users.db"
|
@@ -22,7 +25,8 @@ class User(Base):
|
|
22 |
__tablename__ = "users"
|
23 |
username = Column(String, primary_key=True, index=True)
|
24 |
password = Column(String) # Stores hashed passwords
|
25 |
-
is_admin = Column(Boolean, default=False)
|
|
|
26 |
|
27 |
# Ensure the /data directory exists
|
28 |
os.makedirs(os.path.dirname(DATABASE_PATH), exist_ok=True)
|
@@ -35,8 +39,8 @@ pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
|
35 |
|
36 |
class Settings(BaseSettings):
|
37 |
api_key_secret: str = Field(..., env="API_KEY_SECRET")
|
38 |
-
token_expiration_minutes: int = Field(1440, env="TOKEN_EXPIRATION_MINUTES")
|
39 |
-
refresh_token_expiration_days: int = Field(7, env="REFRESH_TOKEN_EXPIRATION_DAYS")
|
40 |
llm_model_name: str = "google/gemma-3-4b-it"
|
41 |
max_tokens: int = 512
|
42 |
host: str = "0.0.0.0"
|
@@ -61,19 +65,19 @@ settings = Settings()
|
|
61 |
def seed_initial_data():
|
62 |
db = SessionLocal()
|
63 |
try:
|
64 |
-
# Seed test user (non-admin) with a device token-like password
|
65 |
test_username = "[email protected]"
|
66 |
if not db.query(User).filter_by(username=test_username).first():
|
67 |
-
test_device_token = "550e8400-e29b-41d4-a716-446655440000"
|
68 |
hashed_password = pwd_context.hash(test_device_token)
|
69 |
-
|
|
|
70 |
db.commit()
|
71 |
-
# Seed admin user using environment variables
|
72 |
admin_username = settings.default_admin_username
|
73 |
admin_password = settings.default_admin_password
|
74 |
if not db.query(User).filter_by(username=admin_username).first():
|
75 |
hashed_password = pwd_context.hash(admin_password)
|
76 |
-
|
|
|
77 |
db.commit()
|
78 |
logger.info(f"Seeded initial data: test user '{test_username}', admin user '{admin_username}'")
|
79 |
except Exception as e:
|
@@ -82,10 +86,8 @@ def seed_initial_data():
|
|
82 |
finally:
|
83 |
db.close()
|
84 |
|
85 |
-
# Initialize database with seed data
|
86 |
seed_initial_data()
|
87 |
|
88 |
-
# Use HTTPBearer
|
89 |
bearer_scheme = HTTPBearer()
|
90 |
|
91 |
class TokenPayload(BaseModel):
|
@@ -106,6 +108,17 @@ class RegisterRequest(BaseModel):
|
|
106 |
username: str
|
107 |
password: str
|
108 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
109 |
async def create_access_token(user_id: str) -> dict:
|
110 |
expire = datetime.utcnow() + timedelta(minutes=settings.token_expiration_minutes)
|
111 |
payload = {"sub": user_id, "exp": expire.timestamp(), "type": "access"}
|
@@ -166,14 +179,28 @@ async def get_current_user_with_admin(credentials: HTTPAuthorizationCredentials
|
|
166 |
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Admin access required")
|
167 |
return user_id
|
168 |
|
169 |
-
async def login(login_request: LoginRequest) -> TokenResponse:
|
170 |
db = SessionLocal()
|
171 |
-
|
172 |
-
|
173 |
-
|
174 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
175 |
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid email or device token")
|
176 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
177 |
return TokenResponse(access_token=tokens["access_token"], refresh_token=tokens["refresh_token"], token_type="bearer")
|
178 |
|
179 |
async def register(register_request: RegisterRequest, current_user: str = Depends(get_current_user_with_admin)) -> TokenResponse:
|
@@ -194,22 +221,30 @@ async def register(register_request: RegisterRequest, current_user: str = Depend
|
|
194 |
logger.info(f"Registered and generated token for user: {register_request.username} by admin {current_user}")
|
195 |
return TokenResponse(access_token=tokens["access_token"], refresh_token=tokens["refresh_token"], token_type="bearer")
|
196 |
|
197 |
-
async def app_register(register_request: RegisterRequest) -> TokenResponse:
|
198 |
db = SessionLocal()
|
199 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
200 |
if existing_user:
|
201 |
db.close()
|
202 |
-
logger.warning(f"App registration failed: Email {
|
203 |
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Email already registered")
|
204 |
|
205 |
-
hashed_password = pwd_context.hash(
|
206 |
-
new_user = User(username=
|
207 |
db.add(new_user)
|
208 |
db.commit()
|
209 |
db.close()
|
210 |
|
211 |
-
tokens = await create_access_token(user_id=
|
212 |
-
logger.info(f"App registered new user: {
|
213 |
return TokenResponse(access_token=tokens["access_token"], refresh_token=tokens["refresh_token"], token_type="bearer")
|
214 |
|
215 |
async def refresh_token(credentials: HTTPAuthorizationCredentials = Depends(bearer_scheme)) -> TokenResponse:
|
|
|
10 |
from sqlalchemy.orm import sessionmaker
|
11 |
from passlib.context import CryptContext
|
12 |
import os
|
13 |
+
import base64
|
14 |
+
from Crypto.Cipher import AES
|
15 |
+
from Crypto.Random import get_random_bytes
|
16 |
|
17 |
# SQLite database setup with Hugging Face persistent storage
|
18 |
DATABASE_PATH = "/data/users.db"
|
|
|
25 |
__tablename__ = "users"
|
26 |
username = Column(String, primary_key=True, index=True)
|
27 |
password = Column(String) # Stores hashed passwords
|
28 |
+
is_admin = Column(Boolean, default=False)
|
29 |
+
session_key = Column(String, nullable=True) # Stores base64-encoded session key
|
30 |
|
31 |
# Ensure the /data directory exists
|
32 |
os.makedirs(os.path.dirname(DATABASE_PATH), exist_ok=True)
|
|
|
39 |
|
40 |
class Settings(BaseSettings):
|
41 |
api_key_secret: str = Field(..., env="API_KEY_SECRET")
|
42 |
+
token_expiration_minutes: int = Field(1440, env="TOKEN_EXPIRATION_MINUTES")
|
43 |
+
refresh_token_expiration_days: int = Field(7, env="REFRESH_TOKEN_EXPIRATION_DAYS")
|
44 |
llm_model_name: str = "google/gemma-3-4b-it"
|
45 |
max_tokens: int = 512
|
46 |
host: str = "0.0.0.0"
|
|
|
65 |
def seed_initial_data():
|
66 |
db = SessionLocal()
|
67 |
try:
|
|
|
68 |
test_username = "[email protected]"
|
69 |
if not db.query(User).filter_by(username=test_username).first():
|
70 |
+
test_device_token = "550e8400-e29b-41d4-a716-446655440000"
|
71 |
hashed_password = pwd_context.hash(test_device_token)
|
72 |
+
session_key = base64.b64encode(get_random_bytes(16)).decode('utf-8')
|
73 |
+
db.add(User(username=test_username, password=hashed_password, is_admin=False, session_key=session_key))
|
74 |
db.commit()
|
|
|
75 |
admin_username = settings.default_admin_username
|
76 |
admin_password = settings.default_admin_password
|
77 |
if not db.query(User).filter_by(username=admin_username).first():
|
78 |
hashed_password = pwd_context.hash(admin_password)
|
79 |
+
session_key = base64.b64encode(get_random_bytes(16)).decode('utf-8')
|
80 |
+
db.add(User(username=admin_username, password=hashed_password, is_admin=True, session_key=session_key))
|
81 |
db.commit()
|
82 |
logger.info(f"Seeded initial data: test user '{test_username}', admin user '{admin_username}'")
|
83 |
except Exception as e:
|
|
|
86 |
finally:
|
87 |
db.close()
|
88 |
|
|
|
89 |
seed_initial_data()
|
90 |
|
|
|
91 |
bearer_scheme = HTTPBearer()
|
92 |
|
93 |
class TokenPayload(BaseModel):
|
|
|
108 |
username: str
|
109 |
password: str
|
110 |
|
111 |
+
def decrypt_data(encrypted_data: str, key: bytes) -> str:
|
112 |
+
try:
|
113 |
+
data = base64.b64decode(encrypted_data)
|
114 |
+
nonce, ciphertext = data[:12], data[12:]
|
115 |
+
cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
|
116 |
+
plaintext = cipher.decrypt_and_verify(ciphertext[:-16], ciphertext[-16:])
|
117 |
+
return plaintext.decode('utf-8')
|
118 |
+
except Exception as e:
|
119 |
+
logger.error(f"Decryption failed: {str(e)}")
|
120 |
+
raise HTTPException(status_code=400, detail="Invalid encrypted data")
|
121 |
+
|
122 |
async def create_access_token(user_id: str) -> dict:
|
123 |
expire = datetime.utcnow() + timedelta(minutes=settings.token_expiration_minutes)
|
124 |
payload = {"sub": user_id, "exp": expire.timestamp(), "type": "access"}
|
|
|
179 |
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Admin access required")
|
180 |
return user_id
|
181 |
|
182 |
+
async def login(login_request: LoginRequest, session_key_b64: str) -> TokenResponse:
|
183 |
db = SessionLocal()
|
184 |
+
session_key = base64.b64decode(session_key_b64)
|
185 |
+
try:
|
186 |
+
username = decrypt_data(login_request.username, session_key)
|
187 |
+
password = decrypt_data(login_request.password, session_key)
|
188 |
+
except:
|
189 |
+
db.close()
|
190 |
+
raise HTTPException(status_code=400, detail="Invalid encrypted data")
|
191 |
+
|
192 |
+
user = db.query(User).filter_by(username=username).first()
|
193 |
+
if not user or not pwd_context.verify(password, user.password):
|
194 |
+
db.close()
|
195 |
+
logger.warning(f"Login failed for user: {username}")
|
196 |
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid email or device token")
|
197 |
+
|
198 |
+
if user.session_key != session_key_b64:
|
199 |
+
user.session_key = session_key_b64
|
200 |
+
db.commit()
|
201 |
+
db.close()
|
202 |
+
|
203 |
+
tokens = await create_access_token(user_id=username)
|
204 |
return TokenResponse(access_token=tokens["access_token"], refresh_token=tokens["refresh_token"], token_type="bearer")
|
205 |
|
206 |
async def register(register_request: RegisterRequest, current_user: str = Depends(get_current_user_with_admin)) -> TokenResponse:
|
|
|
221 |
logger.info(f"Registered and generated token for user: {register_request.username} by admin {current_user}")
|
222 |
return TokenResponse(access_token=tokens["access_token"], refresh_token=tokens["refresh_token"], token_type="bearer")
|
223 |
|
224 |
+
async def app_register(register_request: RegisterRequest, session_key_b64: str) -> TokenResponse:
|
225 |
db = SessionLocal()
|
226 |
+
session_key = base64.b64decode(session_key_b64)
|
227 |
+
try:
|
228 |
+
username = decrypt_data(register_request.username, session_key)
|
229 |
+
password = decrypt_data(register_request.password, session_key)
|
230 |
+
except:
|
231 |
+
db.close()
|
232 |
+
raise HTTPException(status_code=400, detail="Invalid encrypted data")
|
233 |
+
|
234 |
+
existing_user = db.query(User).filter_by(username=username).first()
|
235 |
if existing_user:
|
236 |
db.close()
|
237 |
+
logger.warning(f"App registration failed: Email {username} already exists")
|
238 |
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Email already registered")
|
239 |
|
240 |
+
hashed_password = pwd_context.hash(password)
|
241 |
+
new_user = User(username=username, password=hashed_password, is_admin=False, session_key=session_key_b64)
|
242 |
db.add(new_user)
|
243 |
db.commit()
|
244 |
db.close()
|
245 |
|
246 |
+
tokens = await create_access_token(user_id=username)
|
247 |
+
logger.info(f"App registered new user: {username}")
|
248 |
return TokenResponse(access_token=tokens["access_token"], refresh_token=tokens["refresh_token"], token_type="bearer")
|
249 |
|
250 |
async def refresh_token(credentials: HTTPAuthorizationCredentials = Depends(bearer_scheme)) -> TokenResponse:
|