cp
This commit is contained in:
parent
d65e4e64f3
commit
c00f0e641f
@ -1,7 +1,7 @@
|
||||
{
|
||||
"version": 4,
|
||||
"terraform_version": "1.10.1",
|
||||
"serial": 393,
|
||||
"serial": 399,
|
||||
"lineage": "a183cd95-f987-8698-c6dd-84e933c394a5",
|
||||
"outputs": {
|
||||
"cloud_function_name": {
|
||||
@ -824,7 +824,7 @@
|
||||
},
|
||||
{
|
||||
"type": "get_attr",
|
||||
"value": "disk_encryption_key_raw"
|
||||
"value": "disk_encryption_key_rsa"
|
||||
}
|
||||
],
|
||||
[
|
||||
@ -841,7 +841,7 @@
|
||||
},
|
||||
{
|
||||
"type": "get_attr",
|
||||
"value": "disk_encryption_key_rsa"
|
||||
"value": "disk_encryption_key_raw"
|
||||
}
|
||||
]
|
||||
],
|
||||
@ -870,8 +870,8 @@
|
||||
"database_edition": "STANDARD",
|
||||
"delete_protection_state": "DELETE_PROTECTION_DISABLED",
|
||||
"deletion_policy": "ABANDON",
|
||||
"earliest_version_time": "2025-05-24T21:09:24.677010Z",
|
||||
"etag": "IPScrICPvY0DMKrW4vCEvY0D",
|
||||
"earliest_version_time": "2025-05-24T21:16:19.051906Z",
|
||||
"etag": "INn0mIORvY0DMKrW4vCEvY0D",
|
||||
"id": "projects/gen-lang-client-0424120530/databases/sereact-imagedb",
|
||||
"key_prefix": "",
|
||||
"location_id": "us-central1",
|
||||
@ -1505,7 +1505,7 @@
|
||||
"md5hash": "NNgXJau9T0I95x7NQhXRFg==",
|
||||
"md5hexhash": "34d81725abbd4f423de71ecd4215d116",
|
||||
"media_link": "https://storage.googleapis.com/download/storage/v1/b/gen-lang-client-0424120530-cloud-function-source/o/function-source-34d81725abbd4f423de71ecd4215d116.zip?generation=1748124439573408\u0026alt=media",
|
||||
"metadata": null,
|
||||
"metadata": {},
|
||||
"name": "function-source-34d81725abbd4f423de71ecd4215d116.zip",
|
||||
"output_name": "function-source-34d81725abbd4f423de71ecd4215d116.zip",
|
||||
"retention": [],
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"version": 4,
|
||||
"terraform_version": "1.10.1",
|
||||
"serial": 388,
|
||||
"serial": 397,
|
||||
"lineage": "a183cd95-f987-8698-c6dd-84e933c394a5",
|
||||
"outputs": {
|
||||
"cloud_function_name": {
|
||||
@ -98,16 +98,16 @@
|
||||
"attributes": {
|
||||
"exclude_symlink_directories": null,
|
||||
"excludes": null,
|
||||
"id": "ebb70c54eaebd24049805bcc1425349f70bc582d",
|
||||
"output_base64sha256": "+Q18L9q1o61gbnGJlSvTmwG9cRv1Qwzf8GI95No2Rb4=",
|
||||
"output_base64sha512": "tK0wkH07eL77+ytrOI8lcHATsN/nP0f/CYq0uzrrlhaRbJ+wsO1/6y0tmeX1hF6xqxW5ZDYTrhrSnayA+2afwQ==",
|
||||
"id": "045029ac803155784c12f8d587fee56b85b1fbe9",
|
||||
"output_base64sha256": "b/FgNMMT30JSXfrLRXNkWeNc6i22YAmT3YwQRTw1+A4=",
|
||||
"output_base64sha512": "7GDDTkHwwQVAlwSxe7yzgtGccMNIRCQ7t72ZRk7bcfDI1tzpruhJ5G/0AbrUMXWQO6LffnWtwumQ7XdFHAIzBA==",
|
||||
"output_file_mode": null,
|
||||
"output_md5": "95eb8ea5146b66f5b26bb830e3f0eab6",
|
||||
"output_md5": "34d81725abbd4f423de71ecd4215d116",
|
||||
"output_path": "./function-source.zip",
|
||||
"output_sha": "ebb70c54eaebd24049805bcc1425349f70bc582d",
|
||||
"output_sha256": "f90d7c2fdab5a3ad606e7189952bd39b01bd711bf5430cdff0623de4da3645be",
|
||||
"output_sha512": "b4ad30907d3b78befbfb2b6b388f25707013b0dfe73f47ff098ab4bb3aeb9616916c9fb0b0ed7feb2d2d99e5f5845eb1ab15b9643613ae1ad29dac80fb669fc1",
|
||||
"output_size": 4781,
|
||||
"output_sha": "045029ac803155784c12f8d587fee56b85b1fbe9",
|
||||
"output_sha256": "6ff16034c313df42525dfacb45736459e35cea2db6600993dd8c10453c35f80e",
|
||||
"output_sha512": "ec60c34e41f0c105409704b17bbcb382d19c70c34844243bb7bd99464edb71f0c8d6dce9aee849e46ff401bad43175903ba2df7e75adc2e990ed77451c023304",
|
||||
"output_size": 5014,
|
||||
"source": [],
|
||||
"source_content": null,
|
||||
"source_content_filename": null,
|
||||
@ -471,7 +471,7 @@
|
||||
"automatic_update_policy": [
|
||||
{}
|
||||
],
|
||||
"build": "projects/761163285547/locations/us-central1/builds/d3341e98-07e4-49de-8dc7-3d53e4d2570a",
|
||||
"build": "projects/761163285547/locations/us-central1/builds/1b8e28d1-ee4d-4d2f-acf2-47e2b03aa421",
|
||||
"docker_repository": "projects/gen-lang-client-0424120530/locations/us-central1/repositories/gcf-artifacts",
|
||||
"entry_point": "process_image_embedding",
|
||||
"environment_variables": {},
|
||||
@ -485,7 +485,7 @@
|
||||
{
|
||||
"bucket": "gen-lang-client-0424120530-cloud-function-source",
|
||||
"generation": 1748123369545880,
|
||||
"object": "function-source-95eb8ea5146b66f5b26bb830e3f0eab6.zip"
|
||||
"object": "function-source-34d81725abbd4f423de71ecd4215d116.zip"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -511,7 +511,7 @@
|
||||
],
|
||||
"id": "projects/gen-lang-client-0424120530/locations/us-central1/functions/process-image-embedding",
|
||||
"kms_key_name": "",
|
||||
"labels": null,
|
||||
"labels": {},
|
||||
"location": "us-central1",
|
||||
"name": "process-image-embedding",
|
||||
"project": "gen-lang-client-0424120530",
|
||||
@ -522,11 +522,17 @@
|
||||
"available_memory": "512M",
|
||||
"binary_authorization_policy": "",
|
||||
"environment_variables": {
|
||||
"FIRESTORE_DATABASE_NAME": "sereact-imagedb",
|
||||
"FIRESTORE_PROJECT_ID": "gen-lang-client-0424120530",
|
||||
"GCS_BUCKET_NAME": "sereact-images",
|
||||
"LOG_EXECUTION_ID": "true",
|
||||
"LOG_LEVEL": "INFO",
|
||||
"QDRANT_API_KEY": "",
|
||||
"QDRANT_COLLECTION": "image_vectors",
|
||||
"QDRANT_HOST": "34.71.6.1",
|
||||
"QDRANT_PORT": "6333"
|
||||
"QDRANT_HTTPS": "false",
|
||||
"QDRANT_PORT": "6333",
|
||||
"VISION_API_ENABLED": "true"
|
||||
},
|
||||
"gcf_uri": "",
|
||||
"ingress_settings": "ALLOW_ALL",
|
||||
@ -548,7 +554,7 @@
|
||||
"goog-terraform-provisioned": "true"
|
||||
},
|
||||
"timeouts": null,
|
||||
"update_time": "2025-05-24T21:52:26.933576416Z",
|
||||
"update_time": "2025-05-24T22:08:16.899711009Z",
|
||||
"url": "https://us-central1-gen-lang-client-0424120530.cloudfunctions.net/process-image-embedding"
|
||||
},
|
||||
"sensitive_attributes": [
|
||||
@ -801,7 +807,18 @@
|
||||
[
|
||||
{
|
||||
"type": "get_attr",
|
||||
"value": "metadata_startup_script"
|
||||
"value": "boot_disk"
|
||||
},
|
||||
{
|
||||
"type": "index",
|
||||
"value": {
|
||||
"value": 0,
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "get_attr",
|
||||
"value": "disk_encryption_key_rsa"
|
||||
}
|
||||
],
|
||||
[
|
||||
@ -824,18 +841,7 @@
|
||||
[
|
||||
{
|
||||
"type": "get_attr",
|
||||
"value": "boot_disk"
|
||||
},
|
||||
{
|
||||
"type": "index",
|
||||
"value": {
|
||||
"value": 0,
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "get_attr",
|
||||
"value": "disk_encryption_key_rsa"
|
||||
"value": "metadata_startup_script"
|
||||
}
|
||||
]
|
||||
],
|
||||
@ -864,8 +870,8 @@
|
||||
"database_edition": "STANDARD",
|
||||
"delete_protection_state": "DELETE_PROTECTION_DISABLED",
|
||||
"deletion_policy": "ABANDON",
|
||||
"earliest_version_time": "2025-05-24T21:09:24.677010Z",
|
||||
"etag": "IOXfuveKvY0DMKrW4vCEvY0D",
|
||||
"earliest_version_time": "2025-05-24T21:15:22.382696Z",
|
||||
"etag": "IPeLluiQvY0DMKrW4vCEvY0D",
|
||||
"id": "projects/gen-lang-client-0424120530/databases/sereact-imagedb",
|
||||
"key_prefix": "",
|
||||
"location_id": "us-central1",
|
||||
@ -896,7 +902,7 @@
|
||||
"schema_version": 0,
|
||||
"attributes": {
|
||||
"condition": [],
|
||||
"etag": "BwY16K9kGDo=",
|
||||
"etag": "BwY16LCINIE=",
|
||||
"id": "gen-lang-client-0424120530/roles/eventarc.eventReceiver/serviceAccount:761163285547-compute@developer.gserviceaccount.com",
|
||||
"member": "serviceAccount:761163285547-compute@developer.gserviceaccount.com",
|
||||
"project": "gen-lang-client-0424120530",
|
||||
@ -920,7 +926,7 @@
|
||||
"schema_version": 0,
|
||||
"attributes": {
|
||||
"condition": [],
|
||||
"etag": "BwY16Kj5NHI=",
|
||||
"etag": "BwY16LCINIE=",
|
||||
"id": "gen-lang-client-0424120530/roles/datastore.user/serviceAccount:761163285547-compute@developer.gserviceaccount.com",
|
||||
"member": "serviceAccount:761163285547-compute@developer.gserviceaccount.com",
|
||||
"project": "gen-lang-client-0424120530",
|
||||
@ -944,7 +950,7 @@
|
||||
"schema_version": 0,
|
||||
"attributes": {
|
||||
"condition": [],
|
||||
"etag": "BwY16K9kGDo=",
|
||||
"etag": "BwY16LCINIE=",
|
||||
"id": "gen-lang-client-0424120530/roles/pubsub.subscriber/serviceAccount:761163285547-compute@developer.gserviceaccount.com",
|
||||
"member": "serviceAccount:761163285547-compute@developer.gserviceaccount.com",
|
||||
"project": "gen-lang-client-0424120530",
|
||||
@ -968,7 +974,7 @@
|
||||
"schema_version": 0,
|
||||
"attributes": {
|
||||
"condition": [],
|
||||
"etag": "BwY16Kj5NHI=",
|
||||
"etag": "BwY16LCINIE=",
|
||||
"id": "gen-lang-client-0424120530/roles/storage.objectViewer/serviceAccount:761163285547-compute@developer.gserviceaccount.com",
|
||||
"member": "serviceAccount:761163285547-compute@developer.gserviceaccount.com",
|
||||
"project": "gen-lang-client-0424120530",
|
||||
@ -992,7 +998,7 @@
|
||||
"schema_version": 0,
|
||||
"attributes": {
|
||||
"condition": [],
|
||||
"etag": "BwY16Kj5NHI=",
|
||||
"etag": "BwY16LCINIE=",
|
||||
"id": "gen-lang-client-0424120530/roles/ml.developer/serviceAccount:761163285547-compute@developer.gserviceaccount.com",
|
||||
"member": "serviceAccount:761163285547-compute@developer.gserviceaccount.com",
|
||||
"project": "gen-lang-client-0424120530",
|
||||
@ -1489,21 +1495,21 @@
|
||||
"content_encoding": "",
|
||||
"content_language": "",
|
||||
"content_type": "application/zip",
|
||||
"crc32c": "tbmr3A==",
|
||||
"crc32c": "YXAlNA==",
|
||||
"customer_encryption": [],
|
||||
"detect_md5hash": "leuOpRRrZvWya7gw4/Dqtg==",
|
||||
"detect_md5hash": "NNgXJau9T0I95x7NQhXRFg==",
|
||||
"event_based_hold": false,
|
||||
"generation": 1748123256911890,
|
||||
"id": "gen-lang-client-0424120530-cloud-function-source-function-source-95eb8ea5146b66f5b26bb830e3f0eab6.zip",
|
||||
"generation": 1748124439573408,
|
||||
"id": "gen-lang-client-0424120530-cloud-function-source-function-source-34d81725abbd4f423de71ecd4215d116.zip",
|
||||
"kms_key_name": "",
|
||||
"md5hash": "leuOpRRrZvWya7gw4/Dqtg==",
|
||||
"md5hexhash": "95eb8ea5146b66f5b26bb830e3f0eab6",
|
||||
"media_link": "https://storage.googleapis.com/download/storage/v1/b/gen-lang-client-0424120530-cloud-function-source/o/function-source-95eb8ea5146b66f5b26bb830e3f0eab6.zip?generation=1748123256911890\u0026alt=media",
|
||||
"md5hash": "NNgXJau9T0I95x7NQhXRFg==",
|
||||
"md5hexhash": "34d81725abbd4f423de71ecd4215d116",
|
||||
"media_link": "https://storage.googleapis.com/download/storage/v1/b/gen-lang-client-0424120530-cloud-function-source/o/function-source-34d81725abbd4f423de71ecd4215d116.zip?generation=1748124439573408\u0026alt=media",
|
||||
"metadata": {},
|
||||
"name": "function-source-95eb8ea5146b66f5b26bb830e3f0eab6.zip",
|
||||
"output_name": "function-source-95eb8ea5146b66f5b26bb830e3f0eab6.zip",
|
||||
"name": "function-source-34d81725abbd4f423de71ecd4215d116.zip",
|
||||
"output_name": "function-source-34d81725abbd4f423de71ecd4215d116.zip",
|
||||
"retention": [],
|
||||
"self_link": "https://www.googleapis.com/storage/v1/b/gen-lang-client-0424120530-cloud-function-source/o/function-source-95eb8ea5146b66f5b26bb830e3f0eab6.zip",
|
||||
"self_link": "https://www.googleapis.com/storage/v1/b/gen-lang-client-0424120530-cloud-function-source/o/function-source-34d81725abbd4f423de71ecd4215d116.zip",
|
||||
"source": "./function-source.zip",
|
||||
"storage_class": "STANDARD",
|
||||
"temporary_hold": false,
|
||||
|
||||
13
main.py
13
main.py
@ -140,16 +140,9 @@ def custom_openapi():
|
||||
if "schemas" not in openapi_schema["components"]:
|
||||
openapi_schema["components"]["schemas"] = {}
|
||||
|
||||
# Apply security to endpoints except auth, users, teams, and API key creation endpoints
|
||||
for path in openapi_schema["paths"]:
|
||||
# Exclude auth endpoints, users endpoints, teams endpoints, and API key creation
|
||||
if not (path.startswith("/api/v1/auth") or
|
||||
path.startswith("/api/v1/users") or
|
||||
path.startswith("/api/v1/teams")):
|
||||
for method in openapi_schema["paths"][path]:
|
||||
if method.lower() in ["get", "post", "put", "delete", "patch"]:
|
||||
if "security" not in openapi_schema["paths"][path][method]:
|
||||
openapi_schema["paths"][path][method]["security"] = [{"ApiKeyAuth": []}]
|
||||
# Note: Authentication is now handled properly in individual route modules
|
||||
# Public endpoints (auth, users, teams) don't require authentication
|
||||
# Protected endpoints (images, search) require API key authentication
|
||||
|
||||
app.openapi_schema = openapi_schema
|
||||
return app.openapi_schema
|
||||
|
||||
@ -10,7 +10,7 @@ from src.db.repositories.team_repository import team_repository
|
||||
from src.schemas.api_key import ApiKeyCreate, ApiKeyResponse, ApiKeyWithValueResponse, ApiKeyListResponse
|
||||
from src.schemas.team import TeamCreate
|
||||
from src.schemas.user import UserCreate
|
||||
from src.auth.security import generate_api_key, verify_api_key, calculate_expiry_date, is_expired, hash_api_key
|
||||
from src.auth.security import generate_api_key, verify_api_key, calculate_expiry_date, is_expired, hash_api_key, get_current_user
|
||||
from src.models.api_key import ApiKeyModel
|
||||
from src.models.team import TeamModel
|
||||
from src.models.user import UserModel
|
||||
@ -20,43 +20,6 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(tags=["Authentication"], prefix="/auth")
|
||||
|
||||
async def get_current_user(x_api_key: Optional[str] = Header(None)):
|
||||
"""
|
||||
Get the current user from API key
|
||||
"""
|
||||
if not x_api_key:
|
||||
raise HTTPException(status_code=401, detail="API key is required")
|
||||
|
||||
# Hash the API key
|
||||
hashed_key = hash_api_key(x_api_key)
|
||||
|
||||
# Get the key from the database
|
||||
api_key = await api_key_repository.get_by_key_hash(hashed_key)
|
||||
if not api_key:
|
||||
raise HTTPException(status_code=401, detail="Invalid API key")
|
||||
|
||||
# Check if the key is active
|
||||
if not api_key.is_active:
|
||||
raise HTTPException(status_code=401, detail="API key is inactive")
|
||||
|
||||
# Check if the key has expired
|
||||
if api_key.expiry_date and is_expired(api_key.expiry_date):
|
||||
raise HTTPException(status_code=401, detail="API key has expired")
|
||||
|
||||
# Get the user
|
||||
user = await user_repository.get_by_id(api_key.user_id)
|
||||
if not user:
|
||||
raise HTTPException(status_code=401, detail="User not found")
|
||||
|
||||
# Check if the user is active
|
||||
if not user.is_active:
|
||||
raise HTTPException(status_code=401, detail="User is inactive")
|
||||
|
||||
# Update last used timestamp
|
||||
await api_key_repository.update_last_used(api_key.id)
|
||||
|
||||
return user
|
||||
|
||||
@router.post("/bootstrap", response_model=ApiKeyWithValueResponse, status_code=201)
|
||||
async def bootstrap_initial_setup(
|
||||
team_name: str,
|
||||
|
||||
@ -5,7 +5,7 @@ from fastapi.responses import StreamingResponse
|
||||
from bson import ObjectId
|
||||
import io
|
||||
|
||||
from src.api.v1.auth import get_current_user
|
||||
from src.auth.security import get_current_user
|
||||
from src.db.repositories.image_repository import image_repository
|
||||
from src.services.storage import StorageService
|
||||
from src.services.image_processor import ImageProcessor
|
||||
|
||||
@ -2,7 +2,7 @@ import logging
|
||||
from typing import Optional, List, Dict, Any
|
||||
from fastapi import APIRouter, Depends, Query, Request, HTTPException
|
||||
|
||||
from src.api.v1.auth import get_current_user
|
||||
from src.auth.security import get_current_user
|
||||
from src.services.vector_db import VectorDatabaseService
|
||||
from src.services.embedding_service import EmbeddingService
|
||||
from src.db.repositories.image_repository import image_repository
|
||||
|
||||
@ -6,8 +6,15 @@ import hashlib
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional, Tuple
|
||||
|
||||
from fastapi import HTTPException, Security, Depends
|
||||
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
||||
from fastapi.security.api_key import APIKeyHeader
|
||||
|
||||
from src.config.config import settings
|
||||
|
||||
# API Key authentication scheme
|
||||
api_key_header = APIKeyHeader(name="X-API-Key", auto_error=False)
|
||||
|
||||
def generate_api_key(team_id: str, user_id: str) -> Tuple[str, str]:
|
||||
"""
|
||||
Generate a secure API key and its hashed value
|
||||
@ -89,3 +96,80 @@ def is_expired(expiry_date: datetime) -> bool:
|
||||
True if expired
|
||||
"""
|
||||
return datetime.utcnow() > expiry_date
|
||||
|
||||
async def get_api_key(api_key: Optional[str] = Security(api_key_header)) -> str:
|
||||
"""
|
||||
Dependency to extract and validate API key from request headers
|
||||
|
||||
Args:
|
||||
api_key: API key from X-API-Key header
|
||||
|
||||
Returns:
|
||||
Valid API key
|
||||
|
||||
Raises:
|
||||
HTTPException: If API key is missing or invalid
|
||||
"""
|
||||
if not api_key:
|
||||
raise HTTPException(
|
||||
status_code=401,
|
||||
detail="API key is required"
|
||||
)
|
||||
|
||||
return api_key
|
||||
|
||||
async def get_current_user(api_key: str = Depends(get_api_key)):
|
||||
"""
|
||||
Dependency to get the current authenticated user from API key
|
||||
|
||||
Args:
|
||||
api_key: Valid API key
|
||||
|
||||
Returns:
|
||||
User model
|
||||
|
||||
Raises:
|
||||
HTTPException: If API key is invalid or user not found
|
||||
"""
|
||||
from src.db.repositories.api_key_repository import api_key_repository
|
||||
from src.db.repositories.user_repository import user_repository
|
||||
|
||||
# Find API key in database
|
||||
api_key_obj = await api_key_repository.get_by_key_hash(hash_api_key(api_key))
|
||||
|
||||
if not api_key_obj:
|
||||
raise HTTPException(
|
||||
status_code=401,
|
||||
detail="Invalid API key"
|
||||
)
|
||||
|
||||
# Check if API key is expired
|
||||
if is_expired(api_key_obj.expiry_date):
|
||||
raise HTTPException(
|
||||
status_code=401,
|
||||
detail="API key has expired"
|
||||
)
|
||||
|
||||
# Check if API key is active
|
||||
if not api_key_obj.is_active:
|
||||
raise HTTPException(
|
||||
status_code=401,
|
||||
detail="API key is inactive"
|
||||
)
|
||||
|
||||
# Get user
|
||||
user = await user_repository.get_by_id(api_key_obj.user_id)
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=401,
|
||||
detail="User not found"
|
||||
)
|
||||
|
||||
# Check if user is active
|
||||
if not user.is_active:
|
||||
raise HTTPException(
|
||||
status_code=401,
|
||||
detail="User account is inactive"
|
||||
)
|
||||
|
||||
return user
|
||||
Loading…
x
Reference in New Issue
Block a user