2 use CGI::Fast qw/:cgi/;
3 use Digest::SHA qw/hmac_sha256/;
4 use MIME::Base64 qw/encode_base64url/;
7 use Time::HiRes qw/sleep/;
12 open HMAC_KEY, "$ENV{BLERG_HOME}/etc/hmac_key"
13 or die "Could not open $ENV{BLERG_HOME}/etc/hmac_key";
14 read(HMAC_KEY, $hmac_key, 256);
18 print header(-type => 'text/html',
19 -status => '404 Not Found');
22 <h1>404 Not Found</h1>
27 print header(-type => 'text/html',
28 -status => '403 Forbidden');
31 <h1>403 Forbidden</h1>
36 sub generate_reset_url {
37 my ($username, $validity) = @_;
40 my $expiry = time + $validity;
41 my $counter = Blerg::Database::auth_get_counter($username)
43 my $data = "$username:$expiry:$counter";
46 my $hmac = encode_base64url(hmac_sha256($data, $hmac_key));
48 return Blerg::Database::BASEURL . "#/recovery/$data:$hmac";
51 sub validate_reset_data {
55 if ($data =~ /^(.*):([^:]+)$/) {
62 my $computed_hmac = encode_base64url(hmac_sha256($payload, $hmac_key));
63 if ($hmac ne $computed_hmac) {
67 my ($username, $expiry, $counter) = split(':', $payload);
69 || $counter != Blerg::Database::auth_get_counter($username)) {
77 while (my $q = new CGI::Fast) {
78 my (undef, $cmd, $args) = split '/', $ENV{PATH_INFO}, 3;
82 # determine that authentication is valid.
83 my $auth = $q->cookie('auth');
88 my ($username, $token) = split('/', $auth);
89 if (!Blerg::Database::auth_check_token($username, $token)) {
94 my $validity = 365 * 86400; # One year
95 print header(-type => 'text/plain');
96 print generate_reset_url($username, $validity);
99 print header(-type => 'application/json');
101 if (!(defined $q->param('username') and defined $q->param('email'))) {
102 say '{"status": "failed"}';
106 # Sleep for a bit to scramble the timing
107 sleep(rand(1.0) + 1);
109 # From here on, we report success so as not to leak user information
110 my $username = $q->param('username');
111 if (!Blerg::Database::exists($username)) {
112 say '{"status": "success"}';
116 # check that the user has a validated mail address
117 my $email_conf_path = Blerg::Database::configuration->{data_path} . "/$username/email";
119 if (!open EMAIL, $email_conf_path) {
120 say '{"status": "success"}';
126 if ($q->param('email') ne $email) {
127 say '{"status": "success"}';
131 my $url = generate_reset_url($username, 900);
132 Mail::Message->build(
133 From => Mail::Address->new('BlergBot', 'noreply@blerg.cc'),
135 Subject => 'Blërg Password Recovery',
136 Mail::Message::Field->new('Content-Type', 'text/plain', 'charset="utf8"'),
138 Here's a 15-minute recovery link to reset your password.
142 If you didn't request a password reset, please ignore this email.
148 say '{"status": "success"}';
151 print header(-type => 'application/json');
153 my $username = validate_reset_data($q->param('data'));
155 if (!defined $username) {
156 say '{"status": "failure"}';
160 my $password = $q->param('password');
161 if (Blerg::Database::auth_set_password($username, $password)) {
162 say '{"status": "success"}';
164 say '{"status": "failure"}';