Text Extraction Done, UI Fixes Done, Extracted Template Layout Saved in DB
This commit is contained in:
38
docengine/app/core/aes.py
Normal file
38
docengine/app/core/aes.py
Normal file
@@ -0,0 +1,38 @@
|
||||
import base64
|
||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||
|
||||
def decrypt(base64_input: str, secret: bytes) -> str | None:
|
||||
try:
|
||||
combined = base64.b64decode(base64_input)
|
||||
|
||||
# IV length is 12 bytes in the Java implementation
|
||||
iv_length = 12
|
||||
if len(combined) <= iv_length + 16:
|
||||
return None
|
||||
|
||||
iv = combined[:iv_length]
|
||||
|
||||
# Java AES/GCM appends a 16-byte authentication tag at the end of cipherBytes
|
||||
# cryptography library expects it to be passed into the modes.GCM(iv, tag)
|
||||
cipher_bytes_with_tag = combined[iv_length:]
|
||||
actual_ciphertext = cipher_bytes_with_tag[:-16]
|
||||
tag = cipher_bytes_with_tag[-16:]
|
||||
|
||||
cipher = Cipher(algorithms.AES(secret), modes.GCM(iv, tag))
|
||||
decryptor = cipher.decryptor()
|
||||
|
||||
plain_bytes = decryptor.update(actual_ciphertext) + decryptor.finalize()
|
||||
return plain_bytes.decode('utf-8')
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def get_string_value(encrypted_id: str, secret: str, secret_key_internal: str) -> str | None:
|
||||
if not secret or not secret_key_internal:
|
||||
return None
|
||||
try:
|
||||
# Replicate Java's logic: (s + secretKeyInternal).substring(0, Math.min(..., 16))
|
||||
combined_str = secret + secret_key_internal
|
||||
combined_secret = combined_str[:16].encode('utf-8')
|
||||
return decrypt(encrypted_id, combined_secret)
|
||||
except Exception:
|
||||
return None
|
||||
@@ -28,30 +28,32 @@ class Settings(BaseSettings):
|
||||
|
||||
# Database
|
||||
db_host: str = "192.168.0.111"
|
||||
db_port: int = 7925
|
||||
db_name: str = "document_engine"
|
||||
db_port: int = 5432
|
||||
db_name: str = "ocr"
|
||||
db_user: str = "postgres"
|
||||
db_password: str = "M@tr!x#149@dm!N"
|
||||
db_password: str = "M@triXPostgr3s@6202"
|
||||
db_schema: str = "admin"
|
||||
db_pool_size: int = 20
|
||||
db_max_overflow: int = 10
|
||||
db_echo: bool = False
|
||||
|
||||
# Redis
|
||||
redis_host: str = "localhost"
|
||||
redis_port: int = 6379
|
||||
redis_host: str = "192.168.0.111"
|
||||
redis_port: int = 7901
|
||||
redis_db: int = 0
|
||||
redis_password: str = ""
|
||||
redis_password: str = "M@triXR3d1s@6202"
|
||||
|
||||
# Celery
|
||||
celery_broker_url: str = "redis://localhost:6379/0"
|
||||
celery_result_backend: str = "redis://localhost:6379/1"
|
||||
celery_broker_url: str = "redis://:M@triXR3d1s@6202@192.168.0.111:7901/0"
|
||||
celery_result_backend: str = "redis://:M@triXR3d1s@6202@192.168.0.111:7901/1"
|
||||
|
||||
# JWT
|
||||
jwt_secret_key: str = "a7f3c9e1d4b8f2a6c0e5d7b3a9f1c4e8d2b6a0f5c3e7d1b9a4f8c2e6d0b5a3"
|
||||
jwt_algorithm: str = "HS256"
|
||||
jwt_access_token_expire_minutes: int = 30
|
||||
jwt_refresh_token_expire_days: int = 7
|
||||
session_encryption_secret: str = ""
|
||||
session_encryption_secret_internal: str = ""
|
||||
|
||||
# Storage
|
||||
storage_provider: str = "local"
|
||||
@@ -67,7 +69,7 @@ class Settings(BaseSettings):
|
||||
log_format: str = "json"
|
||||
|
||||
# CORS
|
||||
cors_origins: list[str] = ["http://localhost:3000", "http://localhost:8080"]
|
||||
cors_origins: list[str] = ["http://localhost:3000", "http://localhost:8080", "http://localhost:4200"]
|
||||
cors_allow_credentials: bool = True
|
||||
|
||||
# Rate Limiting
|
||||
|
||||
@@ -11,17 +11,63 @@ from app.core.security import InvalidTokenError, decode_token
|
||||
from app.models.user import User
|
||||
from app.repositories.user_repository import UserRepository
|
||||
|
||||
security_scheme = HTTPBearer(auto_error=True)
|
||||
import uuid
|
||||
from app.core.config import settings
|
||||
|
||||
security_scheme = HTTPBearer(auto_error=False)
|
||||
|
||||
|
||||
def get_current_user(
|
||||
credentials: Annotated[HTTPAuthorizationCredentials, Depends(security_scheme)],
|
||||
credentials: Annotated[HTTPAuthorizationCredentials | None, Depends(security_scheme)],
|
||||
db: Annotated[Session, Depends(get_db)],
|
||||
) -> User:
|
||||
"""Extract and validate the current user from the JWT token."""
|
||||
if credentials is None:
|
||||
if settings.app_env == "development":
|
||||
# Auto-login as default dev admin user if no token provided in dev
|
||||
user_repo = UserRepository(db)
|
||||
user = db.query(User).first()
|
||||
if user:
|
||||
return user
|
||||
dev_user = User(
|
||||
id=uuid.uuid4(),
|
||||
username="dev_admin",
|
||||
email="admin@docengine.local",
|
||||
hashed_password="mock_password",
|
||||
is_active=True,
|
||||
is_superuser=True,
|
||||
)
|
||||
db.add(dev_user)
|
||||
db.commit()
|
||||
db.refresh(dev_user)
|
||||
return dev_user
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Not authenticated",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
try:
|
||||
payload = decode_token(credentials.credentials)
|
||||
except InvalidTokenError:
|
||||
if settings.app_env == "development":
|
||||
# Fallback to dev admin user on token decode failure in dev
|
||||
user_repo = UserRepository(db)
|
||||
user = db.query(User).first()
|
||||
if user:
|
||||
return user
|
||||
dev_user = User(
|
||||
id=uuid.uuid4(),
|
||||
username="dev_admin",
|
||||
email="admin@docengine.local",
|
||||
hashed_password="mock_password",
|
||||
is_active=True,
|
||||
is_superuser=True,
|
||||
)
|
||||
db.add(dev_user)
|
||||
db.commit()
|
||||
db.refresh(dev_user)
|
||||
return dev_user
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid or expired token",
|
||||
|
||||
@@ -8,6 +8,7 @@ from jose import JWTError, jwt
|
||||
from passlib.context import CryptContext
|
||||
|
||||
from app.core.config import settings
|
||||
from app.core.aes import get_string_value
|
||||
|
||||
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||
|
||||
@@ -44,8 +45,14 @@ def create_refresh_token(data: dict[str, Any], expires_delta: timedelta | None =
|
||||
|
||||
def decode_token(token: str) -> dict[str, Any]:
|
||||
"""Decode and validate a JWT token."""
|
||||
decrypted_token = get_string_value(
|
||||
token,
|
||||
settings.session_encryption_secret,
|
||||
settings.session_encryption_secret_internal
|
||||
)
|
||||
final_token = decrypted_token if decrypted_token else token
|
||||
try:
|
||||
payload = jwt.decode(token, settings.jwt_secret_key, algorithms=[settings.jwt_algorithm])
|
||||
payload = jwt.decode(final_token, settings.jwt_secret_key, algorithms=[settings.jwt_algorithm])
|
||||
return payload
|
||||
except JWTError as e:
|
||||
raise InvalidTokenError(str(e)) from e
|
||||
|
||||
@@ -8,7 +8,7 @@ logger = get_logger(__name__)
|
||||
def on_startup() -> None:
|
||||
"""Application startup event handler."""
|
||||
setup_logging()
|
||||
logger.info("application_starting", event="startup")
|
||||
logger.info("application_starting", phase="startup")
|
||||
|
||||
# Ensure storage directories exist
|
||||
from app.storage.provider import get_storage_provider
|
||||
@@ -25,15 +25,15 @@ def on_startup() -> None:
|
||||
else:
|
||||
logger.error("database_connection_failed")
|
||||
|
||||
logger.info("application_started", event="startup_complete")
|
||||
logger.info("application_started", phase="startup_complete")
|
||||
|
||||
|
||||
def on_shutdown() -> None:
|
||||
"""Application shutdown event handler."""
|
||||
logger.info("application_shutting_down", event="shutdown")
|
||||
logger.info("application_shutting_down", phase="shutdown")
|
||||
|
||||
# Cleanup resources
|
||||
from app.core.database import engine
|
||||
engine.dispose()
|
||||
|
||||
logger.info("application_stopped", event="shutdown_complete")
|
||||
logger.info("application_stopped", phase="shutdown_complete")
|
||||
|
||||
@@ -37,9 +37,9 @@ app = FastAPI(
|
||||
)
|
||||
|
||||
# Setup middleware (order matters: last added = first executed)
|
||||
setup_cors(app)
|
||||
app.add_middleware(AuditMiddleware)
|
||||
app.add_middleware(RateLimitMiddleware)
|
||||
setup_cors(app)
|
||||
|
||||
# Setup Prometheus metrics
|
||||
setup_metrics(app)
|
||||
|
||||
@@ -13,13 +13,7 @@ def setup_cors(app: FastAPI) -> None:
|
||||
allow_origins=settings.cors_origins,
|
||||
allow_credentials=settings.cors_allow_credentials,
|
||||
allow_methods=["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
|
||||
allow_headers=[
|
||||
"Authorization",
|
||||
"Content-Type",
|
||||
"Accept",
|
||||
"X-Request-ID",
|
||||
"X-Requested-With",
|
||||
],
|
||||
allow_headers=["*"],
|
||||
expose_headers=[
|
||||
"X-Request-ID",
|
||||
"X-Process-Time",
|
||||
|
||||
@@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import Boolean, DateTime, ForeignKey, String, Table, Text, func
|
||||
from sqlalchemy import Boolean, Column, DateTime, ForeignKey, String, Table, Text, func
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
@@ -13,8 +13,8 @@ from app.models.base import TimestampMixin, UUIDPrimaryKeyMixin
|
||||
user_roles_table = Table(
|
||||
"user_roles",
|
||||
Base.metadata,
|
||||
mapped_column("user_id", UUID(as_uuid=True), ForeignKey("users.id", ondelete="CASCADE"), primary_key=True),
|
||||
mapped_column("role_id", UUID(as_uuid=True), ForeignKey("roles.id", ondelete="CASCADE"), primary_key=True),
|
||||
Column("user_id", UUID(as_uuid=True), ForeignKey("users.id", ondelete="CASCADE"), primary_key=True),
|
||||
Column("role_id", UUID(as_uuid=True), ForeignKey("roles.id", ondelete="CASCADE"), primary_key=True),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -40,4 +40,9 @@ celery_app.conf.update(
|
||||
},
|
||||
)
|
||||
|
||||
celery_app.autodiscover_tasks(["app.tasks"])
|
||||
celery_app.conf.update(
|
||||
imports=[
|
||||
"app.tasks.document_tasks",
|
||||
"app.tasks.maintenance_tasks"
|
||||
]
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user