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

fix: fixes storage handling for non-auth recipes #942

Merged
merged 31 commits into from
Mar 5, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
eb4496b
fix: non auth recipe stuff
sattvikc Feb 29, 2024
d267312
fix: user roles
sattvikc Feb 29, 2024
6b2a45c
fix: half done
sattvikc Mar 1, 2024
fadf205
fix: thirdparty changes
sattvikc Mar 1, 2024
9a0ff85
fix: passwordless changes
sattvikc Mar 1, 2024
89fd936
fix: active users
sattvikc Mar 1, 2024
458c3b6
fix: session changes
sattvikc Mar 1, 2024
9eb76a1
fix: user metadata
sattvikc Mar 1, 2024
4843083
fix: user roles
sattvikc Mar 1, 2024
69a2466
fix: totp
sattvikc Mar 1, 2024
6728665
fix: email verification
sattvikc Mar 1, 2024
c61c7d7
fix: multitenancy and other minor fixes
sattvikc Mar 1, 2024
c1edaba
fix: compile errors
sattvikc Mar 1, 2024
dd688da
fix: bugs and tests
sattvikc Mar 1, 2024
c5fc6a3
fix: bugs and tests
sattvikc Mar 1, 2024
5f00b5e
fix: func rename
sattvikc Mar 1, 2024
311b9b0
fix: PR comments
sattvikc Mar 4, 2024
75b5a14
fix: pr comments
sattvikc Mar 4, 2024
38c11fd
fix: pr comments
sattvikc Mar 4, 2024
06569c0
fix: pr comments
sattvikc Mar 4, 2024
d94a381
fix: user role multitenant tests
sattvikc Mar 4, 2024
83b802c
fix: email verification tests
sattvikc Mar 4, 2024
3d93ab5
fix: user role deletion
sattvikc Mar 4, 2024
d7cbcfa
fix: user roles
sattvikc Mar 4, 2024
fb2234c
fix: user roles
sattvikc Mar 4, 2024
8945be1
fix: get tenant identifier refactor
sattvikc Mar 4, 2024
a5d7aad
fix: pr comments
sattvikc Mar 4, 2024
88539e5
fix: query
sattvikc Mar 4, 2024
8a31166
fix: tests version and changelog
sattvikc Mar 5, 2024
644b5d6
Update CHANGELOG.md
sattvikc Mar 5, 2024
0bfad8b
fix: pr comments
sattvikc Mar 5, 2024
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
20 changes: 3 additions & 17 deletions src/main/java/io/supertokens/ActiveUsers.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,29 +31,15 @@ public static void updateLastActive(Main main, String userId) {
}
}

public static int countUsersActiveSince(AppIdentifier appIdentifier, Storage storage, long time)
public static int countUsersActiveSince(Main main, AppIdentifier appIdentifier, long time)
throws StorageQueryException, TenantOrAppNotFoundException {
Storage storage = StorageLayer.getStorage(appIdentifier.getAsPublicTenantIdentifier(), main);
return StorageUtils.getActiveUsersStorage(storage).countUsersActiveSince(appIdentifier, time);
}

@TestOnly
public static int countUsersActiveSince(Main main, long time)
throws StorageQueryException, TenantOrAppNotFoundException {
return countUsersActiveSince(new AppIdentifier(null, null),
StorageLayer.getStorage(main), time);
}

public static void removeActiveUser(AppIdentifier appIdentifier, Storage storage, String userId)
throws StorageQueryException {
try {
((AuthRecipeSQLStorage) StorageUtils.getActiveUsersStorage(storage)).startTransaction(con -> {
StorageUtils.getActiveUsersStorage(storage).deleteUserActive_Transaction(con, appIdentifier, userId);
((AuthRecipeSQLStorage) StorageUtils.getActiveUsersStorage(storage)).commitTransaction(con);
return null;
});

} catch (StorageTransactionLogicException e) {
throw new StorageQueryException(e.actualException);
}
return countUsersActiveSince(main, new AppIdentifier(null, null), time);
}
}
6 changes: 3 additions & 3 deletions src/main/java/io/supertokens/authRecipe/AuthRecipe.java
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ private static CanLinkAccountsResult canLinkAccountsHelper(TransactionConnection
TenantIdentifier tenantIdentifier = new TenantIdentifier(
appIdentifier.getConnectionUriDomain(), appIdentifier.getAppId(),
tenantId);
// we do not bother with getting the tenantIdentifierWithStorage here because
// we do not bother with getting the storage for each tenant here because
// we get the tenants from the user itself, and the user can only be shared across
// tenants of the same storage - therefore, the storage will be the same.

Expand Down Expand Up @@ -656,7 +656,7 @@ public static long getUsersCountForTenant(TenantIdentifier tenantIdentifier,
tenantIdentifier, includeRecipeIds);
}

public static long getUsersCountAcrossAllTenants(AppIdentifier appIdentappIdentifierfierWithStorages,
public static long getUsersCountAcrossAllTenants(AppIdentifier appIdentifier,
Storage[] storages,
RECIPE_ID[] includeRecipeIds)
throws StorageQueryException,
Expand All @@ -665,7 +665,7 @@ public static long getUsersCountAcrossAllTenants(AppIdentifier appIdentappIdenti

for (Storage storage : storages) {
count += StorageUtils.getAuthRecipeStorage(storage).getUsersCount(
appIdentappIdentifierfierWithStorages, includeRecipeIds);
appIdentifier, includeRecipeIds);
}

return count;
Expand Down
19 changes: 11 additions & 8 deletions src/main/java/io/supertokens/inmemorydb/Start.java
Original file line number Diff line number Diff line change
Expand Up @@ -1861,7 +1861,7 @@ public int deleteUserMetadata(AppIdentifier appIdentifier, String userId) throws

@Override
public void addRoleToUser(TenantIdentifier tenantIdentifier, String userId, String role)
throws StorageQueryException, UnknownRoleException, DuplicateUserRoleMappingException,
throws StorageQueryException, DuplicateUserRoleMappingException,
TenantOrAppNotFoundException {
try {
UserRolesQueries.addRoleToUser(this, tenantIdentifier, userId, role);
Expand All @@ -1870,13 +1870,6 @@ public void addRoleToUser(TenantIdentifier tenantIdentifier, String userId, Stri
SQLiteConfig config = Config.getConfig(this);
String serverErrorMessage = e.getMessage();

if (isForeignKeyConstraintError(
serverErrorMessage,
config.getRolesTable(),
new String[]{"app_id", "role"},
new Object[]{tenantIdentifier.getAppId(), role})) {
throw new UnknownRoleException();
}
if (isPrimaryKeyError(serverErrorMessage, config.getUserRolesTable(),
new String[]{"app_id", "tenant_id", "user_id", "role"})) {
throw new DuplicateUserRoleMappingException();
Expand Down Expand Up @@ -1952,6 +1945,16 @@ public boolean deleteRole(AppIdentifier appIdentifier, String role) throws Stora
}
}

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

@Override
public String[] getRoles(AppIdentifier appIdentifier) throws StorageQueryException {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,6 @@ public static String getQueryToCreateUserRolesTable(Start start) {
+ "user_id VARCHAR(128) NOT NULL,"
+ "role VARCHAR(255) NOT NULL,"
+ "PRIMARY KEY(app_id, tenant_id, user_id, role),"
+ "FOREIGN KEY(app_id, role) REFERENCES " + Config.getConfig(start).getRolesTable()
+ " (app_id, role) ON DELETE CASCADE,"
+ "FOREIGN KEY(app_id, tenant_id) REFERENCES " + Config.getConfig(start).getTenantsTable()
+ " (app_id, tenant_id) ON DELETE CASCADE"
+ ");";
Expand Down Expand Up @@ -113,8 +111,9 @@ public static void addPermissionToRoleOrDoNothingIfExists_Transaction(Start star
});
}

public static boolean deleteRole(Start start, AppIdentifier appIdentifier, String role) throws SQLException, StorageQueryException {

public static boolean deleteRole(Start start, AppIdentifier appIdentifier,
String role) throws SQLException, StorageQueryException {

try {
return start.startTransaction(con -> {
// Row lock must be taken to delete the role, otherwise the table may be locked for delete
Expand Down Expand Up @@ -341,4 +340,14 @@ public static int deleteAllRolesForUser_Transaction(Connection con, Start start,
pst.setString(2, userId);
});
}

public static boolean deleteAllUserRoleAssociationsForRole(Start start, AppIdentifier appIdentifier, String role)
throws SQLException, StorageQueryException {
String QUERY = "DELETE FROM " + getConfig(start).getRolesTable()
+ " WHERE app_id = ? AND role = ? ;";
return update(start, QUERY, pst -> {
pst.setString(1, appIdentifier.getAppId());
pst.setString(2, role);
}) >= 1;
}
}
57 changes: 57 additions & 0 deletions src/main/java/io/supertokens/session/Session.java
Original file line number Diff line number Diff line change
Expand Up @@ -824,6 +824,20 @@ public static String[] revokeAllSessionsForUser(Main main, AppIdentifier appIden
return revokeSessionUsingSessionHandles(main, appIdentifier, storage, sessionHandles);
}

public static String[] revokeAllSessionsForUser(Main main, AppIdentifier appIdentifier,
rishabhpoddar marked this conversation as resolved.
Show resolved Hide resolved
Storage[] storages, String userId,
boolean revokeSessionsForLinkedAccounts)
throws StorageQueryException {
List<String> revokedSessionHandles = new ArrayList<>();
for (Storage storage : storages) {
String[] sessionHandles = getAllNonExpiredSessionHandlesForUser(main, appIdentifier, storage, userId,
revokeSessionsForLinkedAccounts);
return revokeSessionUsingSessionHandles(main, appIdentifier, storage, sessionHandles);
}

return revokedSessionHandles.toArray(new String[0]);
}

public static String[] revokeAllSessionsForUser(Main main, TenantIdentifier tenantIdentifier, Storage storage,
String userId, boolean revokeSessionsForLinkedAccounts)
throws StorageQueryException {
Expand Down Expand Up @@ -881,6 +895,49 @@ public static String[] getAllNonExpiredSessionHandlesForUser(
return sessionHandles.toArray(new String[0]);
}

public static String[] getAllNonExpiredSessionHandlesForUser(
rishabhpoddar marked this conversation as resolved.
Show resolved Hide resolved
Main main, AppIdentifier appIdentifier, Storage[] storages, String userId,
boolean fetchSessionsForAllLinkedAccounts)
throws StorageQueryException {
TenantConfig[] tenants = Multitenancy.getAllTenantsForApp(
appIdentifier, main);

List<String> sessionHandles = new ArrayList<>();

for (Storage storage : storages) {
Set<String> userIds = new HashSet<>();
userIds.add(userId);
if (fetchSessionsForAllLinkedAccounts) {
if (storage.getType().equals(STORAGE_TYPE.SQL)) {
AuthRecipeUserInfo primaryUser = ((AuthRecipeStorage) storage)
.getPrimaryUserById(appIdentifier, userId);
if (primaryUser != null) {
for (LoginMethod lM : primaryUser.loginMethods) {
userIds.add(lM.getSupertokensUserId());
}
}
}
}

for (String currUserId : userIds) {
for (TenantConfig tenant : tenants) {
try {
sessionHandles.addAll(Arrays.asList(getAllNonExpiredSessionHandlesForUser(
tenant.tenantIdentifier, StorageLayer.getStorage(tenant.tenantIdentifier, main),
currUserId, false)));

} catch (TenantOrAppNotFoundException e) {
// this might happen when a tenant was deleted after the tenant list was fetched
// it is okay to exclude that tenant in the results here
}
}
}
}


return sessionHandles.toArray(new String[0]);
}

public static String[] getAllNonExpiredSessionHandlesForUser(
TenantIdentifier tenantIdentifier, Storage storage, String userId,
boolean fetchSessionsForAllLinkedAccounts)
Expand Down
30 changes: 21 additions & 9 deletions src/main/java/io/supertokens/userroles/UserRoles.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,7 @@ public static boolean addRoleToUser(Main main, TenantIdentifier tenantIdentifier
// We do this because it's not straight forward to replicate roles to all storages of an app
Storage appStorage = StorageLayer.getStorage(
tenantIdentifier.toAppIdentifier().getAsPublicTenantIdentifier(), main);

String[] roles = getRoles(tenantIdentifier.toAppIdentifier(), appStorage);
if (!Arrays.asList(roles).contains(role)) {
if (!doesRoleExist(tenantIdentifier.toAppIdentifier(), appStorage, role)) {
throw new UnknownRoleException();
}

Expand Down Expand Up @@ -287,15 +285,29 @@ public static String[] getRolesThatHavePermission(Main main,
}

// delete a role
public static boolean deleteRole(AppIdentifier appIdentifier, Storage storage, String role)
throws StorageQueryException {
return StorageUtils.getUserRolesStorage(storage).deleteRole(appIdentifier, role);
public static boolean deleteRole(Main main, AppIdentifier appIdentifier, String role)
throws StorageQueryException, TenantOrAppNotFoundException {

Storage[] storages = StorageLayer.getStoragesForApp(main, appIdentifier);
boolean deletedRole = false;
for (Storage storage : storages) {
UserRolesSQLStorage userRolesStorage = StorageUtils.getUserRolesStorage(storage);
deletedRole = userRolesStorage.deleteAllUserRoleAssociationsForRole(appIdentifier, role) || deletedRole;
}

// Delete the role from the public tenant storage in the end so that the user
// never sees a role for user that has been deleted while the deletion is in progress
Storage appStorage = StorageLayer.getStorage(appIdentifier.getAsPublicTenantIdentifier(), main);
UserRolesSQLStorage userRolesStorage = StorageUtils.getUserRolesStorage(appStorage);
deletedRole = userRolesStorage.deleteRole(appIdentifier, role) || deletedRole;

return deletedRole;
}

@TestOnly
public static boolean deleteRole(Main main, String role) throws StorageQueryException {
Storage storage = StorageLayer.getStorage(main);
return deleteRole(new AppIdentifier(null, null), storage, role);
public static boolean deleteRole(Main main, String role) throws StorageQueryException,
TenantOrAppNotFoundException {
return deleteRole(main, new AppIdentifier(null, null), role);
}

// retrieve all roles that have been created
Expand Down
29 changes: 22 additions & 7 deletions src/main/java/io/supertokens/webserver/WebserverAPI.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import io.supertokens.storageLayer.StorageLayer;
import io.supertokens.useridmapping.UserIdType;
import io.supertokens.utils.SemVer;
import io.supertokens.webserver.api.useridmapping.UserIdMappingAPI;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
Expand Down Expand Up @@ -231,7 +232,7 @@ private String getTenantId(HttpServletRequest req) {
apiPath = "/" + apiPath;
}
if (apiPath.equals("/")) {
if ((path.equals("") || path.equals("/"))) {
if (path.equals("") || path.equals("/")) {
return null;
}
} else {
Expand Down Expand Up @@ -302,6 +303,12 @@ protected TenantIdentifier getTenantIdentifier(HttpServletRequest req) throws Se
return new TenantIdentifier(this.getConnectionUriDomain(req), this.getAppId(req), this.getTenantId(req));
}

protected TenantIdentifier ensureTenantExistsAndGetTenantIdentifier(HttpServletRequest req)
rishabhpoddar marked this conversation as resolved.
Show resolved Hide resolved
throws ServletException, TenantOrAppNotFoundException {
getTenantStorage(req);
return new TenantIdentifier(this.getConnectionUriDomain(req), this.getAppId(req), this.getTenantId(req));
}

protected AppIdentifier getAppIdentifier(HttpServletRequest req) throws ServletException {
rishabhpoddar marked this conversation as resolved.
Show resolved Hide resolved
return new AppIdentifier(this.getConnectionUriDomain(req), this.getAppId(req));
}
Expand All @@ -316,7 +323,7 @@ protected Storage getTenantStorage(HttpServletRequest req)
protected Storage[] enforcePublicTenantAndGetAllStoragesForApp(HttpServletRequest req)
throws ServletException, BadPermissionException, TenantOrAppNotFoundException {
if (getTenantId(req) != null) {
throw new BadPermissionException("Only public tenantId can this app specific API");
throw new BadPermissionException("Only public tenantId can call this app specific API");
}

AppIdentifier appIdentifier = getAppIdentifier(req);
Expand All @@ -330,7 +337,7 @@ protected Storage enforcePublicTenantAndGetPublicTenantStorage(
this.getTenantId(req));

if (getTenantId(req) != null) {
throw new BadPermissionException("Only public tenantId can this app specific API");
throw new BadPermissionException("Only public tenantId can call this app specific API");
}

return StorageLayer.getStorage(tenantIdentifier, main);
Expand All @@ -345,16 +352,24 @@ protected StorageAndUserIdMapping getStorageAndUserIdMappingForTenantSpecificApi
userIdType);
}

protected StorageAndUserIdMapping getStorageAndUserIdMappingForAppSpecificApi(
HttpServletRequest req, String userId, UserIdType userIdType)
protected StorageAndUserIdMapping enforcePublicTenantAndGetStorageAndUserIdMappingForAppSpecificApi(
HttpServletRequest req, String userId, UserIdType userIdType, boolean isCallFromAuthRecipeAPI)
throws StorageQueryException, TenantOrAppNotFoundException, UnknownUserIdException, ServletException,
BadPermissionException {
// This function uses storage of the tenant from which the request came from as a priorityStorage
// while searching for the user across all storages for the app
AppIdentifier appIdentifier = getAppIdentifier(req);
Storage[] storages = enforcePublicTenantAndGetAllStoragesForApp(req);
return StorageLayer.findStorageAndUserIdMappingForUser(
appIdentifier, storages, userId, userIdType);
try {
return StorageLayer.findStorageAndUserIdMappingForUser(
appIdentifier, storages, userId, userIdType);
} catch (UnknownUserIdException e) {
if (isCallFromAuthRecipeAPI) {
throw e;
}

return new StorageAndUserIdMapping(enforcePublicTenantAndGetPublicTenantStorage(req), null);
}
}

protected boolean checkIPAccess(HttpServletRequest req, HttpServletResponse resp)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO
try {
String userId = inputRecipeUserId;
StorageAndUserIdMapping storageAndMapping =
getStorageAndUserIdMappingForAppSpecificApi(
req, inputRecipeUserId, UserIdType.ANY);
enforcePublicTenantAndGetStorageAndUserIdMappingForAppSpecificApi(
req, inputRecipeUserId, UserIdType.ANY, true);
storage = storageAndMapping.storage;
if (storageAndMapping.userIdMapping != null) {
userId = storageAndMapping.userIdMapping.superTokensUserId;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO
String recipeUserId = inputRecipeUserId;
{
StorageAndUserIdMapping mappingAndStorage =
getStorageAndUserIdMappingForAppSpecificApi(
req, inputRecipeUserId, UserIdType.ANY);
enforcePublicTenantAndGetStorageAndUserIdMappingForAppSpecificApi(
req, inputRecipeUserId, UserIdType.ANY, true);
if (mappingAndStorage.userIdMapping != null) {
recipeUserId = mappingAndStorage.userIdMapping.superTokensUserId;
}
Expand All @@ -75,8 +75,8 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO
String primaryUserId = inputPrimaryUserId;
{
StorageAndUserIdMapping mappingAndStorage =
getStorageAndUserIdMappingForAppSpecificApi(
req, inputPrimaryUserId, UserIdType.ANY);
enforcePublicTenantAndGetStorageAndUserIdMappingForAppSpecificApi(
req, inputPrimaryUserId, UserIdType.ANY, true);
if (mappingAndStorage.userIdMapping != null) {
primaryUserId = mappingAndStorage.userIdMapping.superTokensUserId;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I
try {
String userId = inputRecipeUserId;
StorageAndUserIdMapping mappingAndStorage =
getStorageAndUserIdMappingForAppSpecificApi(
req, inputRecipeUserId, UserIdType.ANY);
enforcePublicTenantAndGetStorageAndUserIdMappingForAppSpecificApi(
req, inputRecipeUserId, UserIdType.ANY, true);
storage = mappingAndStorage.storage;
if (mappingAndStorage.userIdMapping != null) {
userId = mappingAndStorage.userIdMapping.superTokensUserId;
Expand Down
Loading
Loading