Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: OAuth provider support #228

Merged
merged 12 commits into from
Oct 4, 2024
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.3] - 2024-09-04

- Adds index on `last_active_time` for `user_last_active` table to improve the performance of MAU computation.
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 = "7.1.3"
version = "7.2.0"

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": [
"6.2"
"6.3"
]
}
198 changes: 196 additions & 2 deletions src/main/java/io/supertokens/storage/postgresql/Start.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@
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.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.*;
Expand Down Expand Up @@ -106,7 +111,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, 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.
Expand All @@ -121,7 +126,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;
Expand Down Expand Up @@ -864,6 +868,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);
Expand Down Expand Up @@ -3089,6 +3095,194 @@ public int countUsersThatHaveMoreThanOneLoginMethodOrTOTPEnabledAndActiveSince(A
}
}

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

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

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

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

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

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

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

}

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

@Override
public void addOAuthM2MTokenForStats(AppIdentifier appIdentifier, String clientId, long iat, long exp)
throws StorageQueryException, OAuthClientNotFoundException {
try {
OAuthQueries.addOAuthM2MTokenForStats(this, appIdentifier, clientId, iat, exp);
} catch (SQLException e) {
PostgreSQLConfig config = Config.getConfig(this);
if (e instanceof PSQLException) {
ServerErrorMessage serverMessage = ((PSQLException) e).getServerErrorMessage();

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

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

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

if (isPrimaryKeyError(serverMessage, config.getOAuthLogoutChallengesTable())) {
throw new DuplicateOAuthLogoutChallengeException();
} else if (isForeignKeyConstraintError(serverMessage, config.getOAuthLogoutChallengesTable(), "client_id")) {
throw new OAuthClientNotFoundException();
}
}
throw new StorageQueryException(e);
}
}

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

@Override
public void deleteOAuthLogoutChallenge(AppIdentifier appIdentifier, String challenge) throws StorageQueryException {
try {
OAuthQueries.deleteOAuthLogoutChallenge(this, appIdentifier, challenge);
} catch (SQLException e) {
throw new StorageQueryException(e);
}
}

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

@Override
public int countTotalNumberOfOAuthClients(AppIdentifier appIdentifier) throws StorageQueryException {
try {
return OAuthQueries.countTotalNumberOfClients(this, appIdentifier, false);
} catch (SQLException e) {
throw new StorageQueryException(e);
}
}

@Override
public int countTotalNumberOfClientCredentialsOnlyOAuthClients(AppIdentifier appIdentifier)
throws StorageQueryException {
try {
return OAuthQueries.countTotalNumberOfClients(this, appIdentifier, true);
} catch (SQLException e) {
throw new StorageQueryException(e);
}
}

@Override
public int countTotalNumberOfOAuthM2MTokensCreatedSince(AppIdentifier appIdentifier, long since)
throws StorageQueryException {
try {
return OAuthQueries.countTotalNumberOfOAuthM2MTokensCreatedSince(this, appIdentifier, since);
} catch (SQLException e) {
throw new StorageQueryException(e);
}
}

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

@TestOnly
public int getDbActivityCount(String dbname) throws SQLException, StorageQueryException {
String QUERY = "SELECT COUNT(*) as c FROM pg_stat_activity WHERE datname = ?;";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,22 @@ public String getDashboardSessionsTable() {
return addSchemaAndPrefixToTableName("dashboard_user_sessions");
}

public String getOAuthClientsTable() {
return addSchemaAndPrefixToTableName("oauth_clients");
}

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

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

public String getOAuthLogoutChallengesTable() {
return addSchemaAndPrefixToTableName("oauth_logout_challenges");
}

public String getTotpUsersTable() {
return addSchemaAndPrefixToTableName("totp_users");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,37 @@ 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).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);
}

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) {
Expand Down Expand Up @@ -624,6 +655,10 @@ public static void deleteAllTables(Start start) throws SQLException, StorageQuer
+ getConfig(start).getUserRolesTable() + ","
+ getConfig(start).getDashboardUsersTable() + ","
+ getConfig(start).getDashboardSessionsTable() + ","
+ getConfig(start).getOAuthClientsTable() + ","
+ getConfig(start).getOAuthRevokeTable() + ","
+ getConfig(start).getOAuthM2MTokensTable() + ","
+ getConfig(start).getOAuthLogoutChallengesTable() + ","
+ getConfig(start).getTotpUsedCodesTable() + ","
+ getConfig(start).getTotpUserDevicesTable() + ","
+ getConfig(start).getTotpUsersTable();
Expand Down
Loading
Loading