diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml
index 667784f..ab6c3f8 100644
--- a/.github/workflows/maven.yml
+++ b/.github/workflows/maven.yml
@@ -28,8 +28,4 @@ jobs:
distribution: 'temurin'
cache: maven
- name: Verify with Maven
- run: mvn -B -DskipTests verify --file pom.xml
-
- # Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive
- - name: Update dependency graph
- uses: advanced-security/maven-dependency-submission-action@571e99aab1055c2e71a1e2309b9691de18d6b7d6
+ run: mvn -B -DskipTests verify --file pom.xml
\ No newline at end of file
diff --git a/docs/openapi/components/schemas/SignInStartResponse.yaml b/docs/openapi/components/schemas/SignInStartResponse.yaml
index 12b9384..81862ff 100644
--- a/docs/openapi/components/schemas/SignInStartResponse.yaml
+++ b/docs/openapi/components/schemas/SignInStartResponse.yaml
@@ -9,3 +9,4 @@ properties:
options:
type: string
description: Options for assertion to pass to `navigator.credentials.create()`
+ x-field-extra-annotation: '@com.fasterxml.jackson.annotation.JsonRawValue'
diff --git a/docs/openapi/components/schemas/SignUpStartResponse.yaml b/docs/openapi/components/schemas/SignUpStartResponse.yaml
index b71f9e9..e9144c7 100644
--- a/docs/openapi/components/schemas/SignUpStartResponse.yaml
+++ b/docs/openapi/components/schemas/SignUpStartResponse.yaml
@@ -9,3 +9,4 @@ properties:
options:
type: string
description: Options to pass to `navigator.credentials.create()`
+ x-field-extra-annotation: '@com.fasterxml.jackson.annotation.JsonRawValue'
diff --git a/docs/openapi/paths/v1_credentials_add_start.yaml b/docs/openapi/paths/v1_credentials_add_start.yaml
index 2750bc8..584e9d7 100644
--- a/docs/openapi/paths/v1_credentials_add_start.yaml
+++ b/docs/openapi/paths/v1_credentials_add_start.yaml
@@ -8,7 +8,7 @@ post:
content:
application/json:
schema:
- type: string
+ $ref: ../components/schemas/SignUpStartRequest.yaml
required: true
responses:
'200':
diff --git a/pom.xml b/pom.xml
index a425fee..3b51aea 100644
--- a/pom.xml
+++ b/pom.xml
@@ -199,6 +199,8 @@
true
false
true
+ true
+ true
com.helioauth.passkeys.api.generated.models
com.helioauth.passkeys.api.generated.api
diff --git a/src/main/java/com/helioauth/passkeys/api/controller/ClientApplicationController.java b/src/main/java/com/helioauth/passkeys/api/controller/ClientApplicationController.java
index ea28be1..41565bc 100644
--- a/src/main/java/com/helioauth/passkeys/api/controller/ClientApplicationController.java
+++ b/src/main/java/com/helioauth/passkeys/api/controller/ClientApplicationController.java
@@ -20,18 +20,12 @@
import com.helioauth.passkeys.api.generated.models.AddApplicationRequest;
import com.helioauth.passkeys.api.generated.models.Application;
import com.helioauth.passkeys.api.generated.models.ApplicationApiKey;
-import com.helioauth.passkeys.api.mapper.ClientApplicationMapper;
import com.helioauth.passkeys.api.service.ClientApplicationService;
import lombok.RequiredArgsConstructor;
import lombok.val;
import org.springframework.http.ResponseEntity;
-import org.springframework.web.bind.annotation.DeleteMapping;
-import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.net.URI;
@@ -42,38 +36,27 @@
* @author Viktor Stanchev
*/
@RestController
-@RequestMapping("/admin/v1/apps")
@RequiredArgsConstructor
public class ClientApplicationController implements ApplicationsApi {
private final ClientApplicationService clientApplicationService;
- private final ClientApplicationMapper clientApplicationMapper;
-
- @GetMapping
- @Override
public ResponseEntity> listAll() {
return ResponseEntity.ok(clientApplicationService.listAll());
}
- @GetMapping("/{id}")
- @Override
public ResponseEntity get(@PathVariable UUID id) {
return clientApplicationService.get(id)
.map(ResponseEntity::ok)
.orElseGet(() -> ResponseEntity.notFound().build());
}
- @GetMapping("/{id}/api-key")
- @Override
public ResponseEntity getApiKey(@PathVariable UUID id) {
return clientApplicationService.getApiKey(id)
.map(ResponseEntity::ok)
.orElseGet(() -> ResponseEntity.notFound().build());
}
- @PostMapping
- @Override
public ResponseEntity add(@RequestBody AddApplicationRequest request) {
Application created = clientApplicationService.add(request.getName());
@@ -81,10 +64,7 @@ public ResponseEntity add(@RequestBody AddApplicationRequest reques
.body(created);
}
- @PutMapping("/{id}")
- @Override
public ResponseEntity edit(@PathVariable UUID id, @RequestBody String name) {
-
val updated = clientApplicationService.edit(id, name);
return updated
@@ -92,8 +72,6 @@ public ResponseEntity edit(@PathVariable UUID id, @RequestBody Stri
.orElseGet(() -> ResponseEntity.notFound().build());
}
- @DeleteMapping("/{id}")
- @Override
public ResponseEntity delete(@PathVariable UUID id) {
boolean deleted = clientApplicationService.delete(id);
diff --git a/src/main/java/com/helioauth/passkeys/api/controller/CredentialsController.java b/src/main/java/com/helioauth/passkeys/api/controller/CredentialsController.java
index 4cd97f6..edbf8f7 100644
--- a/src/main/java/com/helioauth/passkeys/api/controller/CredentialsController.java
+++ b/src/main/java/com/helioauth/passkeys/api/controller/CredentialsController.java
@@ -17,17 +17,26 @@
package com.helioauth.passkeys.api.controller;
import com.fasterxml.jackson.core.JsonProcessingException;
-import com.helioauth.passkeys.api.contract.*;
-import com.helioauth.passkeys.api.service.UserCredentialManager;
+import com.helioauth.passkeys.api.contract.SignInFinishRequest;
+import com.helioauth.passkeys.api.contract.SignInFinishResponse;
+import com.helioauth.passkeys.api.contract.SignInStartRequest;
+import com.helioauth.passkeys.api.contract.SignInStartResponse;
+import com.helioauth.passkeys.api.contract.SignUpFinishRequest;
+import com.helioauth.passkeys.api.contract.SignUpFinishResponse;
+import com.helioauth.passkeys.api.contract.SignUpStartRequest;
+import com.helioauth.passkeys.api.contract.SignUpStartResponse;
import com.helioauth.passkeys.api.service.UserSignInService;
import com.helioauth.passkeys.api.service.UserSignupService;
-
import com.helioauth.passkeys.api.service.exception.SignInFailedException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
-import org.springframework.web.bind.annotation.*;
+import org.springframework.web.bind.annotation.CrossOrigin;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;
import jakarta.validation.Valid;
@@ -35,16 +44,15 @@
/**
* @author Viktor Stanchev
*/
+@Slf4j
@RestController
@RequestMapping("/v1")
@CrossOrigin(origins = "*")
-@Slf4j
@RequiredArgsConstructor
public class CredentialsController {
private final UserSignInService userSignInService;
private final UserSignupService userSignupService;
- private final UserCredentialManager userCredentialManager;
@PostMapping(value = "/signup/start", produces = MediaType.APPLICATION_JSON_VALUE)
public SignUpStartResponse postSignupStart(@RequestBody @Valid SignUpStartRequest request) {
@@ -76,14 +84,4 @@ public SignInFinishResponse finishSignInCredential(@RequestBody SignInFinishRequ
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Sign in failed");
}
}
-
- @PostMapping(value = "/credentials/add/start", produces = MediaType.APPLICATION_JSON_VALUE)
- public SignUpStartResponse credentialsAddStart(@RequestBody String username) {
- return userCredentialManager.createCredential(username);
- }
-
- @PostMapping(value = "/credentials/add/finish", produces = MediaType.APPLICATION_JSON_VALUE)
- public SignUpFinishResponse credentialsAddFinish(@RequestBody SignUpFinishRequest request) {
- return userCredentialManager.finishCreateCredential(request);
- }
}
\ No newline at end of file
diff --git a/src/main/java/com/helioauth/passkeys/api/controller/UsersController.java b/src/main/java/com/helioauth/passkeys/api/controller/UsersController.java
index 548cd1b..30b2ada 100644
--- a/src/main/java/com/helioauth/passkeys/api/controller/UsersController.java
+++ b/src/main/java/com/helioauth/passkeys/api/controller/UsersController.java
@@ -16,15 +16,20 @@
package com.helioauth.passkeys.api.controller;
+import com.helioauth.passkeys.api.generated.api.UsersApi;
+import com.helioauth.passkeys.api.generated.models.ListPasskeysResponse;
+import com.helioauth.passkeys.api.generated.models.SignUpFinishRequest;
+import com.helioauth.passkeys.api.generated.models.SignUpFinishResponse;
+import com.helioauth.passkeys.api.generated.models.SignUpStartRequest;
+import com.helioauth.passkeys.api.generated.models.SignUpStartResponse;
import com.helioauth.passkeys.api.service.UserAccountManager;
import com.helioauth.passkeys.api.service.UserCredentialManager;
-import com.helioauth.passkeys.api.service.dto.ListPasskeysResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
-import org.springframework.web.bind.annotation.DeleteMapping;
-import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.UUID;
@@ -32,21 +37,29 @@
/**
* @author Viktor Stanchev
*/
-@RestController
-@RequestMapping("/v1/users")
@Slf4j
+@RestController
@RequiredArgsConstructor
-public class UsersController {
+public class UsersController implements UsersApi {
private final UserCredentialManager userCredentialManager;
private final UserAccountManager userAccountManager;
- @GetMapping("/{uuid}/credentials")
- public ListPasskeysResponse getUserCredentials(@PathVariable UUID uuid) {
- return userCredentialManager.getUserCredentials(uuid);
+ public ResponseEntity getUserCredentials(@PathVariable UUID uuid) {
+ return ResponseEntity.ok(userCredentialManager.getUserCredentials(uuid));
}
- @DeleteMapping("/{uuid}")
- public void deleteUser(@PathVariable UUID uuid) {
+ public ResponseEntity deleteUser(@PathVariable UUID uuid) {
userAccountManager.deleteUser(uuid);
+ return ResponseEntity.noContent().build();
+ }
+
+ @CrossOrigin(origins = "*")
+ public ResponseEntity credentialsAddStart(@RequestBody SignUpStartRequest request) {
+ return ResponseEntity.ok(userCredentialManager.createCredential(request.getName()));
+ }
+
+ @CrossOrigin(origins = "*")
+ public ResponseEntity credentialsAddFinish(@RequestBody SignUpFinishRequest request) {
+ return ResponseEntity.ok(userCredentialManager.finishCreateCredential(request));
}
}
diff --git a/src/main/java/com/helioauth/passkeys/api/mapper/UserCredentialMapper.java b/src/main/java/com/helioauth/passkeys/api/mapper/UserCredentialMapper.java
index 20a34af..42e8936 100644
--- a/src/main/java/com/helioauth/passkeys/api/mapper/UserCredentialMapper.java
+++ b/src/main/java/com/helioauth/passkeys/api/mapper/UserCredentialMapper.java
@@ -17,8 +17,9 @@
package com.helioauth.passkeys.api.mapper;
import com.helioauth.passkeys.api.domain.UserCredential;
+import com.helioauth.passkeys.api.generated.models.PasskeyCredential;
+import com.helioauth.passkeys.api.generated.models.SignUpStartResponse;
import com.helioauth.passkeys.api.service.dto.CredentialRegistrationResult;
-import com.helioauth.passkeys.api.service.dto.PasskeyItem;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingConstants;
@@ -30,11 +31,13 @@
*/
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
public interface UserCredentialMapper {
- List toDto(List userCredentialList);
+ List toDto(List userCredentialList);
@Mapping(target = "createdAt", ignore = true)
@Mapping(target = "lastUsedAt", ignore = true)
@Mapping(target = "user", ignore = true)
@Mapping(target = "id", ignore = true)
UserCredential fromCredentialRegistrationResult(CredentialRegistrationResult registrationResultDto);
+
+ com.helioauth.passkeys.api.contract.SignUpStartResponse toLegacySignUpStartResponse(SignUpStartResponse response);
}
\ No newline at end of file
diff --git a/src/main/java/com/helioauth/passkeys/api/service/UserCredentialManager.java b/src/main/java/com/helioauth/passkeys/api/service/UserCredentialManager.java
index 4de3b04..e8a9d8a 100644
--- a/src/main/java/com/helioauth/passkeys/api/service/UserCredentialManager.java
+++ b/src/main/java/com/helioauth/passkeys/api/service/UserCredentialManager.java
@@ -17,17 +17,16 @@
package com.helioauth.passkeys.api.service;
import com.fasterxml.jackson.core.JsonProcessingException;
-import com.helioauth.passkeys.api.contract.SignUpFinishRequest;
-import com.helioauth.passkeys.api.contract.SignUpFinishResponse;
-import com.helioauth.passkeys.api.contract.SignUpStartResponse;
import com.helioauth.passkeys.api.domain.User;
import com.helioauth.passkeys.api.domain.UserCredential;
import com.helioauth.passkeys.api.domain.UserCredentialRepository;
import com.helioauth.passkeys.api.domain.UserRepository;
+import com.helioauth.passkeys.api.generated.models.ListPasskeysResponse;
+import com.helioauth.passkeys.api.generated.models.SignUpFinishRequest;
+import com.helioauth.passkeys.api.generated.models.SignUpFinishResponse;
+import com.helioauth.passkeys.api.generated.models.SignUpStartResponse;
import com.helioauth.passkeys.api.mapper.UserCredentialMapper;
import com.helioauth.passkeys.api.service.dto.CredentialRegistrationResult;
-import com.helioauth.passkeys.api.service.dto.ListPasskeysResponse;
-import com.helioauth.passkeys.api.service.dto.PasskeyItem;
import com.helioauth.passkeys.api.service.exception.CreateCredentialFailedException;
import com.helioauth.passkeys.api.service.exception.SignUpFailedException;
import lombok.RequiredArgsConstructor;
@@ -62,8 +61,8 @@ public SignUpStartResponse createCredential(String name) {
public SignUpFinishResponse finishCreateCredential(SignUpFinishRequest request) {
try {
CredentialRegistrationResult result = webAuthnAuthenticator.finishRegistration(
- request.requestId(),
- request.publicKeyCredential()
+ request.getRequestId(),
+ request.getPublicKeyCredential()
);
User user = userRepository.findByName(result.name()).orElseThrow(CreateCredentialFailedException::new);
@@ -72,7 +71,7 @@ public SignUpFinishResponse finishCreateCredential(SignUpFinishRequest request)
userCredential.setUser(user);
userCredentialRepository.save(userCredential);
- return new SignUpFinishResponse(request.requestId(), user.getId());
+ return new SignUpFinishResponse(request.getRequestId(), user.getId());
} catch (IOException e) {
log.error("Register Credential failed", e);
throw new SignUpFailedException();
@@ -81,8 +80,7 @@ public SignUpFinishResponse finishCreateCredential(SignUpFinishRequest request)
public ListPasskeysResponse getUserCredentials(UUID userUuid) {
List userCredentials = userCredentialRepository.findAllByUserId(userUuid);
- List passkeyItems = userCredentialMapper.toDto(userCredentials);
- return new ListPasskeysResponse(passkeyItems);
+ return new ListPasskeysResponse(userCredentialMapper.toDto(userCredentials));
}
}
diff --git a/src/main/java/com/helioauth/passkeys/api/service/UserSignupService.java b/src/main/java/com/helioauth/passkeys/api/service/UserSignupService.java
index 42e4992..c5420d2 100644
--- a/src/main/java/com/helioauth/passkeys/api/service/UserSignupService.java
+++ b/src/main/java/com/helioauth/passkeys/api/service/UserSignupService.java
@@ -53,7 +53,7 @@ public SignUpStartResponse startRegistration(String name) {
});
try {
- return webAuthnAuthenticator.startRegistration(name);
+ return usercredentialMapper.toLegacySignUpStartResponse(webAuthnAuthenticator.startRegistration(name));
} catch (JsonProcessingException e) {
log.error("Register Credential failed", e);
throw new SignUpFailedException();
diff --git a/src/main/java/com/helioauth/passkeys/api/service/WebAuthnAuthenticator.java b/src/main/java/com/helioauth/passkeys/api/service/WebAuthnAuthenticator.java
index 3aab0e1..a1a6b3e 100644
--- a/src/main/java/com/helioauth/passkeys/api/service/WebAuthnAuthenticator.java
+++ b/src/main/java/com/helioauth/passkeys/api/service/WebAuthnAuthenticator.java
@@ -21,7 +21,7 @@
import com.github.benmanes.caffeine.cache.Caffeine;
import com.helioauth.passkeys.api.config.properties.WebAuthnRelyingPartyProperties;
import com.helioauth.passkeys.api.contract.SignInStartResponse;
-import com.helioauth.passkeys.api.contract.SignUpStartResponse;
+import com.helioauth.passkeys.api.generated.models.SignUpStartResponse;
import com.helioauth.passkeys.api.mapper.CredentialRegistrationResultMapper;
import com.helioauth.passkeys.api.service.dto.CredentialAssertionResult;
import com.helioauth.passkeys.api.service.dto.CredentialRegistrationResult;
diff --git a/src/test/java/com/helioauth/passkeys/api/service/ClientApplicationServiceTest.java b/src/test/java/com/helioauth/passkeys/api/service/ClientApplicationServiceTest.java
index bccc604..d22336b 100644
--- a/src/test/java/com/helioauth/passkeys/api/service/ClientApplicationServiceTest.java
+++ b/src/test/java/com/helioauth/passkeys/api/service/ClientApplicationServiceTest.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.helioauth.passkeys.api.service;
import com.helioauth.passkeys.api.domain.ClientApplication;
diff --git a/src/test/java/com/helioauth/passkeys/api/service/UserCredentialManagerTest.java b/src/test/java/com/helioauth/passkeys/api/service/UserCredentialManagerTest.java
new file mode 100644
index 0000000..e0e5da7
--- /dev/null
+++ b/src/test/java/com/helioauth/passkeys/api/service/UserCredentialManagerTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.helioauth.passkeys.api.service;
+
+import com.helioauth.passkeys.api.domain.UserCredential;
+import com.helioauth.passkeys.api.domain.UserCredentialRepository;
+import com.helioauth.passkeys.api.domain.UserRepository;
+import com.helioauth.passkeys.api.generated.models.ListPasskeysResponse;
+import com.helioauth.passkeys.api.mapper.UserCredentialMapper;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mapstruct.factory.Mappers;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.time.Instant;
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+/**
+ * @author Viktor Stanchev
+ */
+@ExtendWith(MockitoExtension.class)
+class UserCredentialManagerTest {
+
+ @Mock
+ private UserCredentialRepository userCredentialRepository;
+
+ @Mock
+ private WebAuthnAuthenticator authenticator;
+
+ @Mock
+ private UserRepository userRepository;
+
+ @Spy
+ private UserCredentialMapper userCredentialMapper = Mappers.getMapper(UserCredentialMapper.class);
+
+ @InjectMocks
+ private UserCredentialManager userCredentialManager;
+
+ @Test
+ void getUserCredentials_returnsEmpty_whenResultEmpty() {
+ // Arrange
+ when(userCredentialRepository.findAllByUserId(any())).thenReturn(Collections.emptyList());
+
+ // Act
+ ListPasskeysResponse result = userCredentialManager.getUserCredentials(UUID.randomUUID());
+
+ // Assert
+ assertTrue(result.getPasskeys().isEmpty(), "Expected no user credentials");
+ }
+
+ @Test
+ void getUserCredentials_returnsResult_whenResultNotEmpty() {
+ // Arrange
+ UUID userUuid = UUID.randomUUID();
+
+ UserCredential credential = UserCredential.builder()
+ .id(1L)
+ .credentialId(UUID.randomUUID().toString())
+ .displayName("Credential Name")
+ .createdAt(Instant.now())
+ .lastUsedAt(Instant.now())
+ .build();
+
+ List credentialList = Collections.singletonList(credential);
+
+ when(userCredentialRepository.findAllByUserId(userUuid)).thenReturn(credentialList);
+
+ // Act
+ ListPasskeysResponse response = userCredentialManager.getUserCredentials(userUuid);
+
+ // Assert
+ assertNotNull(response);
+ assertFalse(response.getPasskeys().isEmpty(), "The response should not be empty when credentials exist.");
+ assertEquals(1, response.getPasskeys().size());
+ assertEquals("Credential Name", response.getPasskeys().get(0).getDisplayName());
+ }
+}
\ No newline at end of file