diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml
new file mode 100644
index 0000000..0a45c9c
--- /dev/null
+++ b/.github/workflows/workflow.yml
@@ -0,0 +1,77 @@
+name: Deploy jar to EC2
+
+on:
+ push:
+ branches: [ "main" ]
+
+permissions:
+ id-token: write # This is required for requesting the JWT
+ contents: read # This is required for actions/checkout
+
+jobs:
+ build:
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+ - name: Set up JDK 17
+ uses: actions/setup-java@v3
+ with:
+ java-version: '17'
+ distribution: 'temurin'
+ cache: maven
+
+ - name: Set environment variables
+ run: |
+ echo "SERVER_DB_URL=${{ secrets.DB_URL }}" >> $GITHUB_ENV
+ echo "SERVER_DB_USERNAME=${{ secrets.DB_USERNAME }}" >> $GITHUB_ENV
+ echo "SERVER_DB_PASSWORD=${{ secrets.DB_PASSWORD }}" >> $GITHUB_ENV
+ echo "SERVER_DB_DRIVER_CLASS_NAME=${{ secrets.DB_DRIVER }}" >> $GITHUB_ENV
+ echo "SERVER_DB_DIALECT=${{ secrets.SERVER_DB_DIALECT }}" >> $GITHUB_ENV
+ echo "HTTP_PORT=${{ secrets.HTTP_PORT }}" >> $GITHUB_ENV
+ echo "SERVER_PORT=${{ secrets.SERVER_PORT }}" >> $GITHUB_ENV
+ echo "SERVER_SSL_KEY_STORE_TYPE=${{ secrets.SERVER_SSL_KEY_STORE_TYPE }}" >> $GITHUB_ENV
+ echo "SERVER_SSL_KEY_STORE_LOCATION=${{ secrets.SERVER_SSL_KEY_STORE_LOCATION }}" >> $GITHUB_ENV
+ echo "SERVER_SSL_KEY_STORE_PASSWORD=${{ secrets.SERVER_SSL_KEY_STORE_PASSWORD }}" >> $GITHUB_ENV
+ echo "SERVER_SSL_KEY_ALIAS=${{ secrets.SERVER_SSL_KEY_ALIAS }}" >> $GITHUB_ENV
+ echo "SERVER_SSL_TRUST_STORE_LOCATION=${{ secrets.SERVER_SSL_TRUST_STORE_LOCATION }}" >> $GITHUB_ENV
+ echo "SERVER_SSL_TRUST_STORE_PASSWORD=${{ secrets.SERVER_SSL_TRUST_STORE_PASSWORD }}" >> $GITHUB_ENV
+
+ - name: Build with Maven
+ run: mvn -B package --file pom.xml
+
+ - name: Configure AWS Credentials
+ id: configure-aws-credentials
+ uses: aws-actions/configure-aws-credentials@v4
+ with:
+ role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ secrets.AWS_ROLE }}
+ aws-region: ${{ secrets.AWS_REGION }}
+
+ - name: Get Instance ID
+ id: get_instance_id
+ run: |
+ INSTANCE_ID=$(aws ec2 describe-instances --filters "Name=tag:Name,Values=safeqr-ec2" --query "Reservations[0].Instances[0].InstanceId" --output text)
+ echo "INSTANCE_ID=$INSTANCE_ID" >> $GITHUB_ENV
+
+ - name: Upload JAR to S3
+ run: |
+ aws s3 cp target/app-0.0.1-SNAPSHOT.jar s3://s3-bucket-safeqr/
+
+ - name: Download JAR from S3 to EC2
+ run: |
+ aws ssm send-command --instance-ids ${{ env.INSTANCE_ID }} --document-name "AWS-RunShellScript" --comment "Download JAR from S3" --parameters 'commands=[
+ "aws s3 cp s3://s3-bucket-safeqr/app-0.0.1-SNAPSHOT.jar /home/ssm-user/app-0.0.1-SNAPSHOT.jar",
+ ]'
+
+ - name: Create and Start Systemd Service
+ run: |
+ aws ssm send-command --instance-ids ${{ env.INSTANCE_ID }} --document-name "AWS-RunShellScript" --comment "Create and start service" --parameters 'commands=[
+ "aws s3 cp s3://s3-bucket-safeqr/springboot-app.service /etc/systemd/system/springboot-app.service",
+ "aws s3 cp s3://s3-bucket-safeqr/springboot-app.var /etc/systemd/system/springboot-app.var",
+ "sudo systemctl stop springboot-app",
+ "sudo systemctl daemon-reload",
+ "sudo systemctl enable springboot-app",
+ "sudo systemctl start springboot-app",
+ "sudo systemctl status springboot-app"
+ ]'
diff --git a/.gitignore b/.gitignore
index 6d77e55..d57dd76 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,7 +18,10 @@ target/
*.iws
*.iml
*.ipr
-application-local.properties
+src/main/resources/application-local.properties
+update_env_var.sh
+springboot-app.service
+springboot-app.var
### NetBeans ###
/nbproject/private/
diff --git a/pom.xml b/pom.xml
index cfef930..e8ce888 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,6 +1,6 @@
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
org.springframework.boot
@@ -45,6 +45,30 @@
spring-boot-starter-test
test
+
+
+ org.springframework.boot
+ spring-boot-starter-webflux
+
+
+
+
+ io.projectreactor
+ reactor-core
+
+
+
+
+ io.projectreactor
+ reactor-core
+
+
+
+
+ io.netty
+ netty-resolver-dns-native-macos
+ runtime
+
@@ -64,4 +88,4 @@
-
+
\ No newline at end of file
diff --git a/springboot-app.service b/springboot-app.service
new file mode 100644
index 0000000..4b03773
--- /dev/null
+++ b/springboot-app.service
@@ -0,0 +1,14 @@
+[Unit]
+Description=Spring Boot Application
+After=network.target
+
+[Service]
+User=ssm-user
+EnvironmentFile=/etc/systemd/system/springboot-app.var
+ExecStart=/usr/bin/java -jar /home/ssm-user/app-0.0.1-SNAPSHOT.jar
+SuccessExitStatus=143
+Restart=always
+RestartSec=3
+
+[Install]
+WantedBy=multi-user.target
\ No newline at end of file
diff --git a/src/main/java/com/safeqr/app/qrcode/controller/QRCodeTypeController.java b/src/main/java/com/safeqr/app/qrcode/controller/QRCodeTypeController.java
new file mode 100644
index 0000000..043afd2
--- /dev/null
+++ b/src/main/java/com/safeqr/app/qrcode/controller/QRCodeTypeController.java
@@ -0,0 +1,64 @@
+package com.safeqr.app.qrcode.controller;
+
+import com.safeqr.app.qrcode.dto.QRCodePayload;
+import com.safeqr.app.qrcode.dto.RedirectCountResponse;
+import com.safeqr.app.qrcode.dto.URLVerificationResponse;
+import com.safeqr.app.qrcode.entity.QRCodeType;
+import com.safeqr.app.qrcode.service.QRCodeTypeService;
+import com.safeqr.app.qrcode.service.RedirectCountService;
+import com.safeqr.app.qrcode.service.URLVerificationService;
+import com.safeqr.app.qrcode.service.VirusTotalService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/v1/api/qrcodetypes")
+public class QRCodeTypeController {
+
+ @Autowired
+ private QRCodeTypeService qrCodeTypeService;
+
+ @Autowired
+ private URLVerificationService urlVerificationService;
+
+ @Autowired
+ private VirusTotalService virusTotalService;
+
+ @Autowired
+ private RedirectCountService redirectCountService;
+
+ @GetMapping
+ public ResponseEntity> getAllTypes() {
+ return ResponseEntity.ok(qrCodeTypeService.getAllTypes());
+ }
+
+ @PostMapping("/detect")
+ public ResponseEntity detectType(@RequestBody QRCodePayload payload) {
+ return ResponseEntity.ok(qrCodeTypeService.detectType(payload).block());
+ }
+
+ @PostMapping("/verifyURL")
+ public ResponseEntity verifyURL(@RequestBody QRCodePayload payload) {
+ URLVerificationResponse response = urlVerificationService.verifyURL(payload);
+ return ResponseEntity.ok(response);
+ }
+
+ @PostMapping("/virusTotalCheck")
+ public ResponseEntity virusTotalCheck(@RequestBody QRCodePayload payload) {
+ try {
+ String analysisId = virusTotalService.scanURL(payload);
+ boolean isSafe = virusTotalService.getAnalysis(analysisId);
+ return ResponseEntity.ok(isSafe);
+ } catch (Exception e) {
+ return ResponseEntity.status(500).body(false);
+ }
+ }
+
+ @PostMapping("/checkRedirects")
+ public ResponseEntity checkRedirects(@RequestBody QRCodePayload payload) {
+ return ResponseEntity.ok(redirectCountService.countRedirects(payload).block());
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/safeqr/app/qrcode/dto/QRCodePayload.java b/src/main/java/com/safeqr/app/qrcode/dto/QRCodePayload.java
new file mode 100644
index 0000000..2d140bb
--- /dev/null
+++ b/src/main/java/com/safeqr/app/qrcode/dto/QRCodePayload.java
@@ -0,0 +1,8 @@
+package com.safeqr.app.qrcode.dto;
+
+import lombok.Data;
+
+@Data // Lombok annotation to generate getters, setters, toString, equals, and hashCode methods
+public class QRCodePayload {
+ private String data;
+}
\ No newline at end of file
diff --git a/src/main/java/com/safeqr/app/qrcode/dto/RedirectCountResponse.java b/src/main/java/com/safeqr/app/qrcode/dto/RedirectCountResponse.java
new file mode 100644
index 0000000..ad34005
--- /dev/null
+++ b/src/main/java/com/safeqr/app/qrcode/dto/RedirectCountResponse.java
@@ -0,0 +1,9 @@
+package com.safeqr.app.qrcode.dto;
+
+import lombok.Data;
+
+@Data
+public class RedirectCountResponse {
+ private int redirectCount;
+ private String message;
+}
\ No newline at end of file
diff --git a/src/main/java/com/safeqr/app/qrcode/dto/SafeBrowsingRequest.java b/src/main/java/com/safeqr/app/qrcode/dto/SafeBrowsingRequest.java
new file mode 100644
index 0000000..7fef125
--- /dev/null
+++ b/src/main/java/com/safeqr/app/qrcode/dto/SafeBrowsingRequest.java
@@ -0,0 +1,40 @@
+package com.safeqr.app.qrcode.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class SafeBrowsingRequest {
+ private Client client;
+ private ThreatInfo threatInfo;
+
+ @Data
+ @NoArgsConstructor
+ @AllArgsConstructor
+ public static class Client {
+ private String clientId;
+ private String clientVersion;
+ }
+
+ @Data
+ @NoArgsConstructor
+ @AllArgsConstructor
+ public static class ThreatInfo {
+ private List threatTypes;
+ private List platformTypes;
+ private List threatEntryTypes;
+ private List threatEntries;
+
+ @Data
+ @NoArgsConstructor
+ @AllArgsConstructor
+ public static class ThreatEntry {
+ private String url;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/safeqr/app/qrcode/dto/SafeBrowsingResponse.java b/src/main/java/com/safeqr/app/qrcode/dto/SafeBrowsingResponse.java
new file mode 100644
index 0000000..4466e0a
--- /dev/null
+++ b/src/main/java/com/safeqr/app/qrcode/dto/SafeBrowsingResponse.java
@@ -0,0 +1,33 @@
+
+package com.safeqr.app.qrcode.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class SafeBrowsingResponse {
+ private List matches;
+
+ @Data
+ @NoArgsConstructor
+ @AllArgsConstructor
+ public static class ThreatMatch {
+ private Threat threat;
+ private String threatType;
+ private String platformType;
+ private String threatEntryType;
+
+ @Data
+ @NoArgsConstructor
+ @AllArgsConstructor
+ public static class Threat {
+ private String url;
+ private String hash;
+ }
+ }
+}
diff --git a/src/main/java/com/safeqr/app/qrcode/dto/URLVerificationRequest.java b/src/main/java/com/safeqr/app/qrcode/dto/URLVerificationRequest.java
new file mode 100644
index 0000000..f43dad1
--- /dev/null
+++ b/src/main/java/com/safeqr/app/qrcode/dto/URLVerificationRequest.java
@@ -0,0 +1,14 @@
+package com.safeqr.app.qrcode.dto;
+
+public class URLVerificationRequest {
+ private String url;
+
+ // Getters and Setters
+ public String getUrl() {
+ return url;
+ }
+
+ public void setUrl(String url) {
+ this.url = url;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/safeqr/app/qrcode/dto/URLVerificationResponse.java b/src/main/java/com/safeqr/app/qrcode/dto/URLVerificationResponse.java
new file mode 100644
index 0000000..2a264b2
--- /dev/null
+++ b/src/main/java/com/safeqr/app/qrcode/dto/URLVerificationResponse.java
@@ -0,0 +1,13 @@
+package com.safeqr.app.qrcode.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class URLVerificationResponse {
+ private boolean secure;
+ private String message;
+}
\ No newline at end of file
diff --git a/src/main/java/com/safeqr/app/qrcode/dto/VirusTotalResponse.java b/src/main/java/com/safeqr/app/qrcode/dto/VirusTotalResponse.java
new file mode 100644
index 0000000..0ee0fbe
--- /dev/null
+++ b/src/main/java/com/safeqr/app/qrcode/dto/VirusTotalResponse.java
@@ -0,0 +1,15 @@
+package com.safeqr.app.qrcode.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Map;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class VirusTotalResponse {
+ private boolean safe;
+ private Map response;
+}
\ No newline at end of file
diff --git a/src/main/java/com/safeqr/app/qrcode/entity/QRCode.java b/src/main/java/com/safeqr/app/qrcode/entity/QRCode.java
new file mode 100644
index 0000000..23b5481
--- /dev/null
+++ b/src/main/java/com/safeqr/app/qrcode/entity/QRCode.java
@@ -0,0 +1,30 @@
+
+package com.safeqr.app.qrcode.entity;
+
+import jakarta.persistence.*;
+import lombok.Data;
+import org.hibernate.annotations.GenericGenerator;
+
+import java.util.UUID;
+
+@Entity
+@Table(name = "qr_code", schema = "safeqr")
+@Data
+public class QRCode {
+
+ @Id
+ @GeneratedValue(generator = "UUID")
+ @GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
+ @Column(updatable = false, nullable = false)
+ private UUID id;
+
+ @ManyToOne
+ @JoinColumn(name = "qr_code_type_id", nullable = false)
+ private QRCodeType qrCodeType;
+
+ private String userId;
+ private String contents;
+
+ @Column(name = "created_at", insertable = false, updatable = false)
+ private String createdAt;
+}
diff --git a/src/main/java/com/safeqr/app/qrcode/entity/QRCodeType.java b/src/main/java/com/safeqr/app/qrcode/entity/QRCodeType.java
new file mode 100644
index 0000000..2d4b9f0
--- /dev/null
+++ b/src/main/java/com/safeqr/app/qrcode/entity/QRCodeType.java
@@ -0,0 +1,20 @@
+
+package com.safeqr.app.qrcode.entity;
+
+import jakarta.persistence.*;
+import lombok.Data;
+
+@Entity
+@Table(name = "qr_code_types", schema = "safeqr")
+@Data
+public class QRCodeType {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ private String type;
+ private String description;
+ private String prefix;
+ private String tableName;
+}
diff --git a/src/main/java/com/safeqr/app/qrcode/entity/SafeBrowsingCache.java b/src/main/java/com/safeqr/app/qrcode/entity/SafeBrowsingCache.java
new file mode 100644
index 0000000..cfc0bc8
--- /dev/null
+++ b/src/main/java/com/safeqr/app/qrcode/entity/SafeBrowsingCache.java
@@ -0,0 +1,28 @@
+
+package com.safeqr.app.qrcode.entity;
+
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.Id;
+import jakarta.persistence.Table;
+import lombok.Data;
+import org.hibernate.annotations.GenericGenerator;
+
+import java.util.UUID;
+
+@Data
+@Entity
+@Table(name = "safe_browsing_cache", schema = "safeqr")
+public class SafeBrowsingCache {
+
+ @Id
+ @GeneratedValue(generator = "UUID")
+ @GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
+ private UUID id;
+
+ private String hashPrefix;
+ private String threatType;
+ private String platformType;
+ private String threatEntryType;
+ private String fullHash;
+}
diff --git a/src/main/java/com/safeqr/app/qrcode/repository/QRCodeTypeRepository.java b/src/main/java/com/safeqr/app/qrcode/repository/QRCodeTypeRepository.java
new file mode 100644
index 0000000..c99889b
--- /dev/null
+++ b/src/main/java/com/safeqr/app/qrcode/repository/QRCodeTypeRepository.java
@@ -0,0 +1,10 @@
+
+package com.safeqr.app.qrcode.repository;
+
+import com.safeqr.app.qrcode.entity.QRCodeType;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface QRCodeTypeRepository extends JpaRepository {
+}
diff --git a/src/main/java/com/safeqr/app/qrcode/repository/SafeBrowsingCacheRepository.java b/src/main/java/com/safeqr/app/qrcode/repository/SafeBrowsingCacheRepository.java
new file mode 100644
index 0000000..c96ff72
--- /dev/null
+++ b/src/main/java/com/safeqr/app/qrcode/repository/SafeBrowsingCacheRepository.java
@@ -0,0 +1,11 @@
+package com.safeqr.app.qrcode.repository;
+
+import com.safeqr.app.qrcode.entity.SafeBrowsingCache;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.Optional;
+import java.util.UUID;
+
+public interface SafeBrowsingCacheRepository extends JpaRepository {
+ Optional findByHashPrefix(String hashPrefix);
+}
\ No newline at end of file
diff --git a/src/main/java/com/safeqr/app/qrcode/service/QRCodeTypeService.java b/src/main/java/com/safeqr/app/qrcode/service/QRCodeTypeService.java
new file mode 100644
index 0000000..6b28244
--- /dev/null
+++ b/src/main/java/com/safeqr/app/qrcode/service/QRCodeTypeService.java
@@ -0,0 +1,50 @@
+
+package com.safeqr.app.qrcode.service;
+
+import com.safeqr.app.qrcode.dto.QRCodePayload;
+import com.safeqr.app.qrcode.entity.QRCodeType;
+import com.safeqr.app.qrcode.repository.QRCodeTypeRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import reactor.core.publisher.Mono;
+
+import java.security.NoSuchAlgorithmException;
+import java.util.List;
+
+@Service
+public class QRCodeTypeService {
+
+ @Autowired
+ private QRCodeTypeRepository qrCodeTypeRepository;
+
+ @Autowired
+ private SafeBrowsingService safeBrowsingService;
+
+ public List getAllTypes() {
+ return qrCodeTypeRepository.findAll();
+ }
+
+ public Mono detectType(QRCodePayload payload) {
+ String data = payload.getData();
+ List configs = qrCodeTypeRepository.findAll();
+
+ for (QRCodeType config : configs) {
+ if (data.startsWith(config.getPrefix())) {
+ if ("URL".equals(config.getType())) {
+ try
+ {
+ return safeBrowsingService.isSafeUrl(data)
+ .map(isSafe -> isSafe ? "Safe URL" : "Unsafe URL");
+ } catch (NoSuchAlgorithmException e)
+ {
+ // TODO Auto-generated catch block
+ return Mono.just("Error checking URL safety: " + e.getMessage());
+ }
+ }
+ return Mono.just(config.getType());
+ }
+ }
+
+ return Mono.just("Unknown");
+ }
+}
diff --git a/src/main/java/com/safeqr/app/qrcode/service/RedirectCountService.java b/src/main/java/com/safeqr/app/qrcode/service/RedirectCountService.java
new file mode 100644
index 0000000..9d77201
--- /dev/null
+++ b/src/main/java/com/safeqr/app/qrcode/service/RedirectCountService.java
@@ -0,0 +1,26 @@
+
+package com.safeqr.app.qrcode.service;
+
+import com.safeqr.app.qrcode.dto.QRCodePayload;
+import com.safeqr.app.qrcode.dto.RedirectCountResponse;
+import org.springframework.stereotype.Service;
+import org.springframework.web.reactive.function.client.WebClient;
+import reactor.core.publisher.Mono;
+
+@Service
+public class RedirectCountService {
+
+ public Mono countRedirects(QRCodePayload payload) {
+ String url = payload.getData();
+
+ return WebClient.create()
+ .get()
+ .uri(url)
+ .exchangeToMono(response -> {
+ RedirectCountResponse redirectCountResponse = new RedirectCountResponse();
+ redirectCountResponse.setRedirectCount(response.cookies().size());
+ redirectCountResponse.setMessage("Redirect count calculated.");
+ return Mono.just(redirectCountResponse);
+ });
+ }
+}
diff --git a/src/main/java/com/safeqr/app/qrcode/service/SafeBrowsingService.java b/src/main/java/com/safeqr/app/qrcode/service/SafeBrowsingService.java
new file mode 100644
index 0000000..455df4f
--- /dev/null
+++ b/src/main/java/com/safeqr/app/qrcode/service/SafeBrowsingService.java
@@ -0,0 +1,101 @@
+package com.safeqr.app.qrcode.service;
+
+import com.safeqr.app.qrcode.dto.SafeBrowsingRequest;
+import com.safeqr.app.qrcode.dto.SafeBrowsingResponse;
+import com.safeqr.app.qrcode.entity.SafeBrowsingCache;
+import com.safeqr.app.qrcode.repository.SafeBrowsingCacheRepository;
+
+import jakarta.annotation.PostConstruct;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Service;
+import org.springframework.web.reactive.function.client.WebClient;
+import reactor.core.publisher.Mono;
+
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Base64;
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+
+@Service
+public class SafeBrowsingService {
+
+ @Value("${google.safebrowsing.api.key}")
+ private String apiKey;
+
+ private final WebClient webClient;
+ private final SafeBrowsingCacheRepository cacheRepository;
+
+ public SafeBrowsingService(WebClient.Builder webClientBuilder, SafeBrowsingCacheRepository cacheRepository) {
+ this.webClient = webClientBuilder.baseUrl("https://safebrowsing.googleapis.com/v4/threatMatches:find").build();
+ this.cacheRepository = cacheRepository;
+ }
+
+ @PostConstruct
+ public void initializeCache() {
+ // Fetch the full list of hashes from Google and store them in the local database.
+ // This is a placeholder method. You need to implement the logic to fetch and store the hashes.
+ fetchAndStoreFullHashes();
+ }
+
+ public Mono isSafeUrl(String url) throws NoSuchAlgorithmException {
+ String hashPrefix = getHashPrefix(url);
+
+ Optional cachedResult = cacheRepository.findByHashPrefix(hashPrefix);
+ if (cachedResult.isPresent()) {
+ return Mono.just(cachedResult.get().getFullHash().isEmpty());
+ }
+
+ // If not in cache, call Google Safe Browsing API
+ String requestUrl = "?key=" + apiKey;
+
+ SafeBrowsingRequest request = new SafeBrowsingRequest(
+ new SafeBrowsingRequest.Client("safeqr-fyp-24", "1.0"),
+ new SafeBrowsingRequest.ThreatInfo(
+ List.of("MALWARE", "SOCIAL_ENGINEERING"),
+ List.of("WINDOWS"),
+ List.of("URL"),
+ List.of(new SafeBrowsingRequest.ThreatInfo.ThreatEntry(url))
+ )
+ );
+
+ return webClient.post()
+ .uri(requestUrl)
+ .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
+ .bodyValue(request)
+ .retrieve()
+ .bodyToMono(SafeBrowsingResponse.class)
+ .map(response -> {
+ boolean isSafe = response.getMatches() == null || response.getMatches().isEmpty();
+ if (!isSafe) {
+ SafeBrowsingResponse.ThreatMatch match = response.getMatches().get(0);
+ SafeBrowsingCache cache = new SafeBrowsingCache();
+ cache.setId(UUID.randomUUID());
+ cache.setHashPrefix(hashPrefix);
+ cache.setThreatType(match.getThreatType());
+ cache.setPlatformType(match.getPlatformType());
+ cache.setThreatEntryType(match.getThreatEntryType());
+ cache.setFullHash(match.getThreat().getHash());
+ cacheRepository.save(cache);
+ }
+ return isSafe;
+ });
+ }
+
+ private String getHashPrefix(String url) throws NoSuchAlgorithmException {
+ // Compute hash prefix of the URL (first 4 bytes of SHA-256)
+ MessageDigest digest = MessageDigest.getInstance("SHA-256");
+ byte[] hash = digest.digest(url.getBytes(StandardCharsets.UTF_8));
+ return Base64.getEncoder().encodeToString(hash).substring(0, 4);
+ }
+
+ private void fetchAndStoreFullHashes() {
+ // Implement the logic to fetch and store the full list of hashes from Google Safe Browsing API
+ // This could involve using the threatListUpdates:fetch endpoint
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/safeqr/app/qrcode/service/URLVerificationService.java b/src/main/java/com/safeqr/app/qrcode/service/URLVerificationService.java
new file mode 100644
index 0000000..1d362a6
--- /dev/null
+++ b/src/main/java/com/safeqr/app/qrcode/service/URLVerificationService.java
@@ -0,0 +1,28 @@
+package com.safeqr.app.qrcode.service;
+
+import com.safeqr.app.qrcode.dto.QRCodePayload;
+import com.safeqr.app.qrcode.dto.URLVerificationResponse;
+import org.springframework.stereotype.Service;
+
+@Service
+public class URLVerificationService {
+
+ public URLVerificationResponse verifyURL(QRCodePayload payload) {
+ URLVerificationResponse response = new URLVerificationResponse();
+ try {
+ java.net.URL url = new java.net.URL(payload.getData());
+ String protocol = url.getProtocol();
+ if ("https".equalsIgnoreCase(protocol)) {
+ response.setSecure(true);
+ response.setMessage("The connection is secure.");
+ } else {
+ response.setSecure(false);
+ response.setMessage("The connection is not secure.");
+ }
+ } catch (Exception e) {
+ response.setSecure(false);
+ response.setMessage("Invalid URL.");
+ }
+ return response;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/safeqr/app/qrcode/service/VirusTotalService.java b/src/main/java/com/safeqr/app/qrcode/service/VirusTotalService.java
new file mode 100644
index 0000000..7913357
--- /dev/null
+++ b/src/main/java/com/safeqr/app/qrcode/service/VirusTotalService.java
@@ -0,0 +1,92 @@
+
+package com.safeqr.app.qrcode.service;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.safeqr.app.qrcode.dto.QRCodePayload;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.*;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import java.util.Map;
+
+@Service
+public class VirusTotalService {
+
+ private static final Logger logger = LoggerFactory.getLogger(VirusTotalService.class);
+
+ @Value("${virustotal.api.key}")
+ private String apiKey;
+
+ private final RestTemplate restTemplate;
+ private final ObjectMapper objectMapper;
+
+ public VirusTotalService() {
+ this.restTemplate = new RestTemplate();
+ this.objectMapper = new ObjectMapper();
+ }
+
+ public String scanURL(QRCodePayload payload) {
+ String urlToScan = payload.getData();
+ logger.info("Scanning URL: {}", urlToScan);
+
+ UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl("https://www.virustotal.com/api/v3/urls");
+
+ HttpHeaders headers = new HttpHeaders();
+ headers.set("accept", "application/json");
+ headers.set("content-type", "application/x-www-form-urlencoded");
+ headers.set("x-apikey", apiKey);
+
+ String body = "url=" + urlToScan;
+ HttpEntity request = new HttpEntity<>(body, headers);
+
+ ResponseEntity response = restTemplate.postForEntity(builder.toUriString(), request, String.class);
+ logger.info("Response from VirusTotal scan: {}", response.getBody());
+
+ try {
+ Map responseBody = objectMapper.readValue(response.getBody(), Map.class);
+ Map data = (Map) responseBody.get("data");
+ return (String) data.get("id");
+ } catch (Exception e) {
+ logger.error("Error parsing response from VirusTotal scan", e);
+ throw new RuntimeException("Error parsing response from VirusTotal scan", e);
+ }
+ }
+
+ public boolean getAnalysis(String analysisId) {
+ logger.info("Retrieving analysis for ID: {}", analysisId);
+
+ UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl("https://www.virustotal.com/api/v3/analyses/" + analysisId);
+
+ HttpHeaders headers = new HttpHeaders();
+ headers.set("accept", "application/json");
+ headers.set("x-apikey", apiKey);
+
+ HttpEntity request = new HttpEntity<>(headers);
+
+ ResponseEntity response = restTemplate.exchange(builder.toUriString(), HttpMethod.GET, request, String.class);
+ logger.info("Response from VirusTotal analysis: {}", response.getBody());
+
+ try {
+ Map responseBody = objectMapper.readValue(response.getBody(), Map.class);
+ Map data = (Map) responseBody.get("data");
+ Map attributes = (Map) data.get("attributes");
+ Map stats = (Map) attributes.get("stats");
+
+ return evaluateSafety(stats);
+ } catch (Exception e) {
+ logger.error("Error parsing response from VirusTotal analysis", e);
+ throw new RuntimeException("Error parsing response from VirusTotal analysis", e);
+ }
+ }
+
+ private boolean evaluateSafety(Map stats) {
+ int malicious = stats.getOrDefault("malicious", 0);
+ int suspicious = stats.getOrDefault("suspicious", 0);
+
+ return malicious < 5 && suspicious < 5;
+ }
+}
diff --git a/src/main/java/com/safeqr/app/user/controller/UserController.java b/src/main/java/com/safeqr/app/user/controller/UserController.java
index 27e98a5..b7bb191 100644
--- a/src/main/java/com/safeqr/app/user/controller/UserController.java
+++ b/src/main/java/com/safeqr/app/user/controller/UserController.java
@@ -8,16 +8,18 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
+import java.util.Map;
+
@RestController
@RequestMapping("/v1")
public class UserController {
@Autowired
UserService userService;
@GetMapping(value = "/version", produces = MediaType.APPLICATION_JSON_VALUE)
- public ResponseEntity version() {
- System.out.println(userService.getUserByEmail());
- return ResponseEntity.ok("SafeQR v1.0.0");
-
+ public ResponseEntity