Skip to content

Commit

Permalink
Merge pull request #231 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 cff42cb + ffa0196 commit 72838e3
Show file tree
Hide file tree
Showing 5 changed files with 397 additions and 214 deletions.
19 changes: 11 additions & 8 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,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
198 changes: 142 additions & 56 deletions src/main/java/io/supertokens/storage/postgresql/Start.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,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 @@ -103,7 +106,10 @@
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLTransactionRollbackException;
import java.util.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Set;

import static io.supertokens.storage.postgresql.QueryExecutorTemplate.execute;

Expand Down Expand Up @@ -3095,76 +3101,107 @@ public int countUsersThatHaveMoreThanOneLoginMethodOrTOTPEnabledAndActiveSince(A
}
}


@Override
public boolean doesOAuthClientIdExist(AppIdentifier appIdentifier, String clientId)
throws StorageQueryException {
public boolean deleteOAuthClient(AppIdentifier appIdentifier, String clientId) throws StorageQueryException {
try {
return OAuthQueries.doesOAuthClientIdExist(this, clientId, appIdentifier);
return OAuthQueries.deleteOAuthClient(this, clientId, appIdentifier);
} catch (SQLException e) {
throw new StorageQueryException(e);
}
}



@TestOnly
public int getDbActivityCount(String dbname) throws SQLException, StorageQueryException {
String QUERY = "SELECT COUNT(*) as c FROM pg_stat_activity WHERE datname = ?;";
return execute(this, QUERY, pst -> {
pst.setString(1, dbname);
}, result -> {
if (result.next()) {
return result.getInt("c");
}
return -1;
});
}

@Override
public void addOrUpdateOauthClient(AppIdentifier appIdentifier, String clientId, boolean isClientCredentialsOnly)
public OAuthClient getOAuthClientById(AppIdentifier appIdentifier, String clientId)
throws StorageQueryException, OAuthClientNotFoundException {
try {
OAuthClient client = OAuthQueries.getOAuthClientById(this, clientId, appIdentifier);
if (client == null) {
throw new OAuthClientNotFoundException();
}
return client;
} catch (SQLException e) {
throw new StorageQueryException(e);
}
}

@Override
public void addOrUpdateOauthClient(AppIdentifier appIdentifier, String clientId, String clientSecret, boolean isClientCredentialsOnly, boolean enableRefreshTokenRotation)
throws StorageQueryException, TenantOrAppNotFoundException {
try {
OAuthQueries.addOrUpdateOauthClient(this, appIdentifier, clientId, isClientCredentialsOnly);
OAuthQueries.addOrUpdateOauthClient(this, appIdentifier, clientId, clientSecret, isClientCredentialsOnly, enableRefreshTokenRotation);
} catch (SQLException e) {
ServerErrorMessage errorMessage = ((PSQLException) e).getServerErrorMessage();
PostgreSQLConfig config = Config.getConfig(this);
if (e instanceof PSQLException) {
ServerErrorMessage serverMessage = ((PSQLException) e).getServerErrorMessage();

if (isForeignKeyConstraintError(serverMessage, config.getOAuthClientsTable(), "app_id")) {
throw new TenantOrAppNotFoundException(appIdentifier);
}
if (isForeignKeyConstraintError(
errorMessage,
config.getOAuthClientsTable(),
"app_id")) {
throw new TenantOrAppNotFoundException(appIdentifier);
}
throw new StorageQueryException(e);
}
}

@Override
public boolean deleteOAuthClient(AppIdentifier appIdentifier, String clientId) throws StorageQueryException {
public List<OAuthClient> getOAuthClients(AppIdentifier appIdentifier, List<String> clientIds) throws StorageQueryException {
try {
return OAuthQueries.deleteOAuthClient(this, clientId, appIdentifier);
return OAuthQueries.getOAuthClients(this, appIdentifier, clientIds);
} catch (SQLException e) {
throw new StorageQueryException(e);
}
}

@Override
public List<String> listOAuthClients(AppIdentifier appIdentifier) throws StorageQueryException {
public boolean revokeOAuthTokenByGID(AppIdentifier appIdentifier, String gid) throws StorageQueryException {
try {
return OAuthQueries.listOAuthClients(this, appIdentifier);
return OAuthQueries.deleteOAuthSessionByGID(this, appIdentifier, gid);
} catch (SQLException e) {
throw new StorageQueryException(e);
}
}

@Override
public void revokeOAuthTokensBasedOnTargetFields(AppIdentifier appIdentifier, OAuthRevokeTargetType targetType, String targetValue, long exp)
throws StorageQueryException, TenantOrAppNotFoundException {
public boolean revokeOAuthTokenByClientId(AppIdentifier appIdentifier, String clientId)
throws StorageQueryException {
try {
OAuthQueries.revokeOAuthTokensBasedOnTargetFields(this, appIdentifier, targetType, targetValue, exp);
return OAuthQueries.deleteOAuthSessionByClientId(this, appIdentifier, clientId);
} catch (SQLException e) {
PostgreSQLConfig config = Config.getConfig(this);
if (e instanceof PSQLException) {
ServerErrorMessage serverMessage = ((PSQLException) e).getServerErrorMessage();
throw new StorageQueryException(e);
}
}

if (isForeignKeyConstraintError(serverMessage, config.getOAuthRevokeTable(), "app_id")) {
throw new TenantOrAppNotFoundException(appIdentifier);
}
}
@Override
public boolean revokeOAuthTokenByJTI(AppIdentifier appIdentifier, String gid, String jti)
throws StorageQueryException {
try {
return OAuthQueries.deleteJTIFromOAuthSession(this, appIdentifier, gid, jti);
} catch (SQLException e) {
throw new StorageQueryException(e);
}

}

@Override
public boolean isOAuthTokenRevokedBasedOnTargetFields(AppIdentifier appIdentifier, OAuthRevokeTargetType[] targetTypes, String[] targetValues, long issuedAt)
public boolean revokeOAuthTokenBySessionHandle(AppIdentifier appIdentifier, String sessionHandle)
throws StorageQueryException {
try {
return OAuthQueries.isOAuthTokenRevokedBasedOnTargetFields(this, appIdentifier, targetTypes, targetValues, issuedAt);
return OAuthQueries.deleteOAuthSessionBySessionHandle(this, appIdentifier, sessionHandle);
} catch (SQLException e) {
throw new StorageQueryException(e);
}
Expand All @@ -3176,43 +3213,45 @@ public void addOAuthM2MTokenForStats(AppIdentifier appIdentifier, String clientI
try {
OAuthQueries.addOAuthM2MTokenForStats(this, appIdentifier, clientId, iat, exp);
} catch (SQLException e) {
ServerErrorMessage errorMessage = ((PSQLException) e).getServerErrorMessage();
PostgreSQLConfig config = Config.getConfig(this);
if (e instanceof PSQLException) {
ServerErrorMessage serverMessage = ((PSQLException) e).getServerErrorMessage();

if (isForeignKeyConstraintError(serverMessage, config.getOAuthM2MTokensTable(), "client_id")) {
throw new OAuthClientNotFoundException();
}
if (isForeignKeyConstraintError(
errorMessage,
config.getOAuthM2MTokensTable(),
"client_id")) {
throw new OAuthClientNotFoundException();
}
throw new StorageQueryException(e);
}
}

@Override
public void cleanUpExpiredAndRevokedOAuthTokensList() throws StorageQueryException {
public void deleteExpiredOAuthM2MTokens(long exp) throws StorageQueryException {
try {
OAuthQueries.cleanUpExpiredAndRevokedOAuthTokensList(this);
OAuthQueries.deleteExpiredOAuthM2MTokens(this, exp);
} catch (SQLException e) {
throw new StorageQueryException(e);
}
}

@Override
public void addOAuthLogoutChallenge(AppIdentifier appIdentifier, String challenge, String clientId,
String postLogoutRedirectionUri, String sessionHandle, String state, long timeCreated)
String postLogoutRedirectionUri, String sessionHandle, String state, long timeCreated)
throws StorageQueryException, DuplicateOAuthLogoutChallengeException, OAuthClientNotFoundException {
try {
OAuthQueries.addOAuthLogoutChallenge(this, appIdentifier, challenge, clientId, postLogoutRedirectionUri, sessionHandle, state, timeCreated);
} catch (SQLException e) {
ServerErrorMessage errorMessage = ((PSQLException) e).getServerErrorMessage();
PostgreSQLConfig config = Config.getConfig(this);
if (e instanceof PSQLException) {
ServerErrorMessage serverMessage = ((PSQLException) e).getServerErrorMessage();

if (isPrimaryKeyError(serverMessage, config.getOAuthLogoutChallengesTable())) {
throw new DuplicateOAuthLogoutChallengeException();
} else if (isForeignKeyConstraintError(serverMessage, config.getOAuthLogoutChallengesTable(), "client_id")) {
throw new OAuthClientNotFoundException();
}
if (isPrimaryKeyError(errorMessage, config.getOAuthLogoutChallengesTable())) {
throw new DuplicateOAuthLogoutChallengeException();
} else if (isForeignKeyConstraintError(
errorMessage,
config.getOAuthLogoutChallengesTable(),
"client_id")) {
throw new OAuthClientNotFoundException();
}
throw new StorageQueryException(e);
}
Expand Down Expand Up @@ -3245,6 +3284,47 @@ public void deleteOAuthLogoutChallengesBefore(long time) throws StorageQueryExce
}
}

@Override
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.createOrUpdateOAuthSession(this, appIdentifier, gid, clientId, externalRefreshToken,
internalRefreshToken, sessionHandle, jtis, exp);
} catch (SQLException e) {
ServerErrorMessage errorMessage = ((PSQLException) e).getServerErrorMessage();
PostgreSQLConfig config = Config.getConfig(this);

if (isForeignKeyConstraintError(
errorMessage,
config.getOAuthSessionsTable(),
"client_id")) {
throw new OAuthClientNotFoundException();
}
throw new StorageQueryException(e);
}
}

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

@Override
public void deleteExpiredOAuthSessions(long exp) throws StorageQueryException {
try {
OAuthQueries.deleteExpiredOAuthSessions(this, exp);
} catch (SQLException e) {
throw new StorageQueryException(e);
}
}

@Override
public int countTotalNumberOfOAuthClients(AppIdentifier appIdentifier) throws StorageQueryException {
try {
Expand Down Expand Up @@ -3283,16 +3363,22 @@ public int countTotalNumberOfOAuthM2MTokensAlive(AppIdentifier appIdentifier) th
}
}

@TestOnly
public int getDbActivityCount(String dbname) throws SQLException, StorageQueryException {
String QUERY = "SELECT COUNT(*) as c FROM pg_stat_activity WHERE datname = ?;";
return execute(this, QUERY, pst -> {
pst.setString(1, dbname);
}, result -> {
if (result.next()) {
return result.getInt("c");
}
return -1;
});
@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);
}
}
}
Loading

0 comments on commit 72838e3

Please sign in to comment.