Confirm and password emails sending

This commit is contained in:
2025-03-22 23:47:17 -04:00
parent b6fb636ddd
commit 01fca599ac
7 changed files with 131 additions and 17 deletions

View File

@@ -1,7 +1,7 @@
CREATE TABLE raid_builder.raid_instance_person_character_xref( CREATE TABLE raid_builder.raid_instance_person_character_xref(
raid_instance_person_character_xref_id uuid PRIMARY KEY, raid_instance_person_character_xref_id uuid PRIMARY KEY,
raid_instance_id uuid REFERENCES raid_builder.raid_instance(raid_instance_id) NOT NULL, 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, group_number int NOT NULL,
position_number int NOT NULL, position_number int NOT NULL,

12
pom.xml
View File

@@ -140,6 +140,18 @@
<artifactId>disruptor</artifactId> <artifactId>disruptor</artifactId>
<version>4.0.0</version> <version>4.0.0</version>
</dependency> </dependency>
<!--! Email -->
<dependency>
<groupId>org.eclipse.angus</groupId>
<artifactId>angus-mail</artifactId>
<version>2.0.3</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.18.0</version>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@@ -46,8 +46,8 @@ public class SecurityConfig{
auth.requestMatchers(HttpMethod.OPTIONS).permitAll() auth.requestMatchers(HttpMethod.OPTIONS).permitAll()
.requestMatchers("/icons/**").permitAll() .requestMatchers("/icons/**").permitAll()
.requestMatchers("/auth/refresh", "/auth/test").permitAll() //Permit refresh tokens .requestMatchers("/auth/refresh", "/auth/test").permitAll() //Permit refresh tokens
.requestMatchers(HttpMethod.POST, "/auth/signup", "/auth/confirm").permitAll() //Permit signup operations .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/forgot", "/auth/forgot/*").permitAll() //Permit forgot password operations
.anyRequest().authenticated(); .anyRequest().authenticated();
}) })
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults())) .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))

View File

@@ -27,7 +27,9 @@ import com.mattrixwv.raidbuilder.entity.Account;
import com.mattrixwv.raidbuilder.service.AccountService; import com.mattrixwv.raidbuilder.service.AccountService;
import com.mattrixwv.raidbuilder.util.DatabaseTypeUtil.AccountPermissionType; import com.mattrixwv.raidbuilder.util.DatabaseTypeUtil.AccountPermissionType;
import com.mattrixwv.raidbuilder.util.DatabaseTypeUtil.AccountStatus; 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.Cookie;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
@@ -44,6 +46,8 @@ public class AuthenticationController{
private final PasswordEncoder passwordEncoder; private final PasswordEncoder passwordEncoder;
private final TokenService tokenService; private final TokenService tokenService;
private final AccountService accountService; private final AccountService accountService;
//Email
private final EmailUtil emailUtil;
@GetMapping("/token") @GetMapping("/token")
@@ -121,8 +125,6 @@ public class AuthenticationController{
returnNode.put("accountId", account.getAccountId().toString()); returnNode.put("accountId", account.getAccountId().toString());
returnNode.put("status", "success"); returnNode.put("status", "success");
//TODO: Send email
log.info("Successfully created account: {}", account.getAccountId()); log.info("Successfully created account: {}", account.getAccountId());
} }
else{ else{
@@ -136,10 +138,9 @@ public class AuthenticationController{
return returnNode; return returnNode;
} }
@PostMapping("/confirm") @PostMapping("/confirm/{confirmToken}")
@AccountAuthorization(permissions = {}) @AccountAuthorization(permissions = {})
public ObjectNode confirm(@RequestBody ObjectNode confirmNode){ public ObjectNode confirm(@PathVariable("confirmToken") UUID confirmToken){
UUID confirmToken = UUID.fromString(confirmNode.get("confirmToken").asText());
log.info("Confirming account with token {}", confirmToken); log.info("Confirming account with token {}", confirmToken);
@@ -164,14 +165,14 @@ public class AuthenticationController{
@PostMapping("/forgot") @PostMapping("/forgot")
@AccountAuthorization(permissions = {}) @AccountAuthorization(permissions = {})
public ObjectNode forgot(@RequestParam("loginId") String loginId){ public ObjectNode forgot(@RequestParam("username") String username){
log.info("Setting up user that forgot their password: {}", loginId); log.info("Setting up user that forgot their password: {}", username);
ObjectNode returnNode = mapper.createObjectNode(); ObjectNode returnNode = mapper.createObjectNode();
//Verify the account exists //Verify the account exists
Account account = accountService.getByUsername(loginId); Account account = accountService.getByUsername(username);
if(account != null){ if(account != null){
//Setup token //Setup token
UUID token = UUID.randomUUID(); UUID token = UUID.randomUUID();
@@ -181,24 +182,29 @@ public class AuthenticationController{
account = accountService.updateAccount(account); 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"); returnNode.put("status", "success");
} }
else{ else{
ArrayNode errorsNode = mapper.createArrayNode(); 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); returnNode.set("errors", errorsNode);
} }
return returnNode; return returnNode;
} }
@PostMapping("/forgot/reset") @PostMapping("/forgot/{forgotToken}")
@AccountAuthorization(permissions = {}) @AccountAuthorization(permissions = {})
public ObjectNode setNewPasswordForgot(@RequestBody ObjectNode forgotNode){ public ObjectNode setNewPasswordForgot(@PathVariable("forgotToken") UUID forgotToken, @RequestBody ObjectNode passwordNode){
UUID forgotToken = UUID.fromString(forgotNode.get("forgotToken").asText()); String newPassword = passwordNode.get("password").asText();
String newPassword = forgotNode.get("password").asText();
log.info("Confirming user reset password (forget) with token {}", forgotToken); log.info("Confirming user reset password (forget) with token {}", forgotToken);

View File

@@ -23,11 +23,15 @@ import com.mattrixwv.raidbuilder.repository.account.AccountRepository;
import com.mattrixwv.raidbuilder.util.DatabaseTypeUtil.AccountPermissionType; import com.mattrixwv.raidbuilder.util.DatabaseTypeUtil.AccountPermissionType;
import com.mattrixwv.raidbuilder.util.DatabaseTypeUtil.AccountStatus; import com.mattrixwv.raidbuilder.util.DatabaseTypeUtil.AccountStatus;
import com.mattrixwv.raidbuilder.util.DatabaseTypeUtil.TutorialStatus; import com.mattrixwv.raidbuilder.util.DatabaseTypeUtil.TutorialStatus;
import com.mattrixwv.raidbuilder.util.EmailUtil;
import com.mattrixwv.raidbuilder.util.UserPrincipal; import com.mattrixwv.raidbuilder.util.UserPrincipal;
import jakarta.mail.internet.InternetAddress;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service @Service
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
@RequiredArgsConstructor @RequiredArgsConstructor
@@ -39,6 +43,8 @@ public class AccountService implements UserDetailsService{
private final AccountPermissionService accountPermissionService; private final AccountPermissionService accountPermissionService;
private final GamePermissionService gamePermissionService; private final GamePermissionService gamePermissionService;
private final RaidGroupPermissionService raidGroupPermissionService; private final RaidGroupPermissionService raidGroupPermissionService;
//Emails
private final EmailUtil emailUtil;
//Fields //Fields
@Value("${jwt.refreshTokenDuration}") @Value("${jwt.refreshTokenDuration}")
private Duration refreshTokenDuration; private Duration refreshTokenDuration;
@@ -71,6 +77,16 @@ public class AccountService implements UserDetailsService{
accountTutorialStatus.setInstanceTutorialStatus(TutorialStatus.NOT_COMPLETED); accountTutorialStatus.setInstanceTutorialStatus(TutorialStatus.NOT_COMPLETED);
accountTutorialStatus = accountTutorialStatusService.createAccountTutorialStatus(accountTutorialStatus); 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 the new account
return account; return account;
} }

View File

@@ -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",
"<p>This is the confirmation email for your Raid Builder Account.<br/><a href=https://raidbuilder.mattrixwv.com/confirm/" + confirmationToken.toString() + ">Click Here</a> to confirm your account.</p>"
);
}
public void sendPasswordResetEmail(InternetAddress toAddress, UUID resetToken) throws MessagingException{
sendMessage(
createSession(),
toAddress,
fromAddress,
"Raid Builder Password Reset",
"<p>This is the password reset email for your Raid Builder Account.<br/><a href=https://raidBuilder.mattrixwv.com/forgotPassword/" + resetToken.toString() + ">Click Here</a> to reset your password.</p>"
);
}
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);
}
}

View File

@@ -23,3 +23,9 @@ jwt.refreshTokenDuration=30d
#Files #Files
uploadFileDirectory=../raidBuilderIcons uploadFileDirectory=../raidBuilderIcons
spring.servlet.multipart.max-file-size=10MB spring.servlet.multipart.max-file-size=10MB
#Email
email.username=
email.password=
email.from=