summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog1
-rw-r--r--data/global.pl2
-rw-r--r--lib/VNDB/DB/Users.pm50
-rw-r--r--lib/VNDB/Handler/Users.pm11
-rw-r--r--lib/VNDB/Util/Auth.pm86
-rw-r--r--util/dump.sql12
-rw-r--r--util/updates/update_2.6.sql13
7 files changed, 141 insertions, 34 deletions
diff --git a/ChangeLog b/ChangeLog
index c5e713f2..6e6dcd38 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,6 @@
2.6 - ?
- New screen resolutions: 1024x600 and 1600x1200
+ - Rewritten authentication system
2.5 - 2009-07-09
- Hide NSFW images in diff viewer (unless NSFW warnings are disabled)
diff --git a/data/global.pl b/data/global.pl
index 83cb42fd..09382411 100644
--- a/data/global.pl
+++ b/data/global.pl
@@ -20,7 +20,7 @@ our %S = (%S,
site_title => 'Yet another VNDB clone',
skin_default => 'angel',
cookie_domain => '.vndb.org',
- cookie_key => 'any-private-string-here',
+ global_salt => 'any-private-string-here',
source_url => 'http://git.blicky.net/vndb.git/?h=master',
admin_email => 'contact@vndb.org',
sharedmem_key => 'VNDB',
diff --git a/lib/VNDB/DB/Users.pm b/lib/VNDB/DB/Users.pm
index d5cdcc6c..eaed1234 100644
--- a/lib/VNDB/DB/Users.pm
+++ b/lib/VNDB/DB/Users.pm
@@ -5,7 +5,7 @@ use strict;
use warnings;
use Exporter 'import';
-our @EXPORT = qw|dbUserGet dbUserEdit dbUserAdd dbUserDel|;
+our @EXPORT = qw|dbUserGet dbUserEdit dbUserAdd dbUserDel dbSessionAdd dbSessionDel dbSessionCheck|;
# %options->{ username passwd mail order uid ip registered search results page what }
@@ -75,7 +75,7 @@ sub dbUserEdit {
my %h;
defined $o{$_} && ($h{$_.' = ?'} = $o{$_})
- for (qw| username mail rank show_nsfw show_list skin customcss |);
+ for (qw| username mail rank show_nsfw show_list skin customcss salt |);
$h{'passwd = decode(?, \'hex\')'} = $o{passwd}
if defined $o{passwd};
@@ -88,11 +88,11 @@ sub dbUserEdit {
}
-# username, md5(pass), mail, [ip]
+# username, pass(ecrypted), salt, mail, [ip]
sub dbUserAdd {
my($s, @o) = @_;
- $s->dbExec(q|INSERT INTO users (username, passwd, mail, ip, registered) VALUES(?, decode(?, 'hex'), ?, ?, ?)|,
- @o[0..2], $o[3]||$s->reqIP, time);
+ $s->dbExec(q|INSERT INTO users (username, passwd, salt, mail, ip, registered) VALUES(?, decode(?, 'hex'), ?, ?, ?, ?)|,
+ @o[0..3], $o[4]||$s->reqIP, time);
}
@@ -104,6 +104,7 @@ sub dbUserDel {
q|DELETE FROM rlists WHERE uid = ?|,
q|DELETE FROM wlists WHERE uid = ?|,
q|DELETE FROM votes WHERE uid = ?|,
+ q|DELETE FROM sessions WHERE uid = ?|,
q|UPDATE changes SET requester = 0 WHERE requester = ?|,
q|UPDATE threads_posts SET uid = 0 WHERE uid = ?|,
q|DELETE FROM users WHERE id = ?|
@@ -111,4 +112,43 @@ sub dbUserDel {
}
+# Adds a session to the database
+# If no expiration is supplied the database default is used
+# uid, 40 character session token, expiration time (timestamp)
+sub dbSessionAdd {
+ my($s, @o) = @_;
+ if (defined $o[2]) {
+ $s->dbExec(q|INSERT INTO sessions (uid, token, expiration) VALUES(?, ?, ?)|,
+ @o);
+ } else {
+ $s->dbExec(q|INSERT INTO sessions (uid, token) VALUES(?, ?)|,
+ @o);
+ }
+}
+
+
+# Deletes session(s) from the database
+# If no token is supplied, all sessions for the uid are destroyed
+# uid, token (optional)
+sub dbSessionDel {
+ my($s, @o) = @_;
+ if (defined $o[1]) {
+ $s->dbExec(q|DELETE FROM sessions WHERE uid = ? AND token = ?|,
+ @o[0..1]);
+ } else {
+ $s->dbExec(q|DELETE FROM sessions WHERE uid = ?|,
+ $o[0]);
+ }
+}
+
+
+# Queries the database for the validity of a session
+# Returns 1 if corresponding session found, 0 if not
+# uid, token
+sub dbSessionCheck {
+ my($s, @o) = @_;
+ return $s->dbRow(q|SELECT count(uid) AS count FROM sessions WHERE uid = ? AND token = ? LIMIT 1|, @o)->{count}||0;
+}
+
+
1;
diff --git a/lib/VNDB/Handler/Users.pm b/lib/VNDB/Handler/Users.pm
index d4755ae1..982d6089 100644
--- a/lib/VNDB/Handler/Users.pm
+++ b/lib/VNDB/Handler/Users.pm
@@ -4,7 +4,6 @@ package VNDB::Handler::Users;
use strict;
use warnings;
use YAWF ':html';
-use Digest::MD5 'md5_hex';
use VNDB::Func;
@@ -183,7 +182,9 @@ sub newpass {
if(!$frm->{_err}) {
my @chars = ( 'A'..'Z', 'a'..'z', 0..9 );
my $pass = join '', map $chars[int rand $#chars+1], 0..8;
- $self->dbUserEdit($u->{id}, passwd => md5_hex($pass));
+ my %o;
+ ($o{passwd}, $o{salt}) = $self->authPreparePass($pass);
+ $self->dbUserEdit($u->{id}, %o);
my $body = <<'__';
Hello %s,
@@ -258,7 +259,8 @@ sub register {
push @{$frm->{_err}}, 'oneaday' if !$frm->{_err} && $self->dbUserGet(ip => $self->reqIP, registered => time-24*3600)->[0]{id};
if(!$frm->{_err}) {
- $self->dbUserAdd($frm->{usrname}, md5_hex($frm->{usrpass}), $frm->{mail});
+ my ($pass, $salt) = $self->authPreparePass($frm->{usrpass});
+ $self->dbUserAdd($frm->{usrname}, $pass, $salt, $frm->{mail});
return $self->authLogin($frm->{usrname}, $frm->{usrpass}, '/');
}
}
@@ -330,10 +332,11 @@ sub edit {
$o{mail} = $frm->{mail};
$o{skin} = $frm->{skin};
$o{customcss} = $frm->{customcss};
- $o{passwd} = md5_hex($frm->{usrpass}) if $frm->{usrpass};
+ ($o{passwd}, $o{salt}) = $self->authPreparePass($frm->{usrpass}) if $frm->{usrpass};
$o{show_list} = $frm->{flags_list} ? 1 : 0;
$o{show_nsfw} = $frm->{flags_nsfw} ? 1 : 0;
$self->dbUserEdit($uid, %o);
+ $self->dbSessionDel($uid) if $frm->{usrpass};
return $self->resRedirect("/u$uid/edit?d=1", 'post') if $uid != $self->authInfo->{id} || !$frm->{usrpass};
return $self->authLogin($frm->{usrname}||$u->{username}, $frm->{usrpass}, "/u$uid/edit?d=1");
}
diff --git a/lib/VNDB/Util/Auth.pm b/lib/VNDB/Util/Auth.pm
index cb0751e9..00700e6e 100644
--- a/lib/VNDB/Util/Auth.pm
+++ b/lib/VNDB/Util/Auth.pm
@@ -1,19 +1,17 @@
package VNDB::Util::Auth;
-# This module is just a small improvement of the 1.x equivalent
-# and is designed to work with the cookies and database of VNDB 1.x
-# without modifications. A proper and more secure (incompatible)
-# implementation should be written at some point.
use strict;
use warnings;
use Exporter 'import';
-use Digest::MD5 'md5_hex';
-use Crypt::Lite;
+use Digest::MD5 'md5';
+use Digest::SHA qw|sha1_hex sha256 sha256_hex|;
+use Time::HiRes;
+use POSIX 'strftime';
-our @EXPORT = qw| authInit authLogin authLogout authInfo authCan |;
+our @EXPORT = qw| authInit authLogin authLogout authInfo authCan authPreparePass |;
# initializes authentication information and checks the vndb_auth cookie
@@ -22,12 +20,10 @@ sub authInit {
$self->{_auth} = undef;
my $cookie = $self->reqCookie('vndb_auth');
- return 0 if !$cookie;
- my $str = Crypt::Lite->new()->decrypt($cookie, md5_hex($self->{cookie_key}));
- return 0 if length($str) < 36;
- my $pass = substr($str, 4, 32);
- my $user = substr($str, 36);
- _authCheck($self, $user, $pass);
+ return 0 if !$cookie || length($cookie) < 41;
+ my $token = substr($cookie, 0, 40);
+ my $uid = substr($cookie, 40);
+ $self->{_auth} = $self->dbUserGet(uid => $uid, what => 'mymessages')->[0] if $self->dbSessionCheck($uid, $token);
}
@@ -36,15 +32,21 @@ sub authInit {
sub authLogin {
my $self = shift;
my $user = lc(scalar shift);
- my $pass = md5_hex(shift);
+ my $pass = shift;
my $to = shift;
if(_authCheck($self, $user, $pass)) {
- (my $cookie = Crypt::Lite->new()->encrypt("VNDB$pass$user", md5_hex($self->{cookie_key}))) =~ s/\r?\n//g;
+ my $token = sha1_hex(join('', Time::HiRes::gettimeofday()) . join('', map chr(rand(93)+33), 1..9));
+ my $expiration = time + 31536000; # 1yr
+ my $cookie = $token . $self->{_auth}{id};
+ $self->dbSessionAdd($self->{_auth}{id}, $token);
+
+ my $expstr = strftime("%a, %d %b %Y %H:%M:%S GMT", gmtime($expiration));
$self->resRedirect($to, 'post');
- $self->resHeader('Set-Cookie', "vndb_auth=$cookie; expires=Sat, 01-Jan-2030 00:00:00 GMT; path=/; domain=$self->{cookie_domain}");
+ $self->resHeader('Set-Cookie', "vndb_auth=$cookie; expires=$expstr; path=/; domain=$self->{cookie_domain}");
return 1;
}
+
return 0;
}
@@ -52,6 +54,14 @@ sub authLogin {
# clears authentication cookie and redirects to /
sub authLogout {
my $self = shift;
+
+ my $cookie = $self->reqCookie('vndb_auth');
+ if ($cookie && length($cookie) >= 41) {
+ my $token = substr($cookie, 0, 40);
+ my $uid = substr($cookie, 40);
+ $self->dbSessionDel($uid, $token);
+ }
+
$self->resRedirect('/', 'temp');
$self->resHeader('Set-Cookie', "vndb_auth= ; expires=Sat, 01-Jan-2000 00:00:00 GMT; path=/; domain=$self->{cookie_domain}");
}
@@ -75,20 +85,50 @@ sub authCan {
# Checks for a valid login and writes information in _auth
-# Arguments: user, md5_hex(pass)
+# Arguments: user, pass
# Returns: 1 if login is valid, 0 otherwise
sub _authCheck {
my($self, $user, $pass) = @_;
- return 0 if
- !$user || length($user) > 15 || length($user) < 2
- || !$pass || length($pass) != 32;
+ return 0 if !$user || length($user) > 15 || length($user) < 2 || !$pass;
- my $d = $self->dbUserGet(username => $user, passwd => $pass, what => 'mymessages')->[0];
+ my $d = $self->dbUserGet(username => $user, what => 'mymessages')->[0];
return 0 if !defined $d->{id} || !$d->{rank};
- $self->{_auth} = $d;
- return 1;
+ if (_authEncryptPass($self, $pass, $d->{salt}, 1) eq $d->{passwd}) {
+ $self->{_auth} = $d;
+ return 1;
+ }
+ if (md5($pass) eq $d->{passwd}) {
+ $self->{_auth} = $d;
+ my %o;
+ ($o{passwd}, $o{salt}) = authPreparePass($self, $pass);
+ $self->dbUserEdit($d->{id}, %o);
+ return 1;
+ }
+
+ return 0;
+}
+
+
+# Encryption algorithm for user passwords
+# Arguments: self, pass, salt, binary mode
+# Returns: encrypted password
+sub _authEncryptPass{
+ my ($self, $pass, $salt, $bin) = @_;
+ return sha256($self->{global_salt} . $pass . $salt) if $bin;
+ return sha256_hex($self->{global_salt} . $pass . $salt);
+}
+
+
+# Prepares a plaintext password for database storage
+# Arguments: pass
+# Returns: list (pass, salt)
+sub authPreparePass{
+ my($self, $pass) = @_;
+ my $salt = join '', map chr(rand(93)+33), 1..9;
+ my $hash = _authEncryptPass($self, $pass, $salt);
+ return ($hash, $salt);
}
diff --git a/util/dump.sql b/util/dump.sql
index 84cbecbc..f7f9bb9e 100644
--- a/util/dump.sql
+++ b/util/dump.sql
@@ -161,6 +161,14 @@ CREATE TABLE screenshots (
height smallint NOT NULL DEFAULT 0
);
+-- sessions
+CREATE TABLE sessions (
+ uid integer NOT NULL,
+ token character(40) NOT NULL,
+ expiration timestamp NOT NULL DEFAULT (now() + '1 year'::interval),
+ PRIMARY KEY (uid, token)
+);
+
-- stats_cache
CREATE TABLE stats_cache (
section varchar(25) NOT NULL PRIMARY KEY,
@@ -255,7 +263,8 @@ CREATE TABLE users (
skin varchar(128) NOT NULL DEFAULT '',
customcss text NOT NULL DEFAULT '',
ip inet NOT NULL DEFAULT '0.0.0.0',
- c_tags integer NOT NULL DEFAULT 0
+ c_tags integer NOT NULL DEFAULT 0,
+ salt character(9) NOT NULL DEFAULT ''
);
-- vn
@@ -365,6 +374,7 @@ ALTER TABLE releases_vn ADD FOREIGN KEY (rid) REFERENCES releases_r
ALTER TABLE releases_vn ADD FOREIGN KEY (vid) REFERENCES vn (id);
ALTER TABLE rlists ADD FOREIGN KEY (uid) REFERENCES users (id);
ALTER TABLE rlists ADD FOREIGN KEY (rid) REFERENCES releases (id);
+ALTER TABLE sessions ADD FOREIGN KEY (uid) REFERENCES users (id);
ALTER TABLE tags ADD FOREIGN KEY (addedby) REFERENCES users (id);
ALTER TABLE tags_aliases ADD FOREIGN KEY (tag) REFERENCES tags (id);
ALTER TABLE tags_parents ADD FOREIGN KEY (tag) REFERENCES tags (id);
diff --git a/util/updates/update_2.6.sql b/util/updates/update_2.6.sql
index 567af268..67bbe584 100644
--- a/util/updates/update_2.6.sql
+++ b/util/updates/update_2.6.sql
@@ -1,5 +1,18 @@
+-- Create table for session data storage
+CREATE TABLE sessions (
+ uid integer NOT NULL REFERENCES users(id);
+ token character(40) NOT NULL,
+ expiration NOT NULL DEFAULT (NOW() + '1 year'::interval),
+ PRIMARY KEY (uid, token)
+);
+
+-- Add column to users for salt storage
+ALTER TABLE users ADD COLUMN salt character(9) NOT NULL DEFAULT ''::bpchar;
+
+
+
-- The anime table:
-- - use timestamp data type for anime.lastfetch
-- - allow NULL for all columns except id