summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorYorhel <git@yorhel.nl>2016-11-27 10:15:19 +0100
committerYorhel <git@yorhel.nl>2016-11-27 10:15:19 +0100
commit6a04b3278bb6e2bedbe169870314eff7d5de33da (patch)
treeef0b66773270f15e87ac5ee46c844af1c2f1459e /lib
parenta9df5c8d7e22874d37938b27913f239ce31f9414 (diff)
SQL: Use separate role for the website + disallow access to user data
Previously the website was connected to the database with a "database owner" user, which has far too many permissions. Now there's a special vndb_site user with only the necessary permissions. The primary reason to do this is to decrease the impact if the site process is compromised. E.g. it's now no longer possible to delete or modify old entry revisions. An attacker can still do a lot of damage, however. Additionally (and this was the main reason to implement this change in the first place), the user sessions, passwords and email data is now not easily accessible anymore. Hopefully, the new user management abstractions will prevent email and password dumps in case of an SQL injection or RCE vulnerability in the site code. Of course, this only works if my implementation is fully correct and there's no privilige escalation vulnerability somewhere. Furthermore, changing your password now invalidates any existing sessions, and the password reset function is disabled for 'usermods' (because usermods can list email addresses from the database, and the password reset function could still allow an attacker to gain access to anyone's account). I also changed the format of the password reset tokens, as they totally don't need to be salted.
Diffstat (limited to 'lib')
-rw-r--r--lib/VNDB/DB/Users.pm98
-rw-r--r--lib/VNDB/Handler/Users.pm67
-rw-r--r--lib/VNDB/Util/Auth.pm140
3 files changed, 170 insertions, 135 deletions
diff --git a/lib/VNDB/DB/Users.pm b/lib/VNDB/DB/Users.pm
index d6776b2b..84ff10f2 100644
--- a/lib/VNDB/DB/Users.pm
+++ b/lib/VNDB/DB/Users.pm
@@ -6,15 +6,16 @@ use warnings;
use Exporter 'import';
our @EXPORT = qw|
- dbUserGet dbUserEdit dbUserAdd dbUserDel dbUserPrefSet
- dbSessionAdd dbSessionDel dbSessionUpdateLastUsed
+ dbUserGet dbUserEdit dbUserAdd dbUserDel dbUserPrefSet dbUserLogin dbUserLogout
+ dbUserUpdateLastUsed dbUserEmailExists dbUserGetMail dbUserSetMail dbUserSetPerm dbUserAdminSetPass
+ dbUserResetPass dbUserIsValidToken dbUserSetPass
dbNotifyGet dbNotifyMarkRead dbNotifyRemove
dbThrottleGet dbThrottleSet
|;
-# %options->{ username passwd mail session uid ip registered search results page what sort reverse notperm }
-# what: notifycount stats extended prefs hide_list
+# %options->{ username session uid ip registered search results page what sort reverse notperm }
+# what: notifycount stats scryptargs extended prefs hide_list
# sort: username registered votes changes tags
sub dbUserGet {
my $s = shift;
@@ -26,6 +27,7 @@ sub dbUserGet {
@_
);
+ my $token = unpack 'H*', $o{session}||'';
$o{search} =~ s/%// if $o{search};
my %where = (
$o{username} ? (
@@ -34,8 +36,6 @@ sub dbUserGet {
'SUBSTRING(username from 1 for 1) = ?' => $o{firstchar} ) : (),
!$o{firstchar} && defined $o{firstchar} ? (
'ASCII(username) < 97 OR ASCII(username) > 122' => 1 ) : (),
- $o{mail} ? (
- 'LOWER(mail) = LOWER(?)' => $o{mail} ) : (),
$o{uid} && !ref($o{uid}) ? (
'id = ?' => $o{uid} ) : (),
$o{uid} && ref($o{uid}) ? (
@@ -48,8 +48,8 @@ sub dbUserGet {
'registered > to_timestamp(?)' => $o{registered} ) : (),
$o{search} ? (
'username ILIKE ?' => "%$o{search}%") : (),
- $o{session} ? (
- q|s.token = decode(?, 'hex')| => unpack 'H*', $o{session} ) : (),
+ $token ? (
+ q|user_isloggedin(id, decode(?, 'hex')) IS NOT NULL| => $token ) : (),
$o{notperm} ? (
'perm & ~(?::smallint) > 0' => $o{notperm} ) : (),
);
@@ -57,8 +57,9 @@ 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 ign_votes passwd| : (),
+ $o{what} =~ /extended/ ? qw|perm ign_votes| : (), # mail
$o{what} =~ /hide_list/ ? 'up.value AS hide_list' : (),
+ $o{what} =~ /scryptargs/ ? 'user_getscryptargs(id) AS scryptargs' : (),
$o{what} =~ /notifycount/ ?
'(SELECT COUNT(*) FROM notifications WHERE uid = u.id AND read IS NULL) AS notifycount' : (),
$o{what} =~ /stats/ ? (
@@ -69,11 +70,10 @@ sub dbUserGet {
'(SELECT COUNT(DISTINCT tag) FROM tags_vn WHERE uid = u.id) AS tagcount',
'(SELECT COUNT(DISTINCT vid) FROM tags_vn WHERE uid = u.id) AS tagvncount',
) : (),
- $o{session} ? q|extract('epoch' from s.lastused) as session_lastused| : (),
+ $token ? qq|extract('epoch' from user_isloggedin(id, decode('$token', 'hex'))) as session_lastused| : (),
);
my @join = (
- $o{session} ? 'JOIN sessions s ON s.uid = u.id' : (),
$o{what} =~ /hide_list/ || $o{sort} eq 'votes' ?
"LEFT JOIN users_prefs up ON up.uid = u.id AND up.key = 'hide_list'" : (),
);
@@ -119,10 +119,7 @@ sub dbUserEdit {
my %h;
defined $o{$_} && ($h{$_.' = ?'} = $o{$_})
- for (qw| username mail perm ign_votes email_confirmed |);
- $h{'passwd = decode(?, \'hex\')'} = unpack 'H*', $o{passwd}
- if defined $o{passwd};
-
+ for (qw| username ign_votes email_confirmed |);
return if scalar keys %h <= 0;
return $s->dbExec(q|
@@ -133,11 +130,9 @@ sub dbUserEdit {
}
-# username, pass(ecrypted), mail, [ip]
+# username, mail, [ip]
sub dbUserAdd {
- my($s, @o) = @_;
- $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};
+ $_[0]->dbRow(q|INSERT INTO users (username, mail, ip) VALUES(?, ?, ?) RETURNING id|, $_[1], $_[2], $_[3]||$_[0]->reqIP)->{id};
}
@@ -156,27 +151,64 @@ sub dbUserPrefSet {
}
-# Adds a session to the database
-# uid, 40 character session token
-sub dbSessionAdd {
- $_[0]->dbExec(q|INSERT INTO sessions (uid, token) VALUES(?, decode(?, 'hex'))|, $_[1], unpack 'H*', $_[2]);
+# uid, encpass, token
+sub dbUserLogin {
+ $_[0]->dbRow(
+ q|SELECT user_login(?, decode(?, 'hex'), decode(?, 'hex')) AS r|,
+ $_[1], unpack('H*', $_[2]), unpack('H*', $_[3])
+ )->{r}||0;
}
-# 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) = @_;
- my %where = ('uid = ?' => $o[0]);
- $where{"token = decode(?, 'hex')"} = unpack 'H*', $o[1] if $o[1];
- $s->dbExec('DELETE FROM sessions !W', \%where);
+# uid, token
+sub dbUserLogout {
+ $_[0]->dbExec(q|SELECT user_logout(?, decode(?, 'hex'))|, $_[1], unpack 'H*', $_[2]);
}
# uid, token
-sub dbSessionUpdateLastUsed {
- $_[0]->dbExec(q|UPDATE sessions SET lastused = NOW() WHERE uid = ? AND token = decode(?, 'hex')|, $_[1], unpack 'H*', $_[2]);
+sub dbUserUpdateLastUsed {
+ $_[0]->dbExec(q|SELECT user_update_lastused(?, decode(?, 'hex'))|, $_[1], unpack 'H*', $_[2]);
+}
+
+
+sub dbUserEmailExists {
+ $_[0]->dbRow(q|SELECT user_emailexists(?) AS r|, $_[1])->{r};
+}
+
+
+sub dbUserIsValidToken {
+ $_[0]->dbRow(q|SELECT user_isvalidtoken(?, decode(?, 'hex')) AS r|, $_[1], unpack 'H*', $_[2])->{r};
+}
+
+
+sub dbUserResetPass {
+ $_[0]->dbRow(q|SELECT user_resetpass(?, decode(?, 'hex')) AS r|, $_[1], unpack 'H*', $_[2])->{r};
+}
+
+
+sub dbUserSetPass {
+ $_[0]->dbRow(q|SELECT user_setpass(?, decode(?, 'hex'), decode(?, 'hex')) AS r|, $_[1], unpack('H*', $_[2]), unpack('H*', $_[3]))->{r};
+}
+
+
+sub dbUserGetMail {
+ $_[0]->dbRow(q|SELECT user_getmail(?, ?, decode(?, 'hex')) AS r|, $_[1], $_[2], unpack 'H*', $_[3])->{r};
+}
+
+
+sub dbUserSetMail {
+ $_[0]->dbExec(q|SELECT user_setmail(?, ?, decode(?, 'hex'), ?)|, $_[1], $_[2], unpack('H*', $_[3]), $_[4]);
+}
+
+
+sub dbUserSetPerm {
+ $_[0]->dbExec(q|SELECT user_setperm(?, ?, decode(?, 'hex'), ?)|, $_[1], $_[2], unpack('H*', $_[3]), $_[4]);
+}
+
+
+sub dbUserAdminSetPass {
+ $_[0]->dbExec(q|SELECT user_admin_setpass(?, ?, decode(?, 'hex'), decode(?, 'hex'))|, $_[1], $_[2], unpack('H*', $_[3]), unpack('H*', $_[4]));
}
diff --git a/lib/VNDB/Handler/Users.pm b/lib/VNDB/Handler/Users.pm
index 5b6888cb..d1f0df93 100644
--- a/lib/VNDB/Handler/Users.pm
+++ b/lib/VNDB/Handler/Users.pm
@@ -145,7 +145,7 @@ sub userpage {
sub login {
my $self = shift;
- return $self->resRedirect('/') if $self->authInfo->{id};
+ return $self->resRedirect('/', 'temp') if $self->authInfo->{id};
my $tm = $self->dbThrottleGet(norm_ip($self->reqIP));
if($tm-time() > $self->{login_throttle}[1]) {
@@ -209,21 +209,18 @@ sub logout {
sub newpass {
my $self = shift;
- return $self->resRedirect('/') if $self->authInfo->{id};
+ return $self->resRedirect('/', 'temp') if $self->authInfo->{id};
- my($frm, $u);
+ my($frm, $uid, $token);
if($self->reqMethod eq 'POST') {
return if !$self->authCheckCode;
$frm = $self->formValidate({ post => 'mail', template => 'email' });
if(!$frm->{_err}) {
- $u = $self->dbUserGet(mail => $frm->{mail})->[0];
- $frm->{_err} = [ 'No user found with that email address' ] if !$u || !$u->{id};
+ ($uid, $token) = $self->authResetPass($frm->{mail});
+ $frm->{_err} = [ 'No user found with that email address' ] if !$uid;
}
if(!$frm->{_err}) {
- my %o;
- my $token;
- ($token, $o{passwd}) = $self->authPrepareReset();
- $self->dbUserEdit($u->{id}, %o);
+ my $u = $self->dbUserGet(uid => $uid)->[0];
my $body = sprintf
"Hello %s,\n\nYour VNDB.org login has been disabled, you can now set a new password by following the link below:\n\n"
."%s\n\nNow don't forget your password again! :-)\n\nvndb.org",
@@ -253,7 +250,7 @@ sub newpass {
sub newpass_sent {
my $self = shift;
- return $self->resRedirect('/') if $self->authInfo->{id};
+ return $self->resRedirect('/', 'temp') if $self->authInfo->{id};
$self->htmlHeader(title => 'New password', noindex => 1);
div class => 'mainbox';
h1 'New password';
@@ -267,14 +264,14 @@ sub newpass_sent {
sub setpass {
my($self, $uid) = @_;
- return $self->resRedirect('/') if $self->authInfo->{id};
+ return $self->resRedirect('/', 'temp') if $self->authInfo->{id};
my $t = $self->formValidate({get => 't', regex => qr/^[a-f0-9]{40}$/i });
return $self->resNotFound if $t->{_err};
$t = $t->{t};
- my $u = $self->dbUserGet(uid => $uid, what => 'extended')->[0];
- return $self->resNotFound if !$u || !$self->authValidateReset($u->{passwd}, $t);
+ my $u = $self->dbUserGet(uid => $uid)->[0];
+ return $self->resNotFound if !$u || !$self->authIsValidToken($u->{id}, $t);
my $frm;
if($self->reqMethod eq 'POST') {
@@ -286,10 +283,8 @@ sub setpass {
push @{$frm->{_err}}, 'Passwords do not match' if $frm->{usrpass} ne $frm->{usrpass2};
if(!$frm->{_err}) {
- my %o = (email_confirmed => 1);
- $o{passwd} = $self->authPreparePass($frm->{usrpass});
- $self->dbUserEdit($uid, %o);
- return $self->authCreateSession($u->{username}, "/u$uid");
+ $self->dbUserEdit($uid, email_confirmed => 1);
+ return $self->authSetPass($uid, $frm->{usrpass}, "/u$uid", token => $t)
}
}
@@ -306,7 +301,7 @@ sub setpass {
sub register {
my $self = shift;
- return $self->resRedirect('/') if $self->authInfo->{id};
+ return $self->resRedirect('/', 'temp') if $self->authInfo->{id};
my $frm;
if($self->reqMethod eq 'POST') {
@@ -323,7 +318,7 @@ sub register {
push @{$frm->{_err}}, 'Someone already has this username, please choose another name'
if $frm->{usrname} eq 'anonymous' || !$frm->{_err} && $self->dbUserGet(username => $frm->{usrname})->[0]{id};
push @{$frm->{_err}}, 'Someone already registered with that email address'
- if !$frm->{_err} && $self->dbUserGet(mail => $frm->{mail})->[0]{id};
+ if !$frm->{_err} && $self->dbUserEmailExists($frm->{mail});
# Use /32 match for IPv4 and /48 for IPv6. The /48 is fairly broad, so some
# users may have to wait a bit before they can register...
@@ -332,8 +327,8 @@ sub register {
if !$frm->{_err} && $self->dbUserGet(ip => $ip =~ /:/ ? "$ip/48" : $ip, registered => time-24*3600)->[0]{id};
if(!$frm->{_err}) {
- my($token, $pass) = $self->authPrepareReset();
- my $uid = $self->dbUserAdd($frm->{usrname}, $pass, $frm->{mail});
+ my $uid = $self->dbUserAdd($frm->{usrname}, $frm->{mail});
+ my(undef, $token) = $self->authResetPass($frm->{mail});
my $body = sprintf "Hello %s,\n\n"
."Someone has registered an account on VNDB.org with your email address. To confirm your registration, follow the link below.\n\n"
."%s\n\n"
@@ -369,7 +364,7 @@ sub register {
sub register_done {
my $self = shift;
- return $self->resRedirect('/') if $self->authInfo->{id};
+ return $self->resRedirect('/', 'temp') if $self->authInfo->{id};
$self->htmlHeader(title => 'Account created', noindex => 1);
div class => 'mainbox';
h1 'Account created';
@@ -416,9 +411,6 @@ sub edit {
);
push @{$frm->{_err}}, 'Passwords do not match'
if ($frm->{usrpass} || $frm->{usrpass2}) && (!$frm->{usrpass} || !$frm->{usrpass2} || $frm->{usrpass} ne $frm->{usrpass2});
- push @{$frm->{_err}}, 'Invalid password'
- if !($self->authInfo->{id} != $u->{id} && $self->authCan('usermod'))
- && ($frm->{usrpass} || $frm->{usrpass2}) && !$self->authCheck($u->{username}, $frm->{curpass});
if(!$frm->{_err}) {
$frm->{skin} = '' if $frm->{skin} eq $self->{skin_default};
@@ -426,23 +418,34 @@ sub edit {
my $tags_cat = join(',', sort @{$frm->{tags_cat}}) || 'none';
$self->dbUserPrefSet($uid, tags_cat => $tags_cat eq $self->{default_tags_cat} ? '' : $tags_cat);
+
my %o;
if($self->authCan('usermod')) {
$o{username} = $frm->{usrname} if $frm->{usrname};
- $o{perm} = 0;
- $o{perm} |= $self->{permissions}{$_} for(@{ delete $frm->{perms} });
+ $o{ign_votes} = $frm->{ign_votes} ? 1 : 0;
+
+ my $perm = 0;
+ $perm |= $self->{permissions}{$_} for(@{ delete $frm->{perms} });
+ $self->dbUserSetPerm($u->{id}, $self->authInfo->{id}, $self->authInfo->{token}, $perm);
}
- $o{mail} = $frm->{mail};
- $o{passwd} = $self->authPreparePass($frm->{usrpass}) if $frm->{usrpass};
- $o{ign_votes} = $frm->{ign_votes} ? 1 : 0 if $self->authCan('usermod');
+ $self->dbUserSetMail($u->{id}, $self->authInfo->{id}, $self->authInfo->{token}, $frm->{mail});
$self->dbUserEdit($uid, %o);
- return $self->resRedirect("/u$uid/edit?d=1", 'post');
+ $self->authAdminSetPass($u->{id}, $frm->{usrpass}) if $frm->{usrpass} && $self->authInfo->{id} != $u->{id};
+
+ if($frm->{usrpass} && $self->authInfo->{id} == $u->{id}) {
+ # Bit ugly: On incorrect password, all other changes are still saved.
+ my $ok = $self->authSetPass($u->{id}, $frm->{usrpass}, "/u$uid/edit?d=1", pass => $frm->{curpass});
+ return if $ok;
+ push @{$frm->{_err}}, 'Invalid password';
+ } else {
+ return $self->resRedirect("/u$uid/edit?d=1", 'post');
+ }
}
}
# fill out default values
$frm->{usrname} ||= $u->{username};
- $frm->{mail} ||= $u->{mail};
+ $frm->{mail} ||= $self->dbUserGetMail($u->{id}, $self->authInfo->{id}, $self->authInfo->{token});
$frm->{perms} ||= [ grep $u->{perm} & $self->{permissions}{$_}, keys %{$self->{permissions}} ];
$frm->{$_} //= $u->{prefs}{$_} for(qw|skin customcss show_nsfw traits_sexual tags_all hide_list spoilers|);
$frm->{tags_cat} ||= [ split /,/, $u->{prefs}{tags_cat}||$self->{default_tags_cat} ];
diff --git a/lib/VNDB/Util/Auth.pm b/lib/VNDB/Util/Auth.pm
index 6e1dfa5d..e3ee20eb 100644
--- a/lib/VNDB/Util/Auth.pm
+++ b/lib/VNDB/Util/Auth.pm
@@ -14,8 +14,8 @@ use VNDB::Func;
our @EXPORT = qw|
- authInit authLogin authLogout authInfo authCan authPreparePass authCreateSession authCheck
- authPrepareReset authValidateReset authGetCode authCheckCode authPref
+ authInit authLogin authLogout authInfo authCan authSetPass authAdminSetPass
+ authResetPass authIsValidToken authGetCode authCheckCode authPref
|;
@@ -28,7 +28,7 @@ sub randomascii {
# Returns (uid, encrypted_token) on success, (0, '') on failure.
sub parsecookie {
# Earlier versions of the auth cookie didn't have the dot separator, so that's optional.
- return ($_[0]->reqCookie('auth')||'') =~ /^([a-zA-Z0-9]{40})\.?(\d+)$/ ? ($2, sha1 pack 'H*', $1) : (0, '');
+ return ($_[0]->reqCookie('auth')||'') =~ /^([a-fA-F0-9]{40})\.?(\d+)$/ ? ($2, sha1 pack 'H*', $1) : (0, '');
}
@@ -38,9 +38,10 @@ sub authInit {
my($uid, $token_e) = parsecookie($self);
$self->{_auth} = $uid && $self->dbUserGet(uid => $uid, session => $token_e, what => 'extended notifycount prefs')->[0];
+ $self->{_auth}{token} = $token_e if $self->{_auth};
# update the sessions.lastused column if lastused < now()-'6 hours'
- $self->dbSessionUpdateLastUsed($uid, $token_e) if $self->{_auth} && $self->{_auth}{session_lastused} < time()-6*3600;
+ $self->dbUserUpdateLastUsed($uid, $token_e) if $self->{_auth} && $self->{_auth}{session_lastused} < time()-6*3600;
# Drop the cookie if it's not valid
$self->resCookie(auth => undef) if !$self->{_auth} && $self->reqCookie('auth');
@@ -52,29 +53,39 @@ sub authInit {
sub authLogin {
my($self, $user, $pass, $to) = @_;
- if($self->authCheck($user, $pass)) {
- $self->authCreateSession($user, $to);
- return 1;
- }
+ return 0 if !$user || !$pass;
- return 0;
+ my $d = $self->dbUserGet(username => $user, what => 'scryptargs extended prefs notifycount')->[0];
+ return 0 if !$d->{id} || !$d->{scryptargs} || length($d->{scryptargs}) != 14;
+
+ my($N, $r, $p, $salt) = unpack 'NCCa8', $d->{scryptargs};
+ my $encpass = _preparepass($self, $pass, $salt, $N, $r, $p);
+
+ return _createsession($self, $d->{id}, $encpass, $to);
}
-# Args: user, url-to-redirect-to-on-success
-# Should only be called if the user is already authenticated (i.e. after authCheck or when the user just confirmed his email address).
-sub authCreateSession {
- my($self, $user, $to) = @_;
+# Prepares a plaintext password for database storage
+# Arguments: pass, optionally: salt, N, r, p
+# Returns: encrypted password (as a binary string)
+sub _preparepass {
+ my($self, $pass, $salt, $N, $r, $p) = @_;
+ ($N, $r, $p) = @{$self->{scrypt_args}} if !$N;
+ $salt ||= urandom(8);
+ return pack 'NCCa8a*', $N, $r, $p, $salt, scrypt_raw($pass, $self->{scrypt_salt} . $salt, $N, $r, $p, 32);
+}
+
- $self->{_auth} = $self->dbUserGet(username => $user, what => 'extended notifycount')->[0] if $user;
- die "No valid user!" if !$self->{_auth}{id};
+# self, uid, encpass, url-to-redirect-to
+sub _createsession {
+ my($self, $uid, $encpass, $url) = @_;
my $token = urandom(20);
- my $cookie = unpack('H*', $token).'.'.$self->{_auth}{id};
- $self->dbSessionAdd($self->{_auth}{id}, sha1 $token);
+ return 0 if !$self->dbUserLogin($uid, $encpass, sha1 $token);
- $self->resRedirect($to, 'post');
- $self->resCookie(auth => $cookie, httponly => 1, expires => time + 31536000); # keep the cookie for 1 year
+ $self->resRedirect($url, 'post');
+ $self->resCookie(auth => unpack('H*', $token).'.'.$uid, httponly => 1, expires => time + 31536000); # keep the cookie for 1 year
+ return 1;
}
@@ -83,82 +94,71 @@ sub authLogout {
my $self = shift;
my($uid, $token_e) = parsecookie($self);
- $self->dbSessionDel($uid, $token_e) if $uid;
+ $self->dbUserLogout($uid, $token_e) if $uid;
$self->resRedirect('/', 'temp');
$self->resCookie(auth => undef);
}
-# returns a hashref with information about the current loggedin user
-# the hash is identical to the hash returned by dbUserGet
-# returns empty hash if no user is logged in.
-sub authInfo {
- return shift->{_auth} || {};
+# Replaces the user's password with a random token that can be used to reset the password.
+sub authResetPass {
+ my $self = shift;
+ my $mail = shift;
+ my $token = unpack 'H*', urandom(20);
+ my $id = $self->dbUserResetPass($mail, sha1(lc($token)));
+ return $id ? ($id, $token) : ();
}
-# returns whether the currently loggedin or anonymous user can perform
-# a certain action. Argument is the action name as defined in global.pl
-sub authCan {
- my($self, $act) = @_;
- return $self->{_auth} ? $self->{_auth}{perm} & $self->{permissions}{$act} : 0;
+# uid, token
+sub authIsValidToken {
+ $_[0]->dbUserIsValidToken($_[1], sha1(lc($_[2])))
}
-# Checks for a valid login and writes information in _auth
-# 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;
+# uid, new_pass, url_to_redir_to, 'token'|'pass', $token_or_pass
+# Changes the user's password, invalidates all existing sessions, creates a new
+# session and redirects.
+sub authSetPass {
+ my($self, $uid, $pass, $redir, $oldtype, $oldpass) = @_;
- my $d = $self->dbUserGet(username => $user, what => 'extended notifycount')->[0];
- return 0 if !$d->{id};
+ if($oldtype eq 'token') {
+ $oldpass = sha1(lc($oldpass));
- # scrypt format
- if(length $d->{passwd} == 46) {
- my($N, $r, $p, $salt) = unpack 'NCCa8', $d->{passwd};
- return 0 if $self->authPreparePass($pass, $salt, $N, $r, $p) ne $d->{passwd};
- $self->{_auth} = $d;
- return 1;
+ } elsif($oldtype eq 'pass') {
+ my $u = $self->dbUserGet(uid => $uid, what => 'scryptargs')->[0];
+ return 0 if !$u->{id} || !$u->{scryptargs} || length($u->{scryptargs}) != 14;
+ my($N, $r, $p, $salt) = unpack 'NCCa8', $u->{scryptargs};
+ $oldpass = _preparepass($self, $oldpass, $salt, $N, $r, $p);
}
- return 0;
+ $pass = _preparepass($self, $pass);
+ return 0 if !$self->dbUserSetPass($uid, $oldpass, $pass);
+ return _createsession($self, $uid, $pass, $redir);
}
-# Prepares a plaintext password for database storage
-# Arguments: pass, optionally: salt, N, r, p
-# Returns: encrypted password (as a binary string)
-sub authPreparePass {
- my($self, $pass, $salt, $N, $r, $p) = @_;
- ($N, $r, $p) = @{$self->{scrypt_args}} if !$N;
- $salt ||= urandom(8);
- return pack 'NCCa8a*', $N, $r, $p, $salt, scrypt_raw($pass, $self->{scrypt_salt} . $salt, $N, $r, $p, 32);
+sub authAdminSetPass {
+ my($self, $uid, $pass) = @_;
+ $pass = _preparepass($self, $pass);
+ $self->dbUserAdminSetPass($uid, $self->authInfo->{id}, $self->authInfo->{token}, $pass);
}
-# Generates a random token that can be used to reset the password.
-# Returns: token (hex string), token-encrypted (binary string)
-sub authPrepareReset {
- my $self = shift;
- my $token = unpack 'H*', urandom(20);
- my $salt = randomascii(9);
- my $token_e = encode_utf8($salt) . sha1(lc($token).$salt);
- return ($token, $token_e);
+# returns a hashref with information about the current loggedin user
+# the hash is identical to the hash returned by dbUserGet
+# returns empty hash if no user is logged in.
+sub authInfo {
+ return shift->{_auth} || {};
}
-# Checks whether the password reset token is valid.
-# Arguments: passwd (binary string), token (hex string)
-sub authValidateReset {
- 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;
+# returns whether the currently loggedin or anonymous user can perform
+# a certain action. Argument is the action name as defined in global.pl
+sub authCan {
+ my($self, $act) = @_;
+ return $self->{_auth} ? $self->{_auth}{perm} & $self->{permissions}{$act} : 0;
}