diff --git a/README.md b/README.md index 1911ca5..ff72c0f 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ cd backend mvn spring-boot:run ``` -后端运行在 http://localhost:8080 +后端运行在 http://localhost:48080 默认登录:`admin` / `admin123` diff --git a/backend/src/main/java/com/sshmanager/controller/SftpController.java b/backend/src/main/java/com/sshmanager/controller/SftpController.java index fae8e7d..883bfe6 100644 --- a/backend/src/main/java/com/sshmanager/controller/SftpController.java +++ b/backend/src/main/java/com/sshmanager/controller/SftpController.java @@ -9,12 +9,13 @@ import com.sshmanager.service.ConnectionService; import com.sshmanager.service.SftpService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.security.core.Authentication; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.multipart.MultipartFile; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import java.util.HashMap; import java.util.List; @@ -57,12 +58,27 @@ public class SftpController { return userId + ":" + connectionId; } - private T withSessionLock(String key, Supplier action) { - Object lock = sessionLocks.computeIfAbsent(key, k -> new Object()); - synchronized (lock) { - return action.get(); - } - } + private T withSessionLock(String key, Supplier action) { + Object lock = sessionLocks.computeIfAbsent(key, k -> new Object()); + synchronized (lock) { + return action.get(); + } + } + + private T withTwoSessionLocks(String keyA, String keyB, Supplier action) { + if (keyA.equals(keyB)) { + return withSessionLock(keyA, action); + } + String first = keyA.compareTo(keyB) < 0 ? keyA : keyB; + String second = keyA.compareTo(keyB) < 0 ? keyB : keyA; + Object firstLock = sessionLocks.computeIfAbsent(first, k -> new Object()); + Object secondLock = sessionLocks.computeIfAbsent(second, k -> new Object()); + synchronized (firstLock) { + synchronized (secondLock) { + return action.get(); + } + } + } private SftpService.SftpSession getOrCreateSession(Long connectionId, Long userId) throws Exception { String key = sessionKey(userId, connectionId); @@ -160,35 +176,38 @@ public class SftpController { } } - @GetMapping("/download") - public ResponseEntity download( - @RequestParam Long connectionId, - @RequestParam String path, - Authentication authentication) { - try { - Long userId = getCurrentUserId(authentication); - String key = sessionKey(userId, connectionId); - return withSessionLock(key, () -> { - try { - SftpService.SftpSession session = getOrCreateSession(connectionId, userId); - byte[] data = sftpService.download(session, path); - String filename = path.contains("/") ? path.substring(path.lastIndexOf('/') + 1) : path; - return ResponseEntity.ok() - .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + filename + "\"") - .contentType(MediaType.APPLICATION_OCTET_STREAM) - .body(data); - } catch (Exception e) { - SftpService.SftpSession existing = sessions.remove(key); - if (existing != null) { - existing.disconnect(); - } - throw new RuntimeException(e); - } - }); - } catch (Exception e) { - return ResponseEntity.status(500).build(); - } - } + @GetMapping("/download") + public ResponseEntity download( + @RequestParam Long connectionId, + @RequestParam String path, + Authentication authentication) { + try { + Long userId = getCurrentUserId(authentication); + String key = sessionKey(userId, connectionId); + String filename = path.contains("/") ? path.substring(path.lastIndexOf('/') + 1) : path; + StreamingResponseBody stream = outputStream -> withSessionLock(key, () -> { + try { + SftpService.SftpSession session = getOrCreateSession(connectionId, userId); + sftpService.download(session, path, outputStream); + outputStream.flush(); + return null; + } catch (Exception e) { + SftpService.SftpSession existing = sessions.remove(key); + if (existing != null) { + existing.disconnect(); + } + throw new RuntimeException(e); + } + }); + + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + filename + "\"") + .contentType(MediaType.APPLICATION_OCTET_STREAM) + .body(stream); + } catch (Exception e) { + return ResponseEntity.status(500).build(); + } + } @PostMapping("/upload") public ResponseEntity> upload( @@ -202,13 +221,15 @@ public class SftpController { return withSessionLock(key, () -> { try { SftpService.SftpSession session = getOrCreateSession(connectionId, userId); - String remotePath = (path == null || path.isEmpty() || path.equals("/")) - ? "/" + file.getOriginalFilename() - : (path.endsWith("/") ? path + file.getOriginalFilename() : path + "/" + file.getOriginalFilename()); - sftpService.upload(session, remotePath, file.getBytes()); - Map result = new HashMap<>(); - result.put("message", "Uploaded"); - return ResponseEntity.ok(result); + String remotePath = (path == null || path.isEmpty() || path.equals("/")) + ? "/" + file.getOriginalFilename() + : (path.endsWith("/") ? path + file.getOriginalFilename() : path + "/" + file.getOriginalFilename()); + try (java.io.InputStream in = file.getInputStream()) { + sftpService.upload(session, remotePath, in); + } + Map result = new HashMap<>(); + result.put("message", "Uploaded"); + return ResponseEntity.ok(result); } catch (Exception e) { SftpService.SftpSession existing = sessions.remove(key); if (existing != null) { @@ -317,36 +338,55 @@ public class SftpController { } @PostMapping("/transfer-remote") - public ResponseEntity> transferRemote( - @RequestParam Long sourceConnectionId, - @RequestParam String sourcePath, - @RequestParam Long targetConnectionId, - @RequestParam String targetPath, - Authentication authentication) { - try { - Long userId = getCurrentUserId(authentication); + public ResponseEntity> transferRemote( + @RequestParam Long sourceConnectionId, + @RequestParam String sourcePath, + @RequestParam Long targetConnectionId, + @RequestParam String targetPath, + Authentication authentication) { + try { + Long userId = getCurrentUserId(authentication); if (sourcePath == null || sourcePath.trim().isEmpty()) { Map err = new HashMap<>(); err.put("error", "sourcePath is required"); return ResponseEntity.badRequest().body(err); } - if (targetPath == null || targetPath.trim().isEmpty()) { - Map err = new HashMap<>(); - err.put("error", "targetPath is required"); - return ResponseEntity.badRequest().body(err); - } - SftpService.SftpSession sourceSession = getOrCreateSession(sourceConnectionId, userId); - SftpService.SftpSession targetSession = getOrCreateSession(targetConnectionId, userId); - if (sourceConnectionId.equals(targetConnectionId)) { - sftpService.rename(sourceSession, sourcePath.trim(), targetPath.trim()); - } else { - sftpService.transferRemote(sourceSession, sourcePath.trim(), targetSession, targetPath.trim()); - } - Map result = new HashMap<>(); - result.put("message", "Transferred"); - return ResponseEntity.ok(result); - } catch (Exception e) { - Map error = new HashMap<>(); + if (targetPath == null || targetPath.trim().isEmpty()) { + Map err = new HashMap<>(); + err.put("error", "targetPath is required"); + return ResponseEntity.badRequest().body(err); + } + String sourceKey = sessionKey(userId, sourceConnectionId); + String targetKey = sessionKey(userId, targetConnectionId); + withTwoSessionLocks(sourceKey, targetKey, () -> { + try { + SftpService.SftpSession sourceSession = getOrCreateSession(sourceConnectionId, userId); + SftpService.SftpSession targetSession = getOrCreateSession(targetConnectionId, userId); + if (sourceConnectionId.equals(targetConnectionId)) { + sftpService.rename(sourceSession, sourcePath.trim(), targetPath.trim()); + } else { + sftpService.transferRemote(sourceSession, sourcePath.trim(), targetSession, targetPath.trim()); + } + return null; + } catch (Exception e) { + SftpService.SftpSession source = sessions.remove(sourceKey); + if (source != null) { + source.disconnect(); + } + if (!sourceKey.equals(targetKey)) { + SftpService.SftpSession target = sessions.remove(targetKey); + if (target != null) { + target.disconnect(); + } + } + throw new RuntimeException(e); + } + }); + Map result = new HashMap<>(); + result.put("message", "Transferred"); + return ResponseEntity.ok(result); + } catch (Exception e) { + Map error = new HashMap<>(); error.put("error", e.getMessage() != null ? e.getMessage() : "Transfer failed"); return ResponseEntity.status(500).body(error); } diff --git a/backend/src/main/java/com/sshmanager/service/ConnectionService.java b/backend/src/main/java/com/sshmanager/service/ConnectionService.java index 38aa2d2..b8a387a 100644 --- a/backend/src/main/java/com/sshmanager/service/ConnectionService.java +++ b/backend/src/main/java/com/sshmanager/service/ConnectionService.java @@ -45,17 +45,18 @@ public class ConnectionService { conn.setName(request.getName()); conn.setHost(request.getHost()); conn.setPort(request.getPort() != null ? request.getPort() : 22); - conn.setUsername(request.getUsername()); - conn.setAuthType(request.getAuthType() != null ? request.getAuthType() : Connection.AuthType.PASSWORD); - - if (conn.getAuthType() == Connection.AuthType.PASSWORD && request.getPassword() != null) { - conn.setEncryptedPassword(encryptionService.encrypt(request.getPassword())); - } else if (conn.getAuthType() == Connection.AuthType.PRIVATE_KEY && request.getPrivateKey() != null) { - conn.setEncryptedPrivateKey(encryptionService.encrypt(request.getPrivateKey())); - if (request.getPassphrase() != null && !request.getPassphrase().isEmpty()) { - conn.setPassphrase(encryptionService.encrypt(request.getPassphrase())); - } - } + conn.setUsername(request.getUsername()); + conn.setAuthType(request.getAuthType() != null ? request.getAuthType() : Connection.AuthType.PASSWORD); + + if (conn.getAuthType() == Connection.AuthType.PASSWORD) { + conn.setEncryptedPassword(encryptionService.encrypt(request.getPassword())); + conn.setEncryptedPrivateKey(null); + conn.setPassphrase(null); + } else { + conn.setEncryptedPassword(null); + conn.setEncryptedPrivateKey(encryptionService.encrypt(request.getPrivateKey())); + conn.setPassphrase(encryptionService.encrypt(request.getPassphrase())); + } conn = connectionRepository.save(conn); return ConnectionDto.fromEntity(conn); @@ -69,22 +70,27 @@ public class ConnectionService { throw new RuntimeException("Access denied"); } - if (request.getName() != null) conn.setName(request.getName()); - if (request.getHost() != null) conn.setHost(request.getHost()); - if (request.getPort() != null) conn.setPort(request.getPort()); - if (request.getUsername() != null) conn.setUsername(request.getUsername()); - if (request.getAuthType() != null) conn.setAuthType(request.getAuthType()); - - if (request.getPassword() != null) { - conn.setEncryptedPassword(encryptionService.encrypt(request.getPassword())); - } - if (request.getPrivateKey() != null) { - conn.setEncryptedPrivateKey(encryptionService.encrypt(request.getPrivateKey())); - } - if (request.getPassphrase() != null) { - conn.setPassphrase(request.getPassphrase().isEmpty() ? null : - encryptionService.encrypt(request.getPassphrase())); - } + if (request.getName() != null) conn.setName(request.getName()); + if (request.getHost() != null) conn.setHost(request.getHost()); + if (request.getPort() != null) conn.setPort(request.getPort()); + if (request.getUsername() != null) conn.setUsername(request.getUsername()); + if (request.getAuthType() != null) conn.setAuthType(request.getAuthType()); + + if (conn.getAuthType() == Connection.AuthType.PASSWORD) { + if (request.getPassword() != null) { + conn.setEncryptedPassword(encryptionService.encrypt(request.getPassword())); + } + conn.setEncryptedPrivateKey(null); + conn.setPassphrase(null); + } else { + if (request.getPrivateKey() != null) { + conn.setEncryptedPrivateKey(encryptionService.encrypt(request.getPrivateKey())); + } + if (request.getPassphrase() != null) { + conn.setPassphrase(encryptionService.encrypt(request.getPassphrase())); + } + conn.setEncryptedPassword(null); + } conn.setUpdatedAt(Instant.now()); conn = connectionRepository.save(conn); diff --git a/backend/src/main/java/com/sshmanager/service/SftpService.java b/backend/src/main/java/com/sshmanager/service/SftpService.java index a87f550..55f368e 100644 --- a/backend/src/main/java/com/sshmanager/service/SftpService.java +++ b/backend/src/main/java/com/sshmanager/service/SftpService.java @@ -4,15 +4,14 @@ import com.jcraft.jsch.ChannelSftp; import com.jcraft.jsch.JSch; import com.jcraft.jsch.Session; import com.jcraft.jsch.SftpException; -import com.sshmanager.entity.Connection; -import org.springframework.stereotype.Service; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.InputStream; -import java.io.PipedInputStream; -import java.io.PipedOutputStream; -import java.nio.charset.StandardCharsets; +import com.sshmanager.entity.Connection; +import org.springframework.stereotype.Service; + +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Vector; @@ -149,15 +148,13 @@ public class SftpService { } } - public byte[] download(SftpSession sftpSession, String remotePath) throws Exception { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - sftpSession.getChannel().get(remotePath, out); - return out.toByteArray(); - } - - public void upload(SftpSession sftpSession, String remotePath, byte[] data) throws Exception { - sftpSession.getChannel().put(new ByteArrayInputStream(data), remotePath); - } + public void download(SftpSession sftpSession, String remotePath, OutputStream out) throws Exception { + sftpSession.getChannel().get(remotePath, out); + } + + public void upload(SftpSession sftpSession, String remotePath, InputStream in) throws Exception { + sftpSession.getChannel().put(in, remotePath); + } public void delete(SftpSession sftpSession, String remotePath, boolean isDir) throws Exception { if (isDir) { diff --git a/backend/src/test/java/com/sshmanager/controller/SftpControllerTest.java b/backend/src/test/java/com/sshmanager/controller/SftpControllerTest.java new file mode 100644 index 0000000..496d858 --- /dev/null +++ b/backend/src/test/java/com/sshmanager/controller/SftpControllerTest.java @@ -0,0 +1,149 @@ +package com.sshmanager.controller; + +import com.jcraft.jsch.ChannelSftp; +import com.jcraft.jsch.Session; +import com.sshmanager.entity.Connection; +import com.sshmanager.entity.User; +import com.sshmanager.repository.UserRepository; +import com.sshmanager.service.ConnectionService; +import com.sshmanager.service.SftpService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; + +import java.util.Map; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class SftpControllerTest { + + @Mock + private ConnectionService connectionService; + + @Mock + private UserRepository userRepository; + + @Mock + private SftpService sftpService; + + @InjectMocks + private SftpController sftpController; + + @BeforeEach + void setUp() { + User user = new User(); + user.setId(1L); + user.setUsername("alice"); + when(userRepository.findByUsername("alice")).thenReturn(Optional.of(user)); + } + + @Test + void transferRemoteReturnsBadRequestWhenSourcePathMissing() { + ResponseEntity> response = sftpController.transferRemote( + 1L, + " ", + 2L, + "/tmp/target.txt", + authentication() + ); + + assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); + assertEquals("sourcePath is required", response.getBody().get("error")); + verifyNoInteractions(sftpService); + } + + @Test + void transferRemoteUsesRenameWhenSourceAndTargetConnectionAreSame() throws Exception { + when(connectionService.getConnectionForSsh(anyLong(), eq(1L))).thenReturn(new Connection()); + SftpService.SftpSession session = connectedSession(true); + when(sftpService.connect(any(Connection.class), any(), any(), any())).thenReturn(session); + + ResponseEntity> response = sftpController.transferRemote( + 3L, + "/src/file.txt", + 3L, + "/dst/file.txt", + authentication() + ); + + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals("Transferred", response.getBody().get("message")); + verify(sftpService).rename(session, "/src/file.txt", "/dst/file.txt"); + verify(sftpService, never()).transferRemote(any(), any(), any(), any()); + } + + @Test + void transferRemoteUsesCrossSessionTransferWhenConnectionsDiffer() throws Exception { + when(connectionService.getConnectionForSsh(anyLong(), eq(1L))).thenReturn(new Connection()); + SftpService.SftpSession sourceSession = connectedSession(false); + SftpService.SftpSession targetSession = connectedSession(false); + when(sftpService.connect(any(Connection.class), any(), any(), any())) + .thenReturn(sourceSession) + .thenReturn(targetSession); + + ResponseEntity> response = sftpController.transferRemote( + 10L, + "/src/file.txt", + 20L, + "/dst/file.txt", + authentication() + ); + + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals("Transferred", response.getBody().get("message")); + verify(sftpService).transferRemote(sourceSession, "/src/file.txt", targetSession, "/dst/file.txt"); + verify(sftpService, never()).rename(any(), any(), any()); + } + + @Test + void transferRemoteReturnsServerErrorWhenTransferFails() throws Exception { + when(connectionService.getConnectionForSsh(anyLong(), eq(1L))).thenReturn(new Connection()); + SftpService.SftpSession session = connectedSession(true); + when(sftpService.connect(any(Connection.class), any(), any(), any())).thenReturn(session); + doThrow(new RuntimeException("boom")).when(sftpService).rename(any(), any(), any()); + + ResponseEntity> response = sftpController.transferRemote( + 3L, + "/src/file.txt", + 3L, + "/dst/file.txt", + authentication() + ); + + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode()); + assertTrue(response.getBody().get("error").contains("boom")); + } + + private Authentication authentication() { + Authentication authentication = mock(Authentication.class); + when(authentication.getName()).thenReturn("alice"); + return authentication; + } + + private SftpService.SftpSession connectedSession(boolean connected) { + Session session = mock(Session.class); + ChannelSftp channel = mock(ChannelSftp.class); + if (connected) { + when(channel.isConnected()).thenReturn(true); + } + return new SftpService.SftpSession(session, channel); + } +} diff --git a/backend/src/test/java/com/sshmanager/service/ConnectionServiceTest.java b/backend/src/test/java/com/sshmanager/service/ConnectionServiceTest.java new file mode 100644 index 0000000..d7c7273 --- /dev/null +++ b/backend/src/test/java/com/sshmanager/service/ConnectionServiceTest.java @@ -0,0 +1,152 @@ +package com.sshmanager.service; + +import com.sshmanager.dto.ConnectionCreateRequest; +import com.sshmanager.dto.ConnectionDto; +import com.sshmanager.entity.Connection; +import com.sshmanager.repository.ConnectionRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class ConnectionServiceTest { + + @Mock + private ConnectionRepository connectionRepository; + + @Mock + private EncryptionService encryptionService; + + @InjectMocks + private ConnectionService connectionService; + + @BeforeEach + void setUp() { + when(connectionRepository.save(any(Connection.class))).thenAnswer(invocation -> invocation.getArgument(0)); + } + + @Test + void createPasswordConnectionClearsPrivateKeyCredentials() { + ConnectionCreateRequest request = new ConnectionCreateRequest(); + request.setName("prod"); + request.setHost("127.0.0.1"); + request.setUsername("root"); + request.setAuthType(Connection.AuthType.PASSWORD); + request.setPassword("secret"); + request.setPrivateKey("unused-key"); + request.setPassphrase("unused-passphrase"); + + when(encryptionService.encrypt("secret")).thenReturn("enc-secret"); + + ConnectionDto result = connectionService.create(request, 1L); + + assertNotNull(result); + ArgumentCaptor captor = ArgumentCaptor.forClass(Connection.class); + verify(connectionRepository).save(captor.capture()); + Connection saved = captor.getValue(); + + assertEquals(Connection.AuthType.PASSWORD, saved.getAuthType()); + assertEquals("enc-secret", saved.getEncryptedPassword()); + assertNull(saved.getEncryptedPrivateKey()); + assertNull(saved.getPassphrase()); + } + + @Test + void createPrivateKeyConnectionClearsPasswordCredential() { + ConnectionCreateRequest request = new ConnectionCreateRequest(); + request.setName("prod"); + request.setHost("127.0.0.1"); + request.setUsername("root"); + request.setAuthType(Connection.AuthType.PRIVATE_KEY); + request.setPassword("unused-password"); + request.setPrivateKey("private-key"); + request.setPassphrase("passphrase"); + + when(encryptionService.encrypt("private-key")).thenReturn("enc-key"); + when(encryptionService.encrypt("passphrase")).thenReturn("enc-passphrase"); + + ConnectionDto result = connectionService.create(request, 1L); + + assertNotNull(result); + ArgumentCaptor captor = ArgumentCaptor.forClass(Connection.class); + verify(connectionRepository).save(captor.capture()); + Connection saved = captor.getValue(); + + assertEquals(Connection.AuthType.PRIVATE_KEY, saved.getAuthType()); + assertEquals("enc-key", saved.getEncryptedPrivateKey()); + assertEquals("enc-passphrase", saved.getPassphrase()); + assertNull(saved.getEncryptedPassword()); + } + + @Test + void updateSwitchToPrivateKeyClearsPasswordCredential() { + Connection existing = new Connection(); + existing.setId(10L); + existing.setUserId(1L); + existing.setAuthType(Connection.AuthType.PASSWORD); + existing.setEncryptedPassword("old-password"); + + ConnectionCreateRequest request = new ConnectionCreateRequest(); + request.setAuthType(Connection.AuthType.PRIVATE_KEY); + request.setPrivateKey("new-key"); + request.setPassphrase("new-passphrase"); + + when(connectionRepository.findById(10L)).thenReturn(Optional.of(existing)); + when(encryptionService.encrypt("new-key")).thenReturn("enc-new-key"); + when(encryptionService.encrypt("new-passphrase")).thenReturn("enc-new-passphrase"); + + ConnectionDto result = connectionService.update(10L, request, 1L); + + assertNotNull(result); + ArgumentCaptor captor = ArgumentCaptor.forClass(Connection.class); + verify(connectionRepository).save(captor.capture()); + Connection saved = captor.getValue(); + + assertEquals(Connection.AuthType.PRIVATE_KEY, saved.getAuthType()); + assertEquals("enc-new-key", saved.getEncryptedPrivateKey()); + assertEquals("enc-new-passphrase", saved.getPassphrase()); + assertNull(saved.getEncryptedPassword()); + } + + @Test + void updateSwitchToPasswordClearsPrivateKeyCredentials() { + Connection existing = new Connection(); + existing.setId(20L); + existing.setUserId(1L); + existing.setAuthType(Connection.AuthType.PRIVATE_KEY); + existing.setEncryptedPrivateKey("old-key"); + existing.setPassphrase("old-passphrase"); + + ConnectionCreateRequest request = new ConnectionCreateRequest(); + request.setAuthType(Connection.AuthType.PASSWORD); + request.setPassword("new-password"); + + when(connectionRepository.findById(20L)).thenReturn(Optional.of(existing)); + when(encryptionService.encrypt("new-password")).thenReturn("enc-new-password"); + + ConnectionDto result = connectionService.update(20L, request, 1L); + + assertNotNull(result); + ArgumentCaptor captor = ArgumentCaptor.forClass(Connection.class); + verify(connectionRepository).save(captor.capture()); + Connection saved = captor.getValue(); + + assertEquals(Connection.AuthType.PASSWORD, saved.getAuthType()); + assertEquals("enc-new-password", saved.getEncryptedPassword()); + assertNull(saved.getEncryptedPrivateKey()); + assertNull(saved.getPassphrase()); + } +} diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 7cf585d..1e5e574 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -637,9 +637,9 @@ "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", - "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", "cpu": [ "arm" ], @@ -651,9 +651,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", - "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", "cpu": [ "arm64" ], @@ -665,9 +665,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", - "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", "cpu": [ "arm64" ], @@ -679,9 +679,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", - "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", "cpu": [ "x64" ], @@ -693,9 +693,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", - "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", "cpu": [ "arm64" ], @@ -707,9 +707,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", - "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", "cpu": [ "x64" ], @@ -721,9 +721,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", - "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", "cpu": [ "arm" ], @@ -735,9 +735,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", - "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", "cpu": [ "arm" ], @@ -749,9 +749,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", - "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", "cpu": [ "arm64" ], @@ -763,9 +763,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", - "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", "cpu": [ "arm64" ], @@ -777,9 +777,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", - "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", "cpu": [ "loong64" ], @@ -791,9 +791,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", - "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", "cpu": [ "loong64" ], @@ -805,9 +805,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", - "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", "cpu": [ "ppc64" ], @@ -819,9 +819,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", - "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", "cpu": [ "ppc64" ], @@ -833,9 +833,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", - "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", "cpu": [ "riscv64" ], @@ -847,9 +847,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", - "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", "cpu": [ "riscv64" ], @@ -861,9 +861,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", - "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", "cpu": [ "s390x" ], @@ -875,9 +875,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", - "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", "cpu": [ "x64" ], @@ -889,9 +889,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", - "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", "cpu": [ "x64" ], @@ -903,9 +903,9 @@ ] }, "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", - "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", "cpu": [ "x64" ], @@ -917,9 +917,9 @@ ] }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", - "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", "cpu": [ "arm64" ], @@ -931,9 +931,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", - "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", "cpu": [ "arm64" ], @@ -945,9 +945,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", - "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", "cpu": [ "ia32" ], @@ -959,9 +959,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", - "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", "cpu": [ "x64" ], @@ -973,9 +973,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", - "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", "cpu": [ "x64" ], @@ -1402,13 +1402,13 @@ } }, "node_modules/axios": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.4.tgz", - "integrity": "sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg==", + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz", + "integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==", "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, @@ -2744,9 +2744,9 @@ "license": "MIT" }, "node_modules/rollup": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", - "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", "dev": true, "license": "MIT", "dependencies": { @@ -2760,31 +2760,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.57.1", - "@rollup/rollup-android-arm64": "4.57.1", - "@rollup/rollup-darwin-arm64": "4.57.1", - "@rollup/rollup-darwin-x64": "4.57.1", - "@rollup/rollup-freebsd-arm64": "4.57.1", - "@rollup/rollup-freebsd-x64": "4.57.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", - "@rollup/rollup-linux-arm-musleabihf": "4.57.1", - "@rollup/rollup-linux-arm64-gnu": "4.57.1", - "@rollup/rollup-linux-arm64-musl": "4.57.1", - "@rollup/rollup-linux-loong64-gnu": "4.57.1", - "@rollup/rollup-linux-loong64-musl": "4.57.1", - "@rollup/rollup-linux-ppc64-gnu": "4.57.1", - "@rollup/rollup-linux-ppc64-musl": "4.57.1", - "@rollup/rollup-linux-riscv64-gnu": "4.57.1", - "@rollup/rollup-linux-riscv64-musl": "4.57.1", - "@rollup/rollup-linux-s390x-gnu": "4.57.1", - "@rollup/rollup-linux-x64-gnu": "4.57.1", - "@rollup/rollup-linux-x64-musl": "4.57.1", - "@rollup/rollup-openbsd-x64": "4.57.1", - "@rollup/rollup-openharmony-arm64": "4.57.1", - "@rollup/rollup-win32-arm64-msvc": "4.57.1", - "@rollup/rollup-win32-ia32-msvc": "4.57.1", - "@rollup/rollup-win32-x64-gnu": "4.57.1", - "@rollup/rollup-win32-x64-msvc": "4.57.1", + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", "fsevents": "~2.3.2" } },