From 05636de7d2e250dd31710b5d4dcde8c5c6724a0d Mon Sep 17 00:00:00 2001 From: Graham Christensen Date: Fri, 16 Apr 2021 12:09:30 -0400 Subject: [PATCH] hydra-init: upgrade passwords to Argon2 on startup --- src/script/hydra-init | 9 ++++++ t/scripts/hydra-init.t | 68 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 t/scripts/hydra-init.t diff --git a/src/script/hydra-init b/src/script/hydra-init index 74498186..ac9d4afe 100755 --- a/src/script/hydra-init +++ b/src/script/hydra-init @@ -72,3 +72,12 @@ for (my $n = $schemaVersion; $n < $maxSchemaVersion; $n++) { }; die "schema upgrade failed: $@\n" if $@; } + +my @usersWithSha1s = $db->resultset('Users')->search(\['LENGTH(password) = 40 AND password ~ \'^[0-9a-f]{40}$\'']); +if (scalar(@usersWithSha1s) > 0) { + print STDERR "upgrading user passwords from sha1\n"; + for my $user (@usersWithSha1s) { + print STDERR " * " . $user->username . "\n"; + $user->setPassword($user->password); + } +} diff --git a/t/scripts/hydra-init.t b/t/scripts/hydra-init.t new file mode 100644 index 00000000..517a14d7 --- /dev/null +++ b/t/scripts/hydra-init.t @@ -0,0 +1,68 @@ +use feature 'unicode_strings'; +use strict; +use Setup; + +my %ctx = test_init(); + +require Hydra::Schema; +require Hydra::Model::DB; + +use Test2::V0; + +my $db = Hydra::Model::DB->new; +hydra_setup($db); + +subtest "hydra-init upgrades user's password hashes from sha1 to sha1 inside Argon2" => sub { + my $alice = $db->resultset('Users')->create({ + "username" => "alice", + "emailaddress" => 'alice@nixos.org', + "password" => "8843d7f92416211de9ebb963ff4ce28125932878" # SHA1 of "foobar" + }); + my $janet = $db->resultset('Users')->create({ + "username" => "janet", + "emailaddress" => 'janet@nixos.org', + "password" => "!" + }); + $janet->setPassword("foobar"); + + is($alice->password, "8843d7f92416211de9ebb963ff4ce28125932878", "Alices's sha1 is stored in the database"); + my ($res, $stdout, $stderr) = captureStdoutStderr(5, ("hydra-init")); + if ($res != 0) { + is($stdout, ""); + is($stderr, ""); + } + is($res, 0, "hydra-init should exit zero"); + + subtest "Alice had their password updated in place" => sub { + my $updatedAlice = $db->resultset('Users')->find({ username => "alice" }); + isnt($updatedAlice, undef); + isnt($updatedAlice->password, "8843d7f92416211de9ebb963ff4ce28125932878", "The password was updated in place."); + + my $storedPassword = $updatedAlice->password; + ok($updatedAlice->check_password("foobar"), "Their password validates"); + isnt($storedPassword, $updatedAlice->password, "The password is upgraded in place."); + }; + + subtest "Janet did not have their password change" => sub { + my $updatedJanet = $db->resultset('Users')->find({ username => "janet" }); + isnt($updatedJanet, undef); + is($updatedJanet->password, $janet->password, "The password was not updated in place."); + + ok($updatedJanet->check_password("foobar"), "Their password validates"); + is($updatedJanet->password, $janet->password, "The password is not upgraded in place."); + }; + + subtest "Running hydra-init don't break Alice or Janet's passwords" => sub { + my ($res, $stdout, $stderr) = captureStdoutStderr(5, ("hydra-init")); + is($res, 0, "hydra-init should exit zero"); + + my $updatedAlice = $db->resultset('Users')->find({ username => "alice" }); + ok($updatedAlice->check_password("foobar"), "Alice's password validates"); + + my $updatedJanet = $db->resultset('Users')->find({ username => "janet" }); + ok($updatedJanet->check_password("foobar"), "Janet's password validates"); + }; + +}; + +done_testing;