This commit is contained in:
johnpccd 2025-05-25 00:17:00 +02:00
parent d65e4e64f3
commit c00f0e641f
7 changed files with 147 additions and 101 deletions

View File

@ -1,7 +1,7 @@
{ {
"version": 4, "version": 4,
"terraform_version": "1.10.1", "terraform_version": "1.10.1",
"serial": 393, "serial": 399,
"lineage": "a183cd95-f987-8698-c6dd-84e933c394a5", "lineage": "a183cd95-f987-8698-c6dd-84e933c394a5",
"outputs": { "outputs": {
"cloud_function_name": { "cloud_function_name": {
@ -824,7 +824,7 @@
}, },
{ {
"type": "get_attr", "type": "get_attr",
"value": "disk_encryption_key_raw" "value": "disk_encryption_key_rsa"
} }
], ],
[ [
@ -841,7 +841,7 @@
}, },
{ {
"type": "get_attr", "type": "get_attr",
"value": "disk_encryption_key_rsa" "value": "disk_encryption_key_raw"
} }
] ]
], ],
@ -870,8 +870,8 @@
"database_edition": "STANDARD", "database_edition": "STANDARD",
"delete_protection_state": "DELETE_PROTECTION_DISABLED", "delete_protection_state": "DELETE_PROTECTION_DISABLED",
"deletion_policy": "ABANDON", "deletion_policy": "ABANDON",
"earliest_version_time": "2025-05-24T21:09:24.677010Z", "earliest_version_time": "2025-05-24T21:16:19.051906Z",
"etag": "IPScrICPvY0DMKrW4vCEvY0D", "etag": "INn0mIORvY0DMKrW4vCEvY0D",
"id": "projects/gen-lang-client-0424120530/databases/sereact-imagedb", "id": "projects/gen-lang-client-0424120530/databases/sereact-imagedb",
"key_prefix": "", "key_prefix": "",
"location_id": "us-central1", "location_id": "us-central1",
@ -1505,7 +1505,7 @@
"md5hash": "NNgXJau9T0I95x7NQhXRFg==", "md5hash": "NNgXJau9T0I95x7NQhXRFg==",
"md5hexhash": "34d81725abbd4f423de71ecd4215d116", "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", "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", "name": "function-source-34d81725abbd4f423de71ecd4215d116.zip",
"output_name": "function-source-34d81725abbd4f423de71ecd4215d116.zip", "output_name": "function-source-34d81725abbd4f423de71ecd4215d116.zip",
"retention": [], "retention": [],

View File

@ -1,7 +1,7 @@
{ {
"version": 4, "version": 4,
"terraform_version": "1.10.1", "terraform_version": "1.10.1",
"serial": 388, "serial": 397,
"lineage": "a183cd95-f987-8698-c6dd-84e933c394a5", "lineage": "a183cd95-f987-8698-c6dd-84e933c394a5",
"outputs": { "outputs": {
"cloud_function_name": { "cloud_function_name": {
@ -98,16 +98,16 @@
"attributes": { "attributes": {
"exclude_symlink_directories": null, "exclude_symlink_directories": null,
"excludes": null, "excludes": null,
"id": "ebb70c54eaebd24049805bcc1425349f70bc582d", "id": "045029ac803155784c12f8d587fee56b85b1fbe9",
"output_base64sha256": "+Q18L9q1o61gbnGJlSvTmwG9cRv1Qwzf8GI95No2Rb4=", "output_base64sha256": "b/FgNMMT30JSXfrLRXNkWeNc6i22YAmT3YwQRTw1+A4=",
"output_base64sha512": "tK0wkH07eL77+ytrOI8lcHATsN/nP0f/CYq0uzrrlhaRbJ+wsO1/6y0tmeX1hF6xqxW5ZDYTrhrSnayA+2afwQ==", "output_base64sha512": "7GDDTkHwwQVAlwSxe7yzgtGccMNIRCQ7t72ZRk7bcfDI1tzpruhJ5G/0AbrUMXWQO6LffnWtwumQ7XdFHAIzBA==",
"output_file_mode": null, "output_file_mode": null,
"output_md5": "95eb8ea5146b66f5b26bb830e3f0eab6", "output_md5": "34d81725abbd4f423de71ecd4215d116",
"output_path": "./function-source.zip", "output_path": "./function-source.zip",
"output_sha": "ebb70c54eaebd24049805bcc1425349f70bc582d", "output_sha": "045029ac803155784c12f8d587fee56b85b1fbe9",
"output_sha256": "f90d7c2fdab5a3ad606e7189952bd39b01bd711bf5430cdff0623de4da3645be", "output_sha256": "6ff16034c313df42525dfacb45736459e35cea2db6600993dd8c10453c35f80e",
"output_sha512": "b4ad30907d3b78befbfb2b6b388f25707013b0dfe73f47ff098ab4bb3aeb9616916c9fb0b0ed7feb2d2d99e5f5845eb1ab15b9643613ae1ad29dac80fb669fc1", "output_sha512": "ec60c34e41f0c105409704b17bbcb382d19c70c34844243bb7bd99464edb71f0c8d6dce9aee849e46ff401bad43175903ba2df7e75adc2e990ed77451c023304",
"output_size": 4781, "output_size": 5014,
"source": [], "source": [],
"source_content": null, "source_content": null,
"source_content_filename": null, "source_content_filename": null,
@ -471,7 +471,7 @@
"automatic_update_policy": [ "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", "docker_repository": "projects/gen-lang-client-0424120530/locations/us-central1/repositories/gcf-artifacts",
"entry_point": "process_image_embedding", "entry_point": "process_image_embedding",
"environment_variables": {}, "environment_variables": {},
@ -485,7 +485,7 @@
{ {
"bucket": "gen-lang-client-0424120530-cloud-function-source", "bucket": "gen-lang-client-0424120530-cloud-function-source",
"generation": 1748123369545880, "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", "id": "projects/gen-lang-client-0424120530/locations/us-central1/functions/process-image-embedding",
"kms_key_name": "", "kms_key_name": "",
"labels": null, "labels": {},
"location": "us-central1", "location": "us-central1",
"name": "process-image-embedding", "name": "process-image-embedding",
"project": "gen-lang-client-0424120530", "project": "gen-lang-client-0424120530",
@ -522,11 +522,17 @@
"available_memory": "512M", "available_memory": "512M",
"binary_authorization_policy": "", "binary_authorization_policy": "",
"environment_variables": { "environment_variables": {
"FIRESTORE_DATABASE_NAME": "sereact-imagedb",
"FIRESTORE_PROJECT_ID": "gen-lang-client-0424120530",
"GCS_BUCKET_NAME": "sereact-images",
"LOG_EXECUTION_ID": "true", "LOG_EXECUTION_ID": "true",
"LOG_LEVEL": "INFO",
"QDRANT_API_KEY": "", "QDRANT_API_KEY": "",
"QDRANT_COLLECTION": "image_vectors", "QDRANT_COLLECTION": "image_vectors",
"QDRANT_HOST": "34.71.6.1", "QDRANT_HOST": "34.71.6.1",
"QDRANT_PORT": "6333" "QDRANT_HTTPS": "false",
"QDRANT_PORT": "6333",
"VISION_API_ENABLED": "true"
}, },
"gcf_uri": "", "gcf_uri": "",
"ingress_settings": "ALLOW_ALL", "ingress_settings": "ALLOW_ALL",
@ -548,7 +554,7 @@
"goog-terraform-provisioned": "true" "goog-terraform-provisioned": "true"
}, },
"timeouts": null, "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" "url": "https://us-central1-gen-lang-client-0424120530.cloudfunctions.net/process-image-embedding"
}, },
"sensitive_attributes": [ "sensitive_attributes": [
@ -801,7 +807,18 @@
[ [
{ {
"type": "get_attr", "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", "type": "get_attr",
"value": "boot_disk" "value": "metadata_startup_script"
},
{
"type": "index",
"value": {
"value": 0,
"type": "number"
}
},
{
"type": "get_attr",
"value": "disk_encryption_key_rsa"
} }
] ]
], ],
@ -864,8 +870,8 @@
"database_edition": "STANDARD", "database_edition": "STANDARD",
"delete_protection_state": "DELETE_PROTECTION_DISABLED", "delete_protection_state": "DELETE_PROTECTION_DISABLED",
"deletion_policy": "ABANDON", "deletion_policy": "ABANDON",
"earliest_version_time": "2025-05-24T21:09:24.677010Z", "earliest_version_time": "2025-05-24T21:15:22.382696Z",
"etag": "IOXfuveKvY0DMKrW4vCEvY0D", "etag": "IPeLluiQvY0DMKrW4vCEvY0D",
"id": "projects/gen-lang-client-0424120530/databases/sereact-imagedb", "id": "projects/gen-lang-client-0424120530/databases/sereact-imagedb",
"key_prefix": "", "key_prefix": "",
"location_id": "us-central1", "location_id": "us-central1",
@ -896,7 +902,7 @@
"schema_version": 0, "schema_version": 0,
"attributes": { "attributes": {
"condition": [], "condition": [],
"etag": "BwY16K9kGDo=", "etag": "BwY16LCINIE=",
"id": "gen-lang-client-0424120530/roles/eventarc.eventReceiver/serviceAccount:761163285547-compute@developer.gserviceaccount.com", "id": "gen-lang-client-0424120530/roles/eventarc.eventReceiver/serviceAccount:761163285547-compute@developer.gserviceaccount.com",
"member": "serviceAccount:761163285547-compute@developer.gserviceaccount.com", "member": "serviceAccount:761163285547-compute@developer.gserviceaccount.com",
"project": "gen-lang-client-0424120530", "project": "gen-lang-client-0424120530",
@ -920,7 +926,7 @@
"schema_version": 0, "schema_version": 0,
"attributes": { "attributes": {
"condition": [], "condition": [],
"etag": "BwY16Kj5NHI=", "etag": "BwY16LCINIE=",
"id": "gen-lang-client-0424120530/roles/datastore.user/serviceAccount:761163285547-compute@developer.gserviceaccount.com", "id": "gen-lang-client-0424120530/roles/datastore.user/serviceAccount:761163285547-compute@developer.gserviceaccount.com",
"member": "serviceAccount:761163285547-compute@developer.gserviceaccount.com", "member": "serviceAccount:761163285547-compute@developer.gserviceaccount.com",
"project": "gen-lang-client-0424120530", "project": "gen-lang-client-0424120530",
@ -944,7 +950,7 @@
"schema_version": 0, "schema_version": 0,
"attributes": { "attributes": {
"condition": [], "condition": [],
"etag": "BwY16K9kGDo=", "etag": "BwY16LCINIE=",
"id": "gen-lang-client-0424120530/roles/pubsub.subscriber/serviceAccount:761163285547-compute@developer.gserviceaccount.com", "id": "gen-lang-client-0424120530/roles/pubsub.subscriber/serviceAccount:761163285547-compute@developer.gserviceaccount.com",
"member": "serviceAccount:761163285547-compute@developer.gserviceaccount.com", "member": "serviceAccount:761163285547-compute@developer.gserviceaccount.com",
"project": "gen-lang-client-0424120530", "project": "gen-lang-client-0424120530",
@ -968,7 +974,7 @@
"schema_version": 0, "schema_version": 0,
"attributes": { "attributes": {
"condition": [], "condition": [],
"etag": "BwY16Kj5NHI=", "etag": "BwY16LCINIE=",
"id": "gen-lang-client-0424120530/roles/storage.objectViewer/serviceAccount:761163285547-compute@developer.gserviceaccount.com", "id": "gen-lang-client-0424120530/roles/storage.objectViewer/serviceAccount:761163285547-compute@developer.gserviceaccount.com",
"member": "serviceAccount:761163285547-compute@developer.gserviceaccount.com", "member": "serviceAccount:761163285547-compute@developer.gserviceaccount.com",
"project": "gen-lang-client-0424120530", "project": "gen-lang-client-0424120530",
@ -992,7 +998,7 @@
"schema_version": 0, "schema_version": 0,
"attributes": { "attributes": {
"condition": [], "condition": [],
"etag": "BwY16Kj5NHI=", "etag": "BwY16LCINIE=",
"id": "gen-lang-client-0424120530/roles/ml.developer/serviceAccount:761163285547-compute@developer.gserviceaccount.com", "id": "gen-lang-client-0424120530/roles/ml.developer/serviceAccount:761163285547-compute@developer.gserviceaccount.com",
"member": "serviceAccount:761163285547-compute@developer.gserviceaccount.com", "member": "serviceAccount:761163285547-compute@developer.gserviceaccount.com",
"project": "gen-lang-client-0424120530", "project": "gen-lang-client-0424120530",
@ -1489,21 +1495,21 @@
"content_encoding": "", "content_encoding": "",
"content_language": "", "content_language": "",
"content_type": "application/zip", "content_type": "application/zip",
"crc32c": "tbmr3A==", "crc32c": "YXAlNA==",
"customer_encryption": [], "customer_encryption": [],
"detect_md5hash": "leuOpRRrZvWya7gw4/Dqtg==", "detect_md5hash": "NNgXJau9T0I95x7NQhXRFg==",
"event_based_hold": false, "event_based_hold": false,
"generation": 1748123256911890, "generation": 1748124439573408,
"id": "gen-lang-client-0424120530-cloud-function-source-function-source-95eb8ea5146b66f5b26bb830e3f0eab6.zip", "id": "gen-lang-client-0424120530-cloud-function-source-function-source-34d81725abbd4f423de71ecd4215d116.zip",
"kms_key_name": "", "kms_key_name": "",
"md5hash": "leuOpRRrZvWya7gw4/Dqtg==", "md5hash": "NNgXJau9T0I95x7NQhXRFg==",
"md5hexhash": "95eb8ea5146b66f5b26bb830e3f0eab6", "md5hexhash": "34d81725abbd4f423de71ecd4215d116",
"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", "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": {}, "metadata": {},
"name": "function-source-95eb8ea5146b66f5b26bb830e3f0eab6.zip", "name": "function-source-34d81725abbd4f423de71ecd4215d116.zip",
"output_name": "function-source-95eb8ea5146b66f5b26bb830e3f0eab6.zip", "output_name": "function-source-34d81725abbd4f423de71ecd4215d116.zip",
"retention": [], "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", "source": "./function-source.zip",
"storage_class": "STANDARD", "storage_class": "STANDARD",
"temporary_hold": false, "temporary_hold": false,

13
main.py
View File

@ -140,16 +140,9 @@ def custom_openapi():
if "schemas" not in openapi_schema["components"]: if "schemas" not in openapi_schema["components"]:
openapi_schema["components"]["schemas"] = {} openapi_schema["components"]["schemas"] = {}
# Apply security to endpoints except auth, users, teams, and API key creation endpoints # Note: Authentication is now handled properly in individual route modules
for path in openapi_schema["paths"]: # Public endpoints (auth, users, teams) don't require authentication
# Exclude auth endpoints, users endpoints, teams endpoints, and API key creation # Protected endpoints (images, search) require API key authentication
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": []}]
app.openapi_schema = openapi_schema app.openapi_schema = openapi_schema
return app.openapi_schema return app.openapi_schema

View File

@ -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.api_key import ApiKeyCreate, ApiKeyResponse, ApiKeyWithValueResponse, ApiKeyListResponse
from src.schemas.team import TeamCreate from src.schemas.team import TeamCreate
from src.schemas.user import UserCreate 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.api_key import ApiKeyModel
from src.models.team import TeamModel from src.models.team import TeamModel
from src.models.user import UserModel from src.models.user import UserModel
@ -20,43 +20,6 @@ logger = logging.getLogger(__name__)
router = APIRouter(tags=["Authentication"], prefix="/auth") 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) @router.post("/bootstrap", response_model=ApiKeyWithValueResponse, status_code=201)
async def bootstrap_initial_setup( async def bootstrap_initial_setup(
team_name: str, team_name: str,

View File

@ -5,7 +5,7 @@ from fastapi.responses import StreamingResponse
from bson import ObjectId from bson import ObjectId
import io 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.db.repositories.image_repository import image_repository
from src.services.storage import StorageService from src.services.storage import StorageService
from src.services.image_processor import ImageProcessor from src.services.image_processor import ImageProcessor

View File

@ -2,7 +2,7 @@ import logging
from typing import Optional, List, Dict, Any from typing import Optional, List, Dict, Any
from fastapi import APIRouter, Depends, Query, Request, HTTPException 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.vector_db import VectorDatabaseService
from src.services.embedding_service import EmbeddingService from src.services.embedding_service import EmbeddingService
from src.db.repositories.image_repository import image_repository from src.db.repositories.image_repository import image_repository

View File

@ -6,8 +6,15 @@ import hashlib
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import Optional, Tuple 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 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]: def generate_api_key(team_id: str, user_id: str) -> Tuple[str, str]:
""" """
Generate a secure API key and its hashed value Generate a secure API key and its hashed value
@ -89,3 +96,80 @@ def is_expired(expiry_date: datetime) -> bool:
True if expired True if expired
""" """
return datetime.utcnow() > expiry_date 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