diff --git a/src/main/java/io/supertokens/oauth/OAuth.java b/src/main/java/io/supertokens/oauth/OAuth.java index 954132ca2..91bc25d90 100644 --- a/src/main/java/io/supertokens/oauth/OAuth.java +++ b/src/main/java/io/supertokens/oauth/OAuth.java @@ -258,13 +258,13 @@ private static void checkNonSuccessResponse(HttpRequest.Response response) throw public static JsonObject transformTokens(Main main, AppIdentifier appIdentifier, Storage storage, JsonObject jsonBody, String iss, boolean useDynamicKey) throws IOException, JWTException, InvalidKeyException, NoSuchAlgorithmException, StorageQueryException, StorageTransactionLogicException, UnsupportedJWTSigningAlgorithmException, TenantOrAppNotFoundException, InvalidKeySpecException, JWTCreationException, InvalidConfigException { if (jsonBody.has("access_token")) { String accessToken = jsonBody.get("access_token").getAsString(); - accessToken = reSignToken(appIdentifier, main, accessToken, iss, SessionTokenType.ACCESS_TOKEN, useDynamicKey, 0); + accessToken = OAuthToken.reSignToken(appIdentifier, main, accessToken, iss, OAuthToken.TokenType.ACCESS_TOKEN, useDynamicKey, 0); jsonBody.addProperty("access_token", accessToken); } if (jsonBody.has("id_token")) { String idToken = jsonBody.get("id_token").getAsString(); - idToken = reSignToken(appIdentifier, main, idToken, iss, SessionTokenType.ID_TOKEN, useDynamicKey, 0); + idToken = OAuthToken.reSignToken(appIdentifier, main, idToken, iss, OAuthToken.TokenType.ID_TOKEN, useDynamicKey, 0); jsonBody.addProperty("id_token", idToken); } @@ -277,35 +277,6 @@ public static JsonObject transformTokens(Main main, AppIdentifier appIdentifier, return jsonBody; } - private static String reSignToken(AppIdentifier appIdentifier, Main main, String token, String iss, SessionTokenType tokenType, boolean useDynamicSigningKey, int retryCount) throws IOException, JWTException, InvalidKeyException, NoSuchAlgorithmException, StorageQueryException, StorageTransactionLogicException, UnsupportedJWTSigningAlgorithmException, TenantOrAppNotFoundException, InvalidKeySpecException, JWTCreationException, InvalidConfigException { - // Load the JWKS from the specified URL - JsonObject payload = JWT.getPayloadWithoutVerifying(token).payload; - - // move keys in ext to root - if (tokenType == SessionTokenType.ACCESS_TOKEN && payload.has("ext")) { - JsonObject ext = payload.getAsJsonObject("ext"); - for (Map.Entry entry : ext.entrySet()) { - payload.add(entry.getKey(), entry.getValue()); - } - payload.remove("ext"); - } - payload.addProperty("iss", iss); - payload.addProperty("stt", tokenType.getValue()); - - JWTSigningKeyInfo keyToUse; - if (useDynamicSigningKey) { - keyToUse = Utils.getJWTSigningKeyInfoFromKeyInfo( - SigningKeys.getInstance(appIdentifier, main).getLatestIssuedDynamicKey()); - } else { - keyToUse = SigningKeys.getInstance(appIdentifier, main) - .getStaticKeyForAlgorithm(JWTSigningKey.SupportedAlgorithms.RS256); - } - - token = JWTSigningFunctions.createJWTToken(JWTSigningKey.SupportedAlgorithms.RS256, new HashMap<>(), - payload, null, payload.get("exp").getAsLong(), payload.get("iat").getAsLong(), keyToUse); - return token; - } - public static void addClientId(Main main, AppIdentifier appIdentifier, Storage storage, String clientId) throws StorageQueryException, OAuth2ClientAlreadyExistsForAppException { OAuthStorage oauthStorage = StorageUtils.getOAuthStorage(storage); oauthStorage.addClientForApp(appIdentifier, clientId); @@ -366,26 +337,11 @@ public static JsonElement convertSnakeCaseToCamelCaseRecursively(JsonElement jso } - public static enum SessionTokenType { - ACCESS_TOKEN(1), - ID_TOKEN(2); - - private final int value; - - SessionTokenType(int value) { - this.value = value; - } - - public int getValue() { - return value; - } - } - public static JsonObject introspectAccessToken(Main main, AppIdentifier appIdentifier, Storage storage, String token) throws StorageQueryException, StorageTransactionLogicException, TenantOrAppNotFoundException, UnsupportedJWTSigningAlgorithmException { try { - JsonObject payload = AccessToken.getPayloadFromAccessToken(appIdentifier, main, token); - if (payload.has("stt") && payload.get("stt").getAsInt() == SessionTokenType.ACCESS_TOKEN.value) { + JsonObject payload = OAuthToken.getPayloadFromJWTToken(appIdentifier, main, token); + if (payload.has("stt") && payload.get("stt").getAsInt() == OAuthToken.TokenType.ACCESS_TOKEN.getValue()) { payload.addProperty("active", true); payload.addProperty("token_type", "Bearer"); payload.addProperty("token_use", "access_token"); diff --git a/src/main/java/io/supertokens/oauth/OAuthToken.java b/src/main/java/io/supertokens/oauth/OAuthToken.java new file mode 100644 index 000000000..4c41e65d0 --- /dev/null +++ b/src/main/java/io/supertokens/oauth/OAuthToken.java @@ -0,0 +1,119 @@ +package io.supertokens.oauth; + +import com.auth0.jwt.exceptions.JWTCreationException; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import io.supertokens.Main; +import io.supertokens.exceptions.TryRefreshTokenException; +import io.supertokens.jwt.JWTSigningFunctions; +import io.supertokens.jwt.exceptions.UnsupportedJWTSigningAlgorithmException; +import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; +import io.supertokens.pluginInterface.jwt.JWTAsymmetricSigningKeyInfo; +import io.supertokens.pluginInterface.jwt.JWTSigningKeyInfo; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; +import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; +import io.supertokens.session.jwt.JWT; +import io.supertokens.session.jwt.JWT.JWTException; +import io.supertokens.signingkeys.JWTSigningKey; +import io.supertokens.signingkeys.SigningKeys; +import io.supertokens.utils.Utils; + +import javax.annotation.Nonnull; +import java.io.IOException; +import java.security.InvalidKeyException; +import java.security.KeyException; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class OAuthToken { + public enum TokenType { + ACCESS_TOKEN(1), + ID_TOKEN(2); + + private final int value; + + TokenType(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + } + + public static JsonObject getPayloadFromJWTToken(AppIdentifier appIdentifier, + @Nonnull Main main, @Nonnull String token) + throws TenantOrAppNotFoundException, TryRefreshTokenException, StorageQueryException, + UnsupportedJWTSigningAlgorithmException, StorageTransactionLogicException { + List keyInfoList = SigningKeys.getInstance(appIdentifier, main).getAllKeys(); + Exception error = null; + JWT.JWTInfo jwtInfo = null; + JWT.JWTPreParseInfo preParseJWTInfo = null; + try { + preParseJWTInfo = JWT.preParseJWTInfo(token); + } catch (JWTException e) { + // This basically should never happen, but it means, that the token structure is + // wrong, can't verify + throw new TryRefreshTokenException(e); + } + + for (JWTSigningKeyInfo keyInfo : keyInfoList) { + try { + jwtInfo = JWT.verifyJWTAndGetPayload(preParseJWTInfo, + ((JWTAsymmetricSigningKeyInfo) keyInfo).publicKey); + error = null; + break; + } catch (NoSuchAlgorithmException e) { + // This basically should never happen, but it means, that can't verify any + // tokens, no need to retry + throw new TryRefreshTokenException(e); + } catch (KeyException | JWTException e) { + error = e; + } + } + + if (jwtInfo == null) { + throw new TryRefreshTokenException(error); + } + + if (jwtInfo.payload.get("exp").getAsLong() * 1000 < System.currentTimeMillis()) { + throw new TryRefreshTokenException("Access token expired"); + } + + return jwtInfo.payload; + } + + public static String reSignToken(AppIdentifier appIdentifier, Main main, String token, String iss, TokenType tokenType, boolean useDynamicSigningKey, int retryCount) throws IOException, JWTException, InvalidKeyException, NoSuchAlgorithmException, StorageQueryException, StorageTransactionLogicException, UnsupportedJWTSigningAlgorithmException, TenantOrAppNotFoundException, InvalidKeySpecException, + JWTCreationException { + // Load the JWKS from the specified URL + JsonObject payload = JWT.getPayloadWithoutVerifying(token).payload; + + // move keys in ext to root + if (tokenType == TokenType.ACCESS_TOKEN && payload.has("ext")) { + JsonObject ext = payload.getAsJsonObject("ext"); + for (Map.Entry entry : ext.entrySet()) { + payload.add(entry.getKey(), entry.getValue()); + } + payload.remove("ext"); + } + payload.addProperty("iss", iss); + payload.addProperty("stt", tokenType.getValue()); + + JWTSigningKeyInfo keyToUse; + if (useDynamicSigningKey) { + keyToUse = Utils.getJWTSigningKeyInfoFromKeyInfo( + SigningKeys.getInstance(appIdentifier, main).getLatestIssuedDynamicKey()); + } else { + keyToUse = SigningKeys.getInstance(appIdentifier, main) + .getStaticKeyForAlgorithm(JWTSigningKey.SupportedAlgorithms.RS256); + } + + token = JWTSigningFunctions.createJWTToken(JWTSigningKey.SupportedAlgorithms.RS256, new HashMap<>(), + payload, null, payload.get("exp").getAsLong(), payload.get("iat").getAsLong(), keyToUse); + return token; + } +} diff --git a/src/main/java/io/supertokens/oauth/exceptions/OAuthAPIInvalidInputException.java b/src/main/java/io/supertokens/oauth/exceptions/OAuthAPIInvalidInputException.java deleted file mode 100644 index e84f89611..000000000 --- a/src/main/java/io/supertokens/oauth/exceptions/OAuthAPIInvalidInputException.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved. - * - * This software is licensed under the Apache License, Version 2.0 (the - * "License") as published by the Apache Software Foundation. - * - * 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 io.supertokens.oauth.exceptions; - -import java.io.Serial; - -public class OAuthAPIInvalidInputException extends OAuthException{ - - @Serial - private static final long serialVersionUID = 665027786586190611L; - - public OAuthAPIInvalidInputException(String error, String errorDescription) { - super(error, errorDescription, 400); - } -} diff --git a/src/main/java/io/supertokens/oauth/exceptions/OAuthClientUpdateException.java b/src/main/java/io/supertokens/oauth/exceptions/OAuthClientUpdateException.java deleted file mode 100644 index a86aa3a27..000000000 --- a/src/main/java/io/supertokens/oauth/exceptions/OAuthClientUpdateException.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved. - * - * This software is licensed under the Apache License, Version 2.0 (the - * "License") as published by the Apache Software Foundation. - * - * 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 io.supertokens.oauth.exceptions; - -import java.io.Serial; - -public class OAuthClientUpdateException extends OAuthException{ - @Serial - private static final long serialVersionUID = -5191044905397936167L; - - public OAuthClientUpdateException(String error, String errorDescription, int statusCode) { - super(error, errorDescription, statusCode); - } -} diff --git a/src/main/java/io/supertokens/oauth/exceptions/OAuthException.java b/src/main/java/io/supertokens/oauth/exceptions/OAuthException.java deleted file mode 100644 index fe8252391..000000000 --- a/src/main/java/io/supertokens/oauth/exceptions/OAuthException.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved. - * - * This software is licensed under the Apache License, Version 2.0 (the - * "License") as published by the Apache Software Foundation. - * - * 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 io.supertokens.oauth.exceptions; - -import java.io.Serial; - -public class OAuthException extends Exception{ - @Serial - private static final long serialVersionUID = 1836718299845759897L; - - public final String error; - public final String errorDescription; - public final int statusCode; - - public OAuthException(String error, String errorDescription, int statusCode){ - super(error); - this.error = error; - this.errorDescription = errorDescription; - this.statusCode = statusCode; - } -} diff --git a/src/main/java/io/supertokens/session/accessToken/AccessToken.java b/src/main/java/io/supertokens/session/accessToken/AccessToken.java index 0b841642c..4e316ad28 100644 --- a/src/main/java/io/supertokens/session/accessToken/AccessToken.java +++ b/src/main/java/io/supertokens/session/accessToken/AccessToken.java @@ -56,46 +56,6 @@ public class AccessToken { - public static JsonObject getPayloadFromAccessToken(AppIdentifier appIdentifier, - @Nonnull Main main, @Nonnull String token) - throws TenantOrAppNotFoundException, TryRefreshTokenException, StorageQueryException, - UnsupportedJWTSigningAlgorithmException, StorageTransactionLogicException { - List keyInfoList = SigningKeys.getInstance(appIdentifier, main).getAllKeys(); - Exception error = null; - JWT.JWTInfo jwtInfo = null; - JWT.JWTPreParseInfo preParseJWTInfo = null; - try { - preParseJWTInfo = JWT.preParseJWTInfo(token); - } catch (JWTException e) { - // This basically should never happen, but it means, that the token structure is wrong, can't verify - throw new TryRefreshTokenException(e); - } - - for (JWTSigningKeyInfo keyInfo : keyInfoList) { - try { - jwtInfo = JWT.verifyJWTAndGetPayload(preParseJWTInfo, - ((JWTAsymmetricSigningKeyInfo) keyInfo).publicKey); - error = null; - break; - } catch (NoSuchAlgorithmException e) { - // This basically should never happen, but it means, that can't verify any tokens, no need to retry - throw new TryRefreshTokenException(e); - } catch (KeyException | JWTException e) { - error = e; - } - } - - if (jwtInfo == null) { - throw new TryRefreshTokenException(error); - } - - if (jwtInfo.payload.get("exp").getAsLong() * 1000 < System.currentTimeMillis()) { - throw new TryRefreshTokenException("Access token expired"); - } - - return jwtInfo.payload; - } - // TODO: device fingerprint - store hash of this in JWT. private static AccessTokenInfo getInfoFromAccessToken(AppIdentifier appIdentifier,