work in progress to encypt passwords and convert cgi-bin to MVC.
[sgn.git] / lib / SGN / Controller / AJAX / User.pm
blobcde2a5b33da9b5687b11523a18def9a7bbb25de0
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 new_account :Path('/ajax/user/new') Args(0) {
18 my $self = shift;
19 my $c = shift;
21 if ($c->conf->{is_mirror}) {
22 $c->stash->{template} = '/system_message.mas';
23 $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.";
24 return;
28 my ($first_name, $last_name, $username, $password, $confirm_password, $email_address, $organization)
29 = $c->req->param(qw(first_name last_name username password confirm_password email_address organization));
31 if ($username) {
33 # check password properties...
35 my @fail = ();
36 if (length($username) < 7) {
37 push @fail, "Username is too short. Username must be 7 or more characters";
38 } else {
39 # does user already exist?
41 my $existing_login = CXGN::People::Login -> get_login($c->dbc()->dbh(), $username);
43 if ($existing_login->get_username()) {
44 push @fail, "Username \"$username\" is already in use. Please pick a different username.";
48 if (length($password) < 7) {
49 push @fail, "Password is too short. Password must be 7 or more characters";
51 if ("$password" ne "$confirm_password") {
52 push @fail, "Password and confirm password do not match.";
55 if (!$organization) {
56 push @fail, "'Organization' is required.'";
59 if ($password eq $username) {
60 push @fail, "Password must not be the same as your username.";
62 if ($email_address !~ m/[^\@]+\@[^\@]+/) {
63 push @fail, "Email address is invalid.";
65 unless($first_name) {
66 push @fail,"You must enter a first name or initial.";
68 unless($last_name) {
69 push @fail,"You must enter a last name.";
72 if (@fail) {
73 $c->stash->{rest} = { error => "Account creation failed for the following reason(s): ".(join ", ", @fail) };
74 return;
80 my $confirm_code = $self->tempname();
81 my $new_user = CXGN::People::Login->new($c->dbc->dbh());
82 $new_user -> set_username($username);
83 $new_user -> set_password($password);
84 $new_user -> set_pending_email($email_address);
85 $new_user -> set_confirm_code($confirm_code);
86 $new_user -> set_disabled('unconfirmed account');
87 $new_user -> set_organization($organization);
88 $new_user -> store();
90 #this is being added because the person object still uses two different objects, despite the fact that we've merged the tables
91 my $person_id=$new_user->get_sp_person_id();
92 my $new_person=CXGN::People::Person->new($self->dbc->dbh(),$person_id);
93 $new_person->set_first_name($first_name);
94 $new_person->set_last_name($last_name);
95 $new_person->store();
97 my $host = $c->req()->hostname();
98 my $subject="[SGN] Email Address Confirmation Request";
99 my $body=<<END_HEREDOC;
101 Please do *NOT* reply to this message. The return address is not valid.
102 Use the <a href="/contact/form">contact form</a> instead.
104 This message is sent to confirm the email address for community user
105 \"$username\"
107 Please click (or cut and paste into your browser) the following link to
108 confirm your account and email address:
110 https://$host/solpeople/account-confirm.pl?username=$username&confirm=$confirm_code
112 Thank you,
113 Sol Genomics Network
115 END_HEREDOC
117 CXGN::Contact::send_email($subject,$body,$email_address);
118 $c->stash->{rest} = { message => qq | <table summary="" width="80%" align="center">
119 <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>
120 <tr><td><br /></td></tr>
121 </table>
122 | };
126 sub change_account_info_action :Path('/ajax/user/update') Args(0) {
127 my $self = shift;
128 my $c = shift;
130 if (! $c->user() ) {
131 $c->stash->{rest} = { error => "You must be logged in to use this page." };
132 return;
135 my $person = new CXGN::People::Login($c->dbc->dbh(), $c->user->get_sp_person_id());
137 # my ($current_password, $change_username, $change_password, $change_email) = $c->req->param({qw(current_password change_username change_password change_email)});
139 my $args = $c->req->params();
141 if (!$args->{change_password} && ! $args->{change_username} && !$args->{change_email}) {
142 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.";
143 print STDERR $error;
144 $c->stash->{rest} = { error => $error };
145 return;
148 print STDERR "Person = ".$person->get_username()."\n";
149 chomp($args->{current_password});
150 if (! $person->verify_password($args->{current_password})) {
151 my $error = "Your current password does not match SGN records.";
152 print STDERR $error;
153 $c->stash->{rest} = { error => "$error" };
154 return;
157 # Check for error conditions in all changes, before making any of them.
158 # Otherwise, we could end up making some changes and then failing on later
159 # ones. The user would then push the back button and their information may
160 # be different now but they will probably assume no changes were made. This
161 # is most troublesome if the current password changes.
163 if ($args->{change_username}) {
164 #unless change_username is set, new_username won't be in the args hash because of the prestore test
165 my $new_username = $args->{new_username};
166 if(length($new_username) < 7) {
167 my $error = "Username must be at least 7 characters long.";
168 print STDERR $error;
169 $c->stash->{rest} = { error => $error };
170 return;
173 my $other_user = CXGN::People::Login->get_login($c->dbc->dbh(), $new_username);
174 if (defined $other_user->get_sp_person_id() &&
175 ($person -> get_sp_person_id() != $other_user->get_sp_person_id())) {
176 print STDERR "Username alread in use.\n";
177 $c->stash->{rest} = { error => "Username \"$new_username\" is already in use. Please select a different username." };
178 return;
181 print STDERR "Saving new username args->{username} to the database...\n";
182 $person->set_username($new_username);
183 $person->store();
186 if ($args->{change_password}) {
187 #unless change_password is set, new_password won't be in the args hash because of the prestore test
188 my ($new_password, $confirm_password) = ($args->{new_password}, $args->{confirm_password});
189 if(length($args->{new_password}) < 7) {
190 print STDERR "Password too short\n";
191 $c->stash->{rest} = { error => "Passwords must be at least 7 characters long. Please try again." };
192 return;
194 #format check
195 if($args->{new_password} !~ /^[a-zA-Z0-9~!@#$^&*_.=:;<>?]+$/) {
196 print STDERR "Illegal characters in password\n";
197 $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>." };
198 return;
200 if($args->{new_password} ne $args->{confirm_password}) {
201 print STDERR "Password don't match.\n";
202 $c->stash->{rest} = { error => "New password entries do not match. You must enter your new password twice to verify accuracy." };
203 return;
206 print STDERR "Saving new password '$args->{new_password}' to the database\n";
207 $person->update_password($args->{new_password});
210 my $user_private_email = $c->user->get_private_email();
211 if($args->{change_email}) {
212 #unless change_email is set, private_email won't be in the args hash because of the prestore test
213 my ($private_email, $confirm_email) = ($args->{private_email}, $args->{confirm_email});
214 if($private_email !~ m/^[a-zA-Z0-9_.-]+@[a-zA-Z0-9_.-]+$/) {
215 print STDERR "Invalid email address\n";
216 $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." };
217 return;
219 if($private_email ne $confirm_email) {
220 print STDERR "Emails don't match\n";
221 $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." };
222 return;
225 print STDERR "Saving private email '$private_email' to the database\n";
226 $person->set_private_email($private_email);
227 my $confirm_code = $self->tempname();
228 $person->set_confirm_code($confirm_code);
229 $person->store();
231 $user_private_email = $private_email;
233 $self->send_confirmation_email($args->{username}, $user_private_email, $confirm_code, $c->req->hostname());
237 $c->stash->{rest} = { message => "Update successful" };
241 sub send_confirmation_email {
242 my ($self, $username, $private_email, $confirm_code, $host) = @_;
243 my $subject = "[SGN] E-mail Address Confirmation Request";
244 my $body = <<END_HEREDOC;
245 Please do *NOT* reply to this message. The return address is not valid.
246 Use <a href="/contact/form">the contact form</a> instead.
248 This message is sent to confirm the private e-mail address for community user
249 \"$username\".
251 Please click (or cut and paste into your browser) the following link to
252 confirm your account and e-mail address:
254 http://$host/user/confirm?username=$username&confirm=$confirm_code
256 Thank you.
257 Sol Genomics Network
258 END_HEREDOC
260 CXGN::Contact::send_email($subject, $body, $private_email);
263 sub reset_password :Path('/ajax/user/reset_password') Args(0) {
264 my $self = shift;
265 my $c = shift;
267 my $email = $c->req->param('password_reset_email');
269 my @person_ids = CXGN::People::Login->get_login_by_email($c->dbc->dbh(), $email);
271 print STDERR Dumper(\@person_ids);
272 if (!@person_ids) {
273 $c->stash->{rest} = { error => "The provided email ($email) is not associated with any account." };
274 return;
277 if (@person_ids > 1) {
278 $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." };
281 my $reset_link = "";
282 foreach my $pid (@person_ids) {
283 print STDERR "Now processing person with id $pid\n";
284 my $email_reset_token = $self->tempname();
285 $reset_link = $c->req->hostname()."/user/reset_password_form?token=$email_reset_token";
286 my $person = CXGN::People::Login->new( $c->dbc->dbh(), $pid);
287 $person->update_confirm_code($email_reset_token);
288 print STDERR "Sending reset link $reset_link\n";
289 $self->send_reset_email_message($c, $pid, $email, $reset_link);
292 $c->stash->{rest} = { message => "Reset link sent. Please check your email and click on the link." };
295 sub process_reset_password_form :Path('/user/process_reset_password') Args(0) {
296 my $self = shift;
297 my $c = shift;
299 my $token = $c->req->param("token");
300 my $new_password = $c->req->param("");
303 my $sp_person_id = CXGN::People::Login->get_login_by_token($c->dbc->dbh, $token);
309 sub send_reset_email_message {
310 my $self = shift;
311 my $c = shift;
312 my $pid = shift;
313 my $private_email = shift;
314 my $reset_link = shift;
316 my $subject = "[SGN] E-mail Address Confirmation Request";
319 my $body = <<END_HEREDOC;
321 Please do *NOT* reply to this message.
322 Use <a href="/contact/form">the contact form</a> to contact us instead.
324 Your password can be reset using the following link:
326 Please click (or cut and paste into your browser) the following link to
327 confirm your account and e-mail address:
329 $reset_link
331 Thank you.
332 Sol Genomics Network
333 END_HEREDOC
335 CXGN::Contact::send_email($subject, $body, $private_email);
338 sub tempname {
339 my $self = shift;
340 my $rand_string = "";
341 my $dev_urandom = new IO::File "</dev/urandom" || print STDERR "Can't open /dev/urandom";
342 $dev_urandom->read( $rand_string, 16 );
343 my @bytes = unpack( "C16", $rand_string );
344 $rand_string = "";
345 foreach (@bytes) {
346 $_ %= 62;
347 if ( $_ < 26 ) {
348 $rand_string .= chr( 65 + $_ );
350 elsif ( $_ < 52 ) {
351 $rand_string .= chr( 97 + ( $_ - 26 ) );
353 else {
354 $rand_string .= chr( 48 + ( $_ - 52 ) );
357 return $rand_string;