diff --git a/src/main/java/com/ysoft/geecon/OAuthResource.java b/src/main/java/com/ysoft/geecon/OAuthResource.java index 71a8e2d..e90f844 100644 --- a/src/main/java/com/ysoft/geecon/OAuthResource.java +++ b/src/main/java/com/ysoft/geecon/OAuthResource.java @@ -3,7 +3,6 @@ package com.ysoft.geecon; import com.ysoft.geecon.dto.*; import com.ysoft.geecon.error.OAuthException; import com.ysoft.geecon.repo.ClientsRepo; -import com.ysoft.geecon.repo.SecureRandomStrings; import com.ysoft.geecon.repo.SessionsRepo; import com.ysoft.geecon.repo.UsersRepo; import io.quarkus.qute.CheckedTemplate; @@ -116,6 +115,7 @@ public class OAuthResource { public AccessTokenResponse token(TokenParams params) { return switch (params.getGrantType()) { case "authorization_code" -> redeemAuthorizationCode(params); + case "urn:ietf:params:oauth:grant-type:device_code" -> redeemDeviceCode(params); default -> throw new OAuthException("Unsupported grant type"); }; } @@ -143,22 +143,25 @@ public class OAuthResource { } private AccessTokenResponse redeemAuthorizationCode(TokenParams params) { - validateClient(params); var session = sessionsRepo.redeemAuthorizationCode(params.getCode()) .orElseThrow(() -> new OAuthException("Invalid code")); + validateClient(params, session); if (!session.validateCodeChallenge(params.getCodeVerifier())) { throw new OAuthException("Invalid code verifier"); } + return session.tokens(); + } - String idToken = null; - - return new AccessTokenResponse("Bearer", - 8400, - SecureRandomStrings.alphanumeric(50), - session.scope(), - SecureRandomStrings.alphanumeric(50), - idToken - ); + private AccessTokenResponse redeemDeviceCode(TokenParams params) { + var session = sessionsRepo.getByAuthorizationCode(params.getDeviceCode()) + .orElseThrow(() -> new OAuthException("Invalid code")); + validateClient(params, session); + if (session.tokens() != null) { + sessionsRepo.redeemAuthorizationCode(params.getDeviceCode()); + return session.tokens(); + } else { + throw new OAuthException("Authorization pending"); + } } private User validateUser(String username, String password) { @@ -179,10 +182,10 @@ public class OAuthResource { return client; } - private OAuthClient validateClient(TokenParams params) { + private OAuthClient validateClient(TokenParams params, AuthorizationSession session) { var client = clientsRepo.getClient(params.getClientId()) .orElseThrow(() -> new RuntimeException("Not a valid client")); - if (!client.validateRedirectUri(params.getRedirectUri())) { + if (!session.validateRedirectUri(params.getRedirectUri())) { throw new RuntimeException("Invalid redirect URI"); } if (!client.validateSecret(params.getClientSecret())) { diff --git a/src/main/java/com/ysoft/geecon/dto/AuthorizationSession.java b/src/main/java/com/ysoft/geecon/dto/AuthorizationSession.java index ec72349..d199672 100644 --- a/src/main/java/com/ysoft/geecon/dto/AuthorizationSession.java +++ b/src/main/java/com/ysoft/geecon/dto/AuthorizationSession.java @@ -3,6 +3,7 @@ package com.ysoft.geecon.dto; import com.ysoft.geecon.repo.SecureRandomStrings; import java.util.List; +import java.util.Objects; public record AuthorizationSession(String sessionId, AuthParams params, @@ -48,4 +49,7 @@ public record AuthorizationSession(String sessionId, return Pkce.validate(params.codeChallengeMethod, params.codeChallenge, codeVerifier); } + public boolean validateRedirectUri(String redirectUri) { + return Objects.equals(params.redirectUri, redirectUri); + } } diff --git a/src/main/java/com/ysoft/geecon/dto/TokenParams.java b/src/main/java/com/ysoft/geecon/dto/TokenParams.java index 0b5f8b6..e79a15b 100644 --- a/src/main/java/com/ysoft/geecon/dto/TokenParams.java +++ b/src/main/java/com/ysoft/geecon/dto/TokenParams.java @@ -22,6 +22,9 @@ public class TokenParams { @FormParam("code_verifier") private String codeVerifier; + @FormParam("device_code") + private String deviceCode; + public String getGrantType() { return grantType; } @@ -69,4 +72,12 @@ public class TokenParams { public void setCodeVerifier(String codeVerifier) { this.codeVerifier = codeVerifier; } + + public String getDeviceCode() { + return deviceCode; + } + + public void setDeviceCode(String deviceCode) { + this.deviceCode = deviceCode; + } } diff --git a/src/test/java/com/ysoft/geecon/DeviceAuthGrantTest.java b/src/test/java/com/ysoft/geecon/DeviceAuthGrantTest.java index 1265f05..25c0bb0 100644 --- a/src/test/java/com/ysoft/geecon/DeviceAuthGrantTest.java +++ b/src/test/java/com/ysoft/geecon/DeviceAuthGrantTest.java @@ -68,6 +68,19 @@ public class DeviceAuthGrantTest { when(). post("auth/consent") .then().statusCode(200); - } + given(). + formParam("grant_type", "urn:ietf:params:oauth:grant-type:device_code"). + formParam("client_id", "myclient"). + formParam("device_code", deviceResponse.deviceCode()). + when(). + post("/auth/token") + .then() + .statusCode(200) + .contentType(JSON) + .body("token_type", is(notNullValue())) + .body("expires_in", is(notNullValue())) + .body("access_token", is(notNullValue())) + .body("refresh_token", is(notNullValue())); + } } \ No newline at end of file