Skip to content

Commit f474b2c

Browse files
authored
feat: OAuth provider support (#228)
* 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
1 parent e40602c commit f474b2c

File tree

7 files changed

+642
-4
lines changed

7 files changed

+642
-4
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

88
## [Unreleased]
99

10+
## [7.2.0] - 2024-10-03
11+
12+
- Compatible with plugin interface version 6.3
13+
- Adds support for OAuthStorage
14+
1015
## [7.1.3] - 2024-09-04
1116

1217
- Adds index on `last_active_time` for `user_last_active` table to improve the performance of MAU computation.

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ plugins {
22
id 'java-library'
33
}
44

5-
version = "7.1.3"
5+
version = "7.2.0"
66

77
repositories {
88
mavenCentral()

pluginInterfaceSupported.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"_comment": "contains a list of plugin interfaces branch names that this core supports",
33
"versions": [
4-
"6.2"
4+
"6.3"
55
]
66
}

src/main/java/io/supertokens/storage/postgresql/Start.java

Lines changed: 196 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@
5454
import io.supertokens.pluginInterface.multitenancy.exceptions.DuplicateThirdPartyIdException;
5555
import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException;
5656
import io.supertokens.pluginInterface.multitenancy.sqlStorage.MultitenancySQLStorage;
57+
import io.supertokens.pluginInterface.oauth.OAuthLogoutChallenge;
58+
import io.supertokens.pluginInterface.oauth.OAuthRevokeTargetType;
59+
import io.supertokens.pluginInterface.oauth.OAuthStorage;
60+
import io.supertokens.pluginInterface.oauth.exception.DuplicateOAuthLogoutChallengeException;
61+
import io.supertokens.pluginInterface.oauth.exception.OAuthClientNotFoundException;
5762
import io.supertokens.pluginInterface.passwordless.PasswordlessCode;
5863
import io.supertokens.pluginInterface.passwordless.PasswordlessDevice;
5964
import io.supertokens.pluginInterface.passwordless.exception.*;
@@ -106,7 +111,7 @@ public class Start
106111
implements SessionSQLStorage, EmailPasswordSQLStorage, EmailVerificationSQLStorage, ThirdPartySQLStorage,
107112
JWTRecipeSQLStorage, PasswordlessSQLStorage, UserMetadataSQLStorage, UserRolesSQLStorage, UserIdMappingStorage,
108113
UserIdMappingSQLStorage, MultitenancyStorage, MultitenancySQLStorage, DashboardSQLStorage, TOTPSQLStorage,
109-
ActiveUsersStorage, ActiveUsersSQLStorage, AuthRecipeSQLStorage {
114+
ActiveUsersStorage, ActiveUsersSQLStorage, AuthRecipeSQLStorage, OAuthStorage {
110115

111116
// these configs are protected from being modified / viewed by the dev using the SuperTokens
112117
// SaaS. If the core is not running in SuperTokens SaaS, this array has no effect.
@@ -121,7 +126,6 @@ public class Start
121126
private ResourceDistributor resourceDistributor = new ResourceDistributor();
122127
private String processId;
123128
private HikariLoggingAppender appender;
124-
private static final String APP_ID_KEY_NAME = "app_id";
125129
private static final String ACCESS_TOKEN_SIGNING_KEY_NAME = "access_token_signing_key";
126130
private static final String REFRESH_TOKEN_KEY_NAME = "refresh_token_key";
127131
public static boolean isTesting = false;
@@ -864,6 +868,8 @@ public void addInfoToNonAuthRecipesBasedOnUserId(TenantIdentifier tenantIdentifi
864868
}
865869
} else if (className.equals(JWTRecipeStorage.class.getName())) {
866870
/* Since JWT recipe tables do not store userId we do not add any data to them */
871+
} else if (className.equals(OAuthStorage.class.getName())) {
872+
/* Since OAuth recipe tables do not store userId we do not add any data to them */
867873
} else if (className.equals(ActiveUsersStorage.class.getName())) {
868874
try {
869875
ActiveUsersQueries.updateUserLastActive(this, tenantIdentifier.toAppIdentifier(), userId);
@@ -3089,6 +3095,194 @@ public int countUsersThatHaveMoreThanOneLoginMethodOrTOTPEnabledAndActiveSince(A
30893095
}
30903096
}
30913097

3098+
@Override
3099+
public boolean doesOAuthClientIdExist(AppIdentifier appIdentifier, String clientId)
3100+
throws StorageQueryException {
3101+
try {
3102+
return OAuthQueries.doesOAuthClientIdExist(this, clientId, appIdentifier);
3103+
} catch (SQLException e) {
3104+
throw new StorageQueryException(e);
3105+
}
3106+
}
3107+
3108+
@Override
3109+
public void addOrUpdateOauthClient(AppIdentifier appIdentifier, String clientId, boolean isClientCredentialsOnly)
3110+
throws StorageQueryException, TenantOrAppNotFoundException {
3111+
try {
3112+
OAuthQueries.addOrUpdateOauthClient(this, appIdentifier, clientId, isClientCredentialsOnly);
3113+
} catch (SQLException e) {
3114+
PostgreSQLConfig config = Config.getConfig(this);
3115+
if (e instanceof PSQLException) {
3116+
ServerErrorMessage serverMessage = ((PSQLException) e).getServerErrorMessage();
3117+
3118+
if (isForeignKeyConstraintError(serverMessage, config.getOAuthClientsTable(), "app_id")) {
3119+
throw new TenantOrAppNotFoundException(appIdentifier);
3120+
}
3121+
}
3122+
throw new StorageQueryException(e);
3123+
}
3124+
}
3125+
3126+
@Override
3127+
public boolean deleteOAuthClient(AppIdentifier appIdentifier, String clientId) throws StorageQueryException {
3128+
try {
3129+
return OAuthQueries.deleteOAuthClient(this, clientId, appIdentifier);
3130+
} catch (SQLException e) {
3131+
throw new StorageQueryException(e);
3132+
}
3133+
}
3134+
3135+
@Override
3136+
public List<String> listOAuthClients(AppIdentifier appIdentifier) throws StorageQueryException {
3137+
try {
3138+
return OAuthQueries.listOAuthClients(this, appIdentifier);
3139+
} catch (SQLException e) {
3140+
throw new StorageQueryException(e);
3141+
}
3142+
}
3143+
3144+
@Override
3145+
public void revokeOAuthTokensBasedOnTargetFields(AppIdentifier appIdentifier, OAuthRevokeTargetType targetType, String targetValue, long exp)
3146+
throws StorageQueryException, TenantOrAppNotFoundException {
3147+
try {
3148+
OAuthQueries.revokeOAuthTokensBasedOnTargetFields(this, appIdentifier, targetType, targetValue, exp);
3149+
} catch (SQLException e) {
3150+
PostgreSQLConfig config = Config.getConfig(this);
3151+
if (e instanceof PSQLException) {
3152+
ServerErrorMessage serverMessage = ((PSQLException) e).getServerErrorMessage();
3153+
3154+
if (isForeignKeyConstraintError(serverMessage, config.getOAuthRevokeTable(), "app_id")) {
3155+
throw new TenantOrAppNotFoundException(appIdentifier);
3156+
}
3157+
}
3158+
throw new StorageQueryException(e);
3159+
}
3160+
3161+
}
3162+
3163+
@Override
3164+
public boolean isOAuthTokenRevokedBasedOnTargetFields(AppIdentifier appIdentifier, OAuthRevokeTargetType[] targetTypes, String[] targetValues, long issuedAt)
3165+
throws StorageQueryException {
3166+
try {
3167+
return OAuthQueries.isOAuthTokenRevokedBasedOnTargetFields(this, appIdentifier, targetTypes, targetValues, issuedAt);
3168+
} catch (SQLException e) {
3169+
throw new StorageQueryException(e);
3170+
}
3171+
}
3172+
3173+
@Override
3174+
public void addOAuthM2MTokenForStats(AppIdentifier appIdentifier, String clientId, long iat, long exp)
3175+
throws StorageQueryException, OAuthClientNotFoundException {
3176+
try {
3177+
OAuthQueries.addOAuthM2MTokenForStats(this, appIdentifier, clientId, iat, exp);
3178+
} catch (SQLException e) {
3179+
PostgreSQLConfig config = Config.getConfig(this);
3180+
if (e instanceof PSQLException) {
3181+
ServerErrorMessage serverMessage = ((PSQLException) e).getServerErrorMessage();
3182+
3183+
if (isForeignKeyConstraintError(serverMessage, config.getOAuthM2MTokensTable(), "client_id")) {
3184+
throw new OAuthClientNotFoundException();
3185+
}
3186+
}
3187+
throw new StorageQueryException(e);
3188+
}
3189+
}
3190+
3191+
@Override
3192+
public void cleanUpExpiredAndRevokedOAuthTokensList() throws StorageQueryException {
3193+
try {
3194+
OAuthQueries.cleanUpExpiredAndRevokedOAuthTokensList(this);
3195+
} catch (SQLException e) {
3196+
throw new StorageQueryException(e);
3197+
}
3198+
}
3199+
3200+
@Override
3201+
public void addOAuthLogoutChallenge(AppIdentifier appIdentifier, String challenge, String clientId,
3202+
String postLogoutRedirectionUri, String sessionHandle, String state, long timeCreated)
3203+
throws StorageQueryException, DuplicateOAuthLogoutChallengeException, OAuthClientNotFoundException {
3204+
try {
3205+
OAuthQueries.addOAuthLogoutChallenge(this, appIdentifier, challenge, clientId, postLogoutRedirectionUri, sessionHandle, state, timeCreated);
3206+
} catch (SQLException e) {
3207+
PostgreSQLConfig config = Config.getConfig(this);
3208+
if (e instanceof PSQLException) {
3209+
ServerErrorMessage serverMessage = ((PSQLException) e).getServerErrorMessage();
3210+
3211+
if (isPrimaryKeyError(serverMessage, config.getOAuthLogoutChallengesTable())) {
3212+
throw new DuplicateOAuthLogoutChallengeException();
3213+
} else if (isForeignKeyConstraintError(serverMessage, config.getOAuthLogoutChallengesTable(), "client_id")) {
3214+
throw new OAuthClientNotFoundException();
3215+
}
3216+
}
3217+
throw new StorageQueryException(e);
3218+
}
3219+
}
3220+
3221+
@Override
3222+
public OAuthLogoutChallenge getOAuthLogoutChallenge(AppIdentifier appIdentifier, String challenge) throws StorageQueryException {
3223+
try {
3224+
return OAuthQueries.getOAuthLogoutChallenge(this, appIdentifier, challenge);
3225+
} catch (SQLException e) {
3226+
throw new StorageQueryException(e);
3227+
}
3228+
}
3229+
3230+
@Override
3231+
public void deleteOAuthLogoutChallenge(AppIdentifier appIdentifier, String challenge) throws StorageQueryException {
3232+
try {
3233+
OAuthQueries.deleteOAuthLogoutChallenge(this, appIdentifier, challenge);
3234+
} catch (SQLException e) {
3235+
throw new StorageQueryException(e);
3236+
}
3237+
}
3238+
3239+
@Override
3240+
public void deleteOAuthLogoutChallengesBefore(long time) throws StorageQueryException {
3241+
try {
3242+
OAuthQueries.deleteOAuthLogoutChallengesBefore(this, time);
3243+
} catch (SQLException e) {
3244+
throw new StorageQueryException(e);
3245+
}
3246+
}
3247+
3248+
@Override
3249+
public int countTotalNumberOfOAuthClients(AppIdentifier appIdentifier) throws StorageQueryException {
3250+
try {
3251+
return OAuthQueries.countTotalNumberOfClients(this, appIdentifier, false);
3252+
} catch (SQLException e) {
3253+
throw new StorageQueryException(e);
3254+
}
3255+
}
3256+
3257+
@Override
3258+
public int countTotalNumberOfClientCredentialsOnlyOAuthClients(AppIdentifier appIdentifier)
3259+
throws StorageQueryException {
3260+
try {
3261+
return OAuthQueries.countTotalNumberOfClients(this, appIdentifier, true);
3262+
} catch (SQLException e) {
3263+
throw new StorageQueryException(e);
3264+
}
3265+
}
3266+
3267+
@Override
3268+
public int countTotalNumberOfOAuthM2MTokensCreatedSince(AppIdentifier appIdentifier, long since)
3269+
throws StorageQueryException {
3270+
try {
3271+
return OAuthQueries.countTotalNumberOfOAuthM2MTokensCreatedSince(this, appIdentifier, since);
3272+
} catch (SQLException e) {
3273+
throw new StorageQueryException(e);
3274+
}
3275+
}
3276+
3277+
@Override
3278+
public int countTotalNumberOfOAuthM2MTokensAlive(AppIdentifier appIdentifier) throws StorageQueryException {
3279+
try {
3280+
return OAuthQueries.countTotalNumberOfOAuthM2MTokensAlive(this, appIdentifier);
3281+
} catch (SQLException e) {
3282+
throw new StorageQueryException(e);
3283+
}
3284+
}
3285+
30923286
@TestOnly
30933287
public int getDbActivityCount(String dbname) throws SQLException, StorageQueryException {
30943288
String QUERY = "SELECT COUNT(*) as c FROM pg_stat_activity WHERE datname = ?;";

src/main/java/io/supertokens/storage/postgresql/config/PostgreSQLConfig.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,22 @@ public String getDashboardSessionsTable() {
439439
return addSchemaAndPrefixToTableName("dashboard_user_sessions");
440440
}
441441

442+
public String getOAuthClientsTable() {
443+
return addSchemaAndPrefixToTableName("oauth_clients");
444+
}
445+
446+
public String getOAuthRevokeTable() {
447+
return addSchemaAndPrefixToTableName("oauth_revoke");
448+
}
449+
450+
public String getOAuthM2MTokensTable() {
451+
return addSchemaAndPrefixToTableName("oauth_m2m_tokens");
452+
}
453+
454+
public String getOAuthLogoutChallengesTable() {
455+
return addSchemaAndPrefixToTableName("oauth_logout_challenges");
456+
}
457+
442458
public String getTotpUsersTable() {
443459
return addSchemaAndPrefixToTableName("totp_users");
444460
}

src/main/java/io/supertokens/storage/postgresql/queries/GeneralQueries.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,37 @@ public static void createTablesIfNotExists(Start start, Connection con) throws S
554554
update(con, TOTPQueries.getQueryToCreateTenantIdIndexForUsedCodesTable(start), NO_OP_SETTER);
555555
}
556556

557+
if (!doesTableExists(start, con, Config.getConfig(start).getOAuthClientsTable())) {
558+
getInstance(start).addState(CREATING_NEW_TABLE, null);
559+
update(start, OAuthQueries.getQueryToCreateOAuthClientTable(start), NO_OP_SETTER);
560+
}
561+
562+
if (!doesTableExists(start, con, Config.getConfig(start).getOAuthRevokeTable())) {
563+
getInstance(start).addState(CREATING_NEW_TABLE, null);
564+
update(start, OAuthQueries.getQueryToCreateOAuthRevokeTable(start), NO_OP_SETTER);
565+
566+
// index
567+
update(con, OAuthQueries.getQueryToCreateOAuthRevokeTimestampIndex(start), NO_OP_SETTER);
568+
update(con, OAuthQueries.getQueryToCreateOAuthRevokeExpIndex(start), NO_OP_SETTER);
569+
}
570+
571+
if (!doesTableExists(start, con, Config.getConfig(start).getOAuthM2MTokensTable())) {
572+
getInstance(start).addState(CREATING_NEW_TABLE, null);
573+
update(start, OAuthQueries.getQueryToCreateOAuthM2MTokensTable(start), NO_OP_SETTER);
574+
575+
// index
576+
update(con, OAuthQueries.getQueryToCreateOAuthM2MTokenIatIndex(start), NO_OP_SETTER);
577+
update(con, OAuthQueries.getQueryToCreateOAuthM2MTokenExpIndex(start), NO_OP_SETTER);
578+
}
579+
580+
if (!doesTableExists(start, con, Config.getConfig(start).getOAuthLogoutChallengesTable())) {
581+
getInstance(start).addState(CREATING_NEW_TABLE, null);
582+
update(con, OAuthQueries.getQueryToCreateOAuthLogoutChallengesTable(start), NO_OP_SETTER);
583+
584+
// index
585+
update(con, OAuthQueries.getQueryToCreateOAuthLogoutChallengesTimeCreatedIndex(start), NO_OP_SETTER);
586+
}
587+
557588
} catch (Exception e) {
558589
if (e.getMessage().contains("schema") && e.getMessage().contains("does not exist")
559590
&& numberOfRetries < 1) {
@@ -624,6 +655,10 @@ public static void deleteAllTables(Start start) throws SQLException, StorageQuer
624655
+ getConfig(start).getUserRolesTable() + ","
625656
+ getConfig(start).getDashboardUsersTable() + ","
626657
+ getConfig(start).getDashboardSessionsTable() + ","
658+
+ getConfig(start).getOAuthClientsTable() + ","
659+
+ getConfig(start).getOAuthRevokeTable() + ","
660+
+ getConfig(start).getOAuthM2MTokensTable() + ","
661+
+ getConfig(start).getOAuthLogoutChallengesTable() + ","
627662
+ getConfig(start).getTotpUsedCodesTable() + ","
628663
+ getConfig(start).getTotpUserDevicesTable() + ","
629664
+ getConfig(start).getTotpUsersTable();

0 commit comments

Comments
 (0)