fix: make password_digest migration compatible with all schema versions (#1078)

* fix: replace 6-word limit with 150-character limit for project names

Replaces the word-based validation with character-based validation
as originally requested in #971. The 6-word limit was causing issues
with small words and separators being counted equally, and didn't
match the original requirement for a character limit.

Changes:
- Backend: Replace wordCount validator with len validator (1-150 chars)
- Frontend: Replace word count validation with character length check
- UI already has line-clamp-3 for display truncation

Fixes #998

* fix: make password_digest migration compatible with all schema versions

Fixes a critical bug where the make-password-optional migration would silently
fail when upgrading from v1.0.0 or running on fresh v1.1.0-dev installations.

The migration was trying to SELECT columns (ai_provider, openai_api_key,
ollama_base_url, ollama_model) that don't exist in the users table at that
point in the migration chain, causing the INSERT...SELECT to fail and leaving
password_digest as NOT NULL. This prevented OIDC auto-provisioning from
creating new users without passwords.

The fix dynamically detects which columns exist in the users table using
PRAGMA table_info and only selects columns that are guaranteed to exist.
Missing columns (AI-related fields) will receive their default values from
the new table schema.

Changes:
- Added dynamic column detection using PRAGMA table_info
- Only SELECT columns that exist in the current users table
- AI columns get default values if they don't exist yet
- Applied same fix to both up and down migrations
- Properly handle password/password_digest column name migration

Fixes #1075
This commit is contained in:
Chris 2026-04-26 10:07:55 +03:00 committed by GitHub
parent b1a0a728d2
commit 7948f6552c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -68,34 +68,79 @@ module.exports = {
);
}
const existingColumns = new Set(columns.map((col) => col.name));
const baseColumns = [
'id',
'uid',
'name',
'surname',
'email',
'appearance',
'language',
'timezone',
'first_day_of_week',
'avatar_image',
'telegram_bot_token',
'telegram_chat_id',
'task_summary_enabled',
'task_summary_frequency',
'task_summary_last_run',
'task_summary_next_run',
'telegram_allowed_users',
'task_intelligence_enabled',
'auto_suggest_next_actions_enabled',
'pomodoro_enabled',
'productivity_assistant_enabled',
'next_task_suggestion_enabled',
'today_settings',
'sidebar_settings',
'ui_settings',
'notification_preferences',
'keyboard_shortcuts',
'email_verified',
'email_verification_token',
'email_verification_token_expires_at',
'created_at',
'updated_at',
];
const aiColumns = [
'ai_provider',
'openai_api_key',
'ollama_base_url',
'ollama_model',
];
const columnsToSelect = baseColumns.filter((col) =>
existingColumns.has(col)
);
const aiColumnsToSelect = aiColumns.filter((col) =>
existingColumns.has(col)
);
const columnsWithoutPassword = columnsToSelect.filter(
(col) => col !== passwordColumn && col !== 'password_digest'
);
const insertColumns = [
...columnsWithoutPassword,
'password_digest',
...aiColumnsToSelect,
];
const selectColumns = [
...columnsWithoutPassword,
`${passwordColumn} as password_digest`,
...aiColumnsToSelect,
];
const insertColumnsStr = insertColumns.join(', ');
const selectColumnsStr = selectColumns.join(', ');
await queryInterface.sequelize.query(`
INSERT INTO users_new (
id, uid, name, surname, email, password_digest, appearance, language,
timezone, first_day_of_week, avatar_image, telegram_bot_token,
telegram_chat_id, task_summary_enabled, task_summary_frequency,
task_summary_last_run, task_summary_next_run, telegram_allowed_users,
task_intelligence_enabled, auto_suggest_next_actions_enabled,
pomodoro_enabled, productivity_assistant_enabled,
next_task_suggestion_enabled, today_settings, sidebar_settings,
ui_settings, notification_preferences, keyboard_shortcuts,
email_verified, email_verification_token,
email_verification_token_expires_at, created_at, updated_at,
ai_provider, openai_api_key, ollama_base_url, ollama_model
)
SELECT
id, uid, name, surname, email,
${passwordColumn} as password_digest,
appearance, language,
timezone, first_day_of_week, avatar_image, telegram_bot_token,
telegram_chat_id, task_summary_enabled, task_summary_frequency,
task_summary_last_run, task_summary_next_run, telegram_allowed_users,
task_intelligence_enabled, auto_suggest_next_actions_enabled,
pomodoro_enabled, productivity_assistant_enabled,
next_task_suggestion_enabled, today_settings, sidebar_settings,
ui_settings, notification_preferences, keyboard_shortcuts,
email_verified, email_verification_token,
email_verification_token_expires_at, created_at, updated_at,
ai_provider, openai_api_key, ollama_base_url, ollama_model
INSERT INTO users_new (${insertColumnsStr})
SELECT ${selectColumnsStr}
FROM users;
`);
@ -155,32 +200,65 @@ module.exports = {
);
`);
const [columns] = await queryInterface.sequelize.query(
'PRAGMA table_info(users);'
);
const existingColumns = new Set(columns.map((col) => col.name));
const baseColumns = [
'id',
'uid',
'name',
'surname',
'email',
'password_digest',
'appearance',
'language',
'timezone',
'first_day_of_week',
'avatar_image',
'telegram_bot_token',
'telegram_chat_id',
'task_summary_enabled',
'task_summary_frequency',
'task_summary_last_run',
'task_summary_next_run',
'telegram_allowed_users',
'task_intelligence_enabled',
'auto_suggest_next_actions_enabled',
'pomodoro_enabled',
'productivity_assistant_enabled',
'next_task_suggestion_enabled',
'today_settings',
'sidebar_settings',
'ui_settings',
'notification_preferences',
'keyboard_shortcuts',
'email_verified',
'email_verification_token',
'email_verification_token_expires_at',
'created_at',
'updated_at',
];
const aiColumns = [
'ai_provider',
'openai_api_key',
'ollama_base_url',
'ollama_model',
];
const columnsToSelect = [
...baseColumns.filter((col) => existingColumns.has(col)),
...aiColumns.filter((col) => existingColumns.has(col)),
];
const insertColumnsStr = columnsToSelect.join(', ');
const selectColumnsStr = columnsToSelect.join(', ');
await queryInterface.sequelize.query(`
INSERT INTO users_new (
id, uid, name, surname, email, password_digest, appearance, language,
timezone, first_day_of_week, avatar_image, telegram_bot_token,
telegram_chat_id, task_summary_enabled, task_summary_frequency,
task_summary_last_run, task_summary_next_run, telegram_allowed_users,
task_intelligence_enabled, auto_suggest_next_actions_enabled,
pomodoro_enabled, productivity_assistant_enabled,
next_task_suggestion_enabled, today_settings, sidebar_settings,
ui_settings, notification_preferences, keyboard_shortcuts,
email_verified, email_verification_token,
email_verification_token_expires_at, created_at, updated_at,
ai_provider, openai_api_key, ollama_base_url, ollama_model
)
SELECT
id, uid, name, surname, email, password_digest, appearance, language,
timezone, first_day_of_week, avatar_image, telegram_bot_token,
telegram_chat_id, task_summary_enabled, task_summary_frequency,
task_summary_last_run, task_summary_next_run, telegram_allowed_users,
task_intelligence_enabled, auto_suggest_next_actions_enabled,
pomodoro_enabled, productivity_assistant_enabled,
next_task_suggestion_enabled, today_settings, sidebar_settings,
ui_settings, notification_preferences, keyboard_shortcuts,
email_verified, email_verification_token,
email_verification_token_expires_at, created_at, updated_at,
ai_provider, openai_api_key, ollama_base_url, ollama_model
INSERT INTO users_new (${insertColumnsStr})
SELECT ${selectColumnsStr}
FROM users;
`);