]> mj.ucw.cz Git - pciids.git/blob - PciIds/Html/Users.pm
Do the login by a redirect
[pciids.git] / PciIds / Html / Users.pm
1 package PciIds::Html::Users;
2 use strict;
3 use warnings;
4 use PciIds::Html::Util;
5 use PciIds::Html::Forms;
6 use PciIds::Email;
7 use PciIds::Users;
8 use PciIds::Address;
9 use CGI;
10 use CGI::Cookie;
11 use Apache2::Const qw(:common);
12 use Apache2::SubRequest;
13 use APR::Table;
14
15 use base 'Exporter';
16
17 our @EXPORT = qw(&checkLogin &notLoggedComplaint);
18
19 sub genRegisterForm( $$$$ ) {
20         my( $req, $args, $error, $values ) = @_;
21         genHtmlHead( $req, 'Register a new user', undef );
22         print "<div class='top'>\n";
23         print '<h1>Register a new user</h1>';
24         genLocMenu( $req, $args, [ [ 'Log in', 'login' ], [ 'Help', 'help', 'account' ] ] );
25         print "<div class='clear'></div></div>\n";
26         print '<div class="error">'.$error.'</div>' if( defined $error );
27         print '<form name="register" id="register" method="POST" action="">
28                 <table>';
29         genForm( [ [ 'Email:', 'text', 'email', 'maxlength="255"' ],
30                 [ '', 'submit', 'register', 'value="Register"' ] ], $values );
31         print '</table></form>';
32         genHtmlTail();
33         return OK;
34 }
35
36 sub registerForm( $$ ) {#Form for registering a new user
37         my( $req, $args ) = @_;
38         return genRegisterForm( $req, $args, undef, {} );
39 }
40
41 sub loginCheck( $$ ) {
42         my( $login, $tables ) = @_;
43         return undef if( ( not defined $login ) || ( $login eq '' ) );#empty login is ok
44         return 'Login too long' if( ( length $login ) > 50 );
45         return 'Login contains invalid characters' unless( $login =~ /^[-_a-zA-Z0-9]+$/ );
46         return 'This login already exists' if( $tables->hasLogin( $login ) );
47         return undef;
48 }
49
50 sub registerSubmit( $$$ ) {#A registration form has been submited
51         my( $req, $args, $tables ) = @_;
52         my( $data, $error ) = getForm( {
53                 'email' => sub {
54                         return emailCheck( shift, $tables );
55                 }
56         }, [] );
57         return genRegisterForm( $req, $args, $error, $data ) if( defined $error );
58         my $site = $req->hostname();
59         my $url = 'https://'.$req->hostname().setAddrPrefix( $req->uri(), 'mods' );
60         sendMail( $data->{'email'}, 'Confirm registration', "Someone, probably you, requested registration of this address\n".
61                 "for the $site site. If it wasn't you, please ignore this email message.\n".
62                 "\nOtherwise, please continue by filling in the form at this address:".
63                 "\n".$url.'?action=register-confirm?email='.$data->{'email'}.'?confirm='.emailConfirm( $data->{'email'} )."\n".
64                 "\nThank you\n".
65                 "\n(This is an autogenerated email, do not respond to it)" );
66         genHtmlHead( $req, 'Registration email sent', undef );
67         print "<div class='top'>\n";
68         print "<h1>Registration email sent</h1>\n";
69         genLocMenu( $req, $args, [ [ 'Log in', 'login' ], [ 'Help', 'help', 'account' ] ] );
70         print "<div class='clear'></div></div>\n";
71         print '<p>
72                         An email containing further information has been sent to you.
73                         Please follow these instruction to finish the registration process.';
74         genHtmlTail();
75         return OK;
76 }
77
78 sub genConfirmForm( $$$$ ) {
79         my( $req, $args, $error, $values ) = @_;
80         genHtmlHead( $req, 'Confirm registration', undef );
81         print "<div class='top'>\n";
82         print '<h1>Confirm registration</h1>';
83         genLocMenu( $req, $args, [ [ 'Register', 'register' ], [ 'Help', 'help', 'account' ] ] );
84         print "<div class='clear'></div></div>\n";
85         print '<div class="error">'.$error.'</div>' if( defined $error );
86         print '<p>Email address: '.encode( $values->{'email'} );
87         print '<form name="register-confirm" id="register-confirm" method="POST" action="">';
88         print '<div class="hidden"><p><input type="hidden" value="'.encode( $values->{'email'} ).'" name="email"><input type="hidden" value="'.encode( $values->{'confirm'} ).'" name="confirm"></div>';
89         print '<table>';
90         genForm( [ [ 'Login (Optional):', 'text', 'login', 'maxlength="50"' ],
91                 [ 'Password:', 'password', 'password' ],
92                 [ 'Confirm password:', 'password', 'confirm_password' ],
93                 [ '', 'submit', 'register', 'value=Register' ] ], $values );
94         print '</table></form>';
95         genHtmlTail();
96         return OK;
97 }
98
99 sub usedAddress( $$ ) {
100         my( $req, $args ) = @_;
101         genHtmlHead( $req, 'Used address', undef );
102         print "<div class='top'>\n";
103         print "<h1>Used address</h1>\n";
104         genLocMenu( $req, $args, [ [ 'Log in', 'login' ], [ 'Reset password', 'respass' ], [ 'Register', 'register' ], [ 'Help', 'help', 'account' ] ] );
105         print "<div class='clear'></div></div>\n";
106         print '<div class="error">
107                 <p>
108                         An account for this address is already registered.
109                         Please, reset or remember your password or start again with a different address.
110                 </div>';
111         genHtmlTail();
112         return 0;
113 }
114
115 sub checkRegHash( $$$$$ ) {
116         my( $req, $args, $tables, $email, $hash ) = @_;
117         if( ! checkConfirmHash( $email, $hash ) ) {
118                 genHtmlHead( $req, 'Invalid registration request', undef );
119                 print '<h1>Invalid registration request</h1>
120                         <div class="error">
121                         <p>
122                                 This registration request is invalid.
123                                 Are you sure you got it from the registration email?
124                         </div>';
125                 genHtmlTail();
126                 return 0;
127         } elsif( $tables->hasEmail( $email ) ) {
128                 return usedAddress( $req, $args );
129         } else {
130                 return 1;
131         }
132 }
133
134 sub confirmForm( $$$$ ) {
135         my( $req, $args, $tables, $auth ) = @_;
136         return HTTPRedirect( $req, 'https://'.$req->hostname().$req->uri().buildArgs( $args ) ) unless $auth->{'ssl'};
137         if( ! checkRegHash( $req, $args, $tables, $args->{'email'}, $args->{'confirm'} ) ) {
138                 return OK;
139         } else {
140                 return genConfirmForm( $req, $args, undef, $args );
141         }
142 }
143
144 sub passLenCheck( $ ) {
145         my( $pass ) = @_;
146         return ( ( length $pass ) >= 4 ) ? undef : 'Password must have at least 4 characters';
147 }
148
149 sub passSameCheck( $ ) {
150         my( $data ) = @_;
151         return ( ( ( defined $data->{'password'} ) != ( defined $data->{'confirm_password'} ) ) || ( ( defined $data->{'password'} ) && ( $data->{'password'} ne $data->{'confirm_password'} ) ) ) ? 'Passwords do not match' : undef;
152 }
153
154 sub confirmSubmit( $$$ ) {
155         my( $req, $args, $tables ) = @_;
156         my( $data, $error ) = getForm( {
157                 'email' => sub {
158                         return emailCheck( shift, $tables );
159                 },
160                 'confirm' => undef,
161                 'login' => sub {
162                         return loginCheck( shift, $tables );
163                 },
164                 'password' => \&passLenCheck,
165                 'confirm_password' => undef }, [ \&passSameCheck ] );
166         return OK if( ! checkRegHash( $req, $args, $tables, $data->{'email'}, $data->{'confirm'} ) );#Not much info, but this is an attack anyway
167         return genConfirmForm( $req, $args, $error, $data ) if( defined $error );
168         unless( addUser( $tables, $data->{'login'}, $data->{'email'}, $data->{'password'} ) ) {
169                 usedAddress( $req, $args );
170                 return OK;
171         }
172         genHtmlHead( $req, 'Registered', undef );
173         print "<div class='top'>\n";
174         print "<h1>Registered</h1>\n";
175         genLocMenu( $req, $args, [ [ 'Log in', 'login' ], [ 'Help', 'help', 'account' ] ] );
176         print "<div class='clear'></div></div>\n";
177         print '<p>
178                         You have registered successfully.';
179         genHtmlTail();
180         return OK;
181 }
182
183 sub genLoginForm( $$$$ ) {
184         my( $req, $args, $error, $values ) = @_;
185         $req->headers_out->add( 'Set-Cookie' => new CGI::Cookie( -name => 'cookie-test', -value => 1 ) );
186         genHtmlHead( $req, 'Login', undef );
187         print "<div class='top'>\n";
188         print '<h1>Login</h1>';
189         genLocMenu( $req, $args, [ [ 'Register', 'register' ], [ 'Reset password', 'respass' ], [ 'Help', 'help', 'account' ] ] );
190         print "<div class='clear'></div></div>\n";
191         print '<div class="error"><p>'.$error.'</div>' if( defined $error );
192         print '<form name="login" id="login" method="POST" action="'.setAddrPrefix( $req->uri(), 'mods' ).buildExcept( 'action', $args ).'?action=login"><table>';
193         genForm( [ [ 'Login name or email:', 'text', 'login', 'maxlength="255"' ],
194                 [ 'Password:', 'password', 'password' ],
195                 [ '', 'submit', 'login', 'value="Login"' ] ], $values );
196         print '</table></form>';
197         genHtmlTail();
198         return OK;
199 }
200
201 sub loginForm( $$$ ) {
202         my( $req, $args, $tables, $auth ) = @_;
203         return HTTPRedirect( $req, 'https://'.$req->hostname().$req->uri().buildArgs( $args ) ) unless( $auth->{'ssl'} );
204         return genLoginForm( $req, $args, undef, {} );
205 }
206
207 sub loginSubmit( $$$ ) {
208         my( $req, $args, $tables ) = @_;
209         my( $data, $error ) = getForm( {
210                 'login' => undef,
211                 'password' => undef
212         }, [] );
213         my $logged = 0;
214         my $cookies = fetch CGI::Cookie;
215         unless( $cookies->{'cookie-test'} ) {
216                 return genLoginForm( $req, $args, 'You need to enable cookies', $data );
217         }
218         my( $id, $passwd, $email, $last ) = $tables->getLogInfo( $data->{'login'} );
219         if( defined $passwd && defined $data->{'password'} ) {
220                 my $salted = saltedPasswd( $email, $data->{'password'} );
221                 $logged = $salted eq $passwd;
222         }
223         if( $logged ) {
224                 $req->err_headers_out->add( 'Set-Cookie' => new CGI::Cookie( -name => 'auth', -value => genAuthToken( $tables, $id, $req, undef, $email ) ) );
225                 $args->{'action'} = ( defined $args->{'redirectaction'} && $args->{'redirectaction'} ne '' ) ? $args->{'redirectaction'} : 'list';
226                 my $url = 'http://'.$req->hostname().setAddrPrefix( $req->uri(), $args->{'action'} eq 'list' ? 'read' : 'mods' ).buildExcept( 'redirectaction', $args );
227                 return HTTPRedirect( $req, $url );
228         } else {
229                 return genLoginForm( $req, $args, 'Invalid login credetials', $data );
230         }
231 }
232
233 sub logout( $$ ) {
234         my( $req, $args ) = @_;
235         $req->err_headers_out->add( 'Set-Cookie' => new CGI::Cookie( -name => 'auth', -value => '0' ) );
236         return HTTPRedirect( $req, 'http://'.$req->hostname().setAddrPrefix( $req->uri(), 'read' ).buildExcept( 'action', $args ) );
237 }
238
239 sub checkLogin( $$ ) {
240         my( $req, $tables ) = @_;
241         my $cookies = fetch CGI::Cookie;
242         my $cookie = $cookies->{'auth'};
243         my( $authed, $id, $regen, $rights, $error, $name ) = checkAuthToken( $tables, $req, defined( $cookie ) ? $cookie->value : undef );
244         if( $regen ) {
245                 $req->headers_out->add( 'Set-Cookie' => new CGI::Cookie( -name => 'auth', -value => genAuthToken( $tables, $id, $req, $rights, $name ) ) );
246         }
247         my $hterror = $authed ? '' : '<div class="error"><p>'.$error.'</div>';
248         return { 'authid' => $authed ? $id : undef, 'accrights' => $rights, 'logerror' => $hterror, 'name' => $authed ? $name : undef };
249 }
250
251 sub notLoggedComplaint( $$$ ) {
252         my( $req, $args, $auth ) = @_;
253         return HTTPRedirect( $req, 'https://'.$req->hostname().$req->uri().buildArgs( $args ) ) unless $auth->{'ssl'};
254         $args->{'redirectaction'} = $args->{'action'};
255         return genLoginForm( $req, $args, 'This action requires you to be logged in', undef );
256 }
257
258 sub genResetPasswdForm( $$$$ ) {
259         my( $req, $args, $error, $values ) = @_;
260         genHtmlHead( $req, 'Reset password', undef );
261         print "<div class='top'>\n";
262         print "<h1>Reset password</h1>\n";
263         genLocMenu( $req, $args, [ [ 'Log in', 'login' ], [ 'Register', 'register' ], [ 'Help', 'help', 'account' ] ] );
264         print "<div class='clear'></div></div>\n";
265         print "<p>If you forgot your password (or didn't create one yet), you can reset it to a new value here.\n";
266         print "Provide your email address here and further instructions will be sent to you.\n";
267         print '<div class="error">'.$error.'</div>' if( defined $error );
268         print '<form name="respass" id="respass" method="POST" action="">
269                 <table>';
270         genForm( [ [ 'Email:', 'text', 'email', 'maxlength="255"' ],
271                 [ '', 'submit', 'respass', 'value="Send"' ] ], $values );
272         print '</table></form>';
273         genHtmlTail();
274         return OK;
275 }
276
277 sub resetPasswdForm( $$$$ ) {
278         my( $req, $args ) = @_;
279         return genResetPasswdForm( $req, $args, undef, {} );
280 }
281
282 sub resetPasswdFormSubmit( $$$ ) {
283         my( $req, $args, $tables ) = @_;
284         my( $data, $error ) = getForm( {
285                 'email' => undef
286         }, [] );
287         my( $id, $login, $passwd ) = $tables->resetInfo( $data->{'email'} );
288         if( defined( $id ) ) {
289                 $login = '' unless( defined( $login ) );
290                 my $site = $req->hostname();
291                 my $url = 'https://'.$req->hostname().setAddrPrefix( $req->uri(), 'mods' );
292                 my $hash = genResetHash( $id, $data->{'email'}, $login, $passwd );
293                 sendMail( $data->{'email'}, 'Reset password',
294                         "A request to reset password for the $site site was received for this address\n".
295                         "If you really wish to get a new password, visit this link:\n\n".
296                         $url.'?action=respass-confirm?email='.$data->{'email'}.'?confirm='.$hash."\n".
297                         "\n\nThank you\n".
298                         "\n(This is an autogenerated email, do not respond to it)" );
299                 genHtmlHead( $req, 'Reset password', undef );
300                 print "<div class='top'>\n";
301                 print "<h1>Reset password</h1>\n";
302                 genLocMenu( $req, $args, [ [ 'Log in', 'login' ], [ 'Help', 'help', 'account' ] ] );
303                 print "<div class='clear'></div></div>\n";
304                 print "<p>An email with information has been sent to your address.\n";
305                 genHtmlTail();
306                 return OK;
307         } else {
308                 $error = '<p>This email address is not registered. Check it for typos or register it.';
309         }
310         return genResetPasswdForm( $req, $args, $error, $data ) if( defined( $error ) );
311 }
312
313 sub genResetPasswdConfigForm( $$$$$$ ) {
314         my( $req, $args, $error, $values, $email, $hash ) = @_;
315         genHtmlHead( $req, 'Reset password', undef );
316         print "<div class='top'>\n";
317         print "<h1>Reset password</h1>\n";
318         genLocMenu( $req, $args, [ [ 'Log in', 'login' ], [ 'Help', 'help', 'account' ] ] );
319         print "<div class='clear'></div></div>\n";
320         print '<div class="error">'.$error.'</div>' if( defined $error );
321         print "<p>You can enter new password here:\n";
322         print '<form name="respass-confirm" id="respass-confirm" method="POST" action="">
323                 <table>';
324         genForm( [ [ 'Password:', 'password', 'password' ],
325                 [ 'Confirm password:', 'password', 'confirm_password' ],
326                 [ '', 'submit', 'respass', 'value="Send"' ] ], $values );
327         print "</table>";
328         print "<input type='hidden' name='email' value='".encode( $email )."'><input type='hidden' name='hash' value='".encode( $hash )."'>\n";
329         print "</form>\n";
330         genHtmlTail();
331         return OK;
332 }
333
334 sub resetPasswdConfirmForm( $$$$ ) {
335         my( $req, $args, $tables, $auth ) = @_;
336         my( $email, $hash ) = ( $args->{'email'}, $args->{'confirm'} );
337         my( $id, $login, $passwd ) = $tables->resetInfo( $email );
338         my $myHash;
339         return HTTPRedirect( $req, 'https://'.$req->hostname().$req->uri().buildArgs( $args ) ) unless $auth->{'ssl'};
340         $myHash = genResetHash( $id, $email, $login, $passwd ) if( defined( $id ) );
341         if( defined( $myHash ) && ( $myHash eq $hash ) ) {#Ok, it is his mail and he asked
342                 return genResetPasswdConfigForm( $req, $args, undef, {}, $email, $hash );
343         } else {
344                 genHtmlHead( $req, 'Reset password', undef );
345                 print "<h1>Reset password</h1>\n";
346                 print "<p>Provided link is not valid. Did you use it already?\n";
347                 print "<p>You can get a <a href='".$req->uri()."?action=respass'>new one</a>.\n";
348                 genHtmlTail();
349                 return OK;
350         }
351 }
352
353 sub resetPasswdConfirmFormSubmit( $$$ ) {
354         my( $req, $args, $tables ) = @_;
355         my( $data, $error ) = getForm( {
356                 'password' => \&passLenCheck,
357                 'confirm_password' => undef,
358                 'email' => undef,
359                 'hash' => undef
360         }, [ \&passSameCheck ] );
361         my( $email, $hash ) = ( $data->{'email'}, $args->{'confirm'} );
362         if( defined( $error ) ) {
363                 return genResetPasswdConfigForm( $req, $args, $error, $data, $email, $hash );
364         } else {
365                 my( $id, $login, $passwd ) = $tables->resetInfo( $email );
366                 my $myHash;
367                 $myHash = genResetHash( $id, $email, $login, $passwd ) if( defined( $id ) );
368                 if( defined( $myHash ) && ( $myHash eq $hash ) ) {
369                         changePasswd( $tables, $id, $data->{'password'}, $email );
370                         genHtmlHead( $req, 'Reset password', undef );
371                         print "<div class='top'>\n";
372                         print "<h1>Reset password</h1>\n";
373                         genLocMenu( $req, $args, [ [ 'Log in', 'login' ], [ 'Help', 'help', 'account' ] ] );
374                         print "<div class='clear'></div></div>\n";
375                         print "<p>Your password was successfuly changed.\n";
376                         genHtmlTail();
377                         return OK;
378                 } else {
379                         return genResetPasswdConfigForm( $req, $args, $error, $data, $email, $hash );
380                 }
381         }
382 }
383
384 sub genProfileForm( $$$$$$ ) {
385         my( $req, $args, $auth, $error, $data, $info ) = @_;
386         genHtmlHead( $req, 'User profile', undef );
387         delete $data->{'current_password'};
388         delete $data->{'confirm_password'};
389         delete $data->{'password'};
390         print "<div class='top'>\n";
391         print "<h1>User profile</h1>\n";
392         genLocMenu( $req, $args, [ logItem( $auth ), [ 'Notifications', 'notifications' ], [ 'Help', 'help', 'profile' ] ] );
393         print "<div class='clear'></div></div>\n";
394         print '<div class="error"><p>'.$error.'</div>' if defined $error;
395         print "<div class='info'><p>$info</div>\n" if defined $info;
396         print '<form name="profile" id="profile" method="POST" action=""><table>';
397         genForm( [ [ 'Email:', 'text', 'email', 'maxlength="255"' ],
398                 [ 'Login:', 'text', 'login', 'maxlength="50"' ],
399                 [ 'Xmpp:', 'text', 'xmpp', 'maxlength="255"' ],
400                 [ 'New password:', 'password', 'password' ],
401                 [ 'Confirm password:', 'password', 'confirm_password' ],
402                 [ 'Current password:', 'password', 'current_password' ],
403                 [ 'Email batch time (min):', 'text', 'email_time', 'maxlength="10"' ],
404                 [ 'Xmpp batch time (min):', 'text', 'xmpp_time', 'maxlength="10"' ],
405                 [ '', 'submit', 'profile', 'value="Submit"' ] ], $data );
406         print '</table></form>';
407         genHtmlTail();
408         return OK;
409 }
410
411 sub profileForm( $$$$ ) {
412         my( $req, $args, $tables, $auth ) = @_;
413         return notLoggedComplaint( $req, $args, $auth ) unless defined $auth->{'authid'};
414         return HTTPRedirect( $req, 'https://'.$req->hostname().$req->uri().buildArgs( $args ) ) unless $auth->{'ssl'};
415         return genProfileForm( $req, $args, $auth, undef, $tables->profileData( $auth->{'authid'} ), undef );
416 }
417
418 sub checkNum( $$ ) {
419         my( $value, $name ) = @_;
420         return ( "$name has invalid number format", '0' ) unless ( $value =~ /\d+/ );
421         return undef;
422 }
423
424 sub profileFormSubmit( $$$$ ) {
425         my( $req, $args, $tables, $auth ) = @_;
426         return notLoggedComplaint( $req, $args, $auth ) unless defined $auth->{'authid'};
427         my $oldData = $tables->profileData( $auth->{'authid'} );
428         my( $data, $error ) = getForm( {
429                 'email' => sub {
430                         my $email = shift;
431                         return undef if ( defined $email ) && ( $email eq $oldData->{'email'} );
432                         return emailCheck( $email, $tables );
433                 },
434                 'login' => sub {
435                         my $login = shift;
436                         $login = undef if ( defined $login ) && ( $login eq '' );
437                         return undef if ( defined $login ) && ( defined $oldData->{'login'} ) && ( $oldData->{'login'} eq $login );
438                         return ( undef, $login ) if ( !defined $login ) && ( !defined $oldData->{'login'} );
439                         return loginCheck( $login, $tables );
440                 },
441                 'xmpp' => sub {
442                         my $xmpp = shift;
443                         return ( undef, undef ) if ( !defined $xmpp ) || ( $xmpp eq '' );
444                         return "Xmpp address limit is 255" if length $xmpp > 255;
445                         return "Invalid Xmpp address" unless $xmpp =~ /^([^'"\@<>\/]+\@)?[^\@'"<>\/]+(\/.*)?/;
446                         return undef;
447                 },
448                 'password' => sub {
449                         my $passwd = shift;
450                         $passwd = undef if ( defined $passwd ) && ( $passwd eq '' );
451                         return ( undef, undef ) unless defined $passwd;
452                         return passLenCheck( $passwd );
453                 },
454                 'confirm_password' => undef,
455                 'current_password' => undef,
456                 'email_time' => sub {
457                         return checkNum( shift, "Email batch time" );
458                 },
459                 'xmpp_time' => sub {
460                         return checkNum( shift, "Xmpp batch time" );
461                 }
462         }, [ sub {
463                 my $data = shift;
464                 return undef unless defined $data->{'password'};
465                 return passSameCheck( $data );
466         }, sub {
467                 my $data = shift;
468                 my $change = 0;
469                 $change = 1 if $data->{'email'} ne $oldData->{'email'};
470                 $change = 1 if ( ( ( defined $data->{'login'} ) != ( defined $oldData->{'login'} ) ) || ( ( defined $data->{'login'} ) && ( defined $oldData->{'login'} ) && ( $data->{'login'} ne $oldData->{'login'} ) ) );
471                 $change = 1 if ( defined $data->{'password'} ) && ( $data->{'password'} ne '' );
472                 return undef unless $change;
473                 my $logged = 0;
474                 my( $id, $passwd, $email, $last ) = $tables->getLogInfo( $oldData->{'email'} );
475                 if( defined $passwd && defined $data->{'current_password'} ) {
476                         my $salted = saltedPasswd( $email, $data->{'current_password'} );
477                         $logged = ( $salted eq $passwd ) && ( $id == $auth->{'authid'} );
478                 }
479                 return "You need to provide correct current password to change email, login or password" unless $logged;
480                 return undef;
481         } ] );
482         return genProfileForm( $req, $args, $auth, $error, $data, undef ) if defined $error;
483         pushProfile( $tables, $auth->{'authid'}, $oldData, $data );
484         return genProfileForm( $req, $args, $auth, undef, $data, "Profile updated." );
485 }
486
487 1;