1616
1717package org .labkey .signup ;
1818
19+ import jakarta .mail .MessagingException ;
1920import org .apache .commons .lang3 .StringUtils ;
2021import org .apache .commons .validator .routines .EmailValidator ;
2122import org .apache .logging .log4j .LogManager ;
@@ -528,7 +529,8 @@ public class BeginAction extends FormViewAction<SignupForm>
528529 @ Override
529530 public void validateCommand (SignupForm form , Errors errors )
530531 {
531- validateSignupForm (form , errors );
532+ // All validation happens in handlePost so the order matches SignUpApiAction
533+ // (captcha first, then blank-field checks, then email parsing, etc.).
532534 }
533535
534536 @ Override
@@ -560,54 +562,39 @@ public ModelAndView getView(SignupForm form, boolean reshow, BindException error
560562 @ Override
561563 public boolean handlePost (SignupForm signupForm , BindException errors ) throws Exception
562564 {
563- // Validate with EmailValidator first. ValidEmail(email) will not throw an exception if the domain is
564- // missing from the email. The default domain configured for the server is appended.
565- EmailValidator validator = EmailValidator .getInstance ();
566- if (!validator .isValid (signupForm .getEmail ()))
565+ String kaptchaError = verifyCaptcha (signupForm .getKaptchaText (), signupForm .getEmail ());
566+ if (kaptchaError != null )
567567 {
568- errors .reject (ERROR_MSG ,"'" + signupForm . getEmail () + "' is not a valid email address." );
568+ errors .reject (ERROR_MSG , kaptchaError );
569569 return false ;
570570 }
571571
572- ValidEmail email ;
573- try
572+ validateSignupForm ( signupForm , errors ) ;
573+ if ( errors . hasErrors ())
574574 {
575- email = new ValidEmail ( signupForm . getEmail ()) ;
575+ return false ;
576576 }
577- catch (ValidEmail .InvalidEmailException iee )
577+
578+ ValidEmail email = parseAndValidateEmail (signupForm , errors );
579+ if (email == null )
578580 {
579- errors .reject (ERROR_MSG , iee .getMessage ());
580581 return false ;
581582 }
582583
583- if (UserManager .userExists (email ))
584+ if (UserManager .userExists (email ))
584585 {
585586 // If the user already exists forward them to a page where they can click on a link to recover their password, if required
586587 signupForm .setAccountExists (true );
587588 signupForm .setNewSignUp (false );
588589 return false ;
589590 }
590591
591- // If the user does not exit in LabKey's core database, check in our temporaryusers table
592- TempUser tempUser = getTempUser (signupForm , email );
593-
594- // Send email to the user.
595- ActionURL confirmationUrl = getConfirmationURL (getContainer (), email , tempUser .getKey ());
596- try
592+ if (!createUserAndSendEmail (signupForm , email , errors ))
597593 {
598- User mockUser = new User ();
599- mockUser .setEmail (email .getEmailAddress ());
600- SecurityManager .sendEmail (getContainer (), mockUser , SecurityManager .getRegistrationMessage (null , false ), email .getEmailAddress (), confirmationUrl );
601- }
602- catch (Exception e )
603- {
604- String systemEmail = LookAndFeelProperties .getInstance (getContainer ()).getSystemEmailAddress ();
605- errors .reject (ERROR_MSG , "Could not send new user registration email. Please contact your server administrator at " + systemEmail );
606- errors .reject (ERROR_MSG , e .getMessage ());
607594 return false ;
608595 }
609596
610-
597+ // Re-render the JSP with the CONFIRMATION_SENT message.
611598 signupForm .setNewSignUp (false );
612599 return false ;
613600 }
@@ -642,6 +629,96 @@ private void validateSignupForm(SignupForm form, Errors errors)
642629 {
643630 errors .reject (ERROR_MSG , "Email cannot be blank." );
644631 }
632+ if (StringUtils .isBlank (form .getEmailConfirm ()))
633+ {
634+ errors .reject (ERROR_MSG , "Confirm email cannot be blank." );
635+ }
636+ else if (form .getEmail () != null && !form .getEmail ().equals (form .getEmailConfirm ()))
637+ {
638+ errors .reject (ERROR_MSG , "Email addresses do not match." );
639+ }
640+ }
641+
642+ // On success returns null and clears the session attribute so the captcha cannot be replayed.
643+ // On failure return a a user-facing error message.
644+ // Logging matches LoginController's RegisterUserAction.
645+ private String verifyCaptcha (String submittedText , String emailForLogging )
646+ {
647+ var session = getViewContext ().getRequest ().getSession (true );
648+ String expected = (String ) session .getAttribute (LabKeyKaptchaServlet .SESSION_KEY_VALUE );
649+ if (expected == null )
650+ {
651+ _log .info ("Captcha not initialized for signup attempt" );
652+ return "Captcha not initialized, please retry." ;
653+ }
654+ if (!expected .equalsIgnoreCase (StringUtils .trimToNull (submittedText )))
655+ {
656+ _log .warn ("Captcha text did not match for signup attempt for " + emailForLogging );
657+ return "Verification text does not match, please retry." ;
658+ }
659+ session .removeAttribute (LabKeyKaptchaServlet .SESSION_KEY_VALUE );
660+ return null ;
661+ }
662+
663+ // Returns a parsed ValidEmail, or null if the address is invalid (errors populated).
664+ // Uses EmailValidator first because ValidEmail's constructor does not throw on bare
665+ // strings like "foo" - it silently appends the server's default domain.
666+ private ValidEmail parseAndValidateEmail (SignupForm form , Errors errors )
667+ {
668+ EmailValidator validator = EmailValidator .getInstance ();
669+ if (!validator .isValid (form .getEmail ()))
670+ {
671+ errors .reject (ERROR_MSG , "'" + form .getEmail () + "' is not a valid email address." );
672+ return null ;
673+ }
674+ try
675+ {
676+ return new ValidEmail (form .getEmail ());
677+ }
678+ catch (ValidEmail .InvalidEmailException iee )
679+ {
680+ errors .reject (ERROR_MSG , iee .getMessage ());
681+ return null ;
682+ }
683+ }
684+
685+ // Creates (or reuses) a TempUser row and sends the confirmation email in a single
686+ // transaction. On send failure the transaction is rolled back so a freshly inserted
687+ // TempUser row does not persist when the user never received a confirmation link.
688+ private boolean createUserAndSendEmail (SignupForm form , ValidEmail email , Errors errors ) throws java .sql .SQLException
689+ {
690+ try (DbScope .Transaction transaction = SignUpManager .getSchema ().getScope ().ensureTransaction ())
691+ {
692+ TempUser tempUser = getTempUser (form , email );
693+ ActionURL confirmationUrl = getConfirmationURL (getContainer (), email , tempUser .getKey ());
694+ try
695+ {
696+ User mockUser = new User ();
697+ mockUser .setEmail (email .getEmailAddress ());
698+ SecurityManager .sendEmail (getContainer (), mockUser ,
699+ SecurityManager .getRegistrationMessage (null , false ),
700+ email .getEmailAddress (), confirmationUrl );
701+ }
702+ catch (MessagingException e )
703+ {
704+ String systemEmail = LookAndFeelProperties .getInstance (getContainer ()).getSystemEmailAddress ();
705+ errors .reject (ERROR_MSG , "Could not send new user registration email. Please contact your server administrator at " + systemEmail );
706+ if (e .getMessage () != null )
707+ {
708+ errors .reject (ERROR_MSG , e .getMessage ());
709+ }
710+ return false ;
711+ }
712+ transaction .commit ();
713+ return true ;
714+ }
715+ }
716+
717+ private static List <String > errorsToMessages (Errors errors )
718+ {
719+ return errors .getAllErrors ().stream ()
720+ .map (e -> e .getDefaultMessage ())
721+ .toList ();
645722 }
646723
647724 public static ActionURL getConfirmationURL (Container c , ValidEmail email , String key )
@@ -658,6 +735,7 @@ public static class SignupForm extends ReturnUrlForm
658735 private String _lastName ;
659736 private String _organization ;
660737 private String _email ;
738+ private String _emailConfirm ;
661739 private boolean _accountExists ;
662740 private boolean _newSignUp = true ;
663741
@@ -701,6 +779,16 @@ public void setEmail(String email)
701779 _email = email ;
702780 }
703781
782+ public String getEmailConfirm ()
783+ {
784+ return _emailConfirm ;
785+ }
786+
787+ public void setEmailConfirm (String emailConfirm )
788+ {
789+ _emailConfirm = emailConfirm ;
790+ }
791+
704792 public boolean isAccountExists ()
705793 {
706794 return _accountExists ;
@@ -791,72 +879,40 @@ public ApiResponse execute(SignupForm signupForm, BindException errors) throws E
791879 {
792880 ApiSimpleResponse response = new ApiSimpleResponse ();
793881
794- String expectedKaptcha = (String ) getViewContext ().getRequest ().getSession (true )
795- .getAttribute (LabKeyKaptchaServlet .SESSION_KEY_VALUE );
796- if (expectedKaptcha == null )
882+ String kaptchaError = verifyCaptcha (signupForm .getKaptchaText (), signupForm .getEmail ());
883+ if (kaptchaError != null )
797884 {
798- _log .info ("Captcha not initialized for signup attempt" );
799- response .put ("error_message" , List .of ("Captcha not initialized, please retry." ));
885+ response .put ("error_message" , List .of (kaptchaError ));
800886 return response ;
801887 }
802- if (!expectedKaptcha .equalsIgnoreCase (StringUtils .trimToNull (signupForm .getKaptchaText ())))
888+
889+ validateSignupForm (signupForm , errors );
890+ if (errors .hasErrors ())
803891 {
804- _log .warn ("Captcha text did not match for signup attempt for " + signupForm .getEmail ());
805- response .put ("error_message" , List .of ("Verification text does not match, please retry." ));
892+ response .put ("error_message" , errorsToMessages (errors ));
806893 return response ;
807894 }
808- getViewContext ().getRequest ().getSession (true ).removeAttribute (LabKeyKaptchaServlet .SESSION_KEY_VALUE );
809895
810- ValidEmail email ;
811- try
812- {
813- email = new ValidEmail (signupForm .getEmail ());
814- }
815- catch (ValidEmail .InvalidEmailException iee )
896+ ValidEmail email = parseAndValidateEmail (signupForm , errors );
897+ if (email == null )
816898 {
817- response .put ("error_message" , List . of ( iee . getMessage () ));
899+ response .put ("error_message" , errorsToMessages ( errors ));
818900 return response ;
819901 }
820902
821- if (UserManager .userExists (email ))
903+ if (UserManager .userExists (email ))
822904 {
823905 response .put ("status" , "USER_EXISTS" );
824906 return response ;
825907 }
826908
827- validateSignupForm (signupForm , errors );
828- if (errors .hasErrors ())
909+ if (!createUserAndSendEmail (signupForm , email , errors ))
829910 {
830- List <String > messages = errors .getAllErrors ().stream ()
831- .map (e -> e .getDefaultMessage ())
832- .toList ();
833- response .put ("error_message" , messages );
911+ response .put ("error_message" , errorsToMessages (errors ));
834912 return response ;
835913 }
836914
837- TempUser tempUser = getTempUser (signupForm , email );
838-
839-
840- // Send email to the user.
841- ActionURL confirmationUrl = getConfirmationURL (getContainer (), email , tempUser .getKey ());
842- try
843- {
844- User mockUser = new User ();
845- mockUser .setEmail (email .getEmailAddress ());
846- SecurityManager .sendEmail (getContainer (), mockUser , SecurityManager .getRegistrationMessage (null , false ), email .getEmailAddress (), confirmationUrl );
847- }
848- catch (Exception e )
849- {
850- String systemEmail = LookAndFeelProperties .getInstance (getContainer ()).getSystemEmailAddress ();
851- List <String > messages = new ArrayList <>();
852- messages .add ("Could not send new user registration email. Please contact your server administrator at " + systemEmail );
853- messages .add (e .getMessage ());
854- response .put ("error_message" , messages );
855- }
856-
857- signupForm .setNewSignUp (false ); // TODO: Most likely not needed here
858915 response .put ("status" , "USER_ADDED" );
859-
860916 return response ;
861917 }
862918 }
0 commit comments