Skip to content

Commit

Permalink
feat: OAuth provider support (#228)
Browse files Browse the repository at this point in the history
* fix: oauth clients table

* fix: oauth db changes

* fix: listClientsForApp

* fix: revoke (#226)

* fix: revoke

* fix: pr comment

* fix: interface

* fix: update

* fix: oauth stats queries

* fix: revoke and cleanup

* fix: stats

* fix: logout queries (#229)

* fix: update queries

* fix: versions

* revert

* fix: changelog

* fix: changelog

* fix: constraints
  • Loading branch information
sattvikc authored Oct 4, 2024
1 parent e40602c commit f474b2c
Show file tree
Hide file tree
Showing 7 changed files with 642 additions and 4 deletions.
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

0 comments on commit f474b2c

Please sign in to comment.