Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: logout apis #1047

Merged
merged 5 commits into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 5 additions & 5 deletions config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
16 changes: 8 additions & 8 deletions devConfig.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
4 changes: 2 additions & 2 deletions src/main/java/io/supertokens/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down
24 changes: 12 additions & 12 deletions src/main/java/io/supertokens/config/CoreConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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");
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.supertokens.cronjobs.cleanupOAuthRevokeList;
package io.supertokens.cronjobs.cleanupOAuthRevokeListAndChallenges;

import java.util.List;

Expand All @@ -12,26 +12,27 @@
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<List<TenantIdentifier>> tenantsInfo) {
private CleanupOAuthRevokeListAndChallenges(Main main, List<List<TenantIdentifier>> tenantsInfo) {
super("CleanupOAuthRevokeList", main, tenantsInfo, true);
}

public static CleanupOAuthRevokeList init(Main main, List<List<TenantIdentifier>> tenantsInfo) {
return (CleanupOAuthRevokeList) main.getResourceDistributor()
public static CleanupOAuthRevokeListAndChallenges init(Main main, List<List<TenantIdentifier>> tenantsInfo) {
return (CleanupOAuthRevokeListAndChallenges) main.getResourceDistributor()
.setResource(new TenantIdentifier(null, null, null), RESOURCE_KEY,
new CleanupOAuthRevokeList(main, tenantsInfo));
new CleanupOAuthRevokeListAndChallenges(main, tenantsInfo));
}

@Override
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
Expand Down
38 changes: 38 additions & 0 deletions src/main/java/io/supertokens/inmemorydb/Start.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,4 +176,8 @@ public String getOAuthRevokeTable() {
public String getOAuthM2MTokensTable() {
return "oauth_m2m_tokens";
}

public String getOAuthLogoutChallengesTable() {
return "oauth_logout_challenges";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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() +
Expand Down Expand Up @@ -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 = ?)";
Expand Down Expand Up @@ -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);
});
}
}
Loading
Loading