From 31bdd2bad1730e20b8d012be46934e384f4ce8e1 Mon Sep 17 00:00:00 2001 From: Student Apnacollege Date: Mon, 24 Aug 2026 01:00:15 +0530 Subject: [PATCH] Fix signup error handling #795 --- activity/tests/test_registration.py | 104 ++++++++++++ activity/util.py | 46 ++++-- activity/views.py | 154 ++++++++++-------- static/js/auth/user_register.js | 235 +++++++++++----------------- 4 files changed, 321 insertions(+), 218 deletions(-) create mode 100644 activity/tests/test_registration.py diff --git a/activity/tests/test_registration.py b/activity/tests/test_registration.py new file mode 100644 index 00000000..ad4f42eb --- /dev/null +++ b/activity/tests/test_registration.py @@ -0,0 +1,104 @@ +from unittest.mock import patch +from django.test import TestCase, Client +from django.contrib.auth.models import User +from django.urls import reverse +from workflow.models import Organization, UserInvite + + +class RegistrationFlowTest(TestCase): + def setUp(self): + self.client = Client() + self.register_url = reverse('register', kwargs={'invite_uuid': 'none'}) + + def test_registration_page_renders(self): + response = self.client.get(self.register_url) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'registration/register.html') + + def test_successful_signup_flow(self): + data = { + 'first_name': 'Jane', + 'last_name': 'Doe', + 'username': 'janedoe', + 'email_address': 'janedoe@example.com', + 'password': 'Password123!', + 'confirm_password': 'Password123!', + } + with patch('activity.views.send_single_mail') as mock_email: + mock_email.return_value = 1 + response = self.client.post(self.register_url, data) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'registration/confirm_email.html') + self.assertTrue(User.objects.filter(username='janedoe').exists()) + user = User.objects.get(username='janedoe') + self.assertFalse(user.is_active) + self.assertEqual(user.email, 'janedoe@example.com') + + def test_signup_duplicate_email_shows_friendly_message(self): + User.objects.create_user( + username='existinguser', + email='duplicate@example.com', + password='Password123!' + ) + data = { + 'first_name': 'Another', + 'last_name': 'User', + 'username': 'newuser', + 'email_address': 'duplicate@example.com', + 'password': 'Password123!', + 'confirm_password': 'Password123!', + } + response = self.client.post(self.register_url, data) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'registration/register.html') + self.assertIn('message_email', response.context) + self.assertIn('email already exists', response.context['message_email'].lower()) + + def test_signup_duplicate_username_shows_friendly_message(self): + User.objects.create_user( + username='takenusername', + email='first@example.com', + password='Password123!' + ) + data = { + 'first_name': 'Another', + 'last_name': 'User', + 'username': 'takenusername', + 'email_address': 'second@example.com', + 'password': 'Password123!', + 'confirm_password': 'Password123!', + } + response = self.client.post(self.register_url, data) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'registration/register.html') + self.assertIn('message_username', response.context) + self.assertIn('username already exists', response.context['message_username'].lower()) + + def test_signup_missing_required_fields_shows_error_message(self): + data = { + 'first_name': '', + 'last_name': '', + 'username': '', + 'email_address': '', + 'password': '', + 'confirm_password': '', + } + response = self.client.post(self.register_url, data) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'registration/register.html') + + def test_signup_mail_failure_handling(self): + data = { + 'first_name': 'Test', + 'last_name': 'EmailFail', + 'username': 'emailfailuser', + 'email_address': 'fail@example.com', + 'password': 'Password123!', + 'confirm_password': 'Password123!', + } + with patch('activity.views.send_single_mail', side_effect=Exception('SMTP Connection Error')): + response = self.client.post(self.register_url, data) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'registration/register.html') + # User should be cleaned up on failure + self.assertFalse(User.objects.filter(username='emailfailuser').exists()) diff --git a/activity/util.py b/activity/util.py index 9eae9bcd..a2413b0a 100755 --- a/activity/util.py +++ b/activity/util.py @@ -135,7 +135,7 @@ def in_groups(u): def group_required(*group_names, url): # Requires user membership in at least one of the groups passed in. def in_groups(u): - if u.is_authenticated(): + if u.is_authenticated: if bool(u.groups.filter(name__in=group_names)) | u.is_superuser: return True raise PermissionDenied @@ -236,7 +236,13 @@ def send_invite_emails(subject, email_from, email_to, data): msg.send() -def send_single_mail(subject, email_from, email_to, data, email_txt, email_html): +import logging +from django.conf import settings + +logger = logging.getLogger(__name__) + + +def send_single_mail(subject, email_from, email_to, data, email_txt, email_html, fail_silently=False): """ Send single email :param subject: email subject @@ -245,27 +251,36 @@ def send_single_mail(subject, email_from, email_to, data, email_txt, email_html) :param data: context data :param email_txt: text email template :param email_html: html email template + :param fail_silently: whether to suppress exceptions """ email_context = data - email_txt = loader.render_to_string(email_txt, email_context) - email_html = loader.get_template(email_html) - email_html_content = email_html.render(email_context) + email_txt_content = loader.render_to_string(email_txt, email_context) + email_html_template = loader.get_template(email_html) + email_html_content = email_html_template.render(email_context) + + sender = getattr(settings, 'DEFAULT_FROM_EMAIL', None) or email_from or 'team.hikaya@gmail.com' + if '<' not in sender: + from_email_header = 'Hikaya <{}>'.format(sender) + else: + from_email_header = sender msg = EmailMultiAlternatives( subject, - email_txt, - 'Hikaya <{}>'.format(email_from), + email_txt_content, + from_email_header, email_to ) msg.attach_alternative(email_html_content, "text/html") - msg.send() + return msg.send(fail_silently=fail_silently) def user_signup_notification(user): url = os.environ.get('SLACK_REGISTRATION_WEBHOOK') + if not url: + return message = ("A new user has signed up on activity") title = ("New User Sign Up :zap:") - date = user.date_joined.strftime('%d-%m-%Y') + date = user.date_joined.strftime('%d-%m-%Y') if user.date_joined else '' slack_data = { "username": "new-user-notification", @@ -294,8 +309,11 @@ def user_signup_notification(user): }, ] } - byte_length = str(sys.getsizeof(slack_data)) - headers = {'Content-Type': "application/json", 'Content-Length': byte_length} - response = requests.post(url, data=json.dumps(slack_data), headers=headers) - if response.status_code != 200: - raise Exception(response.status_code, response.text) + headers = {'Content-Type': "application/json"} + try: + response = requests.post(url, data=json.dumps(slack_data), headers=headers, timeout=5) + if response.status_code != 200: + logger.warning("Slack notification returned non-200 status: %s %s", response.status_code, response.text) + except Exception as e: + logger.warning("Slack registration webhook failed: %s", e) + diff --git a/activity/views.py b/activity/views.py index cd62c36c..ee4f408c 100755 --- a/activity/views.py +++ b/activity/views.py @@ -135,6 +135,11 @@ def switch_organization(request, org_id): return redirect('/') +import logging + +logger = logging.getLogger(__name__) + + def activate_acccount(request, uidb64, token): try: uid = force_text(urlsafe_base64_decode(uidb64)) @@ -146,10 +151,16 @@ def activate_acccount(request, uidb64, token): user.save() # send welcome mail - send_welcome_email(request, user) + try: + send_welcome_email(request, user) + except Exception as e: + logger.warning("Failed to send welcome email on account activation: %s", e) # send slack notification - user_signup_notification(user) + try: + user_signup_notification(user) + except Exception as e: + logger.warning("Failed to send slack notification on account activation: %s", e) # login(request, user) messages.success( @@ -190,71 +201,83 @@ def register(request, invite_uuid): # privacy = ActivitySites.objects.get(id=1) if request.method == 'POST': data = request.POST - first_name = data.get('first_name') - last_name = data.get('last_name') - username = data.get('username') + first_name = data.get('first_name', '').strip() + last_name = data.get('last_name', '').strip() + username = data.get('username', '').strip() + email_raw = data.get('email_address', '').strip() # always change the mail address to LowerCase - email = data.get('email_address').lower() + email = email_raw.lower() if email_raw else '' password = data.get('password') - try: - if User.objects.get(email=email): - invite_uuid = set_invite_uuid(invite_uuid) - invite_uuid['message_email'] = 'email already exists !' - return render(request, 'registration/register.html', invite_uuid) + if not first_name or not username or not email or not password: + messages.error(request, 'Please ensure you fill in all required fields') + return render(request, 'registration/register.html', set_invite_uuid(invite_uuid)) - except User.DoesNotExist: - try: - user = User.objects.create_user( - first_name=first_name, - last_name=last_name, - username=username, - email=email, - password=password, - is_active=False, - ) - except IntegrityError: - invite_uuid = set_invite_uuid(invite_uuid) - invite_uuid['message_username'] = 'username already exists !' - return render(request, 'registration/register.html', invite_uuid) + if User.objects.filter(email=email).exists(): + context = set_invite_uuid(invite_uuid) + context['message_email'] = 'email already exists !' + return render(request, 'registration/register.html', context) + + try: + user = User.objects.create_user( + first_name=first_name, + last_name=last_name, + username=username, + email=email, + password=password, + is_active=False, + ) + except IntegrityError: + context = set_invite_uuid(invite_uuid) + context['message_username'] = 'username already exists !' + return render(request, 'registration/register.html', context) - except ValueError: - messages.error(request, 'Please ensure you fill in all required fields') - return render(request, 'registration/register.html') + except ValueError: + messages.error(request, 'Please ensure you fill in all required fields') + return render(request, 'registration/register.html', set_invite_uuid(invite_uuid)) if user: if user_invite is not None: # activate invited user accounts user.is_active = True user.save() - activity_user = ActivityUser.objects.create( + activity_user, _ = ActivityUser.objects.get_or_create( user=user, - organization_id=user_invite.organization.id, - name='{} {}'.format(user.first_name, user.last_name) + defaults={ + 'organization_id': user_invite.organization.id, + 'name': '{} {}'.format(user.first_name, user.last_name) + } ) + if not activity_user.organization_id: + activity_user.organization = user_invite.organization + activity_user.save() # add organization to user organizations activity_user.organizations.add(user_invite.organization) # define user organization access groups - user_org_access = ActivityUserOrganizationGroup.objects.create( + group, _ = Group.objects.get_or_create(name='Editor') + user_org_access, _ = ActivityUserOrganizationGroup.objects.get_or_create( activity_user=activity_user, organization=user_invite.organization, + defaults={'group': group} ) - # set default permission to editor on invite - group = Group.objects.get(name='Editor') - user_org_access.group = group - user_org_access.save() - if activity_user: - # delete the invitation - user_invite.delete() + if user_org_access.group != group: + user_org_access.group = group + user_org_access.save() + + # delete the invitation + user_invite.delete() - # send welcome email + # send welcome email + try: send_welcome_email(request, user) + except Exception as e: + logger.warning("Failed to send welcome email: %s", e) - messages.success( - request, 'Thanks, your email address has been confirmed') - return render(request, 'registration/login.html', {'invite_uuid': 'none'}) + messages.success( + request, 'Thanks, your email address has been confirmed') + return render(request, 'registration/login.html', {'invite_uuid': 'none'}) else: mail_subject = 'Please confirm your email address' @@ -266,40 +289,40 @@ def register(request, invite_uuid): } email_txt = 'emails/registration/email_confirmation.txt' email_html = 'emails/registration/email_confirmation.html' + email_from = getattr(settings, 'DEFAULT_FROM_EMAIL', 'team.hikaya@gmail.com') or 'team.hikaya@gmail.com' try: send_single_mail( mail_subject, - 'team.hikaya@gmail.com', + email_from, [email], data, email_txt, email_html ) - except SMTPRecipientsRefused: + except Exception as e: + logger.warning("Failed to send confirmation email: %s", e) # delete user if an email can't be sent user.delete() messages.error( request, - 'We can not confirmation mail, please check email address and try again', + 'We cannot send confirmation email, please check email address and try again', fail_silently=True ) - return render(request, 'registration/register.html') + return render(request, 'registration/register.html', set_invite_uuid(invite_uuid)) - activity_user = ActivityUser.objects.create( + activity_user, _ = ActivityUser.objects.get_or_create( user=user, - name='{} {}'.format(user.first_name, user.last_name) + defaults={'name': '{} {}'.format(user.first_name, user.last_name)} ) if activity_user: return render(request, 'registration/confirm_email.html') else: - return render(request, 'registration/register.html') + return render(request, 'registration/register.html', set_invite_uuid(invite_uuid)) else: - invite_uuid = set_invite_uuid(invite_uuid) - return render(request, 'registration/register.html', invite_uuid) @@ -308,10 +331,11 @@ def send_welcome_email(request, user): data = {'user': user, 'domain': request.build_absolute_uri('/').strip('/')} email_txt = 'emails/registration/welcome.txt' email_html = 'emails/registration/welcome.html' + email_from = getattr(settings, 'DEFAULT_FROM_EMAIL', 'team.hikaya@gmail.com') or 'team.hikaya@gmail.com' send_single_mail( mail_subject, - 'team.hikaya@gmail.com', + email_from, [user.email], data, email_txt, @@ -346,7 +370,7 @@ def post(self, request, *args, **kwargs): username = data.get('username', None) password = data.get('password', None) # check if user is active - user = User.objects.filter(Q(username__iexact=username) | Q(email__iexact=username.lower())).first() + user = User.objects.filter(Q(username__iexact=username) | Q(email__iexact=username.lower() if username else '')).first() if not user: messages.error(request, 'Incorrect credentials.', fail_silently=True) return render(request, 'registration/login.html') @@ -362,8 +386,11 @@ def post(self, request, *args, **kwargs): return render(request, 'registration/login.html') login(request, user) - activity_user = ActivityUser.objects.filter(user=user).first() - if activity_user.organization: + activity_user, _ = ActivityUser.objects.get_or_create( + user=user, + defaults={'name': '{} {}'.format(user.first_name, user.last_name)} + ) + if activity_user and activity_user.organization: return HttpResponseRedirect('/') return HttpResponseRedirect('/accounts/register/organization') @@ -391,20 +418,25 @@ def post(self, request, *args, **kwargs): activity_url=activity_url ) if org: - user = ActivityUser.objects.get(user=request.user) + user, _ = ActivityUser.objects.get_or_create( + user=request.user, + defaults={'name': '{} {}'.format(request.user.first_name, request.user.last_name)} + ) # add organization to the current user user.organization = org user.save() user.organizations.add(org) # define user organization access groups - user_org_access = ActivityUserOrganizationGroup.objects.create( + group, _ = Group.objects.get_or_create(name='Owner') + user_org_access, _ = ActivityUserOrganizationGroup.objects.get_or_create( activity_user=user, organization=org, + defaults={'group': group} ) - group = Group.objects.get(name='Owner') - user_org_access.group = group - user_org_access.save() + if user_org_access.group != group: + user_org_access.group = group + user_org_access.save() return redirect('/') else: diff --git a/static/js/auth/user_register.js b/static/js/auth/user_register.js index cc6d1f0c..a014ae37 100644 --- a/static/js/auth/user_register.js +++ b/static/js/auth/user_register.js @@ -1,112 +1,25 @@ $(document).ready(function() { - // Reset form - $('#registerForm').trigger('reset'); let validInputs = []; - validate(); - if ($('#register_email_address').val()) { - validInputs.indexOf('email') === -1 ? validInputs.push('email') : ''; - } - - if ($('#register_username').val()) { - validInputs.indexOf('username') === -1 ? validInputs.push('username') : ''; + function isValidEmail(email) { + var regex = /^([a-zA-Z0-9_.+-])+\@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+$/; + return regex.test(email); } - // required fields - $('#register_first_name').on('input focus keyup', function() { - const firstName = $(this); - if (firstName.val()) { - validInputs.indexOf('first_name') === -1 ? validInputs.push('first_name') : ''; - $('#div_first_name') - .removeClass('has-error') - .addClass('has-success'); - } else { - validInputs = validInputs.filter(item => item !== 'first_name'); - $('#div_first_name') - .removeClass('has-success') - .addClass('has-error'); - } - validate(); - }); - - $('#register_username').on('input focus', function() { - const username = $(this); - $('#usernameHelpBlock').addClass('hikaya-hide'); - if (username.val()) { - validInputs.indexOf('username') === -1 ? validInputs.push('username') : ''; - - $('#div_username') - .removeClass('has-error') - .addClass('has-success'); - } else { - validInputs = validInputs.filter(item => item !== 'username'); - $('#div_username') - .removeClass('has-success') - .addClass('has-error'); - } - validate(); - }); - - // validate email address - $('#register_email_address').on('input focus keyup', function() { - const emailAddressInput = $(this); - const emailAddress = emailAddressInput.val(); - if (emailAddress && isValidEmail(emailAddress)) { - validInputs.indexOf('email') === -1 ? validInputs.push('email') : ''; - $('#div_email_address') - .removeClass('has-error') - .addClass('has-success'); - $('#emailHelpBlock') - .removeClass('hikaya-show') - .addClass('hikaya-hide'); - } else if (emailAddress && !isValidEmail(emailAddress)) { - validInputs = validInputs.filter(item => item !== 'email'); - $('#div_email_address') - .removeClass('has-success') - .addClass('has-error'); - $('#emailHelpBlock') - .removeClass('hikaya-hide') - .addClass('hikaya-show'); + function validate() { + const isValid = ['first_name', 'email', 'username', 'password'].every(item => { + return validInputs.indexOf(item) >= 0; + }); + if (isValid) { + $('#register_submit_btn').removeAttr('disabled'); } else { - validInputs = validInputs.filter(item => item !== 'email'); - $('#div_email_address') - .removeClass('has-success') - .addClass('has-error'); - $('#emailHelpBlock') - .removeClass('hikaya-hide') - .addClass('hikaya-show'); - } - validate(); - }); - - $('#register_password').on('keyup focus input', function() { - const password = $(this); - const confirmPassword = $('#register_confirm_password').val(); - $('#passwordHelpBlock').html(checkStrength(password.val())); - if (confirmPassword) { - if (confirmPassword !== password.val()) { - validInputs = validInputs.filter(item => item !== 'password'); - $('#div_confirm_password') - .removeClass('has-success') - .addClass('has-error'); - $('#confirmPasswordHelpBlock').html('Passwords do not match'); - $('#confirmPasswordHelpBlock').removeClass('hikaya-hide'); - } else { - validInputs.indexOf('password') === -1 ? validInputs.push('password') : ''; - $('#div_confirm_password') - .removeClass('has-error') - .addClass('has-success'); - $('#confirmPasswordHelpBlock').addClass('hikaya-hide'); - } + $('#register_submit_btn').attr('disabled', 'disabled'); } - validate(); - }); + } function checkStrength(password) { - //initial strength var strength = 0; - //if the password length is less than 6, return message. if (password.length < 6) { $('#div_password') .removeClass('has-success') @@ -115,26 +28,12 @@ $(document).ready(function() { return 'Password too short'; } - // length is ok, continue. - - // if length is 8 characters or more, increase strength value if (password.length > 7) strength += 1; - - // if password contains both lower and uppercase characters, increase strength value if (password.match(/([a-z].*[A-Z])|([A-Z].*[a-z])/)) strength += 1; - - //if it has numbers and characters, increase strength value if (password.match(/([a-zA-Z])/) && password.match(/([0-9])/)) strength += 1; - - //if it has one special character, increase strength value if (password.match(/([!,%,&,@,#,$,^,*,?,_,~])/)) strength += 1; - - //if it has two special characters, increase strength value if (password.match(/(.*[!,%,&,@,#,$,^,*,?,_,~].*[!,",%,&,@,#,$,^,*,?,_,~])/)) strength += 1; - //now we have calculated strength value, we can return messages - - //if value is less than 2 if (strength < 2) { $('#div_password') .removeClass('has-success') @@ -149,6 +48,7 @@ $(document).ready(function() { return 'Good'; } else { $('#div_password') + .removeClass('error') .removeClass('has-error') .removeClass('has-warning') .addClass('has-success'); @@ -156,43 +56,92 @@ $(document).ready(function() { } } - // confirm password - $('#register_confirm_password').on('keyup focus', function() { - const confirmPasswordInput = $(this); - const confirmPassword = confirmPasswordInput.val(); - const password = $('#register_password').val(); - if (password && confirmPassword !== password) { - validInputs = validInputs.filter(item => item !== 'password'); - $('#div_confirm_password') - .removeClass('has-success') - .addClass('has-error'); - $('#confirmPasswordHelpBlock').html('Passwords do not match'); - $('#confirmPasswordHelpBlock').removeClass('hikaya-hide'); - validate(); + function validateFirstName() { + const firstName = $('#register_first_name').val(); + if (firstName && firstName.trim().length > 0) { + if (validInputs.indexOf('first_name') === -1) validInputs.push('first_name'); + $('#div_first_name').removeClass('has-error').addClass('has-success'); } else { - validInputs.indexOf('password') === -1 ? validInputs.push('password') : ''; - $('#div_confirm_password') - .removeClass('has-error') - .addClass('has-success'); - $('#confirmPasswordHelpBlock').addClass('hikaya-hide'); - validate(); + validInputs = validInputs.filter(item => item !== 'first_name'); + if (firstName !== undefined && firstName.length > 0) { + $('#div_first_name').removeClass('has-success').addClass('has-error'); + } } - }); + validate(); + } - // Additional helper methods - function isValidEmail(email) { - var regex = /^([a-zA-Z0-9_.+-])+\@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+$/; - return regex.test(email); + function validateUsername() { + const username = $('#register_username').val(); + $('#usernameHelpBlock').addClass('hikaya-hide'); + if (username && username.trim().length > 0) { + if (validInputs.indexOf('username') === -1) validInputs.push('username'); + $('#div_username').removeClass('has-error').addClass('has-success'); + } else { + validInputs = validInputs.filter(item => item !== 'username'); + if (username !== undefined && username.length > 0) { + $('#div_username').removeClass('has-success').addClass('has-error'); + } + } + validate(); } - function validate() { - const isValid = ['first_name', 'email', 'username', 'password'].every(item => { - return validInputs.indexOf(item) >= 0; - }); - if (isValid) { - $('#register_submit_btn').removeAttr('disabled'); + function validateEmail() { + const emailAddress = $('#register_email_address').val(); + if (emailAddress && isValidEmail(emailAddress)) { + if (validInputs.indexOf('email') === -1) validInputs.push('email'); + $('#div_email_address').removeClass('has-error').addClass('has-success'); + $('#emailHelpBlock').removeClass('hikaya-show').addClass('hikaya-hide'); } else { - $('#register_submit_btn').attr('disabled', 'disabled'); + validInputs = validInputs.filter(item => item !== 'email'); + if (emailAddress && emailAddress.length > 0) { + $('#div_email_address').removeClass('has-success').addClass('has-error'); + $('#emailHelpBlock').removeClass('hikaya-hide').addClass('hikaya-show'); + } + } + validate(); + } + + function validatePasswords() { + const password = $('#register_password').val() || ''; + const confirmPassword = $('#register_confirm_password').val() || ''; + + if (password.length > 0) { + $('#passwordHelpBlock').html(checkStrength(password)); + } else { + $('#passwordHelpBlock').html(''); + $('#div_password').removeClass('has-success has-warning has-error'); + } + + if (confirmPassword.length > 0 || password.length > 0) { + if (password.length >= 6 && confirmPassword === password) { + if (validInputs.indexOf('password') === -1) validInputs.push('password'); + $('#div_confirm_password').removeClass('has-error').addClass('has-success'); + $('#confirmPasswordHelpBlock').addClass('hikaya-hide').html(''); + } else { + validInputs = validInputs.filter(item => item !== 'password'); + if (confirmPassword.length > 0) { + $('#div_confirm_password').removeClass('has-success').addClass('has-error'); + $('#confirmPasswordHelpBlock').removeClass('hikaya-hide').html('Passwords do not match'); + } + } + } else { + validInputs = validInputs.filter(item => item !== 'password'); + $('#div_confirm_password').removeClass('has-success has-error'); + $('#confirmPasswordHelpBlock').addClass('hikaya-hide').html(''); } + validate(); } + + // Attach event handlers + $('#register_first_name').on('input focus keyup change', validateFirstName); + $('#register_username').on('input focus keyup change', validateUsername); + $('#register_email_address').on('input focus keyup change', validateEmail); + $('#register_password').on('input focus keyup change', validatePasswords); + $('#register_confirm_password').on('input focus keyup change', validatePasswords); + + // Check initial values + validateFirstName(); + validateUsername(); + validateEmail(); + validatePasswords(); });