X-Git-Url: http://git.vanrenterghem.biz/git.ikiwiki.info.git/blobdiff_plain/e34533d1a0e0fc90a669f7cb3c11cafc6b285b26..975cbe318119e0d600dc1b0957623d211687ade1:/IkiWiki/Plugin/emailauth.pm diff --git a/IkiWiki/Plugin/emailauth.pm b/IkiWiki/Plugin/emailauth.pm index 62c9fe877..becf40ca5 100644 --- a/IkiWiki/Plugin/emailauth.pm +++ b/IkiWiki/Plugin/emailauth.pm @@ -8,7 +8,8 @@ use IkiWiki 3.00; sub import { hook(type => "getsetup", id => "emailauth", "call" => \&getsetup); - hook(type => "auth", id => "emailauth", call => \&auth); + hook(type => "cgi", id => "emailauth", "call" => \&cgi); + hook(type => "formbuilder_setup", id => "emailauth", "call" => \&formbuilder_setup); IkiWiki::loadplugin("loginselector"); IkiWiki::Plugin::loginselector::register_login_plugin( "emailauth", @@ -25,6 +26,12 @@ sub getsetup () { rebuild => 0, section => "auth", }, + emailauth_sender => { + type => "string", + description => "email address to send emailauth mails as (default: adminemail)", + safe => 1, + rebuild => 0, + }, } sub email_setup ($$) { @@ -42,23 +49,155 @@ 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, $session); + my $template=template("emailauth.tmpl"); + $template->param( + wikiname => $config{wikiname}, + # Intentionally using short field names to keep link short. + authurl => IkiWiki::cgiurl_abs_samescheme( + 'e' => $email, + 'v' => $token, + ), + ); + + eval q{use Mail::Sendmail}; + error($@) if $@; + my $shorturl=$config{url}; + $shorturl=~s/^https?:\/\///i; + my $emailauth_sender=$config{emailauth_sender}; + $emailauth_sender=$config{adminemail} unless defined $emailauth_sender; + sendmail( + To => $email, + From => "$config{wikiname} admin <". + (defined $emailauth_sender ? $emailauth_sender : "") + .">", + Subject => "$config{wikiname} login | $shorturl", + Message => $template->output, + ) or error(sprintf(gettext("Failed to send mail: %s"), $Mail::Sendmail::error)); + + $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) { + my $token=gettoken($email); + if ($token eq $v) { + cleartoken($email); + my $session=getsession($email); + 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(); + } + } +} + +sub formbuilder_setup (@) { + my %params=@_; + my $form=$params{form}; + my $session=$params{session}; + + if ($form->title eq "preferences" && + IkiWiki::emailuser($session->param("name"))) { + $form->field(name => "email", disabled => 1); + } +} + +# 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. +# +# 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. +# +# The postsignin value from the session is also stored in the userinfo +# to allow resuming in a different browser session. +sub gentoken ($$) { + my $email=shift; + my $session=shift; + eval q{use CGI::Session}; + error($@) if $@; + my $token = CGI::Session->new("driver:DB_File", undef, {FileName => "/dev/null"})->id; + IkiWiki::userinfo_set($email, "emailauthexpire", time+(60*60*24)); + IkiWiki::userinfo_set($email, "emailauth", $token); + IkiWiki::userinfo_set($email, "emailauthpostsignin", defined $session->param("postsignin") ? $session->param("postsignin") : ""); + 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; +} + +# Generate a session to use after successful login. +sub getsession ($) { + my $email=shift; + + IkiWiki::lockwiki(); + IkiWiki::loadindex(); + my $session=IkiWiki::cgi_getsession(); + + my $postsignin=IkiWiki::userinfo_get($email, "emailauthpostsignin"); + IkiWiki::userinfo_set($email, "emailauthpostsignin", ""); + if (defined $postsignin && length $postsignin) { + $session->param(postsignin => $postsignin); + } + + $session->param(name => $email); + my $nickname=$email; + $nickname=~s/@.*//; + $session->param(nickname => Encode::decode_utf8($nickname)); + + IkiWiki::cgi_savesession($session); + + return $session; +} + +sub cleartoken ($) { + my $email=shift; + IkiWiki::userinfo_set($email, "emailauthexpire", 0); + IkiWiki::userinfo_set($email, "emailauth", ""); } -sub auth ($$) { - # While this hook is not currently used, it needs to exist - # so ikiwiki knows that the wiki supports logins, and will - # enable the Preferences page. +sub loginfailure () { + error "Bad email authentication token. Please retry login."; } 1