Skip to content

Commit e97930f

Browse files
committed
update sending email
1 parent 65d38c4 commit e97930f

File tree

6 files changed

+174
-35
lines changed

6 files changed

+174
-35
lines changed

micro-digiservices/authorizationserver/src/main/java/io/digiservices/authorizationserver/security/AuthorizationServerConfig.java

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import com.fasterxml.jackson.core.type.TypeReference;
44
import com.fasterxml.jackson.databind.ObjectMapper;
5+
import io.digiservices.authorizationserver.model.User;
6+
import io.digiservices.authorizationserver.repository.UserRepository;
57
import jakarta.servlet.ServletException;
68
import jakarta.servlet.http.HttpSession;
79
import lombok.RequiredArgsConstructor;
@@ -27,7 +29,9 @@
2729
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
2830
import org.springframework.security.oauth2.core.OAuth2Token;
2931
import org.springframework.security.oauth2.core.oidc.OidcScopes;
32+
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
3033
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
34+
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
3135
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
3236
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
3337
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
@@ -80,6 +84,8 @@ public class AuthorizationServerConfig {
8084
@Value("${UI_APP_URL:https://digi-creditrural-io.com}")
8185
private String uiAppUrl;
8286

87+
private final UserRepository userRepository;
88+
8389
@Bean
8490
@Order(1)
8591
public SecurityFilterChain oauth2ServerConfig(HttpSecurity http, RegisteredClientRepository registeredClientRepository, OAuth2TokenCustomizer<JwtEncodingContext> customizer) throws Exception {
@@ -253,12 +259,50 @@ public AuthorizationServerSettings authorizationServerSettings()
253259
@Bean
254260
public OAuth2TokenCustomizer<JwtEncodingContext> customizer() {
255261
return context -> {
256-
if(ACCESS_TOKEN.equals(context.getTokenType())) {
257-
context.getClaims().claims(claims -> claims.put(AUTHORITY_KEY, getAuthorities(context)));
262+
Authentication principal = context.getPrincipal();
263+
264+
// Add authorities to access token
265+
if (OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType())) {
266+
context.getClaims().claims(claims ->
267+
claims.put(AUTHORITY_KEY, getAuthoritiesAsString(context))
268+
);
269+
}
270+
271+
// Add user info to ID token for Flutter app
272+
if (OidcParameterNames.ID_TOKEN.equals(context.getTokenType().getValue())) {
273+
try {
274+
// The principal IS the User object!
275+
Object principalObj = principal.getPrincipal();
276+
277+
if (principalObj instanceof User) {
278+
User user = (User) principalObj;
279+
280+
log.info("✅ Adding user claims to ID token for user: {} (ID: {})",
281+
user.getEmail(), user.getUserId());
282+
283+
context.getClaims()
284+
.claim("sub", user.getUserId().toString())
285+
.claim("name", user.getFirstName() + " " + user.getLastName())
286+
.claim("given_name", user.getFirstName())
287+
.claim("family_name", user.getLastName())
288+
.claim("email", user.getEmail())
289+
.claim("preferred_username", user.getEmail());
290+
} else {
291+
log.warn("⚠️ Principal is not a User object: {}", principalObj.getClass().getName());
292+
}
293+
} catch (Exception e) {
294+
log.error("❌ Error adding user claims to ID token: {}", e.getMessage(), e);
295+
}
258296
}
259297
};
260298
}
261299

300+
// Method for access token authorities (returns String)
301+
private String getAuthoritiesAsString(JwtEncodingContext context) {
302+
return context.getPrincipal().getAuthorities().stream()
303+
.map(GrantedAuthority::getAuthority)
304+
.collect(Collectors.joining(","));
305+
}
262306
@Bean
263307
public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) {
264308
JdbcRegisteredClientRepository repository = new JdbcRegisteredClientRepository(jdbcTemplate);
@@ -305,6 +349,8 @@ public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTe
305349
return repository;
306350
}
307351

352+
353+
308354
@Bean
309355
public CorsConfigurationSource corsConfigurationSource() {
310356
var corsConfiguration = new CorsConfiguration();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package io.digiservices.authorizationserver.security;
2+
3+
import jakarta.servlet.*;
4+
import jakarta.servlet.http.HttpServletRequest;
5+
import jakarta.servlet.http.HttpSession;
6+
import lombok.extern.slf4j.Slf4j;
7+
import org.springframework.core.annotation.Order;
8+
import org.springframework.stereotype.Component;
9+
10+
import java.io.IOException;
11+
12+
@Slf4j
13+
@Component
14+
@Order(1) // Execute early in filter chain
15+
public class MobileOAuthSessionFilter implements Filter {
16+
17+
@Override
18+
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
19+
throws IOException, ServletException {
20+
21+
HttpServletRequest httpRequest = (HttpServletRequest) request;
22+
23+
// Check if this is a mobile OAuth authorization request
24+
if (httpRequest.getRequestURI().contains("/oauth2/authorize")) {
25+
String clientId = httpRequest.getParameter("client_id");
26+
String redirectUri = httpRequest.getParameter("redirect_uri");
27+
28+
log.info("OAuth2 authorize request - client_id: {}, redirect_uri: {}", clientId, redirectUri);
29+
30+
// Clear session for mobile app requests to force fresh login
31+
if ("mobile-app-client".equals(clientId)) {
32+
HttpSession session = httpRequest.getSession(false);
33+
if (session != null) {
34+
log.info("✂️ Clearing existing session for mobile OAuth request");
35+
session.invalidate();
36+
} else {
37+
log.info("ℹ️ No existing session found for mobile OAuth request");
38+
}
39+
}
40+
}
41+
42+
chain.doFilter(request, response);
43+
}
44+
}

micro-digiservices/notificationservice/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,11 @@
107107
<artifactId>spring-boot-starter-test</artifactId>
108108
<scope>test</scope>
109109
</dependency>
110+
<dependency>
111+
<groupId>com.sendgrid</groupId>
112+
<artifactId>sendgrid-java</artifactId>
113+
<version>4.10.2</version>
114+
</dependency>
110115
</dependencies>
111116

112117
</project>
Lines changed: 61 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
package io.digiservices.notificationservice.service.impl;
22

3+
import com.sendgrid.*;
4+
import com.sendgrid.helpers.mail.Mail;
5+
import com.sendgrid.helpers.mail.objects.Content;
6+
import com.sendgrid.helpers.mail.objects.Email;
37
import io.digiservices.notificationservice.exception.ApiException;
48
import io.digiservices.notificationservice.service.EmailService;
5-
import jakarta.mail.internet.MimeMessage;
69
import lombok.RequiredArgsConstructor;
710
import lombok.extern.slf4j.Slf4j;
811
import org.springframework.beans.factory.annotation.Value;
9-
import org.springframework.mail.javamail.JavaMailSender;
10-
import org.springframework.mail.javamail.MimeMessageHelper;
1112
import org.springframework.scheduling.annotation.Async;
1213
import org.springframework.stereotype.Service;
1314
import org.thymeleaf.TemplateEngine;
1415
import org.thymeleaf.context.Context;
1516

17+
import java.io.IOException;
1618
import java.util.Map;
1719

1820
import static io.digiservices.notificationservice.utils.EmailUtils.getResetPasswordUrl;
@@ -24,20 +26,27 @@
2426
public class EmailServiceImpl implements EmailService {
2527

2628
public static final String NEW_USER_ACCOUNT_VERIFICATION = "New Account Verification";
27-
public static final String UTF_8_ENCODING = "UTF-8";
2829
public static final String ACCOUNT_VERIFICATION_TEMPLATE = "newaccount";
2930
public static final String PASSWORD_RESET_TEMPLATE = "resetpassword";
31+
public static final String PASSWORD_RESET_REQUEST = "Reset Password Request";
3032
public static final String NEW_TICKET_TEMPLATE = "newticket";
3133
public static final String NEW_COMMENT_TEMPLATE = "newcomment";
3234
public static final String NEW_FILE_TEMPLATE = "newfile";
3335
public static final String NEW_TICKET_REQUEST = "New Support Ticket";
34-
public static final String PASSWORD_RESET_REQUEST = "Password Reset Request";
35-
private final JavaMailSender emailSender;
36+
3637
private final TemplateEngine templateEngine;
38+
39+
@Value("${spring.sendgrid.api.key}")
40+
private String sendGridApiKey;
41+
42+
@Value("${spring.sendgrid.from.email}")
43+
private String fromEmail;
44+
45+
@Value("${spring.sendgrid.from.name}")
46+
private String fromName;
47+
3748
@Value("${verify.email.host}")
3849
private String host;
39-
@Value("${spring.mail.username}")
40-
private String fromEmail;
4150

4251
@Override
4352
@Async
@@ -48,55 +57,74 @@ public void sendNewAccountHtmlEmail(String name, String to, String token) {
4857
"name", name,
4958
"url", getVerificationUrl(host, token)
5059
));
51-
var text = templateEngine.process(ACCOUNT_VERIFICATION_TEMPLATE, context);
52-
MimeMessage message = getMimeMessage();
53-
MimeMessageHelper helper = new MimeMessageHelper(message, true, UTF_8_ENCODING);
54-
helper.setPriority(1);
55-
helper.setSubject(NEW_USER_ACCOUNT_VERIFICATION);
56-
helper.setFrom(fromEmail);
57-
helper.setTo(to);
58-
helper.setText(text, true);
59-
emailSender.send(message);
60+
var htmlContent = templateEngine.process(ACCOUNT_VERIFICATION_TEMPLATE, context);
61+
62+
sendEmail(to, NEW_USER_ACCOUNT_VERIFICATION, htmlContent);
63+
log.info("Account verification email sent to: {}", to);
6064
} catch (Exception exception) {
61-
log.error(exception.getMessage(), exception);
65+
log.error("Error sending account verification email: {}", exception.getMessage(), exception);
6266
throw new ApiException("Unable to send email");
6367
}
6468
}
6569

6670
@Override
71+
@Async
6772
public void sendPasswordResetHtmlEmail(String name, String to, String token) {
6873
try {
6974
var context = new Context();
7075
context.setVariables(Map.of(
7176
"name", name,
7277
"url", getResetPasswordUrl(host, token)
7378
));
74-
var text = templateEngine.process(PASSWORD_RESET_TEMPLATE, context);
75-
MimeMessage message = getMimeMessage();
76-
MimeMessageHelper helper = new MimeMessageHelper(message, true, UTF_8_ENCODING);
77-
helper.setPriority(1);
78-
helper.setSubject(PASSWORD_RESET_REQUEST);
79-
helper.setFrom(fromEmail);
80-
helper.setTo(to);
81-
helper.setText(text, true);
82-
emailSender.send(message);
79+
var htmlContent = templateEngine.process(PASSWORD_RESET_TEMPLATE, context);
80+
81+
sendEmail(to, PASSWORD_RESET_REQUEST, htmlContent);
82+
log.info("Password reset email sent to: {}", to);
8383
} catch (Exception exception) {
84-
log.error(exception.getMessage(), exception);
84+
log.error("Error sending password reset email: {}", exception.getMessage(), exception);
8585
throw new ApiException("Unable to send email");
8686
}
8787
}
8888

8989
@Override
9090
public void sendNewTicketHtmlEmail(String name, String email, String ticketTitle, String ticketNumber, String priority) {
91-
91+
// TODO: Implement when needed
9292
}
9393

9494
@Override
9595
public void sendNewFilesHtmlEmail(String name, String email, String files, String ticketTitle, String ticketNumber, String priority, String date) {
96-
96+
// TODO: Implement when needed
9797
}
9898

99-
private MimeMessage getMimeMessage(){
100-
return emailSender.createMimeMessage();
99+
/**
100+
* Core method to send email using SendGrid
101+
*/
102+
private void sendEmail(String toEmail, String subject, String htmlContent) {
103+
Email from = new Email(fromEmail, fromName);
104+
Email to = new Email(toEmail);
105+
Content content = new Content("text/html", htmlContent);
106+
Mail mail = new Mail(from, subject, to, content);
107+
108+
SendGrid sg = new SendGrid(sendGridApiKey);
109+
Request request = new Request();
110+
111+
try {
112+
request.setMethod(Method.POST);
113+
request.setEndpoint("mail/send");
114+
request.setBody(mail.build());
115+
116+
Response response = sg.api(request);
117+
118+
if (response.getStatusCode() >= 200 && response.getStatusCode() < 300) {
119+
log.info("Email sent successfully to {}. Status: {}", toEmail, response.getStatusCode());
120+
} else {
121+
log.error("Failed to send email to {}. Status: {}, Body: {}",
122+
toEmail, response.getStatusCode(), response.getBody());
123+
throw new ApiException("Failed to send email. Status: " + response.getStatusCode());
124+
}
125+
} catch (IOException ex) {
126+
log.error("Error calling SendGrid API: {}", ex.getMessage(), ex);
127+
throw new ApiException("Failed to send email via SendGrid");
128+
}
101129
}
102-
}
130+
}

micro-digiservices/notificationservice/src/main/resources/application-prod.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@ spring:
2424
required: true
2525
default-encoding: UTF-8
2626

27+
28+
sendgrid:
29+
api:
30+
key: ${SENDGRID_API_KEY}
31+
from:
32+
email: ${SENDGRID_FROM_EMAIL:[email protected]}
33+
name: ${SENDGRID_FROM_NAME:Digicrg}
34+
2735
cloud:
2836
discovery:
2937
client:

micro-digiservices/notificationservice/src/main/resources/application.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@ spring:
2525
required: true
2626
default-encoding: UTF-8
2727

28+
29+
sendgrid:
30+
api:
31+
key: ${SENDGRID_API_KEY}
32+
from:
33+
email: ${SENDGRID_FROM_EMAIL:[email protected]}
34+
name: ${SENDGRID_FROM_NAME:Digicrg}
35+
2836
cloud:
2937
discovery:
3038
client:

0 commit comments

Comments
 (0)