Phase 1: Fix all critical release blockers (B1-B10)

B1: Remove duplicate get_redis() — singleton no longer overwritten
B2: Plugin routes now enforce activation status via require_active_plugin()
B3: Fix UploadFile ForwardRef error — remove functools.wraps from wrap_plugin_route
B4: DMS upload uses true streaming via save_stream() instead of RAM accumulation
B5: Worker on_startup registers plugin event handlers + webhook dispatcher
B6: Implement send_password_reset_email job, remove raw token logging
B7: Webhook SSRF protection (IP validation, no redirects), secret removed from response
B8: RLS repair migration 0044 + separate crm_runtime DB user (NOSUPERUSER, NOBYPASSRLS)
B9: Fix .env.docker.example AUTH_SECRET → SECRET_KEY
B10: Remove Redis default password, remove exposed DB/Redis ports

Also: add frontend_url to config, add SMTP settings to .env.docker.example,
update prestart.sh to use MIGRATION_DATABASE_URL for alembic.
This commit is contained in:
Agent Zero
2026-07-26 20:45:42 +02:00
parent 7a14973c68
commit 5ec1fc9b05
32 changed files with 1781 additions and 76 deletions
+2 -2
View File
@@ -5,7 +5,7 @@ from __future__ import annotations
import uuid
from datetime import datetime
from sqlalchemy import BigInteger, DateTime, String, Text
from sqlalchemy import BigInteger, DateTime, String, Text, func
from sqlalchemy.dialects.postgresql import UUID as PGUUID
from sqlalchemy.orm import Mapped, mapped_column
@@ -39,7 +39,7 @@ class Backup(Base, TenantMixin):
PGUUID(as_uuid=True), nullable=True
)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False, default=datetime.utcnow
DateTime(timezone=True), nullable=False, server_default=func.now()
)
completed_at: Mapped[datetime | None] = mapped_column(
DateTime(timezone=True), nullable=True, default=None
+2 -2
View File
@@ -35,7 +35,7 @@ class Notification(Base, TenantMixin):
user_id: Mapped[uuid.UUID] = mapped_column(
PGUUID(as_uuid=True), ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True
)
type: Mapped[str] = mapped_column(String(20), nullable=False)
type: Mapped[str] = mapped_column(String(100), nullable=False)
title: Mapped[str] = mapped_column(String(200), nullable=False)
body: Mapped[str | None] = mapped_column(Text, nullable=True)
read_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
@@ -83,5 +83,5 @@ class NotificationPreference(Base, TenantMixin):
ForeignKey("users.id", ondelete="CASCADE"),
nullable=False,
)
type_key: Mapped[str] = mapped_column(String(20), nullable=False)
type_key: Mapped[str] = mapped_column(String(100), nullable=False)
is_enabled: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
+1 -1
View File
@@ -6,6 +6,7 @@ import uuid
from datetime import datetime
from sqlalchemy import DateTime, ForeignKey, Index, Integer, String
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.dialects.postgresql import UUID as PGUUID
from sqlalchemy.orm import Mapped, mapped_column
@@ -51,5 +52,4 @@ class SystemSettings(Base, TenantMixin):
theme_font_family: Mapped[str] = mapped_column(String(100), nullable=False, default="Inter")
theme_border_radius: Mapped[str] = mapped_column(String(20), nullable=False, default="0.5rem")
# Automation plugin settings (JSONB)
from sqlalchemy.dialects.postgresql import JSONB
automation_config: Mapped[dict | None] = mapped_column(JSONB, nullable=True)
+2 -1
View File
@@ -4,6 +4,7 @@ from __future__ import annotations
import uuid
from datetime import datetime
from decimal import Decimal
from sqlalchemy import Boolean, DateTime, Index, Numeric, String
from sqlalchemy.dialects.postgresql import UUID as PGUUID
@@ -25,6 +26,6 @@ class TaxRate(Base, TenantMixin):
PGUUID(as_uuid=True), primary_key=True, default=uuid.uuid4
)
name: Mapped[str] = mapped_column(String(100), nullable=False)
rate: Mapped[float] = mapped_column(Numeric(5, 2), nullable=False)
rate: Mapped[Decimal] = mapped_column(Numeric(5, 2), nullable=False)
is_default: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
country: Mapped[str | None] = mapped_column(String(2), nullable=True)
+5
View File
@@ -41,3 +41,8 @@ class Webhook(Base, TenantMixin):
updated_by: Mapped[uuid.UUID | None] = mapped_column(
PGUUID(as_uuid=True), nullable=True
)
@property
def has_secret(self) -> bool:
"""Return True if a webhook secret is set (never expose the secret itself)."""
return self.secret is not None and len(self.secret) > 0