Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [9.4.2]

- Fixes concurrency issue with oauth refresh token

## [9.4.1]

- Fixes env var reading with specific types
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ plugins {
id 'java-library'
}

version = "9.4.1"
version = "9.4.2"

repositories {
mavenCentral()
Expand Down
2 changes: 1 addition & 1 deletion pluginInterfaceSupported.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"_comment": "contains a list of plugin interfaces branch names that this core supports",
"versions": [
"8.4"
"8.5"
]
}
110 changes: 49 additions & 61 deletions src/main/java/io/supertokens/storage/postgresql/Start.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,39 +17,11 @@

package io.supertokens.storage.postgresql;

import java.lang.reflect.Field;
import java.sql.BatchUpdateException;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLTransactionRollbackException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.Nonnull;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import org.postgresql.util.PSQLException;
import org.postgresql.util.ServerErrorMessage;
import org.slf4j.LoggerFactory;

import ch.qos.logback.classic.Logger;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import com.zaxxer.hikari.pool.HikariPool;

import ch.qos.logback.classic.Logger;
import io.supertokens.pluginInterface.ActiveUsersSQLStorage;
import io.supertokens.pluginInterface.ActiveUsersStorage;
import io.supertokens.pluginInterface.ConfigFieldInfo;
import io.supertokens.pluginInterface.KeyValueInfo;
import io.supertokens.pluginInterface.LOG_LEVEL;
import io.supertokens.pluginInterface.RECIPE_ID;
import io.supertokens.pluginInterface.STORAGE_TYPE;
import io.supertokens.pluginInterface.Storage;
import io.supertokens.pluginInterface.*;
import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo;
import io.supertokens.pluginInterface.authRecipe.LoginMethod;
import io.supertokens.pluginInterface.authRecipe.sqlStorage.AuthRecipeSQLStorage;
Expand Down Expand Up @@ -96,16 +68,13 @@
import io.supertokens.pluginInterface.oauth.OAuthStorage;
import io.supertokens.pluginInterface.oauth.exception.DuplicateOAuthLogoutChallengeException;
import io.supertokens.pluginInterface.oauth.exception.OAuthClientNotFoundException;
import io.supertokens.pluginInterface.oauth.sqlStorage.OAuthSQLStorage;
import io.supertokens.pluginInterface.opentelemetry.OtelProvider;
import io.supertokens.pluginInterface.opentelemetry.WithinOtelSpan;
import io.supertokens.pluginInterface.passwordless.PasswordlessCode;
import io.supertokens.pluginInterface.passwordless.PasswordlessDevice;
import io.supertokens.pluginInterface.passwordless.PasswordlessImportUser;
import io.supertokens.pluginInterface.passwordless.exception.DuplicateCodeIdException;
import io.supertokens.pluginInterface.passwordless.exception.DuplicateDeviceIdHashException;
import io.supertokens.pluginInterface.passwordless.exception.DuplicateLinkCodeHashException;
import io.supertokens.pluginInterface.passwordless.exception.DuplicatePhoneNumberException;
import io.supertokens.pluginInterface.passwordless.exception.UnknownDeviceIdHash;
import io.supertokens.pluginInterface.passwordless.exception.*;
import io.supertokens.pluginInterface.passwordless.sqlStorage.PasswordlessSQLStorage;
import io.supertokens.pluginInterface.saml.SAMLClaimsInfo;
import io.supertokens.pluginInterface.saml.SAMLClient;
Expand Down Expand Up @@ -140,43 +109,37 @@
import io.supertokens.pluginInterface.webauthn.AccountRecoveryTokenInfo;
import io.supertokens.pluginInterface.webauthn.WebAuthNOptions;
import io.supertokens.pluginInterface.webauthn.WebAuthNStoredCredential;
import io.supertokens.pluginInterface.webauthn.exceptions.DuplicateOptionsIdException;
import io.supertokens.pluginInterface.webauthn.exceptions.DuplicateRecoverAccountTokenException;
import io.supertokens.pluginInterface.webauthn.exceptions.DuplicateUserEmailException;
import io.supertokens.pluginInterface.webauthn.exceptions.WebauthNCredentialNotExistsException;
import io.supertokens.pluginInterface.webauthn.exceptions.WebauthNOptionsNotExistsException;
import io.supertokens.pluginInterface.webauthn.exceptions.*;
import io.supertokens.pluginInterface.webauthn.slqStorage.WebAuthNSQLStorage;
import static io.supertokens.storage.postgresql.QueryExecutorTemplate.execute;
import io.supertokens.storage.postgresql.annotations.EnvName;
import io.supertokens.storage.postgresql.config.Config;
import io.supertokens.storage.postgresql.config.PostgreSQLConfig;
import io.supertokens.storage.postgresql.output.Logging;
import io.supertokens.storage.postgresql.queries.ActiveUsersQueries;
import io.supertokens.storage.postgresql.queries.BulkImportQueries;
import io.supertokens.storage.postgresql.queries.DashboardQueries;
import io.supertokens.storage.postgresql.queries.EmailPasswordQueries;
import io.supertokens.storage.postgresql.queries.EmailVerificationQueries;
import io.supertokens.storage.postgresql.queries.GeneralQueries;
import io.supertokens.storage.postgresql.queries.JWTSigningQueries;
import io.supertokens.storage.postgresql.queries.MultitenancyQueries;
import io.supertokens.storage.postgresql.queries.OAuthQueries;
import io.supertokens.storage.postgresql.queries.PasswordlessQueries;
import io.supertokens.storage.postgresql.queries.SAMLQueries;
import io.supertokens.storage.postgresql.queries.SessionQueries;
import io.supertokens.storage.postgresql.queries.TOTPQueries;
import io.supertokens.storage.postgresql.queries.ThirdPartyQueries;
import io.supertokens.storage.postgresql.queries.UserIdMappingQueries;
import io.supertokens.storage.postgresql.queries.UserMetadataQueries;
import io.supertokens.storage.postgresql.queries.UserRolesQueries;
import io.supertokens.storage.postgresql.queries.WebAuthNQueries;
import io.supertokens.storage.postgresql.queries.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import org.postgresql.util.PSQLException;
import org.postgresql.util.ServerErrorMessage;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import java.lang.reflect.Field;
import java.sql.BatchUpdateException;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLTransactionRollbackException;
import java.util.*;

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

@WithinOtelSpan
public class Start
implements SessionSQLStorage, EmailPasswordSQLStorage, EmailVerificationSQLStorage, ThirdPartySQLStorage,
JWTRecipeSQLStorage, PasswordlessSQLStorage, UserMetadataSQLStorage, UserRolesSQLStorage, UserIdMappingStorage,
UserIdMappingSQLStorage, MultitenancyStorage, MultitenancySQLStorage, DashboardSQLStorage, TOTPSQLStorage,
ActiveUsersStorage, ActiveUsersSQLStorage, AuthRecipeSQLStorage, OAuthStorage, BulkImportSQLStorage,
WebAuthNSQLStorage, SAMLStorage {
ActiveUsersStorage, ActiveUsersSQLStorage, AuthRecipeSQLStorage, OAuthStorage, OAuthSQLStorage,
BulkImportSQLStorage, WebAuthNSQLStorage, SAMLStorage {

// 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.
Expand Down Expand Up @@ -4053,6 +4016,31 @@ public String getRefreshTokenMapping(AppIdentifier appIdentifier, String externa
}
}

@Override
public String getRefreshTokenMappingForUpdate_Transaction(AppIdentifier appIdentifier, TransactionConnection con,
String externalRefreshToken)
throws StorageQueryException {
try {
return OAuthQueries.getRefreshTokenMappingForUpdate(this, (Connection) con.getConnection(),
appIdentifier, externalRefreshToken);
} catch (SQLException e) {
throw new StorageQueryException(e);
}
}

@Override
public void updateOAuthSessionInternal_Transaction(AppIdentifier appIdentifier, TransactionConnection con,
String gid, String newInternalRefreshToken,
String sessionHandle, String jti, long exp)
throws StorageQueryException {
try {
OAuthQueries.updateOAuthSessionInternal(this, (Connection) con.getConnection(),
appIdentifier, gid, newInternalRefreshToken, sessionHandle, jti, exp);
} catch (SQLException e) {
throw new StorageQueryException(e);
}
}

@Override
public void deleteExpiredOAuthSessions(long exp) throws StorageQueryException {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -674,7 +674,7 @@ private void validateAndNormalise(boolean skipValidation) throws InvalidConfigEx

{ // postgresql_host
if (postgresql_host == null) {
postgresql_host = "localhost";
postgresql_host = System.getProperty("ST_POSTGRESQL_PLUGIN_SERVER_HOST", "localhost");
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import io.supertokens.storage.postgresql.utils.Utils;
import org.jetbrains.annotations.NotNull;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
Expand Down Expand Up @@ -428,6 +429,55 @@ public static String getRefreshTokenMapping(Start start, AppIdentifier appIdenti
});
}

/**
* SELECT FOR UPDATE variant — must be called inside an open transaction.
* Locks the oauth_sessions row for the given externalRefreshToken so that no
* other DB client can read or write it until the transaction is committed or
* rolled back.
*/
public static String getRefreshTokenMappingForUpdate(Start start, Connection con,
AppIdentifier appIdentifier,
String externalRefreshToken)
throws SQLException, StorageQueryException {
String QUERY = "SELECT internal_refresh_token FROM " + Config.getConfig(start).getOAuthSessionsTable() +
" WHERE app_id = ? AND external_refresh_token = ? FOR UPDATE";
return execute(con, QUERY, pst -> {
pst.setString(1, appIdentifier.getAppId());
pst.setString(2, externalRefreshToken);
}, result -> {
if (result.next()) {
return result.getString("internal_refresh_token");
}
return null;
});
}

/**
* Updates the internal token and metadata for a non-rotating refresh.
* Must be called inside the same transaction that previously called
* {@link #getRefreshTokenMappingForUpdate}.
*/
public static void updateOAuthSessionInternal(Start start, Connection con,
AppIdentifier appIdentifier,
String gid,
String newInternalRefreshToken,
String sessionHandle,
String jti,
long exp)
throws SQLException, StorageQueryException {
String QUERY = "UPDATE " + Config.getConfig(start).getOAuthSessionsTable() +
" SET internal_refresh_token = ?, session_handle = ?, jti = CONCAT(jti, ?), exp = ?" +
" WHERE gid = ? AND app_id = ?";
update(con, QUERY, pst -> {
pst.setString(1, newInternalRefreshToken);
pst.setString(2, sessionHandle);
pst.setString(3, jti + ",");
pst.setLong(4, exp);
pst.setString(5, gid);
pst.setString(6, appIdentifier.getAppId());
});
}

public static void deleteExpiredOAuthSessions(Start start, long exp) throws SQLException, StorageQueryException {
// delete expired M2M tokens
String QUERY = "DELETE FROM " + Config.getConfig(start).getOAuthSessionsTable() +
Expand Down
Loading