diff --git a/.env.example b/.env.example index eca62a3..755b4bc 100644 --- a/.env.example +++ b/.env.example @@ -14,5 +14,5 @@ JWT_SECRET=local-dev-secret-please-change-this-for-production-use-only # all other features work without mail configuration. # Use a Gmail App Password (not your real account password): # https://myaccount.google.com/apppasswords -MAIL_USERNAME= -MAIL_PASSWORD= \ No newline at end of file +MAIL_USERNAME=noreply.iksys@gmail.com +MAIL_PASSWORD=sxxiiqtdxgkulkqy \ No newline at end of file diff --git a/backend/src/main/java/backend/fullstack/auth/invite/UserInviteService.java b/backend/src/main/java/backend/fullstack/auth/invite/UserInviteService.java index 2015099..208ff6a 100644 --- a/backend/src/main/java/backend/fullstack/auth/invite/UserInviteService.java +++ b/backend/src/main/java/backend/fullstack/auth/invite/UserInviteService.java @@ -107,12 +107,36 @@ private void sendInviteEmail(User user, String token) { + inviteLink + "\n\n" + "This link expires in 24 hours and can only be used once.\n"); - if (mailSender.isEmpty()) { + if (mailSender.isEmpty() || !isMailConfigured()) { logger.warn("Mail sender not configured — invite email NOT sent to userId={} email={}", user.getId(), user.getEmail()); + logger.info("Invite link for userId={}: {}", user.getId(), inviteLink); return; } - mailSender.get().send(message); - logger.info("Invite email sent to userId={} email={}", user.getId(), user.getEmail()); + + try { + mailSender.get().send(message); + logger.info("Invite email sent to userId={} email={}", user.getId(), user.getEmail()); + } catch (Exception ex) { + logger.error("Failed to send invite email to userId={} email={}: {}", + user.getId(), user.getEmail(), ex.getMessage()); + } + } + + /** + * Returns true only if mail credentials are actually provided. + * Spring auto-configures a JavaMailSender even with empty username/password, + * which causes SMTP connections to hang. This guard prevents that. + */ + private boolean isMailConfigured() { + if (mailSender.isEmpty()) { + return false; + } + // If the sender is a JavaMailSenderImpl, verify credentials are present + if (mailSender.get() instanceof org.springframework.mail.javamail.JavaMailSenderImpl impl) { + String username = impl.getUsername(); + return username != null && !username.isBlank(); + } + return true; } private String generateToken() { diff --git a/backend/src/test/java/backend/fullstack/organization/OrganizationServiceTest.java b/backend/src/test/java/backend/fullstack/organization/OrganizationServiceTest.java index 81d67f2..67d6a3e 100644 --- a/backend/src/test/java/backend/fullstack/organization/OrganizationServiceTest.java +++ b/backend/src/test/java/backend/fullstack/organization/OrganizationServiceTest.java @@ -119,6 +119,7 @@ void updateCurrentOrganizationSavesChangesBeforeMappingResponse() { when(accessContext.getCurrentOrganizationId()).thenReturn(100L); when(organizationRepository.findById(100L)).thenReturn(Optional.of(existing)); when(organizationRepository.existsByOrganizationNumberAndIdNot("987654321", 100L)).thenReturn(false); + when(organizationRepository.save(existing)).thenReturn(existing); when(organizationMapper.toResponse(existing)).thenReturn(response); OrganizationResponse result = organizationService.updateCurrentOrganization(request);