diff options
author | Joey Hess <joeyh@joeyh.name> | 2015-05-13 23:38:56 -0400 |
---|---|---|
committer | Joey Hess <joeyh@joeyh.name> | 2015-05-13 23:38:56 -0400 |
commit | cfb2c22906f41d4a4dd1c3404e8e430a35c1cd41 (patch) | |
tree | b2f0271a5fa334530b3ac3051ad42bc02172a708 | |
parent | ee2905ae0a2786fd5f195fdc1779a34b5f62c1d9 (diff) | |
parent | bf8b7fe2d1602827f7419521d140bac4be6200a8 (diff) | |
download | ikiwiki-cfb2c22906f41d4a4dd1c3404e8e430a35c1cd41.tar ikiwiki-cfb2c22906f41d4a4dd1c3404e8e430a35c1cd41.tar.gz |
Merge branch 'emailauth'
26 files changed, 781 insertions, 383 deletions
diff --git a/IkiWiki.pm b/IkiWiki.pm index f414996db..53eb8235b 100644 --- a/IkiWiki.pm +++ b/IkiWiki.pm @@ -165,7 +165,7 @@ sub getsetup () { default_plugins => { type => "internal", default => [qw{mdwn link inline meta htmlscrubber passwordauth - openid signinedit lockedit conditional + openid emailauth signinedit lockedit conditional recentchanges parentlinks editpage templatebody}], description => "plugins to enable by default", @@ -1464,6 +1464,14 @@ sub openiduser ($) { return; } +sub emailuser ($) { + my $user=shift; + if (defined $user && $user =~ m/(.+)@/) { + return $1; + } + return; +} + sub htmlize ($$$$) { my $page=shift; my $destpage=shift; diff --git a/IkiWiki/Plugin/comments.pm b/IkiWiki/Plugin/comments.pm index fb423e713..eaa924e51 100644 --- a/IkiWiki/Plugin/comments.pm +++ b/IkiWiki/Plugin/comments.pm @@ -198,7 +198,6 @@ sub preprocess { $commentuser = $params{username}; my $oiduser = eval { IkiWiki::openiduser($commentuser) }; - if (defined $oiduser) { # looks like an OpenID $commentauthorurl = $commentuser; @@ -206,6 +205,11 @@ sub preprocess { $commentopenid = $commentuser; } else { + my $emailuser = IkiWiki::emailuser($commentuser); + if (defined $emailuser) { + $commentuser=$emailuser; + } + if (length $config{cgiurl}) { $commentauthorurl = IkiWiki::cgiurl( do => 'goto', diff --git a/IkiWiki/Plugin/emailauth.pm b/IkiWiki/Plugin/emailauth.pm new file mode 100644 index 000000000..aa067238f --- /dev/null +++ b/IkiWiki/Plugin/emailauth.pm @@ -0,0 +1,193 @@ +#!/usr/bin/perl +# Ikiwiki email address as login +package IkiWiki::Plugin::emailauth; + +use warnings; +use strict; +use IkiWiki 3.00; + +sub import { + hook(type => "getsetup", id => "emailauth", "call" => \&getsetup); + 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", + \&email_setup, + \&email_check_input, + \&email_auth, + ); +} + +sub getsetup () { + return + plugin => { + safe => 1, + rebuild => 0, + section => "auth", + }, +} + +sub email_setup ($$) { + my $q=shift; + my $template=shift; + + return 1; +} + +sub email_check_input ($) { + my $cgi=shift; + defined $cgi->param('do') + && $cgi->param("do") eq "signin" + && defined $cgi->param('Email_entry') + && length $cgi->param('Email_entry'); +} + +# Send login link to email. +sub email_auth ($$$$) { + my $cgi=shift; + my $session=shift; + my $errordisplayer=shift; + my $infodisplayer=shift; + + my $email=$cgi->param('Email_entry'); + unless ($email =~ /.\@./) { + $errordisplayer->(gettext("Invalid email address.")); + return; + } + + # 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( + '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) { + 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->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 loginfailure () { + error "Bad email authentication token. Please retry login."; +} + +1 diff --git a/IkiWiki/Plugin/loginselector.pm b/IkiWiki/Plugin/loginselector.pm new file mode 100644 index 000000000..26c80b4ce --- /dev/null +++ b/IkiWiki/Plugin/loginselector.pm @@ -0,0 +1,132 @@ +#!/usr/bin/perl +package IkiWiki::Plugin::loginselector; + +use warnings; +use strict; +use IkiWiki 3.00; + +# Plugins that provide login methods can register themselves here. +# Note that the template and js file also have be be modifed to add a new +# login method. +our %login_plugins; + +sub register_login_plugin ($$$$) { + # Same as the name of the plugin that is registering itself as a + # login plugin. eg, "openid" + my $plugin_name=shift; + # This sub is passed a cgi object and a template object which it + # can manipulate. It should return true if the plugin can be used + # (it might load necessary modules for auth checking, for example). + my $plugin_setup=shift; + # 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, 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, + check_input => $plugin_check_input, + auth => $plugin_auth, + }; +} + +sub login_selector { + my $real_cgi_signin=shift; + my $otherform_label=shift; + my $q=shift; + my $session=shift; + + my $template=IkiWiki::template("login-selector.tmpl"); + + foreach my $plugin (keys %login_plugins) { + if (! $login_plugins{$plugin}->{setup}->($template)) { + delete $login_plugins{$plugin}; + } + else { + $template->param("login_selector_$plugin", 1); + } + } + + foreach my $plugin (keys %login_plugins) { + 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; + } + } + + $template->param( + cgiurl => IkiWiki::cgiurl(), + ($real_cgi_signin ? (otherform => $real_cgi_signin->($q, $session, 1)) : ()), + otherform_label => $otherform_label, + ); + + IkiWiki::printheader($session); + print IkiWiki::cgitemplate($q, "signin", $template->output); + exit; +} + +sub import { + add_underlay("login-selector"); + add_underlay("jquery"); + hook(type => "getsetup", id => "loginselector", call => \&getsetup); + hook(type => "checkconfig", id => "loginselector", call => \&checkconfig); + hook(type => "auth", id => "loginselector", call => \&authstub); +} + +sub checkconfig () { + if ($config{cgi}) { + # Intercept normal signin form, so the login selector + # can be displayed. + # + # When other auth hooks are registered, give the selector + # a reference to the normal signin form. + require IkiWiki::CGI; + my $real_cgi_signin; + my $otherform_label=gettext("Other"); + if (keys %{$IkiWiki::hooks{auth}} > 1) { + $real_cgi_signin=\&IkiWiki::cgi_signin; + # Special case to avoid labeling password auth as + # "Other" when it's the only auth plugin not + # integrated with the loginselector. + my %h=%{$IkiWiki::hooks{auth}}; + foreach my $p (keys %login_plugins) { + delete $h{$p}; + } + delete $h{loginselector}; + if (keys %h == 1 && exists $h{passwordauth}) { + $otherform_label=gettext("Password"); + } + } + inject(name => "IkiWiki::cgi_signin", call => sub ($$) { + login_selector($real_cgi_signin, $otherform_label, @_); + }); + } +} + +sub getsetup () { + return + plugin => { + # this plugin is safe but only makes sense as a + # dependency + safe => 0, + rebuild => 0, + }, +} + +sub authstub ($$) { + # 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. +} + +1 diff --git a/IkiWiki/Plugin/openid.pm b/IkiWiki/Plugin/openid.pm index 00652ebb7..cc4b4ba3d 100644 --- a/IkiWiki/Plugin/openid.pm +++ b/IkiWiki/Plugin/openid.pm @@ -7,35 +7,17 @@ use strict; use IkiWiki 3.00; sub import { - add_underlay("openid-selector"); - add_underlay("jquery"); - hook(type => "checkconfig", id => "openid", call => \&checkconfig); hook(type => "getsetup", id => "openid", call => \&getsetup); hook(type => "auth", id => "openid", call => \&auth); hook(type => "formbuilder_setup", id => "openid", call => \&formbuilder_setup, last => 1); -} - -sub checkconfig () { - if ($config{cgi}) { - # Intercept normal signin form, so the openid selector - # can be displayed. - # - # When other auth hooks are registered, give the selector - # a reference to the normal signin form. - require IkiWiki::CGI; - my $real_cgi_signin; - my $nonopenidform_label=gettext("Other"); - if (keys %{$IkiWiki::hooks{auth}} > 1) { - $real_cgi_signin=\&IkiWiki::cgi_signin; - if (keys %{$IkiWiki::hooks{auth}} == 2 && exists $IkiWiki::hooks{auth}->{passwordauth}) { - $nonopenidform_label=gettext("Password"); - } - } - inject(name => "IkiWiki::cgi_signin", call => sub ($$) { - openid_selector($real_cgi_signin, $nonopenidform_label, @_); - }); - } + IkiWiki::loadplugin("loginselector"); + IkiWiki::Plugin::loginselector::register_login_plugin( + "openid", + \&openid_setup, + \&openid_check_input, + \&openid_auth, + ); } sub getsetup () { @@ -59,40 +41,34 @@ sub getsetup () { }, } -sub openid_selector { - my $real_cgi_signin=shift; - my $nonopenidform_label=shift; - my $q=shift; - my $session=shift; - - my $openid_url=$q->param('openid_identifier'); - my $openid_error; +sub openid_setup ($$) { + my $q=shift; + my $template=shift; - if (! load_openid_module()) { - if ($real_cgi_signin) { - $real_cgi_signin->($q, $session); - exit; + if (load_openid_module()) { + my $openid_url=$q->param('openid_identifier'); + if (defined $openid_url) { + $template->param(openid_url => $openid_url); } - error(sprintf(gettext("failed to load openid module: "), @_)); + return 1; } - elsif (defined $q->param("action") && $q->param("action") eq "verify") { - validate($q, $session, $openid_url, sub { - $openid_error=shift; - }); + else { + return 0; } +} - my $template=IkiWiki::template("openid-selector.tmpl"); - $template->param( - cgiurl => IkiWiki::cgiurl(), - (defined $openid_error ? (openid_error => $openid_error) : ()), - (defined $openid_url ? (openid_url => $openid_url) : ()), - ($real_cgi_signin ? (nonopenidform => $real_cgi_signin->($q, $session, 1)) : ()), - nonopenidform_label => $nonopenidform_label, - ); +sub openid_check_input ($) { + my $q=shift; + my $openid_url=$q->param('openid_identifier'); + defined $q->param("action") && $q->param("action") eq "verify" && defined $openid_url && length $openid_url; +} - IkiWiki::printheader($session); - print IkiWiki::cgitemplate($q, "signin", $template->output); - exit; +sub openid_auth ($$$$) { + my $q=shift; + my $session=shift; + my $errordisplayer=shift; + my $openid_url=$q->param('openid_identifier'); + validate($q, $session, $openid_url, $errordisplayer); } sub formbuilder_setup (@) { diff --git a/IkiWiki/Plugin/passwordauth.pm b/IkiWiki/Plugin/passwordauth.pm index 0cf2a26ea..7c01bb3ff 100644 --- a/IkiWiki/Plugin/passwordauth.pm +++ b/IkiWiki/Plugin/passwordauth.pm @@ -277,7 +277,7 @@ sub formbuilder_setup (@) { } elsif ($form->title eq "preferences") { my $user=$session->param("name"); - if (! IkiWiki::openiduser($user)) { + if (! IkiWiki::openiduser($user) && ! IkiWiki::emailuser($user)) { $form->field(name => "name", disabled => 1, value => $user, force => 1, fieldset => "login"); diff --git a/IkiWiki/Setup/Automator.pm b/IkiWiki/Setup/Automator.pm index a8b04d966..9239974ad 100644 --- a/IkiWiki/Setup/Automator.pm +++ b/IkiWiki/Setup/Automator.pm @@ -154,31 +154,33 @@ sub import (@) { foreach my $admin (@{$config{adminuser}}) { next if defined IkiWiki::openiduser($admin); - # Prompt for password w/o echo. - my ($password, $password2); - system('stty -echo 2>/dev/null'); - local $|=1; - print "\n\nCreating wiki admin $admin ...\n"; - for (;;) { - print "Choose a password: "; - chomp($password=<STDIN>); - print "\n"; - print "Confirm password: "; - chomp($password2=<STDIN>); - - last if $password2 eq $password; - - print "Password mismatch.\n\n"; - } - print "\n\n\n"; - system('stty sane 2>/dev/null'); + if (! defined IkiWiki::emailuser($admin)) { + # Prompt for password w/o echo. + my ($password, $password2); + system('stty -echo 2>/dev/null'); + local $|=1; + print "\n\nCreating wiki admin $admin ...\n"; + for (;;) { + print "Choose a password: "; + chomp($password=<STDIN>); + print "\n"; + print "Confirm password: "; + chomp($password2=<STDIN>); + + last if $password2 eq $password; + + print "Password mismatch.\n\n"; + } + print "\n\n\n"; + system('stty sane 2>/dev/null'); - if (IkiWiki::userinfo_setall($admin, { regdate => time }) && - IkiWiki::Plugin::passwordauth::setpassword($admin, $password)) { - IkiWiki::userinfo_set($admin, "email", $config{adminemail}) if defined $config{adminemail}; - } - else { - error("problem setting up $admin user"); + if (IkiWiki::userinfo_setall($admin, { regdate => time }) && + IkiWiki::Plugin::passwordauth::setpassword($admin, $password)) { + IkiWiki::userinfo_set($admin, "email", $config{adminemail}) if defined $config{adminemail}; + } + else { + error("problem setting up $admin user"); + } } } diff --git a/auto.setup b/auto.setup index 843b4193a..1bcac36cb 100644 --- a/auto.setup +++ b/auto.setup @@ -18,7 +18,7 @@ our $wikiname_short=IkiWiki::Setup::Automator::sanitize_wikiname($wikiname); our $rcs=IkiWiki::Setup::Automator::ask( gettext("What revision control system to use?"), "git"); our $admin=IkiWiki::Setup::Automator::ask( - gettext("Which user (wiki account or openid) will be admin?"), $ENV{USER}); + gettext("Which user (wiki account, openid, or email) will be admin?"), $ENV{USER}); use Net::Domain q{hostfqdn}; our $domain=hostfqdn() || IkiWiki::Setup::Automator::ask( gettext("What is the domain name of the web server?"), ""); diff --git a/debian/changelog b/debian/changelog index 4d8b589cf..45801567f 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,10 +1,14 @@ ikiwiki (3.20150330) UNRELEASED; urgency=medium + * New emailauth plugin lets users log in, without any registration, + by simply clicking on a link in an email. * Re-remove google from openid selector; their openid provider is gone for good. - * When openid and passwordauth are the only enabled auth plugins, - make the openid selector display "Password" instead of "Other", - so users are more likely to click on it when they don't have an openid. + * Make the openid selector display "Password" instead of "Other" + when appropriate, so users are more likely to click on it when + they don't have an openid. + * Converted openid-selector into a more generic loginselector helper + plugin. -- Joey Hess <id@joeyh.name> Tue, 28 Apr 2015 12:24:08 -0400 diff --git a/debian/copyright b/debian/copyright index a73d1ccff..35fadf56b 100644 --- a/debian/copyright +++ b/debian/copyright @@ -201,20 +201,20 @@ Comment: Republished with permission. License: GPL-2+ -Files: underlays/openid-selector/ikiwiki/openid/* +Files: underlays/openid-selector/ikiwiki/login-selector/* Copyright: © 2008-2010 andyjm, david.j.boden Comment: From http://code.google.com/p/openid-selector/ License: BSD-2-clause -Files: underlays/openid-selector/ikiwiki/openid/goa-* +Files: underlays/openid-selector/ikiwiki/login-selector/goa-* Copyright: © 2011 Red Hat, Inc. License: LGPL-2.1+ Comment: taken from data/icons/16x16/ in gnome-online-accounts git -Files: underlays/openid-selector/ikiwiki/openid/wordpress.png +Files: underlays/openid-selector/ikiwiki/login-selector/wordpress.png Copyright: © 2003-2013 "the contributors" License: GPL-2+ @@ -226,9 +226,8 @@ Files: icons/aol.svg icons/livejournal.svg icons/verisign.svg - underlays/openid-selector/ikiwiki/openid/aol.png - underlays/openid-selector/ikiwiki/openid/livejournal.png - underlays/openid-selector/ikiwiki/openid/verisign.png + underlays/openid-selector/ikiwiki/login-selector/aol.png + underlays/openid-selector/ikiwiki/login-selector/verisign.png Copyright: © 2014 Simon McVittie License: other diff --git a/doc/basewiki.mdwn b/doc/basewiki.mdwn index 8392884eb..b4aa60c78 100644 --- a/doc/basewiki.mdwn +++ b/doc/basewiki.mdwn @@ -10,7 +10,7 @@ It currently includes these pages: * [[templates]] * [[ikiwiki/formatting]] * [[ikiwiki/markdown]] -* [[ikiwiki/openid]] +* [[ikiwiki/login-selector]] * [[ikiwiki/pagespec]] * [[ikiwiki/directive]] * [[ikiwiki/subpage]] diff --git a/doc/plugins/emailauth.pm b/doc/plugins/emailauth.pm new file mode 100644 index 000000000..8cb060e93 --- /dev/null +++ b/doc/plugins/emailauth.pm @@ -0,0 +1,17 @@ +[[!template id=plugin name=emailauth core=1 author="[[Joey]]"]] +[[!tag type/auth]] + +This plugin lets users log into ikiwiki using any email address. To complete +the login, a one-time-use link is emailed to the user, and they can simply +open that link in their browser. + +It is enabled by default, but can be turned off if you want to only use +some other form of authentication, such as [[passwordauth]] or [[openid]]. + +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. diff --git a/doc/plugins/write.mdwn b/doc/plugins/write.mdwn index c3f531b66..95f4a39df 100644 --- a/doc/plugins/write.mdwn +++ b/doc/plugins/write.mdwn @@ -458,6 +458,9 @@ object's "name" parameter to the authenticated user's name. Note that if the name is set to the name of a user who is not registered, a basic registration of the user will be automatically performed. +Auth plugins can use the loginselector helper plugin to let the user +select which authentication method to use. + ### <a name="sessioncgi">sessioncgi</a> hook(type => "sessioncgi", id => "foo", call => \&sessioncgi); diff --git a/doc/style.css b/doc/style.css index fe1eb903d..f0846c0d6 100644 --- a/doc/style.css +++ b/doc/style.css @@ -465,40 +465,40 @@ li.L8 { list-style: upper-alpha; } display: none; } -/* openid selector */ -#openid_choice { +/* login selector */ +#login_choice { display: none; } -#openid_input_area { +#login_input_area { clear: both; padding: 10px; } -#openid_btns, #openid_btns br { +#login_btns, #login_btns br { clear: both; } -#openid_highlight { +#login_highlight { background-color: black; float: left; } -.openid_large_btn { +.login_large_btn { padding: 1em 1.5em; border: 1px solid #DDD; margin: 3px; float: left; } -.openid_small_btn { +.login_small_btn { padding: 4px 4px; border: 1px solid #DDD; margin: 3px; float: left; } -a.openid_large_btn:focus { +a.login_large_btn:focus { outline: none; } -a.openid_large_btn:focus { +a.login_large_btn:focus { outline-style: none; } -.openid_selected { +.login_selected { border: 4px solid #DDD; } diff --git a/doc/templates.mdwn b/doc/templates.mdwn index 80372fcb7..c45316e44 100644 --- a/doc/templates.mdwn +++ b/doc/templates.mdwn @@ -92,8 +92,9 @@ Here is a full list of the template files used: * `editpage.tmpl`, `editconflict.tmpl`, `editcreationconflict.tmpl`, `editfailedsave.tmpl`, `editpagegone.tmpl`, `pocreatepage.tmpl`, `editcomment.tmpl` `commentmoderation.tmpl`, `renamesummary.tmpl`, - `passwordmail.tmpl`, `openid-selector.tmpl`, `revert.tmpl` - Parts of ikiwiki's user - interface; do not normally need to be customised. + `passwordmail.tmpl`, `emailauth.tmpl, `login-selector.tmpl`, + `revert.tmpl` - Parts of ikiwiki's user interface; do not normally need + to be customised. [[!meta robots="noindex, follow"]] diff --git a/doc/wikiicons/emaillogin.png b/doc/wikiicons/emaillogin.png Binary files differnew file mode 100644 index 000000000..1b16befd6 --- /dev/null +++ b/doc/wikiicons/emaillogin.png diff --git a/templates/emailauth.tmpl b/templates/emailauth.tmpl new file mode 100644 index 000000000..55614068d --- /dev/null +++ b/templates/emailauth.tmpl @@ -0,0 +1,10 @@ +To log into <TMPL_VAR WIKINAME>, just open the following link: + +<TMPL_VAR AUTHURL> + +This link can only be used once to log in, and will expire in one day. + +(Please disregard this email if you were not trying to log in.) + +-- +ikiwiki diff --git a/templates/login-selector.tmpl b/templates/login-selector.tmpl new file mode 100644 index 000000000..3e7045c63 --- /dev/null +++ b/templates/login-selector.tmpl @@ -0,0 +1,65 @@ +<script type="text/javascript" src="ikiwiki/jquery.min.js"></script> +<script type="text/javascript" src="ikiwiki/login-selector/login-selector.js"></script> +<script type="text/javascript"> +$(document).ready(function() { + selector.init( + 'openid_identifier', + { + <TMPL_IF LOGIN_SELECTOR_OPENID>'openid': 1,</TMPL_IF> + <TMPL_IF LOGIN_SELECTOR_EMAILAUTH>'email': 1,</TMPL_IF> + }, + '<TMPL_IF OTHERFORM>otherform</TMPL_IF>', + '<TMPL_VAR OTHERFORM_LABEL>' + ); +}); +</script> + +<form action="<TMPL_VAR CGIURL>" method="get" id="login_selector_form"> + <div> + <script> + $('fieldset').append("<legend>Select login method</legend>"); + </script> + + <input type="hidden" name="do" value="signin" /> + <input type="hidden" name="action" value="verify" /> + <div id="login_choice"> + <div id="login_btns"></div> + </div> + + <div id="login_input_area"> + <TMPL_IF LOGIN_SELECTOR_OPENID> + <div> + <h3>OpenId login:</h3> + <label for="openid_identifier" class="block">Enter your OpenID:</label> + <input id="openid_identifier" name="openid_identifier" type="text" value="<TMPL_VAR ESCAPE=HTML OPENID_URL>"/> + <input id="openid_submit" type="submit" value="Login"/> + </div> + </TMPL_IF> + <TMPL_IF LOGIN_SELECTOR_EMAILAUTH> + <div> + <h3>Email login:</h3> + <label for="email_address" class="block">Enter your email address:</label> + <input id="email_address" name="Email_entry" type="text" value="<TMPL_VAR ESCAPE=HTML EMAIL_ADDRESS>"/> + <input id="email_submit" type="submit" value="Login"/> + </div> + </TMPL_IF> + </div> + + <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> + +<div id="otherform"> +<TMPL_IF OTHERFORM> +<br /> +<noscript> +<h3><TMPL_VAR OTHERFORM_LABEL> login:</h3> +</noscript> +</TMPL_IF> +<TMPL_VAR OTHERFORM> +</div> diff --git a/templates/openid-selector.tmpl b/templates/openid-selector.tmpl deleted file mode 100644 index 95ba086fe..000000000 --- a/templates/openid-selector.tmpl +++ /dev/null @@ -1,43 +0,0 @@ -<script type="text/javascript" src="ikiwiki/jquery.min.js"></script> -<script type="text/javascript" src="ikiwiki/openid/openid-jquery.js"></script> -<script type="text/javascript"> -$(document).ready(function() { - openid.init('openid_identifier','<TMPL_IF NONOPENIDFORM>nonopenidform</TMPL_IF>', '<TMPL_VAR NONOPENIDFORM_LABEL>'); -}); -</script> - -<noscript> -<h2>OpenID:</h2> -</noscript> - -<form action="<TMPL_VAR CGIURL>" method="get" id="openid_form"> - <fieldset> - <script> - $('fieldset').append("<legend>Select login method</legend>"); - </script> - - <input type="hidden" name="do" value="signin" /> - <input type="hidden" name="action" value="verify" /> - <div id="openid_choice"> - <div id="openid_btns"></div> - </div> - <div id="openid_input_area"> - <label for="openid_identifier" class="block">Enter your OpenID:</label> - <input id="openid_identifier" name="openid_identifier" type="text" value="<TMPL_VAR ESCAPE=HTML OPENID_URL>"/> - <input id="openid_submit" type="submit" value="Login"/> - </div> - <TMPL_IF OPENID_ERROR> - <div class="error"><TMPL_VAR OPENID_ERROR></div> - </TMPL_IF> - </fieldset> -</form> - -<div id="nonopenidform"> -<TMPL_IF NONOPENIDFORM> -<br /> -<noscript> -<h2><TMPL_VAR NONOPENIDFORM_LABEL>:</h2> -</noscript> -</TMPL_IF> -<TMPL_VAR NONOPENIDFORM> -</div> diff --git a/underlays/login-selector/ikiwiki/login-selector/aol.png b/underlays/login-selector/ikiwiki/login-selector/aol.png Binary files differnew file mode 100644 index 000000000..d47f5fa54 --- /dev/null +++ b/underlays/login-selector/ikiwiki/login-selector/aol.png diff --git a/underlays/login-selector/ikiwiki/login-selector/goa-account-flickr.png b/underlays/login-selector/ikiwiki/login-selector/goa-account-flickr.png Binary files differnew file mode 100644 index 000000000..5321642f6 --- /dev/null +++ b/underlays/login-selector/ikiwiki/login-selector/goa-account-flickr.png diff --git a/underlays/openid-selector/ikiwiki/openid/goa-account-yahoo.png b/underlays/login-selector/ikiwiki/login-selector/goa-account-yahoo.png Binary files differindex 51e1c119b..51e1c119b 100644 --- a/underlays/openid-selector/ikiwiki/openid/goa-account-yahoo.png +++ b/underlays/login-selector/ikiwiki/login-selector/goa-account-yahoo.png diff --git a/underlays/login-selector/ikiwiki/login-selector/login-selector.js b/underlays/login-selector/ikiwiki/login-selector/login-selector.js new file mode 100644 index 000000000..71ae0466e --- /dev/null +++ b/underlays/login-selector/ikiwiki/login-selector/login-selector.js @@ -0,0 +1,264 @@ +/* +Based on the Simple OpenID Plugin +http://code.google.com/p/openid-selector/ + +This code is licenced under the New BSD License. +*/ + +var selections_email_large = { + email: { + name: 'Email', + icon: 'wikiicons/email.png', + label: 'Enter your email address:', + url: null + } +}; +var selections_openid_large = { + openid: { + name: 'OpenID', + icon: 'wikiicons/openidlogin-bg.gif', + label: 'Enter your OpenID:', + url: null + } +}; +var selections_openid_small = { + verisign: { + name: 'Verisign', + icon: 'ikiwiki/login-selector/verisign.png', + label: 'Enter your Verisign username:', + url: 'http://{username}.pip.verisignlabs.com/' + }, + yahoo: { + name: 'Yahoo', + icon: 'ikiwiki/login-selector/goa-account-yahoo.png', + url: 'http://me.yahoo.com/' + }, + flickr: { + name: 'Flickr', + icon: 'ikiwiki/login-selector/goa-account-flickr.png', + label: 'Enter your Flickr username:', + url: 'http://flickr.com/photos/{username}/' + }, + wordpress: { + name: 'Wordpress', + icon: 'ikiwiki/login-selector/wordpress.png', + label: 'Enter your Wordpress.com username:', + url: 'http://{username}.wordpress.com/' + }, + aol: { + name: 'AOL', + icon: 'ikiwiki/login-selector/aol.png', + label: 'Enter your AOL username:', + url: 'http://openid.aol.com/{username}' + } +}; +var selections = $.extend({}, selections_email_large, selections_openid_large, selections_openid_small); + +var selector = { + + ajaxHandler: null, + cookie_expires: 6*30, // 6 months. + cookie_name: 'openid_selection', // historical name + cookie_path: '/', + + img_path: 'images/', + + input_id: null, + selection_url: null, + selection_id: null, + othersignin_id: null, + + init: function(input_id, login_methods, othersignin_id, othersignin_label) { + + var selector_btns = $('#login_btns'); + + this.input_id = input_id; + + $('#login_choice').show(); + $('#login_input_area').empty(); + + // add box for each selection + if (login_methods['openid']) { + for (id in selections_openid_large) { + selector_btns.append(this.getBoxHTML(selections_openid_large[id], 'large')); + } + } + if (login_methods['email']) { + for (id in selections_email_large) { + selector_btns.prepend(this.getBoxHTML(selections_email_large[id], 'large')); + } + } + + if (othersignin_label != "") { + this.othersignin_label=othersignin_label; + } + else { + this.othersignin_label="other"; + } + if (othersignin_id != "") { + this.othersignin_id=othersignin_id; + selector_btns.prepend( + '<a href="javascript: selector.signin(\'othersignin\');"' + + ' style="background: #FFF" ' + + 'class="othersignin login_large_btn">' + + '<img alt="" width="16" height="16" src="favicon.ico" />' + + ' ' + this.othersignin_label + + '</a>' + ); + $('#'+this.othersignin_id).hide(); + } + + if (login_methods['openid'] && selections_openid_small) { + selector_btns.append('<br/>'); + + for (id in selections_openid_small) { + selector_btns.append(this.getBoxHTML(selections_openid_small[id], 'small')); + } + } + + $('#login_selector_form').submit(this.submit); + + var box_id = this.readCookie(); + if (box_id) { + this.signin(box_id, true); + } + }, + getBoxHTML: function(selection, box_size) { + var label=""; + var title="" + if (box_size == 'large') { + label=' ' + selection["name"]; + } + else { + title=' title="'+selection["name"]+'"'; + } + var box_id = selection["name"].toLowerCase(); + return '<a' + title +' href="javascript: selector.signin(\''+ box_id +'\');"' + + ' style="background: #FFF" ' + + 'class="' + box_id + ' login_' + box_size + '_btn">' + + '<img alt="" width="16" height="16" src="' + selection["icon"] + '" />' + + label + + '</a>'; + + }, + /* selection image click */ + signin: function(box_id, onload) { + + if (box_id == 'othersignin') { + this.highlight(box_id); + $('#login_input_area').empty(); + $('#'+this.othersignin_id).show(); + this.setCookie(box_id); + return; + } + else { + if (this.othersignin_id) { + $('#'+this.othersignin_id).hide(); + } + } + + var selection = selections[box_id]; + if (! selection) { + return; + } + + this.highlight(box_id); + + this.selection_id = box_id; + this.selection_url = selection['url']; + + // prompt user for input? + if (selection['label']) { + this.setCookie(box_id); + this.useInputBox(selection); + } else { + this.setCookie(''); + $('#login_input_area').empty(); + if (! onload) { + $('#login_selector_form').submit(); + } + } + }, + /* Sign-in button click */ + submit: function() { + var url = selector.selection_url; + if (url) { + url = url.replace('{username}', $('#entry').val()); + selector.setOpenIdUrl(url); + } + else { + selector.setOpenIdUrl(""); + } + if (selector.ajaxHandler) { + selector.ajaxHandler(selector.selection_id, document.getElementById(selector.input_id).value); + return false; + } + return true; + }, + setOpenIdUrl: function (url) { + + var hidden = $('#'+this.input_id); + if (hidden.length > 0) { + hidden.value = url; + } else { + $('#login_selector_form').append('<input style="display:none" id="' + this.input_id + '" name="' + this.input_id + '" value="'+url+'"/>'); + } + }, + highlight: function (box_id) { + + // remove previous highlight. + var highlight = $('#login_highlight'); + if (highlight) { + highlight.replaceWith($('#login_highlight a')[0]); + } + // add new highlight. + $('.'+box_id).wrap('<div id="login_highlight"></div>'); + }, + setCookie: function (value) { + + var date = new Date(); + date.setTime(date.getTime()+(this.cookie_expires*24*60*60*1000)); + var expires = "; expires="+date.toGMTString(); + + document.cookie = this.cookie_name+"="+value+expires+"; path=" + this.cookie_path; + }, + readCookie: function () { + var nameEQ = this.cookie_name + "="; + var ca = document.cookie.split(';'); + for(var i=0;i < ca.length;i++) { + var c = ca[i]; + while (c.charAt(0)==' ') c = c.substring(1,c.length); + if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length); + } + return null; + }, + useInputBox: function (selection) { + + var input_area = $('#login_input_area'); + + var html = ''; + var id = selection['name']+'_entry'; + var value = ''; + var label = selection['label']; + var style = ''; + + if (selection['name'] == 'OpenID') { + id = this.input_id; + value = ''; + style = 'background:#FFF url(wikiicons/openidlogin-bg.gif) no-repeat scroll 0 50%; padding-left:18px;'; + } + if (label) { + html = '<label for="'+ id +'" class="block">' + label + '</label>'; + } + html += '<input id="'+id+'" type="text" style="'+style+'" name="'+id+'" value="'+value+'" />' + + '<input id="selector_submit" type="submit" value="Login"/>'; + + input_area.empty(); + input_area.append(html); + + $('#'+id).focus(); + }, + setAjaxHandler: function (ajaxFunction) { + this.ajaxHandler = ajaxFunction; + } +}; diff --git a/underlays/openid-selector/ikiwiki/openid/verisign.png b/underlays/login-selector/ikiwiki/login-selector/verisign.png Binary files differindex 5120812ed..5120812ed 100644 --- a/underlays/openid-selector/ikiwiki/openid/verisign.png +++ b/underlays/login-selector/ikiwiki/login-selector/verisign.png diff --git a/underlays/login-selector/ikiwiki/login-selector/wordpress.png b/underlays/login-selector/ikiwiki/login-selector/wordpress.png Binary files differnew file mode 100644 index 000000000..96e08bd99 --- /dev/null +++ b/underlays/login-selector/ikiwiki/login-selector/wordpress.png diff --git a/underlays/openid-selector/ikiwiki/openid/openid-jquery.js b/underlays/openid-selector/ikiwiki/openid/openid-jquery.js deleted file mode 100644 index 2c72f19fb..000000000 --- a/underlays/openid-selector/ikiwiki/openid/openid-jquery.js +++ /dev/null @@ -1,237 +0,0 @@ -/* -Simple OpenID Plugin -http://code.google.com/p/openid-selector/ - -This code is licenced under the New BSD License. -*/ - -var providers_large = { - verisign: { - name: 'Verisign', - icon: 'ikiwiki/openid/verisign.png', - label: 'Enter your Verisign username:', - url: 'http://{username}.pip.verisignlabs.com/' - }, - yahoo: { - name: 'Yahoo', - icon: 'ikiwiki/openid/goa-account-yahoo.png', - url: 'http://me.yahoo.com/' - }, - openid: { - name: 'OpenID', - icon: 'wikiicons/openidlogin-bg.gif', - label: 'Enter your OpenID:', - url: null - } -}; -var providers_small = { -}; -var providers = $.extend({}, providers_large, providers_small); - -var openid = { - - demo: false, - ajaxHandler: null, - cookie_expires: 6*30, // 6 months. - cookie_name: 'openid_provider', - cookie_path: '/', - - img_path: 'images/', - - input_id: null, - provider_url: null, - provider_id: null, - localsignin_id: null, - - init: function(input_id, localsignin_id, localsignin_label) { - - var openid_btns = $('#openid_btns'); - - this.input_id = input_id; - - $('#openid_choice').show(); - $('#openid_input_area').empty(); - - // add box for each provider - for (id in providers_large) { - openid_btns.append(this.getBoxHTML(providers_large[id], 'large')); - } - if (localsignin_label != "") { - this.localsignin_label=localsignin_label; - } - else { - this.localsignin_label="other"; - } - if (localsignin_id != "") { - this.localsignin_id=localsignin_id; - openid_btns.append( - '<a href="javascript: openid.signin(\'localsignin\');"' + - ' style="background: #FFF" ' + - 'class="localsignin openid_large_btn">' + - '<img alt="" width="16" height="16" src="favicon.ico" />' + - ' ' + this.localsignin_label + - '</a>' - ); - $('#'+this.localsignin_id).hide(); - } - - if (providers_small) { - openid_btns.append('<br/>'); - - for (id in providers_small) { - - openid_btns.append(this.getBoxHTML(providers_small[id], 'small')); - } - } - - $('#openid_form').submit(this.submit); - - var box_id = this.readCookie(); - if (box_id) { - this.signin(box_id, true); - } - }, - getBoxHTML: function(provider, box_size) { - var label=""; - var title="" - if (box_size == 'large') { - label=' ' + provider["name"]; - } - else { - title=' title="'+provider["name"]+'"'; - } - var box_id = provider["name"].toLowerCase(); - return '<a' + title +' href="javascript: openid.signin(\''+ box_id +'\');"' + - ' style="background: #FFF" ' + - 'class="' + box_id + ' openid_' + box_size + '_btn">' + - '<img alt="" width="16" height="16" src="' + provider["icon"] + '" />' + - label + - '</a>'; - - }, - /* Provider image click */ - signin: function(box_id, onload) { - - if (box_id == 'localsignin') { - this.highlight(box_id); - $('#openid_input_area').empty(); - $('#'+this.localsignin_id).show(); - this.setCookie(box_id); - return; - } - else { - if (this.localsignin_id) { - $('#'+this.localsignin_id).hide(); - } - } - - var provider = providers[box_id]; - if (! provider) { - return; - } - - this.highlight(box_id); - - this.provider_id = box_id; - this.provider_url = provider['url']; - - // prompt user for input? - if (provider['label']) { - this.setCookie(box_id); - this.useInputBox(provider); - } else { - this.setCookie(''); - $('#openid_input_area').empty(); - if (! onload) { - $('#openid_form').submit(); - } - } - }, - /* Sign-in button click */ - submit: function() { - - var url = openid.provider_url; - if (url) { - url = url.replace('{username}', $('#openid_username').val()); - openid.setOpenIdUrl(url); - } - if(openid.ajaxHandler) { - openid.ajaxHandler(openid.provider_id, document.getElementById(openid.input_id).value); - return false; - } - if(openid.demo) { - alert("In client demo mode. Normally would have submitted OpenID:\r\n" + document.getElementById(openid.input_id).value); - return false; - } - return true; - }, - setOpenIdUrl: function (url) { - - var hidden = $('#'+this.input_id); - if (hidden.length > 0) { - hidden.value = url; - } else { - $('#openid_form').append('<input style="display:none" id="' + this.input_id + '" name="' + this.input_id + '" value="'+url+'"/>'); - } - }, - highlight: function (box_id) { - - // remove previous highlight. - var highlight = $('#openid_highlight'); - if (highlight) { - highlight.replaceWith($('#openid_highlight a')[0]); - } - // add new highlight. - $('.'+box_id).wrap('<div id="openid_highlight"></div>'); - }, - setCookie: function (value) { - - var date = new Date(); - date.setTime(date.getTime()+(this.cookie_expires*24*60*60*1000)); - var expires = "; expires="+date.toGMTString(); - - document.cookie = this.cookie_name+"="+value+expires+"; path=" + this.cookie_path; - }, - readCookie: function () { - var nameEQ = this.cookie_name + "="; - var ca = document.cookie.split(';'); - for(var i=0;i < ca.length;i++) { - var c = ca[i]; - while (c.charAt(0)==' ') c = c.substring(1,c.length); - if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length); - } - return null; - }, - useInputBox: function (provider) { - - var input_area = $('#openid_input_area'); - - var html = ''; - var id = 'openid_username'; - var value = ''; - var label = provider['label']; - var style = ''; - - if (provider['name'] == 'OpenID') { - id = this.input_id; - value = ''; - style = 'background:#FFF url(wikiicons/openidlogin-bg.gif) no-repeat scroll 0 50%; padding-left:18px;'; - } - if (label) { - html = '<label for="'+ id +'" class="block">' + label + '</label>'; - } - html += '<input id="'+id+'" type="text" style="'+style+'" name="'+id+'" value="'+value+'" />' + - '<input id="openid_submit" type="submit" value="Login"/>'; - - input_area.empty(); - input_area.append(html); - - $('#'+id).focus(); - }, - setDemoMode: function (demoMode) { - this.demo = demoMode; - }, - setAjaxHandler: function (ajaxFunction) { - this.ajaxHandler = ajaxFunction; - } -}; |