source: backend/web3-Ednevnik-Backend/src/main/java/com/nikola/web3ednevnikbackend/controller/MetamuskController.java

Last change on this file was 81cdf2d, checked in by Nikola Janev <nikolajanev57@…>, 9 days ago

initial commit

  • Property mode set to 100644
File size: 5.4 KB
Line 
1package com.nikola.web3ednevnikbackend.controller;
2
3
4import com.nikola.web3ednevnikbackend.config.security.CustomUserDetails;
5import com.nikola.web3ednevnikbackend.dto.users.BlagajnikResponseDTO;
6import com.nikola.web3ednevnikbackend.dto.users.RoditelResponseDTO;
7import com.nikola.web3ednevnikbackend.service.users.BlagajnikService;
8import com.nikola.web3ednevnikbackend.service.users.RoditelService;
9
10import org.springframework.http.ResponseEntity;
11import org.springframework.security.core.Authentication;
12import org.springframework.web.bind.annotation.*;
13import org.web3j.crypto.*;
14import org.web3j.utils.Numeric;
15
16import java.math.BigInteger;
17import java.nio.charset.StandardCharsets;
18import java.util.*;
19import java.util.concurrent.ConcurrentHashMap;
20
21@RestController
22@RequestMapping("/metamuskAuth")
23public class MetamuskController {
24 private final Map<String, String> nonces = new ConcurrentHashMap<>();
25
26
27 private final RoditelService roditelService;
28 private final BlagajnikService blagajnikService;
29
30 public MetamuskController(RoditelService roditelService, BlagajnikService blagajnikService) {
31 this.roditelService = roditelService;
32 this.blagajnikService = blagajnikService;
33 }
34
35
36 @GetMapping("/nonce")
37 public Map<String, String> getNonce(@RequestParam String address) {
38 String normalized = address.toLowerCase();
39
40 String nonce = UUID.randomUUID().toString();
41 nonces.putIfAbsent(normalized, nonce);
42
43 return Map.of("nonce", nonce);
44 }
45
46 //TODO: @PostMapping("/unlink-wallet") //logika samo za roditel (roditel moze da pravi unlink)
47 @PostMapping("/unlink-wallet")
48 public ResponseEntity<?> unlinkWallet(Authentication authentication) {
49
50 if (authentication == null || !authentication.isAuthenticated()) {
51 return ResponseEntity.status(401)
52 .body(Map.of("message", "Unauthorized"));
53 }
54
55 CustomUserDetails userDetails =
56 (CustomUserDetails) authentication.getPrincipal();
57
58 UUID roditelId = userDetails.getId();
59
60 RoditelResponseDTO roditel = roditelService.getRoditelById(roditelId);
61
62 if (roditel.getMetamusk_adresa() == null) {
63 return ResponseEntity.badRequest()
64 .body(Map.of("message", "No wallet linked"));
65 }
66
67 roditelService.unlinkMetamuskAddress(roditelId);
68
69 return ResponseEntity.ok(Map.of("message", "Wallet unlinked successfully"));
70 }
71
72
73
74 @PostMapping("/link-wallet-roditel") //logika samo za roditel (roditel moze da go menuva wallet)
75 public ResponseEntity<?> linkWalletParent(
76 @RequestBody Map<String, String> payload,
77 Authentication authentication
78 ) {
79
80 if (authentication == null || !authentication.isAuthenticated()) {
81 return ResponseEntity.status(401)
82 .body(Map.of("message", "Unauthorized"));
83 }
84
85 String address = payload.get("address");
86 String signature = payload.get("signature");
87
88 if (address == null || signature == null) {
89 return ResponseEntity.badRequest()
90 .body(Map.of("message", "Missing data"));
91 }
92
93 address = address.trim();
94 if (!WalletUtils.isValidAddress(address)) {
95 return ResponseEntity.badRequest()
96 .body(Map.of("message", "Invalid Ethereum address"));
97 }
98 String normalized = address.toLowerCase();
99
100 String nonce = nonces.get(normalized);
101
102 if (nonce == null) {
103 return ResponseEntity.badRequest()
104 .body(Map.of("message", "Nonce not found"));
105 }
106
107 boolean valid = verifySignature(address, signature, nonce);
108
109 if (!valid) {
110 return ResponseEntity.status(401)
111 .body(Map.of("message", "Invalid signature"));
112 }
113
114 nonces.remove(normalized);
115
116 CustomUserDetails userDetails =
117 (CustomUserDetails) authentication.getPrincipal();
118
119 UUID roditelId = userDetails.getId();
120
121 RoditelResponseDTO roditel = roditelService.getRoditelById(roditelId);
122
123
124 roditelService.updateMetamuskAddress(roditelId, normalized);
125 System.out.println("Linked wallet attempt for roditel id " + roditelId + " with address" + normalized);
126
127
128 return ResponseEntity.ok(Map.of("message", "Wallet linked successfully"));
129 }
130
131
132 public boolean verifySignature(String expectedAddress, String signatureHex, String message) {
133 try {
134 byte[] messageBytes = message.getBytes(StandardCharsets.UTF_8);
135 byte[] sigBytes = Numeric.hexStringToByteArray(signatureHex);
136
137 if (sigBytes.length != 65) {
138 return false;
139 }
140
141 byte v = sigBytes[64];
142 if (v < 27) {
143 v += 27;
144 }
145
146 Sign.SignatureData sigData = new Sign.SignatureData(
147 v,
148 Arrays.copyOfRange(sigBytes, 0, 32),
149 Arrays.copyOfRange(sigBytes, 32, 64)
150 );
151
152
153 BigInteger publicKey = Sign.signedPrefixedMessageToKey(messageBytes, sigData);
154
155 String recoveredAddress = "0x" + Keys.getAddress(publicKey);
156
157 System.out.println("Recovered: " + recoveredAddress);
158
159 return recoveredAddress.equalsIgnoreCase(expectedAddress);
160
161 } catch (Exception e) {
162 e.printStackTrace();
163 return false;
164 }
165 }
166
167
168}
Note: See TracBrowser for help on using the repository browser.