diff --git a/db/1.0.0/18. createRaidInstancePersonCharacterXref.sql b/db/1.0.0/18. createRaidInstancePersonCharacterXref.sql index fa4f50b..fdb1411 100644 --- a/db/1.0.0/18. createRaidInstancePersonCharacterXref.sql +++ b/db/1.0.0/18. createRaidInstancePersonCharacterXref.sql @@ -1,7 +1,7 @@ CREATE TABLE raid_builder.raid_instance_person_character_xref( raid_instance_person_character_xref_id uuid PRIMARY KEY, raid_instance_id uuid REFERENCES raid_builder.raid_instance(raid_instance_id) NOT NULL, - person_character_id uuid REFERENCES raid_builder.person_character(person_character_id) NOT NULL, + person_character_id uuid REFERENCES raid_builder.person_character(person_character_id), group_number int NOT NULL, position_number int NOT NULL, diff --git a/pom.xml b/pom.xml index caa1112..2ebea78 100644 --- a/pom.xml +++ b/pom.xml @@ -140,6 +140,18 @@ disruptor 4.0.0 + + + + org.eclipse.angus + angus-mail + 2.0.3 + + + commons-codec + commons-codec + 1.18.0 + diff --git a/src/main/java/com/mattrixwv/raidbuilder/config/SecurityConfig.java b/src/main/java/com/mattrixwv/raidbuilder/config/SecurityConfig.java index b924272..7403ea1 100644 --- a/src/main/java/com/mattrixwv/raidbuilder/config/SecurityConfig.java +++ b/src/main/java/com/mattrixwv/raidbuilder/config/SecurityConfig.java @@ -46,8 +46,8 @@ public class SecurityConfig{ auth.requestMatchers(HttpMethod.OPTIONS).permitAll() .requestMatchers("/icons/**").permitAll() .requestMatchers("/auth/refresh", "/auth/test").permitAll() //Permit refresh tokens - .requestMatchers(HttpMethod.POST, "/auth/signup", "/auth/confirm").permitAll() //Permit signup operations - .requestMatchers("/auth/forgot", "/auth/forgot/*").permitAll() //Permit forgot password operations + .requestMatchers(HttpMethod.POST, "/auth/signup", "/auth/confirm/*").permitAll() //Permit signup operations + .requestMatchers(HttpMethod.POST, "/auth/forgot", "/auth/forgot/*").permitAll() //Permit forgot password operations .anyRequest().authenticated(); }) .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults())) diff --git a/src/main/java/com/mattrixwv/raidbuilder/controller/AuthenticationController.java b/src/main/java/com/mattrixwv/raidbuilder/controller/AuthenticationController.java index c7034f0..1feccb9 100644 --- a/src/main/java/com/mattrixwv/raidbuilder/controller/AuthenticationController.java +++ b/src/main/java/com/mattrixwv/raidbuilder/controller/AuthenticationController.java @@ -27,7 +27,9 @@ import com.mattrixwv.raidbuilder.entity.Account; import com.mattrixwv.raidbuilder.service.AccountService; import com.mattrixwv.raidbuilder.util.DatabaseTypeUtil.AccountPermissionType; import com.mattrixwv.raidbuilder.util.DatabaseTypeUtil.AccountStatus; +import com.mattrixwv.raidbuilder.util.EmailUtil; +import jakarta.mail.internet.InternetAddress; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -44,6 +46,8 @@ public class AuthenticationController{ private final PasswordEncoder passwordEncoder; private final TokenService tokenService; private final AccountService accountService; + //Email + private final EmailUtil emailUtil; @GetMapping("/token") @@ -121,8 +125,6 @@ public class AuthenticationController{ returnNode.put("accountId", account.getAccountId().toString()); returnNode.put("status", "success"); - //TODO: Send email - log.info("Successfully created account: {}", account.getAccountId()); } else{ @@ -136,10 +138,9 @@ public class AuthenticationController{ return returnNode; } - @PostMapping("/confirm") + @PostMapping("/confirm/{confirmToken}") @AccountAuthorization(permissions = {}) - public ObjectNode confirm(@RequestBody ObjectNode confirmNode){ - UUID confirmToken = UUID.fromString(confirmNode.get("confirmToken").asText()); + public ObjectNode confirm(@PathVariable("confirmToken") UUID confirmToken){ log.info("Confirming account with token {}", confirmToken); @@ -164,14 +165,14 @@ public class AuthenticationController{ @PostMapping("/forgot") @AccountAuthorization(permissions = {}) - public ObjectNode forgot(@RequestParam("loginId") String loginId){ - log.info("Setting up user that forgot their password: {}", loginId); + public ObjectNode forgot(@RequestParam("username") String username){ + log.info("Setting up user that forgot their password: {}", username); ObjectNode returnNode = mapper.createObjectNode(); //Verify the account exists - Account account = accountService.getByUsername(loginId); + Account account = accountService.getByUsername(username); if(account != null){ //Setup token UUID token = UUID.randomUUID(); @@ -181,24 +182,29 @@ public class AuthenticationController{ account = accountService.updateAccount(account); - //TODO: Send email + try{ + emailUtil.sendPasswordResetEmail(new InternetAddress(account.getEmail()), token); + } + catch(Exception error){ + log.error("Error sending email", error); + throw new RuntimeException("Error sending reset email", error); + } returnNode.put("status", "success"); } else{ ArrayNode errorsNode = mapper.createArrayNode(); - errorsNode.add("Could not find account with login " + loginId); + errorsNode.add("Could not find account with login " + username); returnNode.set("errors", errorsNode); } return returnNode; } - @PostMapping("/forgot/reset") + @PostMapping("/forgot/{forgotToken}") @AccountAuthorization(permissions = {}) - public ObjectNode setNewPasswordForgot(@RequestBody ObjectNode forgotNode){ - UUID forgotToken = UUID.fromString(forgotNode.get("forgotToken").asText()); - String newPassword = forgotNode.get("password").asText(); + public ObjectNode setNewPasswordForgot(@PathVariable("forgotToken") UUID forgotToken, @RequestBody ObjectNode passwordNode){ + String newPassword = passwordNode.get("password").asText(); log.info("Confirming user reset password (forget) with token {}", forgotToken); diff --git a/src/main/java/com/mattrixwv/raidbuilder/service/AccountService.java b/src/main/java/com/mattrixwv/raidbuilder/service/AccountService.java index 034311a..b413274 100644 --- a/src/main/java/com/mattrixwv/raidbuilder/service/AccountService.java +++ b/src/main/java/com/mattrixwv/raidbuilder/service/AccountService.java @@ -23,11 +23,15 @@ import com.mattrixwv.raidbuilder.repository.account.AccountRepository; import com.mattrixwv.raidbuilder.util.DatabaseTypeUtil.AccountPermissionType; import com.mattrixwv.raidbuilder.util.DatabaseTypeUtil.AccountStatus; import com.mattrixwv.raidbuilder.util.DatabaseTypeUtil.TutorialStatus; +import com.mattrixwv.raidbuilder.util.EmailUtil; import com.mattrixwv.raidbuilder.util.UserPrincipal; +import jakarta.mail.internet.InternetAddress; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +@Slf4j @Service @Transactional(rollbackFor = Exception.class) @RequiredArgsConstructor @@ -39,6 +43,8 @@ public class AccountService implements UserDetailsService{ private final AccountPermissionService accountPermissionService; private final GamePermissionService gamePermissionService; private final RaidGroupPermissionService raidGroupPermissionService; + //Emails + private final EmailUtil emailUtil; //Fields @Value("${jwt.refreshTokenDuration}") private Duration refreshTokenDuration; @@ -71,6 +77,16 @@ public class AccountService implements UserDetailsService{ accountTutorialStatus.setInstanceTutorialStatus(TutorialStatus.NOT_COMPLETED); accountTutorialStatus = accountTutorialStatusService.createAccountTutorialStatus(accountTutorialStatus); + + //Send confirmation email + try{ + emailUtil.sendConfirmationEmail(new InternetAddress(account.getEmail()), account.getRefreshToken()); + } + catch(Exception error){ + log.error(error.getMessage(), error); + throw new RuntimeException("Could not send confirmation email", error); + } + //Return the new account return account; } diff --git a/src/main/java/com/mattrixwv/raidbuilder/util/EmailUtil.java b/src/main/java/com/mattrixwv/raidbuilder/util/EmailUtil.java new file mode 100644 index 0000000..21f73d6 --- /dev/null +++ b/src/main/java/com/mattrixwv/raidbuilder/util/EmailUtil.java @@ -0,0 +1,74 @@ +package com.mattrixwv.raidbuilder.util; + + +import java.util.Properties; +import java.util.UUID; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import jakarta.mail.Message; +import jakarta.mail.MessagingException; +import jakarta.mail.Session; +import jakarta.mail.Transport; +import jakarta.mail.internet.InternetAddress; +import jakarta.mail.internet.MimeMessage; + + +@Component +public class EmailUtil{ + @Value("${email.username}") + private String username; + @Value("${email.password}") + private String password; + @Value("${email.from}") + private InternetAddress fromAddress; + + + public Session createSession(){ + Properties props = new Properties(); + props.put("mail.smtp.host", "smtp.gmail.com"); + props.put("mail.smtp.port", "465"); + props.put("mail.smtp.auth", "true"); + props.put("mail.smtp.socketFactory.port", "465"); + props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory"); + + return Session.getInstance(props, new jakarta.mail.Authenticator(){ + @Override + protected jakarta.mail.PasswordAuthentication getPasswordAuthentication(){ + return new jakarta.mail.PasswordAuthentication(username, password); + } + }); + } + + public void sendConfirmationEmail(InternetAddress toAddress, UUID confirmationToken) throws MessagingException{ + sendMessage( + createSession(), + toAddress, + fromAddress, + "Raid Builder Confirmation", + "

This is the confirmation email for your Raid Builder Account.
Click Here to confirm your account.

" + ); + } + + public void sendPasswordResetEmail(InternetAddress toAddress, UUID resetToken) throws MessagingException{ + sendMessage( + createSession(), + toAddress, + fromAddress, + "Raid Builder Password Reset", + "

This is the password reset email for your Raid Builder Account.
Click Here to reset your password.

" + ); + } + + + public static void sendMessage(Session session, InternetAddress toAddress, InternetAddress fromAddress, String subject, String body) throws MessagingException{ + MimeMessage message = new MimeMessage(session); + message.setFrom(fromAddress); + message.setRecipient(Message.RecipientType.TO, toAddress); + message.setSubject(subject); + message.setText(body, "utf-8", "html"); + + Transport.send(message); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 653ab35..3375097 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -23,3 +23,9 @@ jwt.refreshTokenDuration=30d #Files uploadFileDirectory=../raidBuilderIcons spring.servlet.multipart.max-file-size=10MB + + +#Email +email.username= +email.password= +email.from=