diff options
-rw-r--r-- | lib/VNDB/DB/Users.pm | 16 | ||||
-rw-r--r-- | lib/VNDB/Handler/Users.pm | 14 | ||||
-rw-r--r-- | lib/VNDB/Util/Auth.pm | 43 | ||||
-rw-r--r-- | util/sql/schema.sql | 10 | ||||
-rw-r--r-- | util/updates/update_2.23.sql | 6 |
5 files changed, 47 insertions, 42 deletions
diff --git a/lib/VNDB/DB/Users.pm b/lib/VNDB/DB/Users.pm index e7bf85ca..9082325a 100644 --- a/lib/VNDB/DB/Users.pm +++ b/lib/VNDB/DB/Users.pm @@ -55,10 +55,7 @@ sub dbUserGet { my @select = ( qw|id username c_votes c_changes c_tags|, q|extract('epoch' from registered) as registered|, - $o{what} =~ /extended/ ? ( - qw|mail perm salt ign_votes|, - q|encode(passwd, 'hex') AS passwd| - ) : (), + $o{what} =~ /extended/ ? qw|mail perm ign_votes passwd| : (), $o{what} =~ /hide_list/ ? 'up.value AS hide_list' : (), $o{what} =~ /notifycount/ ? '(SELECT COUNT(*) FROM notifications WHERE uid = u.id AND read IS NULL) AS notifycount' : (), @@ -119,10 +116,11 @@ sub dbUserEdit { my %h; defined $o{$_} && ($h{$_.' = ?'} = $o{$_}) - for (qw| username mail perm salt ign_votes email_confirmed |); - $h{'passwd = decode(?, \'hex\')'} = $o{passwd} + for (qw| username mail perm ign_votes email_confirmed |); + $h{'passwd = decode(?, \'hex\')'} = unpack 'H*', $o{passwd} if defined $o{passwd}; + return if scalar keys %h <= 0; return $s->dbExec(q| UPDATE users @@ -132,11 +130,11 @@ sub dbUserEdit { } -# username, pass(ecrypted), salt, mail, [ip] +# username, pass(ecrypted), mail, [ip] sub dbUserAdd { my($s, @o) = @_; - $s->dbRow(q|INSERT INTO users (username, passwd, salt, mail, ip) VALUES(?, decode(?, 'hex'), ?, ?, ?) RETURNING id|, - @o[0..3], $o[4]||$s->reqIP)->{id}; + $s->dbRow(q|INSERT INTO users (username, passwd, mail, ip) VALUES(?, decode(?, 'hex'), ?, ?) RETURNING id|, + $o[0], unpack('H*', $o[1]), $o[2], $o[3]||$s->reqIP)->{id}; } diff --git a/lib/VNDB/Handler/Users.pm b/lib/VNDB/Handler/Users.pm index 271561af..bcf94fb4 100644 --- a/lib/VNDB/Handler/Users.pm +++ b/lib/VNDB/Handler/Users.pm @@ -208,8 +208,9 @@ sub newpass { if(!$frm->{_err}) { my %o; my $token; - ($token, $o{passwd}, $o{salt}) = $self->authPrepareReset(); + ($token, $o{passwd}) = $self->authPrepareReset(); $self->dbUserEdit($u->{id}, %o); + #warn "$self->{url}/u$u->{id}/setpass?t=$token"; $self->mail(mt('_newpass_mail_body', $u->{username}, "$self->{url}/u$u->{id}/setpass?t=$token"), To => $frm->{mail}, From => 'VNDB <noreply@vndb.org>', @@ -254,7 +255,7 @@ sub setpass { $t = $t->{t}; my $u = $self->dbUserGet(uid => $uid, what => 'extended')->[0]; - return $self->resNotFound if !$u || !$self->authValidateReset($u, $t); + return $self->resNotFound if !$u || !$self->authValidateReset($u->{passwd}, $t); my $frm; if($self->reqMethod eq 'POST') { @@ -267,7 +268,7 @@ sub setpass { if(!$frm->{_err}) { my %o = (email_confirmed => 1); - ($o{passwd}, $o{salt}) = $self->authPreparePass($frm->{usrpass}); + $o{passwd} = $self->authPreparePass($frm->{usrpass}); $self->dbUserEdit($uid, %o); return $self->authLogin($u->{username}, $frm->{usrpass}, "/u$uid"); } @@ -307,8 +308,9 @@ sub register { push @{$frm->{_err}}, 'oneaday' if !$frm->{_err} && $self->dbUserGet(ip => $ip =~ /:/ ? "$ip/48" : $ip, registered => time-24*3600)->[0]{id}; if(!$frm->{_err}) { - my($token, $pass, $salt) = $self->authPrepareReset(); - my $uid = $self->dbUserAdd($frm->{usrname}, $pass, $salt, $frm->{mail}); + my($token, $pass) = $self->authPrepareReset(); + my $uid = $self->dbUserAdd($frm->{usrname}, $pass, $frm->{mail}); + warn "$self->{url}/u$uid/setpass?t=$token"; $self->mail(mt('_register_mail_body', $frm->{usrname}, "$self->{url}/u$uid/setpass?t=$token"), To => $frm->{mail}, From => 'VNDB <noreply@vndb.org>', @@ -388,7 +390,7 @@ sub edit { $o{perm} |= $self->{permissions}{$_} for(@{ delete $frm->{perms} }); } $o{mail} = $frm->{mail}; - ($o{passwd}, $o{salt}) = $self->authPreparePass($frm->{usrpass}) if $frm->{usrpass}; + $o{passwd} = $self->authPreparePass($frm->{usrpass}) if $frm->{usrpass}; $o{ign_votes} = $frm->{ign_votes} ? 1 : 0 if $self->authCan('usermod'); $self->dbUserEdit($uid, %o); $self->dbSessionDel($uid) if $frm->{usrpass}; diff --git a/lib/VNDB/Util/Auth.pm b/lib/VNDB/Util/Auth.pm index 89cbd215..63812d36 100644 --- a/lib/VNDB/Util/Auth.pm +++ b/lib/VNDB/Util/Auth.pm @@ -5,7 +5,7 @@ package VNDB::Util::Auth; use strict; use warnings; use Exporter 'import'; -use Digest::SHA qw|sha1_hex sha256_hex|; +use Digest::SHA qw|sha1 sha1_hex sha256|; use Time::HiRes; use Encode 'encode_utf8'; use POSIX 'strftime'; @@ -30,7 +30,7 @@ sub authInit { my $token = substr($cookie, 0, 40); my $uid = substr($cookie, 40); $self->{_auth} = $uid =~ /^\d+$/ && $self->dbUserGet(uid => $uid, session => $token, what => 'extended notifycount prefs')->[0]; - # update the sessions.lastused column if lastused < now()'6 hours' + # update the sessions.lastused column if lastused < now()-'6 hours' $self->dbSessionUpdateLastUsed($uid, $token) if $self->{_auth} && $self->{_auth}{session_lastused} < time()-6*3600; return $self->resCookie(auth => undef) if !$self->{_auth}; } @@ -103,9 +103,9 @@ sub _authCheck { return 0 if !$user || length($user) > 15 || length($user) < 2 || !$pass; my $d = $self->dbUserGet(username => $user, what => 'extended notifycount')->[0]; - return 0 if !$d->{id} || $d->{salt} =~ /^ *$/; + return 0 if !$d->{id} || length $d->{passwd} != 41; - if(_authEncryptPass($self, $pass, $d->{salt}) eq $d->{passwd}) { + if($self->authPreparePass($pass, substr $d->{passwd}, 0, 9) eq $d->{passwd}) { $self->{_auth} = $d; return 1; } @@ -114,43 +114,34 @@ sub _authCheck { } -# Encryption algorithm for user passwords -# Arguments: self, pass, salt -# Returns: encrypted password (in hex) -sub _authEncryptPass { - my($self, $pass, $salt, $bin) = @_; - return sha256_hex($self->{global_salt} . encode_utf8($pass) . encode_utf8($salt)); -} - - # Prepares a plaintext password for database storage -# Arguments: pass -# Returns: list (pass, salt) +# Arguments: pass, optionally salt +# Returns: encrypted password (as a binary string) sub authPreparePass { - my($self, $pass) = @_; - my $salt = join '', map chr(rand(93)+33), 1..9; - my $hash = _authEncryptPass($self, $pass, $salt); - return ($hash, $salt); + my($self, $pass, $salt) = @_; + $salt ||= encode_utf8(join '', map chr(rand(93)+33), 1..9); + return $salt.sha256($self->{global_salt} . encode_utf8($pass) . $salt); } # Generates a random token that can be used to reset the password. -# Returns: token, token-encrypted, salt +# Returns: token (hex string), token-encrypted (binary string) sub authPrepareReset { my $self = shift; my $token = sha1_hex(join('', Time::HiRes::gettimeofday()) . join('', map chr(rand(93)+33), 1..9)); my $salt = join '', map chr(rand(93)+33), 1..9; - my $token_e = sha1_hex(lc($token).$salt); - return ($token, $token_e, $salt); + my $token_e = encode_utf8($salt) . sha1(lc($token).$salt); + return ($token, $token_e); } # Checks whether the password reset token is valid. -# Arguments: $u obj, token +# Arguments: passwd (binary string), token (hex string) sub authValidateReset { - my($self, $u, $t) = @_; - return 0 if !$u->{salt} || !$u->{passwd} || length $u->{passwd} != 40 - || lc sha1_hex(lc($t).$u->{salt}) ne lc $u->{passwd}; + my($self, $passwd, $token) = @_; + return 0 if length $passwd != 29; + my $salt = substr $passwd, 0, 9; + return 0 if $salt.sha1(lc($token).$salt) ne $passwd; return 1; } diff --git a/util/sql/schema.sql b/util/sql/schema.sql index 046eccf6..09b334f9 100644 --- a/util/sql/schema.sql +++ b/util/sql/schema.sql @@ -363,13 +363,21 @@ CREATE TABLE users ( username varchar(20) NOT NULL UNIQUE, mail varchar(100) NOT NULL, perm smallint NOT NULL DEFAULT 1+4+16, + -- Interpretation of the passwd column depends on its length: + -- * 29 bytes: Password reset token + -- First 9 bytes: salt (ASCII) + -- Latter 20 bytes: sha1(hex(token) + salt) + -- 'token' is a sha1 digest obtained from random data. + -- * 41 bytes: Hashed/salted password + -- First 9 bytes: salt (ASCII) + -- Latter 32 bytes: sha256(global_salt + password + salt) + -- * Anything else: Invalid, account disabled. passwd bytea NOT NULL DEFAULT '', registered timestamptz NOT NULL DEFAULT NOW(), c_votes integer NOT NULL DEFAULT 0, c_changes integer NOT NULL DEFAULT 0, ip inet NOT NULL DEFAULT '0.0.0.0', c_tags integer NOT NULL DEFAULT 0, - salt character(9) NOT NULL DEFAULT '', ign_votes boolean NOT NULL DEFAULT FALSE, email_confirmed boolean NOT NULL DEFAULT FALSE ); diff --git a/util/updates/update_2.23.sql b/util/updates/update_2.23.sql index f93b1480..28117f4b 100644 --- a/util/updates/update_2.23.sql +++ b/util/updates/update_2.23.sql @@ -92,3 +92,9 @@ ALTER TABLE releases_platforms ALTER COLUMN platform TYPE platform USING platfor ALTER TABLE vn ALTER COLUMN c_platforms DROP DEFAULT; ALTER TABLE vn ALTER COLUMN c_platforms TYPE platform[] USING string_to_array(c_platforms, '/')::platform[]; ALTER TABLE vn ALTER COLUMN c_platforms SET DEFAULT '{}'; + + +-- Merging passwd and salt +--SELECT length(passwd), count(*) from users group by length(passwd); +UPDATE users SET passwd = convert_to(salt, 'UTF-8') || passwd; +ALTER TABLE users DROP COLUMN salt; |