Skip to content

Commit

Permalink
fix: revoke APIs (#1041)
Browse files Browse the repository at this point in the history
* fix: revoke consent sessions

* fix: revoke token

* fix: revoke impl

* fix: revoke session

* fix: introspect impl after revoke

* fix: revoke by client_id

* fix: refresh token check in token exchange

* fix: at hash

* fix: sqlite impl

* fix: client props whitelist

* fix: status and query null check

* fix: plugin interface update

* fix: logout api

* fix: ext

* fix: accept consent

* fix: accept consent

* fix: introspect in token api

* fix: keep fragment while updating query params

* fix: count creds and pr comment

* fix: oauth stats

* fix: oauth cleanup cron task

* fix: gid in refresh token

* fix: inememory impl

* feat: add initial payload fields to accept consent

* fix: revoke cleanup

* fix: stats

* fix: client credentials basic

* fix: authorization header

* fix: authorizaion header in revoke

* fix: missing table

---------

Co-authored-by: Mihaly Lengyel <mihaly@lengyel.tech>
  • Loading branch information
sattvikc and porcellus authored Sep 25, 2024
1 parent c568f5c commit bd86375
Show file tree
Hide file tree
Showing 28 changed files with 1,220 additions and 105 deletions.
28 changes: 28 additions & 0 deletions ee/src/main/java/io/supertokens/ee/EEFeatureFlag.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import io.supertokens.pluginInterface.KeyValueInfo;
import io.supertokens.pluginInterface.STORAGE_TYPE;
import io.supertokens.pluginInterface.Storage;
import io.supertokens.pluginInterface.StorageUtils;
import io.supertokens.pluginInterface.authRecipe.AuthRecipeStorage;
import io.supertokens.pluginInterface.dashboard.sqlStorage.DashboardSQLStorage;
import io.supertokens.pluginInterface.exceptions.StorageQueryException;
Expand All @@ -32,6 +33,7 @@
import io.supertokens.pluginInterface.multitenancy.TenantIdentifier;
import io.supertokens.pluginInterface.multitenancy.ThirdPartyConfig;
import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException;
import io.supertokens.pluginInterface.oauth.OAuthStorage;
import io.supertokens.pluginInterface.session.sqlStorage.SessionSQLStorage;
import io.supertokens.storageLayer.StorageLayer;
import io.supertokens.utils.Utils;
Expand Down Expand Up @@ -338,6 +340,28 @@ private JsonObject getAccountLinkingStats() throws StorageQueryException, Tenant
return result;
}

private JsonObject getOAuthStats() throws StorageQueryException, TenantOrAppNotFoundException {
JsonObject result = new JsonObject();

OAuthStorage oAuthStorage = StorageUtils.getOAuthStorage(StorageLayer.getStorage(
this.appIdentifier.getAsPublicTenantIdentifier(), main));

result.addProperty("totalNumberOfClients", oAuthStorage.countTotalNumberOfClientsForApp(appIdentifier));
result.addProperty("numberOfClientCredentialsOnlyClients", oAuthStorage.countTotalNumberOfClientCredentialsOnlyClientsForApp(appIdentifier));
result.addProperty("numberOfM2MTokensAlive", oAuthStorage.countTotalNumberOfM2MTokensAlive(appIdentifier));

long now = System.currentTimeMillis();
JsonArray tokensCreatedArray = new JsonArray();
for (int i = 1; i <= 31; i++) {
long timestamp = now - (i * 24 * 60 * 60 * 1000L);
int numberOfTokensCreated = oAuthStorage.countTotalNumberOfM2MTokensCreatedSince(this.appIdentifier, timestamp);
tokensCreatedArray.add(new JsonPrimitive(numberOfTokensCreated));
}
result.add("numberOfM2MTokensCreated", tokensCreatedArray);

return result;
}

private JsonArray getMAUs() throws StorageQueryException, TenantOrAppNotFoundException {
JsonArray mauArr = new JsonArray();
long now = System.currentTimeMillis();
Expand Down Expand Up @@ -395,6 +419,10 @@ public JsonObject getPaidFeatureStats() throws StorageQueryException, TenantOrAp
if (feature == EE_FEATURES.SECURITY) {
usageStats.add(EE_FEATURES.SECURITY.toString(), new JsonObject());
}

if (feature == EE_FEATURES.OAUTH) {
usageStats.add(EE_FEATURES.OAUTH.toString(), getOAuthStats());
}
}

usageStats.add("maus", getMAUs());
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/io/supertokens/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +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.deleteExpiredAccessTokenSigningKeys.DeleteExpiredAccessTokenSigningKeys;
import io.supertokens.cronjobs.deleteExpiredDashboardSessions.DeleteExpiredDashboardSessions;
import io.supertokens.cronjobs.deleteExpiredEmailVerificationTokens.DeleteExpiredEmailVerificationTokens;
Expand Down Expand Up @@ -256,6 +257,8 @@ 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));

// this is to ensure tenantInfos are in sync for the new cron job as well
MultitenancyHelper.getInstance(this).refreshCronjobs();

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

import java.util.List;

import io.supertokens.Main;
import io.supertokens.cronjobs.CronTask;
import io.supertokens.cronjobs.CronTaskTest;
import io.supertokens.pluginInterface.Storage;
import io.supertokens.pluginInterface.StorageUtils;
import io.supertokens.pluginInterface.multitenancy.AppIdentifier;
import io.supertokens.pluginInterface.multitenancy.TenantIdentifier;
import io.supertokens.pluginInterface.oauth.OAuthStorage;
import io.supertokens.storageLayer.StorageLayer;

public class CleanupOAuthRevokeList extends CronTask {

public static final String RESOURCE_KEY = "io.supertokens.cronjobs.cleanupOAuthRevokeList" +
".CleanupOAuthRevokeList";

private CleanupOAuthRevokeList(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()
.setResource(new TenantIdentifier(null, null, null), RESOURCE_KEY,
new CleanupOAuthRevokeList(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);
}

@Override
public int getIntervalTimeSeconds() {
if (Main.isTesting) {
Integer interval = CronTaskTest.getInstance(main).getIntervalInSeconds(RESOURCE_KEY);
if (interval != null) {
return interval;
}
}
// Every 24 hours.
return 24 * 3600;
}

@Override
public int getInitialWaitTimeSeconds() {
if (!Main.isTesting) {
return getIntervalTimeSeconds();
} else {
return 0;
}
}
}
96 changes: 82 additions & 14 deletions src/main/java/io/supertokens/inmemorydb/Start.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@
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.exceptions.OAuth2ClientAlreadyExistsForAppException;
import io.supertokens.pluginInterface.oauth.sqlStorage.OAuthSQLStorage;
import io.supertokens.pluginInterface.passwordless.PasswordlessCode;
import io.supertokens.pluginInterface.passwordless.PasswordlessDevice;
Expand Down Expand Up @@ -107,7 +106,6 @@ public class Start
ActiveUsersSQLStorage, DashboardSQLStorage, AuthRecipeSQLStorage, OAuthSQLStorage {

private static final Object appenderLock = new Object();
private static final String APP_ID_KEY_NAME = "app_id";
private static final String ACCESS_TOKEN_SIGNING_KEY_NAME = "access_token_signing_key";
private static final String REFRESH_TOKEN_KEY_NAME = "refresh_token_key";
public static boolean isTesting = false;
Expand Down Expand Up @@ -3011,7 +3009,7 @@ public int countUsersThatHaveMoreThanOneLoginMethodOrTOTPEnabledAndActiveSince(A
}

@Override
public boolean doesClientIdExistForThisApp(AppIdentifier appIdentifier, String clientId)
public boolean doesClientIdExistForApp(AppIdentifier appIdentifier, String clientId)
throws StorageQueryException {
try {
return OAuthQueries.isClientIdForAppId(this, clientId, appIdentifier);
Expand All @@ -3021,19 +3019,11 @@ public boolean doesClientIdExistForThisApp(AppIdentifier appIdentifier, String c
}

@Override
public void addClientForApp(AppIdentifier appIdentifier, String clientId)
throws StorageQueryException, OAuth2ClientAlreadyExistsForAppException {
public void addOrUpdateClientForApp(AppIdentifier appIdentifier, String clientId, boolean isClientCredentialsOnly)
throws StorageQueryException {
try {
OAuthQueries.insertClientIdForAppId(this, clientId, appIdentifier);
OAuthQueries.insertClientIdForAppId(this, appIdentifier, clientId, isClientCredentialsOnly);
} catch (SQLException e) {

SQLiteConfig config = Config.getConfig(this);
String serverErrorMessage = e.getMessage();

if (isPrimaryKeyError(serverErrorMessage, config.getOAuthClientTable(),
new String[]{"app_id", "client_id"})) {
throw new OAuth2ClientAlreadyExistsForAppException();
}
throw new StorageQueryException(e);
}
}
Expand All @@ -3055,4 +3045,82 @@ public List<String> listClientsForApp(AppIdentifier appIdentifier) throws Storag
throw new StorageQueryException(e);
}
}

@Override
public void revoke(AppIdentifier appIdentifier, String targetType, String targetValue, long exp)
throws StorageQueryException {
try {
OAuthQueries.revoke(this, appIdentifier, targetType, targetValue, exp);
} catch (SQLException e) {
throw new StorageQueryException(e);
}

}

@Override
public boolean isRevoked(AppIdentifier appIdentifier, String[] targetTypes, String[] targetValues, long issuedAt)
throws StorageQueryException {
try {
return OAuthQueries.isRevoked(this, appIdentifier, targetTypes, targetValues, issuedAt);
} catch (SQLException e) {
throw new StorageQueryException(e);
}
}

@Override
public void addM2MToken(AppIdentifier appIdentifier, String clientId, long iat, long exp)
throws StorageQueryException {
try {
OAuthQueries.addM2MToken(this, appIdentifier, clientId, iat, exp);
} catch (SQLException e) {
throw new StorageQueryException(e);
}
}

@Override
public void cleanUpExpiredAndRevokedTokens(AppIdentifier appIdentifier) throws StorageQueryException {
try {
OAuthQueries.cleanUpExpiredAndRevokedTokens(this, appIdentifier);
} catch (SQLException e) {
throw new StorageQueryException(e);
}
}

@Override
public int countTotalNumberOfClientCredentialsOnlyClientsForApp(AppIdentifier appIdentifier)
throws StorageQueryException {
try {
return OAuthQueries.countTotalNumberOfClientsForApp(this, appIdentifier, true);
} catch (SQLException e) {
throw new StorageQueryException(e);
}
}

@Override
public int countTotalNumberOfClientsForApp(AppIdentifier appIdentifier) throws StorageQueryException {
try {
return OAuthQueries.countTotalNumberOfClientsForApp(this, appIdentifier, false);
} catch (SQLException e) {
throw new StorageQueryException(e);
}
}

@Override
public int countTotalNumberOfM2MTokensAlive(AppIdentifier appIdentifier) throws StorageQueryException {
try {
return OAuthQueries.countTotalNumberOfM2MTokensAlive(this, appIdentifier);
} catch (SQLException e) {
throw new StorageQueryException(e);
}
}

@Override
public int countTotalNumberOfM2MTokensCreatedSince(AppIdentifier appIdentifier, long since)
throws StorageQueryException {
try {
return OAuthQueries.countTotalNumberOfM2MTokensCreatedSince(this, appIdentifier, since);
} catch (SQLException e) {
throw new StorageQueryException(e);
}
}
}
12 changes: 11 additions & 1 deletion src/main/java/io/supertokens/inmemorydb/config/SQLiteConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -165,5 +165,15 @@ public String getDashboardSessionsTable() {
return "dashboard_user_sessions";
}

public String getOAuthClientTable(){ return "oauth_clients"; }
public String getOAuthClientsTable() {
return "oauth_clients";
}

public String getOAuthRevokeTable() {
return "oauth_revoke";
}

public String getOAuthM2MTokensTable() {
return "oauth_m2m_tokens";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -423,10 +423,27 @@ public static void createTablesIfNotExists(Start start, Main main) throws SQLExc
update(start, TOTPQueries.getQueryToCreateUsedCodesExpiryTimeIndex(start), NO_OP_SETTER);
}

if (!doesTableExists(start, Config.getConfig(start).getOAuthClientTable())) {
if (!doesTableExists(start, Config.getConfig(start).getOAuthClientsTable())) {
getInstance(main).addState(CREATING_NEW_TABLE, null);
update(start, OAuthQueries.getQueryToCreateOAuthClientTable(start), NO_OP_SETTER);
}

if (!doesTableExists(start, Config.getConfig(start).getOAuthRevokeTable())) {
getInstance(main).addState(CREATING_NEW_TABLE, null);
update(start, OAuthQueries.getQueryToCreateOAuthRevokeTable(start), NO_OP_SETTER);

// index
update(start, OAuthQueries.getQueryToCreateOAuthRevokeTimestampIndex(start), NO_OP_SETTER);
}

if (!doesTableExists(start, Config.getConfig(start).getOAuthM2MTokensTable())) {
getInstance(main).addState(CREATING_NEW_TABLE, null);
update(start, OAuthQueries.getQueryToCreateOAuthM2MTokensTable(start), NO_OP_SETTER);

// index
update(start, OAuthQueries.getQueryToCreateOAuthM2MTokenIatIndex(start), NO_OP_SETTER);
update(start, OAuthQueries.getQueryToCreateOAuthM2MTokenExpIndex(start), NO_OP_SETTER);
}
}


Expand Down
Loading

0 comments on commit bd86375

Please sign in to comment.