]> git.vanrenterghem.biz Git - git.ikiwiki.info.git/commitdiff
emailauth link sent and verified; user login works
authorJoey Hess <joeyh@joeyh.name>
Thu, 14 May 2015 02:27:03 +0000 (22:27 -0400)
committerJoey Hess <joeyh@joeyh.name>
Thu, 14 May 2015 02:27:03 +0000 (22:27 -0400)
Still some work to do since the user name is an email address and should
not be leaked.

IkiWiki/Plugin/emailauth.pm
IkiWiki/Plugin/loginselector.pm
IkiWiki/Plugin/openid.pm
doc/plugins/emailauth.pm
templates/login-selector.tmpl

index 3946ace03aadee7b6a4d4224da6bc0535c85b75f..3266e21abcabe6ae2bf7c2c7bb40fbfad2a50d82 100644 (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
index 1a322a53a918a3f59c33deb4ecab44037cf4856f..26c80b4ce4c3a23423daff216b539217abea0307 100644 (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;
                }
index 67b8cd387a947a201463a5bf6254eddf26be1808..cc4b4ba3d4949c737d0d2139729a25c971743ff1 100644 (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;
index 9ba26e40c30083ec040d2145c59ca11c8ba79d17..8cb060e93f761a0d1a8d88f1b43fc3337a87f498 100644 (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.
index 9b68838dc3984dec75e7daa35f0195c44adfce1f..3e7045c63d3b770fdd12a278a5470132cb0f8d3f 100644 (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>