From c493e08df15aed46ed77d8e784209b70f8349912 Mon Sep 17 00:00:00 2001 From: Charles Yao Date: Tue, 11 Nov 2025 20:05:11 -0600 Subject: [PATCH 01/17] add new table of end user oauth --- api/models/__init__.py | 2 ++ api/models/tools.py | 52 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/api/models/__init__.py b/api/models/__init__.py index d5e017e036..2a228b6c06 100644 --- a/api/models/__init__.py +++ b/api/models/__init__.py @@ -80,6 +80,7 @@ from .task import CeleryTask, CeleryTaskSet from .tools import ( ApiToolProvider, BuiltinToolProvider, + EndUserAuthenticationProvider, ToolConversationVariables, ToolFile, ToolLabelBinding, @@ -148,6 +149,7 @@ __all__ = [ "DocumentSegment", "Embedding", "EndUser", + "EndUserAuthenticationProvider", "ExternalKnowledgeApis", "ExternalKnowledgeBindings", "IconType", diff --git a/api/models/tools.py b/api/models/tools.py index cc80ddcf51..3d42cfa682 100644 --- a/api/models/tools.py +++ b/api/models/tools.py @@ -114,6 +114,58 @@ class BuiltinToolProvider(TypeBase): return cast(dict[str, Any], json.loads(self.encrypted_credentials)) +class EndUserAuthenticationProvider(TypeBase): + """ + This table stores the authentication credentials for end users in tools. + Mimics the BuiltinToolProvider structure but for end users instead of tenants. + """ + + __tablename__ = "tool_enduser_authentication_providers" + __table_args__ = ( + sa.PrimaryKeyConstraint("id", name="tool_enduser_authentication_provider_pkey"), + sa.UniqueConstraint("tenant_id", "provider", "end_user_id", "name", name="unique_enduser_authentication_provider"), + sa.Index("tool_enduser_authentication_provider_tenant_id_idx", "tenant_id"), + sa.Index("tool_enduser_authentication_provider_end_user_id_idx", "end_user_id"), + ) + + # id of the authentication provider + id: Mapped[str] = mapped_column(StringUUID, server_default=sa.text("uuid_generate_v4()"), init=False) + name: Mapped[str] = mapped_column( + String(256), + nullable=False, + server_default=sa.text("'API KEY 1'::character varying"), + ) + # id of the tenant + tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False) + # id of the end user + end_user_id: Mapped[str] = mapped_column(StringUUID, nullable=False) + # name of the tool provider + provider: Mapped[str] = mapped_column(String(256), nullable=False) + # encrypted credentials for the end user + encrypted_credentials: Mapped[str | None] = mapped_column(sa.Text, nullable=True, default=None) + created_at: Mapped[datetime] = mapped_column( + sa.DateTime, nullable=False, server_default=sa.text("CURRENT_TIMESTAMP(0)"), init=False + ) + updated_at: Mapped[datetime] = mapped_column( + sa.DateTime, + nullable=False, + server_default=sa.text("CURRENT_TIMESTAMP(0)"), + onupdate=func.current_timestamp(), + init=False, + ) + # credential type, e.g., "api-key", "oauth2" + credential_type: Mapped[str] = mapped_column( + String(32), nullable=False, server_default=sa.text("'api-key'::character varying"), default="api-key" + ) + expires_at: Mapped[int] = mapped_column(sa.BigInteger, nullable=False, server_default=sa.text("-1"), default=-1) + + @property + def credentials(self) -> dict[str, Any]: + if not self.encrypted_credentials: + return {} + return cast(dict[str, Any], json.loads(self.encrypted_credentials)) + + class ApiToolProvider(TypeBase): """ The table stores the api providers. From adf673d03170670b9393243738311038df704b66 Mon Sep 17 00:00:00 2001 From: Asuka Minato Date: Wed, 19 Nov 2025 17:18:50 +0900 Subject: [PATCH 02/17] Apply suggestion from @gemini-code-assist[bot] Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- api/models/tools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/models/tools.py b/api/models/tools.py index 3d42cfa682..7ea12392ca 100644 --- a/api/models/tools.py +++ b/api/models/tools.py @@ -144,12 +144,12 @@ class EndUserAuthenticationProvider(TypeBase): # encrypted credentials for the end user encrypted_credentials: Mapped[str | None] = mapped_column(sa.Text, nullable=True, default=None) created_at: Mapped[datetime] = mapped_column( - sa.DateTime, nullable=False, server_default=sa.text("CURRENT_TIMESTAMP(0)"), init=False + sa.DateTime, nullable=False, server_default=func.current_timestamp(), init=False ) updated_at: Mapped[datetime] = mapped_column( sa.DateTime, nullable=False, - server_default=sa.text("CURRENT_TIMESTAMP(0)"), + server_default=func.current_timestamp(), onupdate=func.current_timestamp(), init=False, ) From 76069b5d6d2cd450082e15efb491c8f55391ce23 Mon Sep 17 00:00:00 2001 From: Charles Yao Date: Tue, 18 Nov 2025 14:57:25 -0600 Subject: [PATCH 03/17] ECO-171: add migration script --- ...9d1_add_enduser_authentication_provider.py | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 api/migrations/versions/2025_11_18_1400-a7b4e8f2c9d1_add_enduser_authentication_provider.py diff --git a/api/migrations/versions/2025_11_18_1400-a7b4e8f2c9d1_add_enduser_authentication_provider.py b/api/migrations/versions/2025_11_18_1400-a7b4e8f2c9d1_add_enduser_authentication_provider.py new file mode 100644 index 0000000000..bb2391d4b7 --- /dev/null +++ b/api/migrations/versions/2025_11_18_1400-a7b4e8f2c9d1_add_enduser_authentication_provider.py @@ -0,0 +1,94 @@ +"""add enduser authentication provider + +Revision ID: a7b4e8f2c9d1 +Revises: 132392a2635f +Create Date: 2025-11-18 14:00:00.000000 + +""" +import models as models +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "a7b4e8f2c9d1" +down_revision = "132392a2635f" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "tool_enduser_authentication_providers", + sa.Column( + "id", + models.types.StringUUID(), + server_default=sa.text("uuid_generate_v4()"), + nullable=False, + ), + sa.Column( + "name", + sa.String(length=256), + server_default=sa.text("'API KEY 1'::character varying"), + nullable=False, + ), + sa.Column("tenant_id", models.types.StringUUID(), nullable=False), + sa.Column("end_user_id", models.types.StringUUID(), nullable=False), + sa.Column("provider", sa.String(length=256), nullable=False), + sa.Column("encrypted_credentials", sa.Text(), nullable=True), + sa.Column( + "created_at", + sa.DateTime(), + server_default=sa.text("CURRENT_TIMESTAMP(0)"), + nullable=False, + ), + sa.Column( + "updated_at", + sa.DateTime(), + server_default=sa.text("CURRENT_TIMESTAMP(0)"), + nullable=False, + ), + sa.Column( + "credential_type", + sa.String(length=32), + server_default=sa.text("'api-key'::character varying"), + nullable=False, + ), + sa.Column("expires_at", sa.BigInteger(), server_default=sa.text("-1"), nullable=False), + sa.PrimaryKeyConstraint("id", name="tool_enduser_authentication_provider_pkey"), + sa.UniqueConstraint( + "tenant_id", + "provider", + "end_user_id", + "name", + name="unique_enduser_authentication_provider", + ), + ) + op.create_index( + "tool_enduser_authentication_provider_tenant_id_idx", + "tool_enduser_authentication_providers", + ["tenant_id"], + unique=False, + ) + op.create_index( + "tool_enduser_authentication_provider_end_user_id_idx", + "tool_enduser_authentication_providers", + ["end_user_id"], + unique=False, + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_index( + "tool_enduser_authentication_provider_end_user_id_idx", + table_name="tool_enduser_authentication_providers", + ) + op.drop_index( + "tool_enduser_authentication_provider_tenant_id_idx", + table_name="tool_enduser_authentication_providers", + ) + op.drop_table("tool_enduser_authentication_providers") + # ### end Alembic commands ### + From 5e93a618656141b8bda89d803971282509ba9992 Mon Sep 17 00:00:00 2001 From: Charles Yao Date: Tue, 18 Nov 2025 15:03:16 -0600 Subject: [PATCH 04/17] ECO-171: edited migration script --- ...1_18_1400-a7b4e8f2c9d1_add_enduser_authentication_provider.py | 1 + 1 file changed, 1 insertion(+) diff --git a/api/migrations/versions/2025_11_18_1400-a7b4e8f2c9d1_add_enduser_authentication_provider.py b/api/migrations/versions/2025_11_18_1400-a7b4e8f2c9d1_add_enduser_authentication_provider.py index bb2391d4b7..2668b9c752 100644 --- a/api/migrations/versions/2025_11_18_1400-a7b4e8f2c9d1_add_enduser_authentication_provider.py +++ b/api/migrations/versions/2025_11_18_1400-a7b4e8f2c9d1_add_enduser_authentication_provider.py @@ -92,3 +92,4 @@ def downgrade(): op.drop_table("tool_enduser_authentication_providers") # ### end Alembic commands ### + From 39de9e72487980360412ea23bd2e0acbfd7c488d Mon Sep 17 00:00:00 2001 From: Charles Yao Date: Thu, 20 Nov 2025 21:18:14 -0600 Subject: [PATCH 05/17] Update api/models/tools.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- api/models/tools.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api/models/tools.py b/api/models/tools.py index 7ea12392ca..0e8cbaebec 100644 --- a/api/models/tools.py +++ b/api/models/tools.py @@ -133,7 +133,8 @@ class EndUserAuthenticationProvider(TypeBase): name: Mapped[str] = mapped_column( String(256), nullable=False, - server_default=sa.text("'API KEY 1'::character varying"), + server_default=sa.text("'API KEY 1'"), + default="API KEY 1", ) # id of the tenant tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False) From 6cd7ab4719c8a859eebfcefcddac45f288b1e718 Mon Sep 17 00:00:00 2001 From: Charles Yao Date: Thu, 20 Nov 2025 21:18:25 -0600 Subject: [PATCH 06/17] Update api/models/tools.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- api/models/tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/models/tools.py b/api/models/tools.py index 0e8cbaebec..bd8d5576a0 100644 --- a/api/models/tools.py +++ b/api/models/tools.py @@ -156,7 +156,7 @@ class EndUserAuthenticationProvider(TypeBase): ) # credential type, e.g., "api-key", "oauth2" credential_type: Mapped[str] = mapped_column( - String(32), nullable=False, server_default=sa.text("'api-key'::character varying"), default="api-key" + String(32), nullable=False, server_default=sa.text("'api-key'"), default="api-key" ) expires_at: Mapped[int] = mapped_column(sa.BigInteger, nullable=False, server_default=sa.text("-1"), default=-1) From 153609b968796019fd15fe36cda0f24e614d7aca Mon Sep 17 00:00:00 2001 From: Charles Yao Date: Thu, 20 Nov 2025 22:57:37 -0600 Subject: [PATCH 07/17] ECO-171: remove server defaults --- api/models/tools.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/api/models/tools.py b/api/models/tools.py index bd8d5576a0..673a39f85b 100644 --- a/api/models/tools.py +++ b/api/models/tools.py @@ -14,6 +14,7 @@ from core.helper import encrypter from core.tools.entities.common_entities import I18nObject from core.tools.entities.tool_bundle import ApiToolBundle from core.tools.entities.tool_entities import ApiProviderSchemaType, WorkflowToolParameterConfiguration +from libs.uuid_utils import uuidv7 from models.base import TypeBase from .engine import db @@ -129,11 +130,10 @@ class EndUserAuthenticationProvider(TypeBase): ) # id of the authentication provider - id: Mapped[str] = mapped_column(StringUUID, server_default=sa.text("uuid_generate_v4()"), init=False) + id: Mapped[str] = mapped_column(StringUUID, default=uuidv7, init=False) name: Mapped[str] = mapped_column( String(256), nullable=False, - server_default=sa.text("'API KEY 1'"), default="API KEY 1", ) # id of the tenant @@ -145,20 +145,20 @@ class EndUserAuthenticationProvider(TypeBase): # encrypted credentials for the end user encrypted_credentials: Mapped[str | None] = mapped_column(sa.Text, nullable=True, default=None) created_at: Mapped[datetime] = mapped_column( - sa.DateTime, nullable=False, server_default=func.current_timestamp(), init=False + sa.DateTime, nullable=False, default=datetime.now, init=False ) updated_at: Mapped[datetime] = mapped_column( sa.DateTime, nullable=False, - server_default=func.current_timestamp(), + default=datetime.now, onupdate=func.current_timestamp(), init=False, ) # credential type, e.g., "api-key", "oauth2" credential_type: Mapped[str] = mapped_column( - String(32), nullable=False, server_default=sa.text("'api-key'"), default="api-key" + String(32), nullable=False, default="api-key" ) - expires_at: Mapped[int] = mapped_column(sa.BigInteger, nullable=False, server_default=sa.text("-1"), default=-1) + expires_at: Mapped[int] = mapped_column(sa.BigInteger, nullable=False, default=-1) @property def credentials(self) -> dict[str, Any]: From bbd466eababdea1e02935819e662bcc2c7934e66 Mon Sep 17 00:00:00 2001 From: Charles Yao Date: Thu, 20 Nov 2025 23:08:00 -0600 Subject: [PATCH 08/17] ECO-171: fix comments --- api/models/tools.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/api/models/tools.py b/api/models/tools.py index 673a39f85b..f365136708 100644 --- a/api/models/tools.py +++ b/api/models/tools.py @@ -123,27 +123,24 @@ class EndUserAuthenticationProvider(TypeBase): __tablename__ = "tool_enduser_authentication_providers" __table_args__ = ( - sa.PrimaryKeyConstraint("id", name="tool_enduser_authentication_provider_pkey"), sa.UniqueConstraint("tenant_id", "provider", "end_user_id", "name", name="unique_enduser_authentication_provider"), - sa.Index("tool_enduser_authentication_provider_tenant_id_idx", "tenant_id"), - sa.Index("tool_enduser_authentication_provider_end_user_id_idx", "end_user_id"), ) # id of the authentication provider - id: Mapped[str] = mapped_column(StringUUID, default=uuidv7, init=False) + id: Mapped[str] = mapped_column(StringUUID, primary_key=True, default=uuidv7, init=False) name: Mapped[str] = mapped_column( String(256), nullable=False, default="API KEY 1", ) # id of the tenant - tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False) + tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False, index=True) # id of the end user - end_user_id: Mapped[str] = mapped_column(StringUUID, nullable=False) + end_user_id: Mapped[str] = mapped_column(StringUUID, nullable=False, index=True) # name of the tool provider - provider: Mapped[str] = mapped_column(String(256), nullable=False) + provider: Mapped[str] = mapped_column(sa.Text, nullable=False) # encrypted credentials for the end user - encrypted_credentials: Mapped[str | None] = mapped_column(sa.Text, nullable=True, default=None) + encrypted_credentials: Mapped[str] = mapped_column(sa.Text, nullable=False, default="") created_at: Mapped[datetime] = mapped_column( sa.DateTime, nullable=False, default=datetime.now, init=False ) From 5fd83e9425aaa50da4f4bd53b66df5f05527d18d Mon Sep 17 00:00:00 2001 From: Charles Yao Date: Sun, 23 Nov 2025 20:41:13 -0600 Subject: [PATCH 09/17] ECO-171: remove postgresql specific features --- ...9d1_add_enduser_authentication_provider.py | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/api/migrations/versions/2025_11_18_1400-a7b4e8f2c9d1_add_enduser_authentication_provider.py b/api/migrations/versions/2025_11_18_1400-a7b4e8f2c9d1_add_enduser_authentication_provider.py index 2668b9c752..f4e1a504dd 100644 --- a/api/migrations/versions/2025_11_18_1400-a7b4e8f2c9d1_add_enduser_authentication_provider.py +++ b/api/migrations/versions/2025_11_18_1400-a7b4e8f2c9d1_add_enduser_authentication_provider.py @@ -23,19 +23,18 @@ def upgrade(): sa.Column( "id", models.types.StringUUID(), - server_default=sa.text("uuid_generate_v4()"), nullable=False, ), sa.Column( "name", sa.String(length=256), - server_default=sa.text("'API KEY 1'::character varying"), + server_default="API KEY 1", nullable=False, ), sa.Column("tenant_id", models.types.StringUUID(), nullable=False), sa.Column("end_user_id", models.types.StringUUID(), nullable=False), - sa.Column("provider", sa.String(length=256), nullable=False), - sa.Column("encrypted_credentials", sa.Text(), nullable=True), + sa.Column("provider", sa.Text(), nullable=False), + sa.Column("encrypted_credentials", sa.Text(), server_default="", nullable=False), sa.Column( "created_at", sa.DateTime(), @@ -51,23 +50,22 @@ def upgrade(): sa.Column( "credential_type", sa.String(length=32), - server_default=sa.text("'api-key'::character varying"), + server_default="api-key", nullable=False, ), sa.Column("expires_at", sa.BigInteger(), server_default=sa.text("-1"), nullable=False), sa.PrimaryKeyConstraint("id", name="tool_enduser_authentication_provider_pkey"), sa.UniqueConstraint( - "tenant_id", - "provider", "end_user_id", + "provider", "name", name="unique_enduser_authentication_provider", ), ) op.create_index( - "tool_enduser_authentication_provider_tenant_id_idx", + "tool_enduser_authentication_provider_name_idx", "tool_enduser_authentication_providers", - ["tenant_id"], + ["name"], unique=False, ) op.create_index( @@ -76,17 +74,27 @@ def upgrade(): ["end_user_id"], unique=False, ) + op.create_index( + "tool_enduser_authentication_provider_provider_idx", + "tool_enduser_authentication_providers", + ["provider"], + unique=False, + ) # ### end Alembic commands ### def downgrade(): # ### commands auto generated by Alembic - please adjust! ### + op.drop_index( + "tool_enduser_authentication_provider_provider_idx", + table_name="tool_enduser_authentication_providers", + ) op.drop_index( "tool_enduser_authentication_provider_end_user_id_idx", table_name="tool_enduser_authentication_providers", ) op.drop_index( - "tool_enduser_authentication_provider_tenant_id_idx", + "tool_enduser_authentication_provider_name_idx", table_name="tool_enduser_authentication_providers", ) op.drop_table("tool_enduser_authentication_providers") From 5b93ed3fcdd51be480b3f1b3606eabe33e8da08b Mon Sep 17 00:00:00 2001 From: Charles Yao Date: Sun, 23 Nov 2025 21:29:23 -0600 Subject: [PATCH 10/17] ECO-171: update unique constraints --- ...7b4e8f2c9d1_add_enduser_authentication_provider.py | 11 ----------- api/models/tools.py | 3 +-- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/api/migrations/versions/2025_11_18_1400-a7b4e8f2c9d1_add_enduser_authentication_provider.py b/api/migrations/versions/2025_11_18_1400-a7b4e8f2c9d1_add_enduser_authentication_provider.py index f4e1a504dd..f04ec55253 100644 --- a/api/migrations/versions/2025_11_18_1400-a7b4e8f2c9d1_add_enduser_authentication_provider.py +++ b/api/migrations/versions/2025_11_18_1400-a7b4e8f2c9d1_add_enduser_authentication_provider.py @@ -58,16 +58,9 @@ def upgrade(): sa.UniqueConstraint( "end_user_id", "provider", - "name", name="unique_enduser_authentication_provider", ), ) - op.create_index( - "tool_enduser_authentication_provider_name_idx", - "tool_enduser_authentication_providers", - ["name"], - unique=False, - ) op.create_index( "tool_enduser_authentication_provider_end_user_id_idx", "tool_enduser_authentication_providers", @@ -93,10 +86,6 @@ def downgrade(): "tool_enduser_authentication_provider_end_user_id_idx", table_name="tool_enduser_authentication_providers", ) - op.drop_index( - "tool_enduser_authentication_provider_name_idx", - table_name="tool_enduser_authentication_providers", - ) op.drop_table("tool_enduser_authentication_providers") # ### end Alembic commands ### diff --git a/api/models/tools.py b/api/models/tools.py index 249eb9fd6a..2af592cd30 100644 --- a/api/models/tools.py +++ b/api/models/tools.py @@ -119,7 +119,7 @@ class EndUserAuthenticationProvider(TypeBase): __tablename__ = "tool_enduser_authentication_providers" __table_args__ = ( - sa.UniqueConstraint("end_user_id", "provider", "name"), + sa.UniqueConstraint("end_user_id", "provider"), ) # id of the authentication provider @@ -128,7 +128,6 @@ class EndUserAuthenticationProvider(TypeBase): String(256), nullable=False, default="API KEY 1", - index=True ) # id of the tenant tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False) From f2df8af4c836683fc91aa1e10cf0954b04789814 Mon Sep 17 00:00:00 2001 From: Charles Yao Date: Sun, 23 Nov 2025 23:00:10 -0600 Subject: [PATCH 11/17] ECO-171: update to use server default time stamp --- api/models/tools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/models/tools.py b/api/models/tools.py index 2af592cd30..6b84d3dd9b 100644 --- a/api/models/tools.py +++ b/api/models/tools.py @@ -138,12 +138,12 @@ class EndUserAuthenticationProvider(TypeBase): # encrypted credentials for the end user encrypted_credentials: Mapped[str] = mapped_column(LongText, nullable=False, default="") created_at: Mapped[datetime] = mapped_column( - sa.DateTime, nullable=False, default=datetime.now, init=False + sa.DateTime, nullable=False, server_default=func.current_timestamp(), init=False ) updated_at: Mapped[datetime] = mapped_column( sa.DateTime, nullable=False, - default=datetime.now, + server_default=func.current_timestamp(), onupdate=func.current_timestamp(), init=False, ) From 4590dab0461c0d74b703980af8e37f6ed2467a80 Mon Sep 17 00:00:00 2001 From: Charles Yao Date: Sun, 23 Nov 2025 23:10:24 -0600 Subject: [PATCH 12/17] ECO-171: update migration script with sql alchemy auto naming --- ...7b4e8f2c9d1_add_enduser_authentication_provider.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/api/migrations/versions/2025_11_18_1400-a7b4e8f2c9d1_add_enduser_authentication_provider.py b/api/migrations/versions/2025_11_18_1400-a7b4e8f2c9d1_add_enduser_authentication_provider.py index f04ec55253..376d57cf23 100644 --- a/api/migrations/versions/2025_11_18_1400-a7b4e8f2c9d1_add_enduser_authentication_provider.py +++ b/api/migrations/versions/2025_11_18_1400-a7b4e8f2c9d1_add_enduser_authentication_provider.py @@ -54,21 +54,20 @@ def upgrade(): nullable=False, ), sa.Column("expires_at", sa.BigInteger(), server_default=sa.text("-1"), nullable=False), - sa.PrimaryKeyConstraint("id", name="tool_enduser_authentication_provider_pkey"), + sa.PrimaryKeyConstraint("id"), sa.UniqueConstraint( "end_user_id", "provider", - name="unique_enduser_authentication_provider", ), ) op.create_index( - "tool_enduser_authentication_provider_end_user_id_idx", + op.f("ix_tool_enduser_authentication_providers_end_user_id"), "tool_enduser_authentication_providers", ["end_user_id"], unique=False, ) op.create_index( - "tool_enduser_authentication_provider_provider_idx", + op.f("ix_tool_enduser_authentication_providers_provider"), "tool_enduser_authentication_providers", ["provider"], unique=False, @@ -79,11 +78,11 @@ def upgrade(): def downgrade(): # ### commands auto generated by Alembic - please adjust! ### op.drop_index( - "tool_enduser_authentication_provider_provider_idx", + op.f("ix_tool_enduser_authentication_providers_provider"), table_name="tool_enduser_authentication_providers", ) op.drop_index( - "tool_enduser_authentication_provider_end_user_id_idx", + op.f("ix_tool_enduser_authentication_providers_end_user_id"), table_name="tool_enduser_authentication_providers", ) op.drop_table("tool_enduser_authentication_providers") From cf2457a03cc05b7f2e673a2b9debd9ec056a52c7 Mon Sep 17 00:00:00 2001 From: Charles Yao Date: Sun, 23 Nov 2025 23:17:24 -0600 Subject: [PATCH 13/17] ECO-171: update credential cast --- api/models/tools.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/api/models/tools.py b/api/models/tools.py index 6b84d3dd9b..e51d4780e9 100644 --- a/api/models/tools.py +++ b/api/models/tools.py @@ -157,7 +157,10 @@ class EndUserAuthenticationProvider(TypeBase): def credentials(self) -> dict[str, Any]: if not self.encrypted_credentials: return {} - return cast(dict[str, Any], json.loads(self.encrypted_credentials)) + try: + return cast(dict[str, Any], json.loads(self.encrypted_credentials)) + except json.JSONDecodeError: + return {} class ApiToolProvider(TypeBase): From 2e2d7a53455490afb84e736e23c5e339376e3539 Mon Sep 17 00:00:00 2001 From: -LAN- Date: Mon, 24 Nov 2025 13:23:31 +0800 Subject: [PATCH 14/17] Apply suggestion from @gemini-code-assist[bot] Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- api/models/tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/models/tools.py b/api/models/tools.py index e51d4780e9..39e3ec100a 100644 --- a/api/models/tools.py +++ b/api/models/tools.py @@ -123,7 +123,7 @@ class EndUserAuthenticationProvider(TypeBase): ) # id of the authentication provider - id: Mapped[str] = mapped_column(StringUUID, primary_key=True, default=uuidv7, init=False) + id: Mapped[str] = mapped_column(StringUUID, primary_key=True, default=lambda: str(uuid4()), init=False) name: Mapped[str] = mapped_column( String(256), nullable=False, From 30594978f97718ced0f0d18cb742071827f219ef Mon Sep 17 00:00:00 2001 From: Charles Yao Date: Sun, 23 Nov 2025 23:43:52 -0600 Subject: [PATCH 15/17] ECO-171: update to competible with mysql --- ...1400-a7b4e8f2c9d1_add_enduser_authentication_provider.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/migrations/versions/2025_11_18_1400-a7b4e8f2c9d1_add_enduser_authentication_provider.py b/api/migrations/versions/2025_11_18_1400-a7b4e8f2c9d1_add_enduser_authentication_provider.py index 376d57cf23..75a24ec343 100644 --- a/api/migrations/versions/2025_11_18_1400-a7b4e8f2c9d1_add_enduser_authentication_provider.py +++ b/api/migrations/versions/2025_11_18_1400-a7b4e8f2c9d1_add_enduser_authentication_provider.py @@ -34,17 +34,17 @@ def upgrade(): sa.Column("tenant_id", models.types.StringUUID(), nullable=False), sa.Column("end_user_id", models.types.StringUUID(), nullable=False), sa.Column("provider", sa.Text(), nullable=False), - sa.Column("encrypted_credentials", sa.Text(), server_default="", nullable=False), + sa.Column("encrypted_credentials", sa.Text(), default="", nullable=False), sa.Column( "created_at", sa.DateTime(), - server_default=sa.text("CURRENT_TIMESTAMP(0)"), + server_default=sa.func.current_timestamp(), nullable=False, ), sa.Column( "updated_at", sa.DateTime(), - server_default=sa.text("CURRENT_TIMESTAMP(0)"), + server_default=sa.func.current_timestamp(), nullable=False, ), sa.Column( From b51bf33b1ed41000276eafcc2a0be12a7ece77c2 Mon Sep 17 00:00:00 2001 From: Charles Yao Date: Mon, 24 Nov 2025 00:02:37 -0600 Subject: [PATCH 16/17] ECO-171: adding comments for expire_at as clarification --- api/models/tools.py | 1 + 1 file changed, 1 insertion(+) diff --git a/api/models/tools.py b/api/models/tools.py index 39e3ec100a..2d4dc2fcf5 100644 --- a/api/models/tools.py +++ b/api/models/tools.py @@ -151,6 +151,7 @@ class EndUserAuthenticationProvider(TypeBase): credential_type: Mapped[CredentialType] = mapped_column( String(32), nullable=False, default=CredentialType.API_KEY ) + # Unix timestamp in seconds since epoch (1970-01-01 UTC); -1 indicates no expiration expires_at: Mapped[int] = mapped_column(sa.BigInteger, nullable=False, default=-1) @property From 0e355079fac369fd240cfd4c42fbe58e3d447a34 Mon Sep 17 00:00:00 2001 From: Charles Yao Date: Mon, 24 Nov 2025 01:26:54 -0600 Subject: [PATCH 17/17] ECO-171: removing duplicated index --- api/models/tools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/models/tools.py b/api/models/tools.py index 2d4dc2fcf5..95cadeb030 100644 --- a/api/models/tools.py +++ b/api/models/tools.py @@ -132,9 +132,9 @@ class EndUserAuthenticationProvider(TypeBase): # id of the tenant tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False) # id of the end user - end_user_id: Mapped[str] = mapped_column(StringUUID, nullable=False, index=True) + end_user_id: Mapped[str] = mapped_column(StringUUID, nullable=False) # name of the tool provider - provider: Mapped[str] = mapped_column(LongText, nullable=False, index=True) + provider: Mapped[str] = mapped_column(LongText, nullable=False) # encrypted credentials for the end user encrypted_credentials: Mapped[str] = mapped_column(LongText, nullable=False, default="") created_at: Mapped[datetime] = mapped_column(