Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions services/chatbot/src/chatbot/langgraph_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,9 +226,13 @@ async def build_langgraph_agent(api_key, model_name, user_jwt):
toolkit = SQLDatabaseToolkit(db=postgresdb, llm=llm)
logger.debug("SQL Database toolkit created")

mcp_client = get_mcp_client(user_jwt)
mcp_tools = await mcp_client.get_tools()
logger.debug("MCP tools loaded: %d tools", len(mcp_tools))
mcp_tools = []
try:
mcp_client = get_mcp_client(user_jwt)
mcp_tools = await mcp_client.get_tools()
logger.debug("MCP tools loaded: %d tools", len(mcp_tools))
except Exception as e:
logger.error("Failed to load MCP tools, continuing without them: %s", e)

db_tools = toolkit.get_tools()
logger.debug("Database tools loaded: %d tools", len(db_tools))
Expand Down
50 changes: 31 additions & 19 deletions services/chatbot/src/mcpserver/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,40 +30,52 @@

def get_api_key():
global API_KEY
# Try 5 times to get client auth
MAX_ATTEMPTS = 5
if API_KEY is not None:
return API_KEY
# Try multiple times to get client auth (identity service may not be ready yet)
MAX_ATTEMPTS = 10
for i in range(MAX_ATTEMPTS):
logger.info(f"Attempt {i+1} to get API key...")
if API_KEY is None:
login_body = {"email": Config.API_USER, "password": Config.API_PASSWORD}
auth_url = f"{BASE_IDENTITY_URL}/identity/management/user/apikey"
headers = {
"Content-Type": "application/json",
}
login_body = {"email": Config.API_USER, "password": Config.API_PASSWORD}
auth_url = f"{BASE_IDENTITY_URL}/identity/management/user/apikey"
headers = {
"Content-Type": "application/json",
}
try:
with httpx.Client(
base_url=BASE_URL,
headers=headers,
verify=False,
) as client:
response = client.post(auth_url, json=login_body)
if response.status_code != 200:
logger.error(
f"Failed to get API key in attempt {i+1}: {response.status_code} {response.text}. Sleeping for {i+1} seconds..."
)
# Reset test users if credentials are rejected
try:
reset_url = f"{BASE_IDENTITY_URL}/identity/api/auth/reset-test-users"
reset_resp = client.post(reset_url)
logger.info(f"Reset test users response: {reset_resp.status_code}")
except Exception as reset_err:
logger.error(f"Failed to reset test users: {reset_err}")
if i == MAX_ATTEMPTS - 1:
logger.error(
f"Failed to get API key after {i+1} attempts: {response.status_code} {response.text}"
)
raise Exception(
f"Failed to get API key after {i+1} attempts: {response.status_code} {response.text}"
f"Failed to get API key after {MAX_ATTEMPTS} attempts: {response.status_code} {response.text}"
)
logger.error(
f"Failed to get API key in attempt {i+1}: {response.status_code} {response.text}. Sleeping for {i} seconds..."
)
time.sleep(i)
time.sleep(i + 1)
continue
response_json = response.json()
logger.info(f"Response: {response_json}")
API_KEY = response_json.get("apiKey")
if API_KEY:
logger.debug("MCP Server API Key obtained successfully.")
return API_KEY
logger.info("MCP Server API Key obtained successfully.")
return API_KEY
logger.error(f"API key not found in response: {response_json}")
except httpx.HTTPError as e:
logger.error(f"HTTP error in attempt {i+1}: {e}")
if i == MAX_ATTEMPTS - 1:
raise
time.sleep(i + 1)
return API_KEY


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

/*
* Need to give path for application.properties file
Expand All @@ -25,6 +26,7 @@
@PropertySource(value = "file:/home/hasher/Music/resources/application.properties", ignoreResourceNotFound = true)
})*/
@SpringBootApplication(scanBasePackages = {"com.crapi"})
@EnableScheduling
public class CRAPIBootApplication {

public static void main(String[] args) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public class UserMessage {
public static final String EMAIL_NOT_REGISTERED = "Given Email is not registered! ";
public static final String INVALID_OTP = "Invalid OTP! Please try again..";
public static final String ERROR = "ERROR..";
public static final String OTP_VARIFIED_SUCCESS = "OTP verified";
public static final String OTP_VERIFIED_SUCCESS = "OTP verified";
public static final String OTP_SEND_SUCCESS_ON_EMAIL = "OTP Sent on the provided email, ";
public static final String EXCEED_NUMBER_OF_ATTEMPS = "You've exceeded the number of attempts.";
public static final String PASSWORD_GOT_RESET = "Password reset successful.";
Expand Down
2 changes: 2 additions & 0 deletions services/identity/src/main/java/com/crapi/entity/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ public class User {

private LocalDate createdOn = LocalDate.now();

private LocalDate passwordUpdatedAt = LocalDate.of(2000, 1, 1);

private String code;

// @OneToOne
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,10 @@ public CRAPIResponse validateOtp(OtpForm otpForm) {
otp = otpRepository.findByUser(user);
if (validateOTPAndEmail(otp, otpForm)) {
user.setPassword(encoder.encode(otpForm.getPassword()));
user.setPasswordUpdatedAt(java.time.LocalDate.now());
userRepository.save(user);
otp.setStatus(EStatus.INACTIVE.toString());
validateOTPResponse = new CRAPIResponse(UserMessage.OTP_VARIFIED_SUCCESS, 200);
validateOTPResponse = new CRAPIResponse(UserMessage.OTP_VERIFIED_SUCCESS, 200);
} else {
otp.setCount(otp.getCount() + 1);
validateOTPResponse = new CRAPIResponse(UserMessage.INVALID_OTP, 500);
Expand All @@ -103,9 +104,10 @@ public CRAPIResponse secureValidateOtp(OtpForm otpForm) {
otp = otpRepository.findByUser(user);
if (validateOTPAndEmail(otp, otpForm)) {
user.setPassword(encoder.encode(otpForm.getPassword()));
user.setPasswordUpdatedAt(java.time.LocalDate.now());
userRepository.save(user);
otp.setStatus(EStatus.INACTIVE.toString());
validateOTPResponse = new CRAPIResponse(UserMessage.OTP_VARIFIED_SUCCESS, 200);
validateOTPResponse = new CRAPIResponse(UserMessage.OTP_VERIFIED_SUCCESS, 200);
} else if (otp.getCount() == 9) {
otp.setCount(otp.getCount() + 1);
invalidateOtp(otp);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import jakarta.servlet.http.HttpServletRequest;
import jakarta.transaction.Transactional;
import java.text.ParseException;
import java.time.LocalDate;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.impl.Log4jContextFactory;
Expand Down Expand Up @@ -172,6 +173,7 @@ public User updateUserPassword(String password, String email) {
User user = userRepository.findByEmail(email);
if (user != null) {
user.setPassword(encoder.encode(password));
user.setPasswordUpdatedAt(LocalDate.now());
userRepository.saveAndFlush(user);
}
return user;
Expand All @@ -188,6 +190,7 @@ public CRAPIResponse resetPassword(LoginForm loginForm, HttpServletRequest reque
User user = getUserFromToken(request);
if (user != null) {
user.setPassword(encoder.encode(loginForm.getPassword()));
user.setPasswordUpdatedAt(LocalDate.now());
userRepository.saveAndFlush(user);
return new CRAPIResponse(UserMessage.PASSWORD_GOT_RESET, 200);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.crapi.service;

import com.crapi.constant.TestUsers;
import com.crapi.entity.User;
import com.crapi.model.SeedUser;
import com.crapi.repository.UserRepository;
import java.time.LocalDate;
import java.util.ArrayList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

@Component
public class UserResetJob {

private static final Logger logger = LoggerFactory.getLogger(UserResetJob.class);

private static final LocalDate DEFAULT_PASSWORD_DATE = LocalDate.of(2000, 1, 1);

@Autowired private UserRepository userRepository;

@Autowired private PasswordEncoder encoder;

@Scheduled(fixedRate = 3600000) // every hour
@Transactional
public void resetTestUserCredsIfChanged() {
ArrayList<SeedUser> testUsers = new TestUsers().getUsers();
int resetCount = 0;

for (SeedUser seedUser : testUsers) {
User user = userRepository.findByEmail(seedUser.getEmail());
if (user == null) {
continue;
}
LocalDate updatedAt = user.getPasswordUpdatedAt();
if (updatedAt != null && !updatedAt.equals(DEFAULT_PASSWORD_DATE)) {
user.setPassword(encoder.encode(seedUser.getPassword()));
user.setPasswordUpdatedAt(DEFAULT_PASSWORD_DATE);
userRepository.saveAndFlush(user);
resetCount++;
logger.info("Reset password for test user: {}", seedUser.getEmail());
}
}

if (resetCount > 0) {
logger.info("Reset credentials for {} test user(s)", resetCount);
} else {
logger.debug("All test user credentials are unchanged, no reset needed");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ public void validateOtpSuccess() {
CRAPIResponse crapiAPIResponse = otpService.validateOtp(otpForm);
Mockito.verify(userRepository, Mockito.times(1)).save(Mockito.any());
Mockito.verify(otpRepository, Mockito.times(1)).save(Mockito.any());
Assertions.assertEquals(UserMessage.OTP_VARIFIED_SUCCESS, crapiAPIResponse.getMessage());
Assertions.assertEquals(UserMessage.OTP_VERIFIED_SUCCESS, crapiAPIResponse.getMessage());
Assertions.assertEquals(HttpStatus.OK.value(), crapiAPIResponse.getStatus());
}

Expand Down Expand Up @@ -128,7 +128,7 @@ public void secureValidateOtpSuccess() {
CRAPIResponse crapiAPIResponse = otpService.secureValidateOtp(otpForm);
Mockito.verify(userRepository, Mockito.times(1)).save(Mockito.any());
Mockito.verify(otpRepository, Mockito.times(1)).save(Mockito.any());
Assertions.assertEquals(UserMessage.OTP_VARIFIED_SUCCESS, crapiAPIResponse.getMessage());
Assertions.assertEquals(UserMessage.OTP_VERIFIED_SUCCESS, crapiAPIResponse.getMessage());
Assertions.assertEquals(HttpStatus.OK.value(), crapiAPIResponse.getStatus());
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package com.crapi.service;

import com.crapi.constant.TestUsers;
import com.crapi.entity.User;
import com.crapi.model.SeedUser;
import com.crapi.repository.UserRepository;
import java.time.LocalDate;
import java.util.ArrayList;
import org.junit.Test;
import org.junit.jupiter.api.Assertions;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.security.crypto.password.PasswordEncoder;

@RunWith(MockitoJUnitRunner.class)
public class UserResetJobTest {

private static final LocalDate DEFAULT_PASSWORD_DATE = LocalDate.of(2000, 1, 1);

@InjectMocks private UserResetJob userResetJob;

@Mock private UserRepository userRepository;

@Mock private PasswordEncoder encoder;

@Test
public void resetSkipsWhenPasswordNotChanged() {
ArrayList<SeedUser> testUsers = new TestUsers().getUsers();
for (SeedUser seedUser : testUsers) {
User user =
new User(seedUser.getEmail(), seedUser.getNumber(), "encoded", seedUser.getRole());
user.setPasswordUpdatedAt(DEFAULT_PASSWORD_DATE);
Mockito.when(userRepository.findByEmail(seedUser.getEmail())).thenReturn(user);
}

userResetJob.resetTestUserCredsIfChanged();

Mockito.verify(userRepository, Mockito.never()).saveAndFlush(Mockito.any());
}

@Test
public void resetResetsWhenPasswordChanged() {
ArrayList<SeedUser> testUsers = new TestUsers().getUsers();
SeedUser firstUser = testUsers.get(0);

// First user has changed password (non-default date)
User changedUser =
new User(firstUser.getEmail(), firstUser.getNumber(), "encoded", firstUser.getRole());
changedUser.setPasswordUpdatedAt(LocalDate.now());
Mockito.when(userRepository.findByEmail(firstUser.getEmail())).thenReturn(changedUser);
Mockito.when(encoder.encode(firstUser.getPassword())).thenReturn("resetEncoded");
Mockito.when(userRepository.saveAndFlush(Mockito.any())).thenReturn(changedUser);

// Remaining users have default date
for (int i = 1; i < testUsers.size(); i++) {
SeedUser seedUser = testUsers.get(i);
User user =
new User(seedUser.getEmail(), seedUser.getNumber(), "encoded", seedUser.getRole());
user.setPasswordUpdatedAt(DEFAULT_PASSWORD_DATE);
Mockito.when(userRepository.findByEmail(seedUser.getEmail())).thenReturn(user);
}

userResetJob.resetTestUserCredsIfChanged();

Mockito.verify(userRepository, Mockito.times(1)).saveAndFlush(Mockito.any());
Assertions.assertEquals("resetEncoded", changedUser.getPassword());
Assertions.assertEquals(DEFAULT_PASSWORD_DATE, changedUser.getPasswordUpdatedAt());
}

@Test
public void resetResetsAllWhenAllPasswordsChanged() {
ArrayList<SeedUser> testUsers = new TestUsers().getUsers();
for (SeedUser seedUser : testUsers) {
User user =
new User(seedUser.getEmail(), seedUser.getNumber(), "encoded", seedUser.getRole());
user.setPasswordUpdatedAt(LocalDate.now());
Mockito.when(userRepository.findByEmail(seedUser.getEmail())).thenReturn(user);
Mockito.when(userRepository.saveAndFlush(Mockito.any())).thenReturn(user);
}
Mockito.when(encoder.encode(Mockito.anyString())).thenReturn("resetEncoded");

userResetJob.resetTestUserCredsIfChanged();

Mockito.verify(userRepository, Mockito.times(testUsers.size())).saveAndFlush(Mockito.any());
}

@Test
public void resetSkipsNullUser() {
ArrayList<SeedUser> testUsers = new TestUsers().getUsers();
for (SeedUser seedUser : testUsers) {
Mockito.when(userRepository.findByEmail(seedUser.getEmail())).thenReturn(null);
}

userResetJob.resetTestUserCredsIfChanged();

Mockito.verify(userRepository, Mockito.never()).saveAndFlush(Mockito.any());
}

@Test
public void resetSkipsWhenPasswordUpdatedAtNull() {
ArrayList<SeedUser> testUsers = new TestUsers().getUsers();
for (SeedUser seedUser : testUsers) {
User user =
new User(seedUser.getEmail(), seedUser.getNumber(), "encoded", seedUser.getRole());
user.setPasswordUpdatedAt(null);
Mockito.when(userRepository.findByEmail(seedUser.getEmail())).thenReturn(user);
}

userResetJob.resetTestUserCredsIfChanged();

Mockito.verify(userRepository, Mockito.never()).saveAndFlush(Mockito.any());
}
}
Loading