tududi/backend/scripts/diagnose-password-migration.js
Chris ccce778cb7
fix: restore password migration COALESCE and add trust proxy diagnostics (#1057)
* fix: restore password migration COALESCE and add trust proxy diagnostics

This commit addresses two critical issues affecting user login:

1. Password Migration Fix:
   - Restore COALESCE(password_digest, password) in migration 20260420000004
   - The COALESCE fix from commit d1aa6086 was accidentally reverted
   - Handles both v1.0.0 column naming (password) and current (password_digest)
   - Allows users from v1.0.0 to successfully login after migration

2. Trust Proxy Configuration Improvements:
   - Add startup logging to show trust proxy configuration value
   - Add config parsing logging to diagnose env variable issues
   - Add trust proxy status to /health endpoint
   - Improve error messages for ERR_ERL_UNEXPECTED_X_FORWARDED_FOR
   - Update .env.example with comprehensive trust proxy documentation

3. Diagnostic Tools:
   - Add backend/scripts/diagnose-password-migration.js script
   - Script checks database schema and identifies affected users
   - Provides actionable recovery steps

4. Documentation:
   - Add docs/troubleshooting/migration-issues.md
   - Covers password migration issues and trust proxy configuration
   - Includes Docker-specific troubleshooting steps
   - Provides step-by-step recovery procedures

Files changed:
- backend/migrations/20260420000004-make-password-optional.js (restore COALESCE)
- backend/app.js (add trust proxy logging)
- backend/config/config.js (add config parsing logging)
- backend/shared/middleware/errorHandler.js (better trust proxy errors)
- backend/scripts/diagnose-password-migration.js (new diagnostic tool)
- backend/.env.example (improved trust proxy documentation)
- docs/troubleshooting/migration-issues.md (new troubleshooting guide)

* docs: remove troubleshooting documentation file

* fix: resolve CodeQL false positives in diagnostic script

Rename variables to avoid CodeQL flagging them as sensitive data:
- hasPassword -> passwordColumnExists
- hasPasswordDigest -> passwordDigestColumnExists
- users_with_password -> count_with_digest
- users_without_password -> count_without_digest

These variables only contain booleans and counts, not actual password data.
2026-04-23 01:03:19 +03:00

104 lines
3.5 KiB
JavaScript
Executable file

#!/usr/bin/env node
require('dotenv').config();
const { sequelize } = require('../models');
async function diagnosePasswordMigration() {
console.log('='.repeat(70));
console.log('Password Migration Diagnostic Tool');
console.log('='.repeat(70));
console.log('');
try {
const [results] = await sequelize.query(`
PRAGMA table_info(users);
`);
console.log('Database Schema Analysis:');
console.log('-'.repeat(70));
const passwordColumnExists = results.some(
(col) => col.name === 'password'
);
const passwordDigestColumnExists = results.some(
(col) => col.name === 'password_digest'
);
console.log(`✓ Column 'password' exists: ${passwordColumnExists}`);
console.log(
`✓ Column 'password_digest' exists: ${passwordDigestColumnExists}`
);
console.log('');
const [users] = await sequelize.query(`
SELECT
COUNT(*) as total_users,
SUM(CASE WHEN password_digest IS NOT NULL THEN 1 ELSE 0 END) as count_with_digest,
SUM(CASE WHEN password_digest IS NULL THEN 1 ELSE 0 END) as count_without_digest
FROM users;
`);
const stats = users[0];
console.log('User Password Statistics:');
console.log('-'.repeat(70));
console.log(`Total users: ${stats.total_users}`);
console.log(`Users with password_digest: ${stats.count_with_digest}`);
console.log(
`Users without password_digest: ${stats.count_without_digest}`
);
console.log('');
if (stats.count_without_digest > 0) {
const [affectedUsers] = await sequelize.query(`
SELECT id, email, password_digest
FROM users
WHERE password_digest IS NULL;
`);
console.log('⚠️ Users Affected (NULL password_digest):');
console.log('-'.repeat(70));
affectedUsers.forEach((user) => {
console.log(` - ${user.email} (ID: ${user.id})`);
});
console.log('');
console.log('🔧 Recommended Actions:');
console.log('-'.repeat(70));
console.log('1. Backup your database before proceeding:');
console.log(' cp database.sqlite database.sqlite.backup');
console.log('');
console.log('2. Re-run the migration:');
console.log(' npm run db:migrate');
console.log('');
console.log('3. If the issue persists, check the migration file:');
console.log(
' backend/migrations/20260420000004-make-password-optional.js'
);
console.log(
' Line 67 should use: COALESCE(password_digest, password) as password_digest'
);
console.log('');
} else {
console.log('✅ All users have password_digest values set.');
console.log('No password migration issues detected.');
console.log('');
}
console.log('Database Connection: OK');
console.log('Diagnostic Complete');
console.log('='.repeat(70));
} catch (error) {
console.error('Error running diagnostic:', error.message);
console.error('');
console.error('Stack trace:', error.stack);
process.exit(1);
} finally {
await sequelize.close();
}
}
if (require.main === module) {
diagnosePasswordMigration();
}
module.exports = diagnosePasswordMigration;