mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-07-22 11:51:04 +02:00
fix: fix org scoped access for resources (#517)
* fix: fix org scoped access for resources * Fix auth and config validation regressions * fix: track org config validation timestamp * fix: backfill org model configuration v2 from legacy user rows Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test: align config tests with org-level v2 resolution Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore: helm example values tweaks Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
041c31a613
commit
fb4038a969
59 changed files with 3531 additions and 517 deletions
|
|
@ -92,6 +92,7 @@ async def agent_stream_websocket(
|
|||
|
||||
quota_result = await authorize_workflow_run_start(
|
||||
workflow_id=workflow.id,
|
||||
organization_id=workflow.organization_id,
|
||||
workflow_run_id=workflow_run.id,
|
||||
)
|
||||
if not quota_result.has_quota:
|
||||
|
|
|
|||
|
|
@ -351,9 +351,12 @@ async def create_campaign(
|
|||
) -> CampaignResponse:
|
||||
"""Create a new campaign"""
|
||||
# Verify workflow exists and belongs to organization
|
||||
workflow_name = await db_client.get_workflow_name(request.workflow_id, user.id)
|
||||
if not workflow_name:
|
||||
workflow = await db_client.get_workflow(
|
||||
request.workflow_id, organization_id=user.selected_organization_id
|
||||
)
|
||||
if not workflow:
|
||||
raise HTTPException(status_code=404, detail="Workflow not found")
|
||||
workflow_name = workflow.name
|
||||
|
||||
# Validate source data (phone_number column and format)
|
||||
sync_service = get_sync_service(request.source_type)
|
||||
|
|
@ -364,9 +367,6 @@ async def create_campaign(
|
|||
raise HTTPException(status_code=400, detail=validation_result.error.message)
|
||||
|
||||
# Validate template variables against source data columns
|
||||
workflow = await db_client.get_workflow(
|
||||
request.workflow_id, organization_id=user.selected_organization_id
|
||||
)
|
||||
if workflow:
|
||||
from api.services.workflow.dto import ReactFlowDTO
|
||||
from api.services.workflow.workflow_graph import WorkflowGraph
|
||||
|
|
@ -512,7 +512,9 @@ async def get_campaign(
|
|||
if not campaign:
|
||||
raise HTTPException(status_code=404, detail="Campaign not found")
|
||||
|
||||
workflow_name = await db_client.get_workflow_name(campaign.workflow_id, user.id)
|
||||
workflow_name = await db_client.get_workflow_name(
|
||||
campaign.workflow_id, organization_id=user.selected_organization_id
|
||||
)
|
||||
|
||||
executed, total = await _get_campaign_stats(campaign.id)
|
||||
cfg_name = await _get_telephony_configuration_name(
|
||||
|
|
@ -552,6 +554,7 @@ async def start_campaign(
|
|||
# model_overrides so we evaluate the keys this campaign will use).
|
||||
quota_result = await authorize_workflow_run_start(
|
||||
workflow_id=campaign.workflow_id,
|
||||
organization_id=user.selected_organization_id,
|
||||
actor_user=user,
|
||||
)
|
||||
if not quota_result.has_quota:
|
||||
|
|
@ -565,7 +568,9 @@ async def start_campaign(
|
|||
|
||||
# Get updated campaign
|
||||
campaign = await db_client.get_campaign(campaign_id, user.selected_organization_id)
|
||||
workflow_name = await db_client.get_workflow_name(campaign.workflow_id, user.id)
|
||||
workflow_name = await db_client.get_workflow_name(
|
||||
campaign.workflow_id, organization_id=user.selected_organization_id
|
||||
)
|
||||
|
||||
executed, total = await _get_campaign_stats(campaign.id)
|
||||
cfg_name = await _get_telephony_configuration_name(
|
||||
|
|
@ -599,7 +604,9 @@ async def pause_campaign(
|
|||
|
||||
# Get updated campaign
|
||||
campaign = await db_client.get_campaign(campaign_id, user.selected_organization_id)
|
||||
workflow_name = await db_client.get_workflow_name(campaign.workflow_id, user.id)
|
||||
workflow_name = await db_client.get_workflow_name(
|
||||
campaign.workflow_id, organization_id=user.selected_organization_id
|
||||
)
|
||||
|
||||
executed, total = await _get_campaign_stats(campaign.id)
|
||||
cfg_name = await _get_telephony_configuration_name(
|
||||
|
|
@ -669,7 +676,9 @@ async def update_campaign(
|
|||
|
||||
# Re-fetch to return updated data
|
||||
campaign = await db_client.get_campaign(campaign_id, user.selected_organization_id)
|
||||
workflow_name = await db_client.get_workflow_name(campaign.workflow_id, user.id)
|
||||
workflow_name = await db_client.get_workflow_name(
|
||||
campaign.workflow_id, organization_id=user.selected_organization_id
|
||||
)
|
||||
|
||||
executed, total = await _get_campaign_stats(campaign.id)
|
||||
cfg_name = await _get_telephony_configuration_name(
|
||||
|
|
@ -838,7 +847,9 @@ async def redial_campaign(
|
|||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
|
||||
workflow_name = await db_client.get_workflow_name(child.workflow_id, user.id)
|
||||
workflow_name = await db_client.get_workflow_name(
|
||||
child.workflow_id, organization_id=user.selected_organization_id
|
||||
)
|
||||
executed, total = await _get_campaign_stats(child.id)
|
||||
cfg_name = await _get_telephony_configuration_name(
|
||||
child.telephony_configuration_id, user.selected_organization_id
|
||||
|
|
@ -877,6 +888,7 @@ async def resume_campaign(
|
|||
# model_overrides so we evaluate the keys this campaign will use).
|
||||
quota_result = await authorize_workflow_run_start(
|
||||
workflow_id=campaign.workflow_id,
|
||||
organization_id=user.selected_organization_id,
|
||||
actor_user=user,
|
||||
)
|
||||
if not quota_result.has_quota:
|
||||
|
|
@ -890,7 +902,9 @@ async def resume_campaign(
|
|||
|
||||
# Get updated campaign
|
||||
campaign = await db_client.get_campaign(campaign_id, user.selected_organization_id)
|
||||
workflow_name = await db_client.get_workflow_name(campaign.workflow_id, user.id)
|
||||
workflow_name = await db_client.get_workflow_name(
|
||||
campaign.workflow_id, organization_id=user.selected_organization_id
|
||||
)
|
||||
|
||||
executed, total = await _get_campaign_stats(campaign.id)
|
||||
cfg_name = await _get_telephony_configuration_name(
|
||||
|
|
|
|||
|
|
@ -375,9 +375,8 @@ async def search_chunks(
|
|||
)
|
||||
from api.services.gen_ai import build_embedding_service
|
||||
|
||||
# Try to get user's embeddings configuration
|
||||
# Try to get the organization's embeddings configuration
|
||||
resolved_config = await get_resolved_ai_model_configuration(
|
||||
user_id=user.id,
|
||||
organization_id=user.selected_organization_id,
|
||||
)
|
||||
effective_config = resolved_config.effective
|
||||
|
|
|
|||
|
|
@ -243,7 +243,6 @@ async def _model_configuration_v2_response(
|
|||
configuration: OrganizationAIModelConfigurationV2 | None = None,
|
||||
) -> OrganizationAIModelConfigurationResponse:
|
||||
resolved = await get_resolved_ai_model_configuration(
|
||||
user_id=user.id,
|
||||
organization_id=user.selected_organization_id,
|
||||
)
|
||||
raw_configuration = (
|
||||
|
|
|
|||
|
|
@ -512,7 +512,6 @@ async def get_daily_usage_breakdown(
|
|||
start_date,
|
||||
end_date,
|
||||
org.price_per_second_usd,
|
||||
user_id=user.id,
|
||||
)
|
||||
|
||||
return breakdown
|
||||
|
|
|
|||
|
|
@ -285,6 +285,7 @@ async def _execute_resolved_target(
|
|||
# the MPS correlation id before the provider starts the call.
|
||||
quota_result = await authorize_workflow_run_start(
|
||||
workflow_id=target.workflow.id,
|
||||
organization_id=target.organization_id,
|
||||
workflow_run_id=workflow_run.id,
|
||||
)
|
||||
if not quota_result.has_quota:
|
||||
|
|
|
|||
|
|
@ -309,11 +309,11 @@ async def initialize_embed_session(
|
|||
workflow_id=embed_token.workflow_id,
|
||||
mode=WorkflowRunMode.SMALLWEBRTC.value,
|
||||
user_id=embed_token.created_by, # Use token creator as run owner
|
||||
organization_id=embed_token.organization_id,
|
||||
initial_context={
|
||||
**(init_request.context_variables or {}),
|
||||
"provider": WorkflowRunMode.SMALLWEBRTC.value,
|
||||
},
|
||||
organization_id=embed_token.organization_id,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to create workflow run: {e}")
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@ async def initiate_call(
|
|||
# the MPS correlation id before initiating the call.
|
||||
quota_result = await authorize_workflow_run_start(
|
||||
workflow_id=workflow.id,
|
||||
organization_id=user.selected_organization_id,
|
||||
workflow_run_id=workflow_run_id,
|
||||
actor_user=user,
|
||||
)
|
||||
|
|
@ -450,11 +451,11 @@ async def _validate_inbound_request(
|
|||
async def _create_inbound_workflow_run(
|
||||
workflow_id: int,
|
||||
user_id: int,
|
||||
organization_id: int,
|
||||
provider: str,
|
||||
normalized_data,
|
||||
telephony_configuration_id: int,
|
||||
from_phone_number_id: Optional[int] = None,
|
||||
organization_id: int | None = None,
|
||||
) -> int:
|
||||
"""Create workflow run for inbound call and return run ID"""
|
||||
call_id = normalized_data.call_id
|
||||
|
|
@ -593,6 +594,20 @@ async def _handle_telephony_websocket(
|
|||
logger.error(f"Workflow {workflow_id} not found")
|
||||
await websocket.close(code=4404, reason="Workflow not found")
|
||||
return
|
||||
if workflow_run.workflow_id != workflow.id:
|
||||
logger.error(
|
||||
f"Workflow run {workflow_run_id} belongs to workflow "
|
||||
f"{workflow_run.workflow_id}, not {workflow.id}"
|
||||
)
|
||||
await websocket.close(code=4400, reason="workflow_run_workflow_mismatch")
|
||||
return
|
||||
if workflow.user_id != user_id:
|
||||
logger.error(
|
||||
f"Telephony websocket user mismatch for workflow {workflow.id}: "
|
||||
f"got {user_id}, expected {workflow.user_id}"
|
||||
)
|
||||
await websocket.close(code=4400, reason="workflow_user_mismatch")
|
||||
return
|
||||
|
||||
# Check workflow run state - only allow 'initialized' state
|
||||
if workflow_run.state != WorkflowRunState.INITIALIZED.value:
|
||||
|
|
@ -814,16 +829,17 @@ async def handle_inbound_run(request: Request):
|
|||
workflow_run_id = await _create_inbound_workflow_run(
|
||||
workflow_id,
|
||||
user_id,
|
||||
config.organization_id,
|
||||
provider_class.PROVIDER_NAME,
|
||||
normalized_data,
|
||||
telephony_configuration_id=telephony_configuration_id,
|
||||
from_phone_number_id=phone_row.id,
|
||||
organization_id=config.organization_id,
|
||||
)
|
||||
await call_concurrency.bind_workflow_run(concurrency_slot, workflow_run_id)
|
||||
|
||||
quota_result = await authorize_workflow_run_start(
|
||||
workflow_id=workflow_id,
|
||||
organization_id=config.organization_id,
|
||||
workflow_run_id=workflow_run_id,
|
||||
)
|
||||
if not quota_result.has_quota:
|
||||
|
|
@ -977,18 +993,19 @@ async def handle_inbound_telephony(
|
|||
workflow_run_id = await _create_inbound_workflow_run(
|
||||
workflow_id,
|
||||
workflow_context["user_id"],
|
||||
organization_id,
|
||||
workflow_context["provider"],
|
||||
normalized_data,
|
||||
telephony_configuration_id=workflow_context[
|
||||
"telephony_configuration_id"
|
||||
],
|
||||
from_phone_number_id=workflow_context.get("from_phone_number_id"),
|
||||
organization_id=organization_id,
|
||||
)
|
||||
await call_concurrency.bind_workflow_run(concurrency_slot, workflow_run_id)
|
||||
|
||||
quota_result = await authorize_workflow_run_start(
|
||||
workflow_id=workflow_id,
|
||||
organization_id=organization_id,
|
||||
workflow_run_id=workflow_run_id,
|
||||
)
|
||||
if not quota_result.has_quota:
|
||||
|
|
|
|||
|
|
@ -16,7 +16,10 @@ from api.schemas.workflow_configurations import (
|
|||
)
|
||||
from api.services.auth.depends import get_user
|
||||
from api.services.configuration.ai_model_configuration import (
|
||||
convert_legacy_ai_model_configuration_to_v2,
|
||||
get_resolved_ai_model_configuration,
|
||||
update_organization_ai_model_configuration_last_validated_at,
|
||||
upsert_organization_ai_model_configuration_v2,
|
||||
)
|
||||
from api.services.configuration.check_validity import (
|
||||
APIKeyStatusResponse,
|
||||
|
|
@ -105,12 +108,29 @@ class UserConfigurationRequestResponseSchema(BaseModel):
|
|||
organization_pricing: dict[str, Union[float, str, bool]] | None = None
|
||||
|
||||
|
||||
def _is_validation_cache_stale(
|
||||
last_validated_at: datetime | None,
|
||||
validity_ttl_seconds: int,
|
||||
) -> bool:
|
||||
if last_validated_at is None:
|
||||
return True
|
||||
|
||||
has_timezone = (
|
||||
last_validated_at.tzinfo is not None
|
||||
and last_validated_at.utcoffset() is not None
|
||||
)
|
||||
if has_timezone:
|
||||
now = datetime.now(last_validated_at.tzinfo)
|
||||
else:
|
||||
now = datetime.now()
|
||||
return last_validated_at < now - timedelta(seconds=validity_ttl_seconds)
|
||||
|
||||
|
||||
@router.get("/configurations/user")
|
||||
async def get_user_configurations(
|
||||
user: UserModel = Depends(get_user),
|
||||
) -> UserConfigurationRequestResponseSchema:
|
||||
resolved_config = await get_resolved_ai_model_configuration(
|
||||
user_id=user.id,
|
||||
organization_id=user.selected_organization_id,
|
||||
)
|
||||
masked_config = mask_user_config(resolved_config.effective)
|
||||
|
|
@ -139,7 +159,11 @@ async def update_user_configurations(
|
|||
request: UserConfigurationRequestResponseSchema,
|
||||
user: UserModel = Depends(get_user),
|
||||
) -> UserConfigurationRequestResponseSchema:
|
||||
existing_config = await db_client.get_user_configurations(user.id)
|
||||
existing_config = (
|
||||
await get_resolved_ai_model_configuration(
|
||||
organization_id=user.selected_organization_id,
|
||||
)
|
||||
).effective
|
||||
|
||||
incoming_dict = request.model_dump(exclude_none=True)
|
||||
|
||||
|
|
@ -152,6 +176,9 @@ async def update_user_configurations(
|
|||
}
|
||||
|
||||
if incoming_dict:
|
||||
if not user.selected_organization_id:
|
||||
raise HTTPException(status_code=400, detail="No organization selected")
|
||||
|
||||
# Merge via helper
|
||||
try:
|
||||
user_configurations = merge_user_configurations(
|
||||
|
|
@ -175,8 +202,16 @@ async def update_user_configurations(
|
|||
except ValueError as e:
|
||||
raise HTTPException(status_code=422, detail=e.args[0])
|
||||
|
||||
user_configurations = await db_client.update_user_configuration(
|
||||
user.id, user_configurations
|
||||
try:
|
||||
organization_configuration = convert_legacy_ai_model_configuration_to_v2(
|
||||
user_configurations
|
||||
)
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=422, detail=str(e))
|
||||
|
||||
await upsert_organization_ai_model_configuration_v2(
|
||||
user.selected_organization_id,
|
||||
organization_configuration,
|
||||
)
|
||||
else:
|
||||
user_configurations = existing_config
|
||||
|
|
@ -235,15 +270,13 @@ async def validate_user_configurations(
|
|||
user: UserModel = Depends(get_user),
|
||||
) -> APIKeyStatusResponse:
|
||||
resolved_config = await get_resolved_ai_model_configuration(
|
||||
user_id=user.id,
|
||||
organization_id=user.selected_organization_id,
|
||||
)
|
||||
configurations = resolved_config.effective
|
||||
|
||||
if (
|
||||
configurations.last_validated_at
|
||||
and configurations.last_validated_at
|
||||
< datetime.now() - timedelta(seconds=validity_ttl_seconds)
|
||||
if _is_validation_cache_stale(
|
||||
configurations.last_validated_at,
|
||||
validity_ttl_seconds,
|
||||
):
|
||||
validator = UserConfigurationValidator()
|
||||
try:
|
||||
|
|
@ -252,7 +285,13 @@ async def validate_user_configurations(
|
|||
organization_id=user.selected_organization_id,
|
||||
created_by=user.provider_id,
|
||||
)
|
||||
await db_client.update_user_configuration_last_validated_at(user.id)
|
||||
if (
|
||||
resolved_config.source == "organization_v2"
|
||||
and user.selected_organization_id is not None
|
||||
):
|
||||
await update_organization_ai_model_configuration_last_validated_at(
|
||||
user.selected_organization_id
|
||||
)
|
||||
return status
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=422, detail=e.args[0])
|
||||
|
|
|
|||
|
|
@ -326,6 +326,7 @@ class SignalingManager:
|
|||
workflow_id: int,
|
||||
workflow_run_id: int,
|
||||
user: UserModel,
|
||||
organization_id: int,
|
||||
enforce_call_concurrency: bool = False,
|
||||
call_concurrency_source: str = "webrtc",
|
||||
):
|
||||
|
|
@ -344,6 +345,7 @@ class SignalingManager:
|
|||
workflow_id,
|
||||
workflow_run_id,
|
||||
user,
|
||||
organization_id,
|
||||
connection_key,
|
||||
enforce_call_concurrency,
|
||||
call_concurrency_source,
|
||||
|
|
@ -387,6 +389,7 @@ class SignalingManager:
|
|||
workflow_id: int,
|
||||
workflow_run_id: int,
|
||||
user: UserModel,
|
||||
organization_id: int,
|
||||
connection_key: str,
|
||||
enforce_call_concurrency: bool,
|
||||
call_concurrency_source: str = "webrtc",
|
||||
|
|
@ -402,6 +405,7 @@ class SignalingManager:
|
|||
workflow_id,
|
||||
workflow_run_id,
|
||||
user,
|
||||
organization_id,
|
||||
connection_key,
|
||||
enforce_call_concurrency,
|
||||
call_concurrency_source,
|
||||
|
|
@ -418,6 +422,7 @@ class SignalingManager:
|
|||
workflow_id: int,
|
||||
workflow_run_id: int,
|
||||
user: UserModel,
|
||||
organization_id: int,
|
||||
connection_key: str,
|
||||
enforce_call_concurrency: bool,
|
||||
call_concurrency_source: str = "webrtc",
|
||||
|
|
@ -440,14 +445,13 @@ class SignalingManager:
|
|||
# Set run context for logging and tracing. org_id must be set before
|
||||
# pc.initialize() so that aiortc's internal tasks inherit it.
|
||||
set_current_run_id(workflow_run_id)
|
||||
org_id = await db_client.get_workflow_organization_id(workflow_id)
|
||||
if org_id:
|
||||
set_current_org_id(org_id)
|
||||
set_current_org_id(organization_id)
|
||||
|
||||
# Check Dograh quota before initiating the call (apply per-workflow
|
||||
# model_overrides so we evaluate the keys this workflow will use).
|
||||
quota_result = await authorize_workflow_run_start(
|
||||
workflow_id=workflow_id,
|
||||
organization_id=organization_id,
|
||||
workflow_run_id=workflow_run_id,
|
||||
actor_user=user,
|
||||
)
|
||||
|
|
@ -497,21 +501,9 @@ class SignalingManager:
|
|||
pipeline_started = False
|
||||
pc = None
|
||||
if enforce_call_concurrency:
|
||||
if org_id is None:
|
||||
await ws.send_json(
|
||||
{
|
||||
"type": "error",
|
||||
"payload": {
|
||||
"error_type": "organization_not_found",
|
||||
"message": "Workflow organization not found",
|
||||
},
|
||||
}
|
||||
)
|
||||
return
|
||||
|
||||
try:
|
||||
concurrency_slot = await call_concurrency.acquire_org_slot(
|
||||
org_id,
|
||||
organization_id,
|
||||
source=call_concurrency_source,
|
||||
timeout=0,
|
||||
)
|
||||
|
|
@ -592,6 +584,7 @@ class SignalingManager:
|
|||
user.id,
|
||||
call_context_vars,
|
||||
user_provider_id=str(user.provider_id),
|
||||
organization_id=organization_id,
|
||||
)
|
||||
)
|
||||
pipeline_started = True
|
||||
|
|
@ -716,26 +709,31 @@ async def signaling_websocket(
|
|||
user: UserModel = Depends(get_user_ws),
|
||||
):
|
||||
"""WebSocket endpoint for WebRTC signaling with ICE trickling."""
|
||||
workflow_run = await db_client.get_workflow_run(workflow_run_id, user.id)
|
||||
if not workflow_run:
|
||||
logger.warning(f"workflow run {workflow_run_id} not found for user {user.id}")
|
||||
raise HTTPException(status_code=400, detail="Bad workflow_run_id")
|
||||
if not user.selected_organization_id:
|
||||
raise HTTPException(status_code=400, detail="No organization selected")
|
||||
|
||||
# The URL's workflow_id drives org resolution, quota, and concurrency
|
||||
# accounting downstream — reject a workflow_id that doesn't own this run
|
||||
# so a caller can't charge their call to an unrelated workflow/org.
|
||||
if workflow_run.workflow_id != workflow_id:
|
||||
workflow_run = await db_client.get_workflow_run(
|
||||
workflow_run_id, organization_id=user.selected_organization_id
|
||||
)
|
||||
if not workflow_run:
|
||||
logger.warning(
|
||||
f"workflow run {workflow_run_id} does not belong to workflow "
|
||||
f"{workflow_id} (belongs to {workflow_run.workflow_id})"
|
||||
f"workflow run {workflow_run_id} not found for org "
|
||||
f"{user.selected_organization_id}"
|
||||
)
|
||||
raise HTTPException(status_code=400, detail="Bad workflow_run_id")
|
||||
if workflow_run.workflow_id != workflow_id:
|
||||
logger.warning(
|
||||
f"workflow run {workflow_run_id} belongs to workflow "
|
||||
f"{workflow_run.workflow_id}, not {workflow_id}"
|
||||
)
|
||||
raise HTTPException(status_code=400, detail="workflow_run_workflow_mismatch")
|
||||
|
||||
await signaling_manager.handle_websocket(
|
||||
websocket,
|
||||
workflow_id,
|
||||
workflow_run_id,
|
||||
user,
|
||||
user.selected_organization_id,
|
||||
enforce_call_concurrency=True,
|
||||
call_concurrency_source="webrtc",
|
||||
)
|
||||
|
|
@ -771,6 +769,17 @@ async def public_signaling_websocket(
|
|||
await websocket.close(code=1008, reason="Invalid embed token")
|
||||
return
|
||||
|
||||
workflow_run = await db_client.get_workflow_run(
|
||||
embed_session.workflow_run_id,
|
||||
organization_id=embed_token.organization_id,
|
||||
)
|
||||
if not workflow_run:
|
||||
await websocket.close(code=1008, reason="Invalid workflow run")
|
||||
return
|
||||
if workflow_run.workflow_id != embed_token.workflow_id:
|
||||
await websocket.close(code=1008, reason="workflow_run_workflow_mismatch")
|
||||
return
|
||||
|
||||
# Enforce the embed token's allowed-domain policy on the public signaling
|
||||
# path, mirroring the HTTP embed endpoints (issue #330). Without this a
|
||||
# leaked or replayed session token could attach from an arbitrary origin.
|
||||
|
|
@ -798,6 +807,7 @@ async def public_signaling_websocket(
|
|||
embed_token.workflow_id,
|
||||
embed_session.workflow_run_id,
|
||||
user,
|
||||
embed_token.organization_id,
|
||||
enforce_call_concurrency=True,
|
||||
call_concurrency_source="public_embed",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1090,7 +1090,6 @@ async def update_workflow(
|
|||
)
|
||||
if existing_v2_override_config is None:
|
||||
resolved_config = await get_resolved_ai_model_configuration(
|
||||
user_id=user.id,
|
||||
organization_id=user.selected_organization_id,
|
||||
)
|
||||
v2_override = merge_ai_model_configuration_v2_secrets(
|
||||
|
|
@ -1133,7 +1132,6 @@ async def update_workflow(
|
|||
existing_configs,
|
||||
)
|
||||
resolved_config = await get_resolved_ai_model_configuration(
|
||||
user_id=user.id,
|
||||
organization_id=user.selected_organization_id,
|
||||
)
|
||||
effective_config = resolved_config.effective
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ async def _ensure_text_chat_quota(
|
|||
) -> None:
|
||||
quota_result = await authorize_workflow_run_start(
|
||||
workflow_id=workflow_id,
|
||||
organization_id=user.selected_organization_id,
|
||||
workflow_run_id=workflow_run_id,
|
||||
actor_user=user,
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue