Skip to content

Commit 4e66593

Browse files
authored
feat: adds userIdMapping support (#49)
* feat: adds userIdMapping support * completes remaining checklist items * fixes CHANGELOG.md
1 parent f7feec4 commit 4e66593

File tree

7 files changed

+301
-6
lines changed

7 files changed

+301
-6
lines changed

CHANGELOG.md

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

88
## [Unreleased]
99

10+
## [1.18.0] - 2022-07-25
11+
12+
- Adds support for UserIdMapping recipe
13+
1014
## [1.17.0] - 2022-06-07
1115

1216
- Compatibility with plugin interface 2.15 - returns only non expired session handles for a user

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 = "1.17.0"
5+
version = "1.18.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-
"2.15"
4+
"2.16"
55
]
66
}

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

Lines changed: 102 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import io.supertokens.pluginInterface.KeyValueInfo;
2323
import io.supertokens.pluginInterface.RECIPE_ID;
2424
import io.supertokens.pluginInterface.STORAGE_TYPE;
25+
import io.supertokens.pluginInterface.authRecipe.AuthRecipeStorage;
2526
import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo;
2627
import io.supertokens.pluginInterface.emailpassword.PasswordResetTokenInfo;
2728
import io.supertokens.pluginInterface.emailpassword.UserInfo;
@@ -48,6 +49,10 @@
4849
import io.supertokens.pluginInterface.sqlStorage.TransactionConnection;
4950
import io.supertokens.pluginInterface.thirdparty.exception.DuplicateThirdPartyUserException;
5051
import io.supertokens.pluginInterface.thirdparty.sqlStorage.ThirdPartySQLStorage;
52+
import io.supertokens.pluginInterface.useridmapping.UserIdMapping;
53+
import io.supertokens.pluginInterface.useridmapping.UserIdMappingStorage;
54+
import io.supertokens.pluginInterface.useridmapping.exception.UnknownSuperTokensUserIdException;
55+
import io.supertokens.pluginInterface.useridmapping.exception.UserIdMappingAlreadyExistsException;
5156
import io.supertokens.pluginInterface.usermetadata.sqlStorage.UserMetadataSQLStorage;
5257
import io.supertokens.pluginInterface.userroles.exception.DuplicateUserRoleMappingException;
5358
import io.supertokens.pluginInterface.userroles.exception.UnknownRoleException;
@@ -69,8 +74,9 @@
6974
import java.sql.SQLTransactionRollbackException;
7075
import java.util.List;
7176

72-
public class Start implements SessionSQLStorage, EmailPasswordSQLStorage, EmailVerificationSQLStorage,
73-
ThirdPartySQLStorage, JWTRecipeSQLStorage, PasswordlessSQLStorage, UserMetadataSQLStorage, UserRolesSQLStorage {
77+
public class Start
78+
implements SessionSQLStorage, EmailPasswordSQLStorage, EmailVerificationSQLStorage, ThirdPartySQLStorage,
79+
JWTRecipeSQLStorage, PasswordlessSQLStorage, UserMetadataSQLStorage, UserRolesSQLStorage, UserIdMappingStorage {
7480

7581
private static final Object appenderLock = new Object();
7682
public static boolean silent = false;
@@ -1026,6 +1032,15 @@ public AuthRecipeUserInfo[] getUsers(@NotNull Integer limit, @NotNull String tim
10261032
}
10271033
}
10281034

1035+
@Override
1036+
public boolean doesUserIdExist(String userId) throws StorageQueryException {
1037+
try {
1038+
return GeneralQueries.doesUserIdExist(this, userId);
1039+
} catch (SQLException e) {
1040+
throw new StorageQueryException(e);
1041+
}
1042+
}
1043+
10291044
@Override
10301045
public List<JWTSigningKeyInfo> getJWTSigningKeys_Transaction(TransactionConnection con)
10311046
throws StorageQueryException {
@@ -1617,4 +1632,89 @@ public boolean doesRoleExist_Transaction(TransactionConnection con, String role)
16171632
}
16181633
}
16191634

1635+
@Override
1636+
public void createUserIdMapping(String superTokensUserId, String externalUserId,
1637+
@Nullable String externalUserIdInfo)
1638+
throws StorageQueryException, UnknownSuperTokensUserIdException, UserIdMappingAlreadyExistsException {
1639+
try {
1640+
UserIdMappingQueries.createUserIdMapping(this, superTokensUserId, externalUserId, externalUserIdInfo);
1641+
} catch (SQLException e) {
1642+
if (e instanceof PSQLException) {
1643+
PostgreSQLConfig config = Config.getConfig(this);
1644+
ServerErrorMessage serverErrorMessage = ((PSQLException) e).getServerErrorMessage();
1645+
if (isForeignKeyConstraintError(serverErrorMessage, config.getUserIdMappingTable(),
1646+
"supertokens_user_id")) {
1647+
throw new UnknownSuperTokensUserIdException();
1648+
}
1649+
1650+
if (isPrimaryKeyError(serverErrorMessage, config.getUserIdMappingTable())) {
1651+
throw new UserIdMappingAlreadyExistsException(true, true);
1652+
}
1653+
1654+
if (isUniqueConstraintError(serverErrorMessage, config.getUserIdMappingTable(),
1655+
"supertokens_user_id")) {
1656+
throw new UserIdMappingAlreadyExistsException(true, false);
1657+
}
1658+
1659+
if (isUniqueConstraintError(serverErrorMessage, config.getUserIdMappingTable(), "external_user_id")) {
1660+
throw new UserIdMappingAlreadyExistsException(false, true);
1661+
}
1662+
}
1663+
throw new StorageQueryException(e);
1664+
}
1665+
1666+
}
1667+
1668+
@Override
1669+
public boolean deleteUserIdMapping(String userId, boolean isSuperTokensUserId) throws StorageQueryException {
1670+
try {
1671+
if (isSuperTokensUserId) {
1672+
return UserIdMappingQueries.deleteUserIdMappingWithSuperTokensUserId(this, userId);
1673+
}
1674+
1675+
return UserIdMappingQueries.deleteUserIdMappingWithExternalUserId(this, userId);
1676+
} catch (SQLException e) {
1677+
throw new StorageQueryException(e);
1678+
}
1679+
}
1680+
1681+
@Override
1682+
public UserIdMapping getUserIdMapping(String userId, boolean isSuperTokensUserId) throws StorageQueryException {
1683+
1684+
try {
1685+
if (isSuperTokensUserId) {
1686+
return UserIdMappingQueries.getuseraIdMappingWithSuperTokensUserId(this, userId);
1687+
}
1688+
1689+
return UserIdMappingQueries.getUserIdMappingWithExternalUserId(this, userId);
1690+
} catch (SQLException e) {
1691+
throw new StorageQueryException(e);
1692+
}
1693+
}
1694+
1695+
@Override
1696+
public UserIdMapping[] getUserIdMapping(String userId) throws StorageQueryException {
1697+
try {
1698+
return UserIdMappingQueries.getUserIdMappingWithEitherSuperTokensUserIdOrExternalUserId(this, userId);
1699+
} catch (SQLException e) {
1700+
throw new StorageQueryException(e);
1701+
}
1702+
}
1703+
1704+
@Override
1705+
public boolean updateOrDeleteExternalUserIdInfo(String userId, boolean isSuperTokensUserId,
1706+
@Nullable String externalUserIdInfo) throws StorageQueryException {
1707+
1708+
try {
1709+
if (isSuperTokensUserId) {
1710+
return UserIdMappingQueries.updateOrDeleteExternalUserIdInfoWithSuperTokensUserId(this, userId,
1711+
externalUserIdInfo);
1712+
}
1713+
1714+
return UserIdMappingQueries.updateOrDeleteExternalUserIdInfoWithExternalUserId(this, userId,
1715+
externalUserIdInfo);
1716+
} catch (SQLException e) {
1717+
throw new StorageQueryException(e);
1718+
}
1719+
}
16201720
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,10 @@ public String getUserRolesTable() {
293293
return addSchemaAndPrefixToTableName("user_roles");
294294
}
295295

296+
public String getUserIdMappingTable() {
297+
return addSchemaAndPrefixToTableName("userid_mapping");
298+
}
299+
296300
private String addSchemaAndPrefixToTableName(String tableName) {
297301
String name = tableName;
298302
if (!postgresql_table_names_prefix.trim().equals("")) {

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

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,11 @@ public static void createTablesIfNotExists(Start start) throws SQLException, Sto
214214
update(start, UserRolesQueries.getQueryToCreateUserRolesRoleIndex(start), NO_OP_SETTER);
215215
}
216216

217+
if (!doesTableExists(start, Config.getConfig(start).getUserIdMappingTable())) {
218+
getInstance(start).addState(CREATING_NEW_TABLE, null);
219+
update(start, UserIdMappingQueries.getQueryToCreateUserIdMappingTable(start), NO_OP_SETTER);
220+
}
221+
217222
} catch (Exception e) {
218223
if (e.getMessage().contains("schema") && e.getMessage().contains("does not exist")
219224
&& numberOfRetries < 1) {
@@ -249,8 +254,9 @@ public static void deleteAllTables(Start start) throws SQLException, StorageQuer
249254

250255
{
251256
String DROP_QUERY = "DROP TABLE IF EXISTS " + getConfig(start).getKeyValueTable() + ","
252-
+ getConfig(start).getUsersTable() + "," + getConfig(start).getAccessTokenSigningKeysTable() + ","
253-
+ getConfig(start).getSessionInfoTable() + "," + getConfig(start).getEmailPasswordUsersTable() + ","
257+
+ getConfig(start).getUserIdMappingTable() + "," + getConfig(start).getUsersTable() + ","
258+
+ getConfig(start).getAccessTokenSigningKeysTable() + "," + getConfig(start).getSessionInfoTable()
259+
+ "," + getConfig(start).getEmailPasswordUsersTable() + ","
254260
+ getConfig(start).getPasswordResetTokensTable() + ","
255261
+ getConfig(start).getEmailVerificationTokensTable() + ","
256262
+ getConfig(start).getEmailVerificationTable() + "," + getConfig(start).getThirdPartyUsersTable()
@@ -341,6 +347,13 @@ public static long getUsersCount(Start start, RECIPE_ID[] includeRecipeIds)
341347
});
342348
}
343349

350+
public static boolean doesUserIdExist(Start start, String userId) throws SQLException, StorageQueryException {
351+
352+
String QUERY = "SELECT 1 FROM " + getConfig(start).getUsersTable() + " WHERE user_id = ?";
353+
return execute(start, QUERY, pst -> pst.setString(1, userId), ResultSet::next);
354+
355+
}
356+
344357
public static AuthRecipeUserInfo[] getUsers(Start start, @NotNull Integer limit, @NotNull String timeJoinedOrder,
345358
@Nullable RECIPE_ID[] includeRecipeIds, @Nullable String userId, @Nullable Long timeJoined)
346359
throws SQLException, StorageQueryException {
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
/*
2+
* Copyright (c) 2022, VRAI Labs and/or its affiliates. All rights reserved.
3+
*
4+
* This software is licensed under the Apache License, Version 2.0 (the
5+
* "License") as published by the Apache Software Foundation.
6+
*
7+
* You may not use this file except in compliance with the License. You may
8+
* obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations
14+
* under the License.
15+
*/
16+
17+
package io.supertokens.storage.postgresql.queries;
18+
19+
import io.supertokens.pluginInterface.RowMapper;
20+
import io.supertokens.pluginInterface.exceptions.StorageQueryException;
21+
import io.supertokens.pluginInterface.useridmapping.UserIdMapping;
22+
import io.supertokens.storage.postgresql.Start;
23+
import io.supertokens.storage.postgresql.config.Config;
24+
import io.supertokens.storage.postgresql.utils.Utils;
25+
26+
import javax.annotation.Nullable;
27+
import java.sql.ResultSet;
28+
import java.sql.SQLException;
29+
import java.util.ArrayList;
30+
31+
import static io.supertokens.storage.postgresql.QueryExecutorTemplate.execute;
32+
import static io.supertokens.storage.postgresql.QueryExecutorTemplate.update;
33+
34+
public class UserIdMappingQueries {
35+
36+
public static String getQueryToCreateUserIdMappingTable(Start start) {
37+
String schema = Config.getConfig(start).getTableSchema();
38+
String userIdMappingTable = Config.getConfig(start).getUserIdMappingTable();
39+
// @formatter:off
40+
return "CREATE TABLE IF NOT EXISTS " + userIdMappingTable + " ("
41+
+ "supertokens_user_id CHAR(36) NOT NULL "
42+
+ "CONSTRAINT " + Utils.getConstraintName(schema, userIdMappingTable, "supertokens_user_id", "key") + " UNIQUE,"
43+
+ "external_user_id VARCHAR(128) NOT NULL"
44+
+ " CONSTRAINT " + Utils.getConstraintName(schema, userIdMappingTable, "external_user_id", "key") + " UNIQUE,"
45+
+ "external_user_id_info TEXT,"
46+
+ " CONSTRAINT " + Utils.getConstraintName(schema, userIdMappingTable, null, "pkey") +
47+
" PRIMARY KEY(supertokens_user_id, external_user_id),"
48+
+ ("CONSTRAINT " + Utils.getConstraintName(schema, userIdMappingTable, "supertokens_user_id", "fkey") +
49+
" FOREIGN KEY (supertokens_user_id)"
50+
+ " REFERENCES " + Config.getConfig(start).getUsersTable() + "(user_id)"
51+
+ " ON DELETE CASCADE);");
52+
// @formatter:on
53+
}
54+
55+
public static void createUserIdMapping(Start start, String superTokensUserId, String externalUserId,
56+
String externalUserIdInfo) throws SQLException, StorageQueryException {
57+
String QUERY = "INSERT INTO " + Config.getConfig(start).getUserIdMappingTable()
58+
+ " (supertokens_user_id, external_user_id, external_user_id_info)" + " VALUES(?, ?, ?)";
59+
60+
update(start, QUERY, pst -> {
61+
pst.setString(1, superTokensUserId);
62+
pst.setString(2, externalUserId);
63+
pst.setString(3, externalUserIdInfo);
64+
});
65+
}
66+
67+
public static UserIdMapping getuseraIdMappingWithSuperTokensUserId(Start start, String userId)
68+
throws SQLException, StorageQueryException {
69+
String QUERY = "SELECT * FROM " + Config.getConfig(start).getUserIdMappingTable()
70+
+ " WHERE supertokens_user_id = ?";
71+
return execute(start, QUERY, pst -> pst.setString(1, userId), result -> {
72+
if (result.next()) {
73+
return UserIdMappingRowMapper.getInstance().mapOrThrow(result);
74+
}
75+
return null;
76+
});
77+
}
78+
79+
public static UserIdMapping getUserIdMappingWithExternalUserId(Start start, String userId)
80+
throws SQLException, StorageQueryException {
81+
String QUERY = "SELECT * FROM " + Config.getConfig(start).getUserIdMappingTable()
82+
+ " WHERE external_user_id = ?";
83+
84+
return execute(start, QUERY, pst -> pst.setString(1, userId), result -> {
85+
if (result.next()) {
86+
return UserIdMappingRowMapper.getInstance().mapOrThrow(result);
87+
}
88+
return null;
89+
});
90+
}
91+
92+
public static UserIdMapping[] getUserIdMappingWithEitherSuperTokensUserIdOrExternalUserId(Start start,
93+
String userId) throws SQLException, StorageQueryException {
94+
String QUERY = "SELECT * FROM " + Config.getConfig(start).getUserIdMappingTable()
95+
+ " WHERE supertokens_user_id = ? OR external_user_id = ? ";
96+
97+
return execute(start, QUERY, pst -> {
98+
pst.setString(1, userId);
99+
pst.setString(2, userId);
100+
}, result -> {
101+
ArrayList<UserIdMapping> userIdMappingArray = new ArrayList<>();
102+
while (result.next()) {
103+
userIdMappingArray.add(UserIdMappingRowMapper.getInstance().mapOrThrow(result));
104+
}
105+
return userIdMappingArray.toArray(UserIdMapping[]::new);
106+
});
107+
108+
}
109+
110+
public static boolean deleteUserIdMappingWithSuperTokensUserId(Start start, String userId)
111+
throws SQLException, StorageQueryException {
112+
String QUERY = "DELETE FROM " + Config.getConfig(start).getUserIdMappingTable()
113+
+ " WHERE supertokens_user_id = ?";
114+
115+
// store the number of rows updated
116+
int rowUpdatedCount = update(start, QUERY, pst -> pst.setString(1, userId));
117+
118+
return rowUpdatedCount > 0;
119+
}
120+
121+
public static boolean deleteUserIdMappingWithExternalUserId(Start start, String userId)
122+
throws SQLException, StorageQueryException {
123+
String QUERY = "DELETE FROM " + Config.getConfig(start).getUserIdMappingTable() + " WHERE external_user_id = ?";
124+
125+
// store the number of rows updated
126+
int rowUpdatedCount = update(start, QUERY, pst -> pst.setString(1, userId));
127+
128+
return rowUpdatedCount > 0;
129+
}
130+
131+
public static boolean updateOrDeleteExternalUserIdInfoWithSuperTokensUserId(Start start, String userId,
132+
@Nullable String externalUserIdInfo) throws SQLException, StorageQueryException {
133+
String QUERY = "UPDATE " + Config.getConfig(start).getUserIdMappingTable()
134+
+ " SET external_user_id_info = ? WHERE supertokens_user_id = ?";
135+
136+
int rowUpdated = update(start, QUERY, pst -> {
137+
pst.setString(1, externalUserIdInfo);
138+
pst.setString(2, userId);
139+
});
140+
141+
return rowUpdated > 0;
142+
}
143+
144+
public static boolean updateOrDeleteExternalUserIdInfoWithExternalUserId(Start start, String userId,
145+
@Nullable String externalUserIdInfo) throws SQLException, StorageQueryException {
146+
String QUERY = "UPDATE " + Config.getConfig(start).getUserIdMappingTable()
147+
+ " SET external_user_id_info = ? WHERE external_user_id = ?";
148+
149+
int rowUpdated = update(start, QUERY, pst -> {
150+
pst.setString(1, externalUserIdInfo);
151+
pst.setString(2, userId);
152+
});
153+
154+
return rowUpdated > 0;
155+
}
156+
157+
private static class UserIdMappingRowMapper implements RowMapper<UserIdMapping, ResultSet> {
158+
private static final UserIdMappingRowMapper INSTANCE = new UserIdMappingRowMapper();
159+
160+
private UserIdMappingRowMapper() {
161+
}
162+
163+
private static UserIdMappingRowMapper getInstance() {
164+
return INSTANCE;
165+
}
166+
167+
@Override
168+
public UserIdMapping map(ResultSet rs) throws Exception {
169+
return new UserIdMapping(rs.getString("supertokens_user_id"), rs.getString("external_user_id"),
170+
rs.getString("external_user_id_info"));
171+
}
172+
}
173+
174+
}

0 commit comments

Comments
 (0)