emailauth link sent and verified; user login works

Still some work to do since the user name is an email address and should
not be leaked.
master
Joey Hess 2015-05-13 22:27:03 -04:00
parent f1d77f8193
commit 95e1e51caa
5 changed files with 124 additions and 12 deletions

View File

@ -8,6 +8,7 @@ use IkiWiki 3.00;
sub import {
hook(type => "getsetup", id => "emailauth", "call" => \&getsetup);
hook(type => "cgi", id => "cgi", "call" => \&cgi);
IkiWiki::loadplugin("loginselector");
IkiWiki::Plugin::loginselector::register_login_plugin(
"emailauth",
@ -41,17 +42,119 @@ sub email_check_input ($) {
&& length $cgi->param('Email_entry');
}
sub email_auth ($$$) {
# Send login link to email.
sub email_auth ($$$$) {
my $cgi=shift;
my $session=shift;
my $errordisplayer=shift;
unless ($cgi->param('Email_entry') =~ /.\@./) {
$errordisplayer->("Invalid email address.");
my $infodisplayer=shift;
my $email=$cgi->param('Email_entry');
unless ($email =~ /.\@./) {
$errordisplayer->(gettext("Invalid email address."));
return;
}
error "EMAIL AUTH";
# Implicit account creation.
my $userinfo=IkiWiki::userinfo_retrieve();
if (! exists $userinfo->{$email} || ! ref $userinfo->{$email}) {
IkiWiki::userinfo_setall($email, {
'email' => $email,
'regdate' => time,
});
}
my $token=gentoken($email);
my $template=template("emailauth.tmpl");
$template->param(
wikiname => $config{wikiname},
# Intentionally using short field names to keep link short.
authurl => IkiWiki::cgiurl_abs(
'e' => $email,
'v' => $token,
),
);
eval q{use Mail::Sendmail};
error($@) if $@;
sendmail(
To => $email,
From => "$config{wikiname} admin <".
(defined $config{adminemail} ? $config{adminemail} : "")
.">",
Subject => "$config{wikiname} login",
Message => $template->output,
) or error(gettext("Failed to send mail"));
$infodisplayer->(gettext("You have been sent an email, with a link you can open to complete the login process."));
}
# Finish login process.
sub cgi ($$) {
my $cgi=shift;
my $email=$cgi->param('e');
my $v=$cgi->param('v');
if (defined $email && defined $v && length $email && length $v) {
# Need to lock the wiki before getting a session.
IkiWiki::lockwiki();
IkiWiki::loadindex();
my $session=IkiWiki::cgi_getsession();
my $token=gettoken($email);
if ($token eq $v) {
print STDERR "SUCCESS $email!!\n";
cleartoken($email);
$session->param(name => $email);
my $nickname=$email;
$nickname=~s/@.*//;
$session->param(nickname => Encode::decode_utf8($nickname));
IkiWiki::cgi_postsignin($cgi, $session);
}
elsif (length $token ne length $cgi->param('v')) {
error(gettext("Wrong login token length. Please check that you pasted in the complete login link from the email!"));
}
else {
loginfailure();
}
}
}
# Generates the token that will be used in the authurl to log the user in.
# This needs to be hard to guess, and relatively short. Generating a cgi
# session id will make it as hard to guess as any cgi session.
sub gentoken ($) {
my $email=shift;
eval q{use CGI::Session};
error($@) if $@;
my $token = CGI::Session->new->id;
# Store token in userinfo; this allows the user to log in
# using a different browser session, if it takes a while for the
# email to get to them.
IkiWiki::userinfo_set($email, "emailauthexpire", time+(60*60*24));
IkiWiki::userinfo_set($email, "emailauth", $token);
return $token;
}
# Gets the token, checking for expiry.
sub gettoken ($) {
my $email=shift;
my $val=IkiWiki::userinfo_get($email, "emailauth");
my $expire=IkiWiki::userinfo_get($email, "emailauthexpire");
if (! length $val || time > $expire) {
loginfailure();
}
return $val;
}
sub cleartoken ($) {
my $email=shift;
IkiWiki::userinfo_set($email, "emailauthexpire", 0);
IkiWiki::userinfo_set($email, "emailauth", "");
}
sub loginfailure () {
error "Bad email authentication token. Please retry login.";
}
1

View File

@ -21,12 +21,13 @@ sub register_login_plugin ($$$$) {
# This sub is passed a cgi object, and should return true
# if it looks like the user is logging in using the plugin.
my $plugin_check_input=shift;
# This sub is passed a cgi object, a session object, and an error
# display callback, and should handle the actual authentication.
# It can either exit w/o returning, if it is able to handle
# auth, or it can pass an error message to the error display
# callback to make the openid selector form be re-disiplayed with
# an error message on it.
# This sub is passed a cgi object, a session object, an error
# display callback, and an info display callback, and should
# handle the actual authentication. It can either exit w/o
# returning, if it is able to handle auth, or it can pass an
# error message to the error display callback to make the
# openid selector form be re-disiplayed with an error message
# on it.
my $plugin_auth=shift;
$login_plugins{$plugin_name}={
setup => $plugin_setup,
@ -56,6 +57,8 @@ sub login_selector {
if ($login_plugins{$plugin}->{check_input}->($q)) {
$login_plugins{$plugin}->{auth}->($q, $session, sub {
$template->param(login_error => shift());
}, sub {
$template->param(login_info => shift());
});
last;
}

View File

@ -63,7 +63,7 @@ sub openid_check_input ($) {
defined $q->param("action") && $q->param("action") eq "verify" && defined $openid_url && length $openid_url;
}
sub openid_auth ($$$) {
sub openid_auth ($$$$) {
my $q=shift;
my $session=shift;
my $errordisplayer=shift;

View File

@ -12,3 +12,6 @@ Users who have logged in using emailauth will have their email address used as
their username. In places where the username is displayed, like the
RecentChanges page, the domain will be omitted, to avoid exposing the
user's email address.
This plugin needs the [[!cpan Mail::SendMail]] perl module installed,
and able to send outgoing email.

View File

@ -48,6 +48,9 @@ $(document).ready(function() {
<TMPL_IF LOGIN_ERROR>
<div class="error"><TMPL_VAR LOGIN_ERROR></div>
</TMPL_IF>
<TMPL_IF LOGIN_INFO>
<TMPL_VAR LOGIN_INFO>
</TMPL_IF>
</div>
</form>