mirror of
https://github.com/ysoftdevs/oauth-playground-server.git
synced 2026-04-19 15:11:25 +02:00
WIP webauthn - backend
This commit is contained in:
4
pom.xml
4
pom.xml
@@ -44,6 +44,10 @@
|
|||||||
<groupId>io.quarkus</groupId>
|
<groupId>io.quarkus</groupId>
|
||||||
<artifactId>quarkus-resteasy-reactive-jackson</artifactId>
|
<artifactId>quarkus-resteasy-reactive-jackson</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.quarkus</groupId>
|
||||||
|
<artifactId>quarkus-security-webauthn</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.quarkus</groupId>
|
<groupId>io.quarkus</groupId>
|
||||||
<artifactId>quarkus-junit5</artifactId>
|
<artifactId>quarkus-junit5</artifactId>
|
||||||
|
|||||||
@@ -1,7 +1,31 @@
|
|||||||
package com.ysoft.geecon.dto;
|
package com.ysoft.geecon.dto;
|
||||||
|
|
||||||
public record User(String login, String password) {
|
import com.ysoft.geecon.webauthn.WebAuthnCredential;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public record User(String login, String password, List<WebAuthnCredential> credentials) {
|
||||||
public boolean validatePassword(String password) {
|
public boolean validatePassword(String password) {
|
||||||
return this.password != null && this.password.equals(password);
|
return this.password != null && this.password.equals(password);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public User withAddedCredential(WebAuthnCredential webAuthnCredential) {
|
||||||
|
Optional<WebAuthnCredential> existing = credentials.stream()
|
||||||
|
.filter(credential -> credential.credID.equals(webAuthnCredential.credID))
|
||||||
|
.findAny();
|
||||||
|
|
||||||
|
List<WebAuthnCredential> newCredentials;
|
||||||
|
if (existing.isPresent()) {
|
||||||
|
// TODO need to decide if immutable or not
|
||||||
|
existing.get().counter++;
|
||||||
|
newCredentials = credentials;
|
||||||
|
} else {
|
||||||
|
newCredentials = new ArrayList<>(credentials);
|
||||||
|
newCredentials.add(webAuthnCredential);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new User(login, password, newCredentials);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import com.ysoft.geecon.dto.User;
|
|||||||
import jakarta.enterprise.context.ApplicationScoped;
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@@ -12,7 +13,7 @@ public class UsersRepo {
|
|||||||
private final Map<String, User> users = new HashMap<>();
|
private final Map<String, User> users = new HashMap<>();
|
||||||
|
|
||||||
public UsersRepo() {
|
public UsersRepo() {
|
||||||
register(new User("bob", "Password1"));
|
register(new User("bob", "Password1", List.of()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<User> getUser(String username) {
|
public Optional<User> getUser(String username) {
|
||||||
@@ -22,4 +23,11 @@ public class UsersRepo {
|
|||||||
public void register(User user) {
|
public void register(User user) {
|
||||||
users.put(user.login(), user);
|
users.put(user.login(), user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Optional<User> findByCredID(String credID) {
|
||||||
|
// TODO
|
||||||
|
return users.values().stream()
|
||||||
|
.filter(u -> u.credentials().stream().anyMatch(c -> c.credID.equals(credID)))
|
||||||
|
.findAny();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
65
src/main/java/com/ysoft/geecon/webauthn/MyWebAuthnSetup.java
Normal file
65
src/main/java/com/ysoft/geecon/webauthn/MyWebAuthnSetup.java
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
package com.ysoft.geecon.webauthn;
|
||||||
|
|
||||||
|
import com.ysoft.geecon.dto.User;
|
||||||
|
import com.ysoft.geecon.repo.UsersRepo;
|
||||||
|
import io.quarkus.security.webauthn.WebAuthnUserProvider;
|
||||||
|
import io.smallrye.mutiny.Uni;
|
||||||
|
import io.vertx.ext.auth.webauthn.AttestationCertificates;
|
||||||
|
import io.vertx.ext.auth.webauthn.Authenticator;
|
||||||
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
|
import jakarta.inject.Inject;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@ApplicationScoped
|
||||||
|
public class MyWebAuthnSetup implements WebAuthnUserProvider {
|
||||||
|
@Inject
|
||||||
|
UsersRepo usersRepo;
|
||||||
|
|
||||||
|
private static List<Authenticator> toAuthenticators(List<WebAuthnCredential> dbs) {
|
||||||
|
return dbs.stream().map(MyWebAuthnSetup::toAuthenticator).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Authenticator toAuthenticator(WebAuthnCredential credential) {
|
||||||
|
Authenticator ret = new Authenticator();
|
||||||
|
ret.setAaguid(credential.aaguid);
|
||||||
|
AttestationCertificates attestationCertificates = new AttestationCertificates();
|
||||||
|
attestationCertificates.setAlg(credential.alg);
|
||||||
|
attestationCertificates.setX5c(credential.x5c);
|
||||||
|
ret.setAttestationCertificates(attestationCertificates);
|
||||||
|
ret.setCounter(credential.counter);
|
||||||
|
ret.setCredID(credential.credID);
|
||||||
|
ret.setFmt(credential.fmt);
|
||||||
|
ret.setPublicKey(credential.publicKey);
|
||||||
|
ret.setType(credential.type);
|
||||||
|
ret.setUserName(credential.userName);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Uni<List<Authenticator>> findWebAuthnCredentialsByUserName(String userName) {
|
||||||
|
return Uni.createFrom().item(usersRepo.getUser(userName)
|
||||||
|
.map((User dbs) -> toAuthenticators(dbs.credentials()))
|
||||||
|
.orElse(List.of())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Uni<List<Authenticator>> findWebAuthnCredentialsByCredID(String credID) {
|
||||||
|
return Uni.createFrom().item(usersRepo.findByCredID(credID)
|
||||||
|
.map((User dbs) -> toAuthenticators(dbs.credentials()))
|
||||||
|
.orElse(List.of())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Uni<Void> updateOrStoreWebAuthnCredentials(Authenticator authenticator) {
|
||||||
|
WebAuthnCredential credential1 = new WebAuthnCredential(authenticator);
|
||||||
|
usersRepo.getUser(authenticator.getUserName())
|
||||||
|
.ifPresentOrElse(
|
||||||
|
user -> usersRepo.register(user.withAddedCredential(credential1)),
|
||||||
|
() -> usersRepo.register(new User(authenticator.getUserName(), null, List.of(credential1)))
|
||||||
|
);
|
||||||
|
return Uni.createFrom().nullItem();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
package com.ysoft.geecon.webauthn;
|
||||||
|
|
||||||
|
import io.vertx.ext.auth.webauthn.Authenticator;
|
||||||
|
import io.vertx.ext.auth.webauthn.PublicKeyCredential;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
public class WebAuthnCredential {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The username linked to this authenticator
|
||||||
|
*/
|
||||||
|
public String userName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of key (must be "public-key")
|
||||||
|
*/
|
||||||
|
public String type = "public-key";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The non user identifiable id for the authenticator
|
||||||
|
*/
|
||||||
|
public String credID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The public key associated with this authenticator
|
||||||
|
*/
|
||||||
|
public String publicKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The signature counter of the authenticator to prevent replay attacks
|
||||||
|
*/
|
||||||
|
public long counter;
|
||||||
|
|
||||||
|
public String aaguid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Authenticator attestation certificates object, a JSON like:
|
||||||
|
* <pre>{@code
|
||||||
|
* {
|
||||||
|
* "alg": "string",
|
||||||
|
* "x5c": [
|
||||||
|
* "base64"
|
||||||
|
* ]
|
||||||
|
* }
|
||||||
|
* }</pre>
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* The algorithm used for the public credential
|
||||||
|
*/
|
||||||
|
public PublicKeyCredential alg;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of X509 certificates encoded as base64url.
|
||||||
|
*/
|
||||||
|
public List<String> x5c = new ArrayList<>();
|
||||||
|
|
||||||
|
public String fmt;
|
||||||
|
|
||||||
|
public WebAuthnCredential() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public WebAuthnCredential(Authenticator authenticator) {
|
||||||
|
aaguid = authenticator.getAaguid();
|
||||||
|
if (authenticator.getAttestationCertificates() != null)
|
||||||
|
alg = authenticator.getAttestationCertificates().getAlg();
|
||||||
|
counter = authenticator.getCounter();
|
||||||
|
credID = authenticator.getCredID();
|
||||||
|
fmt = authenticator.getFmt();
|
||||||
|
publicKey = authenticator.getPublicKey();
|
||||||
|
type = authenticator.getType();
|
||||||
|
userName = authenticator.getUserName();
|
||||||
|
|
||||||
|
if (authenticator.getAttestationCertificates() != null
|
||||||
|
&& authenticator.getAttestationCertificates().getX5c() != null) {
|
||||||
|
this.x5c.addAll(authenticator.getAttestationCertificates().getX5c());
|
||||||
|
}
|
||||||
|
// this.user = user;
|
||||||
|
// user.webAuthnCredential = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// public static Uni<List<WebAuthnCredential>> findByUserName(String userName) {
|
||||||
|
// return list("userName", userName);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// public static Uni<List<WebAuthnCredential>> findByCredID(String credID) {
|
||||||
|
// return list("credID", credID);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// public <T> Uni<T> fetch(T association) {
|
||||||
|
// return getSession().flatMap(session -> session.fetch(association));
|
||||||
|
// }
|
||||||
|
}
|
||||||
@@ -37,7 +37,7 @@ public class AuthCodeGrantTest {
|
|||||||
@BeforeEach
|
@BeforeEach
|
||||||
void beforeAll() {
|
void beforeAll() {
|
||||||
clientsRepo.register(CLIENT);
|
clientsRepo.register(CLIENT);
|
||||||
usersRepo.register(new User("bob", "password"));
|
usersRepo.register(new User("bob", "password", List.of()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import org.junit.jupiter.api.Test;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.is;
|
import static org.hamcrest.CoreMatchers.is;
|
||||||
import static org.hamcrest.CoreMatchers.notNullValue;
|
import static org.hamcrest.CoreMatchers.notNullValue;
|
||||||
@@ -44,7 +45,7 @@ public class DeviceAuthGrantTest {
|
|||||||
@BeforeEach
|
@BeforeEach
|
||||||
void beforeAll() {
|
void beforeAll() {
|
||||||
clientsRepo.register(CLIENT);
|
clientsRepo.register(CLIENT);
|
||||||
usersRepo.register(new User("bob", "password"));
|
usersRepo.register(new User("bob", "password", List.of()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user