diff --git a/CHANGELOG.md b/CHANGELOG.md index 30b7400d..d8f45ac6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,62 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +## [7.2.0] - 2024-10-03 + +- Compatible with plugin interface version 6.3 +- Adds support for OAuthStorage + +### Migration + +```sql +CREATE TABLE IF NOT EXISTS oauth_clients ( + app_id VARCHAR(64), + client_id VARCHAR(128) NOT NULL, + is_client_credentials_only BOOLEAN NOT NULL, + PRIMARY KEY (app_id, client_id), + FOREIGN KEY(app_id) REFERENCES apps(app_id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS oauth_revoke ( + app_id VARCHAR(64) DEFAULT 'public', + target_type VARCHAR(16) NOT NULL, + target_value VARCHAR(128) NOT NULL, + timestamp BIGINT 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 +); + +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 TABLE IF NOT EXISTS oauth_m2m_tokens ( + app_id VARCHAR(64) DEFAULT 'public', + client_id VARCHAR(128) NOT NULL, + iat BIGINT NOT NULL, + exp BIGINT NOT NULL, + PRIMARY KEY (app_id, client_id, iat), + FOREIGN KEY(app_id, client_id) REFERENCES oauth_clients(app_id, client_id) ON DELETE CASCADE +); + +CREATE INDEX IF NOT EXISTS oauth_m2m_token_iat_index ON oauth_m2m_tokens(iat DESC, app_id DESC); +CREATE INDEX IF NOT EXISTS oauth_m2m_token_exp_index ON oauth_m2m_tokens(exp DESC); + +CREATE TABLE IF NOT EXISTS oauth_logout_challenges ( + 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 oauth_clients(app_id, client_id) ON DELETE CASCADE +); + +CREATE INDEX IF NOT EXISTS oauth_logout_challenges_time_created_index ON oauth_logout_challenges(time_created DESC); +``` + ## [7.1.3] - 2024-09-04 - Adds index on `last_active_time` for `user_last_active` table to improve the performance of MAU computation. diff --git a/build.gradle b/build.gradle index bab06c3e..553a2c88 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ plugins { id 'java-library' } -version = "7.1.3" +version = "7.2.0" repositories { mavenCentral() diff --git a/jar/postgresql-plugin-7.1.3.jar b/jar/postgresql-plugin-7.2.0.jar similarity index 65% rename from jar/postgresql-plugin-7.1.3.jar rename to jar/postgresql-plugin-7.2.0.jar index af33d925..86ad2a0a 100644 Binary files a/jar/postgresql-plugin-7.1.3.jar and b/jar/postgresql-plugin-7.2.0.jar differ diff --git a/pluginInterfaceSupported.json b/pluginInterfaceSupported.json index 0dedee88..25f82381 100644 --- a/pluginInterfaceSupported.json +++ b/pluginInterfaceSupported.json @@ -1,6 +1,6 @@ { "_comment": "contains a list of plugin interfaces branch names that this core supports", "versions": [ - "6.2" + "6.3" ] } \ No newline at end of file diff --git a/src/main/java/io/supertokens/storage/postgresql/Start.java b/src/main/java/io/supertokens/storage/postgresql/Start.java index 310e8158..fc2ee5fa 100644 --- a/src/main/java/io/supertokens/storage/postgresql/Start.java +++ b/src/main/java/io/supertokens/storage/postgresql/Start.java @@ -132,7 +132,6 @@ public class Start private ResourceDistributor resourceDistributor = new ResourceDistributor(); private String processId; private HikariLoggingAppender appender; - 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; @@ -875,6 +874,8 @@ public void addInfoToNonAuthRecipesBasedOnUserId(TenantIdentifier tenantIdentifi } } else if (className.equals(JWTRecipeStorage.class.getName())) { /* Since JWT recipe tables do not store userId we do not add any data to them */ + } else if (className.equals(OAuthStorage.class.getName())) { + /* Since OAuth recipe tables do not store userId we do not add any data to them */ } else if (className.equals(ActiveUsersStorage.class.getName())) { try { ActiveUsersQueries.updateUserLastActive(this, tenantIdentifier.toAppIdentifier(), userId); @@ -3100,6 +3101,194 @@ public int countUsersThatHaveMoreThanOneLoginMethodOrTOTPEnabledAndActiveSince(A } } + @Override + public boolean doesOAuthClientIdExist(AppIdentifier appIdentifier, String clientId) + throws StorageQueryException { + try { + return OAuthQueries.doesOAuthClientIdExist(this, clientId, appIdentifier); + } catch (SQLException e) { + throw new StorageQueryException(e); + } + } + + @Override + public void addOrUpdateOauthClient(AppIdentifier appIdentifier, String clientId, boolean isClientCredentialsOnly) + throws StorageQueryException, TenantOrAppNotFoundException { + try { + OAuthQueries.addOrUpdateOauthClient(this, appIdentifier, clientId, isClientCredentialsOnly); + } catch (SQLException e) { + 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); + } + } + throw new StorageQueryException(e); + } + } + + @Override + public boolean deleteOAuthClient(AppIdentifier appIdentifier, String clientId) throws StorageQueryException { + try { + return OAuthQueries.deleteOAuthClient(this, clientId, appIdentifier); + } catch (SQLException e) { + throw new StorageQueryException(e); + } + } + + @Override + public List listOAuthClients(AppIdentifier appIdentifier) throws StorageQueryException { + try { + return OAuthQueries.listOAuthClients(this, appIdentifier); + } catch (SQLException e) { + throw new StorageQueryException(e); + } + } + + @Override + public void revokeOAuthTokensBasedOnTargetFields(AppIdentifier appIdentifier, OAuthRevokeTargetType targetType, String targetValue, long exp) + throws StorageQueryException, TenantOrAppNotFoundException { + try { + OAuthQueries.revokeOAuthTokensBasedOnTargetFields(this, appIdentifier, targetType, targetValue, exp); + } catch (SQLException e) { + PostgreSQLConfig config = Config.getConfig(this); + if (e instanceof PSQLException) { + ServerErrorMessage serverMessage = ((PSQLException) e).getServerErrorMessage(); + + if (isForeignKeyConstraintError(serverMessage, config.getOAuthRevokeTable(), "app_id")) { + throw new TenantOrAppNotFoundException(appIdentifier); + } + } + throw new StorageQueryException(e); + } + + } + + @Override + public boolean isOAuthTokenRevokedBasedOnTargetFields(AppIdentifier appIdentifier, OAuthRevokeTargetType[] targetTypes, String[] targetValues, long issuedAt) + throws StorageQueryException { + try { + return OAuthQueries.isOAuthTokenRevokedBasedOnTargetFields(this, appIdentifier, targetTypes, targetValues, issuedAt); + } catch (SQLException e) { + throw new StorageQueryException(e); + } + } + + @Override + public void addOAuthM2MTokenForStats(AppIdentifier appIdentifier, String clientId, long iat, long exp) + throws StorageQueryException, OAuthClientNotFoundException { + try { + OAuthQueries.addOAuthM2MTokenForStats(this, appIdentifier, clientId, iat, exp); + } catch (SQLException e) { + PostgreSQLConfig config = Config.getConfig(this); + if (e instanceof PSQLException) { + ServerErrorMessage serverMessage = ((PSQLException) e).getServerErrorMessage(); + + if (isForeignKeyConstraintError(serverMessage, config.getOAuthM2MTokensTable(), "client_id")) { + throw new OAuthClientNotFoundException(); + } + } + throw new StorageQueryException(e); + } + } + + @Override + public void cleanUpExpiredAndRevokedOAuthTokensList() throws StorageQueryException { + try { + OAuthQueries.cleanUpExpiredAndRevokedOAuthTokensList(this); + } 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) + throws StorageQueryException, DuplicateOAuthLogoutChallengeException, OAuthClientNotFoundException { + try { + OAuthQueries.addOAuthLogoutChallenge(this, appIdentifier, challenge, clientId, postLogoutRedirectionUri, sessionHandle, state, timeCreated); + } catch (SQLException e) { + 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(); + } + } + throw new StorageQueryException(e); + } + } + + @Override + public OAuthLogoutChallenge getOAuthLogoutChallenge(AppIdentifier appIdentifier, String challenge) throws StorageQueryException { + try { + return OAuthQueries.getOAuthLogoutChallenge(this, appIdentifier, challenge); + } catch (SQLException e) { + throw new StorageQueryException(e); + } + } + + @Override + public void deleteOAuthLogoutChallenge(AppIdentifier appIdentifier, String challenge) throws StorageQueryException { + try { + OAuthQueries.deleteOAuthLogoutChallenge(this, appIdentifier, challenge); + } catch (SQLException e) { + throw new StorageQueryException(e); + } + } + + @Override + public void deleteOAuthLogoutChallengesBefore(long time) throws StorageQueryException { + try { + OAuthQueries.deleteOAuthLogoutChallengesBefore(this, time); + } catch (SQLException e) { + throw new StorageQueryException(e); + } + } + + @Override + public int countTotalNumberOfOAuthClients(AppIdentifier appIdentifier) throws StorageQueryException { + try { + return OAuthQueries.countTotalNumberOfClients(this, appIdentifier, false); + } catch (SQLException e) { + throw new StorageQueryException(e); + } + } + + @Override + public int countTotalNumberOfClientCredentialsOnlyOAuthClients(AppIdentifier appIdentifier) + throws StorageQueryException { + try { + return OAuthQueries.countTotalNumberOfClients(this, appIdentifier, true); + } catch (SQLException e) { + throw new StorageQueryException(e); + } + } + + @Override + public int countTotalNumberOfOAuthM2MTokensCreatedSince(AppIdentifier appIdentifier, long since) + throws StorageQueryException { + try { + return OAuthQueries.countTotalNumberOfOAuthM2MTokensCreatedSince(this, appIdentifier, since); + } catch (SQLException e) { + throw new StorageQueryException(e); + } + } + + @Override + public int countTotalNumberOfOAuthM2MTokensAlive(AppIdentifier appIdentifier) throws StorageQueryException { + try { + return OAuthQueries.countTotalNumberOfOAuthM2MTokensAlive(this, 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 = ?;"; diff --git a/src/main/java/io/supertokens/storage/postgresql/config/PostgreSQLConfig.java b/src/main/java/io/supertokens/storage/postgresql/config/PostgreSQLConfig.java index 2a3f47bc..3cdf80aa 100644 --- a/src/main/java/io/supertokens/storage/postgresql/config/PostgreSQLConfig.java +++ b/src/main/java/io/supertokens/storage/postgresql/config/PostgreSQLConfig.java @@ -434,6 +434,22 @@ public String getDashboardSessionsTable() { return addSchemaAndPrefixToTableName("dashboard_user_sessions"); } + public String getOAuthClientsTable() { + return addSchemaAndPrefixToTableName("oauth_clients"); + } + + public String getOAuthRevokeTable() { + return addSchemaAndPrefixToTableName("oauth_revoke"); + } + + public String getOAuthM2MTokensTable() { + return addSchemaAndPrefixToTableName("oauth_m2m_tokens"); + } + + public String getOAuthLogoutChallengesTable() { + return addSchemaAndPrefixToTableName("oauth_logout_challenges"); + } + public String getTotpUsersTable() { return addSchemaAndPrefixToTableName("totp_users"); } diff --git a/src/main/java/io/supertokens/storage/postgresql/queries/GeneralQueries.java b/src/main/java/io/supertokens/storage/postgresql/queries/GeneralQueries.java index 2cc5e554..3430c41b 100644 --- a/src/main/java/io/supertokens/storage/postgresql/queries/GeneralQueries.java +++ b/src/main/java/io/supertokens/storage/postgresql/queries/GeneralQueries.java @@ -655,6 +655,10 @@ public static void deleteAllTables(Start start) throws SQLException, StorageQuer + getConfig(start).getUserRolesTable() + "," + getConfig(start).getDashboardUsersTable() + "," + getConfig(start).getDashboardSessionsTable() + "," + + getConfig(start).getOAuthClientsTable() + "," + + getConfig(start).getOAuthRevokeTable() + "," + + getConfig(start).getOAuthM2MTokensTable() + "," + + getConfig(start).getOAuthLogoutChallengesTable() + "," + getConfig(start).getTotpUsedCodesTable() + "," + getConfig(start).getTotpUserDevicesTable() + "," + getConfig(start).getTotpUsersTable() + ","