From 986bf8f3aa71c9f1fdf2504913a192d307667ef1 Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Tue, 18 Nov 2025 11:03:39 +0100 Subject: [PATCH] feat: set state column to text instead of varchar Signed-off-by: benjdlambert --- .../20251118120000_oauth_state_text.js | 35 ++++++++++++ plugins/auth-backend/src/migrations.test.ts | 55 +++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 plugins/auth-backend/migrations/20251118120000_oauth_state_text.js diff --git a/plugins/auth-backend/migrations/20251118120000_oauth_state_text.js b/plugins/auth-backend/migrations/20251118120000_oauth_state_text.js new file mode 100644 index 0000000000..f082b8b9f0 --- /dev/null +++ b/plugins/auth-backend/migrations/20251118120000_oauth_state_text.js @@ -0,0 +1,35 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-check + +/** + * @param {import('knex').Knex} knex + */ +exports.up = async function up(knex) { + await knex.schema.alterTable('oauth_authorization_sessions', table => { + table.text('state').nullable().alter(); + }); +}; + +/** + * @param {import('knex').Knex} knex + */ +exports.down = async function down(knex) { + await knex.schema.alterTable('oauth_authorization_sessions', table => { + table.string('state').nullable().alter(); + }); +}; diff --git a/plugins/auth-backend/src/migrations.test.ts b/plugins/auth-backend/src/migrations.test.ts index df7063351a..89def54b71 100644 --- a/plugins/auth-backend/src/migrations.test.ts +++ b/plugins/auth-backend/src/migrations.test.ts @@ -304,4 +304,59 @@ describe('migrations', () => { await knex.destroy(); }, ); + + it.each(databases.eachSupportedId())( + '20251118120000_oauth_state_text.js, %p', + async databaseId => { + const knex = await databases.init(databaseId); + + await migrateUntilBefore(knex, '20251118120000_oauth_state_text.js'); + + // First create a client for the foreign key constraint + await knex + .insert({ + client_id: 'test-client-id', + client_secret: 'test-client-secret', + client_name: 'Test Client', + response_types: JSON.stringify(['code']), + grant_types: JSON.stringify(['authorization_code']), + redirect_uris: JSON.stringify(['https://example.com/callback']), + }) + .into('oidc_clients'); + + // Apply the migration that changes state to TEXT + await migrateUpOnce(knex); + + // Test inserting a state parameter longer than 255 characters + // This is based on the real-world example from the issue + const longState = 'a'.repeat(280); + + await knex + .insert({ + id: 'test-long-state-session', + client_id: 'test-client-id', + redirect_uri: 'https://example.com/callback', + state: longState, + response_type: 'code', + status: 'pending', + expires_at: new Date(Date.now() + 3600000), + }) + .into('oauth_authorization_sessions'); + + await expect( + knex('oauth_authorization_sessions') + .where('id', 'test-long-state-session') + .first(), + ).resolves.toEqual( + expect.objectContaining({ + id: 'test-long-state-session', + state: longState, + }), + ); + + await migrateDownOnce(knex); + + await knex.destroy(); + }, + ); });