diff --git a/pom.xml b/pom.xml index 1397411..048f40c 100644 --- a/pom.xml +++ b/pom.xml @@ -50,6 +50,10 @@ io.quarkus + quarkus-smallrye-jwt-build + + + io.quarkus quarkus-junit5 test @@ -72,6 +76,7 @@ org.jsoup jsoup 1.15.4 + test diff --git a/src/main/java/com/ysoft/geecon/dto/AuthParams.java b/src/main/java/com/ysoft/geecon/dto/AuthParams.java index 8030907..b542c84 100644 --- a/src/main/java/com/ysoft/geecon/dto/AuthParams.java +++ b/src/main/java/com/ysoft/geecon/dto/AuthParams.java @@ -14,7 +14,14 @@ public class AuthParams { public boolean validateResponseType() { try { - return !getResponseTypes().isEmpty(); + List responseTypes = getResponseTypes(); + if (responseTypes.isEmpty()) { + return false; + } + if (responseTypes.contains(ResponseType.id_token)) { + return getScopes().contains("openid"); + } + return true; } catch (IllegalArgumentException exception) { return false; } diff --git a/src/main/java/com/ysoft/geecon/dto/AuthorizationSession.java b/src/main/java/com/ysoft/geecon/dto/AuthorizationSession.java index d199672..5b699dc 100644 --- a/src/main/java/com/ysoft/geecon/dto/AuthorizationSession.java +++ b/src/main/java/com/ysoft/geecon/dto/AuthorizationSession.java @@ -1,6 +1,9 @@ package com.ysoft.geecon.dto; import com.ysoft.geecon.repo.SecureRandomStrings; +import io.smallrye.jwt.build.Jwt; +import io.smallrye.jwt.build.JwtClaimsBuilder; +import org.eclipse.microprofile.jwt.Claims; import java.util.List; import java.util.Objects; @@ -24,21 +27,38 @@ public record AuthorizationSession(String sessionId, } public AuthorizationSession withGeneratedTokens() { - String idToken = null; var tokens = new AccessTokenResponse("Bearer", - 8400, + expiresIn(), SecureRandomStrings.alphanumeric(50), scope(), SecureRandomStrings.alphanumeric(50), - idToken + acceptedScopes.contains("openid") ? idToken() : null ); return new AuthorizationSession(sessionId, params, client, user, acceptedScopes, tokens); } + private int expiresIn() { + return 8400; + } + public String scope() { return acceptedScopes == null ? null : String.join(" ", acceptedScopes); } + private String idToken() { + JwtClaimsBuilder jwt = Jwt.claims() + .issuedAt(System.currentTimeMillis() / 1000) + .expiresAt(System.currentTimeMillis() / 1000 + expiresIn()) + .subject(user().id()) + .audience(client().clientId()) + .preferredUserName(user().login()); + + if (params().nonce != null) + jwt.claim(Claims.nonce, params().nonce); + + return jwt.sign(); + } + public boolean validateCodeChallenge(String codeVerifier) { if (params.codeChallengeMethod == null) { return true; diff --git a/src/main/java/com/ysoft/geecon/dto/User.java b/src/main/java/com/ysoft/geecon/dto/User.java index 2e758e7..40bf540 100644 --- a/src/main/java/com/ysoft/geecon/dto/User.java +++ b/src/main/java/com/ysoft/geecon/dto/User.java @@ -1,12 +1,21 @@ package com.ysoft.geecon.dto; +import com.ysoft.geecon.repo.SecureRandomStrings; 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 credentials) { +public record User(String id, String login, String password, List credentials) { + public User(String login, String password) { + this(SecureRandomStrings.alphanumeric(5), login, password, List.of()); + } + + public User(String login, WebAuthnCredential credential) { + this(SecureRandomStrings.alphanumeric(5), login, null, List.of(credential)); + } + public boolean validatePassword(String password) { return this.password != null && this.password.equals(password); } @@ -26,6 +35,6 @@ public record User(String login, String password, List crede newCredentials.add(webAuthnCredential); } - return new User(login, password, newCredentials); + return new User(id, login, password, newCredentials); } } diff --git a/src/main/java/com/ysoft/geecon/repo/UsersRepo.java b/src/main/java/com/ysoft/geecon/repo/UsersRepo.java index a3857be..9625457 100644 --- a/src/main/java/com/ysoft/geecon/repo/UsersRepo.java +++ b/src/main/java/com/ysoft/geecon/repo/UsersRepo.java @@ -4,7 +4,6 @@ import com.ysoft.geecon.dto.User; import jakarta.enterprise.context.ApplicationScoped; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.Optional; @@ -33,7 +32,7 @@ public class UsersRepo { public final void reset() { users.clear(); - register(new User("bob", "Password1", List.of())); - register(new User("user", "user", List.of())); + register(new User("bob", "Password1")); + register(new User("user", "user")); } } diff --git a/src/main/java/com/ysoft/geecon/webauthn/WebAuthnSetup.java b/src/main/java/com/ysoft/geecon/webauthn/WebAuthnSetup.java index 86d817b..193e5d9 100644 --- a/src/main/java/com/ysoft/geecon/webauthn/WebAuthnSetup.java +++ b/src/main/java/com/ysoft/geecon/webauthn/WebAuthnSetup.java @@ -88,7 +88,7 @@ public class WebAuthnSetup implements WebAuthnUserProvider { return Uni.createFrom().nullItem(); } else if (existingUser.isEmpty()) { // new user -> register - usersRepo.register(new User(authenticator.getUserName(), null, List.of(credential))); + usersRepo.register(new User(authenticator.getUserName(), credential)); return Uni.createFrom().nullItem(); } else { // in production, we should not add a new credentials to an existing user diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 465955b..b13ab76 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,3 +1,4 @@ quarkus.http.cors=true quarkus.http.cors.origins=/.*/ -quarkus.package.type=uber-jar \ No newline at end of file +quarkus.package.type=uber-jar +smallrye.jwt.sign.key.location=key.jwk \ No newline at end of file diff --git a/src/main/resources/key.jwk b/src/main/resources/key.jwk new file mode 100644 index 0000000..7fd1897 --- /dev/null +++ b/src/main/resources/key.jwk @@ -0,0 +1,14 @@ +{ + "p": "8302271HF9MFMVcnFNey7n35KIkxwMpzvimZixHfRTOJCH-YT67FkTIDd1Vle6LI0gfgbKgqrxTRofnyryuVO_3ChIrBQdYHGTrPPHubcgpuFpyMN9ganq7C-aewDIjprygNZTjsjAwKJWvZwMJpOvTVn0biVZ4Iz080iOb4L78", + "kty": "RSA", + "q": "152Rb5VPGYsoORFMbbjb17cr8cUgipVqooLWHTpzo2wGXampptOqdusm_fnHxw42CxyMvCpD7IcDzumg1dCb5VRZjfwRooLI-pqXNyAnuzpr78ApKCiT8-lkFhNn-NHE-1ctY8Js5HKOUUxP_em8CCCkw33wkRur0HLRvjX7bec", + "d": "OgGFO0dui_gXklyLYQ_l9JfQdbwrUuBxYTzv1AZPZ6pY8hs7SrxGr4IrE67zbJEDZgT4ohP2ZTsVABMIfs4J5Gcg213RdQepa05IEXj2rbAUL1LbDAcu6gb-mNDvnBOk0ZQ2l3shY7Q__UWMfUUlZaWEGgO6tQah4K6f6F51cVsggxx9IPsGxOKuocM6UyXW4_DDPnXRc2hFe6mGmnj7TCJ1LL9O59V4G74VVdTO9m2B_BxhsIE-xUqBzP_SfTZ9fQUil_zBtT3Y3UkjeEN70FFaASyKewWRZcEpj6tjZEQ1V0Tq9Yt1CFTww4pb5ctTRBZ5VULynQNBwVXI59jD1Q", + "e": "AQAB", + "use": "sig", + "kid": "MPeUPQwWfMbkj6zB46bbwqd2pdyUtlFHJLFBvyctgTU", + "qi": "JNJgYJYEBjWzYb47wU_1Ms4o1TAM5FIseS_dR9Q2sCz7SjjPr3q2p7-v3DoMnc12pXc4tMUWiz8Yn7EtDrvu_3wXmSP4lRM3fD0iaUnjpyyjocMyhFYnwISR81zWEbRgOByVGzeEWqUb3FwDydD9hotPLBOWrM3sqiLT3ETGeRg", + "dp": "kOhVLKNR2xjn_zxJ8vqH762jCf_UT1NtXJ_vVDe3s7x-8kLVh56Qz99-9pcpBVKUx4KOirvuYzI1rHtPdfavIvvbtvvJFgBlSxuX1_wMP-t7JxPV0ypWdVe2i9PDT0JwKKDij_o3tQU5SJoOBszsyXyKYfdSnfemcJJHxq4GyIs", + "alg": "RS256", + "dq": "qtGxlBZVOWZu8m9K_q5ytT7v-LX05vYjKia_nR5e2PzPOksdFgchSN9Z3-KQrJoMpNb0hGpzr6LzmGytOFfx-kjOPleSXQ6CTVBGNq0p7QIG20WBFci4Fogz--1Z9N2z0nApjJxPCtna-Hud8ArKJiI-hoZzHXMvtpAQrUI1NMc", + "n": "zRQHMT4aJfWUrFpl6fn-J_2_6hP79Ekkd-eZJ5-YyDeUrFqdRTJw05WWKAzNtdhdO7Tot3aRN5QzXMOBW6pxB2cOMJ8Mwi_maMKa3UgT3jsM_La23AcOZKXJVifOksrUR6lieffPCl3Sh7huoZMS__-xVJzk35QLUJ3_PYeSgpOgoK-wD5Fen2xQhR6Jdg7kZMkPaaqjHSlWhavG5syZ5JEBXPvNXu_Nk0mLzhEq7WFdL3O3cESkdGVGkVFAQI1dTjlKGcT0CQnxmZMKmeIyFgJ6u_qZ0evTpRm5orfVZn-RV2QGilURzwoqInK85J5dW0yHJCxnT9IiHufbcYxoWQ" +} \ No newline at end of file diff --git a/src/test/java/com/ysoft/geecon/AuthCodeGrantTest.java b/src/test/java/com/ysoft/geecon/AuthCodeGrantTest.java index e7c543d..4322c07 100644 --- a/src/test/java/com/ysoft/geecon/AuthCodeGrantTest.java +++ b/src/test/java/com/ysoft/geecon/AuthCodeGrantTest.java @@ -37,7 +37,7 @@ public class AuthCodeGrantTest { @BeforeEach void beforeAll() { clientsRepo.register(CLIENT); - usersRepo.register(new User("bob", "password", List.of())); + usersRepo.register(new User("bob", "password")); } @Test diff --git a/src/test/java/com/ysoft/geecon/DeviceAuthGrantTest.java b/src/test/java/com/ysoft/geecon/DeviceAuthGrantTest.java index 2e3014e..2dcb77f 100644 --- a/src/test/java/com/ysoft/geecon/DeviceAuthGrantTest.java +++ b/src/test/java/com/ysoft/geecon/DeviceAuthGrantTest.java @@ -20,7 +20,6 @@ import org.junit.jupiter.api.Test; import java.io.IOException; import java.net.URI; -import java.util.List; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; @@ -45,7 +44,7 @@ public class DeviceAuthGrantTest { @BeforeEach void beforeAll() { clientsRepo.register(CLIENT); - usersRepo.register(new User("bob", "password", List.of())); + usersRepo.register(new User("bob", "password")); }