From 3c445164ec3aef25c38a11895c4c4ac51f3d9613 Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Tue, 20 Aug 2024 13:28:34 +0530 Subject: [PATCH 01/11] fix: oauth clients table --- .../postgresql/config/PostgreSQLConfig.java | 4 ++ .../queries/EmailPasswordQueries.java | 2 - .../postgresql/queries/GeneralQueries.java | 6 ++ .../postgresql/queries/OAuthQueries.java | 64 +++++++++++++++++++ 4 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 src/main/java/io/supertokens/storage/postgresql/queries/OAuthQueries.java 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 a1ff555c..9de336ee 100644 --- a/src/main/java/io/supertokens/storage/postgresql/config/PostgreSQLConfig.java +++ b/src/main/java/io/supertokens/storage/postgresql/config/PostgreSQLConfig.java @@ -439,6 +439,10 @@ public String getDashboardSessionsTable() { return addSchemaAndPrefixToTableName("dashboard_user_sessions"); } + public String getOAuthClientTable() { + return addSchemaAndPrefixToTableName("oauth_clients"); + } + public String getTotpUsersTable() { return addSchemaAndPrefixToTableName("totp_users"); } diff --git a/src/main/java/io/supertokens/storage/postgresql/queries/EmailPasswordQueries.java b/src/main/java/io/supertokens/storage/postgresql/queries/EmailPasswordQueries.java index efed6c7f..50c982e9 100644 --- a/src/main/java/io/supertokens/storage/postgresql/queries/EmailPasswordQueries.java +++ b/src/main/java/io/supertokens/storage/postgresql/queries/EmailPasswordQueries.java @@ -20,13 +20,11 @@ import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.authRecipe.LoginMethod; import io.supertokens.pluginInterface.emailpassword.PasswordResetTokenInfo; -import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; -import io.supertokens.storage.postgresql.ConnectionPool; import io.supertokens.storage.postgresql.Start; import io.supertokens.storage.postgresql.config.Config; import io.supertokens.storage.postgresql.utils.Utils; 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 1608d3df..a4a597e2 100644 --- a/src/main/java/io/supertokens/storage/postgresql/queries/GeneralQueries.java +++ b/src/main/java/io/supertokens/storage/postgresql/queries/GeneralQueries.java @@ -552,6 +552,11 @@ public static void createTablesIfNotExists(Start start, Connection con) throws S update(con, TOTPQueries.getQueryToCreateTenantIdIndexForUsedCodesTable(start), NO_OP_SETTER); } + if (!doesTableExists(start, con, Config.getConfig(start).getOAuthClientTable())) { + getInstance(start).addState(CREATING_NEW_TABLE, null); + update(start, OAuthQueries.getQueryToCreateOAuthClientTable(start), NO_OP_SETTER); + } + } catch (Exception e) { if (e.getMessage().contains("schema") && e.getMessage().contains("does not exist") && numberOfRetries < 1) { @@ -622,6 +627,7 @@ public static void deleteAllTables(Start start) throws SQLException, StorageQuer + getConfig(start).getUserRolesTable() + "," + getConfig(start).getDashboardUsersTable() + "," + getConfig(start).getDashboardSessionsTable() + "," + + getConfig(start).getOAuthClientTable() + "," + getConfig(start).getTotpUsedCodesTable() + "," + getConfig(start).getTotpUserDevicesTable() + "," + getConfig(start).getTotpUsersTable(); diff --git a/src/main/java/io/supertokens/storage/postgresql/queries/OAuthQueries.java b/src/main/java/io/supertokens/storage/postgresql/queries/OAuthQueries.java new file mode 100644 index 00000000..03050e60 --- /dev/null +++ b/src/main/java/io/supertokens/storage/postgresql/queries/OAuthQueries.java @@ -0,0 +1,64 @@ +package io.supertokens.storage.postgresql.queries; + +import java.sql.ResultSet; +import java.sql.SQLException; + +import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; +import io.supertokens.storage.postgresql.Start; +import io.supertokens.storage.postgresql.config.Config; +import io.supertokens.storage.postgresql.utils.Utils; + +import static io.supertokens.storage.postgresql.QueryExecutorTemplate.execute; +import static io.supertokens.storage.postgresql.QueryExecutorTemplate.update; + + +public class OAuthQueries { + public static String getQueryToCreateOAuthClientTable(Start start) { + String schema = Config.getConfig(start).getTableSchema(); + String oAuth2ClientTable = Config.getConfig(start).getOAuthClientTable(); + // @formatter:off + return "CREATE TABLE IF NOT EXISTS " + oAuth2ClientTable + " (" + + "app_id VARCHAR(64) DEFAULT 'public'," + + "client_id VARCHAR(128) NOT NULL," + + "CONSTRAINT " + Utils.getConstraintName(schema, oAuth2ClientTable, "client_id", "pkey") + + " PRIMARY KEY (app_id, client_id)," + + "CONSTRAINT " + Utils.getConstraintName(schema, oAuth2ClientTable, "app_id", "fkey") + + " FOREIGN KEY(app_id)" + + " REFERENCES " + Config.getConfig(start).getAppsTable() + "(app_id) ON DELETE CASCADE" + + ");"; + // @formatter:on + } + + public static boolean isClientIdForAppId(Start start, String clientId, AppIdentifier appIdentifier) + throws SQLException, StorageQueryException { + String QUERY = "SELECT app_id FROM " + Config.getConfig(start).getOAuthClientTable() + + " WHERE client_id = ? AND app_id = ?"; + + return execute(start, QUERY, pst -> { + pst.setString(1, clientId); + pst.setString(2, appIdentifier.getAppId()); + }, ResultSet::next); + } + + public static void insertClientIdForAppId(Start start, String clientId, AppIdentifier appIdentifier) + throws SQLException, StorageQueryException { + String INSERT = "INSERT INTO " + Config.getConfig(start).getOAuthClientTable() + + "(app_id, client_id) VALUES(?, ?)"; + update(start, INSERT, pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setString(2, clientId); + }); + } + + public static boolean deleteClientIdForAppId(Start start, String clientId, AppIdentifier appIdentifier) + throws SQLException, StorageQueryException { + String DELETE = "DELETE FROM " + Config.getConfig(start).getOAuthClientTable() + + " WHERE app_id = ? AND client_id = ?"; + int numberOfRow = update(start, DELETE, pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setString(2, clientId); + }); + return numberOfRow > 0; + } +} From 17621e9c8b3caf61e10fcc649b143940adc0c459 Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Tue, 20 Aug 2024 17:50:56 +0530 Subject: [PATCH 02/11] fix: oauth db changes --- .../supertokens/storage/postgresql/Start.java | 44 ++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/supertokens/storage/postgresql/Start.java b/src/main/java/io/supertokens/storage/postgresql/Start.java index a0eea865..f51ba62d 100644 --- a/src/main/java/io/supertokens/storage/postgresql/Start.java +++ b/src/main/java/io/supertokens/storage/postgresql/Start.java @@ -54,6 +54,8 @@ 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; import io.supertokens.pluginInterface.passwordless.exception.*; @@ -106,7 +108,7 @@ public class Start implements SessionSQLStorage, EmailPasswordSQLStorage, EmailVerificationSQLStorage, ThirdPartySQLStorage, JWTRecipeSQLStorage, PasswordlessSQLStorage, UserMetadataSQLStorage, UserRolesSQLStorage, UserIdMappingStorage, UserIdMappingSQLStorage, MultitenancyStorage, MultitenancySQLStorage, DashboardSQLStorage, TOTPSQLStorage, - ActiveUsersStorage, ActiveUsersSQLStorage, AuthRecipeSQLStorage { + ActiveUsersStorage, ActiveUsersSQLStorage, AuthRecipeSQLStorage, OAuthSQLStorage { // these configs are protected from being modified / viewed by the dev using the SuperTokens // SaaS. If the core is not running in SuperTokens SaaS, this array has no effect. @@ -3080,6 +3082,46 @@ public int countUsersThatHaveMoreThanOneLoginMethodOrTOTPEnabledAndActiveSince(A } } + @Override + public boolean doesClientIdExistForThisApp(AppIdentifier appIdentifier, String clientId) + throws StorageQueryException { + try { + return OAuthQueries.isClientIdForAppId(this, clientId, appIdentifier); + } catch (SQLException e) { + throw new StorageQueryException(e); + } + } + + @Override + public void addClientForApp(AppIdentifier appIdentifier, String clientId) + throws StorageQueryException, OAuth2ClientAlreadyExistsForAppException { + try { + OAuthQueries.insertClientIdForAppId(this, clientId, appIdentifier); + } catch (SQLException e) { + + if (e instanceof PSQLException) { + PostgreSQLConfig config = Config.getConfig(this); + ServerErrorMessage serverMessage = ((PSQLException) e).getServerErrorMessage(); + + if (isPrimaryKeyError(serverMessage, config.getOAuthClientTable())) { + throw new OAuth2ClientAlreadyExistsForAppException(); + } + } + throw new StorageQueryException(e); + } + } + + @Override + public boolean removeAppClientAssociation(AppIdentifier appIdentifier, String clientId) + throws StorageQueryException { + try { + return OAuthQueries.deleteClientIdForAppId(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 = ?;"; From 19b7a434538ecbb82f27d498292367b7d69d232c Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Thu, 5 Sep 2024 16:02:58 +0530 Subject: [PATCH 03/11] fix: listClientsForApp --- .../supertokens/storage/postgresql/Start.java | 9 +++++++++ .../postgresql/queries/OAuthQueries.java | 17 +++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/main/java/io/supertokens/storage/postgresql/Start.java b/src/main/java/io/supertokens/storage/postgresql/Start.java index f51ba62d..55b452dd 100644 --- a/src/main/java/io/supertokens/storage/postgresql/Start.java +++ b/src/main/java/io/supertokens/storage/postgresql/Start.java @@ -3121,6 +3121,15 @@ public boolean removeAppClientAssociation(AppIdentifier appIdentifier, String cl } } + @Override + public List listClientsForApp(AppIdentifier appIdentifier) throws StorageQueryException { + try { + return OAuthQueries.listClientsForApp(this, appIdentifier); + } catch (SQLException e) { + throw new StorageQueryException(e); + } + } + @TestOnly public int getDbActivityCount(String dbname) throws SQLException, StorageQueryException { diff --git a/src/main/java/io/supertokens/storage/postgresql/queries/OAuthQueries.java b/src/main/java/io/supertokens/storage/postgresql/queries/OAuthQueries.java index 03050e60..c0c99f73 100644 --- a/src/main/java/io/supertokens/storage/postgresql/queries/OAuthQueries.java +++ b/src/main/java/io/supertokens/storage/postgresql/queries/OAuthQueries.java @@ -2,6 +2,8 @@ import java.sql.ResultSet; import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; @@ -41,6 +43,21 @@ public static boolean isClientIdForAppId(Start start, String clientId, AppIdenti }, ResultSet::next); } + public static List listClientsForApp(Start start, AppIdentifier appIdentifier) + throws SQLException, StorageQueryException { + String QUERY = "SELECT client_id FROM " + Config.getConfig(start).getOAuthClientTable() + + " WHERE app_id = ?"; + return execute(start, QUERY, pst -> { + pst.setString(1, appIdentifier.getAppId()); + }, (result) -> { + List res = new ArrayList<>(); + while (result.next()) { + res.add(result.getString("client_id")); + } + return res; + }); + } + public static void insertClientIdForAppId(Start start, String clientId, AppIdentifier appIdentifier) throws SQLException, StorageQueryException { String INSERT = "INSERT INTO " + Config.getConfig(start).getOAuthClientTable() From eed2827d85c2ad2aedbc6089694d266a9b9c6747 Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Wed, 25 Sep 2024 16:14:31 +0530 Subject: [PATCH 04/11] fix: revoke (#226) * fix: revoke * fix: pr comment * fix: interface * fix: update * fix: oauth stats queries * fix: revoke and cleanup * fix: stats --- .../supertokens/storage/postgresql/Start.java | 95 ++++++-- .../postgresql/config/PostgreSQLConfig.java | 10 +- .../postgresql/queries/GeneralQueries.java | 24 +- .../postgresql/queries/OAuthQueries.java | 225 +++++++++++++++++- 4 files changed, 328 insertions(+), 26 deletions(-) diff --git a/src/main/java/io/supertokens/storage/postgresql/Start.java b/src/main/java/io/supertokens/storage/postgresql/Start.java index 55b452dd..39799ecc 100644 --- a/src/main/java/io/supertokens/storage/postgresql/Start.java +++ b/src/main/java/io/supertokens/storage/postgresql/Start.java @@ -54,7 +54,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; @@ -123,7 +122,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; @@ -3083,7 +3081,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); @@ -3093,20 +3091,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) { - - if (e instanceof PSQLException) { - PostgreSQLConfig config = Config.getConfig(this); - ServerErrorMessage serverMessage = ((PSQLException) e).getServerErrorMessage(); - - if (isPrimaryKeyError(serverMessage, config.getOAuthClientTable())) { - throw new OAuth2ClientAlreadyExistsForAppException(); - } - } throw new StorageQueryException(e); } } @@ -3130,6 +3119,82 @@ public List listClientsForApp(AppIdentifier appIdentifier) throws Storag } } + @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 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); + } + } + + @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); + } + } @TestOnly public int getDbActivityCount(String dbname) throws SQLException, StorageQueryException { 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 9de336ee..228719f5 100644 --- a/src/main/java/io/supertokens/storage/postgresql/config/PostgreSQLConfig.java +++ b/src/main/java/io/supertokens/storage/postgresql/config/PostgreSQLConfig.java @@ -439,10 +439,18 @@ public String getDashboardSessionsTable() { return addSchemaAndPrefixToTableName("dashboard_user_sessions"); } - public String getOAuthClientTable() { + public String getOAuthClientsTable() { return addSchemaAndPrefixToTableName("oauth_clients"); } + public String getOAuthRevokeTable() { + return addSchemaAndPrefixToTableName("oauth_revoke"); + } + + public String getOAuthM2MTokensTable() { + return addSchemaAndPrefixToTableName("oauth_m2m_tokens"); + } + 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 a4a597e2..02b35de8 100644 --- a/src/main/java/io/supertokens/storage/postgresql/queries/GeneralQueries.java +++ b/src/main/java/io/supertokens/storage/postgresql/queries/GeneralQueries.java @@ -552,11 +552,29 @@ public static void createTablesIfNotExists(Start start, Connection con) throws S update(con, TOTPQueries.getQueryToCreateTenantIdIndexForUsedCodesTable(start), NO_OP_SETTER); } - if (!doesTableExists(start, con, Config.getConfig(start).getOAuthClientTable())) { + if (!doesTableExists(start, con, Config.getConfig(start).getOAuthClientsTable())) { getInstance(start).addState(CREATING_NEW_TABLE, null); update(start, OAuthQueries.getQueryToCreateOAuthClientTable(start), NO_OP_SETTER); } + if (!doesTableExists(start, con, Config.getConfig(start).getOAuthRevokeTable())) { + getInstance(start).addState(CREATING_NEW_TABLE, null); + update(start, OAuthQueries.getQueryToCreateOAuthRevokeTable(start), NO_OP_SETTER); + + // index + update(con, OAuthQueries.getQueryToCreateOAuthRevokeTimestampIndex(start), NO_OP_SETTER); + update(con, OAuthQueries.getQueryToCreateOAuthRevokeExpIndex(start), NO_OP_SETTER); + } + + if (!doesTableExists(start, con, Config.getConfig(start).getOAuthM2MTokensTable())) { + getInstance(start).addState(CREATING_NEW_TABLE, null); + update(start, OAuthQueries.getQueryToCreateOAuthM2MTokensTable(start), NO_OP_SETTER); + + // index + update(con, OAuthQueries.getQueryToCreateOAuthM2MTokenIatIndex(start), NO_OP_SETTER); + update(con, OAuthQueries.getQueryToCreateOAuthM2MTokenExpIndex(start), NO_OP_SETTER); + } + } catch (Exception e) { if (e.getMessage().contains("schema") && e.getMessage().contains("does not exist") && numberOfRetries < 1) { @@ -627,7 +645,9 @@ public static void deleteAllTables(Start start) throws SQLException, StorageQuer + getConfig(start).getUserRolesTable() + "," + getConfig(start).getDashboardUsersTable() + "," + getConfig(start).getDashboardSessionsTable() + "," - + getConfig(start).getOAuthClientTable() + "," + + getConfig(start).getOAuthClientsTable() + "," + + getConfig(start).getOAuthRevokeTable() + "," + + getConfig(start).getOAuthM2MTokensTable() + "," + getConfig(start).getTotpUsedCodesTable() + "," + getConfig(start).getTotpUserDevicesTable() + "," + getConfig(start).getTotpUsersTable(); diff --git a/src/main/java/io/supertokens/storage/postgresql/queries/OAuthQueries.java b/src/main/java/io/supertokens/storage/postgresql/queries/OAuthQueries.java index c0c99f73..3775a02b 100644 --- a/src/main/java/io/supertokens/storage/postgresql/queries/OAuthQueries.java +++ b/src/main/java/io/supertokens/storage/postgresql/queries/OAuthQueries.java @@ -14,15 +14,15 @@ import static io.supertokens.storage.postgresql.QueryExecutorTemplate.execute; import static io.supertokens.storage.postgresql.QueryExecutorTemplate.update; - public class OAuthQueries { public static String getQueryToCreateOAuthClientTable(Start start) { String schema = Config.getConfig(start).getTableSchema(); - String oAuth2ClientTable = Config.getConfig(start).getOAuthClientTable(); + String oAuth2ClientTable = Config.getConfig(start).getOAuthClientsTable(); // @formatter:off return "CREATE TABLE IF NOT EXISTS " + oAuth2ClientTable + " (" + "app_id VARCHAR(64) DEFAULT 'public'," + "client_id VARCHAR(128) NOT NULL," + + "is_client_credentials_only BOOLEAN NOT NULL," + "CONSTRAINT " + Utils.getConstraintName(schema, oAuth2ClientTable, "client_id", "pkey") + " PRIMARY KEY (app_id, client_id)," + "CONSTRAINT " + Utils.getConstraintName(schema, oAuth2ClientTable, "app_id", "fkey") @@ -32,9 +32,70 @@ public static String getQueryToCreateOAuthClientTable(Start start) { // @formatter:on } + public static String getQueryToCreateOAuthRevokeTable(Start start) { + String schema = Config.getConfig(start).getTableSchema(); + String oAuth2ClientTable = Config.getConfig(start).getOAuthRevokeTable(); + // @formatter:off + return "CREATE TABLE IF NOT EXISTS " + oAuth2ClientTable + " (" + + "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," + + "CONSTRAINT " + Utils.getConstraintName(schema, oAuth2ClientTable, "client_id", "pkey") + + " PRIMARY KEY (app_id, target_type, target_value)," + + "CONSTRAINT " + Utils.getConstraintName(schema, oAuth2ClientTable, "app_id", "fkey") + + " FOREIGN KEY(app_id)" + + " REFERENCES " + Config.getConfig(start).getAppsTable() + "(app_id) ON DELETE CASCADE" + + ");"; + // @formatter:on + } + + public static String getQueryToCreateOAuthRevokeTimestampIndex(Start start) { + String oAuth2ClientTable = Config.getConfig(start).getOAuthRevokeTable(); + return "CREATE INDEX IF NOT EXISTS oauth_revoke_timestamp_index ON " + + oAuth2ClientTable + "(timestamp DESC, app_id DESC);"; + } + + public static String getQueryToCreateOAuthRevokeExpIndex(Start start) { + String oAuth2ClientTable = Config.getConfig(start).getOAuthRevokeTable(); + return "CREATE INDEX IF NOT EXISTS oauth_revoke_exp_index ON " + + oAuth2ClientTable + "(exp DESC, app_id DESC);"; + } + + public static String getQueryToCreateOAuthM2MTokensTable(Start start) { + String schema = Config.getConfig(start).getTableSchema(); + String oAuth2ClientTable = Config.getConfig(start).getOAuthM2MTokensTable(); + // @formatter:off + return "CREATE TABLE IF NOT EXISTS " + oAuth2ClientTable + " (" + + "app_id VARCHAR(64) DEFAULT 'public'," + + "client_id VARCHAR(128) NOT NULL," + + "iat BIGINT NOT NULL," + + "exp BIGINT NOT NULL," + + "CONSTRAINT " + Utils.getConstraintName(schema, oAuth2ClientTable, "client_id", "pkey") + + " PRIMARY KEY (app_id, client_id, iat)," + + "CONSTRAINT " + Utils.getConstraintName(schema, oAuth2ClientTable, "app_id", "fkey") + + " FOREIGN KEY(app_id)" + + " REFERENCES " + Config.getConfig(start).getAppsTable() + "(app_id) ON DELETE CASCADE" + + ");"; + // @formatter:on + } + + public static String getQueryToCreateOAuthM2MTokenIatIndex(Start start) { + String oAuth2ClientTable = Config.getConfig(start).getOAuthM2MTokensTable(); + return "CREATE INDEX IF NOT EXISTS oauth_m2m_token_iat_index ON " + + oAuth2ClientTable + "(iat DESC, app_id DESC);"; + } + + public static String getQueryToCreateOAuthM2MTokenExpIndex(Start start) { + String oAuth2ClientTable = Config.getConfig(start).getOAuthM2MTokensTable(); + return "CREATE INDEX IF NOT EXISTS oauth_m2m_token_exp_index ON " + + oAuth2ClientTable + "(exp DESC, app_id DESC);"; + } + public static boolean isClientIdForAppId(Start start, String clientId, AppIdentifier appIdentifier) throws SQLException, StorageQueryException { - String QUERY = "SELECT app_id FROM " + Config.getConfig(start).getOAuthClientTable() + + String QUERY = "SELECT app_id FROM " + Config.getConfig(start).getOAuthClientsTable() + " WHERE client_id = ? AND app_id = ?"; return execute(start, QUERY, pst -> { @@ -45,7 +106,7 @@ public static boolean isClientIdForAppId(Start start, String clientId, AppIdenti public static List listClientsForApp(Start start, AppIdentifier appIdentifier) throws SQLException, StorageQueryException { - String QUERY = "SELECT client_id FROM " + Config.getConfig(start).getOAuthClientTable() + + String QUERY = "SELECT client_id FROM " + Config.getConfig(start).getOAuthClientsTable() + " WHERE app_id = ?"; return execute(start, QUERY, pst -> { pst.setString(1, appIdentifier.getAppId()); @@ -58,19 +119,23 @@ public static List listClientsForApp(Start start, AppIdentifier appIdent }); } - public static void insertClientIdForAppId(Start start, String clientId, AppIdentifier appIdentifier) + public static void insertClientIdForAppId(Start start, AppIdentifier appIdentifier, String clientId, + boolean isClientCredentialsOnly) throws SQLException, StorageQueryException { - String INSERT = "INSERT INTO " + Config.getConfig(start).getOAuthClientTable() - + "(app_id, client_id) VALUES(?, ?)"; + String INSERT = "INSERT INTO " + Config.getConfig(start).getOAuthClientsTable() + + "(app_id, client_id, is_client_credentials_only) VALUES(?, ?, ?) " + + "ON CONFLICT (app_id, client_id) DO UPDATE SET is_client_credentials_only = ?"; update(start, INSERT, pst -> { pst.setString(1, appIdentifier.getAppId()); pst.setString(2, clientId); + pst.setBoolean(3, isClientCredentialsOnly); + pst.setBoolean(4, isClientCredentialsOnly); }); } public static boolean deleteClientIdForAppId(Start start, String clientId, AppIdentifier appIdentifier) throws SQLException, StorageQueryException { - String DELETE = "DELETE FROM " + Config.getConfig(start).getOAuthClientTable() + String DELETE = "DELETE FROM " + Config.getConfig(start).getOAuthClientsTable() + " WHERE app_id = ? AND client_id = ?"; int numberOfRow = update(start, DELETE, pst -> { pst.setString(1, appIdentifier.getAppId()); @@ -78,4 +143,148 @@ public static boolean deleteClientIdForAppId(Start start, String clientId, AppId }); return numberOfRow > 0; } + + public static void revoke(Start start, AppIdentifier appIdentifier, String targetType, String targetValue, long exp) + throws SQLException, StorageQueryException { + String INSERT = "INSERT INTO " + Config.getConfig(start).getOAuthRevokeTable() + + "(app_id, target_type, target_value, timestamp, exp) VALUES (?, ?, ?, ?, ?) " + + "ON CONFLICT (app_id, target_type, target_value) DO UPDATE SET timestamp = ?, exp = ?"; + + long currentTime = System.currentTimeMillis() / 1000; + update(start, INSERT, pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setString(2, targetType); + pst.setString(3, targetValue); + pst.setLong(4, currentTime); + pst.setLong(5, exp); + pst.setLong(6, currentTime); + pst.setLong(7, exp); + }); + } + + 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 ("; + + for (int i = 0; i < targetTypes.length; i++) { + QUERY += "(target_type = ? AND target_value = ?)"; + + if (i < targetTypes.length - 1) { + QUERY += " OR "; + } + } + + QUERY += ")"; + + return execute(start, QUERY, pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setLong(2, issuedAt); + + int index = 3; + for (int i = 0; i < targetTypes.length; i++) { + pst.setString(index, targetTypes[i]); + index++; + pst.setString(index, targetValues[i]); + index++; + } + }, ResultSet::next); + } + + public static int countTotalNumberOfClientsForApp(Start start, AppIdentifier appIdentifier, + boolean filterByClientCredentialsOnly) throws SQLException, StorageQueryException { + if (filterByClientCredentialsOnly) { + String QUERY = "SELECT COUNT(*) as c FROM " + Config.getConfig(start).getOAuthClientsTable() + + " WHERE app_id = ? AND is_client_credentials_only = ?"; + return execute(start, QUERY, pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setBoolean(2, true); + }, result -> { + if (result.next()) { + return result.getInt("c"); + } + return 0; + }); + } else { + String QUERY = "SELECT COUNT(*) as c FROM " + Config.getConfig(start).getOAuthClientsTable() + + " WHERE app_id = ?"; + return execute(start, QUERY, pst -> { + pst.setString(1, appIdentifier.getAppId()); + }, result -> { + if (result.next()) { + return result.getInt("c"); + } + return 0; + }); + } + } + + public static int countTotalNumberOfM2MTokensAlive(Start start, AppIdentifier appIdentifier) + throws SQLException, StorageQueryException { + String QUERY = "SELECT COUNT(*) as c FROM " + Config.getConfig(start).getOAuthM2MTokensTable() + + " WHERE app_id = ? AND exp > ?"; + return execute(start, QUERY, pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setLong(2, System.currentTimeMillis()/1000); + }, result -> { + if (result.next()) { + return result.getInt("c"); + } + return 0; + }); + } + + public static int countTotalNumberOfM2MTokensCreatedSince(Start start, AppIdentifier appIdentifier, long since) + throws SQLException, StorageQueryException { + String QUERY = "SELECT COUNT(*) as c FROM " + Config.getConfig(start).getOAuthM2MTokensTable() + + " WHERE app_id = ? AND iat >= ?"; + return execute(start, QUERY, pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setLong(2, since / 1000); + }, result -> { + if (result.next()) { + return result.getInt("c"); + } + return 0; + }); + } + + public static void addM2MToken(Start start, AppIdentifier appIdentifier, String clientId, long iat, long exp) + throws SQLException, StorageQueryException { + String QUERY = "INSERT INTO " + Config.getConfig(start).getOAuthM2MTokensTable() + + " (app_id, client_id, iat, exp) VALUES (?, ?, ?, ?)"; + update(start, QUERY, pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setString(2, clientId); + pst.setLong(3, iat); + pst.setLong(4, exp); + }); + } + + public static void cleanUpExpiredAndRevokedTokens(Start start, AppIdentifier appIdentifier) throws SQLException, StorageQueryException { + { + // delete expired M2M tokens + String QUERY = "DELETE FROM " + Config.getConfig(start).getOAuthM2MTokensTable() + + " WHERE app_id = ? AND exp < ?"; + + long timestamp = System.currentTimeMillis() / 1000 - 3600 * 24 * 31; // expired 31 days ago + update(start, QUERY, pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setLong(2, timestamp); + }); + } + + { + // delete expired revoked tokens + String QUERY = "DELETE FROM " + Config.getConfig(start).getOAuthRevokeTable() + + " WHERE app_id = ? AND exp < ?"; + + long timestamp = System.currentTimeMillis() / 1000 - 3600 * 24 * 31; // expired 31 days ago + update(start, QUERY, pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setLong(2, timestamp); + }); + } + } } From 2a39742182620c705ee0e59b2a33b642b8bedf70 Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Thu, 3 Oct 2024 13:09:57 +0530 Subject: [PATCH 05/11] fix: logout queries (#229) --- .../supertokens/storage/postgresql/Start.java | 39 ++++++ .../postgresql/config/PostgreSQLConfig.java | 4 + .../postgresql/queries/GeneralQueries.java | 9 ++ .../postgresql/queries/OAuthQueries.java | 127 +++++++++++++++--- 4 files changed, 159 insertions(+), 20 deletions(-) diff --git a/src/main/java/io/supertokens/storage/postgresql/Start.java b/src/main/java/io/supertokens/storage/postgresql/Start.java index 39799ecc..57da9eaf 100644 --- a/src/main/java/io/supertokens/storage/postgresql/Start.java +++ b/src/main/java/io/supertokens/storage/postgresql/Start.java @@ -54,6 +54,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; @@ -3149,6 +3150,44 @@ 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 { 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 228719f5..29150141 100644 --- a/src/main/java/io/supertokens/storage/postgresql/config/PostgreSQLConfig.java +++ b/src/main/java/io/supertokens/storage/postgresql/config/PostgreSQLConfig.java @@ -451,6 +451,10 @@ 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 02b35de8..7eed849c 100644 --- a/src/main/java/io/supertokens/storage/postgresql/queries/GeneralQueries.java +++ b/src/main/java/io/supertokens/storage/postgresql/queries/GeneralQueries.java @@ -575,6 +575,14 @@ public static void createTablesIfNotExists(Start start, Connection con) throws S update(con, OAuthQueries.getQueryToCreateOAuthM2MTokenExpIndex(start), NO_OP_SETTER); } + if (!doesTableExists(start, con, Config.getConfig(start).getOAuthLogoutChallengesTable())) { + getInstance(start).addState(CREATING_NEW_TABLE, null); + update(con, OAuthQueries.getQueryToCreateOAuthLogoutChallengesTable(start), NO_OP_SETTER); + + // index + update(con, OAuthQueries.getQueryToCreateOAuthLogoutChallengesTimeCreatedIndex(start), NO_OP_SETTER); + } + } catch (Exception e) { if (e.getMessage().contains("schema") && e.getMessage().contains("does not exist") && numberOfRetries < 1) { @@ -648,6 +656,7 @@ public static void deleteAllTables(Start start) throws SQLException, StorageQuer + getConfig(start).getOAuthClientsTable() + "," + getConfig(start).getOAuthRevokeTable() + "," + getConfig(start).getOAuthM2MTokensTable() + "," + + getConfig(start).getOAuthLogoutChallengesTable() + "," + getConfig(start).getTotpUsedCodesTable() + "," + getConfig(start).getTotpUserDevicesTable() + "," + getConfig(start).getTotpUsersTable(); diff --git a/src/main/java/io/supertokens/storage/postgresql/queries/OAuthQueries.java b/src/main/java/io/supertokens/storage/postgresql/queries/OAuthQueries.java index 3775a02b..df7af769 100644 --- a/src/main/java/io/supertokens/storage/postgresql/queries/OAuthQueries.java +++ b/src/main/java/io/supertokens/storage/postgresql/queries/OAuthQueries.java @@ -7,6 +7,7 @@ import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; +import io.supertokens.pluginInterface.oauth.OAuthLogoutChallenge; import io.supertokens.storage.postgresql.Start; import io.supertokens.storage.postgresql.config.Config; import io.supertokens.storage.postgresql.utils.Utils; @@ -17,15 +18,15 @@ public class OAuthQueries { public static String getQueryToCreateOAuthClientTable(Start start) { String schema = Config.getConfig(start).getTableSchema(); - String oAuth2ClientTable = Config.getConfig(start).getOAuthClientsTable(); + String oAuthClientsTable = Config.getConfig(start).getOAuthClientsTable(); // @formatter:off - return "CREATE TABLE IF NOT EXISTS " + oAuth2ClientTable + " (" + return "CREATE TABLE IF NOT EXISTS " + oAuthClientsTable + " (" + "app_id VARCHAR(64) DEFAULT 'public'," + "client_id VARCHAR(128) NOT NULL," + "is_client_credentials_only BOOLEAN NOT NULL," - + "CONSTRAINT " + Utils.getConstraintName(schema, oAuth2ClientTable, "client_id", "pkey") + + "CONSTRAINT " + Utils.getConstraintName(schema, oAuthClientsTable, "client_id", "pkey") + " PRIMARY KEY (app_id, client_id)," - + "CONSTRAINT " + Utils.getConstraintName(schema, oAuth2ClientTable, "app_id", "fkey") + + "CONSTRAINT " + Utils.getConstraintName(schema, oAuthClientsTable, "app_id", "fkey") + " FOREIGN KEY(app_id)" + " REFERENCES " + Config.getConfig(start).getAppsTable() + "(app_id) ON DELETE CASCADE" + ");"; @@ -34,17 +35,17 @@ public static String getQueryToCreateOAuthClientTable(Start start) { public static String getQueryToCreateOAuthRevokeTable(Start start) { String schema = Config.getConfig(start).getTableSchema(); - String oAuth2ClientTable = Config.getConfig(start).getOAuthRevokeTable(); + String oAuthRevokeTable = Config.getConfig(start).getOAuthRevokeTable(); // @formatter:off - return "CREATE TABLE IF NOT EXISTS " + oAuth2ClientTable + " (" + return "CREATE TABLE IF NOT EXISTS " + oAuthRevokeTable + " (" + "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," - + "CONSTRAINT " + Utils.getConstraintName(schema, oAuth2ClientTable, "client_id", "pkey") + + "CONSTRAINT " + Utils.getConstraintName(schema, oAuthRevokeTable, "client_id", "pkey") + " PRIMARY KEY (app_id, target_type, target_value)," - + "CONSTRAINT " + Utils.getConstraintName(schema, oAuth2ClientTable, "app_id", "fkey") + + "CONSTRAINT " + Utils.getConstraintName(schema, oAuthRevokeTable, "app_id", "fkey") + " FOREIGN KEY(app_id)" + " REFERENCES " + Config.getConfig(start).getAppsTable() + "(app_id) ON DELETE CASCADE" + ");"; @@ -52,29 +53,29 @@ public static String getQueryToCreateOAuthRevokeTable(Start start) { } public static String getQueryToCreateOAuthRevokeTimestampIndex(Start start) { - String oAuth2ClientTable = Config.getConfig(start).getOAuthRevokeTable(); + String oAuthRevokeTable = Config.getConfig(start).getOAuthRevokeTable(); return "CREATE INDEX IF NOT EXISTS oauth_revoke_timestamp_index ON " - + oAuth2ClientTable + "(timestamp DESC, app_id DESC);"; + + oAuthRevokeTable + "(timestamp DESC, app_id DESC);"; } public static String getQueryToCreateOAuthRevokeExpIndex(Start start) { - String oAuth2ClientTable = Config.getConfig(start).getOAuthRevokeTable(); + String oAuthRevokeTable = Config.getConfig(start).getOAuthRevokeTable(); return "CREATE INDEX IF NOT EXISTS oauth_revoke_exp_index ON " - + oAuth2ClientTable + "(exp DESC, app_id DESC);"; + + oAuthRevokeTable + "(exp DESC, app_id DESC);"; } public static String getQueryToCreateOAuthM2MTokensTable(Start start) { String schema = Config.getConfig(start).getTableSchema(); - String oAuth2ClientTable = Config.getConfig(start).getOAuthM2MTokensTable(); + String oAuthM2MTokensTable = Config.getConfig(start).getOAuthM2MTokensTable(); // @formatter:off - return "CREATE TABLE IF NOT EXISTS " + oAuth2ClientTable + " (" + return "CREATE TABLE IF NOT EXISTS " + oAuthM2MTokensTable + " (" + "app_id VARCHAR(64) DEFAULT 'public'," + "client_id VARCHAR(128) NOT NULL," + "iat BIGINT NOT NULL," + "exp BIGINT NOT NULL," - + "CONSTRAINT " + Utils.getConstraintName(schema, oAuth2ClientTable, "client_id", "pkey") + + "CONSTRAINT " + Utils.getConstraintName(schema, oAuthM2MTokensTable, "client_id", "pkey") + " PRIMARY KEY (app_id, client_id, iat)," - + "CONSTRAINT " + Utils.getConstraintName(schema, oAuth2ClientTable, "app_id", "fkey") + + "CONSTRAINT " + Utils.getConstraintName(schema, oAuthM2MTokensTable, "app_id", "fkey") + " FOREIGN KEY(app_id)" + " REFERENCES " + Config.getConfig(start).getAppsTable() + "(app_id) ON DELETE CASCADE" + ");"; @@ -82,15 +83,45 @@ public static String getQueryToCreateOAuthM2MTokensTable(Start start) { } public static String getQueryToCreateOAuthM2MTokenIatIndex(Start start) { - String oAuth2ClientTable = Config.getConfig(start).getOAuthM2MTokensTable(); + String oAuthM2MTokensTable = Config.getConfig(start).getOAuthM2MTokensTable(); return "CREATE INDEX IF NOT EXISTS oauth_m2m_token_iat_index ON " - + oAuth2ClientTable + "(iat DESC, app_id DESC);"; + + oAuthM2MTokensTable + "(iat DESC, app_id DESC);"; } public static String getQueryToCreateOAuthM2MTokenExpIndex(Start start) { - String oAuth2ClientTable = Config.getConfig(start).getOAuthM2MTokensTable(); + String oAuthM2MTokensTable = Config.getConfig(start).getOAuthM2MTokensTable(); return "CREATE INDEX IF NOT EXISTS oauth_m2m_token_exp_index ON " - + oAuth2ClientTable + "(exp DESC, app_id DESC);"; + + oAuthM2MTokensTable + "(exp DESC, app_id DESC);"; + } + + public static String getQueryToCreateOAuthLogoutChallengesTable(Start start) { + String schema = Config.getConfig(start).getTableSchema(); + 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," + + "CONSTRAINT " + Utils.getConstraintName(schema, oAuth2LogoutChallengesTable, "app_id_challenge", "pkey") + + " PRIMARY KEY (app_id, challenge)," + + "CONSTRAINT " + Utils.getConstraintName(schema, oAuth2LogoutChallengesTable, "app_id_client_id", "fkey") + + " FOREIGN KEY(app_id, client_id)" + + " REFERENCES " + Config.getConfig(start).getOAuthClientsTable() + "(app_id, client_id) ON DELETE CASCADE," + + "CONSTRAINT " + Utils.getConstraintName(schema, oAuth2LogoutChallengesTable, "app_id", "fkey") + + " 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) @@ -287,4 +318,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); + }); + } } From f0a7375f32aaf5f4996698c2fcffdb9df7232e11 Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Thu, 3 Oct 2024 13:25:33 +0530 Subject: [PATCH 06/11] fix: update queries --- .../supertokens/storage/postgresql/Start.java | 86 +++++++++++-------- .../postgresql/queries/OAuthQueries.java | 74 +++++++++------- 2 files changed, 93 insertions(+), 67 deletions(-) diff --git a/src/main/java/io/supertokens/storage/postgresql/Start.java b/src/main/java/io/supertokens/storage/postgresql/Start.java index 57da9eaf..bc4915a1 100644 --- a/src/main/java/io/supertokens/storage/postgresql/Start.java +++ b/src/main/java/io/supertokens/storage/postgresql/Start.java @@ -55,7 +55,9 @@ 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.oauth.OAuthRevokeTargetType; +import io.supertokens.pluginInterface.oauth.OAuthStorage; +import io.supertokens.pluginInterface.oauth.exception.DuplicateOAuthLogoutChallengeException; import io.supertokens.pluginInterface.passwordless.PasswordlessCode; import io.supertokens.pluginInterface.passwordless.PasswordlessDevice; import io.supertokens.pluginInterface.passwordless.exception.*; @@ -108,7 +110,7 @@ public class Start implements SessionSQLStorage, EmailPasswordSQLStorage, EmailVerificationSQLStorage, ThirdPartySQLStorage, JWTRecipeSQLStorage, PasswordlessSQLStorage, UserMetadataSQLStorage, UserRolesSQLStorage, UserIdMappingStorage, UserIdMappingSQLStorage, MultitenancyStorage, MultitenancySQLStorage, DashboardSQLStorage, TOTPSQLStorage, - ActiveUsersStorage, ActiveUsersSQLStorage, AuthRecipeSQLStorage, OAuthSQLStorage { + ActiveUsersStorage, ActiveUsersSQLStorage, AuthRecipeSQLStorage, OAuthStorage { // these configs are protected from being modified / viewed by the dev using the SuperTokens // SaaS. If the core is not running in SuperTokens SaaS, this array has no effect. @@ -865,6 +867,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); @@ -3082,154 +3086,162 @@ public int countUsersThatHaveMoreThanOneLoginMethodOrTOTPEnabledAndActiveSince(A } @Override - public boolean doesClientIdExistForApp(AppIdentifier appIdentifier, String clientId) + public boolean doesOAuthClientIdExist(AppIdentifier appIdentifier, String clientId) throws StorageQueryException { try { - return OAuthQueries.isClientIdForAppId(this, clientId, appIdentifier); + return OAuthQueries.doesOAuthClientIdExist(this, clientId, appIdentifier); } catch (SQLException e) { throw new StorageQueryException(e); } } @Override - public void addOrUpdateClientForApp(AppIdentifier appIdentifier, String clientId, boolean isClientCredentialsOnly) + public void addOrUpdateOauthClient(AppIdentifier appIdentifier, String clientId, boolean isClientCredentialsOnly) throws StorageQueryException { try { - OAuthQueries.insertClientIdForAppId(this, appIdentifier, clientId, isClientCredentialsOnly); + OAuthQueries.addOrUpdateOauthClient(this, appIdentifier, clientId, isClientCredentialsOnly); } catch (SQLException e) { throw new StorageQueryException(e); } } @Override - public boolean removeAppClientAssociation(AppIdentifier appIdentifier, String clientId) - throws StorageQueryException { + public boolean deleteOAuthClient(AppIdentifier appIdentifier, String clientId) throws StorageQueryException { try { - return OAuthQueries.deleteClientIdForAppId(this, clientId, appIdentifier); + return OAuthQueries.deleteOAuthClient(this, clientId, appIdentifier); } catch (SQLException e) { throw new StorageQueryException(e); } } @Override - public List listClientsForApp(AppIdentifier appIdentifier) throws StorageQueryException { + public List listOAuthClients(AppIdentifier appIdentifier) throws StorageQueryException { try { - return OAuthQueries.listClientsForApp(this, appIdentifier); + return OAuthQueries.listOAuthClients(this, appIdentifier); } catch (SQLException e) { throw new StorageQueryException(e); } } @Override - public void revoke(AppIdentifier appIdentifier, String targetType, String targetValue, long exp) + public void revokeOAuthTokensBasedOnTargetFields(AppIdentifier appIdentifier, OAuthRevokeTargetType targetType, String targetValue, long exp) throws StorageQueryException { try { - OAuthQueries.revoke(this, appIdentifier, targetType, targetValue, exp); + OAuthQueries.revokeOAuthTokensBasedOnTargetFields(this, appIdentifier, targetType, targetValue, exp); } catch (SQLException e) { throw new StorageQueryException(e); } + } @Override - public boolean isRevoked(AppIdentifier appIdentifier, String[] targetTypes, String[] targetValues, long issuedAt) + public boolean isOAuthTokenRevokedBasedOnTargetFields(AppIdentifier appIdentifier, OAuthRevokeTargetType[] targetTypes, String[] targetValues, long issuedAt) throws StorageQueryException { try { - return OAuthQueries.isRevoked(this, appIdentifier, targetTypes, targetValues, issuedAt); + return OAuthQueries.isOAuthTokenRevokedBasedOnTargetFields(this, appIdentifier, targetTypes, targetValues, issuedAt); } catch (SQLException e) { throw new StorageQueryException(e); } } @Override - public void addM2MToken(AppIdentifier appIdentifier, String clientId, long iat, long exp) + public void addOAuthM2MTokenForStats(AppIdentifier appIdentifier, String clientId, long iat, long exp) throws StorageQueryException { try { - OAuthQueries.addM2MToken(this, appIdentifier, clientId, iat, exp); + OAuthQueries.addOAuthM2MTokenForStats(this, appIdentifier, clientId, iat, exp); } catch (SQLException e) { throw new StorageQueryException(e); } } @Override - public void addLogoutChallenge(AppIdentifier appIdentifier, String challenge, String clientId, - String postLogoutRedirectionUri, String sessionHandle, String state, long timeCreated) throws StorageQueryException { + public void cleanUpExpiredAndRevokedOAuthTokensList() throws StorageQueryException { try { - OAuthQueries.addLogoutChallenge(this, appIdentifier, challenge, clientId, postLogoutRedirectionUri, sessionHandle, state, timeCreated); + OAuthQueries.cleanUpExpiredAndRevokedOAuthTokensList(this); } catch (SQLException e) { throw new StorageQueryException(e); } } @Override - public OAuthLogoutChallenge getLogoutChallenge(AppIdentifier appIdentifier, String challenge) - throws StorageQueryException { + public void addOAuthLogoutChallenge(AppIdentifier appIdentifier, String challenge, String clientId, + String postLogoutRedirectionUri, String sessionHandle, String state, long timeCreated) + throws StorageQueryException, DuplicateOAuthLogoutChallengeException { try { - return OAuthQueries.getLogoutChallenge(this, appIdentifier, challenge); + 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(); + } + } throw new StorageQueryException(e); } } @Override - public void deleteLogoutChallenge(AppIdentifier appIdentifier, String challenge) throws StorageQueryException { + public OAuthLogoutChallenge getOAuthLogoutChallenge(AppIdentifier appIdentifier, String challenge) throws StorageQueryException { try { - OAuthQueries.deleteLogoutChallenge(this, appIdentifier, challenge); + return OAuthQueries.getOAuthLogoutChallenge(this, appIdentifier, challenge); } catch (SQLException e) { throw new StorageQueryException(e); } } @Override - public void deleteLogoutChallengesBefore(AppIdentifier appIdentifier, long time) throws StorageQueryException { + public void deleteOAuthLogoutChallenge(AppIdentifier appIdentifier, String challenge) throws StorageQueryException { try { - OAuthQueries.deleteLogoutChallengesBefore(this, appIdentifier, time); + OAuthQueries.deleteOAuthLogoutChallenge(this, appIdentifier, challenge); } catch (SQLException e) { throw new StorageQueryException(e); } } @Override - public void cleanUpExpiredAndRevokedTokens(AppIdentifier appIdentifier) throws StorageQueryException { + public void deleteOAuthLogoutChallengesBefore(long time) throws StorageQueryException { try { - OAuthQueries.cleanUpExpiredAndRevokedTokens(this, appIdentifier); + OAuthQueries.deleteOAuthLogoutChallengesBefore(this, time); } catch (SQLException e) { throw new StorageQueryException(e); } } @Override - public int countTotalNumberOfM2MTokensAlive(AppIdentifier appIdentifier) throws StorageQueryException { + public int countTotalNumberOfOAuthClients(AppIdentifier appIdentifier) throws StorageQueryException { try { - return OAuthQueries.countTotalNumberOfM2MTokensAlive(this, appIdentifier); + return OAuthQueries.countTotalNumberOfClients(this, appIdentifier, false); } catch (SQLException e) { throw new StorageQueryException(e); } } @Override - public int countTotalNumberOfM2MTokensCreatedSince(AppIdentifier appIdentifier, long since) + public int countTotalNumberOfClientCredentialsOnlyOAuthClients(AppIdentifier appIdentifier) throws StorageQueryException { try { - return OAuthQueries.countTotalNumberOfM2MTokensCreatedSince(this, appIdentifier, since); + return OAuthQueries.countTotalNumberOfClients(this, appIdentifier, true); } catch (SQLException e) { throw new StorageQueryException(e); } } @Override - public int countTotalNumberOfClientCredentialsOnlyClientsForApp(AppIdentifier appIdentifier) + public int countTotalNumberOfOAuthM2MTokensCreatedSince(AppIdentifier appIdentifier, long since) throws StorageQueryException { try { - return OAuthQueries.countTotalNumberOfClientsForApp(this, appIdentifier, true); + return OAuthQueries.countTotalNumberOfOAuthM2MTokensCreatedSince(this, appIdentifier, since); } catch (SQLException e) { throw new StorageQueryException(e); } } @Override - public int countTotalNumberOfClientsForApp(AppIdentifier appIdentifier) throws StorageQueryException { + public int countTotalNumberOfOAuthM2MTokensAlive(AppIdentifier appIdentifier) throws StorageQueryException { try { - return OAuthQueries.countTotalNumberOfClientsForApp(this, appIdentifier, false); + return OAuthQueries.countTotalNumberOfOAuthM2MTokensAlive(this, appIdentifier); } catch (SQLException e) { throw new StorageQueryException(e); } diff --git a/src/main/java/io/supertokens/storage/postgresql/queries/OAuthQueries.java b/src/main/java/io/supertokens/storage/postgresql/queries/OAuthQueries.java index df7af769..217c5dea 100644 --- a/src/main/java/io/supertokens/storage/postgresql/queries/OAuthQueries.java +++ b/src/main/java/io/supertokens/storage/postgresql/queries/OAuthQueries.java @@ -1,4 +1,20 @@ -package io.supertokens.storage.postgresql.queries; +/* + * Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + + package io.supertokens.storage.postgresql.queries; import java.sql.ResultSet; import java.sql.SQLException; @@ -8,6 +24,7 @@ import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.oauth.OAuthLogoutChallenge; +import io.supertokens.pluginInterface.oauth.OAuthRevokeTargetType; import io.supertokens.storage.postgresql.Start; import io.supertokens.storage.postgresql.config.Config; import io.supertokens.storage.postgresql.utils.Utils; @@ -61,7 +78,7 @@ public static String getQueryToCreateOAuthRevokeTimestampIndex(Start start) { public static String getQueryToCreateOAuthRevokeExpIndex(Start start) { String oAuthRevokeTable = Config.getConfig(start).getOAuthRevokeTable(); return "CREATE INDEX IF NOT EXISTS oauth_revoke_exp_index ON " - + oAuthRevokeTable + "(exp DESC, app_id DESC);"; + + oAuthRevokeTable + "(exp DESC);"; } public static String getQueryToCreateOAuthM2MTokensTable(Start start) { @@ -91,7 +108,7 @@ public static String getQueryToCreateOAuthM2MTokenIatIndex(Start start) { public static String getQueryToCreateOAuthM2MTokenExpIndex(Start start) { String oAuthM2MTokensTable = Config.getConfig(start).getOAuthM2MTokensTable(); return "CREATE INDEX IF NOT EXISTS oauth_m2m_token_exp_index ON " - + oAuthM2MTokensTable + "(exp DESC, app_id DESC);"; + + oAuthM2MTokensTable + "(exp DESC);"; } public static String getQueryToCreateOAuthLogoutChallengesTable(Start start) { @@ -124,7 +141,7 @@ public static String getQueryToCreateOAuthLogoutChallengesTimeCreatedIndex(Start + oAuth2LogoutChallengesTable + "(time_created ASC, app_id ASC);"; } - public static boolean isClientIdForAppId(Start start, String clientId, AppIdentifier appIdentifier) + public static boolean doesOAuthClientIdExist(Start start, String clientId, AppIdentifier appIdentifier) throws SQLException, StorageQueryException { String QUERY = "SELECT app_id FROM " + Config.getConfig(start).getOAuthClientsTable() + " WHERE client_id = ? AND app_id = ?"; @@ -135,7 +152,7 @@ public static boolean isClientIdForAppId(Start start, String clientId, AppIdenti }, ResultSet::next); } - public static List listClientsForApp(Start start, AppIdentifier appIdentifier) + public static List listOAuthClients(Start start, AppIdentifier appIdentifier) throws SQLException, StorageQueryException { String QUERY = "SELECT client_id FROM " + Config.getConfig(start).getOAuthClientsTable() + " WHERE app_id = ?"; @@ -150,7 +167,7 @@ public static List listClientsForApp(Start start, AppIdentifier appIdent }); } - public static void insertClientIdForAppId(Start start, AppIdentifier appIdentifier, String clientId, + public static void addOrUpdateOauthClient(Start start, AppIdentifier appIdentifier, String clientId, boolean isClientCredentialsOnly) throws SQLException, StorageQueryException { String INSERT = "INSERT INTO " + Config.getConfig(start).getOAuthClientsTable() @@ -164,7 +181,7 @@ public static void insertClientIdForAppId(Start start, AppIdentifier appIdentifi }); } - public static boolean deleteClientIdForAppId(Start start, String clientId, AppIdentifier appIdentifier) + public static boolean deleteOAuthClient(Start start, String clientId, AppIdentifier appIdentifier) throws SQLException, StorageQueryException { String DELETE = "DELETE FROM " + Config.getConfig(start).getOAuthClientsTable() + " WHERE app_id = ? AND client_id = ?"; @@ -175,7 +192,7 @@ public static boolean deleteClientIdForAppId(Start start, String clientId, AppId return numberOfRow > 0; } - public static void revoke(Start start, AppIdentifier appIdentifier, String targetType, String targetValue, long exp) + public static void revokeOAuthTokensBasedOnTargetFields(Start start, AppIdentifier appIdentifier, OAuthRevokeTargetType targetType, String targetValue, long exp) throws SQLException, StorageQueryException { String INSERT = "INSERT INTO " + Config.getConfig(start).getOAuthRevokeTable() + "(app_id, target_type, target_value, timestamp, exp) VALUES (?, ?, ?, ?, ?) " @@ -184,7 +201,7 @@ public static void revoke(Start start, AppIdentifier appIdentifier, String targe long currentTime = System.currentTimeMillis() / 1000; update(start, INSERT, pst -> { pst.setString(1, appIdentifier.getAppId()); - pst.setString(2, targetType); + pst.setString(2, targetType.getValue()); pst.setString(3, targetValue); pst.setLong(4, currentTime); pst.setLong(5, exp); @@ -193,11 +210,11 @@ public static void revoke(Start start, AppIdentifier appIdentifier, String targe }); } - public static boolean isRevoked(Start start, AppIdentifier appIdentifier, String[] targetTypes, + public static boolean isOAuthTokenRevokedBasedOnTargetFields(Start start, AppIdentifier appIdentifier, OAuthRevokeTargetType[] 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 = ?)"; @@ -215,7 +232,7 @@ public static boolean isRevoked(Start start, AppIdentifier appIdentifier, String int index = 3; for (int i = 0; i < targetTypes.length; i++) { - pst.setString(index, targetTypes[i]); + pst.setString(index, targetTypes[i].getValue()); index++; pst.setString(index, targetValues[i]); index++; @@ -223,7 +240,7 @@ public static boolean isRevoked(Start start, AppIdentifier appIdentifier, String }, ResultSet::next); } - public static int countTotalNumberOfClientsForApp(Start start, AppIdentifier appIdentifier, + public static int countTotalNumberOfClients(Start start, AppIdentifier appIdentifier, boolean filterByClientCredentialsOnly) throws SQLException, StorageQueryException { if (filterByClientCredentialsOnly) { String QUERY = "SELECT COUNT(*) as c FROM " + Config.getConfig(start).getOAuthClientsTable() + @@ -251,7 +268,7 @@ public static int countTotalNumberOfClientsForApp(Start start, AppIdentifier app } } - public static int countTotalNumberOfM2MTokensAlive(Start start, AppIdentifier appIdentifier) + public static int countTotalNumberOfOAuthM2MTokensAlive(Start start, AppIdentifier appIdentifier) throws SQLException, StorageQueryException { String QUERY = "SELECT COUNT(*) as c FROM " + Config.getConfig(start).getOAuthM2MTokensTable() + " WHERE app_id = ? AND exp > ?"; @@ -266,7 +283,7 @@ public static int countTotalNumberOfM2MTokensAlive(Start start, AppIdentifier ap }); } - public static int countTotalNumberOfM2MTokensCreatedSince(Start start, AppIdentifier appIdentifier, long since) + public static int countTotalNumberOfOAuthM2MTokensCreatedSince(Start start, AppIdentifier appIdentifier, long since) throws SQLException, StorageQueryException { String QUERY = "SELECT COUNT(*) as c FROM " + Config.getConfig(start).getOAuthM2MTokensTable() + " WHERE app_id = ? AND iat >= ?"; @@ -281,7 +298,7 @@ public static int countTotalNumberOfM2MTokensCreatedSince(Start start, AppIdenti }); } - public static void addM2MToken(Start start, AppIdentifier appIdentifier, String clientId, long iat, long exp) + public static void addOAuthM2MTokenForStats(Start start, AppIdentifier appIdentifier, String clientId, long iat, long exp) throws SQLException, StorageQueryException { String QUERY = "INSERT INTO " + Config.getConfig(start).getOAuthM2MTokensTable() + " (app_id, client_id, iat, exp) VALUES (?, ?, ?, ?)"; @@ -293,33 +310,31 @@ public static void addM2MToken(Start start, AppIdentifier appIdentifier, String }); } - public static void cleanUpExpiredAndRevokedTokens(Start start, AppIdentifier appIdentifier) throws SQLException, StorageQueryException { + public static void cleanUpExpiredAndRevokedOAuthTokensList(Start start) throws SQLException, StorageQueryException { { // delete expired M2M tokens String QUERY = "DELETE FROM " + Config.getConfig(start).getOAuthM2MTokensTable() + - " WHERE app_id = ? AND exp < ?"; + " WHERE exp < ?"; long timestamp = System.currentTimeMillis() / 1000 - 3600 * 24 * 31; // expired 31 days ago update(start, QUERY, pst -> { - pst.setString(1, appIdentifier.getAppId()); - pst.setLong(2, timestamp); + pst.setLong(1, timestamp); }); } { // delete expired revoked tokens String QUERY = "DELETE FROM " + Config.getConfig(start).getOAuthRevokeTable() + - " WHERE app_id = ? AND exp < ?"; + " WHERE exp < ?"; long timestamp = System.currentTimeMillis() / 1000 - 3600 * 24 * 31; // expired 31 days ago update(start, QUERY, pst -> { - pst.setString(1, appIdentifier.getAppId()); - pst.setLong(2, timestamp); + pst.setLong(1, timestamp); }); } } - public static void addLogoutChallenge(Start start, AppIdentifier appIdentifier, String challenge, String clientId, + public static void addOAuthLogoutChallenge(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 (?, ?, ?, ?, ?, ?, ?)"; @@ -334,7 +349,7 @@ public static void addLogoutChallenge(Start start, AppIdentifier appIdentifier, }); } - public static OAuthLogoutChallenge getLogoutChallenge(Start start, AppIdentifier appIdentifier, String challenge) throws SQLException, StorageQueryException { + public static OAuthLogoutChallenge getOAuthLogoutChallenge(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 = ?"; @@ -357,7 +372,7 @@ public static OAuthLogoutChallenge getLogoutChallenge(Start start, AppIdentifier }); } - public static void deleteLogoutChallenge(Start start, AppIdentifier appIdentifier, String challenge) throws SQLException, StorageQueryException { + public static void deleteOAuthLogoutChallenge(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 -> { @@ -366,12 +381,11 @@ public static void deleteLogoutChallenge(Start start, AppIdentifier appIdentifie }); } - public static void deleteLogoutChallengesBefore(Start start, AppIdentifier appIdentifier, long time) throws SQLException, StorageQueryException { + public static void deleteOAuthLogoutChallengesBefore(Start start, long time) throws SQLException, StorageQueryException { String QUERY = "DELETE FROM " + Config.getConfig(start).getOAuthLogoutChallengesTable() + - " WHERE app_id = ? AND time_created < ?"; + " WHERE time_created < ?"; update(start, QUERY, pst -> { - pst.setString(1, appIdentifier.getAppId()); - pst.setLong(2, time); + pst.setLong(1, time); }); } } From ac8e8e132e15bf0ecbdfbe3998682a11637e0d40 Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Thu, 3 Oct 2024 13:28:11 +0530 Subject: [PATCH 07/11] fix: versions --- build.gradle | 2 +- pluginInterfaceSupported.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 5d172564..553a2c88 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ plugins { id 'java-library' } -version = "7.1.1" +version = "7.2.0" repositories { mavenCentral() 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 From 8130dd61c90a034365e629c41d889f512bdf7d67 Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Thu, 3 Oct 2024 15:09:55 +0530 Subject: [PATCH 08/11] revert --- .../storage/postgresql/queries/EmailPasswordQueries.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/io/supertokens/storage/postgresql/queries/EmailPasswordQueries.java b/src/main/java/io/supertokens/storage/postgresql/queries/EmailPasswordQueries.java index 50c982e9..efed6c7f 100644 --- a/src/main/java/io/supertokens/storage/postgresql/queries/EmailPasswordQueries.java +++ b/src/main/java/io/supertokens/storage/postgresql/queries/EmailPasswordQueries.java @@ -20,11 +20,13 @@ import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.authRecipe.LoginMethod; import io.supertokens.pluginInterface.emailpassword.PasswordResetTokenInfo; +import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; +import io.supertokens.storage.postgresql.ConnectionPool; import io.supertokens.storage.postgresql.Start; import io.supertokens.storage.postgresql.config.Config; import io.supertokens.storage.postgresql.utils.Utils; From 4472f93afe58a7329adc8448ac73de0ecfa80db6 Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Thu, 3 Oct 2024 15:11:26 +0530 Subject: [PATCH 09/11] fix: changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5be1e95c..428bbcdd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ 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 + ## [7.1.1] - 2024-08-08 - Fixes tests that check for `Internal Error` in 500 status responses From 9bf435da0ddf53fdb3f02a66caaefd5b2d22d49a Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Thu, 3 Oct 2024 15:17:47 +0530 Subject: [PATCH 10/11] fix: changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 428bbcdd..3dd2981d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [7.2.0] - 2024-10-03 -- Compatible with plugin interface version 6.3. +- Compatible with plugin interface version 6.3 - Adds support for OAuthStorage ## [7.1.1] - 2024-08-08 From ff6ec15dbd56000e1b199acea2466c042dc526fe Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Fri, 4 Oct 2024 09:04:13 +0530 Subject: [PATCH 11/11] fix: constraints --- .../supertokens/storage/postgresql/Start.java | 35 ++++++++++++++++--- .../postgresql/queries/OAuthQueries.java | 23 ++++++------ 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/src/main/java/io/supertokens/storage/postgresql/Start.java b/src/main/java/io/supertokens/storage/postgresql/Start.java index bc4915a1..986e51e0 100644 --- a/src/main/java/io/supertokens/storage/postgresql/Start.java +++ b/src/main/java/io/supertokens/storage/postgresql/Start.java @@ -58,6 +58,7 @@ 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; import io.supertokens.pluginInterface.passwordless.PasswordlessCode; import io.supertokens.pluginInterface.passwordless.PasswordlessDevice; import io.supertokens.pluginInterface.passwordless.exception.*; @@ -3097,10 +3098,18 @@ public boolean doesOAuthClientIdExist(AppIdentifier appIdentifier, String client @Override public void addOrUpdateOauthClient(AppIdentifier appIdentifier, String clientId, boolean isClientCredentialsOnly) - throws StorageQueryException { + 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); } } @@ -3125,10 +3134,18 @@ public List listOAuthClients(AppIdentifier appIdentifier) throws Storage @Override public void revokeOAuthTokensBasedOnTargetFields(AppIdentifier appIdentifier, OAuthRevokeTargetType targetType, String targetValue, long exp) - throws StorageQueryException { + 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); } @@ -3146,10 +3163,18 @@ public boolean isOAuthTokenRevokedBasedOnTargetFields(AppIdentifier appIdentifie @Override public void addOAuthM2MTokenForStats(AppIdentifier appIdentifier, String clientId, long iat, long exp) - throws StorageQueryException { + 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); } } @@ -3166,7 +3191,7 @@ public void cleanUpExpiredAndRevokedOAuthTokensList() throws StorageQueryExcepti @Override public void addOAuthLogoutChallenge(AppIdentifier appIdentifier, String challenge, String clientId, String postLogoutRedirectionUri, String sessionHandle, String state, long timeCreated) - throws StorageQueryException, DuplicateOAuthLogoutChallengeException { + throws StorageQueryException, DuplicateOAuthLogoutChallengeException, OAuthClientNotFoundException { try { OAuthQueries.addOAuthLogoutChallenge(this, appIdentifier, challenge, clientId, postLogoutRedirectionUri, sessionHandle, state, timeCreated); } catch (SQLException e) { @@ -3176,6 +3201,8 @@ public void addOAuthLogoutChallenge(AppIdentifier appIdentifier, String challeng if (isPrimaryKeyError(serverMessage, config.getOAuthLogoutChallengesTable())) { throw new DuplicateOAuthLogoutChallengeException(); + } else if (isForeignKeyConstraintError(serverMessage, config.getOAuthLogoutChallengesTable(), "client_id")) { + throw new OAuthClientNotFoundException(); } } throw new StorageQueryException(e); diff --git a/src/main/java/io/supertokens/storage/postgresql/queries/OAuthQueries.java b/src/main/java/io/supertokens/storage/postgresql/queries/OAuthQueries.java index 217c5dea..aa2168c9 100644 --- a/src/main/java/io/supertokens/storage/postgresql/queries/OAuthQueries.java +++ b/src/main/java/io/supertokens/storage/postgresql/queries/OAuthQueries.java @@ -41,7 +41,7 @@ public static String getQueryToCreateOAuthClientTable(Start start) { + "app_id VARCHAR(64) DEFAULT 'public'," + "client_id VARCHAR(128) NOT NULL," + "is_client_credentials_only BOOLEAN NOT NULL," - + "CONSTRAINT " + Utils.getConstraintName(schema, oAuthClientsTable, "client_id", "pkey") + + "CONSTRAINT " + Utils.getConstraintName(schema, oAuthClientsTable, null, "pkey") + " PRIMARY KEY (app_id, client_id)," + "CONSTRAINT " + Utils.getConstraintName(schema, oAuthClientsTable, "app_id", "fkey") + " FOREIGN KEY(app_id)" @@ -60,7 +60,7 @@ public static String getQueryToCreateOAuthRevokeTable(Start start) { + "target_value VARCHAR(128) NOT NULL," + "timestamp BIGINT NOT NULL," + "exp BIGINT NOT NULL," - + "CONSTRAINT " + Utils.getConstraintName(schema, oAuthRevokeTable, "client_id", "pkey") + + "CONSTRAINT " + Utils.getConstraintName(schema, oAuthRevokeTable, null, "pkey") + " PRIMARY KEY (app_id, target_type, target_value)," + "CONSTRAINT " + Utils.getConstraintName(schema, oAuthRevokeTable, "app_id", "fkey") + " FOREIGN KEY(app_id)" @@ -90,11 +90,11 @@ public static String getQueryToCreateOAuthM2MTokensTable(Start start) { + "client_id VARCHAR(128) NOT NULL," + "iat BIGINT NOT NULL," + "exp BIGINT NOT NULL," - + "CONSTRAINT " + Utils.getConstraintName(schema, oAuthM2MTokensTable, "client_id", "pkey") + + "CONSTRAINT " + Utils.getConstraintName(schema, oAuthM2MTokensTable, null, "pkey") + " PRIMARY KEY (app_id, client_id, iat)," - + "CONSTRAINT " + Utils.getConstraintName(schema, oAuthM2MTokensTable, "app_id", "fkey") - + " FOREIGN KEY(app_id)" - + " REFERENCES " + Config.getConfig(start).getAppsTable() + "(app_id) ON DELETE CASCADE" + + "CONSTRAINT " + Utils.getConstraintName(schema, oAuthM2MTokensTable, "client_id", "fkey") + + " FOREIGN KEY(app_id, client_id)" + + " REFERENCES " + Config.getConfig(start).getOAuthClientsTable() + "(app_id, client_id) ON DELETE CASCADE" + ");"; // @formatter:on } @@ -123,14 +123,11 @@ public static String getQueryToCreateOAuthLogoutChallengesTable(Start start) { + "session_handle VARCHAR(128)," + "state VARCHAR(128)," + "time_created BIGINT NOT NULL," - + "CONSTRAINT " + Utils.getConstraintName(schema, oAuth2LogoutChallengesTable, "app_id_challenge", "pkey") + + "CONSTRAINT " + Utils.getConstraintName(schema, oAuth2LogoutChallengesTable, null, "pkey") + " PRIMARY KEY (app_id, challenge)," - + "CONSTRAINT " + Utils.getConstraintName(schema, oAuth2LogoutChallengesTable, "app_id_client_id", "fkey") + + "CONSTRAINT " + Utils.getConstraintName(schema, oAuth2LogoutChallengesTable, "client_id", "fkey") + " FOREIGN KEY(app_id, client_id)" - + " REFERENCES " + Config.getConfig(start).getOAuthClientsTable() + "(app_id, client_id) ON DELETE CASCADE," - + "CONSTRAINT " + Utils.getConstraintName(schema, oAuth2LogoutChallengesTable, "app_id", "fkey") - + " FOREIGN KEY(app_id)" - + " REFERENCES " + Config.getConfig(start).getAppsTable() + "(app_id) ON DELETE CASCADE" + + " REFERENCES " + Config.getConfig(start).getOAuthClientsTable() + "(app_id, client_id) ON DELETE CASCADE" + ");"; // @formatter:on } @@ -301,7 +298,7 @@ public static int countTotalNumberOfOAuthM2MTokensCreatedSince(Start start, AppI public static void addOAuthM2MTokenForStats(Start start, AppIdentifier appIdentifier, String clientId, long iat, long exp) throws SQLException, StorageQueryException { String QUERY = "INSERT INTO " + Config.getConfig(start).getOAuthM2MTokensTable() + - " (app_id, client_id, iat, exp) VALUES (?, ?, ?, ?)"; + " (app_id, client_id, iat, exp) VALUES (?, ?, ?, ?) ON CONFLICT DO NOTHING"; update(start, QUERY, pst -> { pst.setString(1, appIdentifier.getAppId()); pst.setString(2, clientId);