mirror of
https://github.com/discourse/discourse.git
synced 2025-02-25 18:55:32 -06:00
DEV: Migrate user passwords data to UserPassword table (#28746)
* Add migrations to ensure password hash is synced across users & user_passwords * Persist password-related data in user_passwords instead of users * Merge User#expire_old_email_tokens with User#expire_tokens_if_password_changed * Add post deploy migration to mark password-related columns from users table as read-only * Refactored UserPassword#confirm_password? and changes required to accommodate hashing the password after validations
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
# frozen_string_literal: true
|
||||
class AddTriggerToUsersToSyncUserPasswords < ActiveRecord::Migration[7.1]
|
||||
def up
|
||||
# necessary for postgres < v14 which does not have CREATE OR REPLACE TRIGGER
|
||||
execute <<~SQL.squish
|
||||
DROP TRIGGER IF EXISTS
|
||||
users_password_sync ON users;
|
||||
SQL
|
||||
|
||||
execute <<~SQL.squish
|
||||
CREATE OR REPLACE FUNCTION mirror_user_password_data() RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
INSERT INTO user_passwords (user_id, password_hash, password_salt, password_algorithm, password_expired_at, created_at, updated_at)
|
||||
VALUES (NEW.id, NEW.password_hash, NEW.salt, NEW.password_algorithm, NULL, now(), now())
|
||||
ON CONFLICT(user_id)
|
||||
DO UPDATE SET
|
||||
password_hash = EXCLUDED.password_hash,
|
||||
password_salt = EXCLUDED.password_salt,
|
||||
password_algorithm = EXCLUDED.password_algorithm,
|
||||
password_expired_at = EXCLUDED.password_expired_at,
|
||||
updated_at = now()
|
||||
WHERE user_passwords.password_hash <> EXCLUDED.password_hash;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
SQL
|
||||
|
||||
execute <<~SQL.squish
|
||||
CREATE TRIGGER users_password_sync
|
||||
AFTER INSERT OR UPDATE OF password_hash ON users
|
||||
FOR EACH ROW
|
||||
WHEN (NEW.password_hash IS NOT NULL)
|
||||
EXECUTE PROCEDURE mirror_user_password_data();
|
||||
SQL
|
||||
|
||||
execute <<~SQL.squish
|
||||
DROP TRIGGER IF EXISTS
|
||||
users_password_sync_on_delete_password ON users;
|
||||
SQL
|
||||
|
||||
execute <<~SQL.squish
|
||||
CREATE OR REPLACE FUNCTION delete_user_password() RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
DELETE FROM user_passwords WHERE user_id = NEW.id;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
SQL
|
||||
|
||||
execute <<~SQL.squish
|
||||
CREATE TRIGGER users_password_sync_on_delete_password
|
||||
AFTER UPDATE OF password_hash ON users
|
||||
FOR EACH ROW
|
||||
WHEN (NEW.password_hash IS NULL)
|
||||
EXECUTE PROCEDURE delete_user_password();
|
||||
SQL
|
||||
end
|
||||
|
||||
def down
|
||||
execute <<~SQL.squish
|
||||
DROP TRIGGER IF EXISTS users_password_sync_on_delete_password ON users;
|
||||
DROP FUNCTION IF EXISTS delete_user_password;
|
||||
DROP TRIGGER IF EXISTS users_password_sync ON users;
|
||||
DROP FUNCTION IF EXISTS mirror_user_password_data;
|
||||
SQL
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,33 @@
|
||||
# frozen_string_literal: true
|
||||
class BackfillUserPasswordsFromUsers < ActiveRecord::Migration[7.1]
|
||||
disable_ddl_transaction!
|
||||
BATCH_SIZE = 50_000
|
||||
|
||||
def up
|
||||
min_id, max_id = DB.query_single(<<~SQL.squish)
|
||||
SELECT MIN(id), MAX(id)
|
||||
FROM users
|
||||
WHERE password_hash IS NOT NULL AND salt IS NOT NULL AND password_algorithm IS NOT NULL;
|
||||
SQL
|
||||
|
||||
return if max_id.nil?
|
||||
|
||||
(min_id..max_id).step(BATCH_SIZE) { |start_id| execute <<~SQL }
|
||||
INSERT INTO user_passwords (user_id, password_hash, password_salt, password_algorithm, password_expired_at, created_at, updated_at)
|
||||
SELECT id, password_hash, salt, password_algorithm, NULL, now(), now()
|
||||
FROM users
|
||||
WHERE id >= #{start_id} AND id < #{start_id + BATCH_SIZE} AND password_hash IS NOT NULL AND salt IS NOT NULL AND password_algorithm IS NOT NULL
|
||||
ON CONFLICT (user_id) DO UPDATE SET
|
||||
password_hash = EXCLUDED.password_hash,
|
||||
password_salt = EXCLUDED.password_salt,
|
||||
password_algorithm = EXCLUDED.password_algorithm,
|
||||
password_expired_at = EXCLUDED.password_expired_at,
|
||||
updated_at = now()
|
||||
WHERE user_passwords.password_hash <> EXCLUDED.password_hash
|
||||
SQL
|
||||
end
|
||||
|
||||
def down
|
||||
raise ActiveRecord::IrreversibleMigration
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user