Skip to content

Commit

Permalink
Merge pull request #1067 from supertokens/feat/oauth/allow-list
Browse files Browse the repository at this point in the history
feat: oauth/allow list
  • Loading branch information
porcellus authored Oct 27, 2024
2 parents ac3cf84 + 8f80b8c commit 2abfb68
Show file tree
Hide file tree
Showing 19 changed files with 779 additions and 438 deletions.
41 changes: 24 additions & 17 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,18 +53,21 @@ CREATE TABLE IF NOT EXISTS oauth_clients (
FOREIGN KEY(app_id) REFERENCES apps(app_id) ON DELETE CASCADE
);

CREATE TABLE IF NOT EXISTS oauth_revoke (
CREATE TABLE IF NOT EXISTS oauth_sessions (
gid VARCHAR(255),
app_id VARCHAR(64) DEFAULT 'public',
target_type VARCHAR(16) NOT NULL,
target_value VARCHAR(128) NOT NULL,
timestamp BIGINT NOT NULL,
client_id VARCHAR(255) NOT NULL,
session_handle VARCHAR(128),
external_refresh_token VARCHAR(255) UNIQUE,
internal_refresh_token VARCHAR(255) UNIQUE,
jti TEXT NOT NULL,
exp BIGINT NOT NULL,
PRIMARY KEY (app_id, target_type, target_value),
FOREIGN KEY(app_id) REFERENCES apps(app_id) ON DELETE CASCADE
PRIMARY KEY (gid),
FOREIGN KEY(app_id, client_id) REFERENCES oauth_clients(app_id, client_id) ON DELETE CASCADE
);

CREATE INDEX IF NOT EXISTS oauth_revoke_timestamp_index ON oauth_revoke(timestamp DESC, app_id DESC);
CREATE INDEX IF NOT EXISTS oauth_revoke_exp_index ON oauth_revoke(exp DESC);
CREATE INDEX IF NOT EXISTS oauth_session_exp_index ON oauth_sessions(exp DESC);
CREATE INDEX IF NOT EXISTS oauth_session_external_refresh_token_index ON oauth_sessions(app_id, external_refresh_token DESC);

CREATE TABLE IF NOT EXISTS oauth_m2m_tokens (
app_id VARCHAR(64) DEFAULT 'public',
Expand Down Expand Up @@ -104,18 +107,22 @@ CREATE TABLE IF NOT EXISTS oauth_clients (
FOREIGN KEY(app_id) REFERENCES apps(app_id) ON DELETE CASCADE
);

CREATE TABLE IF NOT EXISTS oauth_revoke (

CREATE TABLE IF NOT EXISTS oauth_sessions (
gid VARCHAR(255),
app_id VARCHAR(64) DEFAULT 'public',
target_type VARCHAR(16) NOT NULL,
target_value VARCHAR(128) NOT NULL,
timestamp BIGINT UNSIGNED NOT NULL,
exp BIGINT UNSIGNED NOT NULL,
PRIMARY KEY (app_id, target_type, target_value),
FOREIGN KEY(app_id) REFERENCES apps(app_id) ON DELETE CASCADE
client_id VARCHAR(255) NOT NULL,
session_handle VARCHAR(128),
external_refresh_token VARCHAR(255) UNIQUE,
internal_refresh_token VARCHAR(255) UNIQUE,
jti TEXT NOT NULL,
exp BIGINT NOT NULL,
PRIMARY KEY (gid),
FOREIGN KEY(app_id, client_id) REFERENCES oauth_clients(app_id, client_id) ON DELETE CASCADE
);

CREATE INDEX oauth_revoke_timestamp_index ON oauth_revoke(timestamp DESC, app_id DESC);
CREATE INDEX oauth_revoke_exp_index ON oauth_revoke(exp DESC);
CREATE INDEX IF NOT EXISTS oauth_session_exp_index ON oauth_sessions(exp DESC);
CREATE INDEX IF NOT EXISTS oauth_session_external_refresh_token_index ON oauth_sessions(app_id, external_refresh_token DESC);

CREATE TABLE oauth_m2m_tokens (
app_id VARCHAR(64) DEFAULT 'public',
Expand Down
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.cleanupOAuthRevokeListAndChallenges.CleanupOAuthRevokeListAndChallenges;
import io.supertokens.cronjobs.cleanupOAuthRevokeListAndChallenges.CleanupOAuthSessionsAndChallenges;
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, CleanupOAuthRevokeListAndChallenges.init(this, uniqueUserPoolIdsTenants));
Cronjobs.addCronjob(this, CleanupOAuthSessionsAndChallenges.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
@@ -1,7 +1,5 @@
package io.supertokens.cronjobs.cleanupOAuthRevokeListAndChallenges;

import java.util.List;

import io.supertokens.Main;
import io.supertokens.cronjobs.CronTask;
import io.supertokens.cronjobs.CronTaskTest;
Expand All @@ -11,19 +9,21 @@
import io.supertokens.pluginInterface.multitenancy.TenantIdentifier;
import io.supertokens.pluginInterface.oauth.OAuthStorage;

public class CleanupOAuthRevokeListAndChallenges extends CronTask {
import java.util.List;

public class CleanupOAuthSessionsAndChallenges extends CronTask {

public static final String RESOURCE_KEY = "io.supertokens.cronjobs.cleanupOAuthRevokeListAndChallenges" +
".CleanupOAuthRevokeListAndChallenges";

private CleanupOAuthRevokeListAndChallenges(Main main, List<List<TenantIdentifier>> tenantsInfo) {
private CleanupOAuthSessionsAndChallenges(Main main, List<List<TenantIdentifier>> tenantsInfo) {
super("CleanupOAuthRevokeList", main, tenantsInfo, true);
}

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

@Override
Expand All @@ -34,7 +34,7 @@ protected void doTaskPerStorage(Storage storage) throws Exception {

OAuthStorage oauthStorage = StorageUtils.getOAuthStorage(storage);
long monthAgo = System.currentTimeMillis() / 1000 - 31 * 24 * 3600;
oauthStorage.deleteExpiredRevokedOAuthTokens(monthAgo);
oauthStorage.deleteExpiredOAuthSessions(monthAgo);
oauthStorage.deleteExpiredOAuthM2MTokens(monthAgo);

oauthStorage.deleteOAuthLogoutChallengesBefore(System.currentTimeMillis() - 1000 * 60 * 60 * 48); // 48 hours
Expand Down
97 changes: 60 additions & 37 deletions src/main/java/io/supertokens/inmemorydb/Start.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,17 @@
import io.supertokens.pluginInterface.jwt.JWTSigningKeyInfo;
import io.supertokens.pluginInterface.jwt.exceptions.DuplicateKeyIdException;
import io.supertokens.pluginInterface.jwt.sqlstorage.JWTRecipeSQLStorage;
import io.supertokens.pluginInterface.multitenancy.*;
import io.supertokens.pluginInterface.multitenancy.AppIdentifier;
import io.supertokens.pluginInterface.multitenancy.MultitenancyStorage;
import io.supertokens.pluginInterface.multitenancy.TenantConfig;
import io.supertokens.pluginInterface.multitenancy.TenantIdentifier;
import io.supertokens.pluginInterface.multitenancy.exceptions.DuplicateClientTypeException;
import io.supertokens.pluginInterface.multitenancy.exceptions.DuplicateTenantException;
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.OAuthClient;
import io.supertokens.pluginInterface.oauth.OAuthLogoutChallenge;
import io.supertokens.pluginInterface.oauth.OAuthRevokeTargetType;
import io.supertokens.pluginInterface.oauth.OAuthStorage;
import io.supertokens.pluginInterface.oauth.exception.DuplicateOAuthLogoutChallengeException;
import io.supertokens.pluginInterface.oauth.exception.OAuthClientNotFoundException;
Expand Down Expand Up @@ -3070,42 +3072,39 @@ public List<OAuthClient> getOAuthClients(AppIdentifier appIdentifier, List<Strin
}

@Override
public void revokeOAuthTokensBasedOnTargetFields(AppIdentifier appIdentifier, OAuthRevokeTargetType targetType, String targetValue, long exp)
throws StorageQueryException, TenantOrAppNotFoundException {
public boolean revokeOAuthTokenByGID(AppIdentifier appIdentifier, String gid) throws StorageQueryException {
try {
OAuthQueries.revokeOAuthTokensBasedOnTargetFields(this, appIdentifier, targetType, targetValue, exp);
return OAuthQueries.deleteOAuthSessionByGID(this, appIdentifier, gid);
} catch (SQLException e) {
if (e instanceof SQLiteException) {
String errorMessage = e.getMessage();
SQLiteConfig config = Config.getConfig(this);
throw new StorageQueryException(e);
}
}

if (isForeignKeyConstraintError(
errorMessage,
config.getOAuthRevokeTable(),
new String[]{"app_id"},
new Object[]{appIdentifier.getAppId()})) {
throw new TenantOrAppNotFoundException(appIdentifier);
}
}
@Override
public boolean revokeOAuthTokenByClientId(AppIdentifier appIdentifier, String clientId)
throws StorageQueryException {
try {
return OAuthQueries.deleteOAuthSessionByClientId(this, appIdentifier, clientId);
} catch (SQLException e) {
throw new StorageQueryException(e);
}

}

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

@Override
public void deleteExpiredRevokedOAuthTokens(long exp) throws StorageQueryException {
public boolean revokeOAuthTokenBySessionHandle(AppIdentifier appIdentifier, String sessionHandle)
throws StorageQueryException {
try {
OAuthQueries.deleteExpiredRevokedOAuthTokens(this, exp);
return OAuthQueries.deleteOAuthSessionBySessionHandle(this, appIdentifier, sessionHandle);
} catch (SQLException e) {
throw new StorageQueryException(e);
}
Expand Down Expand Up @@ -3194,39 +3193,44 @@ public void deleteOAuthLogoutChallengesBefore(long time) throws StorageQueryExce
}

@Override
public void createOrUpdateRefreshTokenMapping(AppIdentifier appIdentifier, String superTokensRefreshToken,
String oauthProviderRefreshToken, long exp) throws StorageQueryException {
public void createOrUpdateOAuthSession(AppIdentifier appIdentifier, String gid, String clientId,
String externalRefreshToken, String internalRefreshToken,
String sessionHandle, List<String> jtis, long exp)
throws StorageQueryException, OAuthClientNotFoundException {
try {
OAuthQueries.createOrUpdateRefreshTokenMapping(this, appIdentifier, superTokensRefreshToken, oauthProviderRefreshToken, exp);
OAuthQueries.createOrUpdateOAuthSession(this, appIdentifier, gid, clientId, externalRefreshToken,
internalRefreshToken, sessionHandle, jtis, exp);
} catch (SQLException e) {
throw new StorageQueryException(e);
}
}
if (e instanceof SQLiteException) {
String errorMessage = e.getMessage();
SQLiteConfig config = Config.getConfig(this);

@Override
public String getRefreshTokenMapping(AppIdentifier appIdentifier, String superTokensRefreshToken)
throws StorageQueryException {
try {
return OAuthQueries.getRefreshTokenMapping(this, appIdentifier, superTokensRefreshToken);
} catch (SQLException e) {
if (isForeignKeyConstraintError(
errorMessage,
config.getOAuthClientsTable(),
new String[]{"app_id", "client_id"},
new Object[]{appIdentifier.getAppId(), clientId})) {
throw new OAuthClientNotFoundException();
}
}
throw new StorageQueryException(e);
}
}

@Override
public void deleteRefreshTokenMapping(AppIdentifier appIdentifier, String superTokensRefreshToken)
public String getRefreshTokenMapping(AppIdentifier appIdentifier, String externalRefreshToken)
throws StorageQueryException {
try {
OAuthQueries.deleteRefreshTokenMapping(this, appIdentifier, superTokensRefreshToken);
return OAuthQueries.getRefreshTokenMapping(this, appIdentifier, externalRefreshToken);
} catch (SQLException e) {
throw new StorageQueryException(e);
}
}

@Override
public void deleteExpiredRefreshTokenMappings(long exp) throws StorageQueryException {
public void deleteExpiredOAuthSessions(long exp) throws StorageQueryException {
try {
OAuthQueries.deleteExpiredRefreshTokenMappings(this, exp);
OAuthQueries.deleteExpiredOAuthSessions(this, exp);
} catch (SQLException e) {
throw new StorageQueryException(e);
}
Expand Down Expand Up @@ -3269,4 +3273,23 @@ public int countTotalNumberOfOAuthM2MTokensAlive(AppIdentifier appIdentifier) th
throw new StorageQueryException(e);
}
}

@Override
public boolean isOAuthTokenRevokedByGID(AppIdentifier appIdentifier, String gid) throws StorageQueryException {
try {
return !OAuthQueries.isOAuthSessionExistsByGID(this, appIdentifier, gid);
} catch (SQLException e) {
throw new StorageQueryException(e);
}
}

@Override
public boolean isOAuthTokenRevokedByJTI(AppIdentifier appIdentifier, String gid, String jti)
throws StorageQueryException {
try {
return !OAuthQueries.isOAuthSessionExistsByJTI(this, appIdentifier, gid, jti);
} catch (SQLException e) {
throw new StorageQueryException(e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -173,14 +173,14 @@ public String getOAuthRefreshTokenMappingTable() {
return "oauth_refresh_token_mapping";
}

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

public String getOAuthM2MTokensTable() {
return "oauth_m2m_tokens";
}

public String getOAuthSessionsTable() {
return "oauth_sessions";
}

public String getOAuthLogoutChallengesTable() {
return "oauth_logout_challenges";
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -432,21 +432,13 @@ public static void createTablesIfNotExists(Start start, Main main) throws SQLExc
update(start, OAuthQueries.getQueryToCreateOAuthClientTable(start), NO_OP_SETTER);
}

if (!doesTableExists(start, Config.getConfig(start).getOAuthRefreshTokenMappingTable())) {
if (!doesTableExists(start, Config.getConfig(start).getOAuthSessionsTable())) {
getInstance(main).addState(CREATING_NEW_TABLE, null);
update(start, OAuthQueries.getQueryToCreateOAuthRefreshTokenMappingTable(start), NO_OP_SETTER);
update(start, OAuthQueries.getQueryToCreateOAuthSessionsTable(start), NO_OP_SETTER);

// index
update(start, OAuthQueries.getQueryToCreateOAuthRefreshTokenMappingExpIndex(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);
update(start, OAuthQueries.getQueryToCreateOAuthRevokeExpIndex(start), NO_OP_SETTER);
update(start, OAuthQueries.getQueryToCreateOAuthSessionsExpIndex(start), NO_OP_SETTER);
update(start, OAuthQueries.getQueryToCreateOAuthSessionsExternalRefreshTokenIndex(start), NO_OP_SETTER);
}

if (!doesTableExists(start, Config.getConfig(start).getOAuthM2MTokensTable())) {
Expand Down
Loading

0 comments on commit 2abfb68

Please sign in to comment.