diff --git a/pom.xml b/pom.xml index daafa7d..de05254 100644 --- a/pom.xml +++ b/pom.xml @@ -23,6 +23,7 @@ prescription-service + users-service @@ -42,10 +43,6 @@ org.springframework.boot spring-boot-starter-data-mongodb - - org.springframework.boot - spring-boot-starter-data-redis - org.springframework.boot spring-boot-starter-web @@ -54,10 +51,6 @@ io.micrometer micrometer-tracing-bridge-brave - - io.zipkin.reporter2 - zipkin-reporter-brave - org.projectlombok diff --git a/users-service/pom.xml b/users-service/pom.xml new file mode 100644 index 0000000..1d6b128 --- /dev/null +++ b/users-service/pom.xml @@ -0,0 +1,87 @@ + + + 4.0.0 + + + com.stackbytes + mediva-backend + 0.0.1-SNAPSHOT + + + org.example + users-service + + + 21 + 21 + UTF-8 + + + + + de.svenkubiak + jBCrypt + 0.4 + + + org.springframework.boot + spring-boot-starter-security + + + + org.springframework.boot + spring-boot-starter-web + + + + javax.servlet + javax.servlet-api + 4.0.1 + provided + + + + org.springframework.security + spring-security-jwt + 1.1.1.RELEASE + + + + + io.jsonwebtoken + jjwt + 0.9.1 + + + javax.xml.bind + jaxb-api + 2.3.1 + + + + + org.glassfish.jaxb + jaxb-runtime + 2.3.1 + + + + + org.bouncycastle + bcpg-jdk15on + 1.70 + + + org.bouncycastle + bcprov-jdk15on + 1.70 + + + com.fasterxml.jackson.core + jackson-core + 2.18.1 + + + \ No newline at end of file diff --git a/users-service/src/main/java/com/stackbytes/Main.java b/users-service/src/main/java/com/stackbytes/Main.java new file mode 100644 index 0000000..050b4aa --- /dev/null +++ b/users-service/src/main/java/com/stackbytes/Main.java @@ -0,0 +1,11 @@ +package com.stackbytes; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Main { + public static void main(String[] args) { + SpringApplication.run(Main.class, args); + } +} \ No newline at end of file diff --git a/users-service/src/main/java/com/stackbytes/controllers/UserController.java b/users-service/src/main/java/com/stackbytes/controllers/UserController.java new file mode 100644 index 0000000..6a2df21 --- /dev/null +++ b/users-service/src/main/java/com/stackbytes/controllers/UserController.java @@ -0,0 +1,35 @@ +package com.stackbytes.controllers; + +import com.stackbytes.models.LoginData; +import com.stackbytes.models.RegisterRequestDto; +import com.stackbytes.models.ResponseJson; +import com.stackbytes.services.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import com.stackbytes.models.User; + +@RestController() +@RequestMapping("/users") +public class UserController { + @Autowired + private final UserService userService; + public UserController(UserService userService) { + this.userService = userService; + } + @CrossOrigin + @PostMapping("/login") + public ResponseJson loginUser(@RequestBody LoginData loginData) { + return userService.loginUser(loginData); + } + @CrossOrigin + @PostMapping("/register") + public ResponseJson registerUser(@RequestBody RegisterRequestDto registerRequestDto) throws Exception{ + return userService.registerUser(registerRequestDto); + } + @CrossOrigin + @GetMapping("/test") + public String test() throws Exception{ + return userService.test(); + } +} diff --git a/users-service/src/main/java/com/stackbytes/models/ContactInfo.java b/users-service/src/main/java/com/stackbytes/models/ContactInfo.java new file mode 100644 index 0000000..303cc62 --- /dev/null +++ b/users-service/src/main/java/com/stackbytes/models/ContactInfo.java @@ -0,0 +1,13 @@ +package com.stackbytes.models; + +import lombok.Builder; +import lombok.Data; +import lombok.Getter; + +@Builder +@Getter +@Data +public class ContactInfo { + private String phone; + private String email; +} diff --git a/users-service/src/main/java/com/stackbytes/models/LoginData.java b/users-service/src/main/java/com/stackbytes/models/LoginData.java new file mode 100644 index 0000000..56e1bd8 --- /dev/null +++ b/users-service/src/main/java/com/stackbytes/models/LoginData.java @@ -0,0 +1,14 @@ +package com.stackbytes.models; + +import lombok.Builder; +import lombok.Data; +import lombok.Getter; + +@Builder +@Getter +@Data +public class LoginData { + private String email; + private String password; + private boolean isMedic; +} diff --git a/users-service/src/main/java/com/stackbytes/models/Medic.java b/users-service/src/main/java/com/stackbytes/models/Medic.java new file mode 100644 index 0000000..562197b --- /dev/null +++ b/users-service/src/main/java/com/stackbytes/models/Medic.java @@ -0,0 +1,34 @@ +package com.stackbytes.models; + +import lombok.Builder; +import lombok.Data; +import lombok.Getter; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; +import org.springframework.data.util.Pair; + +import java.util.Date; +import java.util.HashMap; +import java.util.List; + +@Document(collection = "medics") +@Data +@Builder +@Getter +public class Medic { + @Id + private String id; + private String medicalId; + private String password; + private Date activeSince; + private String speciality; + private String grade; + private Pair gpg; + private String workPlace; + private double ratings; + private String bio; + private ContactInfo contactInfo; + private List userId; + private Date createdAt; + private Date updatedAt; +} diff --git a/users-service/src/main/java/com/stackbytes/models/RegisterRequestDto.java b/users-service/src/main/java/com/stackbytes/models/RegisterRequestDto.java new file mode 100644 index 0000000..5e42f67 --- /dev/null +++ b/users-service/src/main/java/com/stackbytes/models/RegisterRequestDto.java @@ -0,0 +1,17 @@ +package com.stackbytes.models; + +import lombok.Builder; +import lombok.Data; +import lombok.Getter; + +@Builder +@Getter +@Data +public class RegisterRequestDto { + private User user; + private Medic medic; + private boolean isMedic; + public boolean isMedic() { + return isMedic; + } +} diff --git a/users-service/src/main/java/com/stackbytes/models/ResponseJson.java b/users-service/src/main/java/com/stackbytes/models/ResponseJson.java new file mode 100644 index 0000000..f8a6b79 --- /dev/null +++ b/users-service/src/main/java/com/stackbytes/models/ResponseJson.java @@ -0,0 +1,17 @@ +package com.stackbytes.models; + +import lombok.Builder; +import lombok.Getter; +import org.springframework.data.util.Pair; + +import java.util.HashMap; + +@Builder +@Getter +public class ResponseJson { + private int code; + private boolean status; + private String message; + private String token; + private Pair gpg; +} diff --git a/users-service/src/main/java/com/stackbytes/models/User.java b/users-service/src/main/java/com/stackbytes/models/User.java new file mode 100644 index 0000000..1ce90c9 --- /dev/null +++ b/users-service/src/main/java/com/stackbytes/models/User.java @@ -0,0 +1,33 @@ +package com.stackbytes.models; + +import lombok.Builder; +import lombok.Data; +import lombok.Getter; +import lombok.Setter; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; +import org.springframework.scheduling.support.SimpleTriggerContext; + +import java.util.Date; +import java.util.HashMap; +import java.util.List; + +@Document(collection = "users") +@Builder +@Getter +@Setter +@Data +public class User { + @Id + private String id; + private String username; + private String email; + private String password; + private String fullName; + private String phone; + private String avatar; + private Medic medic; + private List doctorsId; + private Date createdAt; + private Date updatedAt; +} diff --git a/users-service/src/main/java/com/stackbytes/services/BouncyCastleSetup.java b/users-service/src/main/java/com/stackbytes/services/BouncyCastleSetup.java new file mode 100644 index 0000000..451b157 --- /dev/null +++ b/users-service/src/main/java/com/stackbytes/services/BouncyCastleSetup.java @@ -0,0 +1,11 @@ +package com.stackbytes.services; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; + +import java.security.Security; + +public class BouncyCastleSetup { + public static void setup() { + Security.addProvider(new BouncyCastleProvider()); + } +} diff --git a/users-service/src/main/java/com/stackbytes/services/GPGKeyGenerator2.java b/users-service/src/main/java/com/stackbytes/services/GPGKeyGenerator2.java new file mode 100644 index 0000000..a183d31 --- /dev/null +++ b/users-service/src/main/java/com/stackbytes/services/GPGKeyGenerator2.java @@ -0,0 +1,65 @@ +package com.stackbytes.services; + +import jakarta.annotation.PostConstruct; +import org.bouncycastle.openpgp.*; +import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor; +import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder; +import org.bouncycastle.openpgp.operator.PGPDigestCalculator; +import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder; +import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder; +import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyPair; +import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags; + +import java.io.ByteArrayOutputStream; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.util.Date; + +@Component +public class GPGKeyGenerator2 { + @Autowired + private GetProperties getProperties; + @PostConstruct + public void init() { + BouncyCastleSetup.setup(); + } + + public PGPKeyRingGenerator generateKey(String id) throws Exception{ + String passphrase = getProperties.getProperties("gpg.passphrase"); + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(2048); + KeyPair rsaKeyPair = keyPairGenerator.generateKeyPair(); + PGPKeyPair pgpKeyPair = new JcaPGPKeyPair(PGPPublicKey.RSA_GENERAL, rsaKeyPair, new Date()); + PGPDigestCalculator sha256Calc = new JcaPGPDigestCalculatorProviderBuilder().build().get(PGPUtil.SHA1); + PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder(SymmetricKeyAlgorithmTags.AES_256, sha256Calc) + .setProvider("BC") + .build(passphrase.toCharArray()); + PGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder(pgpKeyPair.getPublicKey().getAlgorithm(), PGPUtil.SHA1) + .setProvider("BC"); + return new PGPKeyRingGenerator( + PGPSignature.POSITIVE_CERTIFICATION, + pgpKeyPair, + id, + sha256Calc, + null, + null, + contentSignerBuilder, + keyEncryptor + ); + } + public byte[] getPublicKeyBytes(PGPKeyRingGenerator keyRingGenerator) throws Exception { + PGPPublicKeyRing publicKeyRing = keyRingGenerator.generatePublicKeyRing(); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + publicKeyRing.encode(out); + return out.toByteArray(); + } + public byte[] getPrivateKeyBytes(PGPKeyRingGenerator keyRingGenerator) throws Exception { + PGPSecretKeyRing secretKeyRing = keyRingGenerator.generateSecretKeyRing(); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + secretKeyRing.encode(out); + return out.toByteArray(); + } +} diff --git a/users-service/src/main/java/com/stackbytes/services/GetProperties.java b/users-service/src/main/java/com/stackbytes/services/GetProperties.java new file mode 100644 index 0000000..4881686 --- /dev/null +++ b/users-service/src/main/java/com/stackbytes/services/GetProperties.java @@ -0,0 +1,22 @@ +package com.stackbytes.services; + +import org.springframework.context.annotation.Configuration; +import org.springframework.stereotype.Component; + +import java.io.FileInputStream; +import java.util.Properties; + +@Configuration +public class GetProperties { + public String getProperties(String key) { + Properties prop = new Properties(); + try { + prop.load(new FileInputStream("users-service/src/main/resources/application.properties")); + String DB_URL = prop.getProperty(key); + return DB_URL; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } +} diff --git a/users-service/src/main/java/com/stackbytes/services/JwtUtil.java b/users-service/src/main/java/com/stackbytes/services/JwtUtil.java new file mode 100644 index 0000000..acb9fe9 --- /dev/null +++ b/users-service/src/main/java/com/stackbytes/services/JwtUtil.java @@ -0,0 +1,32 @@ +package com.stackbytes.services; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; + +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +@Configuration +public class JwtUtil { + @Autowired + private GetProperties getProperties; + public String getToken(String jwtHeader) { + String key = getProperties.getProperties("jwt.secret"); + Map claims = new HashMap<>(); + try { + return Jwts.builder() + .setClaims(claims) + .setSubject(jwtHeader.toString()) + .setIssuedAt(new Date(System.currentTimeMillis())) + .signWith(SignatureAlgorithm.HS256, key) + .compact(); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } +} diff --git a/users-service/src/main/java/com/stackbytes/services/SecurityConfig.java b/users-service/src/main/java/com/stackbytes/services/SecurityConfig.java new file mode 100644 index 0000000..750bda3 --- /dev/null +++ b/users-service/src/main/java/com/stackbytes/services/SecurityConfig.java @@ -0,0 +1,18 @@ +package com.stackbytes.services; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.web.SecurityFilterChain; + +@Configuration +@EnableWebSecurity +public class SecurityConfig { + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + .csrf(csrf -> csrf.disable()); + return http.build(); + } +} diff --git a/users-service/src/main/java/com/stackbytes/services/UserService.java b/users-service/src/main/java/com/stackbytes/services/UserService.java new file mode 100644 index 0000000..6911b40 --- /dev/null +++ b/users-service/src/main/java/com/stackbytes/services/UserService.java @@ -0,0 +1,155 @@ +package com.stackbytes.services; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ser.Serializers; +import com.stackbytes.models.*; +import org.bouncycastle.openpgp.PGPKeyRingGenerator; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.util.Pair; +import org.springframework.stereotype.Service; + +import org.mindrot.jbcrypt.BCrypt; + +import java.util.Base64; +import java.util.Date; +import java.util.HashMap; + +@Service +public class UserService { + private final MongoTemplate mongoTemplate; + @Autowired + private GPGKeyGenerator2 gpgKeyGenerator2; + @Autowired + private JwtUtil jwtUtil; + @Autowired + private GetProperties getProperties; + private final ObjectMapper objectMapper; + public UserService(MongoTemplate mongoTemplate, ObjectMapper objectMapper) { + this.mongoTemplate = mongoTemplate; + this.objectMapper = objectMapper; + } + public ResponseJson verifyMedic(Medic medic) { + ContactInfo contactInfo = medic.getContactInfo(); + Medic findMedic = mongoTemplate.findOne(new Query(Criteria.where("email").is(contactInfo.getEmail())), Medic.class); + if (findMedic == null) { + return ResponseJson.builder().code(404).status(false).message("Medic does not exist").build(); + } + if (findMedic.getGpg() == null) { + return ResponseJson.builder().code(404).status(false).message("Medic does not have a GPG key").build(); + } + try { + Pair gpg = medic.getGpg(); + return ResponseJson.builder().code(200).status(true).message("Medic verified").gpg(gpg).build(); + } catch (Exception e) { + return ResponseJson.builder().code(500).status(false).message("Internal server error").build(); + } + } + public String test() throws Exception{ + PGPKeyRingGenerator keyGenerator = gpgKeyGenerator2.generateKey("rares"); + byte[] publicKey = gpgKeyGenerator2.getPublicKeyBytes(keyGenerator); + byte[] privateKey = gpgKeyGenerator2.getPrivateKeyBytes(keyGenerator); + return "public key : " + Base64.getEncoder().encodeToString(publicKey) + "\nprivate key : " + Base64.getEncoder().encodeToString(privateKey); + } + public ResponseJson loginUser(LoginData loginData) { + if (loginData.getEmail() == null || loginData.getPassword() == null) { + return ResponseJson.builder().code(404).status(false).message("Email or password cannot be null").build(); + } + if(loginData.isMedic() == true) { + System.out.println("medic"); + Medic medic = mongoTemplate.findOne(new Query(Criteria.where("contactInfo.email").is(loginData.getEmail())), Medic.class); + if (medic == null) { + return ResponseJson.builder().code(404).status(false).message("Medic not found").build(); + } + if (BCrypt.checkpw(loginData.getPassword(), medic.getPassword())) { + String jsonStringTokenContent = null; + try { + jsonStringTokenContent = objectMapper.writeValueAsString(medic); + } catch (Exception e) { + return ResponseJson.builder().code(500).status(false).message(e.getMessage()).build(); + } + String token = jwtUtil.getToken(jsonStringTokenContent); + return ResponseJson.builder().code(200).status(true).message("Medic logged in").token(token).build(); + } else { + return ResponseJson.builder().code(404).status(false).message("Invalid password").build(); + } + } else { + try { + System.out.println("user"); + User user = mongoTemplate.findOne(new Query(Criteria.where("email").is(loginData.getEmail())), User.class); + System.out.println("here1"); + if (user == null) { + return ResponseJson.builder().code(404).status(false).message("User not found").build(); + } + if (BCrypt.checkpw(loginData.getPassword(), user.getPassword())) { + String jsonStringTokenContent = null; + try { + jsonStringTokenContent = objectMapper.writeValueAsString(user); + } catch (Exception e) { + return ResponseJson.builder().code(500).status(false).message(e.getMessage()).build(); + } + String token = jwtUtil.getToken(jsonStringTokenContent); + return ResponseJson.builder().code(200).status(true).message("User logged in").token(token).build(); + } else { + return ResponseJson.builder().code(404).status(false).message("Invalid password").build(); + } + } catch (Exception e) { + return ResponseJson.builder().code(500).status(false).message(e.getMessage()).build(); + } + } + } + public ResponseJson registerUser(RegisterRequestDto registerRequestDto) throws Exception{ + if (registerRequestDto == null) { + return ResponseJson.builder().code(404).status(false).message("User cannot be null").build(); + } + if (registerRequestDto.isMedic()) { + try { + Medic medic = registerRequestDto.getMedic(); + ContactInfo contactInfo = medic.getContactInfo(); + Medic findMedic = mongoTemplate.findOne(new Query(Criteria.where("contactInfo.email").is(contactInfo.getEmail())), Medic.class); + if (findMedic != null) { + return ResponseJson.builder().code(404).status(false).message("Medic already exists").build(); + } + medic.setPassword(BCrypt.hashpw(medic.getPassword(), BCrypt.gensalt())); + medic.setCreatedAt(new Date()); + medic.setUpdatedAt(new Date()); + PGPKeyRingGenerator keyGenerator = gpgKeyGenerator2.generateKey(contactInfo.getEmail()); + String publicKey = gpgKeyGenerator2.getPublicKeyBytes(keyGenerator).toString(); + String privateKey = gpgKeyGenerator2.getPrivateKeyBytes(keyGenerator).toString(); + String publicKeyString = Base64.getEncoder().encodeToString(publicKey.getBytes()); + String privateKeyString = Base64.getEncoder().encodeToString(privateKey.getBytes()); + Pair gpg = Pair.of(publicKeyString, privateKeyString); + medic.setGpg(gpg); + try { + mongoTemplate.insert(medic); + return ResponseJson.builder().code(200).status(true).message("Medic registered").build(); + } catch (Exception e) { + return ResponseJson.builder().code(500).status(false).message(e.getMessage()).build(); + } + } catch (Exception e) { + return ResponseJson.builder().code(500).status(false).message(e.getMessage()).build(); + } + } else { + try { + User user = registerRequestDto.getUser(); + User findUser = mongoTemplate.findOne(new Query(Criteria.where("email").is(user.getEmail())), User.class); + if (findUser != null) { + return ResponseJson.builder().code(404).status(false).message("User already exists").build(); + } + user.setPassword(BCrypt.hashpw(user.getPassword(), BCrypt.gensalt())); + user.setCreatedAt(new Date()); + user.setUpdatedAt(new Date()); + try { + mongoTemplate.insert(user); + return ResponseJson.builder().code(200).status(true).message("User registered").build(); + } catch (Exception e) { + return ResponseJson.builder().code(500).status(false).message(e.getMessage()).build(); + } + } catch (Exception e) { + return ResponseJson.builder().code(500).status(false).message(e.getMessage()).build(); + } + } + } +}