draw login buttons using javascript.
[sgn.git] / lib / SGN / Controller / AJAX / User.pm
blob14ed4050896e74536035126d3a470b3036058149
2 package SGN::Controller::AJAX::User;
4 use Moose;
5 use IO::File;
6 use Data::Dumper;
8 BEGIN { extends 'Catalyst::Controller::REST' };
10 __PACKAGE__->config(
11 default => 'application/json',
12 stash_key => 'rest',
13 map => { 'application/json' => 'JSON', 'text/html' => 'JSON' },
17 sub login : Path('/ajax/user/login') Args(0) {
18 my $self = shift;
19 my $c = shift;
21 my $username = $c->req->param("username");
22 my $password = $c->req->param("password");
24 my $login = CXGN::Login->new($c->dbc->dbh());
25 my $login_info = $login->login_user($username, $password);
27 if (exists($login_info->{incorrect_password}) && $login_info->{incorrect_password} == 1) {
28 $c->stash->{rest} = { error => "Login credentials are incorrect. Please try again." };
31 elsif (exists($login_info->{account_disabled}) && $login_info->{account_disabled}) {
32 $c->stash->{rest} = { error => "This account has been disabled due to $login_info->{account_disabled}. Please contact the database to fix this problem." };
35 else {
36 $c->stash->{rest} = { message => 'Something happened, but nodoby knows what.' };
38 $c->stash->{rest} = { message => "Login successful" };
43 sub new_account :Path('/ajax/user/new') Args(0) {
44 my $self = shift;
45 my $c = shift;
47 if ($c->conf->{is_mirror}) {
48 $c->stash->{template} = '/system_message.mas';
49 $c->stash->{message} = "This site is a mirror site and does not support adding users. Please go to the main site to create an account.";
50 return;
54 my ($first_name, $last_name, $username, $password, $confirm_password, $email_address, $organization)
55 = $c->req->param(qw(first_name last_name username password confirm_password email_address organization));
57 if ($username) {
59 # check password properties...
61 my @fail = ();
62 if (length($username) < 7) {
63 push @fail, "Username is too short. Username must be 7 or more characters";
64 } else {
65 # does user already exist?
67 my $existing_login = CXGN::People::Login -> get_login($c->dbc()->dbh(), $username);
69 if ($existing_login->get_username()) {
70 push @fail, "Username \"$username\" is already in use. Please pick a different username.";
74 if (length($password) < 7) {
75 push @fail, "Password is too short. Password must be 7 or more characters";
77 if ("$password" ne "$confirm_password") {
78 push @fail, "Password and confirm password do not match.";
81 if (!$organization) {
82 push @fail, "'Organization' is required.'";
85 if ($password eq $username) {
86 push @fail, "Password must not be the same as your username.";
88 if ($email_address !~ m/[^\@]+\@[^\@]+/) {
89 push @fail, "Email address is invalid.";
91 unless($first_name) {
92 push @fail,"You must enter a first name or initial.";
94 unless($last_name) {
95 push @fail,"You must enter a last name.";
98 if (@fail) {
99 $c->stash->{rest} = { error => "Account creation failed for the following reason(s): ".(join ", ", @fail) };
100 return;
104 my $confirm_code = $self->tempname();
105 my $new_user = CXGN::People::Login->new($c->dbc->dbh());
106 $new_user -> set_username($username);
107 $new_user -> set_password($password);
108 $new_user -> set_pending_email($email_address);
109 $new_user -> set_confirm_code($confirm_code);
110 $new_user -> set_disabled('unconfirmed account');
111 $new_user -> set_organization($organization);
112 $new_user -> store();
114 #this is being added because the person object still uses two different objects, despite the fact that we've merged the tables
115 my $person_id=$new_user->get_sp_person_id();
116 my $new_person=CXGN::People::Person->new($self->dbc->dbh(),$person_id);
117 $new_person->set_first_name($first_name);
118 $new_person->set_last_name($last_name);
119 $new_person->store();
121 my $host = $c->req()->hostname();
122 my $subject="[SGN] Email Address Confirmation Request";
123 my $body=<<END_HEREDOC;
125 Please do *NOT* reply to this message. The return address is not valid.
126 Use the <a href="/contact/form">contact form</a> instead.
128 This message is sent to confirm the email address for community user
129 \"$username\"
131 Please click (or cut and paste into your browser) the following link to
132 confirm your account and email address:
134 https://$host/solpeople/account-confirm.pl?username=$username&confirm=$confirm_code
136 Thank you,
137 Sol Genomics Network
139 END_HEREDOC
141 CXGN::Contact::send_email($subject,$body,$email_address);
142 $c->stash->{rest} = { message => qq | <table summary="" width="80%" align="center">
143 <tr><td><p>Account was created with username \"$username\". To continue, you must confirm that SGN staff can reach you via email address \"$email_address\". An email has been sent with a URL to confirm this address. Please check your email for this message and use the link to confirm your email address.</p></td></tr>
144 <tr><td><br /></td></tr>
145 </table>
146 | };
150 sub change_account_info_action :Path('/ajax/user/update') Args(0) {
151 my $self = shift;
152 my $c = shift;
154 if (! $c->user() ) {
155 $c->stash->{rest} = { error => "You must be logged in to use this page." };
156 return;
159 my $person = new CXGN::People::Login($c->dbc->dbh(), $c->user->get_sp_person_id());
161 # my ($current_password, $change_username, $change_password, $change_email) = $c->req->param({qw(current_password change_username change_password change_email)});
163 my $args = $c->req->params();
165 if (!$args->{change_password} && ! $args->{change_username} && !$args->{change_email}) {
166 my $error = "No actions were requested. Please select which fields you would like to update by checking the appropriate checkbox(es) on the form and entering your new information.";
167 print STDERR $error;
168 $c->stash->{rest} = { error => $error };
169 return;
172 print STDERR "Person = ".$person->get_username()."\n";
173 chomp($args->{current_password});
174 if (! $person->verify_password($args->{current_password})) {
175 my $error = "Your current password does not match SGN records.";
176 print STDERR $error;
177 $c->stash->{rest} = { error => "$error" };
178 return;
181 # Check for error conditions in all changes, before making any of them.
182 # Otherwise, we could end up making some changes and then failing on later
183 # ones. The user would then push the back button and their information may
184 # be different now but they will probably assume no changes were made. This
185 # is most troublesome if the current password changes.
187 if ($args->{change_username}) {
188 #unless change_username is set, new_username won't be in the args hash because of the prestore test
189 my $new_username = $args->{new_username};
190 if(length($new_username) < 7) {
191 my $error = "Username must be at least 7 characters long.";
192 print STDERR $error;
193 $c->stash->{rest} = { error => $error };
194 return;
197 my $other_user = CXGN::People::Login->get_login($c->dbc->dbh(), $new_username);
198 if (defined $other_user->get_sp_person_id() &&
199 ($person -> get_sp_person_id() != $other_user->get_sp_person_id())) {
200 print STDERR "Username alread in use.\n";
201 $c->stash->{rest} = { error => "Username \"$new_username\" is already in use. Please select a different username." };
202 return;
205 print STDERR "Saving new username args->{username} to the database...\n";
206 $person->set_username($new_username);
207 $person->store();
210 if ($args->{change_password}) {
211 #unless change_password is set, new_password won't be in the args hash because of the prestore test
212 my ($new_password, $confirm_password) = ($args->{new_password}, $args->{confirm_password});
213 if(length($args->{new_password}) < 7) {
214 print STDERR "Password too short\n";
215 $c->stash->{rest} = { error => "Passwords must be at least 7 characters long. Please try again." };
216 return;
218 #format check
219 if($args->{new_password} !~ /^[a-zA-Z0-9~!@#$^&*_.=:;<>?]+$/) {
220 print STDERR "Illegal characters in password\n";
221 $c->stash->{rest} = { error => "An error occurred. Please use your browser's back button to try again.. The Password can't contain spaces or these symbols: <u><b>` ( ) [ ] { } - + ' \" / \\ , |</b></u>." };
222 return;
224 if($args->{new_password} ne $args->{confirm_password}) {
225 print STDERR "Password don't match.\n";
226 $c->stash->{rest} = { error => "New password entries do not match. You must enter your new password twice to verify accuracy." };
227 return;
230 print STDERR "Saving new password '$args->{new_password}' to the database\n";
231 $person->update_password($args->{new_password});
234 my $user_private_email = $c->user->get_private_email();
235 if($args->{change_email}) {
236 #unless change_email is set, private_email won't be in the args hash because of the prestore test
237 my ($private_email, $confirm_email) = ($args->{private_email}, $args->{confirm_email});
238 if($private_email !~ m/^[a-zA-Z0-9_.-]+@[a-zA-Z0-9_.-]+$/) {
239 print STDERR "Invalid email address\n";
240 $c->stash->{rest} = { error => "An error occurred. Please use your browser's back button to try again. The E-mail address \"$private_email\" does not appear to be a valid e-mail address." };
241 return;
243 if($private_email ne $confirm_email) {
244 print STDERR "Emails don't match\n";
245 $c->stash->{rest} = { error => "An error occurred. Please use your browser's back button to try again. New e-mail address entries do not match. You must enter your new e-mail address twice to verify accuracy." };
246 return;
249 print STDERR "Saving private email '$private_email' to the database\n";
250 $person->set_private_email($private_email);
251 my $confirm_code = $self->tempname();
252 $person->set_confirm_code($confirm_code);
253 $person->store();
255 $user_private_email = $private_email;
257 $self->send_confirmation_email($args->{username}, $user_private_email, $confirm_code, $c->req->hostname());
261 $c->stash->{rest} = { message => "Update successful" };
265 sub send_confirmation_email {
266 my ($self, $username, $private_email, $confirm_code, $host) = @_;
267 my $subject = "[SGN] E-mail Address Confirmation Request";
268 my $body = <<END_HEREDOC;
269 Please do *NOT* reply to this message. The return address is not valid.
270 Use <a href="/contact/form">the contact form</a> instead.
272 This message is sent to confirm the private e-mail address for community user
273 \"$username\".
275 Please click (or cut and paste into your browser) the following link to
276 confirm your account and e-mail address:
278 http://$host/user/confirm?username=$username&confirm=$confirm_code
280 Thank you.
281 Sol Genomics Network
282 END_HEREDOC
284 CXGN::Contact::send_email($subject, $body, $private_email);
287 sub reset_password :Path('/ajax/user/reset_password') Args(0) {
288 my $self = shift;
289 my $c = shift;
291 my $email = $c->req->param('password_reset_email');
293 my @person_ids = CXGN::People::Login->get_login_by_email($c->dbc->dbh(), $email);
295 print STDERR Dumper(\@person_ids);
296 if (!@person_ids) {
297 $c->stash->{rest} = { error => "The provided email ($email) is not associated with any account." };
298 return;
301 if (@person_ids > 1) {
302 $c->stash->{rest} = { message => "The provided email ($email) is associated with multiple accounts. An email is sent for each account. Please notify the database team using the contact form to consolidate the accounts." };
305 my $reset_link = "";
306 foreach my $pid (@person_ids) {
307 print STDERR "Now processing person with id $pid\n";
308 my $email_reset_token = $self->tempname();
309 $reset_link = $c->req->hostname()."/user/reset_password_form?token=$email_reset_token";
310 my $person = CXGN::People::Login->new( $c->dbc->dbh(), $pid);
311 $person->update_confirm_code($email_reset_token);
312 print STDERR "Sending reset link $reset_link\n";
313 $self->send_reset_email_message($c, $pid, $email, $reset_link);
316 $c->stash->{rest} = { message => "Reset link sent. Please check your email and click on the link." };
319 sub process_reset_password_form :Path('/ajax/user/process_reset_password') Args(0) {
320 my $self = shift;
321 my $c = shift;
323 my $token = $c->req->param("token");
324 my $new_password = $c->req->param("");
326 eval {
327 my $sp_person_id = CXGN::People::Login->get_login_by_token($c->dbc->dbh, $token);
329 my $login = CXGN::People::Login->new($c->dbc->dbh(), $sp_person_id);
330 $login->update_password($new_password);
331 $login->update_confirm_code("");
333 if ($@) {
334 $c->stash->{rest} = { error => $@ };
336 else {
337 $c->stash->{rest} = { message => "The password was successfully updated." };
343 sub send_reset_email_message {
344 my $self = shift;
345 my $c = shift;
346 my $pid = shift;
347 my $private_email = shift;
348 my $reset_link = shift;
350 my $subject = "[SGN] E-mail Address Confirmation Request";
353 my $body = <<END_HEREDOC;
355 Please do *NOT* reply to this message.
356 Use <a href="/contact/form">the contact form</a> to contact us instead.
358 Your password can be reset using the following link:
360 Please click (or cut and paste into your browser) the following link to
361 confirm your account and e-mail address:
363 $reset_link
365 Thank you.
366 Sol Genomics Network
367 END_HEREDOC
369 CXGN::Contact::send_email($subject, $body, $private_email);
372 sub tempname {
373 my $self = shift;
374 my $rand_string = "";
375 my $dev_urandom = new IO::File "</dev/urandom" || print STDERR "Can't open /dev/urandom";
376 $dev_urandom->read( $rand_string, 16 );
377 my @bytes = unpack( "C16", $rand_string );
378 $rand_string = "";
379 foreach (@bytes) {
380 $_ %= 62;
381 if ( $_ < 26 ) {
382 $rand_string .= chr( 65 + $_ );
384 elsif ( $_ < 52 ) {
385 $rand_string .= chr( 97 + ( $_ - 26 ) );
387 else {
388 $rand_string .= chr( 48 + ( $_ - 52 ) );
391 return $rand_string;
394 sub get_login_button_html :Path('/ajax/user/login_button_html') Args(0) {
395 my $self = shift;
396 my $c = shift;
397 eval {
398 my $production_site = $c->config->{main_production_site_url};
399 print STDERR "Get login button... site: $production_site\n";
400 if ($c->user()) {
401 print STDERR "Detected logged in users...\n";
403 else {
404 print STDERR "No logged in user found!\n";
406 my $html = "";
407 # if the site is a mirror, gray out the login/logout links
408 if( $c->config->{'is_mirror'} ) {
409 print STDERR "generating login button for mirror site...\n";
410 $html = <<HTML;
411 <a style="line-height: 1.2; text-decoration: underline; background: none" href="$production_site" title="log in on main site">main site</a>
412 } elsif ( $c->config->{disable_login} ) {
413 <li class="dropdown">
414 <div class="btn-group" role="group" aria-label="..." style="height:34px; margin: 1px 0px 0px 0px" >
415 <button class="btn btn-primary disabled" type="button" style="margin: 7px 7px 0px 0px">Login</button>
416 </div>
417 </li>
419 HTML
421 } elsif ( $c->req->uri->path_query =~ "logout=yes") {
422 print STDERR "generating login button for logout...\n";
423 $html = <<HTML;
424 <li class="dropdown">
425 <div class="btn-group" role="group" aria-label="..." style="height:34px; margin: 1px 0px 0px 0px" >
426 <a href="/user/login">
427 <button class="btn btn-primary" type="button" style="margin: 7px 7px 0px 0px">Login</button>
428 </a>
429 </div>
430 </li>
431 HTML
433 } elsif ( $c->user_exists ) {
434 print STDERR "Generate login button for logged in user...\n";
435 my $sp_person_id = $c->user->get_object->get_sp_person_id;
436 my $username = $c->user->get_username();
437 $html = <<HTML;
438 <li>
439 <div class="btn-group" role="group" aria-label="..." style="height:34px; margin: 1px 3px 0px 0px">
440 <button id="navbar_profile" class="btn btn-primary" type="button" onclick='location.href="/solpeople/profile/$sp_person_id"' style="margin: 7px 0px 0px 0px" title="My Profile">$username></button>
441 <button id="navbar_lists" name="lists_link" class="btn btn-info" style="margin:7px 0px 0px 0px" type="button" title="Lists">
442 Lists <span class="glyphicon glyphicon-list-alt" ></span>
443 </button>
444 <button id="navbar_personal_calendar" name="personal_calendar_link" class="btn btn-primary" style="margin:7px 0px 0px 0px" type="button" title="Your Calendar">Calendar&nbsp;<span class="glyphicon glyphicon-calendar" ></span>
445 </button>
446 <button id="navbar_logout" class="btn btn-default glyphicon glyphicon-log-out" style="margin:6px 0px 0px 0px" type="button" onclick="location.href='/solpeople/login.pl?logout=yes';" title="Logout"></button>
447 </div>
448 </li>
449 HTML
451 } else {
452 print STDERR "generating regular login button..\n";
453 $html = <<HTML;
454 <li class="dropdown">
455 <div class="btn-group" role="group" aria-label="..." style="height:34px; margin: 1px 0px 0px 0px" >
456 <a href="/user/login">
457 <button class="btn btn-primary" type="button" style="margin: 7px 7px 0px 0px;">Login</button>
458 </a>
459 </div>
460 </li>
461 HTML
464 if ($@) {
465 print STDERR "ERROR: $@\n";
466 $c->stash->{rest} = { error => $@ };
468 return $c->stash->{rest} = { html => $html };