diff --git a/backend/open_webui/migrations/versions/b10670c03dd5_update_user_table.py b/backend/open_webui/migrations/versions/b10670c03dd5_update_user_table.py index c3965dbd7c..ac0e3eeb61 100644 --- a/backend/open_webui/migrations/versions/b10670c03dd5_update_user_table.py +++ b/backend/open_webui/migrations/versions/b10670c03dd5_update_user_table.py @@ -185,9 +185,8 @@ def upgrade() -> None: for uid, oauth_sub in rows: if oauth_sub: provider, sub = oauth_sub.split('@', 1) if '@' in oauth_sub else ('oidc', oauth_sub) - conn.execute( - sa.update(_user).where(_user.c.id == uid).values(oauth=json.dumps({provider: {'sub': sub}})) - ) + # no json.dumps here: the column is JSON, SQLAlchemy serializes it + conn.execute(sa.update(_user).where(_user.c.id == uid).values(oauth={provider: {'sub': sub}})) # ── Migrate api_key column → api_key table (only if old column still exists) if 'api_key' in user_columns: @@ -226,10 +225,8 @@ def downgrade() -> None: for uid, oauth in rows: try: - data = json.loads(oauth) - provider = list(data.keys())[0] - sub = data[provider].get('sub') - oauth_sub = f'{provider}@{sub}' + provider = next(iter(oauth)) + oauth_sub = f'{provider}@{oauth[provider]["sub"]}' except Exception: oauth_sub = None diff --git a/backend/open_webui/migrations/versions/d4a71b6e29c8_repair_double_encoded_user_oauth.py b/backend/open_webui/migrations/versions/d4a71b6e29c8_repair_double_encoded_user_oauth.py new file mode 100644 index 0000000000..8d6b4d6294 --- /dev/null +++ b/backend/open_webui/migrations/versions/d4a71b6e29c8_repair_double_encoded_user_oauth.py @@ -0,0 +1,33 @@ +"""Repair double-encoded user.oauth values + +Revision ID: d4a71b6e29c8 +Revises: 1ce6ade7d93b +Create Date: 2026-08-04 12:00:00.000000 + +""" + +import json + +import sqlalchemy as sa +from alembic import op + +revision = 'd4a71b6e29c8' +down_revision = '1ce6ade7d93b' +branch_labels = None +depends_on = None + +_user = sa.table('user', sa.column('id', sa.Text), sa.column('oauth', sa.JSON)) + + +def upgrade(): + conn = op.get_bind() + rows = conn.execute(sa.select(_user.c.id, _user.c.oauth).where(_user.c.oauth.is_not(None))).fetchall() + + for uid, oauth in rows: + # b10670c03dd5 used to serialize this column twice, leaving a JSON string instead of an object + if isinstance(oauth, str): + conn.execute(sa.update(_user).where(_user.c.id == uid).values(oauth=json.loads(oauth))) + + +def downgrade(): + pass