aboutsummaryrefslogtreecommitdiff
path: root/IkiWiki/Plugin/emailauth.pm
blob: 3266e21abcabe6ae2bf7c2c7bb40fbfad2a50d82 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
#!/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 => "cgi", "call" => \&cgi);
	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);
	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