diff --git a/CHANGELOG.md b/CHANGELOG.md index 40c0ded73..12303b44d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - `oauth_provider_public_service_url` - `oauth_provider_admin_service_url` - `oauth_provider_consent_login_base_url` - - `oauth_provider_url_configured_in_hydra` + - `oauth_provider_url_configured_in_oauth_provider` - Adds POST `/recipe/oauth/auth` for OAuth2 auth flow support - Adds POST `/recipe/oauth/clients` for OAuth2 client registration - Adds GET `/recipe/oauth/clients?clientId=example_id` for loading OAuth2 client diff --git a/config.yaml b/config.yaml index 7dd967768..1acbfabf8 100644 --- a/config.yaml +++ b/config.yaml @@ -153,17 +153,17 @@ core_config_version: 0 # supertokens_saas_load_only_cud: # (OPTIONAL | Default: null) string value. If specified, the core uses this URL to connect to the OAuth provider -# public service. +# public service. # oauth_provider_public_service_url: # (OPTIONAL | Default: null) string value. If specified, the core uses this URL to connect to the OAuth provider admin -# service. +# service. # oauth_provider_admin_service_url: # (OPTIONAL | Default: null) string value. If specified, the core uses this URL replace the default # consent and login URLs to {apiDomain}. # oauth_provider_consent_login_base_url: -# (OPTIONAL | Default: oauth_provider_public_service_url) If specified, the core uses this URL to parse responses from the oauth provider when -# the oauth provider's internal address differs from the known public provider address. -# oauth_provider_url_configured_in_hydra: +# (OPTIONAL | Default: oauth_provider_public_service_url) If specified, the core uses this URL to parse responses from +# the oauth provider when the oauth provider's internal address differs from the known public provider address. +# oauth_provider_url_configured_in_oauth_provider: diff --git a/devConfig.yaml b/devConfig.yaml index ed83e6bb5..c6653e403 100644 --- a/devConfig.yaml +++ b/devConfig.yaml @@ -153,17 +153,17 @@ disable_telemetry: true # supertokens_saas_load_only_cud: # (OPTIONAL | Default: null) string value. If specified, the core uses this URL to connect to the OAuth provider -# public service. -oauth_provider_public_service_url: http://localhost:4444 +# public service. +# oauth_provider_public_service_url: # (OPTIONAL | Default: null) string value. If specified, the core uses this URL to connect to the OAuth provider admin -# service. -oauth_provider_admin_service_url: http://localhost:4445 +# service. +# oauth_provider_admin_service_url: # (OPTIONAL | Default: null) string value. If specified, the core uses this URL replace the default # consent and login URLs to {apiDomain}. -oauth_provider_consent_login_base_url: http://localhost:4001/auth +# oauth_provider_consent_login_base_url: -# (OPTIONAL | Default: oauth_provider_public_service_url) If specified, the core uses this URL to parse responses from the oauth provider when -# the oauth provider's internal address differs from the known public provider address. -# oauth_provider_url_configured_in_hydra: +# (OPTIONAL | Default: oauth_provider_public_service_url) If specified, the core uses this URL to parse responses from +# the oauth provider when the oauth provider's internal address differs from the known public provider address. +# oauth_provider_url_configured_in_oauth_provider: diff --git a/src/main/java/io/supertokens/Main.java b/src/main/java/io/supertokens/Main.java index 1eef7e500..7a4fded61 100644 --- a/src/main/java/io/supertokens/Main.java +++ b/src/main/java/io/supertokens/Main.java @@ -20,7 +20,7 @@ import io.supertokens.config.Config; import io.supertokens.config.CoreConfig; import io.supertokens.cronjobs.Cronjobs; -import io.supertokens.cronjobs.cleanupOAuthRevokeList.CleanupOAuthRevokeList; +import io.supertokens.cronjobs.cleanupOAuthRevokeListAndChallenges.CleanupOAuthRevokeListAndChallenges; import io.supertokens.cronjobs.deleteExpiredAccessTokenSigningKeys.DeleteExpiredAccessTokenSigningKeys; import io.supertokens.cronjobs.deleteExpiredDashboardSessions.DeleteExpiredDashboardSessions; import io.supertokens.cronjobs.deleteExpiredEmailVerificationTokens.DeleteExpiredEmailVerificationTokens; @@ -257,7 +257,7 @@ private void init() throws IOException, StorageQueryException { // starts DeleteExpiredAccessTokenSigningKeys cronjob if the access token signing keys can change Cronjobs.addCronjob(this, DeleteExpiredAccessTokenSigningKeys.init(this, uniqueUserPoolIdsTenants)); - Cronjobs.addCronjob(this, CleanupOAuthRevokeList.init(this, uniqueUserPoolIdsTenants)); + Cronjobs.addCronjob(this, CleanupOAuthRevokeListAndChallenges.init(this, uniqueUserPoolIdsTenants)); // this is to ensure tenantInfos are in sync for the new cron job as well MultitenancyHelper.getInstance(this).refreshCronjobs(); diff --git a/src/main/java/io/supertokens/config/CoreConfig.java b/src/main/java/io/supertokens/config/CoreConfig.java index b82053248..1f90400a1 100644 --- a/src/main/java/io/supertokens/config/CoreConfig.java +++ b/src/main/java/io/supertokens/config/CoreConfig.java @@ -67,7 +67,7 @@ public class CoreConfig { "oauth_provider_public_service_url", "oauth_provider_admin_service_url", "oauth_provider_consent_login_base_url", - "oauth_provider_url_configured_in_hydra" + "oauth_provider_url_configured_in_oauth_provider" }; @IgnoreForAnnotationCheck @@ -297,15 +297,15 @@ public class CoreConfig { @JsonProperty @HideFromDashboard @ConfigDescription( - "If specified, the core uses this URL replace the default consent and login URLs to {apiDomain}. Defaults to 'null'") + "If specified, the core uses this URL replace the default consent and login URLs to {apiDomain}.") private String oauth_provider_consent_login_base_url = null; @NotConflictingInApp @JsonProperty @HideFromDashboard @ConfigDescription( - "If specified, the core uses this URL to parse responses from the oauth provider when the oauth provider's internal address differs from the known public provider address. Defaults to the oauth_provider_public_service_url") - private String oauth_provider_url_configured_in_hydra = null; + "If specified, the core uses this URL to parse responses from the oauth provider when the oauth provider's internal address differs from the known public provider address.") + private String oauth_provider_url_configured_in_oauth_provider = null; @ConfigYamlOnly @JsonProperty @@ -373,11 +373,11 @@ public String getOauthProviderConsentLoginBaseUrl() throws InvalidConfigExceptio return oauth_provider_consent_login_base_url; } - public String getOauthProviderUrlConfiguredInHydra() throws InvalidConfigException { - if(oauth_provider_url_configured_in_hydra == null) { - throw new InvalidConfigException("oauth_provider_url_configured_in_hydra is not set"); + public String getOAuthProviderUrlConfiguredInOAuthProvider() throws InvalidConfigException { + if(oauth_provider_url_configured_in_oauth_provider == null) { + throw new InvalidConfigException("oauth_provider_url_configured_in_oauth_provider is not set"); } - return oauth_provider_url_configured_in_hydra; + return oauth_provider_url_configured_in_oauth_provider; } public String getIpAllowRegex() { @@ -891,13 +891,13 @@ void normalizeAndValidate(Main main, boolean includeConfigFilePath) throws Inval } - if(oauth_provider_url_configured_in_hydra == null) { - oauth_provider_url_configured_in_hydra = oauth_provider_public_service_url; + if(oauth_provider_url_configured_in_oauth_provider == null) { + oauth_provider_url_configured_in_oauth_provider = oauth_provider_public_service_url; } else { try { - URL url = new URL(oauth_provider_url_configured_in_hydra); + URL url = new URL(oauth_provider_url_configured_in_oauth_provider); } catch (MalformedURLException malformedURLException){ - throw new InvalidConfigException("oauth_provider_url_configured_in_hydra is not a valid URL"); + throw new InvalidConfigException("oauth_provider_url_configured_in_oauth_provider is not a valid URL"); } } diff --git a/src/main/java/io/supertokens/cronjobs/cleanupOAuthRevokeList/CleanupOAuthRevokeList.java b/src/main/java/io/supertokens/cronjobs/cleanupOAuthRevokeListAndChallenges/CleanupOAuthRevokeListAndChallenges.java similarity index 68% rename from src/main/java/io/supertokens/cronjobs/cleanupOAuthRevokeList/CleanupOAuthRevokeList.java rename to src/main/java/io/supertokens/cronjobs/cleanupOAuthRevokeListAndChallenges/CleanupOAuthRevokeListAndChallenges.java index a94d65d51..b94370839 100644 --- a/src/main/java/io/supertokens/cronjobs/cleanupOAuthRevokeList/CleanupOAuthRevokeList.java +++ b/src/main/java/io/supertokens/cronjobs/cleanupOAuthRevokeListAndChallenges/CleanupOAuthRevokeListAndChallenges.java @@ -1,4 +1,4 @@ -package io.supertokens.cronjobs.cleanupOAuthRevokeList; +package io.supertokens.cronjobs.cleanupOAuthRevokeListAndChallenges; import java.util.List; @@ -12,19 +12,19 @@ import io.supertokens.pluginInterface.oauth.OAuthStorage; import io.supertokens.storageLayer.StorageLayer; -public class CleanupOAuthRevokeList extends CronTask { +public class CleanupOAuthRevokeListAndChallenges extends CronTask { - public static final String RESOURCE_KEY = "io.supertokens.cronjobs.cleanupOAuthRevokeList" + - ".CleanupOAuthRevokeList"; + public static final String RESOURCE_KEY = "io.supertokens.cronjobs.cleanupOAuthRevokeListAndChallenges" + + ".CleanupOAuthRevokeListAndChallenges"; - private CleanupOAuthRevokeList(Main main, List> tenantsInfo) { + private CleanupOAuthRevokeListAndChallenges(Main main, List> tenantsInfo) { super("CleanupOAuthRevokeList", main, tenantsInfo, true); } - public static CleanupOAuthRevokeList init(Main main, List> tenantsInfo) { - return (CleanupOAuthRevokeList) main.getResourceDistributor() + public static CleanupOAuthRevokeListAndChallenges init(Main main, List> tenantsInfo) { + return (CleanupOAuthRevokeListAndChallenges) main.getResourceDistributor() .setResource(new TenantIdentifier(null, null, null), RESOURCE_KEY, - new CleanupOAuthRevokeList(main, tenantsInfo)); + new CleanupOAuthRevokeListAndChallenges(main, tenantsInfo)); } @Override @@ -32,6 +32,7 @@ protected void doTaskPerApp(AppIdentifier app) throws Exception { Storage storage = StorageLayer.getStorage(app.getAsPublicTenantIdentifier(), main); OAuthStorage oauthStorage = StorageUtils.getOAuthStorage(storage); oauthStorage.cleanUpExpiredAndRevokedTokens(app); + oauthStorage.deleteLogoutChallengesBefore(app, System.currentTimeMillis() - 1000 * 60 * 60 * 48); } @Override diff --git a/src/main/java/io/supertokens/inmemorydb/Start.java b/src/main/java/io/supertokens/inmemorydb/Start.java index 288e6593e..93715c091 100644 --- a/src/main/java/io/supertokens/inmemorydb/Start.java +++ b/src/main/java/io/supertokens/inmemorydb/Start.java @@ -55,6 +55,7 @@ import io.supertokens.pluginInterface.multitenancy.exceptions.DuplicateThirdPartyIdException; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.multitenancy.sqlStorage.MultitenancySQLStorage; +import io.supertokens.pluginInterface.oauth.OAuthLogoutChallenge; import io.supertokens.pluginInterface.oauth.sqlStorage.OAuthSQLStorage; import io.supertokens.pluginInterface.passwordless.PasswordlessCode; import io.supertokens.pluginInterface.passwordless.PasswordlessDevice; @@ -3077,6 +3078,43 @@ public void addM2MToken(AppIdentifier appIdentifier, String clientId, long iat, } } + @Override + public void addLogoutChallenge(AppIdentifier appIdentifier, String challenge, String clientId, + String postLogoutRedirectionUri, String sessionHandle, String state, long timeCreated) throws StorageQueryException { + try { + OAuthQueries.addLogoutChallenge(this, appIdentifier, challenge, clientId, postLogoutRedirectionUri, sessionHandle, state, timeCreated); + } catch (SQLException e) { + throw new StorageQueryException(e); + } + } + + @Override + public OAuthLogoutChallenge getLogoutChallenge(AppIdentifier appIdentifier, String challenge) throws StorageQueryException { + try { + return OAuthQueries.getLogoutChallenge(this, appIdentifier, challenge); + } catch (SQLException e) { + throw new StorageQueryException(e); + } + } + + @Override + public void deleteLogoutChallenge(AppIdentifier appIdentifier, String challenge) throws StorageQueryException { + try { + OAuthQueries.deleteLogoutChallenge(this, appIdentifier, challenge); + } catch (SQLException e) { + throw new StorageQueryException(e); + } + } + + @Override + public void deleteLogoutChallengesBefore(AppIdentifier appIdentifier, long time) throws StorageQueryException { + try { + OAuthQueries.deleteLogoutChallengesBefore(this, appIdentifier, time); + } catch (SQLException e) { + throw new StorageQueryException(e); + } + } + @Override public void cleanUpExpiredAndRevokedTokens(AppIdentifier appIdentifier) throws StorageQueryException { try { diff --git a/src/main/java/io/supertokens/inmemorydb/config/SQLiteConfig.java b/src/main/java/io/supertokens/inmemorydb/config/SQLiteConfig.java index c14646456..75b98b3d5 100644 --- a/src/main/java/io/supertokens/inmemorydb/config/SQLiteConfig.java +++ b/src/main/java/io/supertokens/inmemorydb/config/SQLiteConfig.java @@ -176,4 +176,8 @@ public String getOAuthRevokeTable() { public String getOAuthM2MTokensTable() { return "oauth_m2m_tokens"; } + + public String getOAuthLogoutChallengesTable() { + return "oauth_logout_challenges"; + } } diff --git a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java index 13d4ee092..1c23d487d 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java @@ -444,8 +444,15 @@ public static void createTablesIfNotExists(Start start, Main main) throws SQLExc update(start, OAuthQueries.getQueryToCreateOAuthM2MTokenIatIndex(start), NO_OP_SETTER); update(start, OAuthQueries.getQueryToCreateOAuthM2MTokenExpIndex(start), NO_OP_SETTER); } - } + if (!doesTableExists(start, Config.getConfig(start).getOAuthLogoutChallengesTable())) { + getInstance(main).addState(CREATING_NEW_TABLE, null); + update(start, OAuthQueries.getQueryToCreateOAuthLogoutChallengesTable(start), NO_OP_SETTER); + + // index + update(start, OAuthQueries.getQueryToCreateOAuthLogoutChallengesTimeCreatedIndex(start), NO_OP_SETTER); + } + } public static void setKeyValue_Transaction(Start start, Connection con, TenantIdentifier tenantIdentifier, String key, KeyValueInfo info) diff --git a/src/main/java/io/supertokens/inmemorydb/queries/OAuthQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/OAuthQueries.java index 5a27ee8d5..6aec76e02 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/OAuthQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/OAuthQueries.java @@ -20,6 +20,7 @@ import io.supertokens.inmemorydb.config.Config; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; +import io.supertokens.pluginInterface.oauth.OAuthLogoutChallenge; import java.sql.ResultSet; import java.sql.SQLException; @@ -92,6 +93,32 @@ public static String getQueryToCreateOAuthM2MTokenExpIndex(Start start) { + oAuth2M2MTokensTable + "(exp DESC, app_id DESC);"; } + public static String getQueryToCreateOAuthLogoutChallengesTable(Start start) { + String oAuth2LogoutChallengesTable = Config.getConfig(start).getOAuthLogoutChallengesTable(); + // @formatter:off + return "CREATE TABLE IF NOT EXISTS " + oAuth2LogoutChallengesTable + " (" + + "app_id VARCHAR(64) DEFAULT 'public'," + + "challenge VARCHAR(128) NOT NULL," + + "client_id VARCHAR(128) NOT NULL," + + "post_logout_redirect_uri VARCHAR(1024)," + + "session_handle VARCHAR(128)," + + "state VARCHAR(128)," + + "time_created BIGINT NOT NULL," + + "PRIMARY KEY (app_id, challenge)," + + "FOREIGN KEY(app_id, client_id)" + + " REFERENCES " + Config.getConfig(start).getOAuthClientsTable() + "(app_id, client_id) ON DELETE CASCADE," + + "FOREIGN KEY(app_id)" + + " REFERENCES " + Config.getConfig(start).getAppsTable() + "(app_id) ON DELETE CASCADE" + + ");"; + // @formatter:on + } + + public static String getQueryToCreateOAuthLogoutChallengesTimeCreatedIndex(Start start) { + String oAuth2LogoutChallengesTable = Config.getConfig(start).getOAuthLogoutChallengesTable(); + return "CREATE INDEX IF NOT EXISTS oauth_logout_challenges_time_created_index ON " + + oAuth2LogoutChallengesTable + "(time_created ASC, app_id ASC);"; + } + public static boolean isClientIdForAppId(Start start, String clientId, AppIdentifier appIdentifier) throws SQLException, StorageQueryException { String QUERY = "SELECT app_id FROM " + Config.getConfig(start).getOAuthClientsTable() + @@ -164,7 +191,7 @@ public static void revoke(Start start, AppIdentifier appIdentifier, String targe public static boolean isRevoked(Start start, AppIdentifier appIdentifier, String[] targetTypes, String[] targetValues, long issuedAt) throws SQLException, StorageQueryException { String QUERY = "SELECT app_id FROM " + Config.getConfig(start).getOAuthRevokeTable() + - " WHERE app_id = ? AND timestamp > ? AND ("; + " WHERE app_id = ? AND timestamp >= ? AND ("; for (int i = 0; i < targetTypes.length; i++) { QUERY += "(target_type = ? AND target_value = ?)"; @@ -285,4 +312,60 @@ public static void cleanUpExpiredAndRevokedTokens(Start start, AppIdentifier app }); } } + + public static void addLogoutChallenge(Start start, AppIdentifier appIdentifier, String challenge, String clientId, + String postLogoutRedirectionUri, String sessionHandle, String state, long timeCreated) throws SQLException, StorageQueryException { + String QUERY = "INSERT INTO " + Config.getConfig(start).getOAuthLogoutChallengesTable() + + " (app_id, challenge, client_id, post_logout_redirect_uri, session_handle, state, time_created) VALUES (?, ?, ?, ?, ?, ?, ?)"; + update(start, QUERY, pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setString(2, challenge); + pst.setString(3, clientId); + pst.setString(4, postLogoutRedirectionUri); + pst.setString(5, sessionHandle); + pst.setString(6, state); + pst.setLong(7, timeCreated); + }); + } + + public static OAuthLogoutChallenge getLogoutChallenge(Start start, AppIdentifier appIdentifier, String challenge) throws SQLException, StorageQueryException { + String QUERY = "SELECT challenge, client_id, post_logout_redirect_uri, session_handle, state, time_created FROM " + + Config.getConfig(start).getOAuthLogoutChallengesTable() + + " WHERE app_id = ? AND challenge = ?"; + + return execute(start, QUERY, pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setString(2, challenge); + }, result -> { + if (result.next()) { + return new OAuthLogoutChallenge( + result.getString("challenge"), + result.getString("client_id"), + result.getString("post_logout_redirect_uri"), + result.getString("session_handle"), + result.getString("state"), + result.getLong("time_created") + ); + } + return null; + }); + } + + public static void deleteLogoutChallenge(Start start, AppIdentifier appIdentifier, String challenge) throws SQLException, StorageQueryException { + String QUERY = "DELETE FROM " + Config.getConfig(start).getOAuthLogoutChallengesTable() + + " WHERE app_id = ? AND challenge = ?"; + update(start, QUERY, pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setString(2, challenge); + }); + } + + public static void deleteLogoutChallengesBefore(Start start, AppIdentifier appIdentifier, long time) throws SQLException, StorageQueryException { + String QUERY = "DELETE FROM " + Config.getConfig(start).getOAuthLogoutChallengesTable() + + " WHERE app_id = ? AND time_created < ?"; + update(start, QUERY, pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setLong(2, time); + }); + } } diff --git a/src/main/java/io/supertokens/oauth/OAuth.java b/src/main/java/io/supertokens/oauth/OAuth.java index 0c73d6a33..3cff18da7 100644 --- a/src/main/java/io/supertokens/oauth/OAuth.java +++ b/src/main/java/io/supertokens/oauth/OAuth.java @@ -36,6 +36,7 @@ import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; +import io.supertokens.pluginInterface.oauth.OAuthLogoutChallenge; import io.supertokens.pluginInterface.oauth.OAuthStorage; import io.supertokens.session.jwt.JWT.JWTException; import io.supertokens.utils.Utils; @@ -330,6 +331,8 @@ public static String transformTokensInAuthRedirect(Main main, AppIdentifier appI public static JsonObject transformTokens(Main main, AppIdentifier appIdentifier, Storage storage, JsonObject jsonBody, String iss, JsonObject accessTokenUpdate, JsonObject idTokenUpdate, boolean useDynamicKey) throws IOException, JWTException, InvalidKeyException, NoSuchAlgorithmException, StorageQueryException, StorageTransactionLogicException, UnsupportedJWTSigningAlgorithmException, TenantOrAppNotFoundException, InvalidKeySpecException, JWTCreationException, InvalidConfigException { String atHash = null; + System.out.println("transformTokens: " + jsonBody.toString()); + if (jsonBody.has("refresh_token")) { String refreshToken = jsonBody.get("refresh_token").getAsString(); refreshToken = refreshToken.replace("ory_rt_", "st_rt_"); @@ -551,45 +554,57 @@ public static void revokeSessionHandle(Main main, AppIdentifier appIdentifier, S oauthStorage.revoke(appIdentifier, "session_handle", sessionHandle, exp); } - public static void verifyIdTokenHintClientIdAndUpdateQueryParamsForLogout(Main main, AppIdentifier appIdentifier, Storage storage, - Map queryParams) throws StorageQueryException, OAuthAPIException, TenantOrAppNotFoundException, UnsupportedJWTSigningAlgorithmException, StorageTransactionLogicException { + public static JsonObject verifyIdTokenAndGetPayload(Main main, AppIdentifier appIdentifier, Storage storage, + String idToken) throws StorageQueryException, OAuthAPIException, TenantOrAppNotFoundException, UnsupportedJWTSigningAlgorithmException, StorageTransactionLogicException { + try { + return OAuthToken.getPayloadFromJWTToken(appIdentifier, main, idToken); + } catch (TryRefreshTokenException e) { + // invalid id token + throw new OAuthAPIException("invalid_request", "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed.", 400); + } + } - String idTokenHint = queryParams.get("idTokenHint"); - String clientId = queryParams.get("clientId"); + public static void addM2MToken(Main main, AppIdentifier appIdentifier, Storage storage, String accessToken) throws StorageQueryException, TenantOrAppNotFoundException, TryRefreshTokenException, UnsupportedJWTSigningAlgorithmException, StorageTransactionLogicException { + OAuthStorage oauthStorage = StorageUtils.getOAuthStorage(storage); + JsonObject payload = OAuthToken.getPayloadFromJWTToken(appIdentifier, main, accessToken); + oauthStorage.addM2MToken(appIdentifier, payload.get("client_id").getAsString(), payload.get("iat").getAsLong(), payload.get("exp").getAsLong()); + } - JsonObject idTokenPayload = null; - if (idTokenHint != null) { - queryParams.remove("idTokenHint"); + public static String createLogoutRequestAndReturnRedirectUri(Main main, AppIdentifier appIdentifier, Storage storage, String clientId, + String postLogoutRedirectionUri, String sessionHandle, String state) throws StorageQueryException { + + OAuthStorage oauthStorage = StorageUtils.getOAuthStorage(storage); - try { - idTokenPayload = OAuthToken.getPayloadFromJWTToken(appIdentifier, main, idTokenHint); - } catch (TryRefreshTokenException e) { - // invalid id token - throw new OAuthAPIException("invalid_request", "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed.", 400); - } - } + String logoutChallenge = UUID.randomUUID().toString(); + oauthStorage.addLogoutChallenge(appIdentifier, logoutChallenge, clientId, postLogoutRedirectionUri, sessionHandle, state, System.currentTimeMillis()); - if (idTokenPayload != null) { - if (!idTokenPayload.has("stt") || idTokenPayload.get("stt").getAsInt() != OAuthToken.TokenType.ID_TOKEN.getValue()) { - // Invalid id token - throw new OAuthAPIException("invalid_request", "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed.", 400); - } + return "{apiDomain}/oauth/logout?logout_challenge=" + logoutChallenge; + } - String clientIdInIdTokenPayload = idTokenPayload.get("aud").getAsString(); + public static String consumeLogoutChallengeAndGetRedirectUri(Main main, AppIdentifier appIdentifier, Storage storage, String challenge) throws StorageQueryException, OAuthAPIException { + OAuthStorage oauthStorage = StorageUtils.getOAuthStorage(storage); + OAuthLogoutChallenge logoutChallenge = oauthStorage.getLogoutChallenge(appIdentifier, challenge); - if (clientId != null) { - if (!clientId.equals(clientIdInIdTokenPayload)) { - throw new OAuthAPIException("invalid_request", "The client_id in the id_token_hint does not match the client_id in the request.", 400); - } - } + if (logoutChallenge == null) { + throw new OAuthAPIException("invalid_request", "Logout request not found", 400); + } - queryParams.put("clientId", clientIdInIdTokenPayload); + revokeSessionHandle(main, appIdentifier, oauthStorage, logoutChallenge.sessionHandle); + + if (logoutChallenge.postLogoutRedirectionUri != null) { + String url = logoutChallenge.postLogoutRedirectionUri; + if (logoutChallenge.state != null) { + return url + "?state=" + logoutChallenge.state; + } else { + return url; + } + } else { + return "{apiDomain}/fallbacks/logout/callback"; } } - public static void addM2MToken(Main main, AppIdentifier appIdentifier, Storage storage, String accessToken) throws StorageQueryException, TenantOrAppNotFoundException, TryRefreshTokenException, UnsupportedJWTSigningAlgorithmException, StorageTransactionLogicException { + public static void deleteLogoutChallenge(Main main, AppIdentifier appIdentifier, Storage storage, String challenge) throws StorageQueryException { OAuthStorage oauthStorage = StorageUtils.getOAuthStorage(storage); - JsonObject payload = OAuthToken.getPayloadFromJWTToken(appIdentifier, main, accessToken); - oauthStorage.addM2MToken(appIdentifier, payload.get("client_id").getAsString(), payload.get("iat").getAsLong(), payload.get("exp").getAsLong()); + oauthStorage.deleteLogoutChallenge(appIdentifier, challenge); } } diff --git a/src/main/java/io/supertokens/oauth/OAuthToken.java b/src/main/java/io/supertokens/oauth/OAuthToken.java index 0d92b0b96..0a9ac61df 100644 --- a/src/main/java/io/supertokens/oauth/OAuthToken.java +++ b/src/main/java/io/supertokens/oauth/OAuthToken.java @@ -120,9 +120,10 @@ public static String reSignToken(AppIdentifier appIdentifier, Main main, String } } else { payloadUpdate = payload.getAsJsonObject("initialPayload"); - payload.remove("initialPayload"); } } + payload.remove("ext"); + payload.remove("initialPayload"); if (payloadUpdate != null) { for (Map.Entry entry : payloadUpdate.entrySet()) { diff --git a/src/main/java/io/supertokens/oauth/Transformations.java b/src/main/java/io/supertokens/oauth/Transformations.java index aa842470b..a0157696c 100644 --- a/src/main/java/io/supertokens/oauth/Transformations.java +++ b/src/main/java/io/supertokens/oauth/Transformations.java @@ -145,7 +145,7 @@ public static JsonElement transformJsonResponseFromHydra(Main main, AppIdentifie private static String transformRedirectUrlFromHydra(Main main, AppIdentifier appIdentifier,String redirectTo) throws InvalidConfigException, TenantOrAppNotFoundException, OAuthAPIException { String hydraInternalAddress = Config.getConfig(appIdentifier.getAsPublicTenantIdentifier(), main) - .getOauthProviderUrlConfiguredInHydra(); + .getOAuthProviderUrlConfiguredInOAuthProvider(); String hydraBaseUrlForConsentAndLogin = Config .getConfig(appIdentifier.getAsPublicTenantIdentifier(), main) .getOauthProviderConsentLoginBaseUrl(); diff --git a/src/main/java/io/supertokens/webserver/Webserver.java b/src/main/java/io/supertokens/webserver/Webserver.java index f283ca8b2..44e6a1339 100644 --- a/src/main/java/io/supertokens/webserver/Webserver.java +++ b/src/main/java/io/supertokens/webserver/Webserver.java @@ -280,7 +280,6 @@ private void setupRoutes() { addAPI(new OAuthGetAuthLoginRequestAPI(main)); addAPI(new OAuthAcceptAuthLoginRequestAPI(main)); addAPI(new OAuthRejectAuthLoginRequestAPI(main)); - addAPI(new OAuthGetAuthLogoutRequestAPI(main)); addAPI(new OAuthAcceptAuthLogoutRequestAPI(main)); addAPI(new OAuthRejectAuthLogoutRequestAPI(main)); addAPI(new OAuthTokenIntrospectAPI(main)); diff --git a/src/main/java/io/supertokens/webserver/api/oauth/OAuthAcceptAuthConsentRequestAPI.java b/src/main/java/io/supertokens/webserver/api/oauth/OAuthAcceptAuthConsentRequestAPI.java index f9d28c9fc..1579dfdd2 100644 --- a/src/main/java/io/supertokens/webserver/api/oauth/OAuthAcceptAuthConsentRequestAPI.java +++ b/src/main/java/io/supertokens/webserver/api/oauth/OAuthAcceptAuthConsentRequestAPI.java @@ -43,11 +43,11 @@ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IO accessToken.addProperty("tId", tId); accessToken.addProperty("rsub", rsub); accessToken.addProperty("sessionHandle", sessionHandle); + accessToken.addProperty("gid", UUID.randomUUID().toString()); accessToken.add("initialPayload", initialAccessTokenPayload); JsonObject idToken = new JsonObject(); idToken.add("initialPayload", initialIdTokenPayload); - accessToken.addProperty("gid", UUID.randomUUID().toString()); // remove the above from input input.remove("iss"); diff --git a/src/main/java/io/supertokens/webserver/api/oauth/OAuthAcceptAuthLogoutRequestAPI.java b/src/main/java/io/supertokens/webserver/api/oauth/OAuthAcceptAuthLogoutRequestAPI.java index 7d07c254d..052c47465 100644 --- a/src/main/java/io/supertokens/webserver/api/oauth/OAuthAcceptAuthLogoutRequestAPI.java +++ b/src/main/java/io/supertokens/webserver/api/oauth/OAuthAcceptAuthLogoutRequestAPI.java @@ -8,7 +8,10 @@ import io.supertokens.Main; import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.oauth.HttpRequestForOry; +import io.supertokens.oauth.OAuth; +import io.supertokens.oauth.exceptions.OAuthAPIException; import io.supertokens.pluginInterface.RECIPE_ID; +import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.webserver.InputParser; import io.supertokens.webserver.WebserverAPI; @@ -30,26 +33,19 @@ public String getPath() { @Override protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { JsonObject input = InputParser.parseJsonObjectOrThrowError(req); + String challenge = InputParser.parseStringOrThrowError(input, "challenge", false); try { - HttpRequestForOry.Response response = OAuthProxyHelper.proxyJsonPUT( - main, req, resp, - getAppIdentifier(req), - enforcePublicTenantAndGetPublicTenantStorage(req), - null, // clientIdToCheck - "/admin/oauth2/auth/requests/logout/accept", // proxyPath - true, // proxyToAdmin - true, // camelToSnakeCaseConversion - OAuthProxyHelper.defaultGetQueryParamsFromRequest(req), // queryParams - input, // jsonBody - new HashMap<>() // headers - ); - - if (response != null) { - response.jsonResponse.getAsJsonObject().addProperty("status", "OK"); - super.sendJsonResponse(200, response.jsonResponse, resp); - } - } catch (IOException | TenantOrAppNotFoundException | BadPermissionException e) { + String redirectTo = OAuth.consumeLogoutChallengeAndGetRedirectUri(main, getAppIdentifier(req), enforcePublicTenantAndGetPublicTenantStorage(req), challenge); + + JsonObject response = new JsonObject(); + response.addProperty("status", "OK"); + response.addProperty("redirectTo", redirectTo); + super.sendJsonResponse(200, response, resp); + + } catch (OAuthAPIException e) { + OAuthProxyHelper.handleOAuthAPIException(resp, e); + } catch (IOException | TenantOrAppNotFoundException | BadPermissionException | StorageQueryException e) { throw new ServletException(e); } } diff --git a/src/main/java/io/supertokens/webserver/api/oauth/OAuthGetAuthLogoutRequestAPI.java b/src/main/java/io/supertokens/webserver/api/oauth/OAuthGetAuthLogoutRequestAPI.java deleted file mode 100644 index 143e505b0..000000000 --- a/src/main/java/io/supertokens/webserver/api/oauth/OAuthGetAuthLogoutRequestAPI.java +++ /dev/null @@ -1,54 +0,0 @@ -package io.supertokens.webserver.api.oauth; - -import java.io.IOException; -import java.util.HashMap; - -import io.supertokens.Main; -import io.supertokens.multitenancy.exception.BadPermissionException; -import io.supertokens.oauth.HttpRequestForOry; -import io.supertokens.oauth.Transformations; -import io.supertokens.pluginInterface.RECIPE_ID; -import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; -import io.supertokens.webserver.WebserverAPI; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -public class OAuthGetAuthLogoutRequestAPI extends WebserverAPI { - - public OAuthGetAuthLogoutRequestAPI(Main main) { - super(main, RECIPE_ID.OAUTH.toString()); - } - - @Override - public String getPath() { - return "/recipe/oauth/auth/requests/logout"; - } - - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { - try { - HttpRequestForOry.Response response = OAuthProxyHelper.proxyGET( - main, req, resp, - getAppIdentifier(req), - enforcePublicTenantAndGetPublicTenantStorage(req), - null, // clientIdToCheck - "/admin/oauth2/auth/requests/logout", // proxyPath - true, // proxyToAdmin - true, // camelToSnakeCaseConversion - OAuthProxyHelper.defaultGetQueryParamsFromRequest(req), - new HashMap<>() // headers - ); - - if (response != null) { - Transformations.applyClientPropsWhiteList(response.jsonResponse.getAsJsonObject().get("client").getAsJsonObject()); - - response.jsonResponse.getAsJsonObject().addProperty("status", "OK"); - super.sendJsonResponse(200, response.jsonResponse, resp); - } - - } catch (IOException | TenantOrAppNotFoundException | BadPermissionException e) { - throw new ServletException(e); - } - } -} diff --git a/src/main/java/io/supertokens/webserver/api/oauth/OAuthLogoutAPI.java b/src/main/java/io/supertokens/webserver/api/oauth/OAuthLogoutAPI.java index 8e6626733..7b2e98ea8 100644 --- a/src/main/java/io/supertokens/webserver/api/oauth/OAuthLogoutAPI.java +++ b/src/main/java/io/supertokens/webserver/api/oauth/OAuthLogoutAPI.java @@ -4,6 +4,7 @@ import java.util.HashMap; import java.util.Map; +import com.google.gson.JsonArray; import com.google.gson.JsonObject; import io.supertokens.Main; @@ -18,6 +19,7 @@ import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; +import io.supertokens.webserver.InputParser; import io.supertokens.webserver.WebserverAPI; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; @@ -35,35 +37,105 @@ public String getPath() { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { + String clientId = InputParser.getQueryParamOrThrowError(req, "clientId", true); + String idTokenHint = InputParser.getQueryParamOrThrowError(req, "idTokenHint", true); + String postLogoutRedirectionUri = InputParser.getQueryParamOrThrowError(req, "postLogoutRedirectUri", true); + String state = InputParser.getQueryParamOrThrowError(req, "state", true); + try { AppIdentifier appIdentifier = getAppIdentifier(req); Storage storage = enforcePublicTenantAndGetPublicTenantStorage(req); - Map queryParams = OAuthProxyHelper.defaultGetQueryParamsFromRequest(req); - OAuth.verifyIdTokenHintClientIdAndUpdateQueryParamsForLogout(main, appIdentifier, storage, queryParams); - - HttpRequestForOry.Response response = OAuthProxyHelper.proxyGET( - main, req, resp, - appIdentifier, - storage, - queryParams.get("clientId"), // clientIdToCheck - "/oauth2/sessions/logout", // proxyPath - false, // proxyToAdmin - true, // camelToSnakeCaseConversion - queryParams, - new HashMap<>() // headers - ); - - if (response != null) { - JsonObject finalResponse = new JsonObject(); - String redirectTo = response.headers.get("Location").get(0); - - finalResponse.addProperty("status", "OK"); - finalResponse.addProperty("redirectTo", redirectTo); - - super.sendJsonResponse(200, finalResponse, resp); + // Validations + if (idTokenHint == null) { + if (postLogoutRedirectionUri != null ) { + throw new OAuthAPIException("invalid_request", "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. Logout failed because query parameter postLogoutRedirectionUri is set but idTokenHint is missing.", 400); + } + + if (state != null) { + throw new OAuthAPIException("invalid_request", "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. Logout failed because query parameter state is set but idTokenHint is missing.", 400); + } + } + // Verify id token and client id associations + JsonObject idTokenPayload = null; + String sessionHandle = null; + if (idTokenHint != null) { + idTokenPayload = OAuth.verifyIdTokenAndGetPayload(main, appIdentifier, storage, idTokenHint); + if (idTokenPayload.has("sid")) { + sessionHandle = idTokenPayload.get("sid").getAsString(); + } + + if (clientId != null) { + String clientIdInIdTokenPayload = idTokenPayload.get("aud").getAsString(); + if (!clientId.equals(clientIdInIdTokenPayload)) { + throw new OAuthAPIException("invalid_request", "The client_id in the id_token_hint does not match the client_id in the request.", 400); + } + } else { + clientId = idTokenPayload.get("aud").getAsString(); + } + } + + // Check if the post logout redirection URI is valid for the clientId + if (postLogoutRedirectionUri != null) { + HttpRequestForOry.Response response = OAuthProxyHelper.proxyGET( + main, req, resp, + appIdentifier, storage, + clientId, // clientIdToCheck + "/admin/clients/" + clientId, // path + true, // proxyToAdmin + true, // camelToSnakeCaseConversion + new HashMap<>(), // queryParams + new HashMap<>() // headers + ); + + if (response == null) { + return; // proxy would have already responded + } + + String[] postLogoutRedirectUris = null; + if (response.jsonResponse.getAsJsonObject().has("postLogoutRedirectUris") && response.jsonResponse.getAsJsonObject().get("postLogoutRedirectUris").isJsonArray()) { + JsonArray postLogoutRedirectUrisArray = response.jsonResponse.getAsJsonObject().get("postLogoutRedirectUris").getAsJsonArray(); + postLogoutRedirectUris = new String[postLogoutRedirectUrisArray.size()]; + for (int i = 0; i < postLogoutRedirectUrisArray.size(); i++) { + postLogoutRedirectUris[i] = postLogoutRedirectUrisArray.get(i).getAsString(); + } + } + + if (postLogoutRedirectUris == null || postLogoutRedirectUris.length == 0) { + throw new OAuthAPIException("", "", 400); + } + + boolean isValidPostLogoutRedirectUri = false; + for (String uri : postLogoutRedirectUris) { + if (uri.equals(postLogoutRedirectionUri)) { + isValidPostLogoutRedirectUri = true; + break; + } + } + + if (!isValidPostLogoutRedirectUri) { + throw new OAuthAPIException("invalid_request", "The post_logout_redirect_uri is not valid for this client.", 400); + } + } + + // Validations are complete, time to respond + + if (postLogoutRedirectionUri == null && state == null && idTokenHint == null) { + JsonObject response = new JsonObject(); + response.addProperty("status", "OK"); + response.addProperty("redirectTo", "{apiDomain}/fallbacks/logout/callback"); + super.sendJsonResponse(200, response, resp); + + return; } + String redirectTo = OAuth.createLogoutRequestAndReturnRedirectUri(main, appIdentifier, storage, clientId, postLogoutRedirectionUri, sessionHandle, state); + + JsonObject response = new JsonObject(); + response.addProperty("status", "OK"); + response.addProperty("redirectTo", redirectTo); + super.sendJsonResponse(200, response, resp); + } catch (OAuthAPIException e) { OAuthProxyHelper.handleOAuthAPIException(resp, e); } catch (IOException | TenantOrAppNotFoundException | BadPermissionException | StorageQueryException | UnsupportedJWTSigningAlgorithmException | StorageTransactionLogicException e) { diff --git a/src/main/java/io/supertokens/webserver/api/oauth/OAuthRejectAuthLogoutRequestAPI.java b/src/main/java/io/supertokens/webserver/api/oauth/OAuthRejectAuthLogoutRequestAPI.java index 10252c230..42bf9dd84 100644 --- a/src/main/java/io/supertokens/webserver/api/oauth/OAuthRejectAuthLogoutRequestAPI.java +++ b/src/main/java/io/supertokens/webserver/api/oauth/OAuthRejectAuthLogoutRequestAPI.java @@ -8,7 +8,9 @@ import io.supertokens.Main; import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.oauth.HttpRequestForOry; +import io.supertokens.oauth.OAuth; import io.supertokens.pluginInterface.RECIPE_ID; +import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.webserver.InputParser; import io.supertokens.webserver.WebserverAPI; @@ -30,27 +32,16 @@ public String getPath() { @Override protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { JsonObject input = InputParser.parseJsonObjectOrThrowError(req); + String challenge = InputParser.parseStringOrThrowError(input, "challenge", false); try { - HttpRequestForOry.Response response = OAuthProxyHelper.proxyJsonPUT( - main, req, resp, - getAppIdentifier(req), - enforcePublicTenantAndGetPublicTenantStorage(req), - null, // clientIdToCheck - "/admin/oauth2/auth/requests/logout/reject", // proxyPath - true, // proxyToAdmin - true, // camelToSnakeCaseConversion - OAuthProxyHelper.defaultGetQueryParamsFromRequest(req), - input, // jsonBody - new HashMap<>() // headers - ); - - if (response != null) { - response.jsonResponse.getAsJsonObject().addProperty("status", "OK"); - super.sendJsonResponse(200, response.jsonResponse, resp); - } - - } catch (IOException | TenantOrAppNotFoundException | BadPermissionException e) { + OAuth.deleteLogoutChallenge(main, getAppIdentifier(req), enforcePublicTenantAndGetPublicTenantStorage(req), challenge); + + JsonObject response = new JsonObject(); + response.addProperty("status", "OK"); + super.sendJsonResponse(200, response, resp); + + } catch (IOException | TenantOrAppNotFoundException | BadPermissionException | StorageQueryException e) { throw new ServletException(e); } } diff --git a/src/test/java/io/supertokens/test/CronjobTest.java b/src/test/java/io/supertokens/test/CronjobTest.java index 6d72addb8..158d79b80 100644 --- a/src/test/java/io/supertokens/test/CronjobTest.java +++ b/src/test/java/io/supertokens/test/CronjobTest.java @@ -965,7 +965,7 @@ public void testThatCronJobsHaveTenantsInfoAfterRestart() throws Exception { { List>> tenantsInfos = Cronjobs.getInstance(process.getProcess()) .getTenantInfos(); - assertEquals(10, tenantsInfos.size()); + assertEquals(11, tenantsInfos.size()); int count = 0; for (List> tenantsInfo : tenantsInfos) { if (tenantsInfo != null) { @@ -1049,6 +1049,8 @@ public void testThatThereAreTasksOfAllCronTaskClassesAndHaveCorrectIntervals() t intervals.put("io.supertokens.cronjobs.telemetry.Telemetry", 86400); intervals.put("io.supertokens.cronjobs.deleteExpiredAccessTokenSigningKeys.DeleteExpiredAccessTokenSigningKeys", 86400); + intervals.put("io.supertokens.cronjobs.cleanupOAuthRevokeListAndChallenges.CleanupOAuthRevokeListAndChallenges", + 86400); Map delays = new HashMap<>(); delays.put("io.supertokens.ee.cronjobs.EELicenseCheck", 86400); @@ -1063,9 +1065,11 @@ public void testThatThereAreTasksOfAllCronTaskClassesAndHaveCorrectIntervals() t delays.put("io.supertokens.cronjobs.telemetry.Telemetry", 0); delays.put("io.supertokens.cronjobs.deleteExpiredAccessTokenSigningKeys.DeleteExpiredAccessTokenSigningKeys", 0); + delays.put("io.supertokens.cronjobs.cleanupOAuthRevokeListAndChallenges.CleanupOAuthRevokeListAndChallenges", + 0); List allTasks = Cronjobs.getInstance(process.getProcess()).getTasks(); - assertEquals(10, allTasks.size()); + assertEquals(11, allTasks.size()); for (CronTask task : allTasks) { assertEquals(intervals.get(task.getClass().getName()).intValue(), task.getIntervalTimeSeconds()); diff --git a/src/test/java/io/supertokens/test/SuperTokensSaaSSecretTest.java b/src/test/java/io/supertokens/test/SuperTokensSaaSSecretTest.java index d35aa0436..719934021 100644 --- a/src/test/java/io/supertokens/test/SuperTokensSaaSSecretTest.java +++ b/src/test/java/io/supertokens/test/SuperTokensSaaSSecretTest.java @@ -428,9 +428,22 @@ public static void checkSessionResponse(JsonObject response, TestingProcessManag assertEquals(response.get("refreshToken").getAsJsonObject().entrySet().size(), 3); } - private static final String[] PROTECTED_CORE_CONFIG = new String[]{"ip_allow_regex", "ip_deny_regex"}; + private static final String[] PROTECTED_CORE_CONFIG = new String[]{ + "ip_allow_regex", + "ip_deny_regex", + "oauth_provider_public_service_url", + "oauth_provider_admin_service_url", + "oauth_provider_consent_login_base_url", + "oauth_provider_url_configured_in_oauth_provider" + }; private static final Object[] PROTECTED_CORE_CONFIG_VALUES = new String[]{ - "127\\\\.\\\\d+\\\\.\\\\d+\\\\.\\\\d+|::1|0:0:0:0:0:0:0:1", "192.0.0.1"}; + "127\\\\.\\\\d+\\\\.\\\\d+\\\\.\\\\d+|::1|0:0:0:0:0:0:0:1", + "192.0.0.1", + "http://localhost:4444", + "http://localhost:4445", + "http://localhost:3001/auth/oauth", + "http://localhost:4444" + }; @Test public void testThatTenantCannotSetProtectedConfigIfSuperTokensSaaSSecretIsSet() diff --git a/src/test/java/io/supertokens/test/multitenant/AppTenantUserTest.java b/src/test/java/io/supertokens/test/multitenant/AppTenantUserTest.java index 0dc0f0565..d239f9c9d 100644 --- a/src/test/java/io/supertokens/test/multitenant/AppTenantUserTest.java +++ b/src/test/java/io/supertokens/test/multitenant/AppTenantUserTest.java @@ -27,8 +27,10 @@ import io.supertokens.pluginInterface.STORAGE_TYPE; import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; +import io.supertokens.pluginInterface.jwt.JWTRecipeStorage; import io.supertokens.pluginInterface.multitenancy.*; import io.supertokens.pluginInterface.nonAuthRecipe.NonAuthRecipeStorage; +import io.supertokens.pluginInterface.oauth.OAuthStorage; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.TestingProcessManager; import io.supertokens.test.Utils; @@ -76,8 +78,11 @@ public void testDeletingAppDeleteNonAuthRecipeData() throws Exception { } // this list contains the package names for recipes which dont use UserIdMapping - ArrayList classesToSkip = new ArrayList<>( - List.of("io.supertokens.pluginInterface.jwt.JWTRecipeStorage", ActiveUsersStorage.class.getName())); + List classesToSkip = List.of( + JWTRecipeStorage.class.getName(), + ActiveUsersStorage.class.getName(), + OAuthStorage.class.getName() + ); Reflections reflections = new Reflections("io.supertokens.pluginInterface"); Set> classes = reflections.getSubTypesOf(NonAuthRecipeStorage.class); @@ -181,8 +186,11 @@ public void testDisassociationOfUserDeletesNonAuthRecipeData() throws Exception } // this list contains the package names for recipes which dont use UserIdMapping - ArrayList classesToSkip = new ArrayList<>( - List.of("io.supertokens.pluginInterface.jwt.JWTRecipeStorage", ActiveUsersStorage.class.getName())); + List classesToSkip = List.of( + JWTRecipeStorage.class.getName(), + ActiveUsersStorage.class.getName(), + OAuthStorage.class.getName() + ); Reflections reflections = new Reflections("io.supertokens.pluginInterface"); Set> classes = reflections.getSubTypesOf(NonAuthRecipeStorage.class); diff --git a/src/test/java/io/supertokens/test/multitenant/api/TestTenantUserAssociation.java b/src/test/java/io/supertokens/test/multitenant/api/TestTenantUserAssociation.java index 6907017cb..3bb6857a5 100644 --- a/src/test/java/io/supertokens/test/multitenant/api/TestTenantUserAssociation.java +++ b/src/test/java/io/supertokens/test/multitenant/api/TestTenantUserAssociation.java @@ -37,6 +37,7 @@ import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.nonAuthRecipe.NonAuthRecipeStorage; +import io.supertokens.pluginInterface.oauth.OAuthStorage; import io.supertokens.pluginInterface.usermetadata.UserMetadataStorage; import io.supertokens.session.Session; import io.supertokens.session.info.SessionInformationHolder; @@ -199,6 +200,7 @@ public void testUserDisassociationForNotAuthRecipes() throws Exception { if (name.equals(UserMetadataStorage.class.getName()) || name.equals(JWTRecipeStorage.class.getName()) || name.equals(ActiveUsersStorage.class.getName()) + || name.equals(OAuthStorage.class.getName()) ) { // user metadata is app specific and does not have any tenant specific data // JWT storage does not have any user specific data diff --git a/src/test/java/io/supertokens/test/userIdMapping/UserIdMappingTest.java b/src/test/java/io/supertokens/test/userIdMapping/UserIdMappingTest.java index 014c1c6e9..38419fb3c 100644 --- a/src/test/java/io/supertokens/test/userIdMapping/UserIdMappingTest.java +++ b/src/test/java/io/supertokens/test/userIdMapping/UserIdMappingTest.java @@ -25,9 +25,11 @@ import io.supertokens.pluginInterface.ActiveUsersStorage; import io.supertokens.pluginInterface.STORAGE_TYPE; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; +import io.supertokens.pluginInterface.jwt.JWTRecipeStorage; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.nonAuthRecipe.NonAuthRecipeStorage; +import io.supertokens.pluginInterface.oauth.OAuthStorage; import io.supertokens.pluginInterface.useridmapping.UserIdMappingStorage; import io.supertokens.pluginInterface.useridmapping.exception.UnknownSuperTokensUserIdException; import io.supertokens.pluginInterface.useridmapping.exception.UserIdMappingAlreadyExistsException; @@ -799,8 +801,11 @@ public void checkThatCreateUserIdMappingHasAllNonAuthRecipeChecks() throws Excep .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{EE_FEATURES.MFA}); // this list contains the package names for recipes which dont use UserIdMapping - ArrayList nonAuthRecipesWhichDontNeedUserIdMapping = new ArrayList<>( - List.of("io.supertokens.pluginInterface.jwt.JWTRecipeStorage", ActiveUsersStorage.class.getName())); + List nonAuthRecipesWhichDontNeedUserIdMapping = List.of( + JWTRecipeStorage.class.getName(), + ActiveUsersStorage.class.getName(), + OAuthStorage.class.getName() + ); Reflections reflections = new Reflections("io.supertokens.pluginInterface"); Set> classes = reflections.getSubTypesOf(NonAuthRecipeStorage.class); @@ -880,8 +885,11 @@ public void checkThatDeleteUserIdMappingHasAllNonAuthRecipeChecks() throws Excep return; } - ArrayList nonAuthRecipesWhichDontNeedUserIdMapping = new ArrayList<>( - List.of("io.supertokens.pluginInterface.jwt.JWTRecipeStorage", ActiveUsersStorage.class.getName())); + List nonAuthRecipesWhichDontNeedUserIdMapping = List.of( + JWTRecipeStorage.class.getName(), + ActiveUsersStorage.class.getName(), + OAuthStorage.class.getName() + ); Reflections reflections = new Reflections("io.supertokens.pluginInterface"); Set> classes = reflections.getSubTypesOf(NonAuthRecipeStorage.class); List names = classes.stream().map(Class::getCanonicalName).collect(Collectors.toList());