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 #129

Merged
merged 3 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"
]
}
197 changes: 196 additions & 1 deletion src/main/java/io/supertokens/storage/mysql/Start.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,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 @@ -112,7 +117,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 Down Expand Up @@ -837,6 +842,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 @@ -3034,6 +3041,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) {
if (e instanceof SQLIntegrityConstraintViolationException) {
MySQLConfig config = Config.getConfig(this);
String serverMessage = e.getMessage();

if (isForeignKeyConstraintError(serverMessage, config.getAppsTable(), "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) {
if (e instanceof SQLIntegrityConstraintViolationException) {
MySQLConfig config = Config.getConfig(this);
String serverMessage = e.getMessage();

if (isForeignKeyConstraintError(serverMessage, config.getAppsTable(), "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) {
if (e instanceof SQLIntegrityConstraintViolationException) {
MySQLConfig config = Config.getConfig(this);
String serverMessage = e.getMessage();

if (isForeignKeyConstraintError(serverMessage, config.getOAuthClientsTable(), "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) {
if (e instanceof SQLIntegrityConstraintViolationException) {
MySQLConfig config = Config.getConfig(this);
String serverMessage = e.getMessage();
if (isPrimaryKeyError(serverMessage, config.getOAuthLogoutChallengesTable())) {
throw new DuplicateOAuthLogoutChallengeException();
}
else if (isForeignKeyConstraintError(serverMessage, config.getOAuthClientsTable(), "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);
}
}

public static boolean isEnabledForDeadlockTesting() {
return enableForDeadlockTesting;
}
Expand Down
16 changes: 16 additions & 0 deletions src/main/java/io/supertokens/storage/mysql/config/MySQLConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,22 @@ public String getTotpUsedCodesTable() {
return addPrefixToTableName("totp_used_codes");
}

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

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

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

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

private String addPrefixToTableName(String tableName) {
return mysql_table_names_prefix + tableName;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,37 @@ public static void createTablesIfNotExists(Start start, Connection con) throws S
// index:
update(con, TOTPQueries.getQueryToCreateUsedCodesExpiryTimeIndex(start), NO_OP_SETTER);
}

if (!doesTableExists(start, con, Config.getConfig(start).getOAuthClientsTable())) {
getInstance(start).addState(CREATING_NEW_TABLE, null);
update(con, OAuthQueries.getQueryToCreateOAuthClientTable(start), NO_OP_SETTER);
}

if (!doesTableExists(start, con, Config.getConfig(start).getOAuthRevokeTable())) {
getInstance(start).addState(CREATING_NEW_TABLE, null);
update(con, 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(con, 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);
}
}

@TestOnly
Expand Down
Loading
Loading