fix: bootstrap service account with BYPASSRLS session
This commit is contained in:
@@ -14,6 +14,8 @@ import logging
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.admin_database import _admin_session_factory
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
SERVICE_ACCOUNT_EMAIL = "noreply@resolutionflow.com"
|
||||
@@ -52,40 +54,45 @@ async def _ensure_system_account(db: AsyncSession) -> uuid.UUID:
|
||||
async def ensure_service_account(db: AsyncSession) -> uuid.UUID:
|
||||
"""Ensure the ResolutionFlow service account exists and return its ID.
|
||||
|
||||
Idempotent — safe to call on every startup. Creates the account if it
|
||||
does not exist. The account has no usable password and is_service_account=True
|
||||
so it can never log in via normal auth flows.
|
||||
Idempotent — safe to call on every startup. This lookup must bypass RLS
|
||||
because startup runs before any request-scoped tenant context exists and
|
||||
the users table is tenant-isolated in Phase 4. The service account is
|
||||
normally created by Alembic migration 1490781700bc; the runtime create path
|
||||
remains as a self-healing fallback for environments that predate that seed.
|
||||
"""
|
||||
_ = db # Retained for call-site compatibility in app lifespan startup.
|
||||
|
||||
from app.models.user import User
|
||||
|
||||
result = await db.execute(
|
||||
select(User).where(User.email == SERVICE_ACCOUNT_EMAIL)
|
||||
)
|
||||
user = result.scalar_one_or_none()
|
||||
async with _admin_session_factory() as admin_db:
|
||||
result = await admin_db.execute(
|
||||
select(User).where(User.email == SERVICE_ACCOUNT_EMAIL)
|
||||
)
|
||||
user = result.scalar_one_or_none()
|
||||
|
||||
if user is not None:
|
||||
if not user.is_service_account:
|
||||
user.is_service_account = True
|
||||
await db.commit()
|
||||
return user.id
|
||||
if user is not None:
|
||||
if not user.is_service_account:
|
||||
user.is_service_account = True
|
||||
await admin_db.commit()
|
||||
return user.id
|
||||
|
||||
account_id = await _ensure_system_account(db)
|
||||
account_id = await _ensure_system_account(admin_db)
|
||||
|
||||
new_user = User(
|
||||
id=uuid.uuid4(),
|
||||
email=SERVICE_ACCOUNT_EMAIL,
|
||||
name=SERVICE_ACCOUNT_NAME,
|
||||
password_hash="!service-account-no-login", # bcrypt can't produce this prefix
|
||||
role="engineer",
|
||||
is_super_admin=False,
|
||||
is_team_admin=False,
|
||||
is_active=True,
|
||||
is_service_account=True,
|
||||
must_change_password=False,
|
||||
account_id=account_id,
|
||||
account_role="engineer",
|
||||
)
|
||||
db.add(new_user)
|
||||
await db.commit()
|
||||
logger.info(f"[service_account] Created service account (id={new_user.id})")
|
||||
return new_user.id
|
||||
new_user = User(
|
||||
id=uuid.uuid4(),
|
||||
email=SERVICE_ACCOUNT_EMAIL,
|
||||
name=SERVICE_ACCOUNT_NAME,
|
||||
password_hash="!service-account-no-login", # bcrypt can't produce this prefix
|
||||
role="engineer",
|
||||
is_super_admin=False,
|
||||
is_team_admin=False,
|
||||
is_active=True,
|
||||
is_service_account=True,
|
||||
must_change_password=False,
|
||||
account_id=account_id,
|
||||
account_role="engineer",
|
||||
)
|
||||
admin_db.add(new_user)
|
||||
await admin_db.commit()
|
||||
logger.info(f"[service_account] Created service account (id={new_user.id})")
|
||||
return new_user.id
|
||||
|
||||
Reference in New Issue
Block a user