From e648127da4caaac4455e190cf1b34a2e42c1c261 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Fri, 7 Jul 2023 14:00:24 +0530 Subject: [PATCH 001/131] table schema changes --- CHANGELOG.md | 189 +++++++++--------- .../inmemorydb/queries/GeneralQueries.java | 33 ++- 2 files changed, 115 insertions(+), 107 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4139f41ab..b67eb0a4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [unreleased] + +- Db schema changes: + - Added new index `all_auth_recipe_users_primary_user_id_index`. + - Modified `all_auth_recipe_users_pagination_index` index to be on `primary_or_recipe_user_id` instead of `user_id` + - Added a two new columns in `all_auth_recipe_users`: + - `primary_or_recipe_user_id` (default value is equal to `user_id` column) + - `is_linked_or_is_a_primary_user` (default value is false) + ## [6.0.2] - 2023-07-04 - Fixes some of the session APIs to return `tenantId` @@ -19,11 +28,11 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - Fixes `CreateOrUpdateThirdPartyConfigAPI` as per CDI 3.0 - Fixes `sessionHandle` to include tenant information and the related APIs are now app specific - Updated GET `/appid-//recipe/session/user` - - Adds `fetchAcrossAllTenants` with default `true` - controls fetching of sessions across all tenants or only a - particular tenant + - Adds `fetchAcrossAllTenants` with default `true` - controls fetching of sessions across all tenants or only a + particular tenant - Updated POST `/appid-//recipe/session/remove` - - Adds `revokeAcrossAllTenants` with default `true` - controls revoking of sessions across all tenants or only a - particular tenant + - Adds `revokeAcrossAllTenants` with default `true` - controls revoking of sessions across all tenants or only a + particular tenant - Updated telemetry to send `connectionUriDomain`, `appId` and `mau` information - Updated feature flag stats to report `usersCount` per tenant @@ -33,8 +42,8 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - Support for multitenancy. - New config `supertokens_saas_secret` added to support multitenancy in SaaS mode. -- New config `supertokens_default_cdi_version` is added to specify the version of CDI core must assume when the - version is not specified in the request. If this config is not specified, the core will assume the latest version. +- New config `supertokens_default_cdi_version` is added to specify the version of CDI core must assume when the version + is not specified in the request. If this config is not specified, the core will assume the latest version. ### Fixes @@ -44,22 +53,22 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - Modifies the `/recipe/dashboard/session/verify` API to include the user's email in the response - Support for multitenancy - - New APIs to manage apps and tenants - - `/recipe/multitenancy/connectionuridomain` PUT - - `/recipe/multitenancy/connectionuridomain/remove` POST - - `/recipe/multitenancy/connectionuridomain/list` GET - - `/recipe/multitenancy/app` PUT - - `/recipe/multitenancy/app/remove` POST - - `/recipe/multitenancy/app/list` GET - - `/appid-/recipe/multitenancy/tenant` PUT - - `/appid-//recipe/multitenancy/tenant` GET - - `/appid-/recipe/multitenancy/tenant/remove` POST - - `/appid-/recipe/multitenancy/tenant/list` GET - - `/appid-/recipe/multitenancy/config/thirdparty` PUT - - `/appid-/recipe/multitenancy/config/thirdparty/remove` POST - - `/appid-//recipe/multitenancy/tenant/user` POST - - `/appid-//recipe/multitenancy/tenant/user/remove` POST - - API paths can be prefixed with `/appid-/` to perform app or tenant specific operations. + - New APIs to manage apps and tenants + - `/recipe/multitenancy/connectionuridomain` PUT + - `/recipe/multitenancy/connectionuridomain/remove` POST + - `/recipe/multitenancy/connectionuridomain/list` GET + - `/recipe/multitenancy/app` PUT + - `/recipe/multitenancy/app/remove` POST + - `/recipe/multitenancy/app/list` GET + - `/appid-/recipe/multitenancy/tenant` PUT + - `/appid-//recipe/multitenancy/tenant` GET + - `/appid-/recipe/multitenancy/tenant/remove` POST + - `/appid-/recipe/multitenancy/tenant/list` GET + - `/appid-/recipe/multitenancy/config/thirdparty` PUT + - `/appid-/recipe/multitenancy/config/thirdparty/remove` POST + - `/appid-//recipe/multitenancy/tenant/user` POST + - `/appid-//recipe/multitenancy/tenant/user/remove` POST + - API paths can be prefixed with `/appid-/` to perform app or tenant specific operations. ### Migration steps for SQL @@ -68,10 +77,10 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 3. Run the migration script
- + If using PostgreSQL - - #### Run the following SQL script + + #### Run the following SQL script ```sql -- General Tables @@ -931,15 +940,15 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). CREATE INDEX IF NOT EXISTS user_last_active_app_id_index ON user_last_active (app_id); ``` - +
- +
- + If using MySQL - - #### Run the following SQL script - + + #### Run the following SQL script + ```sql -- helper stored procedures @@ -1668,28 +1677,28 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - Using an internal `SemVer` class to handle version numbers. This will make handling CDI version ranges easier. - Support for CDI version `2.21` - - Removed POST `/recipe/handshake` - - Added `useDynamicSigningKey` into `createNewSession` (POST `/recipe/session`), replacing - `access_token_signing_key_dynamic` used in CDI<=2.18 - - Added `useStaticSigningKey` into `createSignedJWT` (POST `/recipe/jwt`) - - Added `checkDatabase` into `verifySession` (POST `/recipe/session/verify`), replacing - `access_token_blacklisting` used in CDI<=2.18 - - Removed `idRefreshToken`, `jwtSigningPublicKey`, `jwtSigningPublicKeyExpiryTime` and `jwtSigningPublicKeyList` - from responses - - Deprecated GET `/recipe/jwt/jwks` - - Added GET `/.well-known/jwks.json`: a standard jwks + - Removed POST `/recipe/handshake` + - Added `useDynamicSigningKey` into `createNewSession` (POST `/recipe/session`), replacing + `access_token_signing_key_dynamic` used in CDI<=2.18 + - Added `useStaticSigningKey` into `createSignedJWT` (POST `/recipe/jwt`) + - Added `checkDatabase` into `verifySession` (POST `/recipe/session/verify`), replacing + `access_token_blacklisting` used in CDI<=2.18 + - Removed `idRefreshToken`, `jwtSigningPublicKey`, `jwtSigningPublicKeyExpiryTime` and `jwtSigningPublicKeyList` + from responses + - Deprecated GET `/recipe/jwt/jwks` + - Added GET `/.well-known/jwks.json`: a standard jwks - Added new access token version - - Uses standard prop names (i.e.: `sub` instead of `userId`) - - Contains the id of the signing key in the header (as `kid`) - - Stores the user payload merged into the root level, instead of the `userData` prop -- Session handling function now throw if the user payload contains protected props (`sub`, `iat`, `exp`, + - Uses standard prop names (i.e.: `sub` instead of `userId`) + - Contains the id of the signing key in the header (as `kid`) + - Stores the user payload merged into the root level, instead of the `userData` prop +- Session handling function now throw if the user payload contains protected props (`sub`, `iat`, `exp`, `sessionHandle`, `refreshTokenHash1`, `parentRefreshTokenHash1`, `antiCsrfToken`) - - A related exception type was added as `AccessTokenPayloadError` + - A related exception type was added as `AccessTokenPayloadError` - Refactored the handling of signing keys -- `createNewSession` now takes a `useStaticKey` parameter instead of depending on the +- `createNewSession` now takes a `useStaticKey` parameter instead of depending on the `access_token_signing_key_dynamic` config value - `createJWTToken` now supports signing by a dynamic key -- `getSession` now takes a `checkDatabase` parameter instead of using the `access_token_blacklisting` config value +- `getSession` now takes a `checkDatabase` parameter instead of using the `access_token_blacklisting` config value - Updated plugin interface version to 2.21 ### Configuration Changes @@ -1706,59 +1715,46 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). #### Migration steps for SQL - If using `access_token_signing_key_dynamic` false: - - ```sql - ALTER TABLE session_info ADD COLUMN use_static_key BOOLEAN NOT NULL DEFAULT(true); - ALTER TABLE session_info ALTER COLUMN use_static_key DROP DEFAULT; + - ```sql + ALTER TABLE session_info ADD COLUMN use_static_key BOOLEAN NOT NULL DEFAULT(true); ALTER TABLE session_info ALTER + COLUMN use_static_key DROP DEFAULT; ``` - - ```sql + - ```sql INSERT INTO jwt_signing_keys(key_id, key_string, algorithm, created_at) select CONCAT('s-', created_at_time) as key_id, value as key_string, 'RS256' as algorithm, created_at_time as created_at from session_access_token_signing_keys; ``` - If using `access_token_signing_key_dynamic` true or not set: - - ```sql - ALTER TABLE session_info ADD COLUMN use_static_key BOOLEAN NOT NULL DEFAULT(false); - ALTER TABLE session_info ALTER COLUMN use_static_key DROP DEFAULT; + - ```sql + ALTER TABLE session_info ADD COLUMN use_static_key BOOLEAN NOT NULL DEFAULT(false); ALTER TABLE session_info ALTER + COLUMN use_static_key DROP DEFAULT; ``` #### Migration steps for MongoDB - If using `access_token_signing_key_dynamic` false: - - ``` - db.session_info.update({}, - { - "$set": { - "use_static_key": true - } - }); + - ``` + db.session_info.update({}, { + "$set": { + "use_static_key": true } }); ``` - - ``` - db.key_value.aggregate([ - { - "$match": { - _id: "access_token_signing_key_list" - } - }, - { - $unwind: "$keys" - }, - { - $addFields: { - _id: { - "$concat": [ - "s-", - { - $convert: { - input: "$keys.created_at_time", - to: "string" - } - } - ] - }, - "key_string": "$keys.value", - "algorithm": "RS256", - "created_at": "$keys.created_at_time", - + - ``` + db.key_value.aggregate([ + { + "$match": { + _id: "access_token_signing_key_list" + } }, { $unwind: "$keys" + }, { $addFields: { + _id: { + "$concat": [ + "s-", { $convert: { input: "$keys.created_at_time", to: "string" + } } + ] + }, + "key_string": "$keys.value", + "algorithm": "RS256", + "created_at": "$keys.created_at_time", + } }, { @@ -1773,17 +1769,14 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). } } - ]); + ]); ``` - If using `access_token_signing_key_dynamic` true or not set: - - ``` - db.session_info.update({}, - { - "$set": { - "use_static_key": false - } - }); + - ``` + db.session_info.update({}, { + "$set": { + "use_static_key": false } }); ``` ## [4.6.0] - 2023-03-30 @@ -1791,8 +1784,8 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - Add Optional Search Tags to Pagination API to enable dashboard search ### New APIs: - - `GET /user/search/tags` retrieves the available search tags +- `GET /user/search/tags` retrieves the available search tags ## [4.5.0] - 2023-03-27 diff --git a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java index 7141a7da7..eadaba9b1 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java @@ -73,6 +73,8 @@ static String getQueryToCreateUsersTable(Start start) { + "app_id VARCHAR(64) DEFAULT 'public'," + "tenant_id VARCHAR(64) DEFAULT 'public'," + "user_id CHAR(36) NOT NULL," + + "primary_or_recipe_user_id CHAR(36) NOT NULL," + + "is_linked_or_is_a_primary_user BOOLEAN NOT NULL DEFAULT FALSE," + "recipe_id VARCHAR(128) NOT NULL," + "time_joined BIGINT UNSIGNED NOT NULL," + "PRIMARY KEY (app_id, tenant_id, user_id)," @@ -85,7 +87,12 @@ static String getQueryToCreateUsersTable(Start start) { static String getQueryToCreateUserPaginationIndex(Start start) { return "CREATE INDEX all_auth_recipe_users_pagination_index ON " + Config.getConfig(start).getUsersTable() - + "(time_joined DESC, user_id DESC, tenant_id DESC, app_id DESC);"; + + "(time_joined DESC, primary_or_recipe_user_id DESC, tenant_id DESC, app_id DESC);"; + } + + static String getQueryToCreatePrimaryUserIdIndex(Start start) { + return "CREATE INDEX all_auth_recipe_users_primary_user_id_index ON " + Config.getConfig(start).getUsersTable() + + "(primary_or_recipe_user_id);"; } private static String getQueryToCreateAppsTable(Start start) { @@ -127,7 +134,6 @@ static String getQueryToCreateKeyValueTable(Start start) { } - private static String getQueryToCreateAppIdToUserIdTable(Start start) { String appToUserTable = Config.getConfig(start).getAppIdToUserIdTable(); // @formatter:off @@ -168,6 +174,7 @@ public static void createTablesIfNotExists(Start start, Main main) throws SQLExc update(start, getQueryToCreateUsersTable(start), NO_OP_SETTER); // index + update(start, getQueryToCreatePrimaryUserIdIndex(start), NO_OP_SETTER); update(start, getQueryToCreateUserPaginationIndex(start), NO_OP_SETTER); } @@ -391,7 +398,9 @@ public static KeyValueInfo getKeyValue_Transaction(Start start, Connection con, String key) throws SQLException, StorageQueryException { - ((ConnectionWithLocks) con).lock(tenantIdentifier.getAppId() + "~" + tenantIdentifier.getTenantId() + "~" + key + Config.getConfig(start).getKeyValueTable()); + ((ConnectionWithLocks) con).lock( + tenantIdentifier.getAppId() + "~" + tenantIdentifier.getTenantId() + "~" + key + + Config.getConfig(start).getKeyValueTable()); String QUERY = "SELECT value, created_at_time FROM " + getConfig(start).getKeyValueTable() + " WHERE app_id = ? AND tenant_id = ? AND name = ?"; @@ -436,6 +445,7 @@ public static long getUsersCount(Start start, AppIdentifier appIdentifier, RECIP } QUERY.append(")"); } + QUERY.append(" GROUP BY primary_or_recipe_user_id"); return execute(start, QUERY.toString(), pst -> { pst.setString(1, appIdentifier.getAppId()); @@ -468,6 +478,7 @@ public static long getUsersCount(Start start, TenantIdentifier tenantIdentifier, } QUERY.append(")"); } + QUERY.append(" GROUP BY primary_or_recipe_user_id"); return execute(start, QUERY.toString(), pst -> { pst.setString(1, tenantIdentifier.getAppId()); @@ -511,8 +522,9 @@ public static boolean doesUserIdExist(Start start, TenantIdentifier tenantIdenti public static AuthRecipeUserInfo[] getUsers(Start start, TenantIdentifier tenantIdentifier, @NotNull Integer limit, @NotNull String timeJoinedOrder, - @org.jetbrains.annotations.Nullable RECIPE_ID[] includeRecipeIds, @org.jetbrains.annotations.Nullable - String userId, + @org.jetbrains.annotations.Nullable RECIPE_ID[] includeRecipeIds, + @org.jetbrains.annotations.Nullable + String userId, @org.jetbrains.annotations.Nullable Long timeJoined, @Nullable DashboardSearchTags dashboardSearchTags) throws SQLException, StorageQueryException { @@ -839,10 +851,12 @@ private static List getUserInfoForRecipeIdFromUser } } - public static String getRecipeIdForUser_Transaction(Start start, Connection sqlCon, TenantIdentifier tenantIdentifier, String userId) + public static String getRecipeIdForUser_Transaction(Start start, Connection sqlCon, + TenantIdentifier tenantIdentifier, String userId) throws SQLException, StorageQueryException { - ((ConnectionWithLocks) sqlCon).lock(tenantIdentifier.getAppId() + "~" + userId + Config.getConfig(start).getAppIdToUserIdTable()); + ((ConnectionWithLocks) sqlCon).lock( + tenantIdentifier.getAppId() + "~" + userId + Config.getConfig(start).getAppIdToUserIdTable()); String QUERY = "SELECT recipe_id FROM " + getConfig(start).getAppIdToUserIdTable() + " WHERE app_id = ? AND user_id = ?"; @@ -858,7 +872,8 @@ public static String getRecipeIdForUser_Transaction(Start start, Connection sqlC }); } - public static Map> getTenantIdsForUserIds_transaction(Start start, Connection sqlCon, String[] userIds) + public static Map> getTenantIdsForUserIds_transaction(Start start, Connection sqlCon, + String[] userIds) throws SQLException, StorageQueryException { if (userIds != null && userIds.length > 0) { StringBuilder QUERY = new StringBuilder("SELECT user_id, tenant_id " @@ -905,7 +920,7 @@ public static String[] getAllTablesInTheDatabase(Start start) throws SQLExceptio List tableNames = new ArrayList<>(); try (Connection con = ConnectionPool.getConnection(start)) { DatabaseMetaData metadata = con.getMetaData(); - ResultSet resultSet = metadata.getTables(null, null, null, new String[] { "TABLE" }); + ResultSet resultSet = metadata.getTables(null, null, null, new String[]{"TABLE"}); while (resultSet.next()) { String tableName = resultSet.getString("TABLE_NAME"); tableNames.add(tableName); From c4506274195e44fc59ef408bada51c9333f013b9 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Fri, 7 Jul 2023 15:05:50 +0530 Subject: [PATCH 002/131] changes to user count API --- CHANGELOG.md | 1 + .../inmemorydb/queries/GeneralQueries.java | 26 ++++++++++++++----- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b67eb0a4f..c57ccffd3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - Db schema changes: - Added new index `all_auth_recipe_users_primary_user_id_index`. + - Added new index `all_auth_recipe_users_primary_user_id_and_tenant_id_index`. - Modified `all_auth_recipe_users_pagination_index` index to be on `primary_or_recipe_user_id` instead of `user_id` - Added a two new columns in `all_auth_recipe_users`: - `primary_or_recipe_user_id` (default value is equal to `user_id` column) diff --git a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java index eadaba9b1..25528e1bd 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java @@ -92,7 +92,13 @@ static String getQueryToCreateUserPaginationIndex(Start start) { static String getQueryToCreatePrimaryUserIdIndex(Start start) { return "CREATE INDEX all_auth_recipe_users_primary_user_id_index ON " + Config.getConfig(start).getUsersTable() - + "(primary_or_recipe_user_id);"; + + "(app_id, primary_or_recipe_user_id);"; + } + + static String getQueryToCreatePrimaryUserIdAndTenantIndex(Start start) { + return "CREATE INDEX all_auth_recipe_users_primary_user_id_and_tenant_id_index ON " + + Config.getConfig(start).getUsersTable() + + "(app_id, tenant_id, primary_or_recipe_user_id);"; } private static String getQueryToCreateAppsTable(Start start) { @@ -176,6 +182,7 @@ public static void createTablesIfNotExists(Start start, Main main) throws SQLExc // index update(start, getQueryToCreatePrimaryUserIdIndex(start), NO_OP_SETTER); update(start, getQueryToCreateUserPaginationIndex(start), NO_OP_SETTER); + update(start, getQueryToCreatePrimaryUserIdAndTenantIndex(start), NO_OP_SETTER); } if (!doesTableExists(start, Config.getConfig(start).getUserLastActiveTable())) { @@ -499,9 +506,11 @@ public static long getUsersCount(Start start, TenantIdentifier tenantIdentifier, public static boolean doesUserIdExist(Start start, AppIdentifier appIdentifier, String userId) throws SQLException, StorageQueryException { - - String QUERY = "SELECT 1 FROM " + getConfig(start).getAppIdToUserIdTable() - + " WHERE app_id = ? AND user_id = ?"; + // We query both tables cause there is a case where a primary user ID exists, but its associated + // recipe user ID has been deleted AND there are other recipe user IDs linked to this primary user ID already. + String QUERY = "(SELECT 1 FROM " + getConfig(start).getAppIdToUserIdTable() + + " WHERE app_id = ? AND user_id = ?) UNION (SELECT 1 FROM " + getConfig(start).getUsersTable() + + " WHERE app_id = ? AND primary_or_recipe_user_id = ?)"; return execute(start, QUERY, pst -> { pst.setString(1, appIdentifier.getAppId()); pst.setString(2, userId); @@ -510,9 +519,12 @@ public static boolean doesUserIdExist(Start start, AppIdentifier appIdentifier, public static boolean doesUserIdExist(Start start, TenantIdentifier tenantIdentifier, String userId) throws SQLException, StorageQueryException { - - String QUERY = "SELECT 1 FROM " + getConfig(start).getUsersTable() - + " WHERE app_id = ? AND tenant_id = ? AND user_id = ?"; + // We query both tables cause there is a case where a primary user ID exists, but its associated + // recipe user ID has been deleted AND there are other recipe user IDs linked to this primary user ID already. + String QUERY = "(SELECT 1 FROM " + getConfig(start).getUsersTable() + + " WHERE app_id = ? AND tenant_id = ? AND user_id = ?) UNION (SELECT 1 FROM " + + getConfig(start).getUsersTable() + + " WHERE app_id = ? AND tenant_id = ? AND primary_or_recipe_user_id = ?)"; return execute(start, QUERY, pst -> { pst.setString(1, tenantIdentifier.getAppId()); pst.setString(2, tenantIdentifier.getTenantId()); From 0bbb9a730877ea6a50f521293a9c85b808580691 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Fri, 7 Jul 2023 17:32:27 +0530 Subject: [PATCH 003/131] small change --- src/main/java/io/supertokens/authRecipe/AuthRecipe.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/io/supertokens/authRecipe/AuthRecipe.java b/src/main/java/io/supertokens/authRecipe/AuthRecipe.java index d14c38e6b..eef8e7c63 100644 --- a/src/main/java/io/supertokens/authRecipe/AuthRecipe.java +++ b/src/main/java/io/supertokens/authRecipe/AuthRecipe.java @@ -26,7 +26,6 @@ import io.supertokens.pluginInterface.dashboard.DashboardSearchTags; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; -import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; @@ -226,7 +225,7 @@ private static void deleteNonAuthRecipeUser(AppIdentifierWithStorage } public static boolean deleteNonAuthRecipeUser(TenantIdentifierWithStorage - tenantIdentifierWithStorage, String userId) + tenantIdentifierWithStorage, String userId) throws StorageQueryException { // UserMetadata is per app, so nothing to delete From ee69339797e0a2173e2df8d9505ddaa8e0801bf5 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Sat, 8 Jul 2023 13:29:47 +0530 Subject: [PATCH 004/131] implements changes to auth queries to fetch tenant ids and verified status --- .../java/io/supertokens/inmemorydb/Start.java | 220 ++++++++---- .../java/io/supertokens/inmemorydb/Utils.java | 30 ++ .../queries/EmailPasswordQueries.java | 230 ++++++++----- .../queries/EmailVerificationQueries.java | 91 ++++- .../inmemorydb/queries/GeneralQueries.java | 123 ++++++- .../queries/PasswordlessQueries.java | 313 ++++++++++++------ .../inmemorydb/queries/ThirdPartyQueries.java | 234 ++++++++----- .../io/supertokens/thirdparty/ThirdParty.java | 6 +- .../test/thirdparty/ThirdPartyTest.java | 6 +- .../test/thirdparty/ThirdPartyTest2_7.java | 6 +- 10 files changed, 872 insertions(+), 387 deletions(-) create mode 100644 src/main/java/io/supertokens/inmemorydb/Utils.java diff --git a/src/main/java/io/supertokens/inmemorydb/Start.java b/src/main/java/io/supertokens/inmemorydb/Start.java index 43be8c364..29c748299 100644 --- a/src/main/java/io/supertokens/inmemorydb/Start.java +++ b/src/main/java/io/supertokens/inmemorydb/Start.java @@ -24,6 +24,7 @@ import io.supertokens.inmemorydb.queries.*; import io.supertokens.pluginInterface.*; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; +import io.supertokens.pluginInterface.authRecipe.LoginMethod; import io.supertokens.pluginInterface.dashboard.DashboardSearchTags; import io.supertokens.pluginInterface.dashboard.DashboardSessionInfo; import io.supertokens.pluginInterface.dashboard.DashboardUser; @@ -48,7 +49,10 @@ import io.supertokens.pluginInterface.jwt.JWTSigningKeyInfo; import io.supertokens.pluginInterface.jwt.exceptions.DuplicateKeyIdException; import io.supertokens.pluginInterface.jwt.sqlstorage.JWTRecipeSQLStorage; -import io.supertokens.pluginInterface.multitenancy.*; +import io.supertokens.pluginInterface.multitenancy.AppIdentifier; +import io.supertokens.pluginInterface.multitenancy.MultitenancyStorage; +import io.supertokens.pluginInterface.multitenancy.TenantConfig; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.exceptions.DuplicateClientTypeException; import io.supertokens.pluginInterface.multitenancy.exceptions.DuplicateTenantException; import io.supertokens.pluginInterface.multitenancy.exceptions.DuplicateThirdPartyIdException; @@ -90,7 +94,10 @@ import java.sql.Connection; import java.sql.SQLException; import java.sql.SQLTransactionRollbackException; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Set; public class Start implements SessionSQLStorage, EmailPasswordSQLStorage, EmailVerificationSQLStorage, ThirdPartySQLStorage, @@ -134,7 +141,8 @@ public STORAGE_TYPE getType() { } @Override - public void loadConfig(JsonObject ignored, Set logLevel, TenantIdentifier tenantIdentifier) throws InvalidConfigException { + public void loadConfig(JsonObject ignored, Set logLevel, TenantIdentifier tenantIdentifier) + throws InvalidConfigException { Config.loadConfig(this); } @@ -248,7 +256,8 @@ public KeyValueInfo getLegacyAccessTokenSigningKey_Transaction(AppIdentifier app throws StorageQueryException { Connection sqlCon = (Connection) con.getConnection(); try { - return GeneralQueries.getKeyValue_Transaction(this, sqlCon, appIdentifier.getAsPublicTenantIdentifier(), ACCESS_TOKEN_SIGNING_KEY_NAME); + return GeneralQueries.getKeyValue_Transaction(this, sqlCon, appIdentifier.getAsPublicTenantIdentifier(), + ACCESS_TOKEN_SIGNING_KEY_NAME); } catch (SQLException e) { throw new StorageQueryException(e); } @@ -448,7 +457,7 @@ public KeyValueInfo getKeyValue(TenantIdentifier tenantIdentifier, String key) t } } - @Override + @Override public void setKeyValue(TenantIdentifier tenantIdentifier, String key, KeyValueInfo info) throws StorageQueryException, TenantOrAppNotFoundException { try { @@ -514,7 +523,8 @@ public void updateSessionInfo_Transaction(TenantIdentifier tenantIdentifier, Tra long expiry) throws StorageQueryException { Connection sqlCon = (Connection) con.getConnection(); try { - SessionQueries.updateSessionInfo_Transaction(this, sqlCon, tenantIdentifier, sessionHandle, refreshTokenHash2, expiry); + SessionQueries.updateSessionInfo_Transaction(this, sqlCon, tenantIdentifier, sessionHandle, + refreshTokenHash2, expiry); } catch (SQLException e) { throw new StorageQueryException(e); } @@ -602,7 +612,8 @@ public boolean isUserIdBeingUsedInNonAuthRecipe(AppIdentifier appIdentifier, Str @TestOnly @Override - public void addInfoToNonAuthRecipesBasedOnUserId(TenantIdentifier tenantIdentifier, String className, String userId) throws StorageQueryException { + public void addInfoToNonAuthRecipesBasedOnUserId(TenantIdentifier tenantIdentifier, String className, String userId) + throws StorageQueryException { if (!isTesting) { throw new UnsupportedOperationException(); } @@ -672,7 +683,8 @@ public void addInfoToNonAuthRecipesBasedOnUserId(TenantIdentifier tenantIdentifi try { long now = System.currentTimeMillis(); TOTPQueries.insertUsedCode_Transaction(this, - (Connection) con.getConnection(), tenantIdentifier, new TOTPUsedCode(userId, "123456", true, 1000+now, now)); + (Connection) con.getConnection(), tenantIdentifier, + new TOTPUsedCode(userId, "123456", true, 1000 + now, now)); } catch (SQLException e) { throw new StorageTransactionLogicException(e); } @@ -706,7 +718,8 @@ public String[] getProtectedConfigsFromSuperTokensSaaSUsers() { } @Override - public UserInfo signUp(TenantIdentifier tenantIdentifier, String id, String email, String passwordHash, long timeJoined) + public UserInfo signUp(TenantIdentifier tenantIdentifier, String id, String email, String passwordHash, + long timeJoined) throws StorageQueryException, DuplicateUserIdException, DuplicateEmailException, TenantOrAppNotFoundException { try { @@ -716,12 +729,17 @@ public UserInfo signUp(TenantIdentifier tenantIdentifier, String id, String emai SQLiteConfig config = Config.getConfig(this); String serverMessage = eTemp.actualException.getMessage(); - if (isUniqueConstraintError(serverMessage, config.getEmailPasswordUserToTenantTable(), new String[]{"app_id", "tenant_id", "email"})) { + if (isUniqueConstraintError(serverMessage, config.getEmailPasswordUserToTenantTable(), + new String[]{"app_id", "tenant_id", "email"})) { throw new DuplicateEmailException(); - } else if (isPrimaryKeyError(serverMessage, config.getEmailPasswordUsersTable(), new String[]{"app_id", "user_id"}) - || isPrimaryKeyError(serverMessage, config.getUsersTable(), new String[]{"app_id", "tenant_id", "user_id"}) - || isPrimaryKeyError(serverMessage, config.getEmailPasswordUserToTenantTable(), new String[]{"app_id", "tenant_id", "user_id"}) - || isPrimaryKeyError(serverMessage, config.getAppIdToUserIdTable(), new String[]{"app_id", "user_id"})) { + } else if (isPrimaryKeyError(serverMessage, config.getEmailPasswordUsersTable(), + new String[]{"app_id", "user_id"}) + || isPrimaryKeyError(serverMessage, config.getUsersTable(), + new String[]{"app_id", "tenant_id", "user_id"}) + || isPrimaryKeyError(serverMessage, config.getEmailPasswordUserToTenantTable(), + new String[]{"app_id", "tenant_id", "user_id"}) + || isPrimaryKeyError(serverMessage, config.getAppIdToUserIdTable(), + new String[]{"app_id", "user_id"})) { throw new DuplicateUserIdException(); } else if (isForeignKeyConstraintError( serverMessage, @@ -780,7 +798,8 @@ public void addPasswordResetToken(AppIdentifier appIdentifier, PasswordResetToke if (e instanceof SQLiteException) { String serverMessage = e.getMessage(); - if (isPrimaryKeyError(serverMessage, Config.getConfig(this).getPasswordResetTokensTable(), new String[]{"app_id", "user_id", "token"})) { + if (isPrimaryKeyError(serverMessage, Config.getConfig(this).getPasswordResetTokensTable(), + new String[]{"app_id", "user_id", "token"})) { throw new DuplicatePasswordResetTokenException(); } else if (isForeignKeyConstraintError( serverMessage, @@ -861,7 +880,8 @@ public void updateUsersEmail_Transaction(AppIdentifier appIdentifier, Transactio EmailPasswordQueries.updateUsersEmail_Transaction(this, sqlCon, appIdentifier, userId, email); } catch (SQLException e) { if (isUniqueConstraintError(e.getMessage(), - Config.getConfig(this).getEmailPasswordUserToTenantTable(), new String[]{"app_id", "tenant_id", "email"})) { + Config.getConfig(this).getEmailPasswordUserToTenantTable(), + new String[]{"app_id", "tenant_id", "email"})) { throw new DuplicateEmailException(); } throw new StorageQueryException(e); @@ -984,7 +1004,8 @@ public void addEmailVerificationToken(TenantIdentifier tenantIdentifier, SQLiteConfig config = Config.getConfig(this); String serverMessage = e.getMessage(); - if (isPrimaryKeyError(serverMessage, config.getEmailVerificationTokensTable(), new String[]{"app_id", "tenant_id", "user_id", "email", "token"})) { + if (isPrimaryKeyError(serverMessage, config.getEmailVerificationTokensTable(), + new String[]{"app_id", "tenant_id", "user_id", "email", "token"})) { throw new DuplicateEmailVerificationTokenException(); } @@ -1093,7 +1114,7 @@ public void updateUserEmail_Transaction(AppIdentifier appIdentifier, Transaction @Override public io.supertokens.pluginInterface.thirdparty.UserInfo signUp( TenantIdentifier tenantIdentifier, String id, String email, - io.supertokens.pluginInterface.thirdparty.UserInfo.ThirdParty thirdParty, long timeJoined) + LoginMethod.ThirdParty thirdParty, long timeJoined) throws StorageQueryException, io.supertokens.pluginInterface.thirdparty.exception.DuplicateUserIdException, DuplicateThirdPartyUserException, TenantOrAppNotFoundException { try { @@ -1107,10 +1128,14 @@ public io.supertokens.pluginInterface.thirdparty.UserInfo signUp( new String[]{"app_id", "tenant_id", "third_party_id", "third_party_user_id"})) { throw new DuplicateThirdPartyUserException(); - } else if (isPrimaryKeyError(serverMessage, config.getThirdPartyUsersTable(), new String[]{"app_id", "user_id"}) - || isPrimaryKeyError(serverMessage, config.getUsersTable(), new String[]{"app_id", "tenant_id", "user_id"}) - || isPrimaryKeyError(serverMessage, config.getThirdPartyUserToTenantTable(), new String[]{"app_id", "tenant_id", "user_id"}) - || isPrimaryKeyError(serverMessage, config.getAppIdToUserIdTable(), new String[]{"app_id", "user_id"})) { + } else if (isPrimaryKeyError(serverMessage, config.getThirdPartyUsersTable(), + new String[]{"app_id", "user_id"}) + || isPrimaryKeyError(serverMessage, config.getUsersTable(), + new String[]{"app_id", "tenant_id", "user_id"}) + || isPrimaryKeyError(serverMessage, config.getThirdPartyUserToTenantTable(), + new String[]{"app_id", "tenant_id", "user_id"}) + || isPrimaryKeyError(serverMessage, config.getAppIdToUserIdTable(), + new String[]{"app_id", "user_id"})) { throw new io.supertokens.pluginInterface.thirdparty.exception.DuplicateUserIdException(); } else if (isForeignKeyConstraintError( @@ -1300,7 +1325,8 @@ public void setJWTSigningKey_Transaction(AppIdentifier appIdentifier, Transactio SQLiteConfig config = Config.getConfig(this); String serverMessage = e.getMessage(); - if (isPrimaryKeyError(serverMessage, config.getJWTSigningKeysTable(), new String[]{"app_id", "key_id"})) { + if (isPrimaryKeyError(serverMessage, config.getJWTSigningKeysTable(), + new String[]{"app_id", "key_id"})) { throw new DuplicateKeyIdException(); } @@ -1322,7 +1348,8 @@ private boolean isUniqueConstraintError(String serverMessage, String tableName, return isPrimaryKeyError(serverMessage, tableName, columnNames); } - private boolean isForeignKeyConstraintError(String serverMessage, String tableName, String[] columnNames, Object[] values) { + private boolean isForeignKeyConstraintError(String serverMessage, String tableName, String[] columnNames, + Object[] values) { if (!serverMessage.contains("FOREIGN KEY constraint failed")) { return false; } @@ -1496,7 +1523,8 @@ public void deleteCode_Transaction(TenantIdentifier tenantIdentifier, Transactio } @Override - public void updateUserEmail_Transaction(AppIdentifier appIdentifier, TransactionConnection con, String userId, String email) + public void updateUserEmail_Transaction(AppIdentifier appIdentifier, TransactionConnection con, String userId, + String email) throws StorageQueryException, UnknownUserIdException, DuplicateEmailException { Connection sqlCon = (Connection) con.getConnection(); try { @@ -1508,7 +1536,8 @@ public void updateUserEmail_Transaction(AppIdentifier appIdentifier, Transaction } catch (SQLException e) { if (e instanceof SQLiteException) { if (isUniqueConstraintError(e.getMessage(), - Config.getConfig(this).getPasswordlessUserToTenantTable(), new String[]{"app_id", "tenant_id", "email"})) { + Config.getConfig(this).getPasswordlessUserToTenantTable(), + new String[]{"app_id", "tenant_id", "email"})) { throw new DuplicateEmailException(); } } @@ -1533,7 +1562,8 @@ public void updateUserPhoneNumber_Transaction(AppIdentifier appIdentifier, Trans } catch (SQLException e) { if (e instanceof SQLiteException) { if (isUniqueConstraintError(e.getMessage(), - Config.getConfig(this).getPasswordlessUserToTenantTable(), new String[]{"app_id", "tenant_id", "phone_number"})) { + Config.getConfig(this).getPasswordlessUserToTenantTable(), + new String[]{"app_id", "tenant_id", "phone_number"})) { throw new DuplicatePhoneNumberException(); } } @@ -1559,13 +1589,16 @@ public void createDeviceWithCode(TenantIdentifier tenantIdentifier, @Nullable St String serverMessage = e.actualException.getMessage(); SQLiteConfig config = Config.getConfig(this); - if (isPrimaryKeyError(serverMessage, config.getPasswordlessDevicesTable(), new String[]{"app_id", "tenant_id", "device_id_hash"})) { + if (isPrimaryKeyError(serverMessage, config.getPasswordlessDevicesTable(), + new String[]{"app_id", "tenant_id", "device_id_hash"})) { throw new DuplicateDeviceIdHashException(); } - if (isPrimaryKeyError(serverMessage, config.getPasswordlessCodesTable(), new String[]{"app_id", "tenant_id", "code_id"})) { + if (isPrimaryKeyError(serverMessage, config.getPasswordlessCodesTable(), + new String[]{"app_id", "tenant_id", "code_id"})) { throw new DuplicateCodeIdException(); } - if (isUniqueConstraintError(serverMessage, config.getPasswordlessCodesTable(), new String[]{"app_id", "tenant_id", "link_code_hash"})) { + if (isUniqueConstraintError(serverMessage, config.getPasswordlessCodesTable(), + new String[]{"app_id", "tenant_id", "link_code_hash"})) { throw new DuplicateLinkCodeHashException(); } if (isForeignKeyConstraintError( @@ -1582,7 +1615,8 @@ public void createDeviceWithCode(TenantIdentifier tenantIdentifier, @Nullable St } @Override - public void createCode(TenantIdentifier tenantIdentifier, PasswordlessCode code) throws StorageQueryException, UnknownDeviceIdHash, + public void createCode(TenantIdentifier tenantIdentifier, PasswordlessCode code) + throws StorageQueryException, UnknownDeviceIdHash, DuplicateCodeIdException, DuplicateLinkCodeHashException { try { @@ -1615,8 +1649,10 @@ public void createCode(TenantIdentifier tenantIdentifier, PasswordlessCode code) @Override public io.supertokens.pluginInterface.passwordless.UserInfo createUser(TenantIdentifier tenantIdentifier, - String id, @javax.annotation.Nullable String email, - @javax.annotation.Nullable String phoneNumber, long timeJoined) + String id, + @javax.annotation.Nullable String email, + @javax.annotation.Nullable + String phoneNumber, long timeJoined) throws StorageQueryException, DuplicateEmailException, DuplicatePhoneNumberException, DuplicateUserIdException, TenantOrAppNotFoundException { @@ -1627,10 +1663,14 @@ public io.supertokens.pluginInterface.passwordless.UserInfo createUser(TenantIde SQLiteConfig config = Config.getConfig(this); String serverMessage = e.actualException.getMessage(); - if (isPrimaryKeyError(serverMessage, config.getPasswordlessUsersTable(), new String[]{"app_id", "user_id"}) - || isPrimaryKeyError(serverMessage, config.getUsersTable(), new String[]{"app_id", "tenant_id", "user_id"}) - || isPrimaryKeyError(serverMessage, config.getPasswordlessUserToTenantTable(), new String[]{"app_id", "tenant_id", "user_id"}) - || isPrimaryKeyError(serverMessage, config.getAppIdToUserIdTable(), new String[]{"app_id", "user_id"})) { + if (isPrimaryKeyError(serverMessage, config.getPasswordlessUsersTable(), + new String[]{"app_id", "user_id"}) + || isPrimaryKeyError(serverMessage, config.getUsersTable(), + new String[]{"app_id", "tenant_id", "user_id"}) + || isPrimaryKeyError(serverMessage, config.getPasswordlessUserToTenantTable(), + new String[]{"app_id", "tenant_id", "user_id"}) + || isPrimaryKeyError(serverMessage, config.getAppIdToUserIdTable(), + new String[]{"app_id", "user_id"})) { throw new DuplicateUserIdException(); } @@ -1640,7 +1680,8 @@ public io.supertokens.pluginInterface.passwordless.UserInfo createUser(TenantIde } if (isUniqueConstraintError(serverMessage, - config.getPasswordlessUserToTenantTable(), new String[]{"app_id", "tenant_id", "phone_number"})) { + config.getPasswordlessUserToTenantTable(), + new String[]{"app_id", "tenant_id", "phone_number"})) { throw new DuplicatePhoneNumberException(); } @@ -1756,7 +1797,8 @@ public io.supertokens.pluginInterface.passwordless.UserInfo getUserById(AppIdent } @Override - public io.supertokens.pluginInterface.passwordless.UserInfo getUserByEmail(TenantIdentifier tenantIdentifier, String email) + public io.supertokens.pluginInterface.passwordless.UserInfo getUserByEmail(TenantIdentifier tenantIdentifier, + String email) throws StorageQueryException { try { return PasswordlessQueries.getUserByEmail(this, tenantIdentifier, email); @@ -1766,7 +1808,8 @@ public io.supertokens.pluginInterface.passwordless.UserInfo getUserByEmail(Tenan } @Override - public io.supertokens.pluginInterface.passwordless.UserInfo getUserByPhoneNumber(TenantIdentifier tenantIdentifier, String phoneNumber) + public io.supertokens.pluginInterface.passwordless.UserInfo getUserByPhoneNumber(TenantIdentifier tenantIdentifier, + String phoneNumber) throws StorageQueryException { try { return PasswordlessQueries.getUserByPhoneNumber(this, tenantIdentifier, phoneNumber); @@ -1797,7 +1840,8 @@ public JsonObject getUserMetadata_Transaction(AppIdentifier appIdentifier, Trans } @Override - public int setUserMetadata_Transaction(AppIdentifier appIdentifier, TransactionConnection con, String userId, JsonObject metadata) + public int setUserMetadata_Transaction(AppIdentifier appIdentifier, TransactionConnection con, String userId, + JsonObject metadata) throws StorageQueryException, TenantOrAppNotFoundException { Connection sqlCon = (Connection) con.getConnection(); try { @@ -1847,7 +1891,8 @@ public void addRoleToUser(TenantIdentifier tenantIdentifier, String userId, Stri new Object[]{tenantIdentifier.getAppId(), role})) { throw new UnknownRoleException(); } - if (isPrimaryKeyError(serverErrorMessage, config.getUserRolesTable(), new String[]{"app_id", "tenant_id", "user_id", "role"})) { + if (isPrimaryKeyError(serverErrorMessage, config.getUserRolesTable(), + new String[]{"app_id", "tenant_id", "user_id", "role"})) { throw new DuplicateUserRoleMappingException(); } if (isForeignKeyConstraintError( @@ -1960,7 +2005,8 @@ public void deleteAllRolesForUser(AppIdentifier appIdentifier, String userId) th } @Override - public boolean deleteRoleForUser_Transaction(TenantIdentifier tenantIdentifier, TransactionConnection con, String userId, String role) + public boolean deleteRoleForUser_Transaction(TenantIdentifier tenantIdentifier, TransactionConnection con, + String userId, String role) throws StorageQueryException { Connection sqlCon = (Connection) con.getConnection(); try { @@ -2022,7 +2068,8 @@ public void addPermissionToRoleOrDoNothingIfExists_Transaction(AppIdentifier app } @Override - public boolean deletePermissionForRole_Transaction(AppIdentifier appIdentifier, TransactionConnection con, String role, String permission) + public boolean deletePermissionForRole_Transaction(AppIdentifier appIdentifier, TransactionConnection con, + String role, String permission) throws StorageQueryException { Connection sqlCon = (Connection) con.getConnection(); try { @@ -2034,7 +2081,8 @@ public boolean deletePermissionForRole_Transaction(AppIdentifier appIdentifier, } @Override - public int deleteAllPermissionsForRole_Transaction(AppIdentifier appIdentifier, TransactionConnection con, String role) + public int deleteAllPermissionsForRole_Transaction(AppIdentifier appIdentifier, TransactionConnection con, + String role) throws StorageQueryException { Connection sqlCon = (Connection) con.getConnection(); try { @@ -2046,7 +2094,8 @@ public int deleteAllPermissionsForRole_Transaction(AppIdentifier appIdentifier, } @Override - public boolean doesRoleExist_Transaction(AppIdentifier appIdentifier, TransactionConnection con, String role) throws StorageQueryException { + public boolean doesRoleExist_Transaction(AppIdentifier appIdentifier, TransactionConnection con, String role) + throws StorageQueryException { Connection sqlCon = (Connection) con.getConnection(); try { return UserRoleQueries.doesRoleExist_transaction(this, sqlCon, appIdentifier, role); @@ -2075,7 +2124,8 @@ public void createUserIdMapping(AppIdentifier appIdentifier, String superTokensU throw new UnknownSuperTokensUserIdException(); } - if (isPrimaryKeyError(serverErrorMessage, config.getUserIdMappingTable(), new String[]{"app_id", "supertokens_user_id", "external_user_id"})) { + if (isPrimaryKeyError(serverErrorMessage, config.getUserIdMappingTable(), + new String[]{"app_id", "supertokens_user_id", "external_user_id"})) { throw new UserIdMappingAlreadyExistsException(true, true); } @@ -2095,7 +2145,8 @@ public void createUserIdMapping(AppIdentifier appIdentifier, String superTokensU } @Override - public boolean deleteUserIdMapping(AppIdentifier appIdentifier, String userId, boolean isSuperTokensUserId) throws StorageQueryException { + public boolean deleteUserIdMapping(AppIdentifier appIdentifier, String userId, boolean isSuperTokensUserId) + throws StorageQueryException { try { if (isSuperTokensUserId) { return UserIdMappingQueries.deleteUserIdMappingWithSuperTokensUserId(this, appIdentifier, @@ -2110,7 +2161,8 @@ public boolean deleteUserIdMapping(AppIdentifier appIdentifier, String userId, b } @Override - public UserIdMapping getUserIdMapping(AppIdentifier appIdentifier, String userId, boolean isSuperTokensUserId) throws StorageQueryException { + public UserIdMapping getUserIdMapping(AppIdentifier appIdentifier, String userId, boolean isSuperTokensUserId) + throws StorageQueryException { try { if (isSuperTokensUserId) { return UserIdMappingQueries.getuseraIdMappingWithSuperTokensUserId(this, appIdentifier, @@ -2173,13 +2225,17 @@ public void createTenant(TenantConfig tenantConfig) if (e.actualException instanceof SQLiteException) { String errorMessage = e.actualException.getMessage(); SQLiteConfig config = Config.getConfig(this); - if (isPrimaryKeyError(errorMessage, config.getTenantConfigsTable(), new String[]{"connection_uri_domain", "app_id", "tenant_id"})) { + if (isPrimaryKeyError(errorMessage, config.getTenantConfigsTable(), + new String[]{"connection_uri_domain", "app_id", "tenant_id"})) { throw new DuplicateTenantException(); } - if (isPrimaryKeyError(errorMessage, config.getTenantThirdPartyProvidersTable(), new String[]{"connection_uri_domain", "app_id", "tenant_id", "third_party_id"})) { + if (isPrimaryKeyError(errorMessage, config.getTenantThirdPartyProvidersTable(), + new String[]{"connection_uri_domain", "app_id", "tenant_id", "third_party_id"})) { throw new DuplicateThirdPartyIdException(); } - if (isPrimaryKeyError(errorMessage, config.getTenantThirdPartyProviderClientsTable(), new String[]{"connection_uri_domain", "app_id", "tenant_id", "third_party_id", "client_type"})) { + if (isPrimaryKeyError(errorMessage, config.getTenantThirdPartyProviderClientsTable(), + new String[]{"connection_uri_domain", "app_id", "tenant_id", "third_party_id", + "client_type"})) { throw new DuplicateClientTypeException(); } } @@ -2195,7 +2251,8 @@ public void addTenantIdInTargetStorage(TenantIdentifier tenantIdentifier) } catch (StorageTransactionLogicException e) { if (e.actualException instanceof SQLiteException) { String errorMessage = e.actualException.getMessage(); - if (isPrimaryKeyError(errorMessage, Config.getConfig(this).getTenantsTable(), new String[]{"app_id", "tenant_id"})) { + if (isPrimaryKeyError(errorMessage, Config.getConfig(this).getTenantsTable(), + new String[]{"app_id", "tenant_id"})) { throw new DuplicateTenantException(); } } @@ -2216,12 +2273,15 @@ public void overwriteTenantConfig(TenantConfig tenantConfig) if (e.actualException instanceof SQLiteException) { SQLiteConfig config = Config.getConfig(this); if (isPrimaryKeyError(e.actualException.getMessage(), - config.getTenantThirdPartyProvidersTable(), new String[]{"connection_uri_domain", "app_id", "tenant_id", "third_party_id"})) { + config.getTenantThirdPartyProvidersTable(), + new String[]{"connection_uri_domain", "app_id", "tenant_id", "third_party_id"})) { throw new DuplicateThirdPartyIdException(); } if (isPrimaryKeyError(e.actualException.getMessage(), - config.getTenantThirdPartyProviderClientsTable(), new String[]{"connection_uri_domain", "app_id", "tenant_id", "third_party_id", "client_type"})) { + config.getTenantThirdPartyProviderClientsTable(), + new String[]{"connection_uri_domain", "app_id", "tenant_id", "third_party_id", + "client_type"})) { throw new DuplicateClientTypeException(); } } @@ -2271,11 +2331,13 @@ public boolean addUserIdToTenant(TenantIdentifier tenantIdentifier, String userI boolean added; if (recipeId.equals("emailpassword")) { - added = EmailPasswordQueries.addUserIdToTenant_Transaction(this, sqlCon, tenantIdentifier, userId); + added = EmailPasswordQueries.addUserIdToTenant_Transaction(this, sqlCon, tenantIdentifier, + userId); } else if (recipeId.equals("thirdparty")) { added = ThirdPartyQueries.addUserIdToTenant_Transaction(this, sqlCon, tenantIdentifier, userId); } else if (recipeId.equals("passwordless")) { - added = PasswordlessQueries.addUserIdToTenant_Transaction(this, sqlCon, tenantIdentifier, userId); + added = PasswordlessQueries.addUserIdToTenant_Transaction(this, sqlCon, tenantIdentifier, + userId); } else { throw new IllegalStateException("Should never come here!"); } @@ -2298,18 +2360,22 @@ public boolean addUserIdToTenant(TenantIdentifier tenantIdentifier, String userI new Object[]{tenantIdentifier.getAppId(), tenantIdentifier.getTenantId()})) { throw new TenantOrAppNotFoundException(tenantIdentifier); } - if (isUniqueConstraintError(serverErrorMessage, config.getEmailPasswordUserToTenantTable(), new String[]{"app_id", "tenant_id", "email"})) { + if (isUniqueConstraintError(serverErrorMessage, config.getEmailPasswordUserToTenantTable(), + new String[]{"app_id", "tenant_id", "email"})) { throw new DuplicateEmailException(); } - if (isUniqueConstraintError(serverErrorMessage, config.getThirdPartyUserToTenantTable(), new String[]{"app_id", "tenant_id", "third_party_id", "third_party_user_id"})) { + if (isUniqueConstraintError(serverErrorMessage, config.getThirdPartyUserToTenantTable(), + new String[]{"app_id", "tenant_id", "third_party_id", "third_party_user_id"})) { throw new DuplicateThirdPartyUserException(); } if (isUniqueConstraintError(serverErrorMessage, - Config.getConfig(this).getPasswordlessUserToTenantTable(), new String[]{"app_id", "tenant_id", "phone_number"})) { + Config.getConfig(this).getPasswordlessUserToTenantTable(), + new String[]{"app_id", "tenant_id", "phone_number"})) { throw new DuplicatePhoneNumberException(); } if (isUniqueConstraintError(serverErrorMessage, - Config.getConfig(this).getPasswordlessUserToTenantTable(), new String[]{"app_id", "tenant_id", "email"})) { + Config.getConfig(this).getPasswordlessUserToTenantTable(), + new String[]{"app_id", "tenant_id", "email"})) { throw new DuplicateEmailException(); } @@ -2340,11 +2406,14 @@ public boolean removeUserIdFromTenant(TenantIdentifier tenantIdentifier, String boolean removed; if (recipeId.equals("emailpassword")) { - removed = EmailPasswordQueries.removeUserIdFromTenant_Transaction(this, sqlCon, tenantIdentifier, userId); + removed = EmailPasswordQueries.removeUserIdFromTenant_Transaction(this, sqlCon, + tenantIdentifier, userId); } else if (recipeId.equals("thirdparty")) { - removed = ThirdPartyQueries.removeUserIdFromTenant_Transaction(this, sqlCon, tenantIdentifier, userId); + removed = ThirdPartyQueries.removeUserIdFromTenant_Transaction(this, sqlCon, tenantIdentifier, + userId); } else if (recipeId.equals("passwordless")) { - removed = PasswordlessQueries.removeUserIdFromTenant_Transaction(this, sqlCon, tenantIdentifier, userId); + removed = PasswordlessQueries.removeUserIdFromTenant_Transaction(this, sqlCon, tenantIdentifier, + userId); } else { throw new IllegalStateException("Should never come here!"); } @@ -2366,7 +2435,8 @@ public boolean removeUserIdFromTenant(TenantIdentifier tenantIdentifier, String } @Override - public boolean deleteDashboardUserWithUserId(AppIdentifier appIdentifier, String userId) throws StorageQueryException { + public boolean deleteDashboardUserWithUserId(AppIdentifier appIdentifier, String userId) + throws StorageQueryException { try { return DashboardQueries.deleteDashboardUserWithUserId(this, appIdentifier, userId); } catch (SQLException e) { @@ -2414,7 +2484,8 @@ public DashboardSessionInfo getSessionInfoWithSessionId(AppIdentifier appIdentif } @Override - public boolean revokeSessionWithSessionId(AppIdentifier appIdentifier, String sessionId) throws StorageQueryException { + public boolean revokeSessionWithSessionId(AppIdentifier appIdentifier, String sessionId) + throws StorageQueryException { try { return DashboardQueries.deleteDashboardUserSessionWithSessionId(this, appIdentifier, sessionId); @@ -2424,7 +2495,8 @@ public boolean revokeSessionWithSessionId(AppIdentifier appIdentifier, String se } @Override - public void updateDashboardUsersEmailWithUserId_Transaction(AppIdentifier appIdentifier, TransactionConnection con, String userId, + public void updateDashboardUsersEmailWithUserId_Transaction(AppIdentifier appIdentifier, TransactionConnection con, + String userId, String newEmail) throws StorageQueryException, io.supertokens.pluginInterface.dashboard.exceptions.DuplicateEmailException, UserIdNotFoundException { Connection sqlCon = (Connection) con.getConnection(); @@ -2497,7 +2569,8 @@ public void createNewDashboardUser(AppIdentifier appIdentifier, DashboardUser us SQLiteConfig config = Config.getConfig(this); String serverErrorMessage = e.getMessage(); - if (isPrimaryKeyError(serverErrorMessage, config.getDashboardUsersTable(), new String[]{"app_id", "user_id"})) { + if (isPrimaryKeyError(serverErrorMessage, config.getDashboardUsersTable(), + new String[]{"app_id", "user_id"})) { throw new io.supertokens.pluginInterface.dashboard.exceptions.DuplicateUserIdException(); } if (isUniqueConstraintError(serverErrorMessage, config.getDashboardUsersTable(), @@ -2546,7 +2619,8 @@ public void createDevice(AppIdentifier appIdentifier, TOTPDevice device) if (e.actualException instanceof SQLiteException) { String errMsg = e.actualException.getMessage(); - if (isPrimaryKeyError(errMsg, Config.getConfig(this).getTotpUserDevicesTable(), new String[]{"app_id", "user_id", "device_name"})) { + if (isPrimaryKeyError(errMsg, Config.getConfig(this).getTotpUserDevicesTable(), + new String[]{"app_id", "user_id", "device_name"})) { throw new DeviceAlreadyExistsException(); } else if (isForeignKeyConstraintError( errMsg, @@ -2621,11 +2695,12 @@ public void updateDeviceName(AppIdentifier appIdentifier, String userId, String } catch (SQLException e) { if (e instanceof SQLiteException) { String errMsg = e.getMessage(); - if (isPrimaryKeyError(errMsg, Config.getConfig(this).getTotpUserDevicesTable(), new String[]{"app_id", "user_id", "device_name"})) { + if (isPrimaryKeyError(errMsg, Config.getConfig(this).getTotpUserDevicesTable(), + new String[]{"app_id", "user_id", "device_name"})) { throw new DeviceAlreadyExistsException(); } } - throw new StorageQueryException(e); + throw new StorageQueryException(e); } } @@ -2659,7 +2734,8 @@ public void insertUsedCode_Transaction(TransactionConnection con, TenantIdentifi try { TOTPQueries.insertUsedCode_Transaction(this, sqlCon, tenantIdentifier, usedCodeObj); } catch (SQLException e) { - if (isPrimaryKeyError(e.getMessage(), Config.getConfig(this).getTotpUsedCodesTable(), new String[]{"app_id", "tenant_id", "user_id", "created_time_ms"})) { + if (isPrimaryKeyError(e.getMessage(), Config.getConfig(this).getTotpUsedCodesTable(), + new String[]{"app_id", "tenant_id", "user_id", "created_time_ms"})) { throw new UsedCodeAlreadyExistsException(); } else if (isForeignKeyConstraintError( diff --git a/src/main/java/io/supertokens/inmemorydb/Utils.java b/src/main/java/io/supertokens/inmemorydb/Utils.java new file mode 100644 index 000000000..ce8bfa367 --- /dev/null +++ b/src/main/java/io/supertokens/inmemorydb/Utils.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2023, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.inmemorydb; + +public class Utils { + public static String generateCommaSeperatedQuestionMarks(int size) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < size; i++) { + builder.append("?"); + if (i != size - 1) { + builder.append(","); + } + } + return builder.toString(); + } +} diff --git a/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java index f46b1c0e2..c2c675fb4 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java @@ -18,22 +18,23 @@ import io.supertokens.inmemorydb.ConnectionPool; import io.supertokens.inmemorydb.ConnectionWithLocks; -import io.supertokens.inmemorydb.ResultSetValueExtractor; import io.supertokens.inmemorydb.Start; +import io.supertokens.inmemorydb.Utils; import io.supertokens.inmemorydb.config.Config; import io.supertokens.pluginInterface.RowMapper; +import io.supertokens.pluginInterface.authRecipe.LoginMethod; import io.supertokens.pluginInterface.emailpassword.PasswordResetTokenInfo; import io.supertokens.pluginInterface.emailpassword.UserInfo; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; -import org.jetbrains.annotations.NotNull; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.util.*; +import java.util.stream.Collectors; import static io.supertokens.inmemorydb.QueryExecutorTemplate.execute; import static io.supertokens.inmemorydb.QueryExecutorTemplate.update; @@ -52,7 +53,8 @@ static String getQueryToCreateUsersTable(Start start) { + "time_joined BIGINT UNSIGNED NOT NULL," + "PRIMARY KEY (app_id, user_id)," + "FOREIGN KEY(app_id, user_id)" - + " REFERENCES " + Config.getConfig(start).getAppIdToUserIdTable() + " (app_id, user_id) ON DELETE CASCADE" + + " REFERENCES " + Config.getConfig(start).getAppIdToUserIdTable() + + " (app_id, user_id) ON DELETE CASCADE" + ");"; } @@ -67,7 +69,8 @@ static String getQueryToCreateEmailPasswordUserToTenantTable(Start start) { + "UNIQUE (app_id, tenant_id, email)," + "PRIMARY KEY (app_id, tenant_id, user_id)," + "FOREIGN KEY (app_id, tenant_id, user_id)" - + " REFERENCES " + Config.getConfig(start).getUsersTable() + "(app_id, tenant_id, user_id) ON DELETE CASCADE" + + " REFERENCES " + Config.getConfig(start).getUsersTable() + + "(app_id, tenant_id, user_id) ON DELETE CASCADE" + ");"; // @formatter:on } @@ -95,7 +98,8 @@ public static void deleteExpiredPasswordResetTokens(Start start) throws SQLExcep update(start, QUERY, pst -> pst.setLong(1, currentTimeMillis())); } - public static void updateUsersPassword_Transaction(Start start, Connection con, AppIdentifier appIdentifier, String userId, String newPassword) + public static void updateUsersPassword_Transaction(Start start, Connection con, AppIdentifier appIdentifier, + String userId, String newPassword) throws SQLException, StorageQueryException { String QUERY = "UPDATE " + getConfig(start).getEmailPasswordUsersTable() + " SET password_hash = ? WHERE app_id = ? AND user_id = ?"; @@ -107,7 +111,8 @@ public static void updateUsersPassword_Transaction(Start start, Connection con, }); } - public static void updateUsersEmail_Transaction(Start start, Connection con, AppIdentifier appIdentifier, String userId, String newEmail) + public static void updateUsersEmail_Transaction(Start start, Connection con, AppIdentifier appIdentifier, + String userId, String newEmail) throws SQLException, StorageQueryException { { String QUERY = "UPDATE " + getConfig(start).getEmailPasswordUsersTable() @@ -131,10 +136,12 @@ public static void updateUsersEmail_Transaction(Start start, Connection con, App } } - public static void deleteAllPasswordResetTokensForUser_Transaction(Start start, Connection con, AppIdentifier appIdentifier, String userId) + public static void deleteAllPasswordResetTokensForUser_Transaction(Start start, Connection con, + AppIdentifier appIdentifier, String userId) throws SQLException, StorageQueryException { - String QUERY = "DELETE FROM " + getConfig(start).getPasswordResetTokensTable() + " WHERE app_id = ? AND user_id = ?"; + String QUERY = + "DELETE FROM " + getConfig(start).getPasswordResetTokensTable() + " WHERE app_id = ? AND user_id = ?"; update(con, QUERY, pst -> { pst.setString(1, appIdentifier.getAppId()); @@ -169,7 +176,8 @@ public static PasswordResetTokenInfo[] getAllPasswordResetTokenInfoForUser_Trans String userId) throws SQLException, StorageQueryException { - ((ConnectionWithLocks) con).lock(appIdentifier.getAppId() + "~" + userId + Config.getConfig(start).getPasswordResetTokensTable()); + ((ConnectionWithLocks) con).lock( + appIdentifier.getAppId() + "~" + userId + Config.getConfig(start).getPasswordResetTokensTable()); String QUERY = "SELECT user_id, token, token_expiry FROM " + getConfig(start).getPasswordResetTokensTable() @@ -195,7 +203,8 @@ public static UserInfo getUserInfoUsingId_Transaction(Start start, Connection co String id) throws SQLException, StorageQueryException { - ((ConnectionWithLocks) con).lock(appIdentifier.getAppId() + "~" + id + Config.getConfig(start).getEmailPasswordUsersTable()); + ((ConnectionWithLocks) con).lock( + appIdentifier.getAppId() + "~" + id + Config.getConfig(start).getEmailPasswordUsersTable()); String QUERY = "SELECT user_id, email, password_hash, time_joined FROM " + getConfig(start).getEmailPasswordUsersTable() @@ -209,10 +218,16 @@ public static UserInfo getUserInfoUsingId_Transaction(Start start, Connection co } return null; }); - return userInfoWithTenantIds_transaction(start, con, userInfo); + if (userInfo == null) { + return null; + } + fillUserInfoWithTenantIds_transaction(start, con, appIdentifier, userInfo); + fillUserInfoWithVerified_transaction(start, con, appIdentifier, userInfo); + return new UserInfo(userInfo.id, userInfo.verified, userInfo.toLoginMethod()); } - public static PasswordResetTokenInfo getPasswordResetTokenInfo(Start start, AppIdentifier appIdentifier, String token) + public static PasswordResetTokenInfo getPasswordResetTokenInfo(Start start, AppIdentifier appIdentifier, + String token) throws SQLException, StorageQueryException { String QUERY = "SELECT user_id, token, token_expiry FROM " + getConfig(start).getPasswordResetTokensTable() + " WHERE app_id = ? AND token = ?"; @@ -227,7 +242,8 @@ public static PasswordResetTokenInfo getPasswordResetTokenInfo(Start start, AppI }); } - public static void addPasswordResetToken(Start start, AppIdentifier appIdentifier, String userId, String tokenHash, long expiry) + public static void addPasswordResetToken(Start start, AppIdentifier appIdentifier, String userId, String tokenHash, + long expiry) throws SQLException, StorageQueryException { String QUERY = "INSERT INTO " + getConfig(start).getPasswordResetTokensTable() + "(app_id, user_id, token, token_expiry)" + " VALUES(?, ?, ?, ?)"; @@ -240,7 +256,8 @@ public static void addPasswordResetToken(Start start, AppIdentifier appIdentifie }); } - public static UserInfo signUp(Start start, TenantIdentifier tenantIdentifier, String userId, String email, String passwordHash, long timeJoined) + public static UserInfo signUp(Start start, TenantIdentifier tenantIdentifier, String userId, String email, + String passwordHash, long timeJoined) throws StorageQueryException, StorageTransactionLogicException { return start.startTransaction(con -> { Connection sqlCon = (Connection) con.getConnection(); @@ -292,10 +309,11 @@ public static UserInfo signUp(Start start, TenantIdentifier tenantIdentifier, St }); } - UserInfo userInfo = userInfoWithTenantIds_transaction(start, sqlCon, new UserInfoPartial(userId, email, passwordHash, timeJoined)); - + UserInfoPartial userInfo = new UserInfoPartial(userId, email, passwordHash, timeJoined); + fillUserInfoWithTenantIds_transaction(start, sqlCon, tenantIdentifier.toAppIdentifier(), userInfo); + fillUserInfoWithVerified_transaction(start, sqlCon, tenantIdentifier.toAppIdentifier(), userInfo); sqlCon.commit(); - return userInfo; + return new UserInfo(userId, userInfo.verified, userInfo.toLoginMethod()); } catch (SQLException throwables) { throw new StorageTransactionLogicException(throwables); } @@ -325,24 +343,34 @@ public static void deleteUser(Start start, AppIdentifier appIdentifier, String u }); } - public static UserInfo getUserInfoUsingId(Start start, AppIdentifier appIdentifier, String id) throws SQLException, StorageQueryException { + public static UserInfo getUserInfoUsingId(Start start, AppIdentifier appIdentifier, String id) + throws SQLException, StorageQueryException { String QUERY = "SELECT user_id, email, password_hash, time_joined FROM " + getConfig(start).getEmailPasswordUsersTable() + " WHERE app_id = ? AND user_id = ?"; - UserInfoPartial userInfo = execute(start, QUERY.toString(), pst -> { - pst.setString(1, appIdentifier.getAppId()); - pst.setString(2, id); - }, result -> { - if (result.next()) { - return UserInfoRowMapper.getInstance().mapOrThrow(result); + try (Connection con = ConnectionPool.getConnection(start)) { + UserInfoPartial userInfo = execute(con, QUERY.toString(), pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setString(2, id); + }, result -> { + if (result.next()) { + return UserInfoRowMapper.getInstance().mapOrThrow(result); + } + return null; + }); + if (userInfo == null) { + return null; } - return null; - }); - return userInfoWithTenantIds(start, userInfo); + fillUserInfoWithTenantIds_transaction(start, con, appIdentifier, userInfo); + fillUserInfoWithVerified_transaction(start, con, appIdentifier, userInfo); + return new UserInfo(userInfo.id, userInfo.verified, userInfo.toLoginMethod()); + } } - public static UserInfoPartial getUserInfoUsingId(Start start, Connection sqlCon, AppIdentifier appIdentifier, String id) throws SQLException, StorageQueryException { - // we don't need a LOCK here because this is already part of a transaction, and locked on app_id_to_user_id table + public static UserInfoPartial getUserInfoUsingId(Start start, Connection sqlCon, AppIdentifier appIdentifier, + String id) throws SQLException, StorageQueryException { + // we don't need a LOCK here because this is already part of a transaction, and locked on app_id_to_user_id + // table String QUERY = "SELECT user_id, email, password_hash, time_joined FROM " + getConfig(start).getEmailPasswordUsersTable() + " WHERE app_id = ? AND user_id = ?"; @@ -357,59 +385,68 @@ public static UserInfoPartial getUserInfoUsingId(Start start, Connection sqlCon, }); } - public static List getUsersInfoUsingIdList(Start start, List ids) + public static List getUsersInfoUsingIdList(Start start, Set ids, AppIdentifier appIdentifier) throws SQLException, StorageQueryException { if (ids.size() > 0) { // No need to filter based on tenantId because the id list is already filtered for a tenant - StringBuilder QUERY = new StringBuilder("SELECT user_id, email, password_hash, time_joined " - + "FROM " + getConfig(start).getEmailPasswordUsersTable()); - QUERY.append(" WHERE user_id IN ("); - for (int i = 0; i < ids.size(); i++) { - - QUERY.append("?"); - if (i != ids.size() - 1) { - // not the last element - QUERY.append(","); - } + String QUERY = "SELECT user_id, email, password_hash, time_joined " + + "FROM " + getConfig(start).getEmailPasswordUsersTable() + " WHERE user_id IN (" + + Utils.generateCommaSeperatedQuestionMarks(ids.size()) + ") AND app_id = ?"; + + try (Connection con = ConnectionPool.getConnection(start)) { + List userInfos = execute(con, QUERY, pst -> { + int index = 1; + for (String id : ids) { + pst.setString(index, id); + index++; + } + pst.setString(index, appIdentifier.getAppId()); + }, result -> { + List finalResult = new ArrayList<>(); + while (result.next()) { + finalResult.add(UserInfoRowMapper.getInstance().mapOrThrow(result)); + } + return finalResult; + }); + fillUserInfoWithTenantIds_transaction(start, con, appIdentifier, userInfos); + fillUserInfoWithVerified_transaction(start, con, appIdentifier, userInfos); + return userInfos.stream().map(UserInfoPartial::toLoginMethod) + .collect(Collectors.toList()); } - QUERY.append(")"); - - List userInfos = execute(start, QUERY.toString(), pst -> { - for (int i = 0; i < ids.size(); i++) { - // i+1 cause this starts with 1 and not 0 - pst.setString(i + 1, ids.get(i)); - } - }, result -> { - List finalResult = new ArrayList<>(); - while (result.next()) { - finalResult.add(UserInfoRowMapper.getInstance().mapOrThrow(result)); - } - return finalResult; - }); - return userInfoWithTenantIds(start, userInfos); } return Collections.emptyList(); } - public static UserInfo getUserInfoUsingEmail(Start start, TenantIdentifier tenantIdentifier, String email) throws StorageQueryException, SQLException { + public static UserInfo getUserInfoUsingEmail(Start start, TenantIdentifier tenantIdentifier, String email) + throws StorageQueryException, SQLException { String QUERY = "SELECT ep_users_to_tenant.user_id as user_id, ep_users_to_tenant.email as email, " + "ep_users.password_hash as password_hash, ep_users.time_joined as time_joined " + "FROM " + getConfig(start).getEmailPasswordUserToTenantTable() + " AS ep_users_to_tenant " + "JOIN " + getConfig(start).getEmailPasswordUsersTable() + " AS ep_users " + "ON ep_users.app_id = ep_users_to_tenant.app_id AND ep_users.user_id = ep_users_to_tenant.user_id " - + "WHERE ep_users_to_tenant.app_id = ? AND ep_users_to_tenant.tenant_id = ? AND ep_users_to_tenant.email = ?"; + + + "WHERE ep_users_to_tenant.app_id = ? AND ep_users_to_tenant.tenant_id = ? AND ep_users_to_tenant" + + ".email = ?"; - UserInfoPartial userInfo = execute(start, QUERY, pst -> { - pst.setString(1, tenantIdentifier.getAppId()); - pst.setString(2, tenantIdentifier.getTenantId()); - pst.setString(3, email); - }, result -> { - if (result.next()) { - return UserInfoRowMapper.getInstance().mapOrThrow(result); + try (Connection con = ConnectionPool.getConnection(start)) { + UserInfoPartial userInfo = execute(con, QUERY, pst -> { + pst.setString(1, tenantIdentifier.getAppId()); + pst.setString(2, tenantIdentifier.getTenantId()); + pst.setString(3, email); + }, result -> { + if (result.next()) { + return UserInfoRowMapper.getInstance().mapOrThrow(result); + } + return null; + }); + if (userInfo == null) { + return null; } - return null; - }); - return userInfoWithTenantIds(start, userInfo); + fillUserInfoWithTenantIds_transaction(start, con, tenantIdentifier.toAppIdentifier(), userInfo); + fillUserInfoWithVerified_transaction(start, con, tenantIdentifier.toAppIdentifier(), userInfo); + return new UserInfo(userInfo.id, userInfo.verified, + userInfo.toLoginMethod()); + } } public static boolean addUserIdToTenant_Transaction(Start start, Connection sqlCon, @@ -447,7 +484,8 @@ public static boolean addUserIdToTenant_Transaction(Start start, Connection sqlC } } - public static boolean removeUserIdFromTenant_Transaction(Start start, Connection sqlCon, TenantIdentifier tenantIdentifier, String userId) + public static boolean removeUserIdFromTenant_Transaction(Start start, Connection sqlCon, + TenantIdentifier tenantIdentifier, String userId) throws SQLException, StorageQueryException { { // all_auth_recipe_users String QUERY = "DELETE FROM " + getConfig(start).getUsersTable() @@ -463,42 +501,59 @@ public static boolean removeUserIdFromTenant_Transaction(Start start, Connection // automatically deleted from emailpassword_user_to_tenant because of foreign key constraint } - private static UserInfo userInfoWithTenantIds(Start start, UserInfoPartial userInfo) + private static UserInfoPartial fillUserInfoWithVerified_transaction(Start start, Connection sqlCon, + AppIdentifier appIdentifier, + UserInfoPartial userInfo) throws SQLException, StorageQueryException { if (userInfo == null) return null; - try (Connection con = ConnectionPool.getConnection(start)) { - return userInfoWithTenantIds_transaction(start, con, Arrays.asList(userInfo)).get(0); - } + return fillUserInfoWithVerified_transaction(start, sqlCon, appIdentifier, List.of(userInfo)).get(0); } - private static List userInfoWithTenantIds(Start start, List userInfos) + private static List fillUserInfoWithVerified_transaction(Start start, Connection sqlCon, + AppIdentifier appIdentifier, + List userInfos) throws SQLException, StorageQueryException { - try (Connection con = ConnectionPool.getConnection(start)) { - return userInfoWithTenantIds_transaction(start, con, userInfos); + List userIdsAndEmails = new ArrayList<>(); + for (UserInfoPartial userInfo : userInfos) { + userIdsAndEmails.add(new EmailVerificationQueries.UserIdAndEmail(userInfo.id, userInfo.email)); } + List userIdsThatAreVerified = EmailVerificationQueries.isEmailVerified_transaction(start, sqlCon, + appIdentifier, + userIdsAndEmails); + Set verifiedUserIdsSet = new HashSet<>(userIdsThatAreVerified); + for (UserInfoPartial userInfo : userInfos) { + if (verifiedUserIdsSet.contains(userInfo.id)) { + userInfo.verified = true; + } + } + return userInfos; } - private static UserInfo userInfoWithTenantIds_transaction(Start start, Connection sqlCon, UserInfoPartial userInfo) + private static UserInfoPartial fillUserInfoWithTenantIds_transaction(Start start, Connection sqlCon, + AppIdentifier appIdentifier, + UserInfoPartial userInfo) throws SQLException, StorageQueryException { if (userInfo == null) return null; - return userInfoWithTenantIds_transaction(start, sqlCon, Arrays.asList(userInfo)).get(0); + return fillUserInfoWithTenantIds_transaction(start, sqlCon, appIdentifier, Arrays.asList(userInfo)).get(0); } - private static List userInfoWithTenantIds_transaction(Start start, Connection sqlCon, List userInfos) + private static List fillUserInfoWithTenantIds_transaction(Start start, Connection sqlCon, + AppIdentifier appIdentifier, + List userInfos) throws SQLException, StorageQueryException { String[] userIds = new String[userInfos.size()]; for (int i = 0; i < userInfos.size(); i++) { userIds[i] = userInfos.get(i).id; } - Map> tenantIdsForUserIds = GeneralQueries.getTenantIdsForUserIds_transaction(start, sqlCon, userIds); + Map> tenantIdsForUserIds = GeneralQueries.getTenantIdsForUserIds_transaction(start, sqlCon, + appIdentifier, + userIds); List result = new ArrayList<>(); for (UserInfoPartial userInfo : userInfos) { - result.add(new UserInfo(userInfo.id, userInfo.email, userInfo.passwordHash, userInfo.timeJoined, - tenantIdsForUserIds.get(userInfo.id).toArray(new String[0]))); + userInfo.tenantIds = tenantIdsForUserIds.get(userInfo.id).toArray(new String[0]); } - - return result; + return userInfos; } private static class UserInfoPartial { @@ -506,6 +561,8 @@ private static class UserInfoPartial { public final long timeJoined; public final String email; public final String passwordHash; + public String[] tenantIds; + public Boolean verified; public UserInfoPartial(String id, String email, String passwordHash, long timeJoined) { this.id = id.trim(); @@ -513,6 +570,13 @@ public UserInfoPartial(String id, String email, String passwordHash, long timeJo this.email = email; this.passwordHash = passwordHash; } + + public LoginMethod toLoginMethod() { + assert (tenantIds != null); + assert (verified != null); + return new LoginMethod(id, timeJoined, verified, email, + passwordHash, tenantIds); + } } private static class PasswordResetRowMapper implements RowMapper { diff --git a/src/main/java/io/supertokens/inmemorydb/queries/EmailVerificationQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/EmailVerificationQueries.java index 849c8e9fa..338db2b45 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/EmailVerificationQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/EmailVerificationQueries.java @@ -17,8 +17,8 @@ package io.supertokens.inmemorydb.queries; import io.supertokens.inmemorydb.ConnectionWithLocks; -import io.supertokens.inmemorydb.QueryExecutorTemplate; import io.supertokens.inmemorydb.Start; +import io.supertokens.inmemorydb.Utils; import io.supertokens.inmemorydb.config.Config; import io.supertokens.pluginInterface.RowMapper; import io.supertokens.pluginInterface.emailverification.EmailVerificationTokenInfo; @@ -28,11 +28,9 @@ import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import java.sql.Connection; -import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; +import java.util.*; import static io.supertokens.inmemorydb.QueryExecutorTemplate.execute; import static io.supertokens.inmemorydb.QueryExecutorTemplate.update; @@ -80,7 +78,8 @@ public static void deleteExpiredEmailVerificationTokens(Start start) throws SQLE public static void updateUsersIsEmailVerified_Transaction(Start start, Connection con, AppIdentifier appIdentifier, String userId, String email, - boolean isEmailVerified) throws SQLException, StorageQueryException { + boolean isEmailVerified) + throws SQLException, StorageQueryException { if (isEmailVerified) { String QUERY = "INSERT INTO " + getConfig(start).getEmailVerificationTable() @@ -104,8 +103,10 @@ public static void updateUsersIsEmailVerified_Transaction(Start start, Connectio } public static void deleteAllEmailVerificationTokensForUser_Transaction(Start start, Connection con, - TenantIdentifier tenantIdentifier, String userId, - String email) throws SQLException, StorageQueryException { + TenantIdentifier tenantIdentifier, + String userId, + String email) + throws SQLException, StorageQueryException { String QUERY = "DELETE FROM " + getConfig(start).getEmailVerificationTokensTable() + " WHERE app_id = ? AND tenant_id = ? AND user_id = ? AND email = ?"; @@ -117,7 +118,8 @@ public static void deleteAllEmailVerificationTokensForUser_Transaction(Start sta }); } - public static EmailVerificationTokenInfo getEmailVerificationTokenInfo(Start start, TenantIdentifier tenantIdentifier, + public static EmailVerificationTokenInfo getEmailVerificationTokenInfo(Start start, + TenantIdentifier tenantIdentifier, String token) throws SQLException, StorageQueryException { String QUERY = "SELECT user_id, token, token_expiry, email FROM " @@ -135,7 +137,8 @@ public static EmailVerificationTokenInfo getEmailVerificationTokenInfo(Start sta }); } - public static void addEmailVerificationToken(Start start, TenantIdentifier tenantIdentifier, String userId, String tokenHash, long expiry, + public static void addEmailVerificationToken(Start start, TenantIdentifier tenantIdentifier, String userId, + String tokenHash, long expiry, String email) throws SQLException, StorageQueryException { String QUERY = "INSERT INTO " + getConfig(start).getEmailVerificationTokensTable() + "(app_id, tenant_id, user_id, token, token_expiry, email)" + " VALUES(?, ?, ?, ?, ?, ?)"; @@ -153,12 +156,17 @@ public static void addEmailVerificationToken(Start start, TenantIdentifier tenan public static EmailVerificationTokenInfo[] getAllEmailVerificationTokenInfoForUser_Transaction(Start start, Connection con, TenantIdentifier tenantIdentifier, - String userId, String email) throws SQLException, StorageQueryException { + String userId, + String email) + throws SQLException, StorageQueryException { - ((ConnectionWithLocks) con).lock(tenantIdentifier.getAppId() + "~" + tenantIdentifier.getTenantId() + "~" + userId + "~" + email + Config.getConfig(start).getEmailVerificationTokensTable()); + ((ConnectionWithLocks) con).lock( + tenantIdentifier.getAppId() + "~" + tenantIdentifier.getTenantId() + "~" + userId + "~" + email + + Config.getConfig(start).getEmailVerificationTokensTable()); String QUERY = "SELECT user_id, token, token_expiry, email FROM " - + getConfig(start).getEmailVerificationTokensTable() + " WHERE app_id = ? AND tenant_id = ? AND user_id = ? AND email = ?"; + + getConfig(start).getEmailVerificationTokensTable() + + " WHERE app_id = ? AND tenant_id = ? AND user_id = ? AND email = ?"; return execute(con, QUERY, pst -> { pst.setString(1, tenantIdentifier.getAppId()); @@ -181,9 +189,11 @@ public static EmailVerificationTokenInfo[] getAllEmailVerificationTokenInfoForUs public static EmailVerificationTokenInfo[] getAllEmailVerificationTokenInfoForUser(Start start, TenantIdentifier tenantIdentifier, String userId, - String email) throws SQLException, StorageQueryException { + String email) + throws SQLException, StorageQueryException { String QUERY = "SELECT user_id, token, token_expiry, email FROM " - + getConfig(start).getEmailVerificationTokensTable() + " WHERE app_id = ? AND tenant_id = ? AND user_id = ? AND email = ?"; + + getConfig(start).getEmailVerificationTokensTable() + + " WHERE app_id = ? AND tenant_id = ? AND user_id = ? AND email = ?"; return execute(start, QUERY, pst -> { pst.setString(1, tenantIdentifier.getAppId()); @@ -215,6 +225,59 @@ public static boolean isEmailVerified(Start start, AppIdentifier appIdentifier, }, result -> result.next()); } + public static class UserIdAndEmail { + public String userId; + public String email; + + public UserIdAndEmail(String userId, String email) { + this.userId = userId; + this.email = email; + } + } + + // returns list of userIds where email is verified. + public static List isEmailVerified_transaction(Start start, Connection sqlCon, AppIdentifier appIdentifier, + List userIdAndEmail) + throws SQLException, StorageQueryException { + List emails = new ArrayList<>(); + List userIds = new ArrayList<>(); + Map userIdToEmailMap = new HashMap<>(); + for (UserIdAndEmail ue : userIdAndEmail) { + emails.add(ue.email); + userIds.add(ue.userId); + } + for (UserIdAndEmail ue : userIdAndEmail) { + if (userIdToEmailMap.containsKey(ue.userId)) { + throw new RuntimeException("Found a bug!"); + } + userIdToEmailMap.put(ue.userId, ue.email); + } + String QUERY = "SELECT * FROM " + getConfig(start).getEmailVerificationTable() + + " WHERE app_id = ? AND user_id IN (" + Utils.generateCommaSeperatedQuestionMarks(userIds.size()) + + ") AND email = IN (" + Utils.generateCommaSeperatedQuestionMarks(emails.size()) + ")"; + + return execute(sqlCon, QUERY, pst -> { + pst.setString(1, appIdentifier.getAppId()); + int index = 2; + for (String userId : userIds) { + pst.setString(index++, userId); + } + for (String email : emails) { + pst.setString(index++, email); + } + }, result -> { + List res = new ArrayList<>(); + while (result.next()) { + String userId = result.getString("user_id"); + String email = result.getString("email"); + if (Objects.equals(userIdToEmailMap.get(userId), email)) { + res.add(userId); + } + } + return res; + }); + } + public static void deleteUserInfo(Start start, AppIdentifier appIdentifier, String userId) throws StorageQueryException, StorageTransactionLogicException { start.startTransaction(con -> { diff --git a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java index 25528e1bd..c42b8aef7 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java @@ -25,6 +25,7 @@ import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.RowMapper; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; +import io.supertokens.pluginInterface.authRecipe.LoginMethod; import io.supertokens.pluginInterface.dashboard.DashboardSearchTags; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; @@ -37,10 +38,8 @@ import java.sql.DatabaseMetaData; import java.sql.ResultSet; import java.sql.SQLException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; +import java.util.stream.Collectors; import static io.supertokens.ProcessState.PROCESS_STATE.CREATING_NEW_TABLE; import static io.supertokens.ProcessState.getInstance; @@ -829,7 +828,8 @@ public static AuthRecipeUserInfo[] getUsers(Start start, TenantIdentifier tenant // we give the userId[] for each recipe to fetch all those user's details for (RECIPE_ID recipeId : recipeIdToUserIdListMap.keySet()) { List users = getUserInfoForRecipeIdFromUserIds(start, - tenantIdentifier, recipeId, recipeIdToUserIdListMap.get(recipeId)); + tenantIdentifier.toAppIdentifier(), + recipeIdToUserIdListMap.get(recipeId)); // we fill in all the slots in finalResult based on their position in // usersFromQuery @@ -847,20 +847,86 @@ public static AuthRecipeUserInfo[] getUsers(Start start, TenantIdentifier tenant return finalResult; } - private static List getUserInfoForRecipeIdFromUserIds(Start start, - TenantIdentifier tenantIdentifier, - RECIPE_ID recipeId, - List userIds) + private static List getUserInfoForRecipeIdFromUserIds(Start start, + AppIdentifier appIdentifier, + List userIds) throws StorageQueryException, SQLException { - if (recipeId == RECIPE_ID.EMAIL_PASSWORD) { - return EmailPasswordQueries.getUsersInfoUsingIdList(start, userIds); - } else if (recipeId == RECIPE_ID.THIRD_PARTY) { - return ThirdPartyQueries.getUsersInfoUsingIdList(start, userIds); - } else if (recipeId == RECIPE_ID.PASSWORDLESS) { - return PasswordlessQueries.getUsersByIdList(start, userIds); - } else { - throw new IllegalArgumentException("No implementation of get users for recipe: " + recipeId.toString()); + if (userIds.size() == 0) { + return new ArrayList<>(); } + + // We check both user_id and primary_or_recipe_user_id because the input may have a recipe userId + // which is linked to a primary user ID in which case it won't be in the primary_or_recipe_user_id column, + // or the input may have a primary user ID whose recipe user ID was removed, so it won't be in the user_id + // column + String QUERY = "SELECT * FROM " + getConfig(start).getUsersTable() + " WHERE (user_id IN (" + + io.supertokens.inmemorydb.Utils.generateCommaSeperatedQuestionMarks(userIds.size()) + + ") OR primary_or_recipe_user_id IN (" + + io.supertokens.inmemorydb.Utils.generateCommaSeperatedQuestionMarks(userIds.size()) + + ")) AND app_id = ?"; + + List allAuthUsersResult = execute(start, QUERY, pst -> { + // IN user_id + int index = 1; + for (int i = 0; i < userIds.size(); i++, index++) { + pst.setString(index, userIds.get(i)); + } + // IN primary_or_recipe_user_id + for (int i = 0; i < userIds.size(); i++, index++) { + pst.setString(index, userIds.get(i)); + } + // for app_id + pst.setString(index, appIdentifier.getAppId()); + }, result -> { + List parsedResult = new ArrayList<>(); + while (result.next()) { + parsedResult.add(new AllAuthRecipeUsersResultHolder(result.getString("user_id"), + result.getString("tenant_id"), + result.getString("primary_or_recipe_user_id"), + result.getBoolean("is_linked_or_is_a_primary_user"), + result.getString("recipe_id"), + result.getLong("time_joined"))); + } + return parsedResult; + }); + + // Now we form the userIds again, but based on the user_id in the result from above. + Set recipeUserIdsToFetch = new HashSet<>(); + for (AllAuthRecipeUsersResultHolder user : allAuthUsersResult) { + // this will remove duplicate entries wherein a user id is shared across several tenants. + recipeUserIdsToFetch.add(user.userId); + } + + List loginMethods = new ArrayList<>(); + loginMethods.addAll(EmailPasswordQueries.getUsersInfoUsingIdList(start, recipeUserIdsToFetch, appIdentifier)); + loginMethods.addAll(ThirdPartyQueries.getUsersInfoUsingIdList(start, recipeUserIdsToFetch, appIdentifier)); + loginMethods.addAll(PasswordlessQueries.getUsersInfoUsingIdList(start, recipeUserIdsToFetch, appIdentifier)); + + Map recipeUserIdToLoginMethodMap = new HashMap<>(); + for (LoginMethod loginMethod : loginMethods) { + recipeUserIdToLoginMethodMap.put(loginMethod.recipeUserId, loginMethod); + } + + Map userIdToAuthRecipeUserInfo = new HashMap<>(); + + for (AllAuthRecipeUsersResultHolder authRecipeUsersResultHolder : allAuthUsersResult) { + String recipeUserId = authRecipeUsersResultHolder.userId; + LoginMethod loginMethod = recipeUserIdToLoginMethodMap.get(recipeUserId); + assert (loginMethod != null); + + String primaryUserId = authRecipeUsersResultHolder.primaryOrRecipeUserId; + AuthRecipeUserInfo curr = userIdToAuthRecipeUserInfo.get(primaryUserId); + if (curr == null) { + curr = new AuthRecipeUserInfo(primaryUserId, authRecipeUsersResultHolder.isLinkedOrIsAPrimaryUser, + loginMethod); + } else { + curr.addLoginMethod(loginMethod); + } + userIdToAuthRecipeUserInfo.put(primaryUserId, curr); + } + + return userIdToAuthRecipeUserInfo.keySet().stream().map(userIdToAuthRecipeUserInfo::get) + .collect(Collectors.toList()); } public static String getRecipeIdForUser_Transaction(Start start, Connection sqlCon, @@ -885,6 +951,7 @@ public static String getRecipeIdForUser_Transaction(Start start, Connection sqlC } public static Map> getTenantIdsForUserIds_transaction(Start start, Connection sqlCon, + AppIdentifier appIdentifier, String[] userIds) throws SQLException, StorageQueryException { if (userIds != null && userIds.length > 0) { @@ -899,13 +966,14 @@ public static Map> getTenantIdsForUserIds_transaction(Start QUERY.append(","); } } - QUERY.append(")"); + QUERY.append(") AND app_id = ?"); return execute(sqlCon, QUERY.toString(), pst -> { for (int i = 0; i < userIds.length; i++) { // i+1 cause this starts with 1 and not 0 pst.setString(i + 1, userIds[i]); } + pst.setString(userIds.length + 1, appIdentifier.getAppId()); }, result -> { Map> finalResult = new HashMap<>(); while (result.next()) { @@ -977,6 +1045,25 @@ private static class UserInfoPaginationResultHolder { } } + private static class AllAuthRecipeUsersResultHolder { + String userId; + String tenantId; + String primaryOrRecipeUserId; + boolean isLinkedOrIsAPrimaryUser; + RECIPE_ID recipeId; + long timeJoined; + + AllAuthRecipeUsersResultHolder(String userId, String tenantId, String primaryOrRecipeUserId, + boolean isLinkedOrIsAPrimaryUser, String recipeId, long timeJoined) { + this.userId = userId; + this.tenantId = tenantId; + this.primaryOrRecipeUserId = primaryOrRecipeUserId; + this.isLinkedOrIsAPrimaryUser = isLinkedOrIsAPrimaryUser; + this.recipeId = RECIPE_ID.valueOf(recipeId); + this.timeJoined = timeJoined; + } + } + private static class KeyValueInfoRowMapper implements RowMapper { public static final KeyValueInfoRowMapper INSTANCE = new KeyValueInfoRowMapper(); diff --git a/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java index bf24e1d70..7636cce5f 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java @@ -19,8 +19,10 @@ import io.supertokens.inmemorydb.ConnectionPool; import io.supertokens.inmemorydb.ConnectionWithLocks; import io.supertokens.inmemorydb.Start; +import io.supertokens.inmemorydb.Utils; import io.supertokens.inmemorydb.config.Config; import io.supertokens.pluginInterface.RowMapper; +import io.supertokens.pluginInterface.authRecipe.LoginMethod; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; @@ -36,6 +38,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.*; +import java.util.stream.Collectors; import static io.supertokens.inmemorydb.QueryExecutorTemplate.execute; import static io.supertokens.inmemorydb.QueryExecutorTemplate.update; @@ -69,7 +72,8 @@ static String getQueryToCreatePasswordlessUserToTenantTable(Start start) { + "UNIQUE (app_id, tenant_id, phone_number)," + "PRIMARY KEY (app_id, tenant_id, user_id)," + "FOREIGN KEY (app_id, tenant_id, user_id)" - + " REFERENCES " + Config.getConfig(start).getUsersTable() + "(app_id, tenant_id, user_id) ON DELETE CASCADE" + + " REFERENCES " + Config.getConfig(start).getUsersTable() + + "(app_id, tenant_id, user_id) ON DELETE CASCADE" + ");"; // @formatter:on } @@ -85,7 +89,7 @@ public static String getQueryToCreateDevicesTable(Start start) { + "failed_attempts INT UNSIGNED NOT NULL," + "PRIMARY KEY (app_id, tenant_id, device_id_hash)," + "FOREIGN KEY(app_id, tenant_id)" - + " REFERENCES " + Config.getConfig(start).getTenantsTable() + " (app_id, tenant_id) ON DELETE CASCADE" + + " REFERENCES " + Config.getConfig(start).getTenantsTable() + " (app_id, tenant_id) ON DELETE CASCADE" + ");"; } @@ -98,8 +102,9 @@ public static String getQueryToCreateCodesTable(Start start) { + "link_code_hash CHAR(44) NOT NULL," + "created_at BIGINT UNSIGNED NOT NULL," + "PRIMARY KEY (app_id, tenant_id, code_id)," - + "UNIQUE (app_id, tenant_id, link_code_hash)," - + "FOREIGN KEY (app_id, tenant_id, device_id_hash) REFERENCES " + Config.getConfig(start).getPasswordlessDevicesTable() + + "UNIQUE (app_id, tenant_id, link_code_hash)," + + "FOREIGN KEY (app_id, tenant_id, device_id_hash) REFERENCES " + + Config.getConfig(start).getPasswordlessDevicesTable() + " (app_id, tenant_id, device_id_hash) ON DELETE CASCADE ON UPDATE CASCADE" + ");"; } @@ -111,7 +116,8 @@ public static String getQueryToCreateDeviceEmailIndex(Start start) { public static String getQueryToCreateDevicePhoneNumberIndex(Start start) { return "CREATE INDEX passwordless_devices_phone_number_index ON " - + Config.getConfig(start).getPasswordlessDevicesTable() + " (app_id, tenant_id, phone_number);"; // USING hash + + Config.getConfig(start).getPasswordlessDevicesTable() + + " (app_id, tenant_id, phone_number);"; // USING hash } public static String getQueryToCreateCodeDeviceIdHashIndex(Start start) { @@ -125,8 +131,10 @@ public static String getQueryToCreateCodeCreatedAtIndex(Start start) { } - public static void createDeviceWithCode(Start start, TenantIdentifier tenantIdentifier, String email, String phoneNumber, String linkCodeSalt, - PasswordlessCode code) throws StorageTransactionLogicException, StorageQueryException { + public static void createDeviceWithCode(Start start, TenantIdentifier tenantIdentifier, String email, + String phoneNumber, String linkCodeSalt, + PasswordlessCode code) + throws StorageTransactionLogicException, StorageQueryException { start.startTransaction(con -> { Connection sqlCon = (Connection) con.getConnection(); try { @@ -155,7 +163,9 @@ public static PasswordlessDevice getDevice_Transaction(Start start, Connection c TenantIdentifier tenantIdentifier, String deviceIdHash) throws StorageQueryException, SQLException { - ((ConnectionWithLocks) con).lock(tenantIdentifier.getAppId() + "~" + tenantIdentifier.getTenantId() + "~" + deviceIdHash + Config.getConfig(start).getPasswordlessDevicesTable()); + ((ConnectionWithLocks) con).lock( + tenantIdentifier.getAppId() + "~" + tenantIdentifier.getTenantId() + "~" + deviceIdHash + + Config.getConfig(start).getPasswordlessDevicesTable()); String QUERY = "SELECT device_id_hash, email, phone_number, link_code_salt, failed_attempts FROM " + getConfig(start).getPasswordlessDevicesTable() @@ -173,7 +183,8 @@ public static PasswordlessDevice getDevice_Transaction(Start start, Connection c } public static void incrementDeviceFailedAttemptCount_Transaction(Start start, Connection con, - TenantIdentifier tenantIdentifier, String deviceIdHash) + TenantIdentifier tenantIdentifier, + String deviceIdHash) throws SQLException, StorageQueryException { String QUERY = "UPDATE " + getConfig(start).getPasswordlessDevicesTable() + " SET failed_attempts = failed_attempts + 1" @@ -186,7 +197,8 @@ public static void incrementDeviceFailedAttemptCount_Transaction(Start start, Co }); } - public static void deleteDevice_Transaction(Start start, Connection con, TenantIdentifier tenantIdentifier, String deviceIdHash) + public static void deleteDevice_Transaction(Start start, Connection con, TenantIdentifier tenantIdentifier, + String deviceIdHash) throws SQLException, StorageQueryException { String QUERY = "DELETE FROM " + getConfig(start).getPasswordlessDevicesTable() + " WHERE app_id = ? AND tenant_id = ? AND device_id_hash = ?"; @@ -197,7 +209,9 @@ public static void deleteDevice_Transaction(Start start, Connection con, TenantI }); } - public static void deleteDevicesByPhoneNumber_Transaction(Start start, Connection con, TenantIdentifier tenantIdentifier, @Nonnull String phoneNumber) + public static void deleteDevicesByPhoneNumber_Transaction(Start start, Connection con, + TenantIdentifier tenantIdentifier, + @Nonnull String phoneNumber) throws SQLException, StorageQueryException { String QUERY = "DELETE FROM " + getConfig(start).getPasswordlessDevicesTable() @@ -210,7 +224,8 @@ public static void deleteDevicesByPhoneNumber_Transaction(Start start, Connectio }); } - public static void deleteDevicesByPhoneNumber_Transaction(Start start, Connection con, AppIdentifier appIdentifier, @Nonnull String phoneNumber, String userId) + public static void deleteDevicesByPhoneNumber_Transaction(Start start, Connection con, AppIdentifier appIdentifier, + @Nonnull String phoneNumber, String userId) throws SQLException, StorageQueryException { String QUERY = "DELETE FROM " + getConfig(start).getPasswordlessDevicesTable() @@ -227,7 +242,8 @@ public static void deleteDevicesByPhoneNumber_Transaction(Start start, Connectio }); } - public static void deleteDevicesByEmail_Transaction(Start start, Connection con, TenantIdentifier tenantIdentifier, @Nonnull String email) + public static void deleteDevicesByEmail_Transaction(Start start, Connection con, TenantIdentifier tenantIdentifier, + @Nonnull String email) throws SQLException, StorageQueryException { String QUERY = "DELETE FROM " + getConfig(start).getPasswordlessDevicesTable() @@ -240,7 +256,8 @@ public static void deleteDevicesByEmail_Transaction(Start start, Connection con, }); } - public static void deleteDevicesByEmail_Transaction(Start start, Connection con, AppIdentifier appIdentifier, @Nonnull String email, String userId) + public static void deleteDevicesByEmail_Transaction(Start start, Connection con, AppIdentifier appIdentifier, + @Nonnull String email, String userId) throws SQLException, StorageQueryException { String QUERY = "DELETE FROM " + getConfig(start).getPasswordlessDevicesTable() @@ -257,7 +274,8 @@ public static void deleteDevicesByEmail_Transaction(Start start, Connection con, }); } - private static void createCode_Transaction(Start start, Connection con, TenantIdentifier tenantIdentifier, PasswordlessCode code) + private static void createCode_Transaction(Start start, Connection con, TenantIdentifier tenantIdentifier, + PasswordlessCode code) throws SQLException, StorageQueryException { String QUERY = "INSERT INTO " + getConfig(start).getPasswordlessCodesTable() + "(app_id, tenant_id, code_id, device_id_hash, link_code_hash, created_at)" @@ -287,7 +305,9 @@ public static void createCode(Start start, TenantIdentifier tenantIdentifier, Pa }); } - public static PasswordlessCode[] getCodesOfDevice_Transaction(Start start, Connection con, TenantIdentifier tenantIdentifier, String deviceIdHash) + public static PasswordlessCode[] getCodesOfDevice_Transaction(Start start, Connection con, + TenantIdentifier tenantIdentifier, + String deviceIdHash) throws StorageQueryException, SQLException { // We do not lock here, since the device is already locked earlier in the transaction. String QUERY = "SELECT code_id, device_id_hash, link_code_hash, created_at FROM " @@ -311,7 +331,9 @@ public static PasswordlessCode[] getCodesOfDevice_Transaction(Start start, Conne }); } - public static PasswordlessCode getCodeByLinkCodeHash_Transaction(Start start, Connection con, TenantIdentifier tenantIdentifier, String linkCodeHash) + public static PasswordlessCode getCodeByLinkCodeHash_Transaction(Start start, Connection con, + TenantIdentifier tenantIdentifier, + String linkCodeHash) throws StorageQueryException, SQLException { // We do not lock here, since the device is already locked earlier in the transaction. String QUERY = "SELECT code_id, device_id_hash, link_code_hash, created_at FROM " @@ -330,7 +352,8 @@ public static PasswordlessCode getCodeByLinkCodeHash_Transaction(Start start, Co }); } - public static void deleteCode_Transaction(Start start, Connection con, TenantIdentifier tenantIdentifier, String codeId) + public static void deleteCode_Transaction(Start start, Connection con, TenantIdentifier tenantIdentifier, + String codeId) throws SQLException, StorageQueryException { String QUERY = "DELETE FROM " + getConfig(start).getPasswordlessCodesTable() + " WHERE app_id = ? AND tenant_id = ? AND code_id = ?"; @@ -342,7 +365,8 @@ public static void deleteCode_Transaction(Start start, Connection con, TenantIde }); } - public static UserInfo createUser(Start start, TenantIdentifier tenantIdentifier, String id, @Nullable String email, @Nullable String phoneNumber, long timeJoined) + public static UserInfo createUser(Start start, TenantIdentifier tenantIdentifier, String id, @Nullable String email, + @Nullable String phoneNumber, long timeJoined) throws StorageTransactionLogicException, StorageQueryException { return start.startTransaction(con -> { Connection sqlCon = (Connection) con.getConnection(); @@ -393,16 +417,21 @@ public static UserInfo createUser(Start start, TenantIdentifier tenantIdentifier pst.setString(5, phoneNumber); }); } - UserInfo userInfo = userInfoWithTenantIds_transaction(start, sqlCon, new UserInfoPartial(id, email, phoneNumber, timeJoined)); + + UserInfoPartial userInfo = new UserInfoPartial(id, email, phoneNumber, timeJoined); + fillUserInfoWithTenantIds_transaction(start, sqlCon, tenantIdentifier.toAppIdentifier(), userInfo); + fillUserInfoWithVerified_transaction(start, sqlCon, tenantIdentifier.toAppIdentifier(), userInfo); sqlCon.commit(); - return userInfo; + return new UserInfo(id, userInfo.verified, + userInfo.toLoginMethod()); } catch (SQLException throwables) { throw new StorageTransactionLogicException(throwables); } }); } - private static UserInfoWithTenantId[] getUserInfosWithTenant(Start start, Connection con, AppIdentifier appIdentifier, String userId) + private static UserInfoWithTenantId[] getUserInfosWithTenant(Start start, Connection con, + AppIdentifier appIdentifier, String userId) throws StorageQueryException, SQLException { String QUERY = "SELECT pl_users.user_id as user_id, pl_users.email as email, " + "pl_users.phone_number as phone_number, pl_users_to_tenant.tenant_id as tenant_id " @@ -471,7 +500,8 @@ public static void deleteUser(Start start, AppIdentifier appIdentifier, String u }); } - public static int updateUserEmail_Transaction(Start start, Connection con, AppIdentifier appIdentifier, String userId, String email) + public static int updateUserEmail_Transaction(Start start, Connection con, AppIdentifier appIdentifier, + String userId, String email) throws SQLException, StorageQueryException { { String QUERY = "UPDATE " + Config.getConfig(start).getPasswordlessUserToTenantTable() @@ -495,7 +525,8 @@ public static int updateUserEmail_Transaction(Start start, Connection con, AppId } } - public static int updateUserPhoneNumber_Transaction(Start start, Connection con, AppIdentifier appIdentifier, String userId, String phoneNumber) + public static int updateUserPhoneNumber_Transaction(Start start, Connection con, AppIdentifier appIdentifier, + String userId, String phoneNumber) throws SQLException, StorageQueryException { { String QUERY = "UPDATE " + Config.getConfig(start).getPasswordlessUserToTenantTable() @@ -538,7 +569,8 @@ public static PasswordlessDevice getDevice(Start start, TenantIdentifier tenantI } } - public static PasswordlessDevice[] getDevicesByEmail(Start start, TenantIdentifier tenantIdentifier, @Nonnull String email) + public static PasswordlessDevice[] getDevicesByEmail(Start start, TenantIdentifier tenantIdentifier, + @Nonnull String email) throws StorageQueryException, SQLException { String QUERY = "SELECT device_id_hash, email, phone_number, link_code_salt, failed_attempts FROM " + getConfig(start).getPasswordlessDevicesTable() @@ -585,7 +617,8 @@ public static PasswordlessDevice[] getDevicesByPhoneNumber(Start start, TenantId }); } - public static PasswordlessCode[] getCodesOfDevice(Start start, TenantIdentifier tenantIdentifier, String deviceIdHash) + public static PasswordlessCode[] getCodesOfDevice(Start start, TenantIdentifier tenantIdentifier, + String deviceIdHash) throws StorageQueryException, SQLException { try (Connection con = ConnectionPool.getConnection(start)) { // We can call the transaction version here because it doesn't lock anything. @@ -593,7 +626,8 @@ public static PasswordlessCode[] getCodesOfDevice(Start start, TenantIdentifier } } - public static PasswordlessCode[] getCodesBefore(Start start, TenantIdentifier tenantIdentifier, long time) throws StorageQueryException, SQLException { + public static PasswordlessCode[] getCodesBefore(Start start, TenantIdentifier tenantIdentifier, long time) + throws StorageQueryException, SQLException { String QUERY = "SELECT code_id, device_id_hash, link_code_hash, created_at FROM " + getConfig(start).getPasswordlessCodesTable() + " WHERE app_id = ? AND tenant_id = ? AND created_at < ?"; @@ -615,7 +649,8 @@ public static PasswordlessCode[] getCodesBefore(Start start, TenantIdentifier te }); } - public static PasswordlessCode getCode(Start start, TenantIdentifier tenantIdentifier, String codeId) throws StorageQueryException, SQLException { + public static PasswordlessCode getCode(Start start, TenantIdentifier tenantIdentifier, String codeId) + throws StorageQueryException, SQLException { String QUERY = "SELECT code_id, device_id_hash, link_code_hash, created_at FROM " + getConfig(start).getPasswordlessCodesTable() + " WHERE app_id = ? AND tenant_id = ? AND code_id = ?"; @@ -632,7 +667,8 @@ public static PasswordlessCode getCode(Start start, TenantIdentifier tenantIdent }); } - public static PasswordlessCode getCodeByLinkCodeHash(Start start, TenantIdentifier tenantIdentifier, String linkCodeHash) + public static PasswordlessCode getCodeByLinkCodeHash(Start start, TenantIdentifier tenantIdentifier, + String linkCodeHash) throws StorageQueryException, SQLException { try (Connection con = ConnectionPool.getConnection(start)) { // We can call the transaction version here because it doesn't lock anything. @@ -640,57 +676,66 @@ public static PasswordlessCode getCodeByLinkCodeHash(Start start, TenantIdentifi } } - public static List getUsersByIdList(Start start, List ids) + public static List getUsersInfoUsingIdList(Start start, Set ids, AppIdentifier appIdentifier) throws SQLException, StorageQueryException { if (ids.size() > 0) { // No need to filter based on tenantId because the id list is already filtered for a tenant - StringBuilder QUERY = new StringBuilder("SELECT user_id, email, phone_number, time_joined " - + "FROM " + getConfig(start).getPasswordlessUsersTable()); - QUERY.append(" WHERE user_id IN ("); - for (int i = 0; i < ids.size(); i++) { - QUERY.append("?"); - if (i != ids.size() - 1) { - // not the last element - QUERY.append(","); - } + String QUERY = "SELECT user_id, email, phone_number, time_joined " + + "FROM " + getConfig(start).getPasswordlessUsersTable() + " WHERE user_id IN (" + + Utils.generateCommaSeperatedQuestionMarks(ids.size()) + ") AND app_id = ?"; + + try (Connection con = ConnectionPool.getConnection(start)) { + List userInfos = execute(con, QUERY, pst -> { + int index = 1; + for (String id : ids) { + pst.setString(index, id); + index++; + } + pst.setString(index, appIdentifier.getAppId()); + }, result -> { + List finalResult = new ArrayList<>(); + while (result.next()) { + finalResult.add(UserInfoRowMapper.getInstance().mapOrThrow(result)); + } + return finalResult; + }); + fillUserInfoWithTenantIds_transaction(start, con, appIdentifier, userInfos); + fillUserInfoWithVerified_transaction(start, con, appIdentifier, userInfos); + return userInfos.stream().map(UserInfoPartial::toLoginMethod).collect(Collectors.toList()); } - QUERY.append(")"); - - List userInfos = execute(start, QUERY.toString(), pst -> { - for (int i = 0; i < ids.size(); i++) { - // i+1 cause this starts with 1 and not 0 - pst.setString(i + 1, ids.get(i)); - } - }, result -> { - List finalResult = new ArrayList<>(); - while (result.next()) { - finalResult.add(UserInfoRowMapper.getInstance().mapOrThrow(result)); - } - return finalResult; - }); - return userInfoWithTenantIds(start, userInfos); } return Collections.emptyList(); } - public static UserInfo getUserById(Start start, AppIdentifier appIdentifier, String userId) throws StorageQueryException, SQLException { + public static UserInfo getUserById(Start start, AppIdentifier appIdentifier, String userId) + throws StorageQueryException, SQLException { String QUERY = "SELECT user_id, email, phone_number, time_joined FROM " + getConfig(start).getPasswordlessUsersTable() + " WHERE app_id = ? AND user_id = ?"; - UserInfoPartial userInfo = execute(start, QUERY, pst -> { - pst.setString(1, appIdentifier.getAppId()); - pst.setString(2, userId); - }, result -> { - if (result.next()) { - return UserInfoRowMapper.getInstance().mapOrThrow(result); + try (Connection con = ConnectionPool.getConnection(start)) { + UserInfoPartial userInfo = execute(con, QUERY, pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setString(2, userId); + }, result -> { + if (result.next()) { + return UserInfoRowMapper.getInstance().mapOrThrow(result); + } + return null; + }); + if (userInfo == null) { + return null; } - return null; - }); - return userInfoWithTenantIds(start, userInfo); + fillUserInfoWithTenantIds_transaction(start, con, appIdentifier, userInfo); + fillUserInfoWithVerified_transaction(start, con, appIdentifier, userInfo); + return new UserInfo(userInfo.id, userInfo.verified, + userInfo.toLoginMethod()); + } } - public static UserInfoPartial getUserById(Start start, Connection sqlCon, AppIdentifier appIdentifier, String userId) throws StorageQueryException, SQLException { - // we don't need a LOCK here because this is already part of a transaction, and locked on app_id_to_user_id table + public static UserInfoPartial getUserById(Start start, Connection sqlCon, AppIdentifier appIdentifier, + String userId) throws StorageQueryException, SQLException { + // we don't need a LOCK here because this is already part of a transaction, and locked on app_id_to_user_id + // table String QUERY = "SELECT user_id, email, phone_number, time_joined FROM " + getConfig(start).getPasswordlessUsersTable() + " WHERE app_id = ? AND user_id = ?"; @@ -713,44 +758,66 @@ public static UserInfo getUserByEmail(Start start, TenantIdentifier tenantIdenti + "FROM " + getConfig(start).getPasswordlessUserToTenantTable() + " AS pl_users_to_tenant " + "JOIN " + getConfig(start).getPasswordlessUsersTable() + " AS pl_users " + "ON pl_users.app_id = pl_users_to_tenant.app_id AND pl_users.user_id = pl_users_to_tenant.user_id " - + "WHERE pl_users_to_tenant.app_id = ? AND pl_users_to_tenant.tenant_id = ? AND pl_users_to_tenant.email = ? "; + + + "WHERE pl_users_to_tenant.app_id = ? AND pl_users_to_tenant.tenant_id = ? AND pl_users_to_tenant" + + ".email = ? "; - UserInfoPartial userInfo = execute(start, QUERY, pst -> { - pst.setString(1, tenantIdentifier.getAppId()); - pst.setString(2, tenantIdentifier.getTenantId()); - pst.setString(3, email); - }, result -> { - if (result.next()) { - return UserInfoRowMapper.getInstance().mapOrThrow(result); + try (Connection con = ConnectionPool.getConnection(start)) { + UserInfoPartial userInfo = execute(con, QUERY, pst -> { + pst.setString(1, tenantIdentifier.getAppId()); + pst.setString(2, tenantIdentifier.getTenantId()); + pst.setString(3, email); + }, result -> { + if (result.next()) { + return UserInfoRowMapper.getInstance().mapOrThrow(result); + } + return null; + }); + if (userInfo == null) { + return null; } - return null; - }); - return userInfoWithTenantIds(start, userInfo); + fillUserInfoWithTenantIds_transaction(start, con, tenantIdentifier.toAppIdentifier(), userInfo); + fillUserInfoWithVerified_transaction(start, con, tenantIdentifier.toAppIdentifier(), userInfo); + return new UserInfo(userInfo.id, userInfo.verified, + userInfo.toLoginMethod()); + } } - public static UserInfo getUserByPhoneNumber(Start start, TenantIdentifier tenantIdentifier, @Nonnull String phoneNumber) + public static UserInfo getUserByPhoneNumber(Start start, TenantIdentifier tenantIdentifier, + @Nonnull String phoneNumber) throws StorageQueryException, SQLException { String QUERY = "SELECT pl_users.user_id as user_id, pl_users.email as email, " + "pl_users.phone_number as phone_number, pl_users.time_joined as time_joined " + "FROM " + getConfig(start).getPasswordlessUserToTenantTable() + " AS pl_users_to_tenant " + "JOIN " + getConfig(start).getPasswordlessUsersTable() + " AS pl_users " + "ON pl_users.app_id = pl_users_to_tenant.app_id AND pl_users.user_id = pl_users_to_tenant.user_id " - + "WHERE pl_users_to_tenant.app_id = ? AND pl_users_to_tenant.tenant_id = ? AND pl_users_to_tenant.phone_number = ? "; + + + "WHERE pl_users_to_tenant.app_id = ? AND pl_users_to_tenant.tenant_id = ? AND pl_users_to_tenant" + + ".phone_number = ? "; - UserInfoPartial userInfo = execute(start, QUERY, pst -> { - pst.setString(1, tenantIdentifier.getAppId()); - pst.setString(2, tenantIdentifier.getTenantId()); - pst.setString(3, phoneNumber); - }, result -> { - if (result.next()) { - return UserInfoRowMapper.getInstance().mapOrThrow(result); + try (Connection con = ConnectionPool.getConnection(start)) { + UserInfoPartial userInfo = execute(con, QUERY, pst -> { + pst.setString(1, tenantIdentifier.getAppId()); + pst.setString(2, tenantIdentifier.getTenantId()); + pst.setString(3, phoneNumber); + }, result -> { + if (result.next()) { + return UserInfoRowMapper.getInstance().mapOrThrow(result); + } + return null; + }); + if (userInfo == null) { + return null; } - return null; - }); - return userInfoWithTenantIds(start, userInfo); + fillUserInfoWithTenantIds_transaction(start, con, tenantIdentifier.toAppIdentifier(), userInfo); + fillUserInfoWithVerified_transaction(start, con, tenantIdentifier.toAppIdentifier(), userInfo); + return new UserInfo(userInfo.id, userInfo.verified, + userInfo.toLoginMethod()); + } } - public static boolean addUserIdToTenant_Transaction(Start start, Connection sqlCon, TenantIdentifier tenantIdentifier, String userId) + public static boolean addUserIdToTenant_Transaction(Start start, Connection sqlCon, + TenantIdentifier tenantIdentifier, String userId) throws StorageQueryException, SQLException { UserInfoPartial userInfo = PasswordlessQueries.getUserById(start, sqlCon, tenantIdentifier.toAppIdentifier(), userId); @@ -785,7 +852,8 @@ public static boolean addUserIdToTenant_Transaction(Start start, Connection sqlC } } - public static boolean removeUserIdFromTenant_Transaction(Start start, Connection sqlCon, TenantIdentifier tenantIdentifier, String userId) + public static boolean removeUserIdFromTenant_Transaction(Start start, Connection sqlCon, + TenantIdentifier tenantIdentifier, String userId) throws SQLException, StorageQueryException { { // all_auth_recipe_users String QUERY = "DELETE FROM " + getConfig(start).getUsersTable() @@ -803,42 +871,66 @@ public static boolean removeUserIdFromTenant_Transaction(Start start, Connection // automatically deleted from passwordless_user_to_tenant because of foreign key constraint } - private static UserInfo userInfoWithTenantIds(Start start, UserInfoPartial userInfo) + private static UserInfoPartial fillUserInfoWithVerified_transaction(Start start, + Connection sqlCon, + AppIdentifier appIdentifier, + UserInfoPartial userInfo) throws SQLException, StorageQueryException { if (userInfo == null) return null; - try (Connection con = ConnectionPool.getConnection(start)) { - return userInfoWithTenantIds_transaction(start, con, Arrays.asList(userInfo)).get(0); - } + return fillUserInfoWithVerified_transaction(start, sqlCon, appIdentifier, List.of(userInfo)).get(0); } - private static List userInfoWithTenantIds(Start start, List userInfos) + private static List fillUserInfoWithVerified_transaction(Start start, + Connection sqlCon, + AppIdentifier appIdentifier, + List userInfos) throws SQLException, StorageQueryException { - try (Connection con = ConnectionPool.getConnection(start)) { - return userInfoWithTenantIds_transaction(start, con, userInfos); + List userIdsAndEmails = new ArrayList<>(); + for (UserInfoPartial userInfo : userInfos) { + if (userInfo.email == null) { + // phone number, so we mark it as verified + userInfo.verified = true; + } else { + userIdsAndEmails.add(new EmailVerificationQueries.UserIdAndEmail(userInfo.id, userInfo.email)); + } } + List userIdsThatAreVerified = EmailVerificationQueries.isEmailVerified_transaction(start, sqlCon, + appIdentifier, + userIdsAndEmails); + Set verifiedUserIdsSet = new HashSet<>(userIdsThatAreVerified); + for (UserInfoPartial userInfo : userInfos) { + if (verifiedUserIdsSet.contains(userInfo.id)) { + userInfo.verified = true; + } + } + return userInfos; } - private static UserInfo userInfoWithTenantIds_transaction(Start start, Connection sqlCon, UserInfoPartial userInfo) + private static UserInfoPartial fillUserInfoWithTenantIds_transaction(Start start, Connection sqlCon, + AppIdentifier appIdentifier, + UserInfoPartial userInfo) throws SQLException, StorageQueryException { if (userInfo == null) return null; - return userInfoWithTenantIds_transaction(start, sqlCon, Arrays.asList(userInfo)).get(0); + return fillUserInfoWithTenantIds_transaction(start, sqlCon, appIdentifier, List.of(userInfo)).get(0); } - private static List userInfoWithTenantIds_transaction(Start start, Connection sqlCon, List userInfos) + private static List fillUserInfoWithTenantIds_transaction(Start start, Connection sqlCon, + AppIdentifier appIdentifier, + List userInfos) throws SQLException, StorageQueryException { String[] userIds = new String[userInfos.size()]; for (int i = 0; i < userInfos.size(); i++) { userIds[i] = userInfos.get(i).id; } - Map> tenantIdsForUserIds = GeneralQueries.getTenantIdsForUserIds_transaction(start, sqlCon, userIds); + Map> tenantIdsForUserIds = GeneralQueries.getTenantIdsForUserIds_transaction(start, sqlCon, + appIdentifier, + userIds); List result = new ArrayList<>(); for (UserInfoPartial userInfo : userInfos) { - result.add(new UserInfo(userInfo.id, userInfo.email, userInfo.phoneNumber, userInfo.timeJoined, - tenantIdsForUserIds.get(userInfo.id).toArray(new String[0]))); + userInfo.tenantIds = tenantIdsForUserIds.get(userInfo.id).toArray(new String[0]); } - - return result; + return userInfos; } private static class PasswordlessDeviceRowMapper implements RowMapper { @@ -881,6 +973,8 @@ private static class UserInfoPartial { public final long timeJoined; public final String email; public final String phoneNumber; + public String[] tenantIds; + public Boolean verified; UserInfoPartial(String id, @Nullable String email, @Nullable String phoneNumber, long timeJoined) { this.id = id.trim(); @@ -893,6 +987,13 @@ private static class UserInfoPartial { this.email = email; this.phoneNumber = phoneNumber; } + + public LoginMethod toLoginMethod() { + assert (tenantIds != null); + assert (verified != null); + return new LoginMethod(id, timeJoined, verified, new LoginMethod.PasswordlessInfo(email, phoneNumber), + tenantIds); + } } private static class UserInfoRowMapper implements RowMapper { diff --git a/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java index 7e1fb3ad6..bc4c4db68 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java @@ -19,8 +19,10 @@ import io.supertokens.inmemorydb.ConnectionPool; import io.supertokens.inmemorydb.ConnectionWithLocks; import io.supertokens.inmemorydb.Start; +import io.supertokens.inmemorydb.Utils; import io.supertokens.inmemorydb.config.Config; import io.supertokens.pluginInterface.RowMapper; +import io.supertokens.pluginInterface.authRecipe.LoginMethod; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; @@ -32,6 +34,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.*; +import java.util.stream.Collectors; import static io.supertokens.inmemorydb.QueryExecutorTemplate.execute; import static io.supertokens.inmemorydb.QueryExecutorTemplate.update; @@ -76,12 +79,14 @@ static String getQueryToCreateThirdPartyUserToTenantTable(Start start) { + "UNIQUE (app_id, tenant_id, third_party_id, third_party_user_id)," + "PRIMARY KEY (app_id, tenant_id, user_id)," + "FOREIGN KEY (app_id, tenant_id, user_id)" - + " REFERENCES " + Config.getConfig(start).getUsersTable() + "(app_id, tenant_id, user_id) ON DELETE CASCADE" + + " REFERENCES " + Config.getConfig(start).getUsersTable() + + "(app_id, tenant_id, user_id) ON DELETE CASCADE" + ");"; // @formatter:on } - public static UserInfo signUp(Start start, TenantIdentifier tenantIdentifier, String id, String email, UserInfo.ThirdParty thirdParty, long timeJoined) + public static UserInfo signUp(Start start, TenantIdentifier tenantIdentifier, String id, String email, + LoginMethod.ThirdParty thirdParty, long timeJoined) throws StorageQueryException, StorageTransactionLogicException { return start.startTransaction(con -> { Connection sqlCon = (Connection) con.getConnection(); @@ -135,9 +140,11 @@ public static UserInfo signUp(Start start, TenantIdentifier tenantIdentifier, St }); } - UserInfo userInfo = userInfoWithTenantIds_transaction(start, sqlCon, new UserInfoPartial(id, email, thirdParty, timeJoined)); + UserInfoPartial userInfo = new UserInfoPartial(id, email, thirdParty, timeJoined); + fillUserInfoWithTenantIds_transaction(start, sqlCon, tenantIdentifier.toAppIdentifier(), userInfo); + fillUserInfoWithVerified_transaction(start, sqlCon, tenantIdentifier.toAppIdentifier(), userInfo); sqlCon.commit(); - return userInfo; + return new UserInfo(id, userInfo.verified, userInfo.toLoginMethod()); } catch (SQLException throwables) { throw new StorageTransactionLogicException(throwables); @@ -170,52 +177,56 @@ public static void deleteUser(Start start, AppIdentifier appIdentifier, String u public static UserInfo getThirdPartyUserInfoUsingId(Start start, AppIdentifier appIdentifier, String userId) throws SQLException, StorageQueryException { + // TODO: this should go away.. String QUERY = "SELECT user_id, third_party_id, third_party_user_id, email, time_joined FROM " + getConfig(start).getThirdPartyUsersTable() + " WHERE app_id = ? AND user_id = ?"; - UserInfoPartial userInfo = execute(start, QUERY.toString(), pst -> { - pst.setString(1, appIdentifier.getAppId()); - pst.setString(2, userId); - }, result -> { - if (result.next()) { - return UserInfoRowMapper.getInstance().mapOrThrow(result); + try (Connection con = ConnectionPool.getConnection(start)) { + UserInfoPartial userInfo = execute(con, QUERY.toString(), pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setString(2, userId); + }, result -> { + if (result.next()) { + return UserInfoRowMapper.getInstance().mapOrThrow(result); + } + return null; + }); + if (userInfo == null) { + return null; } - return null; - }); - return userInfoWithTenantIds(start, userInfo); + fillUserInfoWithTenantIds_transaction(start, con, appIdentifier, userInfo); + fillUserInfoWithVerified_transaction(start, con, appIdentifier, userInfo); + return new UserInfo(userInfo.id, userInfo.verified, userInfo.toLoginMethod()); + } } - public static List getUsersInfoUsingIdList(Start start, List ids) + public static List getUsersInfoUsingIdList(Start start, Set ids, AppIdentifier appIdentifier) throws SQLException, StorageQueryException { if (ids.size() > 0) { - // No need to filter based on tenantId because the id list is already filtered for a tenant - StringBuilder QUERY = new StringBuilder( - "SELECT user_id, third_party_id, third_party_user_id, email, time_joined " - + "FROM " + getConfig(start).getThirdPartyUsersTable()); - QUERY.append(" WHERE user_id IN ("); - for (int i = 0; i < ids.size(); i++) { - - QUERY.append("?"); - if (i != ids.size() - 1) { - // not the last element - QUERY.append(","); - } + String QUERY = "SELECT user_id, third_party_id, third_party_user_id, email, time_joined " + + "FROM " + getConfig(start).getThirdPartyUsersTable() + " WHERE user_id IN (" + + Utils.generateCommaSeperatedQuestionMarks(ids.size()) + ") AND app_id = ?"; + + try (Connection con = ConnectionPool.getConnection(start)) { + List userInfos = execute(con, QUERY, pst -> { + int index = 1; + for (String id : ids) { + pst.setString(index, id); + index++; + } + pst.setString(index, appIdentifier.getAppId()); + }, result -> { + List finalResult = new ArrayList<>(); + while (result.next()) { + finalResult.add(UserInfoRowMapper.getInstance().mapOrThrow(result)); + } + return finalResult; + }); + + fillUserInfoWithTenantIds_transaction(start, con, appIdentifier, userInfos); + fillUserInfoWithVerified_transaction(start, con, appIdentifier, userInfos); + return userInfos.stream().map(UserInfoPartial::toLoginMethod).collect(Collectors.toList()); } - QUERY.append(")"); - - List userInfos = execute(start, QUERY.toString(), pst -> { - for (int i = 0; i < ids.size(); i++) { - // i+1 cause this starts with 1 and not 0 - pst.setString(i + 1, ids.get(i)); - } - }, result -> { - List finalResult = new ArrayList<>(); - while (result.next()) { - finalResult.add(UserInfoRowMapper.getInstance().mapOrThrow(result)); - } - return finalResult; - }); - return userInfoWithTenantIds(start, userInfos); } return Collections.emptyList(); } @@ -233,18 +244,26 @@ public static UserInfo getThirdPartyUserInfoUsingId(Start start, TenantIdentifie + "WHERE tp_users_to_tenant.app_id = ? AND tp_users_to_tenant.tenant_id = ? " + "AND tp_users_to_tenant.third_party_id = ? AND tp_users_to_tenant.third_party_user_id = ?"; - UserInfoPartial userInfo = execute(start, QUERY, pst -> { - pst.setString(1, tenantIdentifier.getAppId()); - pst.setString(2, tenantIdentifier.getTenantId()); - pst.setString(3, thirdPartyId); - pst.setString(4, thirdPartyUserId); - }, result -> { - if (result.next()) { - return UserInfoRowMapper.getInstance().mapOrThrow(result); + + try (Connection con = ConnectionPool.getConnection(start)) { + UserInfoPartial userInfo = execute(con, QUERY, pst -> { + pst.setString(1, tenantIdentifier.getAppId()); + pst.setString(2, tenantIdentifier.getTenantId()); + pst.setString(3, thirdPartyId); + pst.setString(4, thirdPartyUserId); + }, result -> { + if (result.next()) { + return UserInfoRowMapper.getInstance().mapOrThrow(result); + } + return null; + }); + if (userInfo == null) { + return null; } - return null; - }); - return userInfoWithTenantIds(start, userInfo); + fillUserInfoWithTenantIds_transaction(start, con, tenantIdentifier.toAppIdentifier(), userInfo); + fillUserInfoWithVerified_transaction(start, con, tenantIdentifier.toAppIdentifier(), userInfo); + return new UserInfo(userInfo.id, userInfo.verified, userInfo.toLoginMethod()); + } } public static void updateUserEmail_Transaction(Start start, Connection con, AppIdentifier appIdentifier, @@ -266,7 +285,8 @@ public static UserInfo getUserInfoUsingId_Transaction(Start start, Connection co String thirdPartyUserId) throws SQLException, StorageQueryException { - ((ConnectionWithLocks) con).lock(appIdentifier.getAppId() + "~" + thirdPartyId + "~" + thirdPartyUserId + Config.getConfig(start).getThirdPartyUsersTable()); + ((ConnectionWithLocks) con).lock(appIdentifier.getAppId() + "~" + thirdPartyId + "~" + thirdPartyUserId + + Config.getConfig(start).getThirdPartyUsersTable()); String QUERY = "SELECT user_id, third_party_id, third_party_user_id, email, time_joined FROM " + getConfig(start).getThirdPartyUsersTable() @@ -281,14 +301,20 @@ public static UserInfo getUserInfoUsingId_Transaction(Start start, Connection co } return null; }); - return userInfoWithTenantIds_transaction(start, con, userInfo); + if (userInfo == null) { + return null; + } + fillUserInfoWithTenantIds_transaction(start, con, appIdentifier, userInfo); + fillUserInfoWithVerified_transaction(start, con, appIdentifier, userInfo); + return new UserInfo(userInfo.id, userInfo.verified, userInfo.toLoginMethod()); } private static UserInfoPartial getUserInfoUsingUserId(Start start, Connection con, AppIdentifier appIdentifier, String userId) throws SQLException, StorageQueryException { - // we don't need a LOCK here because this is already part of a transaction, and locked on app_id_to_user_id table + // we don't need a LOCK here because this is already part of a transaction, and locked on app_id_to_user_id + // table String QUERY = "SELECT user_id, third_party_id, third_party_user_id, email, time_joined FROM " + getConfig(start).getThirdPartyUsersTable() + " WHERE app_id = ? AND user_id = ?"; @@ -316,21 +342,27 @@ public static UserInfo[] getThirdPartyUsersByEmail(Start start, TenantIdentifier + "WHERE tp_users_to_tenant.app_id = ? AND tp_users_to_tenant.tenant_id = ? AND tp_users.email = ? " + "ORDER BY time_joined"; - List userInfos = execute(start, QUERY.toString(), pst -> { - pst.setString(1, tenantIdentifier.getAppId()); - pst.setString(2, tenantIdentifier.getTenantId()); - pst.setString(3, email); - }, result -> { - List finalResult = new ArrayList<>(); - while (result.next()) { - finalResult.add(UserInfoRowMapper.getInstance().mapOrThrow(result)); - } - return finalResult; - }); - return userInfoWithTenantIds(start, userInfos).toArray(new UserInfo[0]); + try (Connection con = ConnectionPool.getConnection(start)) { + List userInfos = execute(con, QUERY.toString(), pst -> { + pst.setString(1, tenantIdentifier.getAppId()); + pst.setString(2, tenantIdentifier.getTenantId()); + pst.setString(3, email); + }, result -> { + List finalResult = new ArrayList<>(); + while (result.next()) { + finalResult.add(UserInfoRowMapper.getInstance().mapOrThrow(result)); + } + return finalResult; + }); + fillUserInfoWithTenantIds_transaction(start, con, tenantIdentifier.toAppIdentifier(), userInfos); + fillUserInfoWithVerified_transaction(start, con, tenantIdentifier.toAppIdentifier(), userInfos); + return userInfos.stream().map(x -> new UserInfo(x.id, x.verified, x.toLoginMethod())) + .toArray(UserInfo[]::new); + } } - public static boolean addUserIdToTenant_Transaction(Start start, Connection sqlCon, TenantIdentifier tenantIdentifier, String userId) + public static boolean addUserIdToTenant_Transaction(Start start, Connection sqlCon, + TenantIdentifier tenantIdentifier, String userId) throws SQLException, StorageQueryException { UserInfoPartial userInfo = ThirdPartyQueries.getUserInfoUsingUserId(start, sqlCon, tenantIdentifier.toAppIdentifier(), userId); @@ -364,7 +396,8 @@ public static boolean addUserIdToTenant_Transaction(Start start, Connection sqlC } } - public static boolean removeUserIdFromTenant_Transaction(Start start, Connection sqlCon, TenantIdentifier tenantIdentifier, String userId) + public static boolean removeUserIdFromTenant_Transaction(Start start, Connection sqlCon, + TenantIdentifier tenantIdentifier, String userId) throws SQLException, StorageQueryException { { // all_auth_recipe_users String QUERY = "DELETE FROM " + getConfig(start).getUsersTable() @@ -382,56 +415,81 @@ public static boolean removeUserIdFromTenant_Transaction(Start start, Connection // automatically deleted from thirdparty_user_to_tenant because of foreign key constraint } - private static UserInfo userInfoWithTenantIds(Start start, UserInfoPartial userInfo) + private static UserInfoPartial fillUserInfoWithVerified_transaction(Start start, Connection sqlCon, + AppIdentifier appIdentifier, + UserInfoPartial userInfo) throws SQLException, StorageQueryException { if (userInfo == null) return null; - try (Connection con = ConnectionPool.getConnection(start)) { - return userInfoWithTenantIds_transaction(start, con, Arrays.asList(userInfo)).get(0); - } + return fillUserInfoWithVerified_transaction(start, sqlCon, appIdentifier, List.of(userInfo)).get(0); } - private static List userInfoWithTenantIds(Start start, List userInfos) + private static List fillUserInfoWithVerified_transaction(Start start, Connection sqlCon, + AppIdentifier appIdentifier, + List userInfos) throws SQLException, StorageQueryException { - try (Connection con = ConnectionPool.getConnection(start)) { - return userInfoWithTenantIds_transaction(start, con, userInfos); + List userIdsAndEmails = new ArrayList<>(); + for (UserInfoPartial userInfo : userInfos) { + userIdsAndEmails.add(new EmailVerificationQueries.UserIdAndEmail(userInfo.id, userInfo.email)); + } + List userIdsThatAreVerified = EmailVerificationQueries.isEmailVerified_transaction(start, sqlCon, + appIdentifier, + userIdsAndEmails); + Set verifiedUserIdsSet = new HashSet<>(userIdsThatAreVerified); + for (UserInfoPartial userInfo : userInfos) { + if (verifiedUserIdsSet.contains(userInfo.id)) { + userInfo.verified = true; + } } + return userInfos; } - private static UserInfo userInfoWithTenantIds_transaction(Start start, Connection sqlCon, UserInfoPartial userInfo) + private static UserInfoPartial fillUserInfoWithTenantIds_transaction(Start start, Connection sqlCon, + AppIdentifier appIdentifier, + UserInfoPartial userInfo) throws SQLException, StorageQueryException { if (userInfo == null) return null; - return userInfoWithTenantIds_transaction(start, sqlCon, Arrays.asList(userInfo)).get(0); + return fillUserInfoWithTenantIds_transaction(start, sqlCon, appIdentifier, List.of(userInfo)).get(0); } - private static List userInfoWithTenantIds_transaction(Start start, Connection sqlCon, List userInfos) + private static List fillUserInfoWithTenantIds_transaction(Start start, Connection sqlCon, + AppIdentifier appIdentifier, + List userInfos) throws SQLException, StorageQueryException { String[] userIds = new String[userInfos.size()]; for (int i = 0; i < userInfos.size(); i++) { userIds[i] = userInfos.get(i).id; } - Map> tenantIdsForUserIds = GeneralQueries.getTenantIdsForUserIds_transaction(start, sqlCon, userIds); - List result = new ArrayList<>(); + Map> tenantIdsForUserIds = GeneralQueries.getTenantIdsForUserIds_transaction(start, sqlCon, + appIdentifier, + userIds); for (UserInfoPartial userInfo : userInfos) { - result.add(new UserInfo(userInfo.id, userInfo.email, userInfo.thirdParty, userInfo.timeJoined, - tenantIdsForUserIds.get(userInfo.id).toArray(new String[0]))); + userInfo.tenantIds = tenantIdsForUserIds.get(userInfo.id).toArray(new String[0]); } - - return result; + return userInfos; } private static class UserInfoPartial { public final String id; public final String email; - public final UserInfo.ThirdParty thirdParty; + public final LoginMethod.ThirdParty thirdParty; public final long timeJoined; + public String[] tenantIds; + public Boolean verified; - public UserInfoPartial(String id, String email, UserInfo.ThirdParty thirdParty, long timeJoined) { + public UserInfoPartial(String id, String email, LoginMethod.ThirdParty thirdParty, long timeJoined) { this.id = id.trim(); this.email = email; this.thirdParty = thirdParty; this.timeJoined = timeJoined; } + + public LoginMethod toLoginMethod() { + assert (tenantIds != null); + assert (verified != null); + return new LoginMethod(id, timeJoined, verified, email, + new LoginMethod.ThirdParty(thirdParty.id, thirdParty.userId), tenantIds); + } } private static class UserInfoRowMapper implements RowMapper { @@ -447,7 +505,7 @@ private static UserInfoRowMapper getInstance() { @Override public UserInfoPartial map(ResultSet result) throws Exception { return new UserInfoPartial(result.getString("user_id"), result.getString("email"), - new UserInfo.ThirdParty(result.getString("third_party_id"), + new LoginMethod.ThirdParty(result.getString("third_party_id"), result.getString("third_party_user_id")), result.getLong("time_joined")); } diff --git a/src/main/java/io/supertokens/thirdparty/ThirdParty.java b/src/main/java/io/supertokens/thirdparty/ThirdParty.java index 5300a6c2a..a11c26ef4 100644 --- a/src/main/java/io/supertokens/thirdparty/ThirdParty.java +++ b/src/main/java/io/supertokens/thirdparty/ThirdParty.java @@ -20,6 +20,7 @@ import io.supertokens.multitenancy.Multitenancy; import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.pluginInterface.Storage; +import io.supertokens.pluginInterface.authRecipe.LoginMethod; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; import io.supertokens.pluginInterface.multitenancy.*; @@ -83,7 +84,7 @@ public static SignInUpResponse signInUp2_7(TenantIdentifierWithStorage tenantIde return response; } - + @TestOnly public static SignInUpResponse signInUp2_7(Main main, String thirdPartyId, String thirdPartyUserId, String email, @@ -141,7 +142,8 @@ private static SignInUpResponse signInUpHelper(TenantIdentifierWithStorage tenan long timeJoined = System.currentTimeMillis(); try { - UserInfo createdUser = storage.signUp(tenantIdentifierWithStorage, userId, email, new UserInfo.ThirdParty(thirdPartyId, thirdPartyUserId), timeJoined); + UserInfo createdUser = storage.signUp(tenantIdentifierWithStorage, userId, email, + new LoginMethod.ThirdParty(thirdPartyId, thirdPartyUserId), timeJoined); return new SignInUpResponse(true, createdUser); } catch (DuplicateUserIdException e) { diff --git a/src/test/java/io/supertokens/test/thirdparty/ThirdPartyTest.java b/src/test/java/io/supertokens/test/thirdparty/ThirdPartyTest.java index 7d7ceaf76..92ae01792 100644 --- a/src/test/java/io/supertokens/test/thirdparty/ThirdPartyTest.java +++ b/src/test/java/io/supertokens/test/thirdparty/ThirdPartyTest.java @@ -19,6 +19,7 @@ import io.supertokens.ProcessState; import io.supertokens.emailverification.EmailVerification; import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.authRecipe.LoginMethod; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.thirdparty.UserInfo; import io.supertokens.pluginInterface.thirdparty.exception.DuplicateThirdPartyUserException; @@ -291,7 +292,7 @@ public void testSignUpWithSameThirdPartyThirdPartyUserIdException() throws Excep try { ((ThirdPartySQLStorage) StorageLayer.getStorage(process.getProcess())) .signUp(new TenantIdentifier(null, null, null), io.supertokens.utils.Utils.getUUID(), email, - new UserInfo.ThirdParty(thirdPartyId, thirdPartyUserId), System.currentTimeMillis()); + new LoginMethod.ThirdParty(thirdPartyId, thirdPartyUserId), System.currentTimeMillis()); throw new Exception("Should not come here"); } catch (DuplicateThirdPartyUserException ignored) { } @@ -324,7 +325,8 @@ public void testSignUpWithSameUserIdAndCheckDuplicateUserIdException() throws Ex try { ((ThirdPartySQLStorage) StorageLayer.getStorage(process.getProcess())) .signUp(new TenantIdentifier(null, null, null), signUpResponse.user.id, email, - new UserInfo.ThirdParty("newThirdParty", "newThirdPartyUserId"), System.currentTimeMillis()); + new LoginMethod.ThirdParty("newThirdParty", "newThirdPartyUserId"), + System.currentTimeMillis()); throw new Exception("Should not come here"); } catch (DuplicateUserIdException ignored) { } diff --git a/src/test/java/io/supertokens/test/thirdparty/ThirdPartyTest2_7.java b/src/test/java/io/supertokens/test/thirdparty/ThirdPartyTest2_7.java index cecd0f6c2..b054bf890 100644 --- a/src/test/java/io/supertokens/test/thirdparty/ThirdPartyTest2_7.java +++ b/src/test/java/io/supertokens/test/thirdparty/ThirdPartyTest2_7.java @@ -19,6 +19,7 @@ import io.supertokens.ProcessState; import io.supertokens.emailverification.EmailVerification; import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.authRecipe.LoginMethod; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.thirdparty.UserInfo; import io.supertokens.pluginInterface.thirdparty.exception.DuplicateThirdPartyUserException; @@ -299,7 +300,7 @@ public void testSignUpWithSameThirdPartyThirdPartyUserIdException() throws Excep try { ((ThirdPartySQLStorage) StorageLayer.getStorage(process.getProcess())) .signUp(new TenantIdentifier(null, null, null), io.supertokens.utils.Utils.getUUID(), email, - new UserInfo.ThirdParty(thirdPartyId, thirdPartyUserId), System.currentTimeMillis()); + new LoginMethod.ThirdParty(thirdPartyId, thirdPartyUserId), System.currentTimeMillis()); throw new Exception("Should not come here"); } catch (DuplicateThirdPartyUserException ignored) { } @@ -332,7 +333,8 @@ public void testSignUpWithSameUserIdAndCheckDuplicateUserIdException() throws Ex try { ((ThirdPartySQLStorage) StorageLayer.getStorage(process.getProcess())) .signUp(new TenantIdentifier(null, null, null), signUpResponse.user.id, email, - new UserInfo.ThirdParty("newThirdParty", "newThirdPartyUserId"), System.currentTimeMillis()); + new LoginMethod.ThirdParty("newThirdParty", "newThirdPartyUserId"), + System.currentTimeMillis()); throw new Exception("Should not come here"); } catch (DuplicateUserIdException ignored) { } From 1116e931e5b95ee4817b3d97629d7bb1d864a356 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Sun, 9 Jul 2023 20:16:20 +0530 Subject: [PATCH 005/131] fixes a few bugs --- .../inmemorydb/queries/EmailPasswordQueries.java | 10 +++++++--- .../inmemorydb/queries/EmailVerificationQueries.java | 2 +- .../inmemorydb/queries/PasswordlessQueries.java | 7 +++++++ .../inmemorydb/queries/ThirdPartyQueries.java | 2 ++ 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java index c2c675fb4..45ee7407c 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java @@ -274,13 +274,15 @@ public static UserInfo signUp(Start start, TenantIdentifier tenantIdentifier, St { // all_auth_recipe_users String QUERY = "INSERT INTO " + getConfig(start).getUsersTable() - + "(app_id, tenant_id, user_id, recipe_id, time_joined)" + " VALUES(?, ?, ?, ?, ?)"; + + "(app_id, tenant_id, user_id, primary_or_recipe_user_id, recipe_id, time_joined)" + + " VALUES(?, ?, ?, ?, ?, ?)"; update(sqlCon, QUERY, pst -> { pst.setString(1, tenantIdentifier.getAppId()); pst.setString(2, tenantIdentifier.getTenantId()); pst.setString(3, userId); - pst.setString(4, EMAIL_PASSWORD.toString()); - pst.setLong(5, timeJoined); + pst.setString(4, userId); + pst.setString(5, EMAIL_PASSWORD.toString()); + pst.setLong(6, timeJoined); }); } @@ -524,6 +526,8 @@ private static List fillUserInfoWithVerified_transaction(Start for (UserInfoPartial userInfo : userInfos) { if (verifiedUserIdsSet.contains(userInfo.id)) { userInfo.verified = true; + } else { + userInfo.verified = false; } } return userInfos; diff --git a/src/main/java/io/supertokens/inmemorydb/queries/EmailVerificationQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/EmailVerificationQueries.java index 338db2b45..777a28bab 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/EmailVerificationQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/EmailVerificationQueries.java @@ -254,7 +254,7 @@ public static List isEmailVerified_transaction(Start start, Connection s } String QUERY = "SELECT * FROM " + getConfig(start).getEmailVerificationTable() + " WHERE app_id = ? AND user_id IN (" + Utils.generateCommaSeperatedQuestionMarks(userIds.size()) + - ") AND email = IN (" + Utils.generateCommaSeperatedQuestionMarks(emails.size()) + ")"; + ") AND email IN (" + Utils.generateCommaSeperatedQuestionMarks(emails.size()) + ")"; return execute(sqlCon, QUERY, pst -> { pst.setString(1, appIdentifier.getAppId()); diff --git a/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java index 7636cce5f..9e4433489 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java @@ -899,8 +899,15 @@ private static List fillUserInfoWithVerified_transaction(Start userIdsAndEmails); Set verifiedUserIdsSet = new HashSet<>(userIdsThatAreVerified); for (UserInfoPartial userInfo : userInfos) { + if (userInfo.verified != null) { + // this means phone number + assert (userInfo.email == null); + continue; + } if (verifiedUserIdsSet.contains(userInfo.id)) { userInfo.verified = true; + } else { + userInfo.verified = false; } } return userInfos; diff --git a/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java index bc4c4db68..7854b3068 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java @@ -438,6 +438,8 @@ private static List fillUserInfoWithVerified_transaction(Start for (UserInfoPartial userInfo : userInfos) { if (verifiedUserIdsSet.contains(userInfo.id)) { userInfo.verified = true; + } else { + userInfo.verified = false; } } return userInfos; From c1e6e1645d5315a053ae8005e3a0bd670e4ff2d7 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Mon, 10 Jul 2023 14:09:56 +0530 Subject: [PATCH 006/131] more changes --- .../queries/EmailPasswordQueries.java | 17 ++++++------ .../inmemorydb/queries/GeneralQueries.java | 23 +++++++++------- .../queries/PasswordlessQueries.java | 25 +++++++++-------- .../inmemorydb/queries/ThirdPartyQueries.java | 27 ++++++++++--------- 4 files changed, 52 insertions(+), 40 deletions(-) diff --git a/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java index 45ee7407c..e6fa17b67 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java @@ -223,7 +223,7 @@ public static UserInfo getUserInfoUsingId_Transaction(Start start, Connection co } fillUserInfoWithTenantIds_transaction(start, con, appIdentifier, userInfo); fillUserInfoWithVerified_transaction(start, con, appIdentifier, userInfo); - return new UserInfo(userInfo.id, userInfo.verified, userInfo.toLoginMethod()); + return new UserInfo(userInfo.id, false, userInfo.toLoginMethod()); } public static PasswordResetTokenInfo getPasswordResetTokenInfo(Start start, AppIdentifier appIdentifier, @@ -315,7 +315,7 @@ public static UserInfo signUp(Start start, TenantIdentifier tenantIdentifier, St fillUserInfoWithTenantIds_transaction(start, sqlCon, tenantIdentifier.toAppIdentifier(), userInfo); fillUserInfoWithVerified_transaction(start, sqlCon, tenantIdentifier.toAppIdentifier(), userInfo); sqlCon.commit(); - return new UserInfo(userId, userInfo.verified, userInfo.toLoginMethod()); + return new UserInfo(userId, false, userInfo.toLoginMethod()); } catch (SQLException throwables) { throw new StorageTransactionLogicException(throwables); } @@ -365,7 +365,7 @@ public static UserInfo getUserInfoUsingId(Start start, AppIdentifier appIdentifi } fillUserInfoWithTenantIds_transaction(start, con, appIdentifier, userInfo); fillUserInfoWithVerified_transaction(start, con, appIdentifier, userInfo); - return new UserInfo(userInfo.id, userInfo.verified, userInfo.toLoginMethod()); + return new UserInfo(userInfo.id, false, userInfo.toLoginMethod()); } } @@ -446,7 +446,7 @@ public static UserInfo getUserInfoUsingEmail(Start start, TenantIdentifier tenan } fillUserInfoWithTenantIds_transaction(start, con, tenantIdentifier.toAppIdentifier(), userInfo); fillUserInfoWithVerified_transaction(start, con, tenantIdentifier.toAppIdentifier(), userInfo); - return new UserInfo(userInfo.id, userInfo.verified, + return new UserInfo(userInfo.id, false, userInfo.toLoginMethod()); } } @@ -459,14 +459,15 @@ public static boolean addUserIdToTenant_Transaction(Start start, Connection sqlC { // all_auth_recipe_users String QUERY = "INSERT INTO " + getConfig(start).getUsersTable() - + "(app_id, tenant_id, user_id, recipe_id, time_joined)" - + " VALUES(?, ?, ?, ?, ?)" + " ON CONFLICT DO NOTHING"; + + "(app_id, tenant_id, user_id, primary_or_recipe_user_id, recipe_id, time_joined)" + + " VALUES(?, ?, ?, ?, ?, ?)" + " ON CONFLICT DO NOTHING"; update(sqlCon, QUERY, pst -> { pst.setString(1, tenantIdentifier.getAppId()); pst.setString(2, tenantIdentifier.getTenantId()); pst.setString(3, userId); - pst.setString(4, EMAIL_PASSWORD.toString()); - pst.setLong(5, userInfo.timeJoined); + pst.setString(4, userId); + pst.setString(5, EMAIL_PASSWORD.toString()); + pst.setLong(6, userInfo.timeJoined); }); } diff --git a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java index c42b8aef7..4dc4cddfc 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java @@ -507,12 +507,14 @@ public static boolean doesUserIdExist(Start start, AppIdentifier appIdentifier, throws SQLException, StorageQueryException { // We query both tables cause there is a case where a primary user ID exists, but its associated // recipe user ID has been deleted AND there are other recipe user IDs linked to this primary user ID already. - String QUERY = "(SELECT 1 FROM " + getConfig(start).getAppIdToUserIdTable() - + " WHERE app_id = ? AND user_id = ?) UNION (SELECT 1 FROM " + getConfig(start).getUsersTable() + - " WHERE app_id = ? AND primary_or_recipe_user_id = ?)"; + String QUERY = "SELECT 1 FROM " + getConfig(start).getAppIdToUserIdTable() + + " WHERE app_id = ? AND user_id = ? UNION SELECT 1 FROM " + getConfig(start).getUsersTable() + + " WHERE app_id = ? AND primary_or_recipe_user_id = ?"; return execute(start, QUERY, pst -> { pst.setString(1, appIdentifier.getAppId()); pst.setString(2, userId); + pst.setString(3, appIdentifier.getAppId()); + pst.setString(4, userId); }, ResultSet::next); } @@ -520,14 +522,17 @@ public static boolean doesUserIdExist(Start start, TenantIdentifier tenantIdenti throws SQLException, StorageQueryException { // We query both tables cause there is a case where a primary user ID exists, but its associated // recipe user ID has been deleted AND there are other recipe user IDs linked to this primary user ID already. - String QUERY = "(SELECT 1 FROM " + getConfig(start).getUsersTable() - + " WHERE app_id = ? AND tenant_id = ? AND user_id = ?) UNION (SELECT 1 FROM " + + String QUERY = "SELECT 1 FROM " + getConfig(start).getUsersTable() + + " WHERE app_id = ? AND tenant_id = ? AND user_id = ? UNION SELECT 1 FROM " + getConfig(start).getUsersTable() + - " WHERE app_id = ? AND tenant_id = ? AND primary_or_recipe_user_id = ?)"; + " WHERE app_id = ? AND tenant_id = ? AND primary_or_recipe_user_id = ?"; return execute(start, QUERY, pst -> { pst.setString(1, tenantIdentifier.getAppId()); pst.setString(2, tenantIdentifier.getTenantId()); pst.setString(3, userId); + pst.setString(4, tenantIdentifier.getAppId()); + pst.setString(5, tenantIdentifier.getTenantId()); + pst.setString(6, userId); }, ResultSet::next); } @@ -535,7 +540,7 @@ public static AuthRecipeUserInfo[] getUsers(Start start, TenantIdentifier tenant @NotNull String timeJoinedOrder, @org.jetbrains.annotations.Nullable RECIPE_ID[] includeRecipeIds, @org.jetbrains.annotations.Nullable - String userId, + String userId, @org.jetbrains.annotations.Nullable Long timeJoined, @Nullable DashboardSearchTags dashboardSearchTags) throws SQLException, StorageQueryException { @@ -917,7 +922,7 @@ private static List getUserInfoForRecipeIdFromUserIds(Start String primaryUserId = authRecipeUsersResultHolder.primaryOrRecipeUserId; AuthRecipeUserInfo curr = userIdToAuthRecipeUserInfo.get(primaryUserId); if (curr == null) { - curr = new AuthRecipeUserInfo(primaryUserId, authRecipeUsersResultHolder.isLinkedOrIsAPrimaryUser, + curr = AuthRecipeUserInfo.create(primaryUserId, authRecipeUsersResultHolder.isLinkedOrIsAPrimaryUser, loginMethod); } else { curr.addLoginMethod(loginMethod); @@ -1059,7 +1064,7 @@ private static class AllAuthRecipeUsersResultHolder { this.tenantId = tenantId; this.primaryOrRecipeUserId = primaryOrRecipeUserId; this.isLinkedOrIsAPrimaryUser = isLinkedOrIsAPrimaryUser; - this.recipeId = RECIPE_ID.valueOf(recipeId); + this.recipeId = RECIPE_ID.getEnumFromString(recipeId); this.timeJoined = timeJoined; } } diff --git a/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java index 9e4433489..f24ff1364 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java @@ -383,13 +383,15 @@ public static UserInfo createUser(Start start, TenantIdentifier tenantIdentifier { // all_auth_recipe_users String QUERY = "INSERT INTO " + getConfig(start).getUsersTable() - + "(app_id, tenant_id, user_id, recipe_id, time_joined)" + " VALUES(?, ?, ?, ?, ?)"; + + "(app_id, tenant_id, user_id, primary_or_recipe_user_id, recipe_id, time_joined)" + + " VALUES(?, ?, ?, ?, ?, ?)"; update(sqlCon, QUERY, pst -> { pst.setString(1, tenantIdentifier.getAppId()); pst.setString(2, tenantIdentifier.getTenantId()); pst.setString(3, id); - pst.setString(4, PASSWORDLESS.toString()); - pst.setLong(5, timeJoined); + pst.setString(4, id); + pst.setString(5, PASSWORDLESS.toString()); + pst.setLong(6, timeJoined); }); } @@ -422,7 +424,7 @@ public static UserInfo createUser(Start start, TenantIdentifier tenantIdentifier fillUserInfoWithTenantIds_transaction(start, sqlCon, tenantIdentifier.toAppIdentifier(), userInfo); fillUserInfoWithVerified_transaction(start, sqlCon, tenantIdentifier.toAppIdentifier(), userInfo); sqlCon.commit(); - return new UserInfo(id, userInfo.verified, + return new UserInfo(id, false, userInfo.toLoginMethod()); } catch (SQLException throwables) { throw new StorageTransactionLogicException(throwables); @@ -727,7 +729,7 @@ public static UserInfo getUserById(Start start, AppIdentifier appIdentifier, Str } fillUserInfoWithTenantIds_transaction(start, con, appIdentifier, userInfo); fillUserInfoWithVerified_transaction(start, con, appIdentifier, userInfo); - return new UserInfo(userInfo.id, userInfo.verified, + return new UserInfo(userInfo.id, false, userInfo.toLoginMethod()); } } @@ -778,7 +780,7 @@ public static UserInfo getUserByEmail(Start start, TenantIdentifier tenantIdenti } fillUserInfoWithTenantIds_transaction(start, con, tenantIdentifier.toAppIdentifier(), userInfo); fillUserInfoWithVerified_transaction(start, con, tenantIdentifier.toAppIdentifier(), userInfo); - return new UserInfo(userInfo.id, userInfo.verified, + return new UserInfo(userInfo.id, false, userInfo.toLoginMethod()); } } @@ -811,7 +813,7 @@ public static UserInfo getUserByPhoneNumber(Start start, TenantIdentifier tenant } fillUserInfoWithTenantIds_transaction(start, con, tenantIdentifier.toAppIdentifier(), userInfo); fillUserInfoWithVerified_transaction(start, con, tenantIdentifier.toAppIdentifier(), userInfo); - return new UserInfo(userInfo.id, userInfo.verified, + return new UserInfo(userInfo.id, false, userInfo.toLoginMethod()); } } @@ -824,14 +826,15 @@ public static boolean addUserIdToTenant_Transaction(Start start, Connection sqlC { // all_auth_recipe_users String QUERY = "INSERT INTO " + getConfig(start).getUsersTable() - + "(app_id, tenant_id, user_id, recipe_id, time_joined)" - + " VALUES(?, ?, ?, ?, ?)" + " ON CONFLICT DO NOTHING"; + + "(app_id, tenant_id, user_id, primary_or_recipe_user_id, recipe_id, time_joined)" + + " VALUES(?, ?, ?, ?, ?, ?)" + " ON CONFLICT DO NOTHING"; update(sqlCon, QUERY, pst -> { pst.setString(1, tenantIdentifier.getAppId()); pst.setString(2, tenantIdentifier.getTenantId()); pst.setString(3, userInfo.id); - pst.setString(4, PASSWORDLESS.toString()); - pst.setLong(5, userInfo.timeJoined); + pst.setString(4, userInfo.id); + pst.setString(5, PASSWORDLESS.toString()); + pst.setLong(6, userInfo.timeJoined); }); } diff --git a/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java index 7854b3068..01462f6cf 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java @@ -103,13 +103,15 @@ public static UserInfo signUp(Start start, TenantIdentifier tenantIdentifier, St { // all_auth_recipe_users String QUERY = "INSERT INTO " + getConfig(start).getUsersTable() - + "(app_id, tenant_id, user_id, recipe_id, time_joined)" + " VALUES(?, ?, ?, ?, ?)"; + + "(app_id, tenant_id, user_id, primary_or_recipe_user_id, recipe_id, time_joined)" + + " VALUES(?, ?, ?, ?, ?, ?)"; update(sqlCon, QUERY, pst -> { pst.setString(1, tenantIdentifier.getAppId()); pst.setString(2, tenantIdentifier.getTenantId()); pst.setString(3, id); - pst.setString(4, THIRD_PARTY.toString()); - pst.setLong(5, timeJoined); + pst.setString(4, id); + pst.setString(5, THIRD_PARTY.toString()); + pst.setLong(6, timeJoined); }); } @@ -144,7 +146,7 @@ public static UserInfo signUp(Start start, TenantIdentifier tenantIdentifier, St fillUserInfoWithTenantIds_transaction(start, sqlCon, tenantIdentifier.toAppIdentifier(), userInfo); fillUserInfoWithVerified_transaction(start, sqlCon, tenantIdentifier.toAppIdentifier(), userInfo); sqlCon.commit(); - return new UserInfo(id, userInfo.verified, userInfo.toLoginMethod()); + return new UserInfo(id, false, userInfo.toLoginMethod()); } catch (SQLException throwables) { throw new StorageTransactionLogicException(throwables); @@ -196,7 +198,7 @@ public static UserInfo getThirdPartyUserInfoUsingId(Start start, AppIdentifier a } fillUserInfoWithTenantIds_transaction(start, con, appIdentifier, userInfo); fillUserInfoWithVerified_transaction(start, con, appIdentifier, userInfo); - return new UserInfo(userInfo.id, userInfo.verified, userInfo.toLoginMethod()); + return new UserInfo(userInfo.id, false, userInfo.toLoginMethod()); } } @@ -262,7 +264,7 @@ public static UserInfo getThirdPartyUserInfoUsingId(Start start, TenantIdentifie } fillUserInfoWithTenantIds_transaction(start, con, tenantIdentifier.toAppIdentifier(), userInfo); fillUserInfoWithVerified_transaction(start, con, tenantIdentifier.toAppIdentifier(), userInfo); - return new UserInfo(userInfo.id, userInfo.verified, userInfo.toLoginMethod()); + return new UserInfo(userInfo.id, false, userInfo.toLoginMethod()); } } @@ -306,7 +308,7 @@ public static UserInfo getUserInfoUsingId_Transaction(Start start, Connection co } fillUserInfoWithTenantIds_transaction(start, con, appIdentifier, userInfo); fillUserInfoWithVerified_transaction(start, con, appIdentifier, userInfo); - return new UserInfo(userInfo.id, userInfo.verified, userInfo.toLoginMethod()); + return new UserInfo(userInfo.id, false, userInfo.toLoginMethod()); } private static UserInfoPartial getUserInfoUsingUserId(Start start, Connection con, @@ -356,7 +358,7 @@ public static UserInfo[] getThirdPartyUsersByEmail(Start start, TenantIdentifier }); fillUserInfoWithTenantIds_transaction(start, con, tenantIdentifier.toAppIdentifier(), userInfos); fillUserInfoWithVerified_transaction(start, con, tenantIdentifier.toAppIdentifier(), userInfos); - return userInfos.stream().map(x -> new UserInfo(x.id, x.verified, x.toLoginMethod())) + return userInfos.stream().map(x -> new UserInfo(x.id, false, x.toLoginMethod())) .toArray(UserInfo[]::new); } } @@ -369,14 +371,15 @@ public static boolean addUserIdToTenant_Transaction(Start start, Connection sqlC { // all_auth_recipe_users String QUERY = "INSERT INTO " + getConfig(start).getUsersTable() - + "(app_id, tenant_id, user_id, recipe_id, time_joined)" - + " VALUES(?, ?, ?, ?, ?)" + " ON CONFLICT DO NOTHING"; + + "(app_id, tenant_id, user_id, primary_or_recipe_user_id, recipe_id, time_joined)" + + " VALUES(?, ?, ?, ?, ?, ?)" + " ON CONFLICT DO NOTHING"; update(sqlCon, QUERY, pst -> { pst.setString(1, tenantIdentifier.getAppId()); pst.setString(2, tenantIdentifier.getTenantId()); pst.setString(3, userInfo.id); - pst.setString(4, THIRD_PARTY.toString()); - pst.setLong(5, userInfo.timeJoined); + pst.setString(4, userInfo.id); + pst.setString(5, THIRD_PARTY.toString()); + pst.setLong(6, userInfo.timeJoined); }); } From 1f819e43a4083588dc8026482148114006c099d0 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Mon, 10 Jul 2023 17:16:00 +0530 Subject: [PATCH 007/131] fixes query --- .../supertokens/inmemorydb/queries/GeneralQueries.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java index 4dc4cddfc..cb21a576a 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java @@ -438,7 +438,9 @@ public static void deleteKeyValue_Transaction(Start start, Connection con, Tenan public static long getUsersCount(Start start, AppIdentifier appIdentifier, RECIPE_ID[] includeRecipeIds) throws SQLException, StorageQueryException { - StringBuilder QUERY = new StringBuilder("SELECT COUNT(*) as total FROM " + getConfig(start).getUsersTable()); + StringBuilder QUERY = new StringBuilder( + "SELECT COUNT(DISTINCT primary_or_recipe_user_id) AS total FROM " + + getConfig(start).getUsersTable()); QUERY.append(" WHERE app_id = ?"); if (includeRecipeIds != null && includeRecipeIds.length > 0) { QUERY.append(" AND recipe_id IN ("); @@ -451,7 +453,6 @@ public static long getUsersCount(Start start, AppIdentifier appIdentifier, RECIP } QUERY.append(")"); } - QUERY.append(" GROUP BY primary_or_recipe_user_id"); return execute(start, QUERY.toString(), pst -> { pst.setString(1, appIdentifier.getAppId()); @@ -471,7 +472,8 @@ public static long getUsersCount(Start start, AppIdentifier appIdentifier, RECIP public static long getUsersCount(Start start, TenantIdentifier tenantIdentifier, RECIPE_ID[] includeRecipeIds) throws SQLException, StorageQueryException { - StringBuilder QUERY = new StringBuilder("SELECT COUNT(*) as total FROM " + getConfig(start).getUsersTable()); + StringBuilder QUERY = new StringBuilder( + "SELECT COUNT(DISTINCT primary_or_recipe_user_id) AS total FROM " + getConfig(start).getUsersTable()); QUERY.append(" WHERE app_id = ? AND tenant_id = ?"); if (includeRecipeIds != null && includeRecipeIds.length > 0) { QUERY.append(" AND recipe_id IN ("); @@ -484,7 +486,6 @@ public static long getUsersCount(Start start, TenantIdentifier tenantIdentifier, } QUERY.append(")"); } - QUERY.append(" GROUP BY primary_or_recipe_user_id"); return execute(start, QUERY.toString(), pst -> { pst.setString(1, tenantIdentifier.getAppId()); From c21240a48fd0c34fd373702de004122bdee60ad1 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Mon, 10 Jul 2023 19:43:46 +0530 Subject: [PATCH 008/131] fixes tests --- .../queries/EmailPasswordQueries.java | 44 ++++++++++++++++-- .../inmemorydb/queries/GeneralQueries.java | 38 ++++++++++++++++ .../queries/PasswordlessQueries.java | 45 +++++++++++++++++-- .../inmemorydb/queries/ThirdPartyQueries.java | 45 +++++++++++++++++-- .../java/io/supertokens/utils/SemVer.java | 21 +++++---- .../webserver/api/core/UsersAPI.java | 14 +++++- .../ImportUserWithPasswordHashAPI.java | 13 +++--- .../api/emailpassword/SignInAPI.java | 9 ++-- .../api/emailpassword/SignUpAPI.java | 11 ++--- .../webserver/api/emailpassword/UserAPI.java | 12 ++--- .../api/passwordless/ConsumeCodeAPI.java | 11 ++--- .../webserver/api/passwordless/UserAPI.java | 15 ++++--- .../api/thirdparty/GetUsersByEmailAPI.java | 17 ++++--- .../webserver/api/thirdparty/SignInUpAPI.java | 10 +++-- .../webserver/api/thirdparty/UserAPI.java | 13 +++--- 15 files changed, 248 insertions(+), 70 deletions(-) diff --git a/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java index e6fa17b67..51f4778b7 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java @@ -223,7 +223,8 @@ public static UserInfo getUserInfoUsingId_Transaction(Start start, Connection co } fillUserInfoWithTenantIds_transaction(start, con, appIdentifier, userInfo); fillUserInfoWithVerified_transaction(start, con, appIdentifier, userInfo); - return new UserInfo(userInfo.id, false, userInfo.toLoginMethod()); + fillUserInfoWithIsPrimaryUserBoolean_transaction(start, con, appIdentifier, userInfo); + return new UserInfo(userInfo.id, userInfo.isPrimary, userInfo.toLoginMethod()); } public static PasswordResetTokenInfo getPasswordResetTokenInfo(Start start, AppIdentifier appIdentifier, @@ -314,8 +315,10 @@ public static UserInfo signUp(Start start, TenantIdentifier tenantIdentifier, St UserInfoPartial userInfo = new UserInfoPartial(userId, email, passwordHash, timeJoined); fillUserInfoWithTenantIds_transaction(start, sqlCon, tenantIdentifier.toAppIdentifier(), userInfo); fillUserInfoWithVerified_transaction(start, sqlCon, tenantIdentifier.toAppIdentifier(), userInfo); + fillUserInfoWithIsPrimaryUserBoolean_transaction(start, sqlCon, tenantIdentifier.toAppIdentifier(), + userInfo); sqlCon.commit(); - return new UserInfo(userId, false, userInfo.toLoginMethod()); + return new UserInfo(userId, userInfo.isPrimary, userInfo.toLoginMethod()); } catch (SQLException throwables) { throw new StorageTransactionLogicException(throwables); } @@ -365,7 +368,9 @@ public static UserInfo getUserInfoUsingId(Start start, AppIdentifier appIdentifi } fillUserInfoWithTenantIds_transaction(start, con, appIdentifier, userInfo); fillUserInfoWithVerified_transaction(start, con, appIdentifier, userInfo); - return new UserInfo(userInfo.id, false, userInfo.toLoginMethod()); + fillUserInfoWithIsPrimaryUserBoolean_transaction(start, con, appIdentifier, + userInfo); + return new UserInfo(userInfo.id, userInfo.isPrimary, userInfo.toLoginMethod()); } } @@ -446,7 +451,9 @@ public static UserInfo getUserInfoUsingEmail(Start start, TenantIdentifier tenan } fillUserInfoWithTenantIds_transaction(start, con, tenantIdentifier.toAppIdentifier(), userInfo); fillUserInfoWithVerified_transaction(start, con, tenantIdentifier.toAppIdentifier(), userInfo); - return new UserInfo(userInfo.id, false, + fillUserInfoWithIsPrimaryUserBoolean_transaction(start, con, tenantIdentifier.toAppIdentifier(), + userInfo); + return new UserInfo(userInfo.id, userInfo.isPrimary, userInfo.toLoginMethod()); } } @@ -561,6 +568,34 @@ private static List fillUserInfoWithTenantIds_transaction(Start return userInfos; } + private static UserInfoPartial fillUserInfoWithIsPrimaryUserBoolean_transaction(Start start, Connection sqlCon, + AppIdentifier appIdentifier, + UserInfoPartial userInfo) + throws SQLException, StorageQueryException { + if (userInfo == null) return null; + return fillUserInfoWithIsPrimaryUserBoolean_transaction(start, sqlCon, appIdentifier, + Arrays.asList(userInfo)).get(0); + } + + private static List fillUserInfoWithIsPrimaryUserBoolean_transaction(Start start, + Connection sqlCon, + AppIdentifier appIdentifier, + List userInfos) + throws SQLException, StorageQueryException { + String[] userIds = new String[userInfos.size()]; + for (int i = 0; i < userInfos.size(); i++) { + userIds[i] = userInfos.get(i).id; + } + + Map isPrimaryUserForUserIds = GeneralQueries.getIsPrimaryUserBoolean_transaction(start, sqlCon, + appIdentifier, + userIds); + for (UserInfoPartial userInfo : userInfos) { + userInfo.isPrimary = isPrimaryUserForUserIds.get(userInfo.id); + } + return userInfos; + } + private static class UserInfoPartial { public final String id; public final long timeJoined; @@ -568,6 +603,7 @@ private static class UserInfoPartial { public final String passwordHash; public String[] tenantIds; public Boolean verified; + public Boolean isPrimary; public UserInfoPartial(String id, String email, String passwordHash, long timeJoined) { this.id = id.trim(); diff --git a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java index cb21a576a..f5a1997c3 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java @@ -998,6 +998,44 @@ public static Map> getTenantIdsForUserIds_transaction(Start return new HashMap<>(); } + public static Map getIsPrimaryUserBoolean_transaction(Start start, Connection sqlCon, + AppIdentifier appIdentifier, + String[] userIds) + throws SQLException, StorageQueryException { + if (userIds != null && userIds.length > 0) { + StringBuilder QUERY = new StringBuilder("SELECT user_id, is_linked_or_is_a_primary_user " + + "FROM " + getConfig(start).getUsersTable()); + QUERY.append(" WHERE user_id IN ("); + for (int i = 0; i < userIds.length; i++) { + + QUERY.append("?"); + if (i != userIds.length - 1) { + // not the last element + QUERY.append(","); + } + } + QUERY.append(") AND app_id = ?"); + + return execute(sqlCon, QUERY.toString(), pst -> { + for (int i = 0; i < userIds.length; i++) { + // i+1 cause this starts with 1 and not 0 + pst.setString(i + 1, userIds[i]); + } + pst.setString(userIds.length + 1, appIdentifier.getAppId()); + }, result -> { + Map finalResult = new HashMap<>(); + while (result.next()) { + String userId = result.getString("user_id").trim(); + Boolean isLinkedOrPrimaryUser = result.getBoolean("is_linked_or_is_a_primary_user"); + finalResult.put(userId, isLinkedOrPrimaryUser); + } + return finalResult; + }); + } + + return new HashMap<>(); + } + @TestOnly public static String[] getAllTablesInTheDatabase(Start start) throws SQLException, StorageQueryException { if (!Start.isTesting) { diff --git a/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java index f24ff1364..a7622728e 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java @@ -423,8 +423,10 @@ public static UserInfo createUser(Start start, TenantIdentifier tenantIdentifier UserInfoPartial userInfo = new UserInfoPartial(id, email, phoneNumber, timeJoined); fillUserInfoWithTenantIds_transaction(start, sqlCon, tenantIdentifier.toAppIdentifier(), userInfo); fillUserInfoWithVerified_transaction(start, sqlCon, tenantIdentifier.toAppIdentifier(), userInfo); + fillUserInfoWithIsPrimaryUserBoolean_transaction(start, sqlCon, tenantIdentifier.toAppIdentifier(), + userInfo); sqlCon.commit(); - return new UserInfo(id, false, + return new UserInfo(id, userInfo.isPrimary, userInfo.toLoginMethod()); } catch (SQLException throwables) { throw new StorageTransactionLogicException(throwables); @@ -729,7 +731,9 @@ public static UserInfo getUserById(Start start, AppIdentifier appIdentifier, Str } fillUserInfoWithTenantIds_transaction(start, con, appIdentifier, userInfo); fillUserInfoWithVerified_transaction(start, con, appIdentifier, userInfo); - return new UserInfo(userInfo.id, false, + fillUserInfoWithIsPrimaryUserBoolean_transaction(start, con, appIdentifier, + userInfo); + return new UserInfo(userInfo.id, userInfo.isPrimary, userInfo.toLoginMethod()); } } @@ -780,7 +784,9 @@ public static UserInfo getUserByEmail(Start start, TenantIdentifier tenantIdenti } fillUserInfoWithTenantIds_transaction(start, con, tenantIdentifier.toAppIdentifier(), userInfo); fillUserInfoWithVerified_transaction(start, con, tenantIdentifier.toAppIdentifier(), userInfo); - return new UserInfo(userInfo.id, false, + fillUserInfoWithIsPrimaryUserBoolean_transaction(start, con, tenantIdentifier.toAppIdentifier(), + userInfo); + return new UserInfo(userInfo.id, userInfo.isPrimary, userInfo.toLoginMethod()); } } @@ -813,7 +819,9 @@ public static UserInfo getUserByPhoneNumber(Start start, TenantIdentifier tenant } fillUserInfoWithTenantIds_transaction(start, con, tenantIdentifier.toAppIdentifier(), userInfo); fillUserInfoWithVerified_transaction(start, con, tenantIdentifier.toAppIdentifier(), userInfo); - return new UserInfo(userInfo.id, false, + fillUserInfoWithIsPrimaryUserBoolean_transaction(start, con, tenantIdentifier.toAppIdentifier(), + userInfo); + return new UserInfo(userInfo.id, userInfo.isPrimary, userInfo.toLoginMethod()); } } @@ -943,6 +951,34 @@ private static List fillUserInfoWithTenantIds_transaction(Start return userInfos; } + private static UserInfoPartial fillUserInfoWithIsPrimaryUserBoolean_transaction(Start start, Connection sqlCon, + AppIdentifier appIdentifier, + UserInfoPartial userInfo) + throws SQLException, StorageQueryException { + if (userInfo == null) return null; + return fillUserInfoWithIsPrimaryUserBoolean_transaction(start, sqlCon, appIdentifier, + Arrays.asList(userInfo)).get(0); + } + + private static List fillUserInfoWithIsPrimaryUserBoolean_transaction(Start start, + Connection sqlCon, + AppIdentifier appIdentifier, + List userInfos) + throws SQLException, StorageQueryException { + String[] userIds = new String[userInfos.size()]; + for (int i = 0; i < userInfos.size(); i++) { + userIds[i] = userInfos.get(i).id; + } + + Map isPrimaryUserForUserIds = GeneralQueries.getIsPrimaryUserBoolean_transaction(start, sqlCon, + appIdentifier, + userIds); + for (UserInfoPartial userInfo : userInfos) { + userInfo.isPrimary = isPrimaryUserForUserIds.get(userInfo.id); + } + return userInfos; + } + private static class PasswordlessDeviceRowMapper implements RowMapper { private static final PasswordlessDeviceRowMapper INSTANCE = new PasswordlessDeviceRowMapper(); @@ -985,6 +1021,7 @@ private static class UserInfoPartial { public final String phoneNumber; public String[] tenantIds; public Boolean verified; + public Boolean isPrimary; UserInfoPartial(String id, @Nullable String email, @Nullable String phoneNumber, long timeJoined) { this.id = id.trim(); diff --git a/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java index 01462f6cf..63a990c4f 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java @@ -145,8 +145,10 @@ public static UserInfo signUp(Start start, TenantIdentifier tenantIdentifier, St UserInfoPartial userInfo = new UserInfoPartial(id, email, thirdParty, timeJoined); fillUserInfoWithTenantIds_transaction(start, sqlCon, tenantIdentifier.toAppIdentifier(), userInfo); fillUserInfoWithVerified_transaction(start, sqlCon, tenantIdentifier.toAppIdentifier(), userInfo); + fillUserInfoWithIsPrimaryUserBoolean_transaction(start, sqlCon, tenantIdentifier.toAppIdentifier(), + userInfo); sqlCon.commit(); - return new UserInfo(id, false, userInfo.toLoginMethod()); + return new UserInfo(id, userInfo.isPrimary, userInfo.toLoginMethod()); } catch (SQLException throwables) { throw new StorageTransactionLogicException(throwables); @@ -198,7 +200,9 @@ public static UserInfo getThirdPartyUserInfoUsingId(Start start, AppIdentifier a } fillUserInfoWithTenantIds_transaction(start, con, appIdentifier, userInfo); fillUserInfoWithVerified_transaction(start, con, appIdentifier, userInfo); - return new UserInfo(userInfo.id, false, userInfo.toLoginMethod()); + fillUserInfoWithIsPrimaryUserBoolean_transaction(start, con, appIdentifier, + userInfo); + return new UserInfo(userInfo.id, userInfo.isPrimary, userInfo.toLoginMethod()); } } @@ -264,7 +268,9 @@ public static UserInfo getThirdPartyUserInfoUsingId(Start start, TenantIdentifie } fillUserInfoWithTenantIds_transaction(start, con, tenantIdentifier.toAppIdentifier(), userInfo); fillUserInfoWithVerified_transaction(start, con, tenantIdentifier.toAppIdentifier(), userInfo); - return new UserInfo(userInfo.id, false, userInfo.toLoginMethod()); + fillUserInfoWithIsPrimaryUserBoolean_transaction(start, con, tenantIdentifier.toAppIdentifier(), + userInfo); + return new UserInfo(userInfo.id, userInfo.isPrimary, userInfo.toLoginMethod()); } } @@ -308,7 +314,9 @@ public static UserInfo getUserInfoUsingId_Transaction(Start start, Connection co } fillUserInfoWithTenantIds_transaction(start, con, appIdentifier, userInfo); fillUserInfoWithVerified_transaction(start, con, appIdentifier, userInfo); - return new UserInfo(userInfo.id, false, userInfo.toLoginMethod()); + fillUserInfoWithIsPrimaryUserBoolean_transaction(start, con, appIdentifier, + userInfo); + return new UserInfo(userInfo.id, userInfo.isPrimary, userInfo.toLoginMethod()); } private static UserInfoPartial getUserInfoUsingUserId(Start start, Connection con, @@ -474,6 +482,34 @@ private static List fillUserInfoWithTenantIds_transaction(Start return userInfos; } + private static UserInfoPartial fillUserInfoWithIsPrimaryUserBoolean_transaction(Start start, Connection sqlCon, + AppIdentifier appIdentifier, + UserInfoPartial userInfo) + throws SQLException, StorageQueryException { + if (userInfo == null) return null; + return fillUserInfoWithIsPrimaryUserBoolean_transaction(start, sqlCon, appIdentifier, + Arrays.asList(userInfo)).get(0); + } + + private static List fillUserInfoWithIsPrimaryUserBoolean_transaction(Start start, + Connection sqlCon, + AppIdentifier appIdentifier, + List userInfos) + throws SQLException, StorageQueryException { + String[] userIds = new String[userInfos.size()]; + for (int i = 0; i < userInfos.size(); i++) { + userIds[i] = userInfos.get(i).id; + } + + Map isPrimaryUserForUserIds = GeneralQueries.getIsPrimaryUserBoolean_transaction(start, sqlCon, + appIdentifier, + userIds); + for (UserInfoPartial userInfo : userInfos) { + userInfo.isPrimary = isPrimaryUserForUserIds.get(userInfo.id); + } + return userInfos; + } + private static class UserInfoPartial { public final String id; public final String email; @@ -481,6 +517,7 @@ private static class UserInfoPartial { public final long timeJoined; public String[] tenantIds; public Boolean verified; + public Boolean isPrimary; public UserInfoPartial(String id, String email, LoginMethod.ThirdParty thirdParty, long timeJoined) { this.id = id.trim(); diff --git a/src/main/java/io/supertokens/utils/SemVer.java b/src/main/java/io/supertokens/utils/SemVer.java index 87f8974ef..0b73bfeb7 100644 --- a/src/main/java/io/supertokens/utils/SemVer.java +++ b/src/main/java/io/supertokens/utils/SemVer.java @@ -33,6 +33,7 @@ public class SemVer implements Comparable { public static final SemVer v2_20 = new SemVer("2.20"); public static final SemVer v2_21 = new SemVer("2.21"); public static final SemVer v3_0 = new SemVer("3.0"); + public static final SemVer v4_0 = new SemVer("4.0"); final private String version; @@ -41,11 +42,11 @@ public final String get() { } public SemVer(String version) { - if(version == null) { + if (version == null) { throw new IllegalArgumentException("Version can not be null"); } - if(!version.matches("[0-9]+(\\.[0-9]+)*")) { + if (!version.matches("[0-9]+(\\.[0-9]+)*")) { throw new IllegalArgumentException("Invalid version format"); } @@ -64,7 +65,8 @@ public boolean lesserThan(SemVer max) { return this.compareTo(max) < 0; } - @Override public int compareTo(SemVer that) { + @Override + public int compareTo(SemVer that) { if (that == null) { return 1; } @@ -78,11 +80,11 @@ public boolean lesserThan(SemVer max) { int thisPart = i < thisParts.length ? Integer.parseInt(thisParts[i]) : 0; int thatPart = i < thatParts.length ? Integer.parseInt(thatParts[i]) : 0; - if(thisPart < thatPart) { + if (thisPart < thatPart) { return -1; } - if(thisPart > thatPart) { + if (thisPart > thatPart) { return 1; } } @@ -90,16 +92,17 @@ public boolean lesserThan(SemVer max) { return 0; } - @Override public boolean equals(Object that) { - if(this == that) { + @Override + public boolean equals(Object that) { + if (this == that) { return true; } - if(that == null) { + if (that == null) { return false; } - if(!(that instanceof SemVer)) { + if (!(that instanceof SemVer)) { return false; } diff --git a/src/main/java/io/supertokens/webserver/api/core/UsersAPI.java b/src/main/java/io/supertokens/webserver/api/core/UsersAPI.java index 9184eec20..34d5a4d26 100644 --- a/src/main/java/io/supertokens/webserver/api/core/UsersAPI.java +++ b/src/main/java/io/supertokens/webserver/api/core/UsersAPI.java @@ -16,7 +16,9 @@ package io.supertokens.webserver.api.core; -import com.google.gson.*; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import io.supertokens.Main; import io.supertokens.authRecipe.AuthRecipe; import io.supertokens.authRecipe.UserPaginationContainer; @@ -183,7 +185,15 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO JsonObject result = new JsonObject(); result.addProperty("status", "OK"); - JsonArray usersJson = new JsonParser().parse(new Gson().toJson(users.users)).getAsJsonArray(); + JsonArray usersJson = new JsonArray(); + for (UserPaginationContainer.UsersContainer user : users.users) { + JsonObject jsonObj = new JsonObject(); + jsonObj.addProperty("recipeId", user.recipeId); + JsonObject userJson = + getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v4_0) ? user.user.toJson() : + user.user.toJsonWithoutAccountLinking(); + jsonObj.add("user", userJson); + } if (getVersionFromRequest(req).lesserThan(SemVer.v3_0)) { for (JsonElement user : usersJson) { diff --git a/src/main/java/io/supertokens/webserver/api/emailpassword/ImportUserWithPasswordHashAPI.java b/src/main/java/io/supertokens/webserver/api/emailpassword/ImportUserWithPasswordHashAPI.java index cc71246f2..1af432641 100644 --- a/src/main/java/io/supertokens/webserver/api/emailpassword/ImportUserWithPasswordHashAPI.java +++ b/src/main/java/io/supertokens/webserver/api/emailpassword/ImportUserWithPasswordHashAPI.java @@ -16,9 +16,7 @@ package io.supertokens.webserver.api.emailpassword; -import com.google.gson.Gson; import com.google.gson.JsonObject; -import com.google.gson.JsonParser; import io.supertokens.Main; import io.supertokens.config.CoreConfig; import io.supertokens.emailpassword.EmailPassword; @@ -27,6 +25,7 @@ import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.utils.SemVer; import io.supertokens.utils.Utils; @@ -93,12 +92,15 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I } try { + TenantIdentifierWithStorage tenant = this.getTenantIdentifierWithStorageFromRequest(req); EmailPassword.ImportUserResponse importUserResponse = EmailPassword.importUserWithPasswordHash( - this.getTenantIdentifierWithStorageFromRequest(req), main, email, + tenant, main, email, passwordHash, passwordHashingAlgorithm); JsonObject response = new JsonObject(); response.addProperty("status", "OK"); - JsonObject userJson = new JsonParser().parse(new Gson().toJson(importUserResponse.user)).getAsJsonObject(); + JsonObject userJson = + getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v4_0) ? importUserResponse.user.toJson() : + importUserResponse.user.toJsonWithoutAccountLinking(); if (getVersionFromRequest(req).lesserThan(SemVer.v3_0)) { userJson.remove("tenantIds"); @@ -107,7 +109,8 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I response.add("user", userJson); response.addProperty("didUserAlreadyExist", importUserResponse.didUserAlreadyExist); super.sendJsonResponse(200, response, resp); - } catch (StorageQueryException | StorageTransactionLogicException | TenantOrAppNotFoundException | BadPermissionException e) { + } catch (StorageQueryException | StorageTransactionLogicException | TenantOrAppNotFoundException | + BadPermissionException e) { throw new ServletException(e); } catch (UnsupportedPasswordHashingFormatException e) { throw new ServletException(new WebserverAPI.BadRequestException(e.getMessage())); diff --git a/src/main/java/io/supertokens/webserver/api/emailpassword/SignInAPI.java b/src/main/java/io/supertokens/webserver/api/emailpassword/SignInAPI.java index 8f2568d0b..80d399c18 100644 --- a/src/main/java/io/supertokens/webserver/api/emailpassword/SignInAPI.java +++ b/src/main/java/io/supertokens/webserver/api/emailpassword/SignInAPI.java @@ -16,10 +16,7 @@ package io.supertokens.webserver.api.emailpassword; -import com.google.gson.Gson; import com.google.gson.JsonObject; -import com.google.gson.JsonParser; - import io.supertokens.ActiveUsers; import io.supertokens.Main; import io.supertokens.emailpassword.EmailPassword; @@ -79,7 +76,8 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I try { UserInfo user = EmailPassword.signIn(tenantIdentifierWithStorage, super.main, normalisedEmail, password); - ActiveUsers.updateLastActive(tenantIdentifierWithStorage.toAppIdentifierWithStorage(), main, user.id); // use the internal user id + ActiveUsers.updateLastActive(tenantIdentifierWithStorage.toAppIdentifierWithStorage(), main, + user.id); // use the internal user id // if a userIdMapping exists, pass the externalUserId to the response UserIdMapping userIdMapping = io.supertokens.useridmapping.UserIdMapping.getUserIdMapping( @@ -91,7 +89,8 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I JsonObject result = new JsonObject(); result.addProperty("status", "OK"); - JsonObject userJson = new JsonParser().parse(new Gson().toJson(user)).getAsJsonObject(); + JsonObject userJson = getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v4_0) ? user.toJson() : + user.toJsonWithoutAccountLinking(); if (getVersionFromRequest(req).lesserThan(SemVer.v3_0)) { userJson.remove("tenantIds"); } diff --git a/src/main/java/io/supertokens/webserver/api/emailpassword/SignUpAPI.java b/src/main/java/io/supertokens/webserver/api/emailpassword/SignUpAPI.java index 8c184b021..ae69aa3a8 100644 --- a/src/main/java/io/supertokens/webserver/api/emailpassword/SignUpAPI.java +++ b/src/main/java/io/supertokens/webserver/api/emailpassword/SignUpAPI.java @@ -16,10 +16,7 @@ package io.supertokens.webserver.api.emailpassword; -import com.google.gson.Gson; import com.google.gson.JsonObject; -import com.google.gson.JsonParser; - import io.supertokens.ActiveUsers; import io.supertokens.Main; import io.supertokens.emailpassword.EmailPassword; @@ -30,6 +27,7 @@ import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.utils.SemVer; import io.supertokens.utils.Utils; @@ -79,13 +77,16 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I } try { - UserInfo user = EmailPassword.signUp(this.getTenantIdentifierWithStorageFromRequest(req), super.main, normalisedEmail, password); + TenantIdentifierWithStorage tenant = this.getTenantIdentifierWithStorageFromRequest(req); + UserInfo user = EmailPassword.signUp(tenant, super.main, normalisedEmail, password); ActiveUsers.updateLastActive(this.getAppIdentifierWithStorage(req), main, user.id); JsonObject result = new JsonObject(); result.addProperty("status", "OK"); - JsonObject userJson = new JsonParser().parse(new Gson().toJson(user)).getAsJsonObject(); + JsonObject userJson = + getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v4_0) ? user.toJson() : + user.toJsonWithoutAccountLinking(); if (getVersionFromRequest(req).lesserThan(SemVer.v3_0)) { userJson.remove("tenantIds"); diff --git a/src/main/java/io/supertokens/webserver/api/emailpassword/UserAPI.java b/src/main/java/io/supertokens/webserver/api/emailpassword/UserAPI.java index d3503ed2c..b1d82e83f 100644 --- a/src/main/java/io/supertokens/webserver/api/emailpassword/UserAPI.java +++ b/src/main/java/io/supertokens/webserver/api/emailpassword/UserAPI.java @@ -16,9 +16,8 @@ package io.supertokens.webserver.api.emailpassword; -import com.google.gson.Gson; import com.google.gson.JsonObject; -import com.google.gson.JsonParser; +import io.supertokens.AppIdentifierWithStorageAndUserIdMapping; import io.supertokens.Main; import io.supertokens.emailpassword.EmailPassword; import io.supertokens.output.Logging; @@ -31,7 +30,6 @@ import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; -import io.supertokens.AppIdentifierWithStorageAndUserIdMapping; import io.supertokens.useridmapping.UserIdMapping; import io.supertokens.useridmapping.UserIdType; import io.supertokens.utils.SemVer; @@ -99,7 +97,9 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO // API is tenant specific for get by Email // Query by email String normalisedEmail = Utils.normaliseEmail(email); - TenantIdentifierWithStorage tenantIdentifierWithStorage = this.getTenantIdentifierWithStorageFromRequest(req); + TenantIdentifierWithStorage tenantIdentifierWithStorage = + this.getTenantIdentifierWithStorageFromRequest( + req); user = EmailPassword.getUserUsingEmail(tenantIdentifierWithStorage, normalisedEmail); // if a userIdMapping exists, set the userId in the response to the externalUserId @@ -124,7 +124,9 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO } else { JsonObject result = new JsonObject(); result.addProperty("status", "OK"); - JsonObject userJson = new JsonParser().parse(new Gson().toJson(user)).getAsJsonObject(); + JsonObject userJson = + getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v4_0) ? user.toJson() : + user.toJsonWithoutAccountLinking(); if (getVersionFromRequest(req).lesserThan(SemVer.v3_0)) { userJson.remove("tenantIds"); diff --git a/src/main/java/io/supertokens/webserver/api/passwordless/ConsumeCodeAPI.java b/src/main/java/io/supertokens/webserver/api/passwordless/ConsumeCodeAPI.java index 5e654c22a..983b00d61 100644 --- a/src/main/java/io/supertokens/webserver/api/passwordless/ConsumeCodeAPI.java +++ b/src/main/java/io/supertokens/webserver/api/passwordless/ConsumeCodeAPI.java @@ -16,9 +16,7 @@ package io.supertokens.webserver.api.passwordless; -import com.google.gson.Gson; import com.google.gson.JsonObject; -import com.google.gson.JsonParser; import io.supertokens.ActiveUsers; import io.supertokens.Main; import io.supertokens.multitenancy.exception.BadPermissionException; @@ -88,7 +86,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I userInputCode, linkCode); ActiveUsers.updateLastActive(this.getAppIdentifierWithStorage(req), main, consumeCodeResponse.user.id); - + UserIdMapping userIdMapping = io.supertokens.useridmapping.UserIdMapping.getUserIdMapping( this.getAppIdentifierWithStorage(req), consumeCodeResponse.user.id, UserIdType.ANY); @@ -98,7 +96,9 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I JsonObject result = new JsonObject(); result.addProperty("status", "OK"); - JsonObject userJson = new JsonParser().parse(new Gson().toJson(consumeCodeResponse.user)).getAsJsonObject(); + JsonObject userJson = + getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v4_0) ? consumeCodeResponse.user.toJson() : + consumeCodeResponse.user.toJsonWithoutAccountLinking(); if (getVersionFromRequest(req).lesserThan(SemVer.v3_0)) { userJson.remove("tenantIds"); @@ -127,7 +127,8 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I super.sendJsonResponse(200, result, resp); } catch (DeviceIdHashMismatchException ex) { throw new ServletException(new BadRequestException("preAuthSessionId and deviceId doesn't match")); - } catch (StorageTransactionLogicException | StorageQueryException | NoSuchAlgorithmException | InvalidKeyException | TenantOrAppNotFoundException | BadPermissionException e) { + } catch (StorageTransactionLogicException | StorageQueryException | NoSuchAlgorithmException | + InvalidKeyException | TenantOrAppNotFoundException | BadPermissionException e) { throw new ServletException(e); } catch (Base64EncodingException ex) { throw new ServletException(new BadRequestException("Input encoding error in " + ex.source)); diff --git a/src/main/java/io/supertokens/webserver/api/passwordless/UserAPI.java b/src/main/java/io/supertokens/webserver/api/passwordless/UserAPI.java index ccd7a7651..1ebf0b91f 100644 --- a/src/main/java/io/supertokens/webserver/api/passwordless/UserAPI.java +++ b/src/main/java/io/supertokens/webserver/api/passwordless/UserAPI.java @@ -16,12 +16,9 @@ package io.supertokens.webserver.api.passwordless; -import com.google.gson.Gson; import com.google.gson.JsonObject; -import com.google.gson.JsonParser; import io.supertokens.AppIdentifierWithStorageAndUserIdMapping; import io.supertokens.Main; -import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.passwordless.Passwordless; import io.supertokens.passwordless.Passwordless.FieldUpdate; import io.supertokens.passwordless.exceptions.UserWithoutContactInfoException; @@ -29,6 +26,7 @@ import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.passwordless.UserInfo; import io.supertokens.pluginInterface.passwordless.exception.DuplicatePhoneNumberException; import io.supertokens.pluginInterface.useridmapping.UserIdMapping; @@ -103,7 +101,8 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO } } } else { - user = Passwordless.getUserByPhoneNumber(this.getTenantIdentifierWithStorageFromRequest(req), phoneNumber); + user = Passwordless.getUserByPhoneNumber(this.getTenantIdentifierWithStorageFromRequest(req), + phoneNumber); if (user != null) { UserIdMapping userIdMapping = io.supertokens.useridmapping.UserIdMapping.getUserIdMapping( this.getAppIdentifierWithStorage(req), @@ -123,7 +122,9 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO JsonObject result = new JsonObject(); result.addProperty("status", "OK"); - JsonObject userJson = new JsonParser().parse(new Gson().toJson(user)).getAsJsonObject(); + JsonObject userJson = + getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v4_0) ? user.toJson() : + user.toJsonWithoutAccountLinking(); if (getVersionFromRequest(req).lesserThan(SemVer.v3_0)) { userJson.remove("tenantIds"); @@ -146,11 +147,11 @@ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IO FieldUpdate emailUpdate = !input.has("email") ? null : new FieldUpdate(input.get("email").isJsonNull() ? null - : Utils.normaliseEmail(InputParser.parseStringOrThrowError(input, "email", false))); + : Utils.normaliseEmail(InputParser.parseStringOrThrowError(input, "email", false))); FieldUpdate phoneNumberUpdate = !input.has("phoneNumber") ? null : new FieldUpdate(input.get("phoneNumber").isJsonNull() ? null - : InputParser.parseStringOrThrowError(input, "phoneNumber", false)); + : InputParser.parseStringOrThrowError(input, "phoneNumber", false)); try { AppIdentifierWithStorageAndUserIdMapping appIdentifierWithStorageAndUserIdMapping = diff --git a/src/main/java/io/supertokens/webserver/api/thirdparty/GetUsersByEmailAPI.java b/src/main/java/io/supertokens/webserver/api/thirdparty/GetUsersByEmailAPI.java index 73350b50a..9c7a570f6 100644 --- a/src/main/java/io/supertokens/webserver/api/thirdparty/GetUsersByEmailAPI.java +++ b/src/main/java/io/supertokens/webserver/api/thirdparty/GetUsersByEmailAPI.java @@ -16,13 +16,15 @@ package io.supertokens.webserver.api.thirdparty; -import com.google.gson.*; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import io.supertokens.Main; +import io.supertokens.pluginInterface.RECIPE_ID; +import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; -import io.supertokens.pluginInterface.RECIPE_ID; -import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.thirdparty.UserInfo; import io.supertokens.thirdparty.ThirdParty; import io.supertokens.useridmapping.UserIdMapping; @@ -53,7 +55,8 @@ public String getPath() { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { // this API is tenant specific try { - TenantIdentifierWithStorage tenantIdentifierWithStorage = this.getTenantIdentifierWithStorageFromRequest(req); + TenantIdentifierWithStorage tenantIdentifierWithStorage = this.getTenantIdentifierWithStorageFromRequest( + req); AppIdentifierWithStorage appIdentifierWithStorage = this.getAppIdentifierWithStorage(req); String email = InputParser.getQueryParamOrThrowError(req, "email", false); @@ -73,7 +76,11 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO JsonObject result = new JsonObject(); result.addProperty("status", "OK"); - JsonArray usersJson = new JsonParser().parse(new Gson().toJson(users)).getAsJsonArray(); + JsonArray usersJson = new JsonArray(); + for (UserInfo userInfo : users) { + usersJson.add(getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v4_0) ? userInfo.toJson() : + userInfo.toJsonWithoutAccountLinking()); + } if (getVersionFromRequest(req).lesserThan(SemVer.v3_0)) { for (JsonElement user : usersJson) { diff --git a/src/main/java/io/supertokens/webserver/api/thirdparty/SignInUpAPI.java b/src/main/java/io/supertokens/webserver/api/thirdparty/SignInUpAPI.java index 61e612278..4e1550029 100644 --- a/src/main/java/io/supertokens/webserver/api/thirdparty/SignInUpAPI.java +++ b/src/main/java/io/supertokens/webserver/api/thirdparty/SignInUpAPI.java @@ -16,9 +16,7 @@ package io.supertokens.webserver.api.thirdparty; -import com.google.gson.Gson; import com.google.gson.JsonObject; -import com.google.gson.JsonParser; import io.supertokens.ActiveUsers; import io.supertokens.Main; import io.supertokens.multitenancy.exception.BadPermissionException; @@ -83,7 +81,9 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I JsonObject result = new JsonObject(); result.addProperty("status", "OK"); result.addProperty("createdNewUser", response.createdNewUser); - JsonObject userJson = new JsonParser().parse(new Gson().toJson(response.user)).getAsJsonObject(); + JsonObject userJson = + getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v4_0) ? response.user.toJson() : + response.user.toJsonWithoutAccountLinking(); if (getVersionFromRequest(req).lesserThan(SemVer.v3_0)) { userJson.remove("tenantIds"); @@ -131,7 +131,9 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I JsonObject result = new JsonObject(); result.addProperty("status", "OK"); result.addProperty("createdNewUser", response.createdNewUser); - JsonObject userJson = new JsonParser().parse(new Gson().toJson(response.user)).getAsJsonObject(); + JsonObject userJson = + getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v4_0) ? response.user.toJson() : + response.user.toJsonWithoutAccountLinking(); if (getVersionFromRequest(req).lesserThan(SemVer.v3_0)) { userJson.remove("tenantIds"); diff --git a/src/main/java/io/supertokens/webserver/api/thirdparty/UserAPI.java b/src/main/java/io/supertokens/webserver/api/thirdparty/UserAPI.java index 9fa23bdfb..0543ce306 100644 --- a/src/main/java/io/supertokens/webserver/api/thirdparty/UserAPI.java +++ b/src/main/java/io/supertokens/webserver/api/thirdparty/UserAPI.java @@ -16,15 +16,13 @@ package io.supertokens.webserver.api.thirdparty; -import com.google.gson.Gson; import com.google.gson.JsonObject; -import com.google.gson.JsonParser; import io.supertokens.AppIdentifierWithStorageAndUserIdMapping; import io.supertokens.Main; -import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; -import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.RECIPE_ID; +import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.thirdparty.UserInfo; import io.supertokens.thirdparty.ThirdParty; import io.supertokens.useridmapping.UserIdMapping; @@ -83,7 +81,8 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO userId = appIdentifierWithStorageAndUserIdMapping.userIdMapping.superTokensUserId; } - user = ThirdParty.getUser(appIdentifierWithStorageAndUserIdMapping.appIdentifierWithStorage, userId); + user = ThirdParty.getUser(appIdentifierWithStorageAndUserIdMapping.appIdentifierWithStorage, + userId); if (user != null && appIdentifierWithStorageAndUserIdMapping.userIdMapping != null) { user.id = appIdentifierWithStorageAndUserIdMapping.userIdMapping.externalUserId; } @@ -110,7 +109,9 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO } else { JsonObject result = new JsonObject(); result.addProperty("status", "OK"); - JsonObject userJson = new JsonParser().parse(new Gson().toJson(user)).getAsJsonObject(); + JsonObject userJson = + getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v4_0) ? user.toJson() : + user.toJsonWithoutAccountLinking(); if (getVersionFromRequest(req).lesserThan(SemVer.v3_0)) { userJson.remove("tenantIds"); From c15c2993fc5f9f92eb5cbbb16c6e6ffc1026380c Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Mon, 10 Jul 2023 21:51:15 +0530 Subject: [PATCH 009/131] all test fixes --- .../java/io/supertokens/webserver/api/core/UsersAPI.java | 3 ++- .../supertokens/webserver/api/emailpassword/SignInAPI.java | 2 +- .../io/supertokens/webserver/api/emailpassword/UserAPI.java | 4 ++-- .../webserver/api/passwordless/ConsumeCodeAPI.java | 2 +- .../io/supertokens/webserver/api/passwordless/UserAPI.java | 6 +++--- .../webserver/api/thirdparty/GetUsersByEmailAPI.java | 2 +- .../supertokens/webserver/api/thirdparty/SignInUpAPI.java | 2 +- .../io/supertokens/webserver/api/thirdparty/UserAPI.java | 4 ++-- 8 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/main/java/io/supertokens/webserver/api/core/UsersAPI.java b/src/main/java/io/supertokens/webserver/api/core/UsersAPI.java index 34d5a4d26..f3c4ac63c 100644 --- a/src/main/java/io/supertokens/webserver/api/core/UsersAPI.java +++ b/src/main/java/io/supertokens/webserver/api/core/UsersAPI.java @@ -177,7 +177,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO for (int i = 0; i < users.users.length; i++) { String externalId = userIdMapping.get(userIds.get(i)); if (externalId != null) { - users.users[i].user.id = externalId; + users.users[i].user.setExternalUserId(externalId); } } } @@ -193,6 +193,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v4_0) ? user.user.toJson() : user.user.toJsonWithoutAccountLinking(); jsonObj.add("user", userJson); + usersJson.add(jsonObj); } if (getVersionFromRequest(req).lesserThan(SemVer.v3_0)) { diff --git a/src/main/java/io/supertokens/webserver/api/emailpassword/SignInAPI.java b/src/main/java/io/supertokens/webserver/api/emailpassword/SignInAPI.java index 80d399c18..b2c7f94ae 100644 --- a/src/main/java/io/supertokens/webserver/api/emailpassword/SignInAPI.java +++ b/src/main/java/io/supertokens/webserver/api/emailpassword/SignInAPI.java @@ -84,7 +84,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I tenantIdentifierWithStorage.toAppIdentifierWithStorage(), user.id, UserIdType.SUPERTOKENS); if (userIdMapping != null) { - user.id = userIdMapping.externalUserId; + user.setExternalUserId(userIdMapping.externalUserId); } JsonObject result = new JsonObject(); diff --git a/src/main/java/io/supertokens/webserver/api/emailpassword/UserAPI.java b/src/main/java/io/supertokens/webserver/api/emailpassword/UserAPI.java index b1d82e83f..38bc3f316 100644 --- a/src/main/java/io/supertokens/webserver/api/emailpassword/UserAPI.java +++ b/src/main/java/io/supertokens/webserver/api/emailpassword/UserAPI.java @@ -90,7 +90,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO // if the userIdMapping exists set the userId in the response to the externalUserId if (user != null && appIdentifierWithStorageAndUserIdMapping.userIdMapping != null) { - user.id = appIdentifierWithStorageAndUserIdMapping.userIdMapping.externalUserId; + user.setExternalUserId(appIdentifierWithStorageAndUserIdMapping.userIdMapping.externalUserId); } } else { @@ -108,7 +108,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO UserIdMapping.getUserIdMapping( getAppIdentifierWithStorage(req), user.id, UserIdType.SUPERTOKENS); if (userIdMapping != null) { - user.id = userIdMapping.externalUserId; + user.setExternalUserId(userIdMapping.externalUserId); } } } diff --git a/src/main/java/io/supertokens/webserver/api/passwordless/ConsumeCodeAPI.java b/src/main/java/io/supertokens/webserver/api/passwordless/ConsumeCodeAPI.java index 983b00d61..a44088aa3 100644 --- a/src/main/java/io/supertokens/webserver/api/passwordless/ConsumeCodeAPI.java +++ b/src/main/java/io/supertokens/webserver/api/passwordless/ConsumeCodeAPI.java @@ -91,7 +91,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I this.getAppIdentifierWithStorage(req), consumeCodeResponse.user.id, UserIdType.ANY); if (userIdMapping != null) { - consumeCodeResponse.user.id = userIdMapping.externalUserId; + consumeCodeResponse.user.setExternalUserId(userIdMapping.externalUserId); } JsonObject result = new JsonObject(); diff --git a/src/main/java/io/supertokens/webserver/api/passwordless/UserAPI.java b/src/main/java/io/supertokens/webserver/api/passwordless/UserAPI.java index 1ebf0b91f..fb488a8ec 100644 --- a/src/main/java/io/supertokens/webserver/api/passwordless/UserAPI.java +++ b/src/main/java/io/supertokens/webserver/api/passwordless/UserAPI.java @@ -84,7 +84,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO // if the userIdMapping exists set the userId in the response to the externalUserId if (user != null && appIdentifierWithStorageAndUserIdMapping.userIdMapping != null) { - user.id = appIdentifierWithStorageAndUserIdMapping.userIdMapping.externalUserId; + user.setExternalUserId(appIdentifierWithStorageAndUserIdMapping.userIdMapping.externalUserId); } } catch (UnknownUserIdException e) { user = null; @@ -97,7 +97,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO this.getAppIdentifierWithStorage(req), user.id, UserIdType.SUPERTOKENS); if (userIdMapping != null) { - user.id = userIdMapping.externalUserId; + user.setExternalUserId(userIdMapping.externalUserId); } } } else { @@ -108,7 +108,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO this.getAppIdentifierWithStorage(req), user.id, UserIdType.SUPERTOKENS); if (userIdMapping != null) { - user.id = userIdMapping.externalUserId; + user.setExternalUserId(userIdMapping.externalUserId); } } } diff --git a/src/main/java/io/supertokens/webserver/api/thirdparty/GetUsersByEmailAPI.java b/src/main/java/io/supertokens/webserver/api/thirdparty/GetUsersByEmailAPI.java index 9c7a570f6..ece2f2efe 100644 --- a/src/main/java/io/supertokens/webserver/api/thirdparty/GetUsersByEmailAPI.java +++ b/src/main/java/io/supertokens/webserver/api/thirdparty/GetUsersByEmailAPI.java @@ -70,7 +70,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO io.supertokens.pluginInterface.useridmapping.UserIdMapping userIdMapping = UserIdMapping .getUserIdMapping(appIdentifierWithStorage, users[i].id, UserIdType.SUPERTOKENS); if (userIdMapping != null) { - users[i].id = userIdMapping.externalUserId; + users[i].setExternalUserId(userIdMapping.externalUserId); } } diff --git a/src/main/java/io/supertokens/webserver/api/thirdparty/SignInUpAPI.java b/src/main/java/io/supertokens/webserver/api/thirdparty/SignInUpAPI.java index 4e1550029..4f8b34dd1 100644 --- a/src/main/java/io/supertokens/webserver/api/thirdparty/SignInUpAPI.java +++ b/src/main/java/io/supertokens/webserver/api/thirdparty/SignInUpAPI.java @@ -125,7 +125,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I .getUserIdMapping(this.getAppIdentifierWithStorage(req), response.user.id, UserIdType.SUPERTOKENS); if (userIdMapping != null) { - response.user.id = userIdMapping.externalUserId; + response.user.setExternalUserId(userIdMapping.externalUserId); } JsonObject result = new JsonObject(); diff --git a/src/main/java/io/supertokens/webserver/api/thirdparty/UserAPI.java b/src/main/java/io/supertokens/webserver/api/thirdparty/UserAPI.java index 0543ce306..cca5c2540 100644 --- a/src/main/java/io/supertokens/webserver/api/thirdparty/UserAPI.java +++ b/src/main/java/io/supertokens/webserver/api/thirdparty/UserAPI.java @@ -84,7 +84,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO user = ThirdParty.getUser(appIdentifierWithStorageAndUserIdMapping.appIdentifierWithStorage, userId); if (user != null && appIdentifierWithStorageAndUserIdMapping.userIdMapping != null) { - user.id = appIdentifierWithStorageAndUserIdMapping.userIdMapping.externalUserId; + user.setExternalUserId(appIdentifierWithStorageAndUserIdMapping.userIdMapping.externalUserId); } } catch (UnknownUserIdException e) { // let the user be null @@ -96,7 +96,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO io.supertokens.pluginInterface.useridmapping.UserIdMapping userIdMapping = UserIdMapping .getUserIdMapping(this.getAppIdentifierWithStorage(req), user.id, UserIdType.SUPERTOKENS); if (userIdMapping != null) { - user.id = userIdMapping.externalUserId; + user.setExternalUserId(userIdMapping.externalUserId); } } } From 878bc16ce33c7cd68e90da634541edd259f4109d Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Tue, 11 Jul 2023 13:30:54 +0530 Subject: [PATCH 010/131] removes a few unnecessary functions --- .../java/io/supertokens/inmemorydb/Start.java | 38 +++++++++++++++++-- .../queries/EmailPasswordQueries.java | 26 ------------- .../inmemorydb/queries/GeneralQueries.java | 19 ++++++++-- .../queries/PasswordlessQueries.java | 27 ------------- .../inmemorydb/queries/ThirdPartyQueries.java | 27 ------------- 5 files changed, 49 insertions(+), 88 deletions(-) diff --git a/src/main/java/io/supertokens/inmemorydb/Start.java b/src/main/java/io/supertokens/inmemorydb/Start.java index 29c748299..dd1a63128 100644 --- a/src/main/java/io/supertokens/inmemorydb/Start.java +++ b/src/main/java/io/supertokens/inmemorydb/Start.java @@ -772,7 +772,16 @@ public void deleteEmailPasswordUser(AppIdentifier appIdentifier, String userId) @Override public UserInfo getUserInfoUsingId(AppIdentifier appIdentifier, String id) throws StorageQueryException { try { - return EmailPasswordQueries.getUserInfoUsingId(this, appIdentifier, id); + AuthRecipeUserInfo result = GeneralQueries.getPrimaryUserInfoForUserId(this, appIdentifier, id); + if (result == null) { + return null; + } + for (LoginMethod lM : result.loginMethods) { + if (lM.recipeUserId.equals(id)) { + return new UserInfo(lM.recipeUserId, result.isPrimaryUser, lM); + } + } + return null; } catch (SQLException e) { throw new StorageQueryException(e); } @@ -1185,7 +1194,17 @@ public io.supertokens.pluginInterface.thirdparty.UserInfo getThirdPartyUserInfoU String id) throws StorageQueryException { try { - return ThirdPartyQueries.getThirdPartyUserInfoUsingId(this, appIdentifier, id); + AuthRecipeUserInfo result = GeneralQueries.getPrimaryUserInfoForUserId(this, appIdentifier, id); + if (result == null) { + return null; + } + for (LoginMethod lM : result.loginMethods) { + if (lM.recipeUserId.equals(id)) { + return new io.supertokens.pluginInterface.thirdparty.UserInfo(lM.recipeUserId, result.isPrimaryUser, + lM); + } + } + return null; } catch (SQLException e) { throw new StorageQueryException(e); } @@ -1652,7 +1671,7 @@ public io.supertokens.pluginInterface.passwordless.UserInfo createUser(TenantIde String id, @javax.annotation.Nullable String email, @javax.annotation.Nullable - String phoneNumber, long timeJoined) + String phoneNumber, long timeJoined) throws StorageQueryException, DuplicateEmailException, DuplicatePhoneNumberException, DuplicateUserIdException, TenantOrAppNotFoundException { @@ -1790,7 +1809,18 @@ public PasswordlessCode getCodeByLinkCodeHash(TenantIdentifier tenantIdentifier, public io.supertokens.pluginInterface.passwordless.UserInfo getUserById(AppIdentifier appIdentifier, String userId) throws StorageQueryException { try { - return PasswordlessQueries.getUserById(this, appIdentifier, userId); + AuthRecipeUserInfo result = GeneralQueries.getPrimaryUserInfoForUserId(this, appIdentifier, userId); + if (result == null) { + return null; + } + for (LoginMethod lM : result.loginMethods) { + if (lM.recipeUserId.equals(userId)) { + return new io.supertokens.pluginInterface.passwordless.UserInfo(lM.recipeUserId, + result.isPrimaryUser, + lM); + } + } + return null; } catch (SQLException e) { throw new StorageQueryException(e); } diff --git a/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java index 51f4778b7..433fa9de0 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java @@ -348,32 +348,6 @@ public static void deleteUser(Start start, AppIdentifier appIdentifier, String u }); } - public static UserInfo getUserInfoUsingId(Start start, AppIdentifier appIdentifier, String id) - throws SQLException, StorageQueryException { - String QUERY = "SELECT user_id, email, password_hash, time_joined FROM " - + getConfig(start).getEmailPasswordUsersTable() + " WHERE app_id = ? AND user_id = ?"; - - try (Connection con = ConnectionPool.getConnection(start)) { - UserInfoPartial userInfo = execute(con, QUERY.toString(), pst -> { - pst.setString(1, appIdentifier.getAppId()); - pst.setString(2, id); - }, result -> { - if (result.next()) { - return UserInfoRowMapper.getInstance().mapOrThrow(result); - } - return null; - }); - if (userInfo == null) { - return null; - } - fillUserInfoWithTenantIds_transaction(start, con, appIdentifier, userInfo); - fillUserInfoWithVerified_transaction(start, con, appIdentifier, userInfo); - fillUserInfoWithIsPrimaryUserBoolean_transaction(start, con, appIdentifier, - userInfo); - return new UserInfo(userInfo.id, userInfo.isPrimary, userInfo.toLoginMethod()); - } - } - public static UserInfoPartial getUserInfoUsingId(Start start, Connection sqlCon, AppIdentifier appIdentifier, String id) throws SQLException, StorageQueryException { // we don't need a LOCK here because this is already part of a transaction, and locked on app_id_to_user_id diff --git a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java index f5a1997c3..612b897f1 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java @@ -833,7 +833,7 @@ public static AuthRecipeUserInfo[] getUsers(Start start, TenantIdentifier tenant // we give the userId[] for each recipe to fetch all those user's details for (RECIPE_ID recipeId : recipeIdToUserIdListMap.keySet()) { - List users = getUserInfoForRecipeIdFromUserIds(start, + List users = getPrimaryUserInfoForUserIds(start, tenantIdentifier.toAppIdentifier(), recipeIdToUserIdListMap.get(recipeId)); @@ -853,9 +853,20 @@ public static AuthRecipeUserInfo[] getUsers(Start start, TenantIdentifier tenant return finalResult; } - private static List getUserInfoForRecipeIdFromUserIds(Start start, - AppIdentifier appIdentifier, - List userIds) + public static AuthRecipeUserInfo getPrimaryUserInfoForUserId(Start start, AppIdentifier appIdentifier, String id) + throws SQLException, StorageQueryException { + List ids = new ArrayList<>(); + ids.add(id); + List result = getPrimaryUserInfoForUserIds(start, appIdentifier, ids); + if (result.isEmpty()) { + return null; + } + return result.get(0); + } + + private static List getPrimaryUserInfoForUserIds(Start start, + AppIdentifier appIdentifier, + List userIds) throws StorageQueryException, SQLException { if (userIds.size() == 0) { return new ArrayList<>(); diff --git a/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java index a7622728e..a3239413e 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java @@ -711,33 +711,6 @@ public static List getUsersInfoUsingIdList(Start start, Set return Collections.emptyList(); } - public static UserInfo getUserById(Start start, AppIdentifier appIdentifier, String userId) - throws StorageQueryException, SQLException { - String QUERY = "SELECT user_id, email, phone_number, time_joined FROM " - + getConfig(start).getPasswordlessUsersTable() + " WHERE app_id = ? AND user_id = ?"; - - try (Connection con = ConnectionPool.getConnection(start)) { - UserInfoPartial userInfo = execute(con, QUERY, pst -> { - pst.setString(1, appIdentifier.getAppId()); - pst.setString(2, userId); - }, result -> { - if (result.next()) { - return UserInfoRowMapper.getInstance().mapOrThrow(result); - } - return null; - }); - if (userInfo == null) { - return null; - } - fillUserInfoWithTenantIds_transaction(start, con, appIdentifier, userInfo); - fillUserInfoWithVerified_transaction(start, con, appIdentifier, userInfo); - fillUserInfoWithIsPrimaryUserBoolean_transaction(start, con, appIdentifier, - userInfo); - return new UserInfo(userInfo.id, userInfo.isPrimary, - userInfo.toLoginMethod()); - } - } - public static UserInfoPartial getUserById(Start start, Connection sqlCon, AppIdentifier appIdentifier, String userId) throws StorageQueryException, SQLException { // we don't need a LOCK here because this is already part of a transaction, and locked on app_id_to_user_id diff --git a/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java index 63a990c4f..cfce9da88 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java @@ -179,33 +179,6 @@ public static void deleteUser(Start start, AppIdentifier appIdentifier, String u }); } - public static UserInfo getThirdPartyUserInfoUsingId(Start start, AppIdentifier appIdentifier, String userId) - throws SQLException, StorageQueryException { - // TODO: this should go away.. - String QUERY = "SELECT user_id, third_party_id, third_party_user_id, email, time_joined FROM " - + getConfig(start).getThirdPartyUsersTable() + " WHERE app_id = ? AND user_id = ?"; - - try (Connection con = ConnectionPool.getConnection(start)) { - UserInfoPartial userInfo = execute(con, QUERY.toString(), pst -> { - pst.setString(1, appIdentifier.getAppId()); - pst.setString(2, userId); - }, result -> { - if (result.next()) { - return UserInfoRowMapper.getInstance().mapOrThrow(result); - } - return null; - }); - if (userInfo == null) { - return null; - } - fillUserInfoWithTenantIds_transaction(start, con, appIdentifier, userInfo); - fillUserInfoWithVerified_transaction(start, con, appIdentifier, userInfo); - fillUserInfoWithIsPrimaryUserBoolean_transaction(start, con, appIdentifier, - userInfo); - return new UserInfo(userInfo.id, userInfo.isPrimary, userInfo.toLoginMethod()); - } - } - public static List getUsersInfoUsingIdList(Start start, Set ids, AppIdentifier appIdentifier) throws SQLException, StorageQueryException { if (ids.size() > 0) { From aab92ff27a6f24d44de062e90d0d365846d3969a Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Tue, 11 Jul 2023 15:25:30 +0530 Subject: [PATCH 011/131] uses new interface to fetch user based on id --- .../emailpassword/EmailPassword.java | 44 +++++++++--- .../java/io/supertokens/inmemorydb/Start.java | 70 +++---------------- .../passwordless/Passwordless.java | 56 ++++++++++----- .../io/supertokens/thirdparty/ThirdParty.java | 15 +++- .../emailpassword/api/SignUpAPITest2_7.java | 10 +-- .../PasswordlessConsumeCodeTest.java | 38 +++++----- .../passwordless/PasswordlessStorageTest.java | 45 ++++++------ .../PasswordlessUpdateUserTest.java | 20 +++--- 8 files changed, 159 insertions(+), 139 deletions(-) diff --git a/src/main/java/io/supertokens/emailpassword/EmailPassword.java b/src/main/java/io/supertokens/emailpassword/EmailPassword.java index 362772188..ec9b71605 100644 --- a/src/main/java/io/supertokens/emailpassword/EmailPassword.java +++ b/src/main/java/io/supertokens/emailpassword/EmailPassword.java @@ -25,6 +25,8 @@ import io.supertokens.multitenancy.Multitenancy; import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.pluginInterface.Storage; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; +import io.supertokens.pluginInterface.authRecipe.LoginMethod; import io.supertokens.pluginInterface.emailpassword.PasswordResetTokenInfo; import io.supertokens.pluginInterface.emailpassword.UserInfo; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; @@ -34,7 +36,10 @@ import io.supertokens.pluginInterface.emailpassword.sqlStorage.EmailPasswordSQLStorage; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; -import io.supertokens.pluginInterface.multitenancy.*; +import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.TenantConfig; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.utils.Utils; @@ -106,7 +111,8 @@ public static UserInfo signUp(TenantIdentifierWithStorage tenantIdentifierWithSt long timeJoined = System.currentTimeMillis(); try { - return tenantIdentifierWithStorage.getEmailPasswordStorage().signUp(tenantIdentifierWithStorage, userId, email, hashedPassword, timeJoined); + return tenantIdentifierWithStorage.getEmailPasswordStorage() + .signUp(tenantIdentifierWithStorage, userId, email, hashedPassword, timeJoined); } catch (DuplicateUserIdException ignored) { // we retry with a new userId (while loop) @@ -117,7 +123,7 @@ public static UserInfo signUp(TenantIdentifierWithStorage tenantIdentifierWithSt @TestOnly public static ImportUserResponse importUserWithPasswordHash(Main main, @Nonnull String email, @Nonnull String passwordHash, @Nullable - CoreConfig.PASSWORD_HASHING_ALG hashingAlgorithm) + CoreConfig.PASSWORD_HASHING_ALG hashingAlgorithm) throws StorageQueryException, StorageTransactionLogicException, UnsupportedPasswordHashingFormatException { try { Storage storage = StorageLayer.getStorage(main); @@ -133,7 +139,7 @@ public static ImportUserResponse importUserWithPasswordHash(Main main, @Nonnull public static ImportUserResponse importUserWithPasswordHash(TenantIdentifierWithStorage tenantIdentifierWithStorage, Main main, @Nonnull String email, @Nonnull String passwordHash, @Nullable - CoreConfig.PASSWORD_HASHING_ALG hashingAlgorithm) + CoreConfig.PASSWORD_HASHING_ALG hashingAlgorithm) throws StorageQueryException, StorageTransactionLogicException, UnsupportedPasswordHashingFormatException, TenantOrAppNotFoundException, BadPermissionException { @@ -145,7 +151,8 @@ public static ImportUserResponse importUserWithPasswordHash(TenantIdentifierWith throw new BadPermissionException("Email password login not enabled for tenant"); } - PasswordHashingUtils.assertSuperTokensSupportInputPasswordHashFormat(tenantIdentifierWithStorage.toAppIdentifier(), main, + PasswordHashingUtils.assertSuperTokensSupportInputPasswordHashFormat( + tenantIdentifierWithStorage.toAppIdentifier(), main, passwordHash, hashingAlgorithm); while (true) { @@ -202,7 +209,8 @@ public static UserInfo signIn(Main main, @Nonnull String email, } } - public static UserInfo signIn(TenantIdentifierWithStorage tenantIdentifierWithStorage, Main main, @Nonnull String email, + public static UserInfo signIn(TenantIdentifierWithStorage tenantIdentifierWithStorage, Main main, + @Nonnull String email, @Nonnull String password) throws StorageQueryException, WrongCredentialsException, TenantOrAppNotFoundException, BadPermissionException { @@ -224,7 +232,8 @@ public static UserInfo signIn(TenantIdentifierWithStorage tenantIdentifierWithSt try { if (!PasswordHashing.getInstance(main) - .verifyPasswordWithHash(tenantIdentifierWithStorage.toAppIdentifier(), password, user.passwordHash)) { + .verifyPasswordWithHash(tenantIdentifierWithStorage.toAppIdentifier(), password, + user.passwordHash)) { throw new WrongCredentialsException(); } } catch (WrongCredentialsException e) { @@ -351,7 +360,8 @@ public static String resetPassword(TenantIdentifierWithStorage tenantIdentifierW throw new StorageTransactionLogicException(new ResetPasswordInvalidTokenException()); } - storage.deleteAllPasswordResetTokensForUser_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), con, + storage.deleteAllPasswordResetTokensForUser_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), + con, userId); if (matchedToken.tokenExpiry < System.currentTimeMillis()) { @@ -397,7 +407,8 @@ public static void updateUsersEmailOrPassword(AppIdentifierWithStorage appIdenti try { storage.startTransaction(transaction -> { try { - UserInfo userInfo = storage.getUserInfoUsingId_Transaction(appIdentifierWithStorage, transaction, userId); + UserInfo userInfo = storage.getUserInfoUsingId_Transaction(appIdentifierWithStorage, transaction, + userId); if (userInfo == null) { throw new StorageTransactionLogicException(new UnknownUserIdException()); @@ -415,7 +426,8 @@ public static void updateUsersEmailOrPassword(AppIdentifierWithStorage appIdenti if (password != null) { String hashedPassword = PasswordHashing.getInstance(main) .createHashWithSalt(appIdentifierWithStorage, password); - storage.updateUsersPassword_Transaction(appIdentifierWithStorage, transaction, userId, hashedPassword); + storage.updateUsersPassword_Transaction(appIdentifierWithStorage, transaction, userId, + hashedPassword); } storage.commitTransaction(transaction); @@ -449,7 +461,17 @@ public static UserInfo getUserUsingId(Main main, String userId) public static UserInfo getUserUsingId(AppIdentifierWithStorage appIdentifierWithStorage, String userId) throws StorageQueryException, TenantOrAppNotFoundException { - return appIdentifierWithStorage.getEmailPasswordStorage().getUserInfoUsingId(appIdentifierWithStorage, userId); + AuthRecipeUserInfo result = appIdentifierWithStorage.getAuthRecipeStorage() + .getPrimaryUserById(appIdentifierWithStorage, userId); + if (result == null) { + return null; + } + for (LoginMethod lM : result.loginMethods) { + if (lM.recipeUserId.equals(userId)) { + return new UserInfo(lM.recipeUserId, result.isPrimaryUser, lM); + } + } + return null; } public static UserInfo getUserUsingEmail(TenantIdentifierWithStorage tenantIdentifierWithStorage, String email) diff --git a/src/main/java/io/supertokens/inmemorydb/Start.java b/src/main/java/io/supertokens/inmemorydb/Start.java index dd1a63128..f8eafed12 100644 --- a/src/main/java/io/supertokens/inmemorydb/Start.java +++ b/src/main/java/io/supertokens/inmemorydb/Start.java @@ -769,24 +769,6 @@ public void deleteEmailPasswordUser(AppIdentifier appIdentifier, String userId) } } - @Override - public UserInfo getUserInfoUsingId(AppIdentifier appIdentifier, String id) throws StorageQueryException { - try { - AuthRecipeUserInfo result = GeneralQueries.getPrimaryUserInfoForUserId(this, appIdentifier, id); - if (result == null) { - return null; - } - for (LoginMethod lM : result.loginMethods) { - if (lM.recipeUserId.equals(id)) { - return new UserInfo(lM.recipeUserId, result.isPrimaryUser, lM); - } - } - return null; - } catch (SQLException e) { - throw new StorageQueryException(e); - } - } - @Override public UserInfo getUserInfoUsingEmail(TenantIdentifier tenantIdentifier, String email) throws StorageQueryException { @@ -1189,27 +1171,6 @@ public io.supertokens.pluginInterface.thirdparty.UserInfo getThirdPartyUserInfoU } } - @Override - public io.supertokens.pluginInterface.thirdparty.UserInfo getThirdPartyUserInfoUsingId(AppIdentifier appIdentifier, - String id) - throws StorageQueryException { - try { - AuthRecipeUserInfo result = GeneralQueries.getPrimaryUserInfoForUserId(this, appIdentifier, id); - if (result == null) { - return null; - } - for (LoginMethod lM : result.loginMethods) { - if (lM.recipeUserId.equals(id)) { - return new io.supertokens.pluginInterface.thirdparty.UserInfo(lM.recipeUserId, result.isPrimaryUser, - lM); - } - } - return null; - } catch (SQLException e) { - throw new StorageQueryException(e); - } - } - @Override public io.supertokens.pluginInterface.thirdparty.UserInfo[] getThirdPartyUsersByEmail( TenantIdentifier tenantIdentifier, @NotNull String email) @@ -1320,6 +1281,16 @@ public boolean doesUserIdExist(TenantIdentifier tenantIdentifier, String userId) } } + @Override + public AuthRecipeUserInfo getPrimaryUserById(AppIdentifier appIdentifier, String userId) + throws StorageQueryException { + try { + return GeneralQueries.getPrimaryUserInfoForUserId(this, appIdentifier, userId); + } catch (SQLException e) { + throw new StorageQueryException(e); + } + } + @Override public List getJWTSigningKeys_Transaction(AppIdentifier appIdentifier, TransactionConnection con) @@ -1805,27 +1776,6 @@ public PasswordlessCode getCodeByLinkCodeHash(TenantIdentifier tenantIdentifier, } } - @Override - public io.supertokens.pluginInterface.passwordless.UserInfo getUserById(AppIdentifier appIdentifier, String userId) - throws StorageQueryException { - try { - AuthRecipeUserInfo result = GeneralQueries.getPrimaryUserInfoForUserId(this, appIdentifier, userId); - if (result == null) { - return null; - } - for (LoginMethod lM : result.loginMethods) { - if (lM.recipeUserId.equals(userId)) { - return new io.supertokens.pluginInterface.passwordless.UserInfo(lM.recipeUserId, - result.isPrimaryUser, - lM); - } - } - return null; - } catch (SQLException e) { - throw new StorageQueryException(e); - } - } - @Override public io.supertokens.pluginInterface.passwordless.UserInfo getUserByEmail(TenantIdentifier tenantIdentifier, String email) diff --git a/src/main/java/io/supertokens/passwordless/Passwordless.java b/src/main/java/io/supertokens/passwordless/Passwordless.java index 29a38cd02..583dd2ee7 100644 --- a/src/main/java/io/supertokens/passwordless/Passwordless.java +++ b/src/main/java/io/supertokens/passwordless/Passwordless.java @@ -22,6 +22,8 @@ import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.passwordless.exceptions.*; import io.supertokens.pluginInterface.Storage; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; +import io.supertokens.pluginInterface.authRecipe.LoginMethod; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateUserIdException; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; @@ -72,7 +74,8 @@ public static CreateCodeResponse createCode(Main main, String email, String phon } } - public static CreateCodeResponse createCode(TenantIdentifierWithStorage tenantIdentifierWithStorage, Main main, String email, + public static CreateCodeResponse createCode(TenantIdentifierWithStorage tenantIdentifierWithStorage, Main main, + String email, String phoneNumber, @Nullable String deviceId, @Nullable String userInputCode) throws RestartFlowException, DuplicateLinkCodeHashException, @@ -190,7 +193,8 @@ public static List getDevicesWithCodesByEmail( PasswordlessDevice[] devices = passwordlessStorage.getDevicesByEmail(tenantIdentifierWithStorage, email); ArrayList result = new ArrayList(); for (PasswordlessDevice device : devices) { - PasswordlessCode[] codes = passwordlessStorage.getCodesOfDevice(tenantIdentifierWithStorage, device.deviceIdHash); + PasswordlessCode[] codes = passwordlessStorage.getCodesOfDevice(tenantIdentifierWithStorage, + device.deviceIdHash); result.add(new DeviceWithCodes(device, codes)); } @@ -210,10 +214,12 @@ public static List getDevicesWithCodesByPhoneNumber( throws StorageQueryException { PasswordlessSQLStorage passwordlessStorage = tenantIdentifierWithStorage.getPasswordlessStorage(); - PasswordlessDevice[] devices = passwordlessStorage.getDevicesByPhoneNumber(tenantIdentifierWithStorage, phoneNumber); + PasswordlessDevice[] devices = passwordlessStorage.getDevicesByPhoneNumber(tenantIdentifierWithStorage, + phoneNumber); ArrayList result = new ArrayList(); for (PasswordlessDevice device : devices) { - PasswordlessCode[] codes = passwordlessStorage.getCodesOfDevice(tenantIdentifierWithStorage, device.deviceIdHash); + PasswordlessCode[] codes = passwordlessStorage.getCodesOfDevice(tenantIdentifierWithStorage, + device.deviceIdHash); result.add(new DeviceWithCodes(device, codes)); } @@ -274,7 +280,8 @@ public static ConsumeCodeResponse consumeCode(TenantIdentifierWithStorage tenant PasswordlessLinkCode parsedCode = PasswordlessLinkCode.decodeString(linkCode); linkCodeHash = parsedCode.getHash(); - PasswordlessCode code = passwordlessStorage.getCodeByLinkCodeHash(tenantIdentifierWithStorage, linkCodeHash.encode()); + PasswordlessCode code = passwordlessStorage.getCodeByLinkCodeHash(tenantIdentifierWithStorage, + linkCodeHash.encode()); if (code == null || code.createdAt < (System.currentTimeMillis() - passwordlessCodeLifetime)) { throw new RestartFlowException(); } @@ -284,7 +291,8 @@ public static ConsumeCodeResponse consumeCode(TenantIdentifierWithStorage tenant PasswordlessDeviceId parsedDeviceId = PasswordlessDeviceId.decodeString(deviceId); deviceIdHash = parsedDeviceId.getHash(); - PasswordlessDevice device = passwordlessStorage.getDevice(tenantIdentifierWithStorage, deviceIdHash.encode()); + PasswordlessDevice device = passwordlessStorage.getDevice(tenantIdentifierWithStorage, + deviceIdHash.encode()); if (device == null) { throw new RestartFlowException(); } @@ -306,12 +314,14 @@ public static ConsumeCodeResponse consumeCode(TenantIdentifierWithStorage tenant } if (device.failedAttempts >= maxCodeInputAttempts) { // This can happen if the configured maxCodeInputAttempts changes - passwordlessStorage.deleteDevice_Transaction(tenantIdentifierWithStorage, con, deviceIdHash.encode()); + passwordlessStorage.deleteDevice_Transaction(tenantIdentifierWithStorage, con, + deviceIdHash.encode()); passwordlessStorage.commitTransaction(con); throw new StorageTransactionLogicException(new RestartFlowException()); } - PasswordlessCode code = passwordlessStorage.getCodeByLinkCodeHash_Transaction(tenantIdentifierWithStorage, con, + PasswordlessCode code = passwordlessStorage.getCodeByLinkCodeHash_Transaction( + tenantIdentifierWithStorage, con, linkCodeHash.encode()); if (code == null || code.createdAt < System.currentTimeMillis() - passwordlessCodeLifetime) { if (deviceId != null) { @@ -319,11 +329,13 @@ public static ConsumeCodeResponse consumeCode(TenantIdentifierWithStorage tenant // the code expired. This means that we need to increment failedAttempts or clean up the device // if it would exceed the configured max. if (device.failedAttempts + 1 >= maxCodeInputAttempts) { - passwordlessStorage.deleteDevice_Transaction(tenantIdentifierWithStorage, con, deviceIdHash.encode()); + passwordlessStorage.deleteDevice_Transaction(tenantIdentifierWithStorage, con, + deviceIdHash.encode()); passwordlessStorage.commitTransaction(con); throw new StorageTransactionLogicException(new RestartFlowException()); } else { - passwordlessStorage.incrementDeviceFailedAttemptCount_Transaction(tenantIdentifierWithStorage, con, + passwordlessStorage.incrementDeviceFailedAttemptCount_Transaction( + tenantIdentifierWithStorage, con, deviceIdHash.encode()); passwordlessStorage.commitTransaction(con); @@ -340,7 +352,8 @@ public static ConsumeCodeResponse consumeCode(TenantIdentifierWithStorage tenant } if (device.email != null) { - passwordlessStorage.deleteDevicesByEmail_Transaction(tenantIdentifierWithStorage, con, device.email); + passwordlessStorage.deleteDevicesByEmail_Transaction(tenantIdentifierWithStorage, con, + device.email); } else if (device.phoneNumber != null) { passwordlessStorage.deleteDevicesByPhoneNumber_Transaction(tenantIdentifierWithStorage, con, device.phoneNumber); @@ -421,7 +434,8 @@ public static void removeCode(TenantIdentifierWithStorage tenantIdentifierWithSt // Locking the device passwordlessStorage.getDevice_Transaction(tenantIdentifierWithStorage, con, code.deviceIdHash); - PasswordlessCode[] allCodes = passwordlessStorage.getCodesOfDevice_Transaction(tenantIdentifierWithStorage, con, + PasswordlessCode[] allCodes = passwordlessStorage.getCodesOfDevice_Transaction(tenantIdentifierWithStorage, + con, code.deviceIdHash); if (!Stream.of(allCodes).anyMatch(code::equals)) { // Already deleted @@ -491,8 +505,18 @@ public static UserInfo getUserById(Main main, String userId) public static UserInfo getUserById(AppIdentifierWithStorage appIdentifierWithStorage, String userId) throws StorageQueryException { - return appIdentifierWithStorage.getPasswordlessStorage() - .getUserById(appIdentifierWithStorage, userId); + AuthRecipeUserInfo result = appIdentifierWithStorage.getAuthRecipeStorage() + .getPrimaryUserById(appIdentifierWithStorage, userId); + if (result == null) { + return null; + } + for (LoginMethod lM : result.loginMethods) { + if (lM.recipeUserId.equals(userId)) { + return new io.supertokens.pluginInterface.passwordless.UserInfo(lM.recipeUserId, result.isPrimaryUser, + lM); + } + } + return null; } @TestOnly @@ -515,7 +539,7 @@ public static UserInfo getUserByEmail(Main main, String email) throws StorageQueryException { Storage storage = StorageLayer.getStorage(main); return getUserByEmail( - new TenantIdentifierWithStorage(null, null, null,storage), email); + new TenantIdentifierWithStorage(null, null, null, storage), email); } public static UserInfo getUserByEmail(TenantIdentifierWithStorage tenantIdentifierWithStorage, String email) @@ -541,7 +565,7 @@ public static void updateUser(AppIdentifierWithStorage appIdentifierWithStorage, // We do not lock the user here, because we decided that even if the device cleanup used outdated information // it wouldn't leave the system in an incosistent state/cause problems. - UserInfo user = storage.getUserById(appIdentifierWithStorage, userId); + UserInfo user = Passwordless.getUserById(appIdentifierWithStorage, userId); if (user == null) { throw new UnknownUserIdException(); } diff --git a/src/main/java/io/supertokens/thirdparty/ThirdParty.java b/src/main/java/io/supertokens/thirdparty/ThirdParty.java index a11c26ef4..2d473689b 100644 --- a/src/main/java/io/supertokens/thirdparty/ThirdParty.java +++ b/src/main/java/io/supertokens/thirdparty/ThirdParty.java @@ -20,6 +20,7 @@ import io.supertokens.multitenancy.Multitenancy; import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.pluginInterface.Storage; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.authRecipe.LoginMethod; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; @@ -199,8 +200,18 @@ private static SignInUpResponse signInUpHelper(TenantIdentifierWithStorage tenan public static UserInfo getUser(AppIdentifierWithStorage appIdentifierWithStorage, String userId) throws StorageQueryException { - return appIdentifierWithStorage.getThirdPartyStorage() - .getThirdPartyUserInfoUsingId(appIdentifierWithStorage, userId); + AuthRecipeUserInfo result = appIdentifierWithStorage.getAuthRecipeStorage() + .getPrimaryUserById(appIdentifierWithStorage, userId); + if (result == null) { + return null; + } + for (LoginMethod lM : result.loginMethods) { + if (lM.recipeUserId.equals(userId)) { + return new io.supertokens.pluginInterface.thirdparty.UserInfo(lM.recipeUserId, result.isPrimaryUser, + lM); + } + } + return null; } @TestOnly diff --git a/src/test/java/io/supertokens/test/emailpassword/api/SignUpAPITest2_7.java b/src/test/java/io/supertokens/test/emailpassword/api/SignUpAPITest2_7.java index b552b5e5b..666f7cb03 100644 --- a/src/test/java/io/supertokens/test/emailpassword/api/SignUpAPITest2_7.java +++ b/src/test/java/io/supertokens/test/emailpassword/api/SignUpAPITest2_7.java @@ -17,11 +17,11 @@ package io.supertokens.test.emailpassword.api; import com.google.gson.JsonObject; -import io.supertokens.ActiveUsers; - import io.supertokens.ActiveUsers; import io.supertokens.ProcessState; import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeStorage; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.emailpassword.UserInfo; import io.supertokens.pluginInterface.emailpassword.sqlStorage.EmailPasswordSQLStorage; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; @@ -197,10 +197,10 @@ public void testTheNormaliseEmailFunction() throws Exception { assertEquals(signUpUser.get("email").getAsString(), "random@gmail.com"); assertNotNull(signUpUser.get("id")); - UserInfo userInfo = ((EmailPasswordSQLStorage) StorageLayer.getStorage(process.getProcess())) - .getUserInfoUsingId(new AppIdentifier(null, null), signUpUser.get("id").getAsString()); + AuthRecipeUserInfo userInfo = ((AuthRecipeStorage) StorageLayer.getStorage(process.getProcess())) + .getPrimaryUserById(new AppIdentifier(null, null), signUpUser.get("id").getAsString()); - assertEquals(userInfo.email, "random@gmail.com"); + assertEquals(userInfo.loginMethods[0].email, "random@gmail.com"); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); diff --git a/src/test/java/io/supertokens/test/passwordless/PasswordlessConsumeCodeTest.java b/src/test/java/io/supertokens/test/passwordless/PasswordlessConsumeCodeTest.java index b9de4ef09..a1390d96f 100644 --- a/src/test/java/io/supertokens/test/passwordless/PasswordlessConsumeCodeTest.java +++ b/src/test/java/io/supertokens/test/passwordless/PasswordlessConsumeCodeTest.java @@ -24,6 +24,7 @@ import io.supertokens.passwordless.exceptions.IncorrectUserInputCodeException; import io.supertokens.passwordless.exceptions.RestartFlowException; import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; @@ -150,7 +151,7 @@ public void testConsumeUserInputCodeWithExistingUser() throws Exception { PasswordlessStorage storage = (PasswordlessStorage) StorageLayer.getStorage(process.getProcess()); - UserInfo user; + AuthRecipeUserInfo user; { Passwordless.CreateCodeResponse createCodeResponse = Passwordless.createCode(process.getProcess(), EMAIL, null, null, null); @@ -173,7 +174,7 @@ public void testConsumeUserInputCodeWithExistingUser() throws Exception { null); assertNotNull(consumeCodeResponse); assert (!consumeCodeResponse.createdNewUser); - UserInfo user2 = checkUserWithConsumeResponse(storage, consumeCodeResponse, EMAIL, null, 0); + AuthRecipeUserInfo user2 = checkUserWithConsumeResponse(storage, consumeCodeResponse, EMAIL, null, 0); assert (user.equals(user2)); } @@ -203,7 +204,7 @@ public void testConsumeLinkCodeWithExistingUser() throws Exception { PasswordlessStorage storage = (PasswordlessStorage) StorageLayer.getStorage(process.getProcess()); - UserInfo user; + AuthRecipeUserInfo user; { Passwordless.CreateCodeResponse createCodeResponse = Passwordless.createCode(process.getProcess(), EMAIL, null, null, null); @@ -224,7 +225,7 @@ public void testConsumeLinkCodeWithExistingUser() throws Exception { createCodeResponse.deviceId, createCodeResponse.deviceIdHash, null, createCodeResponse.linkCode); assertNotNull(consumeCodeResponse); assert (!consumeCodeResponse.createdNewUser); - UserInfo user2 = checkUserWithConsumeResponse(storage, consumeCodeResponse, EMAIL, null, 0); + AuthRecipeUserInfo user2 = checkUserWithConsumeResponse(storage, consumeCodeResponse, EMAIL, null, 0); assert (user.equals(user2)); } @@ -265,10 +266,11 @@ public void testConsumeCodeCleanupUserInputCodeWithEmailAndPhoneNumber() throws createCodeResponse.deviceId, createCodeResponse.deviceIdHash, createCodeResponse.userInputCode, null); assertNotNull(consumeCodeResponse); - user = storage.getUserById(new AppIdentifier(null, null), consumeCodeResponse.user.id); - Passwordless.updateUser(process.getProcess(), user.id, null, new Passwordless.FieldUpdate(PHONE_NUMBER)); - user = storage.getUserById(new AppIdentifier(null, null), consumeCodeResponse.user.id); - assertEquals(user.phoneNumber, PHONE_NUMBER); + AuthRecipeUserInfo authUser = storage.getPrimaryUserById(new AppIdentifier(null, null), + consumeCodeResponse.user.id); + Passwordless.updateUser(process.getProcess(), authUser.id, null, new Passwordless.FieldUpdate(PHONE_NUMBER)); + authUser = storage.getPrimaryUserById(new AppIdentifier(null, null), consumeCodeResponse.user.id); + assertEquals(authUser.loginMethods[0].phoneNumber, PHONE_NUMBER); // create code with email twice { @@ -339,10 +341,11 @@ public void testConsumeCodeCleanupLinkCodeWithEmailAndPhoneNumber() throws Excep createCodeResponse.deviceId, createCodeResponse.deviceIdHash, createCodeResponse.userInputCode, null); assertNotNull(consumeCodeResponse); - user = storage.getUserById(new AppIdentifier(null, null), consumeCodeResponse.user.id); - Passwordless.updateUser(process.getProcess(), user.id, null, new Passwordless.FieldUpdate(PHONE_NUMBER)); - user = storage.getUserById(new AppIdentifier(null, null), consumeCodeResponse.user.id); - assertEquals(user.phoneNumber, PHONE_NUMBER); + AuthRecipeUserInfo authUser = storage.getPrimaryUserById(new AppIdentifier(null, null), + consumeCodeResponse.user.id); + Passwordless.updateUser(process.getProcess(), authUser.id, null, new Passwordless.FieldUpdate(PHONE_NUMBER)); + authUser = storage.getPrimaryUserById(new AppIdentifier(null, null), consumeCodeResponse.user.id); + assertEquals(authUser.loginMethods[0].phoneNumber, PHONE_NUMBER); // create code with email twice { @@ -816,16 +819,17 @@ public void testConsumeWrongUserInputCodeExceedingMaxAttemptsWithConfigUpdate() assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); } - private UserInfo checkUserWithConsumeResponse(PasswordlessStorage storage, Passwordless.ConsumeCodeResponse resp, - String email, String phoneNumber, long joinedAfter) + private AuthRecipeUserInfo checkUserWithConsumeResponse(PasswordlessStorage storage, + Passwordless.ConsumeCodeResponse resp, + String email, String phoneNumber, long joinedAfter) throws StorageQueryException { - UserInfo user = storage.getUserById(new AppIdentifier(null, null), resp.user.id); + AuthRecipeUserInfo user = storage.getPrimaryUserById(new AppIdentifier(null, null), resp.user.id); assertNotNull(user); assertEquals(email, resp.user.email); - assertEquals(email, user.email); + assertEquals(email, user.loginMethods[0].email); - assertEquals(phoneNumber, user.phoneNumber); + assertEquals(phoneNumber, user.loginMethods[0].phoneNumber); assertEquals(phoneNumber, resp.user.phoneNumber); assert (user.timeJoined >= joinedAfter); diff --git a/src/test/java/io/supertokens/test/passwordless/PasswordlessStorageTest.java b/src/test/java/io/supertokens/test/passwordless/PasswordlessStorageTest.java index 25b9b8e3f..5e3272a28 100644 --- a/src/test/java/io/supertokens/test/passwordless/PasswordlessStorageTest.java +++ b/src/test/java/io/supertokens/test/passwordless/PasswordlessStorageTest.java @@ -18,6 +18,7 @@ import io.supertokens.ProcessState; import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateUserIdException; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; @@ -257,7 +258,7 @@ public void testCreateUserExceptions() throws Exception { storage.createUser(new TenantIdentifier(null, null, null), userId, email, null, timeJoined); storage.createUser(new TenantIdentifier(null, null, null), userId2, null, phoneNumber, timeJoined); - assertNotNull(storage.getUserById(new AppIdentifier(null, null), userId)); + assertNotNull(storage.getPrimaryUserById(new AppIdentifier(null, null), userId)); { Exception error = null; @@ -298,7 +299,7 @@ public void testCreateUserExceptions() throws Exception { assertNotNull(error); assert (error instanceof DuplicateEmailException); - assertNull(storage.getUserById(new AppIdentifier(null, null), userId3)); + assertNull(storage.getPrimaryUserById(new AppIdentifier(null, null), userId3)); } { @@ -312,7 +313,7 @@ public void testCreateUserExceptions() throws Exception { assertNotNull(error); assert (error instanceof DuplicatePhoneNumberException); - assertNull(storage.getUserById(new AppIdentifier(null, null), userId3)); + assertNull(storage.getPrimaryUserById(new AppIdentifier(null, null), userId3)); } { @@ -326,7 +327,7 @@ public void testCreateUserExceptions() throws Exception { assertNotNull(error); assert (error instanceof IllegalArgumentException); - assertNull(storage.getUserById(new AppIdentifier(null, null), userId3)); + assertNull(storage.getPrimaryUserById(new AppIdentifier(null, null), userId3)); } process.kill(); @@ -370,7 +371,7 @@ public void testUpdateUserExceptions() throws Exception { storage.createUser(new TenantIdentifier(null, null, null), userIdPhone2, null, phoneNumber2, timeJoined); - assertNotNull(storage.getUserById(new AppIdentifier(null, null), userIdEmail1)); + assertNotNull(storage.getPrimaryUserById(new AppIdentifier(null, null), userIdEmail1)); { Exception error = null; @@ -391,7 +392,7 @@ public void testUpdateUserExceptions() throws Exception { assertNotNull(error); assert (error instanceof UnknownUserIdException); - assertNull(storage.getUserById(new AppIdentifier(null, null), userIdNotExists)); + assertNull(storage.getPrimaryUserById(new AppIdentifier(null, null), userIdNotExists)); } { @@ -413,7 +414,7 @@ public void testUpdateUserExceptions() throws Exception { assertNotNull(error); assert (error instanceof UnknownUserIdException); - assertNull(storage.getUserById(new AppIdentifier(null, null), userIdNotExists)); + assertNull(storage.getPrimaryUserById(new AppIdentifier(null, null), userIdNotExists)); } { @@ -435,7 +436,8 @@ public void testUpdateUserExceptions() throws Exception { assertNotNull(error); assert (error instanceof DuplicateEmailException); - assertEquals(email, storage.getUserById(new AppIdentifier(null, null), userIdEmail1).email); + assertEquals(email, + storage.getPrimaryUserById(new AppIdentifier(null, null), userIdEmail1).loginMethods[0].email); } { @@ -456,7 +458,8 @@ public void testUpdateUserExceptions() throws Exception { assertNotNull(error); assert (error instanceof DuplicateEmailException); - assertEquals(email, storage.getUserById(new AppIdentifier(null, null), userIdEmail1).email); + assertEquals(email, + storage.getPrimaryUserById(new AppIdentifier(null, null), userIdEmail1).loginMethods[0].email); } { @@ -478,7 +481,9 @@ public void testUpdateUserExceptions() throws Exception { assertNotNull(error); assert (error instanceof DuplicatePhoneNumberException); - assertEquals(phoneNumber, storage.getUserById(new AppIdentifier(null, null), userIdPhone1).phoneNumber); + assertEquals(phoneNumber, + storage.getPrimaryUserById(new AppIdentifier(null, null), + userIdPhone1).loginMethods[0].phoneNumber); } { @@ -500,9 +505,9 @@ public void testUpdateUserExceptions() throws Exception { assertNotNull(error); assert (error instanceof DuplicatePhoneNumberException); - UserInfo userInDb = storage.getUserById(new AppIdentifier(null, null), userIdEmail1); - assertEquals(email, userInDb.email); - assertNull(userInDb.phoneNumber); + AuthRecipeUserInfo userInDb = storage.getPrimaryUserById(new AppIdentifier(null, null), userIdEmail1); + assertEquals(email, userInDb.loginMethods[0].email); + assertNull(userInDb.loginMethods[0].phoneNumber); } { @@ -523,9 +528,9 @@ public void testUpdateUserExceptions() throws Exception { assertNotNull(error); assert (error instanceof DuplicateEmailException); - UserInfo userInDb = storage.getUserById(new AppIdentifier(null, null), userIdPhone1); - assertNull(userInDb.email); - assertEquals(phoneNumber, userInDb.phoneNumber); + AuthRecipeUserInfo userInDb = storage.getPrimaryUserById(new AppIdentifier(null, null), userIdPhone1); + assertNull(userInDb.loginMethods[0].email); + assertEquals(phoneNumber, userInDb.loginMethods[0].phoneNumber); } process.kill(); @@ -557,7 +562,7 @@ public void testUpdateUser() throws Exception { storage.createUser(new TenantIdentifier(null, null, null), userId, email, null, timeJoined); - assertNotNull(storage.getUserById(new AppIdentifier(null, null), userId)); + assertNotNull(storage.getPrimaryUserById(new AppIdentifier(null, null), userId)); storage.startTransaction(con -> { try { @@ -887,9 +892,9 @@ private void checkLockingCalls(PasswordlessSQLStorage storage, TestFunction func private void checkUser(PasswordlessSQLStorage storage, String userId, String email, String phoneNumber) throws StorageQueryException { - UserInfo userById = storage.getUserById(new AppIdentifier(null, null), userId); - assertEquals(email, userById.email); - assertEquals(phoneNumber, userById.phoneNumber); + AuthRecipeUserInfo userById = storage.getPrimaryUserById(new AppIdentifier(null, null), userId); + assertEquals(email, userById.loginMethods[0].email); + assertEquals(phoneNumber, userById.loginMethods[0].phoneNumber); if (email != null) { UserInfo user = storage.getUserByEmail(new TenantIdentifier(null, null, null), email); assert (user.equals(userById)); diff --git a/src/test/java/io/supertokens/test/passwordless/PasswordlessUpdateUserTest.java b/src/test/java/io/supertokens/test/passwordless/PasswordlessUpdateUserTest.java index 19f9e7e2a..c551919a4 100644 --- a/src/test/java/io/supertokens/test/passwordless/PasswordlessUpdateUserTest.java +++ b/src/test/java/io/supertokens/test/passwordless/PasswordlessUpdateUserTest.java @@ -172,7 +172,8 @@ public void updateEmail() throws Exception { Passwordless.updateUser(process.getProcess(), user.id, new Passwordless.FieldUpdate(alternate_email), null); - assertEquals(alternate_email, storage.getUserById(new AppIdentifier(null, null), user.id).email); + assertEquals(alternate_email, + storage.getPrimaryUserById(new AppIdentifier(null, null), user.id).loginMethods[0].email); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -206,7 +207,8 @@ public void updatePhoneNumber() throws Exception { Passwordless.updateUser(process.getProcess(), user.id, null, new Passwordless.FieldUpdate(alternate_phoneNumber)); - assertEquals(alternate_phoneNumber, storage.getUserById(new AppIdentifier(null, null), user.id).phoneNumber); + assertEquals(alternate_phoneNumber, + storage.getPrimaryUserById(new AppIdentifier(null, null), user.id).loginMethods[0].phoneNumber); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -239,8 +241,9 @@ public void clearEmailSetPhoneNumber() throws Exception { Passwordless.updateUser(process.getProcess(), user.id, new Passwordless.FieldUpdate(null), new Passwordless.FieldUpdate(PHONE_NUMBER)); - assertEquals(PHONE_NUMBER, storage.getUserById(new AppIdentifier(null, null), user.id).phoneNumber); - assertNull(storage.getUserById(new AppIdentifier(null, null), user.id).email); + assertEquals(PHONE_NUMBER, + storage.getPrimaryUserById(new AppIdentifier(null, null), user.id).loginMethods[0].phoneNumber); + assertNull(storage.getPrimaryUserById(new AppIdentifier(null, null), user.id).loginMethods[0].email); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -273,8 +276,8 @@ public void clearPhoneNumberSetEmail() throws Exception { Passwordless.updateUser(process.getProcess(), user.id, new Passwordless.FieldUpdate(EMAIL), new Passwordless.FieldUpdate(null)); - assertEquals(EMAIL, storage.getUserById(new AppIdentifier(null, null), user.id).email); - assertNull(storage.getUserById(new AppIdentifier(null, null), user.id).phoneNumber); + assertEquals(EMAIL, storage.getPrimaryUserById(new AppIdentifier(null, null), user.id).loginMethods[0].email); + assertNull(storage.getPrimaryUserById(new AppIdentifier(null, null), user.id).loginMethods[0].phoneNumber); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -425,8 +428,9 @@ public void setPhoneNumberSetEmail() throws Exception { Passwordless.updateUser(process.getProcess(), user.id, new Passwordless.FieldUpdate(EMAIL), new Passwordless.FieldUpdate(alternate_phoneNumber)); - assertEquals(EMAIL, storage.getUserById(new AppIdentifier(null, null), user.id).email); - assertEquals(alternate_phoneNumber, storage.getUserById(new AppIdentifier(null, null), user.id).phoneNumber); + assertEquals(EMAIL, storage.getPrimaryUserById(new AppIdentifier(null, null), user.id).loginMethods[0].email); + assertEquals(alternate_phoneNumber, + storage.getPrimaryUserById(new AppIdentifier(null, null), user.id).loginMethods[0].phoneNumber); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); From 810fcc3d627fe2d7dc734a05544511042938ee99 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Tue, 11 Jul 2023 16:35:17 +0530 Subject: [PATCH 012/131] adds stub for new function --- src/main/java/io/supertokens/inmemorydb/Start.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/io/supertokens/inmemorydb/Start.java b/src/main/java/io/supertokens/inmemorydb/Start.java index f8eafed12..874a1d5e0 100644 --- a/src/main/java/io/supertokens/inmemorydb/Start.java +++ b/src/main/java/io/supertokens/inmemorydb/Start.java @@ -1291,6 +1291,13 @@ public AuthRecipeUserInfo getPrimaryUserById(AppIdentifier appIdentifier, String } } + @Override + public AuthRecipeUserInfo[] listPrimaryUsersByEmail(TenantIdentifier tenantIdentifier, String email) + throws StorageQueryException { + // TODO: + return new AuthRecipeUserInfo[0]; + } + @Override public List getJWTSigningKeys_Transaction(AppIdentifier appIdentifier, TransactionConnection con) From b96f0e68dd8dc79154229b68cebf59c59a4836a0 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Tue, 11 Jul 2023 18:32:44 +0530 Subject: [PATCH 013/131] removes use of unnecessary function --- .../emailpassword/EmailPassword.java | 71 ++++++++++--- .../java/io/supertokens/inmemorydb/Start.java | 17 +-- .../queries/EmailPasswordQueries.java | 26 ++--- .../inmemorydb/queries/GeneralQueries.java | 18 ++++ .../api/emailpassword/SignInAPI.java | 5 +- .../webserver/api/emailpassword/UserAPI.java | 4 +- .../test/emailpassword/EmailPasswordTest.java | 39 ++++--- .../MultitenantEmailPasswordTest.java | 49 +++++---- .../emailpassword/PasswordHashingTest.java | 15 +-- .../UpdateUsersEmailAndPasswordTest.java | 12 ++- .../test/emailpassword/UserMigrationTest.java | 100 +++++++++--------- .../ImportUserWithPasswordHashAPITest.java | 29 ++--- .../emailpassword/api/SignUpAPITest2_7.java | 8 +- .../api/TestTenantUserAssociation.java | 87 +++++++++------ .../recipe/EmailPasswordAPITest.java | 15 ++- 15 files changed, 290 insertions(+), 205 deletions(-) diff --git a/src/main/java/io/supertokens/emailpassword/EmailPassword.java b/src/main/java/io/supertokens/emailpassword/EmailPassword.java index ec9b71605..4ae9795ee 100644 --- a/src/main/java/io/supertokens/emailpassword/EmailPassword.java +++ b/src/main/java/io/supertokens/emailpassword/EmailPassword.java @@ -24,6 +24,7 @@ import io.supertokens.emailpassword.exceptions.WrongCredentialsException; import io.supertokens.multitenancy.Multitenancy; import io.supertokens.multitenancy.exception.BadPermissionException; +import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.authRecipe.LoginMethod; @@ -50,14 +51,15 @@ import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.spec.InvalidKeySpecException; +import java.util.Arrays; public class EmailPassword { public static class ImportUserResponse { public boolean didUserAlreadyExist; - public UserInfo user; + public AuthRecipeUserInfo user; - public ImportUserResponse(boolean didUserAlreadyExist, UserInfo user) { + public ImportUserResponse(boolean didUserAlreadyExist, AuthRecipeUserInfo user) { this.didUserAlreadyExist = didUserAlreadyExist; this.user = user; } @@ -168,12 +170,26 @@ public static ImportUserResponse importUserWithPasswordHash(TenantIdentifierWith } catch (DuplicateUserIdException e) { // we retry with a new userId } catch (DuplicateEmailException e) { - UserInfo userInfoToBeUpdated = storage.getUserInfoUsingEmail(tenantIdentifierWithStorage, email); + AuthRecipeUserInfo[] allUsers = storage.listPrimaryUsersByEmail(tenantIdentifierWithStorage, email); + AuthRecipeUserInfo userInfoToBeUpdated = null; + LoginMethod loginMethod = null; + for (AuthRecipeUserInfo currUser : allUsers) { + for (LoginMethod currLM : currUser.loginMethods) { + if (currLM.email.equals(email) && currLM.recipeId == RECIPE_ID.EMAIL_PASSWORD && + Arrays.stream(currLM.tenantIds) + .anyMatch(s -> s.equals(tenantIdentifierWithStorage.getTenantId()))) { + userInfoToBeUpdated = currUser; + loginMethod = currLM; + break; + } + } + } if (userInfoToBeUpdated != null) { + LoginMethod finalLoginMethod = loginMethod; storage.startTransaction(con -> { storage.updateUsersPassword_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), con, - userInfoToBeUpdated.id, passwordHash); + finalLoginMethod.recipeUserId, passwordHash); return null; }); return new ImportUserResponse(true, userInfoToBeUpdated); @@ -197,8 +213,8 @@ public static ImportUserResponse importUserWithPasswordHash(Main main, @Nonnull } @TestOnly - public static UserInfo signIn(Main main, @Nonnull String email, - @Nonnull String password) + public static AuthRecipeUserInfo signIn(Main main, @Nonnull String email, + @Nonnull String password) throws StorageQueryException, WrongCredentialsException { try { Storage storage = StorageLayer.getStorage(main); @@ -209,9 +225,9 @@ public static UserInfo signIn(Main main, @Nonnull String email, } } - public static UserInfo signIn(TenantIdentifierWithStorage tenantIdentifierWithStorage, Main main, - @Nonnull String email, - @Nonnull String password) + public static AuthRecipeUserInfo signIn(TenantIdentifierWithStorage tenantIdentifierWithStorage, Main main, + @Nonnull String email, + @Nonnull String password) throws StorageQueryException, WrongCredentialsException, TenantOrAppNotFoundException, BadPermissionException { @@ -223,17 +239,30 @@ public static UserInfo signIn(TenantIdentifierWithStorage tenantIdentifierWithSt throw new BadPermissionException("Email password login not enabled for tenant"); } - UserInfo user = tenantIdentifierWithStorage.getEmailPasswordStorage() - .getUserInfoUsingEmail(tenantIdentifierWithStorage, email); + AuthRecipeUserInfo[] users = tenantIdentifierWithStorage.getAuthRecipeStorage() + .listPrimaryUsersByEmail(tenantIdentifierWithStorage, email); + + AuthRecipeUserInfo user = null; + LoginMethod lM = null; + for (AuthRecipeUserInfo currUser : users) { + for (LoginMethod currLM : currUser.loginMethods) { + if (currLM.recipeId == RECIPE_ID.EMAIL_PASSWORD && currLM.email.equals(email) && + Arrays.stream(currLM.tenantIds) + .anyMatch(s -> s.equals(tenantIdentifierWithStorage.getTenantId()))) { + user = currUser; + lM = currLM; + } + } + } - if (user == null) { + if (user == null || lM == null) { throw new WrongCredentialsException(); } try { if (!PasswordHashing.getInstance(main) .verifyPasswordWithHash(tenantIdentifierWithStorage.toAppIdentifier(), password, - user.passwordHash)) { + lM.passwordHash)) { throw new WrongCredentialsException(); } } catch (WrongCredentialsException e) { @@ -474,9 +503,21 @@ public static UserInfo getUserUsingId(AppIdentifierWithStorage appIdentifierWith return null; } - public static UserInfo getUserUsingEmail(TenantIdentifierWithStorage tenantIdentifierWithStorage, String email) + public static AuthRecipeUserInfo getUserUsingEmail(TenantIdentifierWithStorage tenantIdentifierWithStorage, + String email) throws StorageQueryException, TenantOrAppNotFoundException { - return tenantIdentifierWithStorage.getEmailPasswordStorage().getUserInfoUsingEmail( + AuthRecipeUserInfo[] users = tenantIdentifierWithStorage.getAuthRecipeStorage().listPrimaryUsersByEmail( tenantIdentifierWithStorage, email); + // filter used based on login method + for (AuthRecipeUserInfo user : users) { + for (LoginMethod lM : user.loginMethods) { + if (lM.email.equals(email) && lM.recipeId == RECIPE_ID.EMAIL_PASSWORD && + Arrays.stream(lM.tenantIds).anyMatch( + tenantId -> tenantId.equals(tenantIdentifierWithStorage.getTenantId()))) { + return user; + } + } + } + return null; } } diff --git a/src/main/java/io/supertokens/inmemorydb/Start.java b/src/main/java/io/supertokens/inmemorydb/Start.java index 874a1d5e0..5be90b514 100644 --- a/src/main/java/io/supertokens/inmemorydb/Start.java +++ b/src/main/java/io/supertokens/inmemorydb/Start.java @@ -769,16 +769,6 @@ public void deleteEmailPasswordUser(AppIdentifier appIdentifier, String userId) } } - @Override - public UserInfo getUserInfoUsingEmail(TenantIdentifier tenantIdentifier, String email) - throws StorageQueryException { - try { - return EmailPasswordQueries.getUserInfoUsingEmail(this, tenantIdentifier, email); - } catch (SQLException e) { - throw new StorageQueryException(e); - } - } - @Override public void addPasswordResetToken(AppIdentifier appIdentifier, PasswordResetTokenInfo passwordResetTokenInfo) throws StorageQueryException, UnknownUserIdException, DuplicatePasswordResetTokenException { @@ -1294,8 +1284,11 @@ public AuthRecipeUserInfo getPrimaryUserById(AppIdentifier appIdentifier, String @Override public AuthRecipeUserInfo[] listPrimaryUsersByEmail(TenantIdentifier tenantIdentifier, String email) throws StorageQueryException { - // TODO: - return new AuthRecipeUserInfo[0]; + try { + return GeneralQueries.listPrimaryUsersByEmail(this, tenantIdentifier, email); + } catch (SQLException e) { + throw new StorageQueryException(e); + } } @Override diff --git a/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java index 433fa9de0..9304a0d88 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java @@ -398,37 +398,23 @@ public static List getUsersInfoUsingIdList(Start start, Set return Collections.emptyList(); } - public static UserInfo getUserInfoUsingEmail(Start start, TenantIdentifier tenantIdentifier, String email) + public static String getUserIdUsingEmail(Start start, TenantIdentifier tenantIdentifier, String email) throws StorageQueryException, SQLException { - String QUERY = "SELECT ep_users_to_tenant.user_id as user_id, ep_users_to_tenant.email as email, " - + "ep_users.password_hash as password_hash, ep_users.time_joined as time_joined " - + "FROM " + getConfig(start).getEmailPasswordUserToTenantTable() + " AS ep_users_to_tenant " - + "JOIN " + getConfig(start).getEmailPasswordUsersTable() + " AS ep_users " - + "ON ep_users.app_id = ep_users_to_tenant.app_id AND ep_users.user_id = ep_users_to_tenant.user_id " - + - "WHERE ep_users_to_tenant.app_id = ? AND ep_users_to_tenant.tenant_id = ? AND ep_users_to_tenant" + - ".email = ?"; + String QUERY = "SELECT user_id " + + "FROM " + getConfig(start).getEmailPasswordUserToTenantTable() + + " WHERE app_id = ? AND tenant_id = ? AND email = ?"; try (Connection con = ConnectionPool.getConnection(start)) { - UserInfoPartial userInfo = execute(con, QUERY, pst -> { + return execute(con, QUERY, pst -> { pst.setString(1, tenantIdentifier.getAppId()); pst.setString(2, tenantIdentifier.getTenantId()); pst.setString(3, email); }, result -> { if (result.next()) { - return UserInfoRowMapper.getInstance().mapOrThrow(result); + return result.getString("user_id"); } return null; }); - if (userInfo == null) { - return null; - } - fillUserInfoWithTenantIds_transaction(start, con, tenantIdentifier.toAppIdentifier(), userInfo); - fillUserInfoWithVerified_transaction(start, con, tenantIdentifier.toAppIdentifier(), userInfo); - fillUserInfoWithIsPrimaryUserBoolean_transaction(start, con, tenantIdentifier.toAppIdentifier(), - userInfo); - return new UserInfo(userInfo.id, userInfo.isPrimary, - userInfo.toLoginMethod()); } } diff --git a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java index 612b897f1..2f671dd5c 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java @@ -853,6 +853,24 @@ public static AuthRecipeUserInfo[] getUsers(Start start, TenantIdentifier tenant return finalResult; } + public static AuthRecipeUserInfo[] listPrimaryUsersByEmail(Start start, TenantIdentifier tenantIdentifier, + String email) + throws StorageQueryException, SQLException { + List userIds = new ArrayList<>(); + + String emailPasswordUserId = EmailPasswordQueries.getUserIdUsingEmail(start, tenantIdentifier, email); + if (emailPasswordUserId != null) { + userIds.add(emailPasswordUserId); + } + + // TODO:add support for other recipes. + + List result = getPrimaryUserInfoForUserIds(start, tenantIdentifier.toAppIdentifier(), + userIds); + + return result.toArray(new AuthRecipeUserInfo[0]); + } + public static AuthRecipeUserInfo getPrimaryUserInfoForUserId(Start start, AppIdentifier appIdentifier, String id) throws SQLException, StorageQueryException { List ids = new ArrayList<>(); diff --git a/src/main/java/io/supertokens/webserver/api/emailpassword/SignInAPI.java b/src/main/java/io/supertokens/webserver/api/emailpassword/SignInAPI.java index b2c7f94ae..cf21a49b1 100644 --- a/src/main/java/io/supertokens/webserver/api/emailpassword/SignInAPI.java +++ b/src/main/java/io/supertokens/webserver/api/emailpassword/SignInAPI.java @@ -24,7 +24,7 @@ import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.output.Logging; import io.supertokens.pluginInterface.RECIPE_ID; -import io.supertokens.pluginInterface.emailpassword.UserInfo; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; @@ -74,7 +74,8 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I } try { - UserInfo user = EmailPassword.signIn(tenantIdentifierWithStorage, super.main, normalisedEmail, password); + AuthRecipeUserInfo user = EmailPassword.signIn(tenantIdentifierWithStorage, super.main, normalisedEmail, + password); ActiveUsers.updateLastActive(tenantIdentifierWithStorage.toAppIdentifierWithStorage(), main, user.id); // use the internal user id diff --git a/src/main/java/io/supertokens/webserver/api/emailpassword/UserAPI.java b/src/main/java/io/supertokens/webserver/api/emailpassword/UserAPI.java index 38bc3f316..bd8297532 100644 --- a/src/main/java/io/supertokens/webserver/api/emailpassword/UserAPI.java +++ b/src/main/java/io/supertokens/webserver/api/emailpassword/UserAPI.java @@ -22,7 +22,7 @@ import io.supertokens.emailpassword.EmailPassword; import io.supertokens.output.Logging; import io.supertokens.pluginInterface.RECIPE_ID; -import io.supertokens.pluginInterface.emailpassword.UserInfo; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; @@ -73,7 +73,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO try { // API is app specific for get by UserId - UserInfo user = null; + AuthRecipeUserInfo user = null; try { if (userId != null) { diff --git a/src/test/java/io/supertokens/test/emailpassword/EmailPasswordTest.java b/src/test/java/io/supertokens/test/emailpassword/EmailPasswordTest.java index c1346d096..cf4484b0a 100644 --- a/src/test/java/io/supertokens/test/emailpassword/EmailPasswordTest.java +++ b/src/test/java/io/supertokens/test/emailpassword/EmailPasswordTest.java @@ -23,6 +23,8 @@ import io.supertokens.emailpassword.exceptions.ResetPasswordInvalidTokenException; import io.supertokens.emailpassword.exceptions.WrongCredentialsException; import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeStorage; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.emailpassword.PasswordResetTokenInfo; import io.supertokens.pluginInterface.emailpassword.UserInfo; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; @@ -79,12 +81,14 @@ public void testStorageLayerGetMailPasswordStorageLayerThrowsExceptionIfTypeIsNo if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { try { - new TenantIdentifierWithStorage(null, null, null, StorageLayer.getStorage(process.getProcess())).getEmailPasswordStorage(); + new TenantIdentifierWithStorage(null, null, null, + StorageLayer.getStorage(process.getProcess())).getEmailPasswordStorage(); throw new Exception("Should not come here"); } catch (UnsupportedOperationException e) { } } else { - new TenantIdentifierWithStorage(null, null, null, StorageLayer.getStorage(process.getProcess())).getEmailPasswordStorage(); + new TenantIdentifierWithStorage(null, null, null, + StorageLayer.getStorage(process.getProcess())).getEmailPasswordStorage(); } process.kill(); @@ -171,11 +175,11 @@ public void testThatAfterSignUpThePasswordIsHashedAndStoredInTheDatabase() throw UserInfo user = EmailPassword.signUp(process.getProcess(), "random@gmail.com", "validPass123"); - UserInfo userInfo = ((EmailPasswordSQLStorage) StorageLayer.getStorage(process.getProcess())) - .getUserInfoUsingEmail(new TenantIdentifier(null, null, null), user.email); - assertNotEquals(userInfo.passwordHash, "validPass123"); + AuthRecipeUserInfo userInfo = ((AuthRecipeStorage) StorageLayer.getStorage(process.getProcess())) + .listPrimaryUsersByEmail(new TenantIdentifier(null, null, null), user.email)[0]; + assertNotEquals(userInfo.loginMethods[0].passwordHash, "validPass123"); assertTrue(PasswordHashing.getInstance(process.getProcess()).verifyPasswordWithHash("validPass123", - userInfo.passwordHash)); + userInfo.loginMethods[0].passwordHash)); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -198,7 +202,8 @@ public void testThatAfterResetPasswordGenerateTokenTheTokenIsHashedInTheDatabase UserInfo user = EmailPassword.signUp(process.getProcess(), "random@gmail.com", "validPass123"); String resetToken = EmailPassword.generatePasswordResetToken(process.getProcess(), user.id); - PasswordResetTokenInfo resetTokenInfo = ((EmailPasswordSQLStorage) StorageLayer.getStorage(process.getProcess())) + PasswordResetTokenInfo resetTokenInfo = ((EmailPasswordSQLStorage) StorageLayer.getStorage( + process.getProcess())) .getPasswordResetTokenInfo(new AppIdentifier(null, null), io.supertokens.utils.Utils.hashSHA256(resetToken)); @@ -229,12 +234,12 @@ public void testThatAfterResetPasswordIsCompletedThePasswordIsHashedInTheDatabas EmailPassword.resetPassword(process.getProcess(), resetToken, "newValidPass123"); - UserInfo userInfo = ((EmailPasswordSQLStorage) StorageLayer.getStorage(process.getProcess())) - .getUserInfoUsingEmail(new TenantIdentifier(null, null, null), user.email); - assertNotEquals(userInfo.passwordHash, "newValidPass123"); + AuthRecipeUserInfo userInfo = ((AuthRecipeStorage) StorageLayer.getStorage(process.getProcess())) + .listPrimaryUsersByEmail(new TenantIdentifier(null, null, null), user.email)[0]; + assertNotEquals(userInfo.loginMethods[0].passwordHash, "newValidPass123"); assertTrue(PasswordHashing.getInstance(process.getProcess()).verifyPasswordWithHash("newValidPass123", - userInfo.passwordHash)); + userInfo.loginMethods[0].passwordHash)); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -311,9 +316,9 @@ public void multiplePasswordResetTokensPerUserAndThenVerifyWithSignin() throws E } - UserInfo user1 = EmailPassword.signIn(process.getProcess(), "test1@example.com", "newPassword"); + AuthRecipeUserInfo user1 = EmailPassword.signIn(process.getProcess(), "test1@example.com", "newPassword"); - assertEquals(user1.email, user.email); + assertEquals(user1.loginMethods[0].email, user.email); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -437,7 +442,7 @@ public void clashingUserIdDuringSignUp() throws Exception { ((EmailPasswordSQLStorage) StorageLayer.getStorage(process.getProcess())) .signUp(new TenantIdentifier(null, null, null), "8ed86166-bfd8-4234-9dfe-abca9606dbd5", "test1@example.com", "password", - System.currentTimeMillis()); + System.currentTimeMillis()); assert (false); } catch (DuplicateUserIdException ignored) { @@ -490,7 +495,7 @@ public void clashingEmailAndUserIdDuringSignUp() throws Exception { ((EmailPasswordSQLStorage) StorageLayer.getStorage(process.getProcess())) .signUp(new TenantIdentifier(null, null, null), "8ed86166-bfd8-4234-9dfe-abca9606dbd5", "test@example.com", "password", - System.currentTimeMillis()); + System.currentTimeMillis()); assert (false); } catch (DuplicateUserIdException ignored) { @@ -513,9 +518,9 @@ public void signUpAndThenSignIn() throws Exception { UserInfo userSignUp = EmailPassword.signUp(process.getProcess(), "test@example.com", "password"); - UserInfo user = EmailPassword.signIn(process.getProcess(), "test@example.com", "password"); + AuthRecipeUserInfo user = EmailPassword.signIn(process.getProcess(), "test@example.com", "password"); - assert (user.email.equals("test@example.com")); + assert (user.loginMethods[0].email.equals("test@example.com")); assert (userSignUp.id.equals(user.id)); diff --git a/src/test/java/io/supertokens/test/emailpassword/MultitenantEmailPasswordTest.java b/src/test/java/io/supertokens/test/emailpassword/MultitenantEmailPasswordTest.java index ad9e631d0..e5a81bcde 100644 --- a/src/test/java/io/supertokens/test/emailpassword/MultitenantEmailPasswordTest.java +++ b/src/test/java/io/supertokens/test/emailpassword/MultitenantEmailPasswordTest.java @@ -28,6 +28,7 @@ import io.supertokens.multitenancy.exception.CannotModifyBaseConfigException; import io.supertokens.pluginInterface.STORAGE_TYPE; import io.supertokens.pluginInterface.Storage; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.emailpassword.UserInfo; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; @@ -47,7 +48,8 @@ import java.io.IOException; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; public class MultitenantEmailPasswordTest { @AfterClass @@ -157,20 +159,23 @@ public void testSignUpAndLoginInDifferentTenants() { EmailPassword.signUp(t1storage, process.getProcess(), "user1@example.com", "password1"); - UserInfo userInfo = EmailPassword.signIn(t1storage, process.getProcess(), "user1@example.com", "password1"); - assertEquals("user1@example.com", userInfo.email); + AuthRecipeUserInfo userInfo = EmailPassword.signIn(t1storage, process.getProcess(), "user1@example.com", + "password1"); + assertEquals("user1@example.com", userInfo.loginMethods[0].email); } { EmailPassword.signUp(t2storage, process.getProcess(), "user2@example.com", "password2"); - UserInfo userInfo = EmailPassword.signIn(t2storage, process.getProcess(), "user2@example.com", "password2"); - assertEquals("user2@example.com", userInfo.email); + AuthRecipeUserInfo userInfo = EmailPassword.signIn(t2storage, process.getProcess(), "user2@example.com", + "password2"); + assertEquals("user2@example.com", userInfo.loginMethods[0].email); } { EmailPassword.signUp(t3storage, process.getProcess(), "user3@example.com", "password3"); - UserInfo userInfo = EmailPassword.signIn(t3storage, process.getProcess(), "user3@example.com", "password3"); - assertEquals("user3@example.com", userInfo.email); + AuthRecipeUserInfo userInfo = EmailPassword.signIn(t3storage, process.getProcess(), "user3@example.com", + "password3"); + assertEquals("user3@example.com", userInfo.loginMethods[0].email); } process.kill(); @@ -210,18 +215,21 @@ public void testSameEmailWithDifferentPasswordsOnDifferentTenantsWorksCorrectly( EmailPassword.signUp(t3storage, process.getProcess(), "user@example.com", "password3"); { - UserInfo userInfo = EmailPassword.signIn(t1storage, process.getProcess(), "user@example.com", "password1"); - assertEquals("user@example.com", userInfo.email); + AuthRecipeUserInfo userInfo = EmailPassword.signIn(t1storage, process.getProcess(), "user@example.com", + "password1"); + assertEquals("user@example.com", userInfo.loginMethods[0].email); } { - UserInfo userInfo = EmailPassword.signIn(t2storage, process.getProcess(), "user@example.com", "password2"); - assertEquals("user@example.com", userInfo.email); + AuthRecipeUserInfo userInfo = EmailPassword.signIn(t2storage, process.getProcess(), "user@example.com", + "password2"); + assertEquals("user@example.com", userInfo.loginMethods[0].email); } { - UserInfo userInfo = EmailPassword.signIn(t3storage, process.getProcess(), "user@example.com", "password3"); - assertEquals("user@example.com", userInfo.email); + AuthRecipeUserInfo userInfo = EmailPassword.signIn(t3storage, process.getProcess(), "user@example.com", + "password3"); + assertEquals("user@example.com", userInfo.loginMethods[0].email); } process.kill(); @@ -320,17 +328,17 @@ public void testGetUserUsingEmailReturnsTheUserFromTheSpecificTenant() UserInfo user3 = EmailPassword.signUp(t3storage, process.getProcess(), "user@example.com", "password3"); { - UserInfo userInfo = EmailPassword.getUserUsingEmail(t1storage, user1.email); + AuthRecipeUserInfo userInfo = EmailPassword.getUserUsingEmail(t1storage, user1.loginMethods[0].email); assertEquals(user1, userInfo); } { - UserInfo userInfo = EmailPassword.getUserUsingEmail(t2storage, user2.email); + AuthRecipeUserInfo userInfo = EmailPassword.getUserUsingEmail(t2storage, user2.loginMethods[0].email); assertEquals(user2, userInfo); } { - UserInfo userInfo = EmailPassword.getUserUsingEmail(t3storage, user3.email); + AuthRecipeUserInfo userInfo = EmailPassword.getUserUsingEmail(t3storage, user3.loginMethods[0].email); assertEquals(user3, userInfo); } @@ -390,21 +398,24 @@ public void testUpdatePasswordWorksCorrectlyAcrossAllTenants() { t1 = StorageLayer.getTenantIdentifierWithStorageAndUserIdMappingForUser(process.getProcess(), t1, user1.id, UserIdType.SUPERTOKENS).tenantIdentifierWithStorage; - UserInfo userInfo = EmailPassword.signIn(t1storage, process.getProcess(), "user@example.com", "newpassword1"); + AuthRecipeUserInfo userInfo = EmailPassword.signIn(t1storage, process.getProcess(), "user@example.com", + "newpassword1"); assertEquals(user1.id, userInfo.id); } { t2 = StorageLayer.getTenantIdentifierWithStorageAndUserIdMappingForUser(process.getProcess(), t2, user2.id, UserIdType.SUPERTOKENS).tenantIdentifierWithStorage; - UserInfo userInfo = EmailPassword.signIn(t2storage, process.getProcess(), "user@example.com", "newpassword2"); + AuthRecipeUserInfo userInfo = EmailPassword.signIn(t2storage, process.getProcess(), "user@example.com", + "newpassword2"); assertEquals(user2.id, userInfo.id); } { t3 = StorageLayer.getTenantIdentifierWithStorageAndUserIdMappingForUser(process.getProcess(), t3, user3.id, UserIdType.SUPERTOKENS).tenantIdentifierWithStorage; - UserInfo userInfo = EmailPassword.signIn(t3storage, process.getProcess(), "user@example.com", "newpassword3"); + AuthRecipeUserInfo userInfo = EmailPassword.signIn(t3storage, process.getProcess(), "user@example.com", + "newpassword3"); assertEquals(user3.id, userInfo.id); } diff --git a/src/test/java/io/supertokens/test/emailpassword/PasswordHashingTest.java b/src/test/java/io/supertokens/test/emailpassword/PasswordHashingTest.java index f34bb3084..f5e2ab27c 100644 --- a/src/test/java/io/supertokens/test/emailpassword/PasswordHashingTest.java +++ b/src/test/java/io/supertokens/test/emailpassword/PasswordHashingTest.java @@ -25,6 +25,7 @@ import io.supertokens.emailpassword.exceptions.WrongCredentialsException; import io.supertokens.inmemorydb.Start; import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.emailpassword.UserInfo; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.storageLayer.StorageLayer; @@ -88,9 +89,9 @@ public void importUserWithFireBaseSCrypt() throws Exception { CoreConfig.PASSWORD_HASHING_ALG.FIREBASE_SCRYPT); // try signing in - UserInfo user = EmailPassword.signIn(process.main, email, password); - assertEquals(user.email, email); - assertEquals(user.passwordHash, combinedPasswordHash); + AuthRecipeUserInfo user = EmailPassword.signIn(process.main, email, password); + assertEquals(user.loginMethods[0].email, email); + assertEquals(user.loginMethods[0].passwordHash, combinedPasswordHash); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -721,8 +722,8 @@ public void parallelImportUserSignInFirebaseScrypt() throws Exception { EmailPassword.importUserWithPasswordHash(process.main, uniqueEmail, combinedPasswordHash, CoreConfig.PASSWORD_HASHING_ALG.FIREBASE_SCRYPT); // try signing in - UserInfo user = EmailPassword.signIn(process.main, uniqueEmail, password); - assertEquals(user.passwordHash, combinedPasswordHash); + AuthRecipeUserInfo user = EmailPassword.signIn(process.main, uniqueEmail, password); + assertEquals(user.loginMethods[0].passwordHash, combinedPasswordHash); assertNotNull(process .checkOrWaitForEvent(ProcessState.PROCESS_STATE.PASSWORD_VERIFY_FIREBASE_SCRYPT)); int queueSize = PasswordHashing.getInstance(process.getProcess()) @@ -803,8 +804,8 @@ public void parallelImportUserSignInFirebaseScryptWithPoolSize4() throws Excepti EmailPassword.importUserWithPasswordHash(process.main, uniqueEmail, combinedPasswordHash, CoreConfig.PASSWORD_HASHING_ALG.FIREBASE_SCRYPT); // try signing in - UserInfo user = EmailPassword.signIn(process.main, uniqueEmail, password); - assertEquals(user.passwordHash, combinedPasswordHash); + AuthRecipeUserInfo user = EmailPassword.signIn(process.main, uniqueEmail, password); + assertEquals(user.loginMethods[0].passwordHash, combinedPasswordHash); assertNotNull(process .checkOrWaitForEvent(ProcessState.PROCESS_STATE.PASSWORD_VERIFY_FIREBASE_SCRYPT)); int queueSize = PasswordHashing.getInstance(process.getProcess()) diff --git a/src/test/java/io/supertokens/test/emailpassword/UpdateUsersEmailAndPasswordTest.java b/src/test/java/io/supertokens/test/emailpassword/UpdateUsersEmailAndPasswordTest.java index aa62b47b1..208a266f3 100644 --- a/src/test/java/io/supertokens/test/emailpassword/UpdateUsersEmailAndPasswordTest.java +++ b/src/test/java/io/supertokens/test/emailpassword/UpdateUsersEmailAndPasswordTest.java @@ -19,6 +19,7 @@ import io.supertokens.Main; import io.supertokens.emailpassword.EmailPassword; import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.emailpassword.UserInfo; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; @@ -77,10 +78,10 @@ public void testUpdateEmailOnly() throws Exception { EmailPassword.updateUsersEmailOrPassword(main, userInfo.id, "dave.doe@example.com", null); // then - UserInfo changedEmailUserInfo = EmailPassword.signIn(main, "dave.doe@example.com", "password"); + AuthRecipeUserInfo changedEmailUserInfo = EmailPassword.signIn(main, "dave.doe@example.com", "password"); Assert.assertEquals(userInfo.id, changedEmailUserInfo.id); - Assert.assertEquals("dave.doe@example.com", changedEmailUserInfo.email); + Assert.assertEquals("dave.doe@example.com", changedEmailUserInfo.loginMethods[0].email); }); } @@ -123,7 +124,7 @@ public void testUpdatePasswordOnly() throws Exception { EmailPassword.updateUsersEmailOrPassword(main, userInfo.id, null, "newPassword"); // then - UserInfo changedEmailUserInfo = EmailPassword.signIn(main, "john.doe@example.com", "newPassword"); + AuthRecipeUserInfo changedEmailUserInfo = EmailPassword.signIn(main, "john.doe@example.com", "newPassword"); Assert.assertEquals(userInfo.id, changedEmailUserInfo.id); }); @@ -145,10 +146,11 @@ public void testUpdateEmailAndPassword() throws Exception { EmailPassword.updateUsersEmailOrPassword(main, userInfo.id, "dave.doe@example.com", "newPassword"); // then - UserInfo changedCredentialsUserInfo = EmailPassword.signIn(main, "dave.doe@example.com", "newPassword"); + AuthRecipeUserInfo changedCredentialsUserInfo = EmailPassword.signIn(main, "dave.doe@example.com", + "newPassword"); Assert.assertEquals(userInfo.id, changedCredentialsUserInfo.id); - Assert.assertEquals("dave.doe@example.com", changedCredentialsUserInfo.email); + Assert.assertEquals("dave.doe@example.com", changedCredentialsUserInfo.loginMethods[0].email); }); } } diff --git a/src/test/java/io/supertokens/test/emailpassword/UserMigrationTest.java b/src/test/java/io/supertokens/test/emailpassword/UserMigrationTest.java index 1f14f489d..4882074aa 100644 --- a/src/test/java/io/supertokens/test/emailpassword/UserMigrationTest.java +++ b/src/test/java/io/supertokens/test/emailpassword/UserMigrationTest.java @@ -20,9 +20,9 @@ import io.supertokens.config.CoreConfig.PASSWORD_HASHING_ALG; import io.supertokens.emailpassword.EmailPassword; import io.supertokens.emailpassword.ParsedFirebaseSCryptResponse; -import io.supertokens.emailpassword.PasswordHashingUtils; import io.supertokens.emailpassword.exceptions.WrongCredentialsException; import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.emailpassword.UserInfo; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.TestingProcessManager; @@ -51,7 +51,7 @@ public void beforeEach() { @Test public void testSigningInUsersWithDifferentHashingConfigValues() throws Exception { - String[] args = { "../" }; + String[] args = {"../"}; Utils.setValueInConfig("firebase_password_hashing_signer_key", "gRhC3eDeQOdyEn4bMd9c6kxguWVmcIVq/SKa0JDPFeM6TcEevkaW56sIWfx88OHbJKnCXdWscZx0l2WbCJ1wbg=="); @@ -72,16 +72,17 @@ public void testSigningInUsersWithDifferentHashingConfigValues() throws Exceptio String email = "test@example.com"; String password = "testPass123"; String salt = "/cj0jC1br5o4+w=="; - String passwordHash = "9Y8ICWcqbzmI42DxV1jpyEjbrJPG8EQ6nI6oC32JYz+/dd7aEjI/R7jG9P5kYh8v9gyqFKaXMDzMg7eLCypbOA=="; + String passwordHash = "9Y8ICWcqbzmI42DxV1jpyEjbrJPG8EQ6nI6oC32JYz+/dd7aEjI" + + "/R7jG9P5kYh8v9gyqFKaXMDzMg7eLCypbOA=="; String combinedPasswordHash = "$" + ParsedFirebaseSCryptResponse.FIREBASE_SCRYPT_PREFIX + "$" + passwordHash + "$" + salt + "$m=" + firebaseMemCost + "$r=" + firebaseRounds + "$s=" + firebaseSaltSeparator; EmailPassword.importUserWithPasswordHash(process.getProcess(), email, combinedPasswordHash, PASSWORD_HASHING_ALG.FIREBASE_SCRYPT); // try signing in and check that it works - UserInfo userInfo = EmailPassword.signIn(process.getProcess(), email, password); - assertEquals(userInfo.email, email); - assertEquals(userInfo.passwordHash, combinedPasswordHash); + AuthRecipeUserInfo userInfo = EmailPassword.signIn(process.getProcess(), email, password); + assertEquals(userInfo.loginMethods[0].email, email); + assertEquals(userInfo.loginMethods[0].passwordHash, combinedPasswordHash); } // user 2 has a password hash that was generated with mem cost as 15 @@ -93,7 +94,8 @@ public void testSigningInUsersWithDifferentHashingConfigValues() throws Exceptio String email = "test2@example.com"; String password = "testPass123"; String salt = "/cj0jC1br5o4+w=="; - String passwordHash = "LalFtzCxLIl14+ol6e/3cjHoa2B73ULiMN+Mjm+nJJEfQqtsXPpDX1VU4s9XyiuwGrQ5RN69PWL5DrHuNUH+RA=="; + String passwordHash = "LalFtzCxLIl14+ol6e/3cjHoa2B73ULiMN+Mjm" + + "+nJJEfQqtsXPpDX1VU4s9XyiuwGrQ5RN69PWL5DrHuNUH+RA=="; String combinedPasswordHash = "$" + ParsedFirebaseSCryptResponse.FIREBASE_SCRYPT_PREFIX + "$" + passwordHash + "$" + salt + "$m=" + firebaseMemCost + "$r=" + firebaseRounds + "$s=" + firebaseSaltSeparator; @@ -101,9 +103,9 @@ public void testSigningInUsersWithDifferentHashingConfigValues() throws Exceptio PASSWORD_HASHING_ALG.FIREBASE_SCRYPT); // try signing in and check that it works - UserInfo userInfo = EmailPassword.signIn(process.getProcess(), email, password); - assertEquals(userInfo.email, email); - assertEquals(userInfo.passwordHash, combinedPasswordHash); + AuthRecipeUserInfo userInfo = EmailPassword.signIn(process.getProcess(), email, password); + assertEquals(userInfo.loginMethods[0].email, email); + assertEquals(userInfo.loginMethods[0].passwordHash, combinedPasswordHash); } process.kill(); @@ -112,7 +114,7 @@ public void testSigningInUsersWithDifferentHashingConfigValues() throws Exceptio @Test public void testBasicUserMigration() throws Exception { - String[] args = { "../" }; + String[] args = {"../"}; TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); @@ -133,11 +135,11 @@ public void testBasicUserMigration() throws Exception { // check that the user was created assertFalse(importUserResponse.didUserAlreadyExist); // try and sign in with plainTextPassword - UserInfo userInfo = EmailPassword.signIn(process.main, email, plainTextPassword); + AuthRecipeUserInfo userInfo = EmailPassword.signIn(process.main, email, plainTextPassword); assertEquals(userInfo.id, importUserResponse.user.id); - assertEquals(userInfo.passwordHash, passwordHash); - assertEquals(userInfo.email, email); + assertEquals(userInfo.loginMethods[0].passwordHash, passwordHash); + assertEquals(userInfo.loginMethods[0].email, email); } // with argon2 @@ -152,11 +154,11 @@ public void testBasicUserMigration() throws Exception { // check that the user was created assertFalse(importUserResponse.didUserAlreadyExist); // try and sign in with plainTextPassword - UserInfo userInfo = EmailPassword.signIn(process.main, email, plainTextPassword); + AuthRecipeUserInfo userInfo = EmailPassword.signIn(process.main, email, plainTextPassword); assertEquals(userInfo.id, importUserResponse.user.id); - assertEquals(userInfo.passwordHash, passwordHash); - assertEquals(userInfo.email, email); + assertEquals(userInfo.loginMethods[0].passwordHash, passwordHash); + assertEquals(userInfo.loginMethods[0].email, email); } process.kill(); @@ -165,7 +167,7 @@ public void testBasicUserMigration() throws Exception { @Test public void testUpdatingAUsersPasswordHash() throws Exception { - String[] args = { "../" }; + String[] args = {"../"}; TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); @@ -198,11 +200,11 @@ public void testUpdatingAUsersPasswordHash() throws Exception { assertNotNull(error); // sign in with the newPassword and check that it works - UserInfo userInfo = EmailPassword.signIn(process.main, email, newPassword); - assertEquals(userInfo.email, signUpUserInfo.email); + AuthRecipeUserInfo userInfo = EmailPassword.signIn(process.main, email, newPassword); + assertEquals(userInfo.loginMethods[0].email, signUpUserInfo.email); assertEquals(userInfo.id, signUpUserInfo.id); assertEquals(userInfo.timeJoined, signUpUserInfo.timeJoined); - assertEquals(userInfo.passwordHash, newPasswordHash); + assertEquals(userInfo.loginMethods[0].passwordHash, newPasswordHash); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -211,7 +213,7 @@ public void testUpdatingAUsersPasswordHash() throws Exception { // test bcrypt with different salt rounds @Test public void testAddingBcryptHashesWithDifferentSaltRounds() throws Exception { - String[] args = { "../" }; + String[] args = {"../"}; TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); @@ -231,9 +233,9 @@ public void testAddingBcryptHashesWithDifferentSaltRounds() throws Exception { assertFalse(response.didUserAlreadyExist); // test that sign in works - UserInfo userInfo = EmailPassword.signIn(process.main, email, password); - assertEquals(userInfo.email, email); - assertEquals(userInfo.passwordHash, passwordHash); + AuthRecipeUserInfo userInfo = EmailPassword.signIn(process.main, email, password); + assertEquals(userInfo.loginMethods[0].email, email); + assertEquals(userInfo.loginMethods[0].passwordHash, passwordHash); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -241,7 +243,7 @@ public void testAddingBcryptHashesWithDifferentSaltRounds() throws Exception { @Test public void testUsingArgon2HashesWithDifferentConfigValues() throws Exception { - String[] args = { "../" }; + String[] args = {"../"}; TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); @@ -261,9 +263,9 @@ public void testUsingArgon2HashesWithDifferentConfigValues() throws Exception { assertFalse(response.didUserAlreadyExist); // test that sign in works - UserInfo userInfo = EmailPassword.signIn(process.main, email, password); - assertEquals(userInfo.email, email); - assertEquals(userInfo.passwordHash, passwordHash); + AuthRecipeUserInfo userInfo = EmailPassword.signIn(process.main, email, password); + assertEquals(userInfo.loginMethods[0].email, email); + assertEquals(userInfo.loginMethods[0].passwordHash, passwordHash); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -271,7 +273,7 @@ public void testUsingArgon2HashesWithDifferentConfigValues() throws Exception { @Test public void testAddingArgon2WithDifferentVersions() throws Exception { - String[] args = { "../" }; + String[] args = {"../"}; TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); @@ -291,9 +293,9 @@ public void testAddingArgon2WithDifferentVersions() throws Exception { assertFalse(response.didUserAlreadyExist); // test that sign in works - UserInfo userInfo = EmailPassword.signIn(process.main, email, password); - assertEquals(userInfo.email, email); - assertEquals(userInfo.passwordHash, passwordHash); + AuthRecipeUserInfo userInfo = EmailPassword.signIn(process.main, email, password); + assertEquals(userInfo.loginMethods[0].email, email); + assertEquals(userInfo.loginMethods[0].passwordHash, passwordHash); } // $argon2d @@ -307,9 +309,9 @@ public void testAddingArgon2WithDifferentVersions() throws Exception { assertFalse(response.didUserAlreadyExist); // test that sign in works - UserInfo userInfo = EmailPassword.signIn(process.main, email, password); - assertEquals(userInfo.email, email); - assertEquals(userInfo.passwordHash, passwordHash); + AuthRecipeUserInfo userInfo = EmailPassword.signIn(process.main, email, password); + assertEquals(userInfo.loginMethods[0].email, email); + assertEquals(userInfo.loginMethods[0].passwordHash, passwordHash); } process.kill(); @@ -318,7 +320,7 @@ public void testAddingArgon2WithDifferentVersions() throws Exception { @Test public void testAddingBcryptHashesWithDifferentVersions() throws Exception { - String[] args = { "../" }; + String[] args = {"../"}; TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); @@ -339,9 +341,9 @@ public void testAddingBcryptHashesWithDifferentVersions() throws Exception { assertFalse(response.didUserAlreadyExist); // test that sign in works - UserInfo userInfo = EmailPassword.signIn(process.main, email, password); - assertEquals(userInfo.email, email); - assertEquals(userInfo.passwordHash, passwordHash); + AuthRecipeUserInfo userInfo = EmailPassword.signIn(process.main, email, password); + assertEquals(userInfo.loginMethods[0].email, email); + assertEquals(userInfo.loginMethods[0].passwordHash, passwordHash); } // using $2b$ @@ -355,9 +357,9 @@ public void testAddingBcryptHashesWithDifferentVersions() throws Exception { assertFalse(response.didUserAlreadyExist); // test that sign in works - UserInfo userInfo = EmailPassword.signIn(process.main, email, password); - assertEquals(userInfo.email, email); - assertEquals(userInfo.passwordHash, passwordHash); + AuthRecipeUserInfo userInfo = EmailPassword.signIn(process.main, email, password); + assertEquals(userInfo.loginMethods[0].email, email); + assertEquals(userInfo.loginMethods[0].passwordHash, passwordHash); } // using $2x$ @@ -371,9 +373,9 @@ public void testAddingBcryptHashesWithDifferentVersions() throws Exception { assertFalse(response.didUserAlreadyExist); // test that sign in works - UserInfo userInfo = EmailPassword.signIn(process.main, email, password); - assertEquals(userInfo.email, email); - assertEquals(userInfo.passwordHash, passwordHash); + AuthRecipeUserInfo userInfo = EmailPassword.signIn(process.main, email, password); + assertEquals(userInfo.loginMethods[0].email, email); + assertEquals(userInfo.loginMethods[0].passwordHash, passwordHash); } // using $2y$ @@ -387,9 +389,9 @@ public void testAddingBcryptHashesWithDifferentVersions() throws Exception { assertFalse(response.didUserAlreadyExist); // test that sign in works - UserInfo userInfo = EmailPassword.signIn(process.main, email, password); - assertEquals(userInfo.email, email); - assertEquals(userInfo.passwordHash, passwordHash); + AuthRecipeUserInfo userInfo = EmailPassword.signIn(process.main, email, password); + assertEquals(userInfo.loginMethods[0].email, email); + assertEquals(userInfo.loginMethods[0].passwordHash, passwordHash); } process.kill(); diff --git a/src/test/java/io/supertokens/test/emailpassword/api/ImportUserWithPasswordHashAPITest.java b/src/test/java/io/supertokens/test/emailpassword/api/ImportUserWithPasswordHashAPITest.java index 6bf70b186..90609ce21 100644 --- a/src/test/java/io/supertokens/test/emailpassword/api/ImportUserWithPasswordHashAPITest.java +++ b/src/test/java/io/supertokens/test/emailpassword/api/ImportUserWithPasswordHashAPITest.java @@ -22,6 +22,7 @@ import io.supertokens.emailpassword.EmailPassword; import io.supertokens.emailpassword.ParsedFirebaseSCryptResponse; import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.emailpassword.UserInfo; import io.supertokens.pluginInterface.emailpassword.sqlStorage.EmailPasswordSQLStorage; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; @@ -539,8 +540,8 @@ public void testUpdatingAUsersPasswordHash() throws Exception { assertEquals(initialUserInfo.id, response.get("user").getAsJsonObject().get("id").getAsString()); // sign in with the new password to check if the password hash got updated - UserInfo updatedUserInfo = EmailPassword.signIn(process.main, email, newPassword); - assertEquals(updatedUserInfo.passwordHash, passwordHash); + AuthRecipeUserInfo updatedUserInfo = EmailPassword.signIn(process.main, email, newPassword); + assertEquals(updatedUserInfo.loginMethods[0].passwordHash, passwordHash); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -575,9 +576,9 @@ public void testImportingUsersWithHashingAlgorithmFieldWithMixedLowerAndUpperCas assertFalse(response.get("didUserAlreadyExist").getAsBoolean()); // check that the user is created by signing in - UserInfo userInfo = EmailPassword.signIn(process.main, email, password); - assertEquals(email, userInfo.email); - assertEquals(userInfo.passwordHash, passwordHash); + AuthRecipeUserInfo userInfo = EmailPassword.signIn(process.main, email, password); + assertEquals(email, userInfo.loginMethods[0].email); + assertEquals(userInfo.loginMethods[0].passwordHash, passwordHash); } @@ -599,9 +600,9 @@ public void testImportingUsersWithHashingAlgorithmFieldWithMixedLowerAndUpperCas assertFalse(response.get("didUserAlreadyExist").getAsBoolean()); // check that the user is created by signing in - UserInfo userInfo = EmailPassword.signIn(process.main, email, password); - assertEquals(email, userInfo.email); - assertEquals(userInfo.passwordHash, passwordHash); + AuthRecipeUserInfo userInfo = EmailPassword.signIn(process.main, email, password); + assertEquals(email, userInfo.loginMethods[0].email); + assertEquals(userInfo.loginMethods[0].passwordHash, passwordHash); } process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -636,9 +637,9 @@ public void testImportingUsersWithHashingAlgorithmField() throws Exception { assertFalse(response.get("didUserAlreadyExist").getAsBoolean()); // check that the user is created by signing in - UserInfo userInfo = EmailPassword.signIn(process.main, email, password); - assertEquals(email, userInfo.email); - assertEquals(userInfo.passwordHash, passwordHash); + AuthRecipeUserInfo userInfo = EmailPassword.signIn(process.main, email, password); + assertEquals(email, userInfo.loginMethods[0].email); + assertEquals(userInfo.loginMethods[0].passwordHash, passwordHash); } @@ -660,9 +661,9 @@ public void testImportingUsersWithHashingAlgorithmField() throws Exception { assertFalse(response.get("didUserAlreadyExist").getAsBoolean()); // check that the user is created by signing in - UserInfo userInfo = EmailPassword.signIn(process.main, email, password); - assertEquals(email, userInfo.email); - assertEquals(userInfo.passwordHash, passwordHash); + AuthRecipeUserInfo userInfo = EmailPassword.signIn(process.main, email, password); + assertEquals(email, userInfo.loginMethods[0].email); + assertEquals(userInfo.loginMethods[0].passwordHash, passwordHash); } process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); diff --git a/src/test/java/io/supertokens/test/emailpassword/api/SignUpAPITest2_7.java b/src/test/java/io/supertokens/test/emailpassword/api/SignUpAPITest2_7.java index 666f7cb03..cbca37a5b 100644 --- a/src/test/java/io/supertokens/test/emailpassword/api/SignUpAPITest2_7.java +++ b/src/test/java/io/supertokens/test/emailpassword/api/SignUpAPITest2_7.java @@ -22,8 +22,6 @@ import io.supertokens.pluginInterface.STORAGE_TYPE; import io.supertokens.pluginInterface.authRecipe.AuthRecipeStorage; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; -import io.supertokens.pluginInterface.emailpassword.UserInfo; -import io.supertokens.pluginInterface.emailpassword.sqlStorage.EmailPasswordSQLStorage; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.storageLayer.StorageLayer; @@ -147,9 +145,9 @@ public void testGoodInput() throws Exception { int activeUsers = ActiveUsers.countUsersActiveSince(process.getProcess(), startTS); assert (activeUsers == 1); - UserInfo user = ((EmailPasswordSQLStorage) StorageLayer.getStorage(process.getProcess())) - .getUserInfoUsingEmail(new TenantIdentifier(null, null, null), "random@gmail.com"); - assertEquals(user.email, signUpUser.get("email").getAsString()); + AuthRecipeUserInfo user = ((AuthRecipeStorage) StorageLayer.getStorage(process.getProcess())) + .listPrimaryUsersByEmail(new TenantIdentifier(null, null, null), "random@gmail.com")[0]; + assertEquals(user.loginMethods[0].email, signUpUser.get("email").getAsString()); assertEquals(user.id, signUpUser.get("id").getAsString()); JsonObject responseBody = new JsonObject(); diff --git a/src/test/java/io/supertokens/test/multitenant/api/TestTenantUserAssociation.java b/src/test/java/io/supertokens/test/multitenant/api/TestTenantUserAssociation.java index 89fe3f83d..6effceba1 100644 --- a/src/test/java/io/supertokens/test/multitenant/api/TestTenantUserAssociation.java +++ b/src/test/java/io/supertokens/test/multitenant/api/TestTenantUserAssociation.java @@ -28,7 +28,7 @@ import io.supertokens.passwordless.Passwordless; import io.supertokens.pluginInterface.ActiveUsersStorage; import io.supertokens.pluginInterface.STORAGE_TYPE; -import io.supertokens.pluginInterface.emailpassword.UserInfo; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.exceptions.InvalidConfigException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.jwt.JWTRecipeStorage; @@ -129,14 +129,17 @@ public void testUserAssociationWorksForEmailPasswordUser() throws Exception { } createTenants(); - JsonObject user = TestMultitenancyAPIHelper.epSignUp(new TenantIdentifier(null, "a1", "t1"), "user@example.com", "password", process.getProcess()); + JsonObject user = TestMultitenancyAPIHelper.epSignUp(new TenantIdentifier(null, "a1", "t1"), "user@example.com", + "password", process.getProcess()); String userId = user.get("id").getAsString(); - JsonObject response = TestMultitenancyAPIHelper.associateUserToTenant(new TenantIdentifier(null, "a1", "t2"), userId, process.getProcess()); + JsonObject response = TestMultitenancyAPIHelper.associateUserToTenant(new TenantIdentifier(null, "a1", "t2"), + userId, process.getProcess()); assertEquals("OK", response.getAsJsonPrimitive("status").getAsString()); assertFalse(response.get("wasAlreadyAssociated").getAsBoolean()); - response = TestMultitenancyAPIHelper.associateUserToTenant(new TenantIdentifier(null, "a1", "t2"), userId, process.getProcess()); + response = TestMultitenancyAPIHelper.associateUserToTenant(new TenantIdentifier(null, "a1", "t2"), userId, + process.getProcess()); assertEquals("OK", response.getAsJsonPrimitive("status").getAsString()); assertTrue(response.get("wasAlreadyAssociated").getAsBoolean()); } @@ -148,22 +151,27 @@ public void testUserDisassociationWorks() throws Exception { } createTenants(); - JsonObject user = TestMultitenancyAPIHelper.epSignUp(new TenantIdentifier(null, "a1", "t1"), "user@example.com", "password", process.getProcess()); + JsonObject user = TestMultitenancyAPIHelper.epSignUp(new TenantIdentifier(null, "a1", "t1"), "user@example.com", + "password", process.getProcess()); String userId = user.get("id").getAsString(); - JsonObject response = TestMultitenancyAPIHelper.associateUserToTenant(new TenantIdentifier(null, "a1", "t2"), userId, process.getProcess()); + JsonObject response = TestMultitenancyAPIHelper.associateUserToTenant(new TenantIdentifier(null, "a1", "t2"), + userId, process.getProcess()); assertEquals("OK", response.getAsJsonPrimitive("status").getAsString()); assertFalse(response.get("wasAlreadyAssociated").getAsBoolean()); - response = TestMultitenancyAPIHelper.disassociateUserFromTenant(new TenantIdentifier(null, "a1", "t2"), userId, process.getProcess()); + response = TestMultitenancyAPIHelper.disassociateUserFromTenant(new TenantIdentifier(null, "a1", "t2"), userId, + process.getProcess()); assertEquals("OK", response.getAsJsonPrimitive("status").getAsString()); assertTrue(response.get("wasAssociated").getAsBoolean()); - response = TestMultitenancyAPIHelper.disassociateUserFromTenant(new TenantIdentifier(null, "a1", "t2"), userId, process.getProcess()); + response = TestMultitenancyAPIHelper.disassociateUserFromTenant(new TenantIdentifier(null, "a1", "t2"), userId, + process.getProcess()); assertEquals("OK", response.getAsJsonPrimitive("status").getAsString()); assertFalse(response.get("wasAssociated").getAsBoolean()); - response = TestMultitenancyAPIHelper.associateUserToTenant(new TenantIdentifier(null, "a1", "t2"), userId, process.getProcess()); + response = TestMultitenancyAPIHelper.associateUserToTenant(new TenantIdentifier(null, "a1", "t2"), userId, + process.getProcess()); assertEquals("OK", response.getAsJsonPrimitive("status").getAsString()); assertFalse(response.get("wasAlreadyAssociated").getAsBoolean()); } @@ -186,7 +194,8 @@ public void testUserDisassociationForNotAuthRecipes() throws Exception { } if (name.equals(UserMetadataStorage.class.getName()) - || name.equals(JWTRecipeStorage.class.getName()) || name.equals(ActiveUsersStorage.class.getName())) { + || name.equals(JWTRecipeStorage.class.getName()) || + name.equals(ActiveUsersStorage.class.getName())) { // user metadata is app specific and does not have any tenant specific data // JWT storage does not have any user specific data // Active users storage does not have tenant specific data @@ -203,7 +212,8 @@ public void testUserDisassociationForNotAuthRecipes() throws Exception { StorageLayer.getStorage(t2, process.main).addInfoToNonAuthRecipesBasedOnUserId(t2, className, userId); - JsonObject response = TestMultitenancyAPIHelper.disassociateUserFromTenant(t2, userId, process.getProcess()); + JsonObject response = TestMultitenancyAPIHelper.disassociateUserFromTenant(t2, userId, + process.getProcess()); assertEquals("OK", response.getAsJsonPrimitive("status").getAsString()); assertTrue(response.get("wasAssociated").getAsBoolean()); } @@ -216,14 +226,17 @@ public void testDisassociateFromAllTenantsAndThenAssociateWithATenantWorks() thr } createTenants(); - JsonObject user = TestMultitenancyAPIHelper.epSignUp(new TenantIdentifier(null, "a1", "t1"), "user@example.com", "password", process.getProcess()); + JsonObject user = TestMultitenancyAPIHelper.epSignUp(new TenantIdentifier(null, "a1", "t1"), "user@example.com", + "password", process.getProcess()); String userId = user.get("id").getAsString(); - JsonObject response = TestMultitenancyAPIHelper.disassociateUserFromTenant(new TenantIdentifier(null, "a1", "t1"), userId, process.getProcess()); + JsonObject response = TestMultitenancyAPIHelper.disassociateUserFromTenant( + new TenantIdentifier(null, "a1", "t1"), userId, process.getProcess()); assertEquals("OK", response.getAsJsonPrimitive("status").getAsString()); assertTrue(response.get("wasAssociated").getAsBoolean()); - response = TestMultitenancyAPIHelper.associateUserToTenant(new TenantIdentifier(null, "a1", "t2"), userId, process.getProcess()); + response = TestMultitenancyAPIHelper.associateUserToTenant(new TenantIdentifier(null, "a1", "t2"), userId, + process.getProcess()); assertEquals("OK", response.getAsJsonPrimitive("status").getAsString()); assertFalse(response.get("wasAlreadyAssociated").getAsBoolean()); } @@ -239,10 +252,12 @@ public void testAssociateOnDifferentStorageIsNotPossible() throws Exception { } createTenants(); - JsonObject user = TestMultitenancyAPIHelper.epSignUp(new TenantIdentifier(null, "a1", "t1"), "user@example.com", "password", process.getProcess()); + JsonObject user = TestMultitenancyAPIHelper.epSignUp(new TenantIdentifier(null, "a1", "t1"), "user@example.com", + "password", process.getProcess()); String userId = user.get("id").getAsString(); - JsonObject response = TestMultitenancyAPIHelper.associateUserToTenant(new TenantIdentifier(null, "a1", null), userId, process.getProcess()); + JsonObject response = TestMultitenancyAPIHelper.associateUserToTenant(new TenantIdentifier(null, "a1", null), + userId, process.getProcess()); assertEquals("UNKNOWN_USER_ID_ERROR", response.get("status").getAsString()); } @@ -260,7 +275,7 @@ public void testEmailPasswordUsersHaveTenantIds() throws Exception { TenantIdentifierWithStorage t1WithStorage = t1.withStorage(StorageLayer.getStorage(t1, process.getProcess())); TenantIdentifierWithStorage t2WithStorage = t2.withStorage(StorageLayer.getStorage(t2, process.getProcess())); - UserInfo user = EmailPassword.signUp(t1WithStorage, + AuthRecipeUserInfo user = EmailPassword.signUp(t1WithStorage, process.getProcess(), "user@example.com", "password"); assertArrayEquals(new String[]{"t1"}, user.tenantIds); @@ -269,7 +284,7 @@ public void testEmailPasswordUsersHaveTenantIds() throws Exception { Utils.assertArrayEqualsIgnoreOrder(new String[]{"t1", "t2"}, user.tenantIds); - user = EmailPassword.getUserUsingEmail(t1WithStorage, user.email); + user = EmailPassword.getUserUsingEmail(t1WithStorage, user.loginMethods[0].email); Utils.assertArrayEqualsIgnoreOrder(new String[]{"t1", "t2"}, user.tenantIds); Multitenancy.removeUserIdFromTenant(process.getProcess(), t1WithStorage, user.id); @@ -293,7 +308,8 @@ public void testPasswordlessUsersHaveTenantIds1() throws Exception { Passwordless.CreateCodeResponse createCodeResponse = Passwordless.createCode(t1WithStorage, process.getProcess(), "user@example.com", null, null, null); - Passwordless.ConsumeCodeResponse consumeCodeResponse = Passwordless.consumeCode(t1WithStorage, process.getProcess(), + Passwordless.ConsumeCodeResponse consumeCodeResponse = Passwordless.consumeCode(t1WithStorage, + process.getProcess(), createCodeResponse.deviceId, createCodeResponse.deviceIdHash, createCodeResponse.userInputCode, null); assertArrayEquals(new String[]{"t1"}, consumeCodeResponse.user.tenantIds); @@ -326,7 +342,8 @@ public void testPasswordlessUsersHaveTenantIds2() throws Exception { Passwordless.CreateCodeResponse createCodeResponse = Passwordless.createCode(t1WithStorage, process.getProcess(), null, "+919876543210", null, null); - Passwordless.ConsumeCodeResponse consumeCodeResponse = Passwordless.consumeCode(t1WithStorage, process.getProcess(), + Passwordless.ConsumeCodeResponse consumeCodeResponse = Passwordless.consumeCode(t1WithStorage, + process.getProcess(), createCodeResponse.deviceId, createCodeResponse.deviceIdHash, createCodeResponse.userInputCode, null); assertArrayEquals(new String[]{"t1"}, consumeCodeResponse.user.tenantIds); @@ -357,7 +374,8 @@ public void testThirdPartyUsersHaveTenantIds() throws Exception { TenantIdentifierWithStorage t1WithStorage = t1.withStorage(StorageLayer.getStorage(t1, process.getProcess())); TenantIdentifierWithStorage t2WithStorage = t2.withStorage(StorageLayer.getStorage(t2, process.getProcess())); - ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(t1WithStorage, process.getProcess(), "google", + ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(t1WithStorage, process.getProcess(), + "google", "googleid", "user@example.com"); assertArrayEquals(new String[]{"t1"}, signInUpResponse.user.tenantIds); @@ -387,10 +405,12 @@ public void testThatDisassociateUserFromWrongTenantDoesNotWork() throws Exceptio } createTenants(); - JsonObject user = TestMultitenancyAPIHelper.epSignUp(new TenantIdentifier(null, "a1", "t1"), "user@example.com", "password", process.getProcess()); + JsonObject user = TestMultitenancyAPIHelper.epSignUp(new TenantIdentifier(null, "a1", "t1"), "user@example.com", + "password", process.getProcess()); String userId = user.get("id").getAsString(); - JsonObject response = TestMultitenancyAPIHelper.disassociateUserFromTenant(new TenantIdentifier(null, "a1", "t2"), userId, process.getProcess()); + JsonObject response = TestMultitenancyAPIHelper.disassociateUserFromTenant( + new TenantIdentifier(null, "a1", "t2"), userId, process.getProcess()); assertEquals("OK", response.getAsJsonPrimitive("status").getAsString()); assertFalse(response.get("wasAssociated").getAsBoolean()); } @@ -402,12 +422,15 @@ public void testThatDisassociateUserWithUseridMappingFromWrongTenantDoesNotWork( } createTenants(); - JsonObject user = TestMultitenancyAPIHelper.epSignUp(new TenantIdentifier(null, "a1", "t1"), "user@example.com", "password", process.getProcess()); + JsonObject user = TestMultitenancyAPIHelper.epSignUp(new TenantIdentifier(null, "a1", "t1"), "user@example.com", + "password", process.getProcess()); String userId = user.get("id").getAsString(); - TestMultitenancyAPIHelper.createUserIdMapping(new TenantIdentifier(null, "a1", "t1"), userId, "externalid", process.getProcess()); + TestMultitenancyAPIHelper.createUserIdMapping(new TenantIdentifier(null, "a1", "t1"), userId, "externalid", + process.getProcess()); - JsonObject response = TestMultitenancyAPIHelper.disassociateUserFromTenant(new TenantIdentifier(null, "a1", "t2"), "externalid", process.getProcess()); + JsonObject response = TestMultitenancyAPIHelper.disassociateUserFromTenant( + new TenantIdentifier(null, "a1", "t2"), "externalid", process.getProcess()); assertEquals("OK", response.getAsJsonPrimitive("status").getAsString()); assertFalse(response.get("wasAssociated").getAsBoolean()); } @@ -419,16 +442,20 @@ public void testAssociateAndDisassociateWithUseridMapping() throws Exception { } createTenants(); - JsonObject user = TestMultitenancyAPIHelper.epSignUp(new TenantIdentifier(null, "a1", "t1"), "user@example.com", "password", process.getProcess()); + JsonObject user = TestMultitenancyAPIHelper.epSignUp(new TenantIdentifier(null, "a1", "t1"), "user@example.com", + "password", process.getProcess()); String userId = user.get("id").getAsString(); - TestMultitenancyAPIHelper.createUserIdMapping(new TenantIdentifier(null, "a1", "t1"), userId, "externalid", process.getProcess()); + TestMultitenancyAPIHelper.createUserIdMapping(new TenantIdentifier(null, "a1", "t1"), userId, "externalid", + process.getProcess()); - JsonObject response = TestMultitenancyAPIHelper.associateUserToTenant(new TenantIdentifier(null, "a1", "t2"), "externalid", process.getProcess()); + JsonObject response = TestMultitenancyAPIHelper.associateUserToTenant(new TenantIdentifier(null, "a1", "t2"), + "externalid", process.getProcess()); assertEquals("OK", response.getAsJsonPrimitive("status").getAsString()); assertFalse(response.get("wasAlreadyAssociated").getAsBoolean()); - response = TestMultitenancyAPIHelper.disassociateUserFromTenant(new TenantIdentifier(null, "a1", "t2"), "externalid", process.getProcess()); + response = TestMultitenancyAPIHelper.disassociateUserFromTenant(new TenantIdentifier(null, "a1", "t2"), + "externalid", process.getProcess()); assertEquals("OK", response.getAsJsonPrimitive("status").getAsString()); assertTrue(response.get("wasAssociated").getAsBoolean()); diff --git a/src/test/java/io/supertokens/test/userIdMapping/recipe/EmailPasswordAPITest.java b/src/test/java/io/supertokens/test/userIdMapping/recipe/EmailPasswordAPITest.java index fd6342e1f..0237eb447 100644 --- a/src/test/java/io/supertokens/test/userIdMapping/recipe/EmailPasswordAPITest.java +++ b/src/test/java/io/supertokens/test/userIdMapping/recipe/EmailPasswordAPITest.java @@ -21,13 +21,12 @@ import io.supertokens.authRecipe.AuthRecipe; import io.supertokens.emailpassword.EmailPassword; import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.emailpassword.UserInfo; -import io.supertokens.pluginInterface.useridmapping.UserIdMappingStorage; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.TestingProcessManager; import io.supertokens.test.Utils; import io.supertokens.test.httpRequest.HttpRequestForTesting; -import io.supertokens.test.httpRequest.HttpResponseException; import io.supertokens.useridmapping.UserIdMapping; import io.supertokens.useridmapping.UserIdType; import io.supertokens.utils.SemVer; @@ -57,7 +56,7 @@ public void beforeEach() { @Test public void testSignInAPI() throws Exception { - String[] args = { "../" }; + String[] args = {"../"}; TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); @@ -112,7 +111,7 @@ public void testSignInAPI() throws Exception { @Test public void testResetPasswordFlowWithUserIdMapping() throws Exception { - String[] args = { "../" }; + String[] args = {"../"}; TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); @@ -162,7 +161,7 @@ public void testResetPasswordFlowWithUserIdMapping() throws Exception { } // sign in with the new password and check that it works - UserInfo userInfo1 = EmailPassword.signIn(process.main, email, newPassword); + AuthRecipeUserInfo userInfo1 = EmailPassword.signIn(process.main, email, newPassword); assertNotNull(userInfo1); process.kill(); @@ -171,7 +170,7 @@ public void testResetPasswordFlowWithUserIdMapping() throws Exception { @Test public void testRetrievingUser() throws Exception { - String[] args = { "../" }; + String[] args = {"../"}; TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); @@ -217,7 +216,7 @@ public void testRetrievingUser() throws Exception { @Test public void testUpdatingUsersEmail() throws Exception { - String[] args = { "../" }; + String[] args = {"../"}; TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); @@ -250,7 +249,7 @@ public void testUpdatingUsersEmail() throws Exception { } // check that you can now sign in with the new email - UserInfo userInfo1 = EmailPassword.signIn(process.main, newEmail, password); + AuthRecipeUserInfo userInfo1 = EmailPassword.signIn(process.main, newEmail, password); assertNotNull(userInfo1); process.kill(); From f9d5f9b501d9530bf76822ea397c76509cf14196 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Wed, 12 Jul 2023 13:59:04 +0530 Subject: [PATCH 014/131] changes based on interface --- .../java/io/supertokens/inmemorydb/Start.java | 11 ---- .../queries/EmailPasswordQueries.java | 22 ++++---- .../inmemorydb/queries/GeneralQueries.java | 5 ++ .../queries/PasswordlessQueries.java | 42 +++++--------- .../passwordless/Passwordless.java | 55 ++++++++++++++----- .../webserver/api/passwordless/UserAPI.java | 4 +- .../api/TestTenantUserAssociation.java | 6 +- .../PasswordlessConsumeCodeTest.java | 4 +- .../passwordless/PasswordlessGetUserTest.java | 23 ++++---- .../passwordless/PasswordlessStorageTest.java | 6 +- .../PasswordlessUpdateUserTest.java | 41 ++++++++------ .../api/PasswordlessUserPutAPITest2_11.java | 42 +++++++------- .../recipe/PasswordlessAPITest.java | 14 ++--- 13 files changed, 142 insertions(+), 133 deletions(-) diff --git a/src/main/java/io/supertokens/inmemorydb/Start.java b/src/main/java/io/supertokens/inmemorydb/Start.java index 5be90b514..d0ec25d25 100644 --- a/src/main/java/io/supertokens/inmemorydb/Start.java +++ b/src/main/java/io/supertokens/inmemorydb/Start.java @@ -1776,17 +1776,6 @@ public PasswordlessCode getCodeByLinkCodeHash(TenantIdentifier tenantIdentifier, } } - @Override - public io.supertokens.pluginInterface.passwordless.UserInfo getUserByEmail(TenantIdentifier tenantIdentifier, - String email) - throws StorageQueryException { - try { - return PasswordlessQueries.getUserByEmail(this, tenantIdentifier, email); - } catch (SQLException e) { - throw new StorageQueryException(e); - } - } - @Override public io.supertokens.pluginInterface.passwordless.UserInfo getUserByPhoneNumber(TenantIdentifier tenantIdentifier, String phoneNumber) diff --git a/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java index 9304a0d88..162aedb05 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java @@ -404,18 +404,16 @@ public static String getUserIdUsingEmail(Start start, TenantIdentifier tenantIde + "FROM " + getConfig(start).getEmailPasswordUserToTenantTable() + " WHERE app_id = ? AND tenant_id = ? AND email = ?"; - try (Connection con = ConnectionPool.getConnection(start)) { - return execute(con, QUERY, pst -> { - pst.setString(1, tenantIdentifier.getAppId()); - pst.setString(2, tenantIdentifier.getTenantId()); - pst.setString(3, email); - }, result -> { - if (result.next()) { - return result.getString("user_id"); - } - return null; - }); - } + return execute(start, QUERY, pst -> { + pst.setString(1, tenantIdentifier.getAppId()); + pst.setString(2, tenantIdentifier.getTenantId()); + pst.setString(3, email); + }, result -> { + if (result.next()) { + return result.getString("user_id"); + } + return null; + }); } public static boolean addUserIdToTenant_Transaction(Start start, Connection sqlCon, diff --git a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java index 2f671dd5c..9c35ac288 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java @@ -863,6 +863,11 @@ public static AuthRecipeUserInfo[] listPrimaryUsersByEmail(Start start, TenantId userIds.add(emailPasswordUserId); } + String passwordlessUserId = PasswordlessQueries.getUserIdUsingEmail(start, tenantIdentifier, email); + if (passwordlessUserId != null) { + userIds.add(passwordlessUserId); + } + // TODO:add support for other recipes. List result = getPrimaryUserInfoForUserIds(start, tenantIdentifier.toAppIdentifier(), diff --git a/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java index a3239413e..1044f7725 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java @@ -730,38 +730,22 @@ public static UserInfoPartial getUserById(Start start, Connection sqlCon, AppIde }); } - public static UserInfo getUserByEmail(Start start, TenantIdentifier tenantIdentifier, @Nonnull String email) + public static String getUserIdUsingEmail(Start start, TenantIdentifier tenantIdentifier, String email) throws StorageQueryException, SQLException { - String QUERY = "SELECT pl_users.user_id as user_id, pl_users.email as email, " - + "pl_users.phone_number as phone_number, pl_users.time_joined as time_joined " - + "FROM " + getConfig(start).getPasswordlessUserToTenantTable() + " AS pl_users_to_tenant " - + "JOIN " + getConfig(start).getPasswordlessUsersTable() + " AS pl_users " - + "ON pl_users.app_id = pl_users_to_tenant.app_id AND pl_users.user_id = pl_users_to_tenant.user_id " - + - "WHERE pl_users_to_tenant.app_id = ? AND pl_users_to_tenant.tenant_id = ? AND pl_users_to_tenant" + - ".email = ? "; + String QUERY = "SELECT user_id " + + "FROM " + getConfig(start).getPasswordlessUserToTenantTable() + + " WHERE app_id = ? AND tenant_id = ? AND email = ?"; - try (Connection con = ConnectionPool.getConnection(start)) { - UserInfoPartial userInfo = execute(con, QUERY, pst -> { - pst.setString(1, tenantIdentifier.getAppId()); - pst.setString(2, tenantIdentifier.getTenantId()); - pst.setString(3, email); - }, result -> { - if (result.next()) { - return UserInfoRowMapper.getInstance().mapOrThrow(result); - } - return null; - }); - if (userInfo == null) { - return null; + return execute(start, QUERY, pst -> { + pst.setString(1, tenantIdentifier.getAppId()); + pst.setString(2, tenantIdentifier.getTenantId()); + pst.setString(3, email); + }, result -> { + if (result.next()) { + return result.getString("user_id"); } - fillUserInfoWithTenantIds_transaction(start, con, tenantIdentifier.toAppIdentifier(), userInfo); - fillUserInfoWithVerified_transaction(start, con, tenantIdentifier.toAppIdentifier(), userInfo); - fillUserInfoWithIsPrimaryUserBoolean_transaction(start, con, tenantIdentifier.toAppIdentifier(), - userInfo); - return new UserInfo(userInfo.id, userInfo.isPrimary, - userInfo.toLoginMethod()); - } + return null; + }); } public static UserInfo getUserByPhoneNumber(Start start, TenantIdentifier tenantIdentifier, diff --git a/src/main/java/io/supertokens/passwordless/Passwordless.java b/src/main/java/io/supertokens/passwordless/Passwordless.java index 583dd2ee7..df5928492 100644 --- a/src/main/java/io/supertokens/passwordless/Passwordless.java +++ b/src/main/java/io/supertokens/passwordless/Passwordless.java @@ -21,6 +21,7 @@ import io.supertokens.multitenancy.Multitenancy; import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.passwordless.exceptions.*; +import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.authRecipe.LoginMethod; @@ -376,10 +377,28 @@ public static ConsumeCodeResponse consumeCode(TenantIdentifierWithStorage tenant } // Getting here means that we successfully consumed the code - UserInfo user = consumedDevice.email != null ? - passwordlessStorage.getUserByEmail(tenantIdentifierWithStorage, consumedDevice.email) - : passwordlessStorage.getUserByPhoneNumber(tenantIdentifierWithStorage, consumedDevice.phoneNumber); - if (user == null) { + AuthRecipeUserInfo user = null; + LoginMethod loginMethod = null; + if (consumedDevice.email != null) { + AuthRecipeUserInfo[] users = passwordlessStorage.listPrimaryUsersByEmail(tenantIdentifierWithStorage, + consumedDevice.email); + for (AuthRecipeUserInfo currUser : users) { + for (LoginMethod currLM : currUser.loginMethods) { + if (currLM.recipeId == RECIPE_ID.PASSWORDLESS && currLM.email.equals(consumedDevice.email)) { + user = currUser; + loginMethod = currLM; + break; + } + } + } + } else { + user = passwordlessStorage.getUserByPhoneNumber(tenantIdentifierWithStorage, consumedDevice.phoneNumber); + if (user != null) { + loginMethod = user.loginMethods[0]; + } + } + + if (user == null || loginMethod == null) { while (true) { try { String userId = Utils.getUUID(); @@ -402,11 +421,11 @@ public static ConsumeCodeResponse consumeCode(TenantIdentifierWithStorage tenant } else { // We do not need this cleanup if we are creating the user, since it uses the email/phoneNumber of the // device, which has already been cleaned up - if (user.email != null && !user.email.equals(consumedDevice.email)) { - removeCodesByEmail(tenantIdentifierWithStorage, user.email); + if (loginMethod.email != null && !loginMethod.email.equals(consumedDevice.email)) { + removeCodesByEmail(tenantIdentifierWithStorage, loginMethod.email); } - if (user.phoneNumber != null && !user.phoneNumber.equals(consumedDevice.phoneNumber)) { - removeCodesByPhoneNumber(tenantIdentifierWithStorage, user.phoneNumber); + if (loginMethod.phoneNumber != null && !loginMethod.phoneNumber.equals(consumedDevice.phoneNumber)) { + removeCodesByPhoneNumber(tenantIdentifierWithStorage, loginMethod.phoneNumber); } } return new ConsumeCodeResponse(false, user); @@ -535,16 +554,26 @@ public static UserInfo getUserByPhoneNumber(TenantIdentifierWithStorage tenantId } @TestOnly - public static UserInfo getUserByEmail(Main main, String email) + public static AuthRecipeUserInfo getUserByEmail(Main main, String email) throws StorageQueryException { Storage storage = StorageLayer.getStorage(main); return getUserByEmail( new TenantIdentifierWithStorage(null, null, null, storage), email); } - public static UserInfo getUserByEmail(TenantIdentifierWithStorage tenantIdentifierWithStorage, String email) + public static AuthRecipeUserInfo getUserByEmail(TenantIdentifierWithStorage tenantIdentifierWithStorage, + String email) throws StorageQueryException { - return tenantIdentifierWithStorage.getPasswordlessStorage().getUserByEmail(tenantIdentifierWithStorage, email); + AuthRecipeUserInfo[] users = tenantIdentifierWithStorage.getPasswordlessStorage() + .listPrimaryUsersByEmail(tenantIdentifierWithStorage, email); + for (AuthRecipeUserInfo user : users) { + for (LoginMethod lM : user.loginMethods) { + if (lM.recipeId == RECIPE_ID.PASSWORDLESS && lM.email.equals(email)) { + return user; + } + } + } + return null; } @TestOnly @@ -670,9 +699,9 @@ public CreateCodeResponse(String deviceIdHash, String codeId, String deviceId, S public static class ConsumeCodeResponse { public boolean createdNewUser; - public UserInfo user; + public AuthRecipeUserInfo user; - public ConsumeCodeResponse(boolean createdNewUser, UserInfo user) { + public ConsumeCodeResponse(boolean createdNewUser, AuthRecipeUserInfo user) { this.createdNewUser = createdNewUser; this.user = user; } diff --git a/src/main/java/io/supertokens/webserver/api/passwordless/UserAPI.java b/src/main/java/io/supertokens/webserver/api/passwordless/UserAPI.java index fb488a8ec..8966b0ba4 100644 --- a/src/main/java/io/supertokens/webserver/api/passwordless/UserAPI.java +++ b/src/main/java/io/supertokens/webserver/api/passwordless/UserAPI.java @@ -23,11 +23,11 @@ import io.supertokens.passwordless.Passwordless.FieldUpdate; import io.supertokens.passwordless.exceptions.UserWithoutContactInfoException; import io.supertokens.pluginInterface.RECIPE_ID; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; -import io.supertokens.pluginInterface.passwordless.UserInfo; import io.supertokens.pluginInterface.passwordless.exception.DuplicatePhoneNumberException; import io.supertokens.pluginInterface.useridmapping.UserIdMapping; import io.supertokens.useridmapping.UserIdType; @@ -71,7 +71,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO } try { - UserInfo user; + AuthRecipeUserInfo user; if (userId != null) { try { AppIdentifierWithStorageAndUserIdMapping appIdentifierWithStorageAndUserIdMapping = diff --git a/src/test/java/io/supertokens/test/multitenant/api/TestTenantUserAssociation.java b/src/test/java/io/supertokens/test/multitenant/api/TestTenantUserAssociation.java index 6effceba1..bd8f29341 100644 --- a/src/test/java/io/supertokens/test/multitenant/api/TestTenantUserAssociation.java +++ b/src/test/java/io/supertokens/test/multitenant/api/TestTenantUserAssociation.java @@ -313,12 +313,12 @@ public void testPasswordlessUsersHaveTenantIds1() throws Exception { createCodeResponse.deviceId, createCodeResponse.deviceIdHash, createCodeResponse.userInputCode, null); assertArrayEquals(new String[]{"t1"}, consumeCodeResponse.user.tenantIds); - io.supertokens.pluginInterface.passwordless.UserInfo user; + AuthRecipeUserInfo user; Multitenancy.addUserIdToTenant(process.getProcess(), t2WithStorage, consumeCodeResponse.user.id); user = Passwordless.getUserById(t1WithStorage.toAppIdentifierWithStorage(), consumeCodeResponse.user.id); Utils.assertArrayEqualsIgnoreOrder(new String[]{"t1", "t2"}, user.tenantIds); - user = Passwordless.getUserByEmail(t1WithStorage, consumeCodeResponse.user.email); + user = Passwordless.getUserByEmail(t1WithStorage, consumeCodeResponse.user.loginMethods[0].email); Utils.assertArrayEqualsIgnoreOrder(new String[]{"t1", "t2"}, user.tenantIds); Multitenancy.removeUserIdFromTenant(process.getProcess(), t1WithStorage, consumeCodeResponse.user.id); @@ -352,7 +352,7 @@ public void testPasswordlessUsersHaveTenantIds2() throws Exception { user = Passwordless.getUserById(t1WithStorage.toAppIdentifierWithStorage(), consumeCodeResponse.user.id); Utils.assertArrayEqualsIgnoreOrder(new String[]{"t1", "t2"}, user.tenantIds); - user = Passwordless.getUserByPhoneNumber(t1WithStorage, consumeCodeResponse.user.phoneNumber); + user = Passwordless.getUserByPhoneNumber(t1WithStorage, consumeCodeResponse.user.loginMethods[0].phoneNumber); Utils.assertArrayEqualsIgnoreOrder(new String[]{"t1", "t2"}, user.tenantIds); Multitenancy.removeUserIdFromTenant(process.getProcess(), t1WithStorage, consumeCodeResponse.user.id); diff --git a/src/test/java/io/supertokens/test/passwordless/PasswordlessConsumeCodeTest.java b/src/test/java/io/supertokens/test/passwordless/PasswordlessConsumeCodeTest.java index a1390d96f..b4f349b28 100644 --- a/src/test/java/io/supertokens/test/passwordless/PasswordlessConsumeCodeTest.java +++ b/src/test/java/io/supertokens/test/passwordless/PasswordlessConsumeCodeTest.java @@ -826,11 +826,11 @@ private AuthRecipeUserInfo checkUserWithConsumeResponse(PasswordlessStorage stor AuthRecipeUserInfo user = storage.getPrimaryUserById(new AppIdentifier(null, null), resp.user.id); assertNotNull(user); - assertEquals(email, resp.user.email); + assertEquals(email, resp.user.loginMethods[0].email); assertEquals(email, user.loginMethods[0].email); assertEquals(phoneNumber, user.loginMethods[0].phoneNumber); - assertEquals(phoneNumber, resp.user.phoneNumber); + assertEquals(phoneNumber, resp.user.loginMethods[0].phoneNumber); assert (user.timeJoined >= joinedAfter); assertEquals(user.timeJoined, resp.user.timeJoined); diff --git a/src/test/java/io/supertokens/test/passwordless/PasswordlessGetUserTest.java b/src/test/java/io/supertokens/test/passwordless/PasswordlessGetUserTest.java index 8eee9f814..b1f3efc06 100644 --- a/src/test/java/io/supertokens/test/passwordless/PasswordlessGetUserTest.java +++ b/src/test/java/io/supertokens/test/passwordless/PasswordlessGetUserTest.java @@ -19,6 +19,7 @@ import io.supertokens.ProcessState; import io.supertokens.passwordless.Passwordless; import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.passwordless.UserInfo; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.TestingProcessManager; @@ -59,7 +60,7 @@ public void beforeEach() { */ @Test public void getUserByIdWithEmail() throws Exception { - String[] args = { "../" }; + String[] args = {"../"}; TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); @@ -86,7 +87,7 @@ public void getUserByIdWithEmail() throws Exception { */ @Test public void getUserByIdWithPhoneNumber() throws Exception { - String[] args = { "../" }; + String[] args = {"../"}; TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); @@ -113,7 +114,7 @@ public void getUserByIdWithPhoneNumber() throws Exception { */ @Test public void getUserByIdWithEmailAndPhoneNumber() throws Exception { - String[] args = { "../" }; + String[] args = {"../"}; TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); @@ -141,7 +142,7 @@ public void getUserByIdWithEmailAndPhoneNumber() throws Exception { */ @Test public void getUserByInvalidId() throws Exception { - String[] args = { "../" }; + String[] args = {"../"}; TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); @@ -167,7 +168,7 @@ public void getUserByInvalidId() throws Exception { */ @Test public void getUserByEmail() throws Exception { - String[] args = { "../" }; + String[] args = {"../"}; TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); @@ -178,9 +179,9 @@ public void getUserByEmail() throws Exception { createUserWith(process, EMAIL, null); - UserInfo user = Passwordless.getUserByEmail(process.getProcess(), EMAIL); + AuthRecipeUserInfo user = Passwordless.getUserByEmail(process.getProcess(), EMAIL); assertNotNull(user); - assertEquals(user.email, EMAIL); + assertEquals(user.loginMethods[0].email, EMAIL); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -194,7 +195,7 @@ public void getUserByEmail() throws Exception { */ @Test public void getUserByInvalidEmail() throws Exception { - String[] args = { "../" }; + String[] args = {"../"}; TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); @@ -205,7 +206,7 @@ public void getUserByInvalidEmail() throws Exception { createUserWith(process, EMAIL, null); - UserInfo user = Passwordless.getUserByEmail(process.getProcess(), EMAIL + "a"); + AuthRecipeUserInfo user = Passwordless.getUserByEmail(process.getProcess(), EMAIL + "a"); assertNull(user); process.kill(); @@ -220,7 +221,7 @@ public void getUserByInvalidEmail() throws Exception { */ @Test public void getUserByPhoneNumber() throws Exception { - String[] args = { "../" }; + String[] args = {"../"}; TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); @@ -246,7 +247,7 @@ public void getUserByPhoneNumber() throws Exception { */ @Test public void getUserByInvalidPhoneNumber() throws Exception { - String[] args = { "../" }; + String[] args = {"../"}; TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); diff --git a/src/test/java/io/supertokens/test/passwordless/PasswordlessStorageTest.java b/src/test/java/io/supertokens/test/passwordless/PasswordlessStorageTest.java index 5e3272a28..d888134c6 100644 --- a/src/test/java/io/supertokens/test/passwordless/PasswordlessStorageTest.java +++ b/src/test/java/io/supertokens/test/passwordless/PasswordlessStorageTest.java @@ -271,7 +271,7 @@ public void testCreateUserExceptions() throws Exception { assertNotNull(error); assert (error instanceof DuplicateUserIdException); - assertNull(storage.getUserByEmail(new TenantIdentifier(null, null, null), email2)); + assertEquals(0, storage.listPrimaryUsersByEmail(new TenantIdentifier(null, null, null), email2).length); } { @@ -896,8 +896,8 @@ private void checkUser(PasswordlessSQLStorage storage, String userId, String ema assertEquals(email, userById.loginMethods[0].email); assertEquals(phoneNumber, userById.loginMethods[0].phoneNumber); if (email != null) { - UserInfo user = storage.getUserByEmail(new TenantIdentifier(null, null, null), email); - assert (user.equals(userById)); + AuthRecipeUserInfo[] user = storage.listPrimaryUsersByEmail(new TenantIdentifier(null, null, null), email); + assert (user.length == 1 && user[0].equals(userById)); } if (phoneNumber != null) { UserInfo user = storage.getUserByPhoneNumber(new TenantIdentifier(null, null, null), phoneNumber); diff --git a/src/test/java/io/supertokens/test/passwordless/PasswordlessUpdateUserTest.java b/src/test/java/io/supertokens/test/passwordless/PasswordlessUpdateUserTest.java index c551919a4..92e09b76a 100644 --- a/src/test/java/io/supertokens/test/passwordless/PasswordlessUpdateUserTest.java +++ b/src/test/java/io/supertokens/test/passwordless/PasswordlessUpdateUserTest.java @@ -20,6 +20,7 @@ import io.supertokens.passwordless.Passwordless; import io.supertokens.passwordless.exceptions.UserWithoutContactInfoException; import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; @@ -78,15 +79,17 @@ public void updateEmailToAnExistingOne() throws Exception { createUserWith(process, EMAIL, null); createUserWith(process, alternate_email, null); - UserInfo user = storage.getUserByEmail(new TenantIdentifier(null, null, null), EMAIL); - assertNotNull(user); + AuthRecipeUserInfo[] user = storage.listPrimaryUsersByEmail(new TenantIdentifier(null, null, null), EMAIL); + assert (user.length == 1); - UserInfo user_two = storage.getUserByEmail(new TenantIdentifier(null, null, null), alternate_email); - assertNotNull(user_two); + AuthRecipeUserInfo[] user_two = storage.listPrimaryUsersByEmail(new TenantIdentifier(null, null, null), + alternate_email); + assert (user_two.length == 1); Exception ex = null; try { - Passwordless.updateUser(process.getProcess(), user.id, new Passwordless.FieldUpdate(alternate_email), null); + Passwordless.updateUser(process.getProcess(), user[0].id, new Passwordless.FieldUpdate(alternate_email), + null); } catch (Exception e) { ex = e; } @@ -94,7 +97,9 @@ public void updateEmailToAnExistingOne() throws Exception { assertNotNull(ex); assert (ex instanceof DuplicateEmailException); - assertEquals(EMAIL, storage.getUserByEmail(new TenantIdentifier(null, null, null), EMAIL).email); + assertEquals(EMAIL, + storage.listPrimaryUsersByEmail(new TenantIdentifier(null, null, null), + EMAIL)[0].loginMethods[0].email); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -167,13 +172,13 @@ public void updateEmail() throws Exception { createUserWith(process, EMAIL, null); - UserInfo user = storage.getUserByEmail(new TenantIdentifier(null, null, null), EMAIL); - assertNotNull(user); + AuthRecipeUserInfo[] user = storage.listPrimaryUsersByEmail(new TenantIdentifier(null, null, null), EMAIL); + assert (user.length == 1); - Passwordless.updateUser(process.getProcess(), user.id, new Passwordless.FieldUpdate(alternate_email), null); + Passwordless.updateUser(process.getProcess(), user[0].id, new Passwordless.FieldUpdate(alternate_email), null); assertEquals(alternate_email, - storage.getPrimaryUserById(new AppIdentifier(null, null), user.id).loginMethods[0].email); + storage.getPrimaryUserById(new AppIdentifier(null, null), user[0].id).loginMethods[0].email); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -235,15 +240,15 @@ public void clearEmailSetPhoneNumber() throws Exception { createUserWith(process, EMAIL, null); - UserInfo user = storage.getUserByEmail(new TenantIdentifier(null, null, null), EMAIL); - assertNotNull(user); + AuthRecipeUserInfo[] user = storage.listPrimaryUsersByEmail(new TenantIdentifier(null, null, null), EMAIL); + assert (user.length == 1); - Passwordless.updateUser(process.getProcess(), user.id, new Passwordless.FieldUpdate(null), + Passwordless.updateUser(process.getProcess(), user[0].id, new Passwordless.FieldUpdate(null), new Passwordless.FieldUpdate(PHONE_NUMBER)); assertEquals(PHONE_NUMBER, - storage.getPrimaryUserById(new AppIdentifier(null, null), user.id).loginMethods[0].phoneNumber); - assertNull(storage.getPrimaryUserById(new AppIdentifier(null, null), user.id).loginMethods[0].email); + storage.getPrimaryUserById(new AppIdentifier(null, null), user[0].id).loginMethods[0].phoneNumber); + assertNull(storage.getPrimaryUserById(new AppIdentifier(null, null), user[0].id).loginMethods[0].email); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -343,13 +348,13 @@ public void clearEmailOfEmailOnlyUser() throws Exception { createUserWith(process, EMAIL, null); - UserInfo user = storage.getUserByEmail(new TenantIdentifier(null, null, null), EMAIL); - assertNotNull(user); + AuthRecipeUserInfo[] user = storage.listPrimaryUsersByEmail(new TenantIdentifier(null, null, null), EMAIL); + assert (user.length == 1); Exception ex = null; try { - Passwordless.updateUser(process.getProcess(), user.id, new Passwordless.FieldUpdate(null), null); + Passwordless.updateUser(process.getProcess(), user[0].id, new Passwordless.FieldUpdate(null), null); } catch (Exception e) { ex = e; } diff --git a/src/test/java/io/supertokens/test/passwordless/api/PasswordlessUserPutAPITest2_11.java b/src/test/java/io/supertokens/test/passwordless/api/PasswordlessUserPutAPITest2_11.java index 764257b13..bb8d4b549 100644 --- a/src/test/java/io/supertokens/test/passwordless/api/PasswordlessUserPutAPITest2_11.java +++ b/src/test/java/io/supertokens/test/passwordless/api/PasswordlessUserPutAPITest2_11.java @@ -52,7 +52,7 @@ public void beforeEach() { @Test public void testBadInput() throws Exception { - String[] args = { "../" }; + String[] args = {"../"}; TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); @@ -136,7 +136,7 @@ public void testBadInput() throws Exception { @Test public void testEmailToPhone() throws Exception { - String[] args = { "../" }; + String[] args = {"../"}; TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); @@ -164,7 +164,7 @@ public void testEmailToPhone() throws Exception { assertEquals("OK", response.get("status").getAsString()); - assertNull(storage.getUserByEmail(new TenantIdentifier(null, null, null), email)); + assert (storage.listPrimaryUsersByEmail(new TenantIdentifier(null, null, null), email).length == 0); assertNotNull(storage.getUserByPhoneNumber(new TenantIdentifier(null, null, null), phoneNumber)); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -177,7 +177,7 @@ public void testEmailToPhone() throws Exception { */ @Test public void testPhoneToEmail() throws Exception { - String[] args = { "../" }; + String[] args = {"../"}; TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); @@ -205,7 +205,7 @@ public void testPhoneToEmail() throws Exception { assertEquals("OK", response.get("status").getAsString()); - assertNotNull(storage.getUserByEmail(new TenantIdentifier(null, null, null), email)); + assert (storage.listPrimaryUsersByEmail(new TenantIdentifier(null, null, null), email).length == 1); assertNull(storage.getUserByPhoneNumber(new TenantIdentifier(null, null, null), phoneNumber)); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -218,7 +218,7 @@ public void testPhoneToEmail() throws Exception { */ @Test public void testPhoneAndEmail() throws Exception { - String[] args = { "../" }; + String[] args = {"../"}; TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); @@ -248,10 +248,10 @@ public void testPhoneAndEmail() throws Exception { assertEquals("OK", response.get("status").getAsString()); - assertNull(storage.getUserByEmail(new TenantIdentifier(null, null, null), email)); + assert (storage.listPrimaryUsersByEmail(new TenantIdentifier(null, null, null), email).length == 0); assertNull(storage.getUserByPhoneNumber(new TenantIdentifier(null, null, null), phoneNumber)); - assertNotNull(storage.getUserByEmail(new TenantIdentifier(null, null, null), updatedEmail)); + assert (storage.listPrimaryUsersByEmail(new TenantIdentifier(null, null, null), updatedEmail).length == 1); assertNotNull(storage.getUserByPhoneNumber(new TenantIdentifier(null, null, null), updatedPhoneNumber)); process.kill(); @@ -265,7 +265,7 @@ public void testPhoneAndEmail() throws Exception { */ @Test public void clearEmailAndPhone() throws Exception { - String[] args = { "../" }; + String[] args = {"../"}; TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); @@ -314,7 +314,7 @@ public void clearEmailAndPhone() throws Exception { */ @Test public void clearEmailOfEmailOnlyUser() throws Exception { - String[] args = { "../" }; + String[] args = {"../"}; TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); @@ -362,7 +362,7 @@ public void clearEmailOfEmailOnlyUser() throws Exception { */ @Test public void clearPhoneNUmberOfPhoneNumberOnlyUser() throws Exception { - String[] args = { "../" }; + String[] args = {"../"}; TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); @@ -409,7 +409,7 @@ public void clearPhoneNUmberOfPhoneNumberOnlyUser() throws Exception { */ @Test public void clearEmail() throws Exception { - String[] args = { "../" }; + String[] args = {"../"}; TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); @@ -436,7 +436,7 @@ public void clearEmail() throws Exception { assertEquals("OK", response.get("status").getAsString()); - assertNull(storage.getUserByEmail(new TenantIdentifier(null, null, null), email)); + assert (storage.listPrimaryUsersByEmail(new TenantIdentifier(null, null, null), email).length == 0); assertNotNull(storage.getUserByPhoneNumber(new TenantIdentifier(null, null, null), phoneNumber)); process.kill(); @@ -450,7 +450,7 @@ public void clearEmail() throws Exception { */ @Test public void clearPhone() throws Exception { - String[] args = { "../" }; + String[] args = {"../"}; TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); @@ -477,7 +477,7 @@ public void clearPhone() throws Exception { assertEquals("OK", response.get("status").getAsString()); - assertNotNull(storage.getUserByEmail(new TenantIdentifier(null, null, null), email)); + assert (storage.listPrimaryUsersByEmail(new TenantIdentifier(null, null, null), email).length == 1); assertNull(storage.getUserByPhoneNumber(new TenantIdentifier(null, null, null), phoneNumber)); process.kill(); @@ -491,7 +491,7 @@ public void clearPhone() throws Exception { */ @Test public void updateNothing() throws Exception { - String[] args = { "../" }; + String[] args = {"../"}; TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); @@ -517,7 +517,7 @@ public void updateNothing() throws Exception { assertEquals("OK", response.get("status").getAsString()); - assertNotNull(storage.getUserByEmail(new TenantIdentifier(null, null, null), email)); + assert (storage.listPrimaryUsersByEmail(new TenantIdentifier(null, null, null), email).length == 1); assertNotNull(storage.getUserByPhoneNumber(new TenantIdentifier(null, null, null), phoneNumber)); process.kill(); @@ -531,7 +531,7 @@ public void updateNothing() throws Exception { */ @Test public void testUpdateEmail() throws Exception { - String[] args = { "../" }; + String[] args = {"../"}; TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); @@ -558,8 +558,8 @@ public void testUpdateEmail() throws Exception { assertEquals("OK", response.get("status").getAsString()); - assertNotNull(storage.getUserByEmail(new TenantIdentifier(null, null, null), updated_email)); - assertNull(storage.getUserByEmail(new TenantIdentifier(null, null, null), email)); + assert (storage.listPrimaryUsersByEmail(new TenantIdentifier(null, null, null), updated_email).length == 1); + assert (storage.listPrimaryUsersByEmail(new TenantIdentifier(null, null, null), email).length == 0); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -572,7 +572,7 @@ public void testUpdateEmail() throws Exception { */ @Test public void testUpdatePhoneNumber() throws Exception { - String[] args = { "../" }; + String[] args = {"../"}; TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); diff --git a/src/test/java/io/supertokens/test/userIdMapping/recipe/PasswordlessAPITest.java b/src/test/java/io/supertokens/test/userIdMapping/recipe/PasswordlessAPITest.java index efdd23d1d..0bbffcf2a 100644 --- a/src/test/java/io/supertokens/test/userIdMapping/recipe/PasswordlessAPITest.java +++ b/src/test/java/io/supertokens/test/userIdMapping/recipe/PasswordlessAPITest.java @@ -16,14 +16,12 @@ package io.supertokens.test.userIdMapping.recipe; -import com.google.gson.JsonArray; -import com.google.gson.JsonNull; import com.google.gson.JsonObject; import io.supertokens.ProcessState; import io.supertokens.authRecipe.AuthRecipe; import io.supertokens.passwordless.Passwordless; import io.supertokens.pluginInterface.STORAGE_TYPE; -import io.supertokens.pluginInterface.passwordless.UserInfo; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.TestingProcessManager; import io.supertokens.test.Utils; @@ -57,7 +55,7 @@ public void beforeEach() { @Test public void testCreatingAPasswordlessUserMapTheirUserIdAndRetrieveUserId() throws Exception { - String[] args = { "../" }; + String[] args = {"../"}; TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); @@ -123,7 +121,7 @@ public void testCreatingAPasswordlessUserMapTheirUserIdAndRetrieveUserId() throw @Test public void testCreatingAPasswordlessUserAndRetrieveInfo() throws Exception { - String[] args = { "../" }; + String[] args = {"../"}; TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); @@ -201,7 +199,7 @@ public void testCreatingAPasswordlessUserAndRetrieveInfo() throws Exception { @Test public void testCreatingPasswordlessUserWithPhoneNumberAndRetrieveInfo() throws Exception { - String[] args = { "../" }; + String[] args = {"../"}; TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); @@ -252,7 +250,7 @@ public void testCreatingPasswordlessUserWithPhoneNumberAndRetrieveInfo() throws @Test public void testUpdatingPasswordlessUserWithTheirExternalId() throws Exception { - String[] args = { "../" }; + String[] args = {"../"}; TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); @@ -287,7 +285,7 @@ public void testUpdatingPasswordlessUserWithTheirExternalId() throws Exception { assertEquals(updateUserResponse.get("status").getAsString(), "OK"); // check that user got updated - UserInfo userInfo = Passwordless.getUserByEmail(process.main, newEmail); + AuthRecipeUserInfo userInfo = Passwordless.getUserByEmail(process.main, newEmail); assertNotNull(userInfo); assertEquals(userInfo.id, superTokensUserId); } From 8dd0ea4228b5c87091a221205ce685b771491643 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Wed, 12 Jul 2023 14:21:06 +0530 Subject: [PATCH 015/131] adds function for list user by phone number --- .../java/io/supertokens/inmemorydb/Start.java | 21 +++---- .../inmemorydb/queries/GeneralQueries.java | 16 +++++ .../queries/PasswordlessQueries.java | 44 +++++-------- .../passwordless/Passwordless.java | 36 ++++++++--- .../api/TestTenantUserAssociation.java | 2 +- .../passwordless/PasswordlessGetUserTest.java | 6 +- .../passwordless/PasswordlessStorageTest.java | 9 +-- .../PasswordlessUpdateUserTest.java | 63 +++++++++++-------- .../api/PasswordlessUserPutAPITest2_11.java | 24 ++++--- 9 files changed, 125 insertions(+), 96 deletions(-) diff --git a/src/main/java/io/supertokens/inmemorydb/Start.java b/src/main/java/io/supertokens/inmemorydb/Start.java index d0ec25d25..d03b7a3a7 100644 --- a/src/main/java/io/supertokens/inmemorydb/Start.java +++ b/src/main/java/io/supertokens/inmemorydb/Start.java @@ -1291,6 +1291,16 @@ public AuthRecipeUserInfo[] listPrimaryUsersByEmail(TenantIdentifier tenantIdent } } + @Override + public AuthRecipeUserInfo[] listPrimaryUsersByPhoneNumber(TenantIdentifier tenantIdentifier, String phoneNumber) + throws StorageQueryException { + try { + return GeneralQueries.listPrimaryUsersByPhoneNumber(this, tenantIdentifier, phoneNumber); + } catch (SQLException e) { + throw new StorageQueryException(e); + } + } + @Override public List getJWTSigningKeys_Transaction(AppIdentifier appIdentifier, TransactionConnection con) @@ -1776,17 +1786,6 @@ public PasswordlessCode getCodeByLinkCodeHash(TenantIdentifier tenantIdentifier, } } - @Override - public io.supertokens.pluginInterface.passwordless.UserInfo getUserByPhoneNumber(TenantIdentifier tenantIdentifier, - String phoneNumber) - throws StorageQueryException { - try { - return PasswordlessQueries.getUserByPhoneNumber(this, tenantIdentifier, phoneNumber); - } catch (SQLException e) { - throw new StorageQueryException(e); - } - } - @Override public JsonObject getUserMetadata(AppIdentifier appIdentifier, String userId) throws StorageQueryException { try { diff --git a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java index 9c35ac288..981deef42 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java @@ -876,6 +876,22 @@ public static AuthRecipeUserInfo[] listPrimaryUsersByEmail(Start start, TenantId return result.toArray(new AuthRecipeUserInfo[0]); } + public static AuthRecipeUserInfo[] listPrimaryUsersByPhoneNumber(Start start, TenantIdentifier tenantIdentifier, + String phoneNumber) + throws StorageQueryException, SQLException { + List userIds = new ArrayList<>(); + + String passwordlessUserId = PasswordlessQueries.getUserByPhoneNumber(start, tenantIdentifier, phoneNumber); + if (passwordlessUserId != null) { + userIds.add(passwordlessUserId); + } + + List result = getPrimaryUserInfoForUserIds(start, tenantIdentifier.toAppIdentifier(), + userIds); + + return result.toArray(new AuthRecipeUserInfo[0]); + } + public static AuthRecipeUserInfo getPrimaryUserInfoForUserId(Start start, AppIdentifier appIdentifier, String id) throws SQLException, StorageQueryException { List ids = new ArrayList<>(); diff --git a/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java index 1044f7725..701393d6c 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java @@ -748,39 +748,23 @@ public static String getUserIdUsingEmail(Start start, TenantIdentifier tenantIde }); } - public static UserInfo getUserByPhoneNumber(Start start, TenantIdentifier tenantIdentifier, - @Nonnull String phoneNumber) + public static String getUserByPhoneNumber(Start start, TenantIdentifier tenantIdentifier, + @Nonnull String phoneNumber) throws StorageQueryException, SQLException { - String QUERY = "SELECT pl_users.user_id as user_id, pl_users.email as email, " - + "pl_users.phone_number as phone_number, pl_users.time_joined as time_joined " - + "FROM " + getConfig(start).getPasswordlessUserToTenantTable() + " AS pl_users_to_tenant " - + "JOIN " + getConfig(start).getPasswordlessUsersTable() + " AS pl_users " - + "ON pl_users.app_id = pl_users_to_tenant.app_id AND pl_users.user_id = pl_users_to_tenant.user_id " - + - "WHERE pl_users_to_tenant.app_id = ? AND pl_users_to_tenant.tenant_id = ? AND pl_users_to_tenant" + - ".phone_number = ? "; + String QUERY = "SELECT user_id " + + "FROM " + getConfig(start).getPasswordlessUserToTenantTable() + + " WHERE app_id = ? AND tenant_id = ? AND phone_number = ?"; - try (Connection con = ConnectionPool.getConnection(start)) { - UserInfoPartial userInfo = execute(con, QUERY, pst -> { - pst.setString(1, tenantIdentifier.getAppId()); - pst.setString(2, tenantIdentifier.getTenantId()); - pst.setString(3, phoneNumber); - }, result -> { - if (result.next()) { - return UserInfoRowMapper.getInstance().mapOrThrow(result); - } - return null; - }); - if (userInfo == null) { - return null; + return execute(start, QUERY, pst -> { + pst.setString(1, tenantIdentifier.getAppId()); + pst.setString(2, tenantIdentifier.getTenantId()); + pst.setString(3, phoneNumber); + }, result -> { + if (result.next()) { + return result.getString("user_id"); } - fillUserInfoWithTenantIds_transaction(start, con, tenantIdentifier.toAppIdentifier(), userInfo); - fillUserInfoWithVerified_transaction(start, con, tenantIdentifier.toAppIdentifier(), userInfo); - fillUserInfoWithIsPrimaryUserBoolean_transaction(start, con, tenantIdentifier.toAppIdentifier(), - userInfo); - return new UserInfo(userInfo.id, userInfo.isPrimary, - userInfo.toLoginMethod()); - } + return null; + }); } public static boolean addUserIdToTenant_Transaction(Start start, Connection sqlCon, diff --git a/src/main/java/io/supertokens/passwordless/Passwordless.java b/src/main/java/io/supertokens/passwordless/Passwordless.java index df5928492..d8d32cbbf 100644 --- a/src/main/java/io/supertokens/passwordless/Passwordless.java +++ b/src/main/java/io/supertokens/passwordless/Passwordless.java @@ -392,13 +392,21 @@ public static ConsumeCodeResponse consumeCode(TenantIdentifierWithStorage tenant } } } else { - user = passwordlessStorage.getUserByPhoneNumber(tenantIdentifierWithStorage, consumedDevice.phoneNumber); - if (user != null) { - loginMethod = user.loginMethods[0]; + AuthRecipeUserInfo[] users = passwordlessStorage.listPrimaryUsersByPhoneNumber(tenantIdentifierWithStorage, + consumedDevice.phoneNumber); + for (AuthRecipeUserInfo currUser : users) { + for (LoginMethod currLM : currUser.loginMethods) { + if (currLM.recipeId == RECIPE_ID.PASSWORDLESS && + currLM.phoneNumber.equals(consumedDevice.phoneNumber)) { + user = currUser; + loginMethod = currLM; + break; + } + } } } - if (user == null || loginMethod == null) { + if (user == null) { while (true) { try { String userId = Utils.getUUID(); @@ -539,18 +547,26 @@ public static UserInfo getUserById(AppIdentifierWithStorage appIdentifierWithSto } @TestOnly - public static UserInfo getUserByPhoneNumber(Main main, - String phoneNumber) throws StorageQueryException { + public static AuthRecipeUserInfo getUserByPhoneNumber(Main main, + String phoneNumber) throws StorageQueryException { Storage storage = StorageLayer.getStorage(main); return getUserByPhoneNumber( new TenantIdentifierWithStorage(null, null, null, storage), phoneNumber); } - public static UserInfo getUserByPhoneNumber(TenantIdentifierWithStorage tenantIdentifierWithStorage, - String phoneNumber) throws StorageQueryException { - return tenantIdentifierWithStorage.getPasswordlessStorage() - .getUserByPhoneNumber(tenantIdentifierWithStorage, phoneNumber); + public static AuthRecipeUserInfo getUserByPhoneNumber(TenantIdentifierWithStorage tenantIdentifierWithStorage, + String phoneNumber) throws StorageQueryException { + AuthRecipeUserInfo[] users = tenantIdentifierWithStorage.getPasswordlessStorage() + .listPrimaryUsersByPhoneNumber(tenantIdentifierWithStorage, phoneNumber); + for (AuthRecipeUserInfo user : users) { + for (LoginMethod lM : user.loginMethods) { + if (lM.recipeId == RECIPE_ID.PASSWORDLESS && lM.phoneNumber.equals(phoneNumber)) { + return user; + } + } + } + return null; } @TestOnly diff --git a/src/test/java/io/supertokens/test/multitenant/api/TestTenantUserAssociation.java b/src/test/java/io/supertokens/test/multitenant/api/TestTenantUserAssociation.java index bd8f29341..040aef3e1 100644 --- a/src/test/java/io/supertokens/test/multitenant/api/TestTenantUserAssociation.java +++ b/src/test/java/io/supertokens/test/multitenant/api/TestTenantUserAssociation.java @@ -347,7 +347,7 @@ public void testPasswordlessUsersHaveTenantIds2() throws Exception { createCodeResponse.deviceId, createCodeResponse.deviceIdHash, createCodeResponse.userInputCode, null); assertArrayEquals(new String[]{"t1"}, consumeCodeResponse.user.tenantIds); - io.supertokens.pluginInterface.passwordless.UserInfo user; + AuthRecipeUserInfo user; Multitenancy.addUserIdToTenant(process.getProcess(), t2WithStorage, consumeCodeResponse.user.id); user = Passwordless.getUserById(t1WithStorage.toAppIdentifierWithStorage(), consumeCodeResponse.user.id); Utils.assertArrayEqualsIgnoreOrder(new String[]{"t1", "t2"}, user.tenantIds); diff --git a/src/test/java/io/supertokens/test/passwordless/PasswordlessGetUserTest.java b/src/test/java/io/supertokens/test/passwordless/PasswordlessGetUserTest.java index b1f3efc06..34c53ba04 100644 --- a/src/test/java/io/supertokens/test/passwordless/PasswordlessGetUserTest.java +++ b/src/test/java/io/supertokens/test/passwordless/PasswordlessGetUserTest.java @@ -232,9 +232,9 @@ public void getUserByPhoneNumber() throws Exception { createUserWith(process, null, PHONE_NUMBER); - UserInfo user = Passwordless.getUserByPhoneNumber(process.getProcess(), PHONE_NUMBER); + AuthRecipeUserInfo user = Passwordless.getUserByPhoneNumber(process.getProcess(), PHONE_NUMBER); assertNotNull(user); - assertEquals(user.phoneNumber, PHONE_NUMBER); + assertEquals(user.loginMethods[0].phoneNumber, PHONE_NUMBER); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -258,7 +258,7 @@ public void getUserByInvalidPhoneNumber() throws Exception { createUserWith(process, null, PHONE_NUMBER); - UserInfo user = Passwordless.getUserByPhoneNumber(process.getProcess(), PHONE_NUMBER + "1"); + AuthRecipeUserInfo user = Passwordless.getUserByPhoneNumber(process.getProcess(), PHONE_NUMBER + "1"); assertNull(user); process.kill(); diff --git a/src/test/java/io/supertokens/test/passwordless/PasswordlessStorageTest.java b/src/test/java/io/supertokens/test/passwordless/PasswordlessStorageTest.java index d888134c6..9257e741f 100644 --- a/src/test/java/io/supertokens/test/passwordless/PasswordlessStorageTest.java +++ b/src/test/java/io/supertokens/test/passwordless/PasswordlessStorageTest.java @@ -27,7 +27,6 @@ import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.passwordless.PasswordlessCode; -import io.supertokens.pluginInterface.passwordless.UserInfo; import io.supertokens.pluginInterface.passwordless.exception.*; import io.supertokens.pluginInterface.passwordless.sqlStorage.PasswordlessSQLStorage; import io.supertokens.pluginInterface.sqlStorage.TransactionConnection; @@ -285,7 +284,8 @@ public void testCreateUserExceptions() throws Exception { assertNotNull(error); assert (error instanceof DuplicateUserIdException); - assertNull(storage.getUserByPhoneNumber(new TenantIdentifier(null, null, null), phoneNumber2)); + assert (storage.listPrimaryUsersByPhoneNumber(new TenantIdentifier(null, null, null), + phoneNumber2).length == 0); } { @@ -900,8 +900,9 @@ private void checkUser(PasswordlessSQLStorage storage, String userId, String ema assert (user.length == 1 && user[0].equals(userById)); } if (phoneNumber != null) { - UserInfo user = storage.getUserByPhoneNumber(new TenantIdentifier(null, null, null), phoneNumber); - assert (user.equals(userById)); + AuthRecipeUserInfo[] user = storage.listPrimaryUsersByPhoneNumber(new TenantIdentifier(null, null, null), + phoneNumber); + assert (user[0].equals(userById)); } } diff --git a/src/test/java/io/supertokens/test/passwordless/PasswordlessUpdateUserTest.java b/src/test/java/io/supertokens/test/passwordless/PasswordlessUpdateUserTest.java index 92e09b76a..4d46b753a 100644 --- a/src/test/java/io/supertokens/test/passwordless/PasswordlessUpdateUserTest.java +++ b/src/test/java/io/supertokens/test/passwordless/PasswordlessUpdateUserTest.java @@ -25,7 +25,6 @@ import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.passwordless.PasswordlessStorage; -import io.supertokens.pluginInterface.passwordless.UserInfo; import io.supertokens.pluginInterface.passwordless.exception.DuplicatePhoneNumberException; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.TestingProcessManager; @@ -127,14 +126,16 @@ public void updatePhoneNumberToAnExistingOne() throws Exception { createUserWith(process, null, PHONE_NUMBER); createUserWith(process, null, alternate_phoneNumber); - UserInfo user = storage.getUserByPhoneNumber(new TenantIdentifier(null, null, null), PHONE_NUMBER); - assertNotNull(user); - UserInfo user_two = storage.getUserByPhoneNumber(new TenantIdentifier(null, null, null), alternate_phoneNumber); - assertNotNull(user_two); + AuthRecipeUserInfo[] user = storage.listPrimaryUsersByPhoneNumber(new TenantIdentifier(null, null, null), + PHONE_NUMBER); + assert (user.length == 1); + AuthRecipeUserInfo[] user_two = storage.listPrimaryUsersByPhoneNumber(new TenantIdentifier(null, null, null), + alternate_phoneNumber); + assert (user_two.length == 1); Exception ex = null; try { - Passwordless.updateUser(process.getProcess(), user.id, null, + Passwordless.updateUser(process.getProcess(), user[0].id, null, new Passwordless.FieldUpdate(alternate_phoneNumber)); } catch (Exception e) { ex = e; @@ -144,7 +145,8 @@ public void updatePhoneNumberToAnExistingOne() throws Exception { assert (ex instanceof DuplicatePhoneNumberException); assertEquals(PHONE_NUMBER, - storage.getUserByPhoneNumber(new TenantIdentifier(null, null, null), PHONE_NUMBER).phoneNumber); + storage.listPrimaryUsersByPhoneNumber(new TenantIdentifier(null, null, null), + PHONE_NUMBER)[0].loginMethods[0].phoneNumber); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -206,14 +208,15 @@ public void updatePhoneNumber() throws Exception { createUserWith(process, null, PHONE_NUMBER); - UserInfo user = storage.getUserByPhoneNumber(new TenantIdentifier(null, null, null), PHONE_NUMBER); - assertNotNull(user); + AuthRecipeUserInfo[] user = storage.listPrimaryUsersByPhoneNumber(new TenantIdentifier(null, null, null), + PHONE_NUMBER); + assert (user.length == 1); - Passwordless.updateUser(process.getProcess(), user.id, null, + Passwordless.updateUser(process.getProcess(), user[0].id, null, new Passwordless.FieldUpdate(alternate_phoneNumber)); assertEquals(alternate_phoneNumber, - storage.getPrimaryUserById(new AppIdentifier(null, null), user.id).loginMethods[0].phoneNumber); + storage.getPrimaryUserById(new AppIdentifier(null, null), user[0].id).loginMethods[0].phoneNumber); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -275,14 +278,16 @@ public void clearPhoneNumberSetEmail() throws Exception { createUserWith(process, null, PHONE_NUMBER); - UserInfo user = storage.getUserByPhoneNumber(new TenantIdentifier(null, null, null), PHONE_NUMBER); - assertNotNull(user); + AuthRecipeUserInfo[] user = storage.listPrimaryUsersByPhoneNumber(new TenantIdentifier(null, null, null), + PHONE_NUMBER); + assert (user.length == 1); - Passwordless.updateUser(process.getProcess(), user.id, new Passwordless.FieldUpdate(EMAIL), + Passwordless.updateUser(process.getProcess(), user[0].id, new Passwordless.FieldUpdate(EMAIL), new Passwordless.FieldUpdate(null)); - assertEquals(EMAIL, storage.getPrimaryUserById(new AppIdentifier(null, null), user.id).loginMethods[0].email); - assertNull(storage.getPrimaryUserById(new AppIdentifier(null, null), user.id).loginMethods[0].phoneNumber); + assertEquals(EMAIL, + storage.getPrimaryUserById(new AppIdentifier(null, null), user[0].id).loginMethods[0].email); + assertNull(storage.getPrimaryUserById(new AppIdentifier(null, null), user[0].id).loginMethods[0].phoneNumber); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -309,12 +314,13 @@ public void clearPhoneNumberAndEmail() throws Exception { createUserWith(process, null, PHONE_NUMBER); - UserInfo user = storage.getUserByPhoneNumber(new TenantIdentifier(null, null, null), PHONE_NUMBER); - assertNotNull(user); + AuthRecipeUserInfo[] user = storage.listPrimaryUsersByPhoneNumber(new TenantIdentifier(null, null, null), + PHONE_NUMBER); + assert (user.length == 1); Exception ex = null; try { - Passwordless.updateUser(process.getProcess(), user.id, new Passwordless.FieldUpdate(null), + Passwordless.updateUser(process.getProcess(), user[0].id, new Passwordless.FieldUpdate(null), new Passwordless.FieldUpdate(null)); } catch (Exception e) { ex = e; @@ -387,13 +393,14 @@ public void clearPhoneOfPhoneOnlyUser() throws Exception { createUserWith(process, null, PHONE_NUMBER); - UserInfo user = storage.getUserByPhoneNumber(new TenantIdentifier(null, null, null), PHONE_NUMBER); - assertNotNull(user); + AuthRecipeUserInfo[] user = storage.listPrimaryUsersByPhoneNumber(new TenantIdentifier(null, null, null), + PHONE_NUMBER); + assert (user.length == 1); Exception ex = null; try { - Passwordless.updateUser(process.getProcess(), user.id, null, new Passwordless.FieldUpdate(null)); + Passwordless.updateUser(process.getProcess(), user[0].id, null, new Passwordless.FieldUpdate(null)); } catch (Exception e) { ex = e; } @@ -427,15 +434,17 @@ public void setPhoneNumberSetEmail() throws Exception { createUserWith(process, null, PHONE_NUMBER); - UserInfo user = storage.getUserByPhoneNumber(new TenantIdentifier(null, null, null), PHONE_NUMBER); - assertNotNull(user); + AuthRecipeUserInfo[] user = storage.listPrimaryUsersByPhoneNumber(new TenantIdentifier(null, null, null), + PHONE_NUMBER); + assert (user.length == 1); - Passwordless.updateUser(process.getProcess(), user.id, new Passwordless.FieldUpdate(EMAIL), + Passwordless.updateUser(process.getProcess(), user[0].id, new Passwordless.FieldUpdate(EMAIL), new Passwordless.FieldUpdate(alternate_phoneNumber)); - assertEquals(EMAIL, storage.getPrimaryUserById(new AppIdentifier(null, null), user.id).loginMethods[0].email); + assertEquals(EMAIL, + storage.getPrimaryUserById(new AppIdentifier(null, null), user[0].id).loginMethods[0].email); assertEquals(alternate_phoneNumber, - storage.getPrimaryUserById(new AppIdentifier(null, null), user.id).loginMethods[0].phoneNumber); + storage.getPrimaryUserById(new AppIdentifier(null, null), user[0].id).loginMethods[0].phoneNumber); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); diff --git a/src/test/java/io/supertokens/test/passwordless/api/PasswordlessUserPutAPITest2_11.java b/src/test/java/io/supertokens/test/passwordless/api/PasswordlessUserPutAPITest2_11.java index bb8d4b549..5495f5437 100644 --- a/src/test/java/io/supertokens/test/passwordless/api/PasswordlessUserPutAPITest2_11.java +++ b/src/test/java/io/supertokens/test/passwordless/api/PasswordlessUserPutAPITest2_11.java @@ -34,7 +34,8 @@ import org.junit.Test; import org.junit.rules.TestRule; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; public class PasswordlessUserPutAPITest2_11 { @Rule @@ -165,7 +166,7 @@ public void testEmailToPhone() throws Exception { assertEquals("OK", response.get("status").getAsString()); assert (storage.listPrimaryUsersByEmail(new TenantIdentifier(null, null, null), email).length == 0); - assertNotNull(storage.getUserByPhoneNumber(new TenantIdentifier(null, null, null), phoneNumber)); + assert (storage.listPrimaryUsersByPhoneNumber(new TenantIdentifier(null, null, null), phoneNumber).length == 1); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); } @@ -206,7 +207,7 @@ public void testPhoneToEmail() throws Exception { assertEquals("OK", response.get("status").getAsString()); assert (storage.listPrimaryUsersByEmail(new TenantIdentifier(null, null, null), email).length == 1); - assertNull(storage.getUserByPhoneNumber(new TenantIdentifier(null, null, null), phoneNumber)); + assert (storage.listPrimaryUsersByPhoneNumber(new TenantIdentifier(null, null, null), phoneNumber).length == 0); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); } @@ -249,10 +250,12 @@ public void testPhoneAndEmail() throws Exception { assertEquals("OK", response.get("status").getAsString()); assert (storage.listPrimaryUsersByEmail(new TenantIdentifier(null, null, null), email).length == 0); - assertNull(storage.getUserByPhoneNumber(new TenantIdentifier(null, null, null), phoneNumber)); + assert (storage.listPrimaryUsersByPhoneNumber(new TenantIdentifier(null, null, null), phoneNumber).length == 0); assert (storage.listPrimaryUsersByEmail(new TenantIdentifier(null, null, null), updatedEmail).length == 1); - assertNotNull(storage.getUserByPhoneNumber(new TenantIdentifier(null, null, null), updatedPhoneNumber)); + assert (storage.listPrimaryUsersByPhoneNumber(new TenantIdentifier(null, null, null), + updatedPhoneNumber).length == + 1); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -437,7 +440,7 @@ public void clearEmail() throws Exception { assertEquals("OK", response.get("status").getAsString()); assert (storage.listPrimaryUsersByEmail(new TenantIdentifier(null, null, null), email).length == 0); - assertNotNull(storage.getUserByPhoneNumber(new TenantIdentifier(null, null, null), phoneNumber)); + assert (storage.listPrimaryUsersByPhoneNumber(new TenantIdentifier(null, null, null), phoneNumber).length == 1); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -478,7 +481,7 @@ public void clearPhone() throws Exception { assertEquals("OK", response.get("status").getAsString()); assert (storage.listPrimaryUsersByEmail(new TenantIdentifier(null, null, null), email).length == 1); - assertNull(storage.getUserByPhoneNumber(new TenantIdentifier(null, null, null), phoneNumber)); + assert (storage.listPrimaryUsersByPhoneNumber(new TenantIdentifier(null, null, null), phoneNumber).length == 0); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -518,7 +521,7 @@ public void updateNothing() throws Exception { assertEquals("OK", response.get("status").getAsString()); assert (storage.listPrimaryUsersByEmail(new TenantIdentifier(null, null, null), email).length == 1); - assertNotNull(storage.getUserByPhoneNumber(new TenantIdentifier(null, null, null), phoneNumber)); + assert (storage.listPrimaryUsersByPhoneNumber(new TenantIdentifier(null, null, null), phoneNumber).length == 1); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -599,8 +602,9 @@ public void testUpdatePhoneNumber() throws Exception { assertEquals("OK", response.get("status").getAsString()); - assertNotNull(storage.getUserByPhoneNumber(new TenantIdentifier(null, null, null), updatedPhoneNumber)); - assertNull(storage.getUserByPhoneNumber(new TenantIdentifier(null, null, null), phoneNumber)); + assert (storage.listPrimaryUsersByPhoneNumber(new TenantIdentifier(null, null, null), + updatedPhoneNumber).length == 1); + assert (storage.listPrimaryUsersByPhoneNumber(new TenantIdentifier(null, null, null), phoneNumber).length == 0); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); From c0759fd9233e43720302f91bf9d1361727f56b2f Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Wed, 12 Jul 2023 14:47:58 +0530 Subject: [PATCH 016/131] optimises a query --- .../queries/EmailPasswordQueries.java | 10 +++++---- .../inmemorydb/queries/GeneralQueries.java | 7 +++--- .../queries/PasswordlessQueries.java | 22 +++++++++++-------- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java index 162aedb05..36c76f1af 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java @@ -398,11 +398,13 @@ public static List getUsersInfoUsingIdList(Start start, Set return Collections.emptyList(); } - public static String getUserIdUsingEmail(Start start, TenantIdentifier tenantIdentifier, String email) + public static String getPrimaryUserIdUsingEmail(Start start, TenantIdentifier tenantIdentifier, String email) throws StorageQueryException, SQLException { - String QUERY = "SELECT user_id " - + "FROM " + getConfig(start).getEmailPasswordUserToTenantTable() + - " WHERE app_id = ? AND tenant_id = ? AND email = ?"; + String QUERY = "SELECT DISTINCT all_users.primary_or_recipe_user_id AS user_id " + + "FROM " + getConfig(start).getEmailPasswordUserToTenantTable() + " AS ep" + + " JOIN " + getConfig(start).getUsersTable() + " AS all_users" + + " ON ep.app_id = all_users.app_id AND ep.user_id = all_users.user_id" + + " WHERE ep.app_id = ? AND ep.tenant_id = ? AND ep.email = ?"; return execute(start, QUERY, pst -> { pst.setString(1, tenantIdentifier.getAppId()); diff --git a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java index 981deef42..ec1f7cdbc 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java @@ -858,12 +858,12 @@ public static AuthRecipeUserInfo[] listPrimaryUsersByEmail(Start start, TenantId throws StorageQueryException, SQLException { List userIds = new ArrayList<>(); - String emailPasswordUserId = EmailPasswordQueries.getUserIdUsingEmail(start, tenantIdentifier, email); + String emailPasswordUserId = EmailPasswordQueries.getPrimaryUserIdUsingEmail(start, tenantIdentifier, email); if (emailPasswordUserId != null) { userIds.add(emailPasswordUserId); } - String passwordlessUserId = PasswordlessQueries.getUserIdUsingEmail(start, tenantIdentifier, email); + String passwordlessUserId = PasswordlessQueries.getPrimaryUserIdUsingEmail(start, tenantIdentifier, email); if (passwordlessUserId != null) { userIds.add(passwordlessUserId); } @@ -881,7 +881,8 @@ public static AuthRecipeUserInfo[] listPrimaryUsersByPhoneNumber(Start start, Te throws StorageQueryException, SQLException { List userIds = new ArrayList<>(); - String passwordlessUserId = PasswordlessQueries.getUserByPhoneNumber(start, tenantIdentifier, phoneNumber); + String passwordlessUserId = PasswordlessQueries.getPrimaryUserByPhoneNumber(start, tenantIdentifier, + phoneNumber); if (passwordlessUserId != null) { userIds.add(passwordlessUserId); } diff --git a/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java index 701393d6c..86d41dc84 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java @@ -730,11 +730,13 @@ public static UserInfoPartial getUserById(Start start, Connection sqlCon, AppIde }); } - public static String getUserIdUsingEmail(Start start, TenantIdentifier tenantIdentifier, String email) + public static String getPrimaryUserIdUsingEmail(Start start, TenantIdentifier tenantIdentifier, String email) throws StorageQueryException, SQLException { - String QUERY = "SELECT user_id " - + "FROM " + getConfig(start).getPasswordlessUserToTenantTable() + - " WHERE app_id = ? AND tenant_id = ? AND email = ?"; + String QUERY = "SELECT DISTINCT all_users.primary_or_recipe_user_id AS user_id " + + "FROM " + getConfig(start).getPasswordlessUserToTenantTable() + " AS pless" + + " JOIN " + getConfig(start).getUsersTable() + " AS all_users" + + " ON pless.app_id = all_users.app_id AND pless.user_id = all_users.user_id" + + " WHERE pless.app_id = ? AND pless.tenant_id = ? AND pless.email = ?"; return execute(start, QUERY, pst -> { pst.setString(1, tenantIdentifier.getAppId()); @@ -748,12 +750,14 @@ public static String getUserIdUsingEmail(Start start, TenantIdentifier tenantIde }); } - public static String getUserByPhoneNumber(Start start, TenantIdentifier tenantIdentifier, - @Nonnull String phoneNumber) + public static String getPrimaryUserByPhoneNumber(Start start, TenantIdentifier tenantIdentifier, + @Nonnull String phoneNumber) throws StorageQueryException, SQLException { - String QUERY = "SELECT user_id " - + "FROM " + getConfig(start).getPasswordlessUserToTenantTable() + - " WHERE app_id = ? AND tenant_id = ? AND phone_number = ?"; + String QUERY = "SELECT DISTINCT all_users.primary_or_recipe_user_id AS user_id " + + "FROM " + getConfig(start).getPasswordlessUserToTenantTable() + " AS pless" + + " JOIN " + getConfig(start).getUsersTable() + " AS all_users" + + " ON pless.app_id = all_users.app_id AND pless.user_id = all_users.user_id" + + " WHERE pless.app_id = ? AND pless.tenant_id = ? AND pless.phone_number = ?"; return execute(start, QUERY, pst -> { pst.setString(1, tenantIdentifier.getAppId()); From f1928f3d667f0a1be908880947ccd16df7d3e95b Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Wed, 12 Jul 2023 16:21:40 +0530 Subject: [PATCH 017/131] more intefrace function --- .../java/io/supertokens/inmemorydb/Start.java | 24 +++--- .../inmemorydb/queries/GeneralQueries.java | 19 ++++- .../inmemorydb/queries/ThirdPartyQueries.java | 51 +++++------- .../io/supertokens/thirdparty/ThirdParty.java | 20 +++-- .../webserver/api/thirdparty/UserAPI.java | 4 +- .../io/supertokens/test/AuthRecipeTest.java | 78 +++++++++---------- .../test/multitenant/TestAppData.java | 38 +++++---- .../api/TestTenantUserAssociation.java | 4 +- .../test/thirdparty/ThirdPartyTest.java | 36 +++++---- .../test/thirdparty/ThirdPartyTest2_7.java | 46 ++++++----- 10 files changed, 171 insertions(+), 149 deletions(-) diff --git a/src/main/java/io/supertokens/inmemorydb/Start.java b/src/main/java/io/supertokens/inmemorydb/Start.java index d03b7a3a7..01150255d 100644 --- a/src/main/java/io/supertokens/inmemorydb/Start.java +++ b/src/main/java/io/supertokens/inmemorydb/Start.java @@ -1148,19 +1148,6 @@ public void deleteThirdPartyUser(AppIdentifier appIdentifier, String userId) thr } } - @Override - public io.supertokens.pluginInterface.thirdparty.UserInfo getThirdPartyUserInfoUsingId( - TenantIdentifier tenantIdentifier, String thirdPartyId, - String thirdPartyUserId) - throws StorageQueryException { - try { - return ThirdPartyQueries.getThirdPartyUserInfoUsingId(this, tenantIdentifier, thirdPartyId, - thirdPartyUserId); - } catch (SQLException e) { - throw new StorageQueryException(e); - } - } - @Override public io.supertokens.pluginInterface.thirdparty.UserInfo[] getThirdPartyUsersByEmail( TenantIdentifier tenantIdentifier, @NotNull String email) @@ -1301,6 +1288,17 @@ public AuthRecipeUserInfo[] listPrimaryUsersByPhoneNumber(TenantIdentifier tenan } } + @Override + public AuthRecipeUserInfo getPrimaryUserByThirdPartyInfo(TenantIdentifier tenantIdentifier, String thirdPartyId, + String thirdPartyUserId) throws StorageQueryException { + try { + return GeneralQueries.getPrimaryUserByThirdPartyInfo(this, tenantIdentifier, thirdPartyId, + thirdPartyUserId); + } catch (SQLException e) { + throw new StorageQueryException(e); + } + } + @Override public List getJWTSigningKeys_Transaction(AppIdentifier appIdentifier, TransactionConnection con) diff --git a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java index 186bb8025..51c5d2813 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java @@ -29,7 +29,6 @@ import io.supertokens.pluginInterface.dashboard.DashboardSearchTags; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; -import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -590,7 +589,9 @@ public static AuthRecipeUserInfo[] getUsers(Start start, TenantIdentifier tenant String QUERY = "SELECT allAuthUsersTable.*" + " FROM " + getConfig(start).getUsersTable() + " AS allAuthUsersTable" + " JOIN " + getConfig(start).getThirdPartyUserToTenantTable() - + " AS thirdPartyToTenantTable ON allAuthUsersTable.app_id = thirdPartyToTenantTable.app_id AND" + + + " AS thirdPartyToTenantTable ON allAuthUsersTable.app_id = thirdPartyToTenantTable" + + ".app_id AND" + " allAuthUsersTable.tenant_id = thirdPartyToTenantTable.tenant_id AND" + " allAuthUsersTable.user_id = thirdPartyToTenantTable.user_id" + " JOIN " + getConfig(start).getThirdPartyUsersTable() @@ -895,6 +896,20 @@ public static AuthRecipeUserInfo[] listPrimaryUsersByPhoneNumber(Start start, Te return result.toArray(new AuthRecipeUserInfo[0]); } + public static AuthRecipeUserInfo getPrimaryUserByThirdPartyInfo(Start start, TenantIdentifier tenantIdentifier, + String thirdPartyId, + String thirdPartyUserId) + throws StorageQueryException, SQLException { + + String userId = ThirdPartyQueries.getThirdPartyUserInfoUsingId(start, tenantIdentifier, + thirdPartyId, thirdPartyUserId); + if (userId != null) { + return getPrimaryUserInfoForUserId(start, tenantIdentifier.toAppIdentifier(), + userId); + } + return null; + } + public static AuthRecipeUserInfo getPrimaryUserInfoForUserId(Start start, AppIdentifier appIdentifier, String id) throws SQLException, StorageQueryException { List ids = new ArrayList<>(); diff --git a/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java index cfce9da88..91b81d330 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java @@ -210,41 +210,26 @@ public static List getUsersInfoUsingIdList(Start start, Set return Collections.emptyList(); } - public static UserInfo getThirdPartyUserInfoUsingId(Start start, TenantIdentifier tenantIdentifier, - String thirdPartyId, String thirdPartyUserId) + public static String getThirdPartyUserInfoUsingId(Start start, TenantIdentifier tenantIdentifier, + String thirdPartyId, String thirdPartyUserId) throws SQLException, StorageQueryException { - - String QUERY = "SELECT tp_users.user_id as user_id, tp_users.third_party_id as third_party_id, " - + "tp_users.third_party_user_id as third_party_user_id, tp_users.email as email, " - + "tp_users.time_joined as time_joined " - + "FROM " + getConfig(start).getThirdPartyUserToTenantTable() + " AS tp_users_to_tenant " - + "JOIN " + getConfig(start).getThirdPartyUsersTable() + " AS tp_users " - + "ON tp_users.app_id = tp_users_to_tenant.app_id AND tp_users.user_id = tp_users_to_tenant.user_id " - + "WHERE tp_users_to_tenant.app_id = ? AND tp_users_to_tenant.tenant_id = ? " - + "AND tp_users_to_tenant.third_party_id = ? AND tp_users_to_tenant.third_party_user_id = ?"; - - - try (Connection con = ConnectionPool.getConnection(start)) { - UserInfoPartial userInfo = execute(con, QUERY, pst -> { - pst.setString(1, tenantIdentifier.getAppId()); - pst.setString(2, tenantIdentifier.getTenantId()); - pst.setString(3, thirdPartyId); - pst.setString(4, thirdPartyUserId); - }, result -> { - if (result.next()) { - return UserInfoRowMapper.getInstance().mapOrThrow(result); - } - return null; - }); - if (userInfo == null) { - return null; + String QUERY = "SELECT DISTINCT all_users.primary_or_recipe_user_id AS user_id " + + "FROM " + getConfig(start).getThirdPartyUserToTenantTable() + " AS tp" + + " JOIN " + getConfig(start).getUsersTable() + " AS all_users" + + " ON tp.app_id = all_users.app_id AND tp.user_id = all_users.user_id" + + " WHERE tp.app_id = ? AND tp.tenant_id = ? AND tp.third_party_id = ? AND tp.third_party_user_id = ?"; + + return execute(start, QUERY, pst -> { + pst.setString(1, tenantIdentifier.getAppId()); + pst.setString(2, tenantIdentifier.getTenantId()); + pst.setString(3, thirdPartyId); + pst.setString(4, thirdPartyUserId); + }, result -> { + if (result.next()) { + return result.getString("user_id"); } - fillUserInfoWithTenantIds_transaction(start, con, tenantIdentifier.toAppIdentifier(), userInfo); - fillUserInfoWithVerified_transaction(start, con, tenantIdentifier.toAppIdentifier(), userInfo); - fillUserInfoWithIsPrimaryUserBoolean_transaction(start, con, tenantIdentifier.toAppIdentifier(), - userInfo); - return new UserInfo(userInfo.id, userInfo.isPrimary, userInfo.toLoginMethod()); - } + return null; + }); } public static void updateUserEmail_Transaction(Start start, Connection con, AppIdentifier appIdentifier, diff --git a/src/main/java/io/supertokens/thirdparty/ThirdParty.java b/src/main/java/io/supertokens/thirdparty/ThirdParty.java index 2d473689b..e88203cee 100644 --- a/src/main/java/io/supertokens/thirdparty/ThirdParty.java +++ b/src/main/java/io/supertokens/thirdparty/ThirdParty.java @@ -42,9 +42,9 @@ public class ThirdParty { public static class SignInUpResponse { public boolean createdNewUser; - public UserInfo user; + public AuthRecipeUserInfo user; - public SignInUpResponse(boolean createdNewUser, UserInfo user) { + public SignInUpResponse(boolean createdNewUser, AuthRecipeUserInfo user) { this.createdNewUser = createdNewUser; this.user = user; } @@ -65,9 +65,12 @@ public static SignInUpResponse signInUp2_7(TenantIdentifierWithStorage tenantIde try { tenantIdentifierWithStorage.getEmailVerificationStorage().startTransaction(con -> { try { + // this assert is there cause this function should only be used for older CDIs in which + // account linking was not available. So loginMethod length will always be 1. + assert (response.user.loginMethods.length == 1); tenantIdentifierWithStorage.getEmailVerificationStorage() .updateIsEmailVerified_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), con, - response.user.id, response.user.email, true); + response.user.id, response.user.loginMethods[0].email, true); tenantIdentifierWithStorage.getEmailVerificationStorage() .commitTransaction(con); return null; @@ -185,7 +188,7 @@ private static SignInUpResponse signInUpHelper(TenantIdentifierWithStorage tenan }); } - UserInfo user = getUser(tenantIdentifierWithStorage, thirdPartyId, thirdPartyUserId); + AuthRecipeUserInfo user = getUser(tenantIdentifierWithStorage, thirdPartyId, thirdPartyUserId); return new SignInUpResponse(false, user); } catch (StorageTransactionLogicException ignored) { } @@ -220,15 +223,16 @@ public static UserInfo getUser(Main main, String userId) throws StorageQueryExce return getUser(new AppIdentifierWithStorage(null, null, storage), userId); } - public static UserInfo getUser(TenantIdentifierWithStorage tenantIdentifierWithStorage, String thirdPartyId, - String thirdPartyUserId) + public static AuthRecipeUserInfo getUser(TenantIdentifierWithStorage tenantIdentifierWithStorage, + String thirdPartyId, + String thirdPartyUserId) throws StorageQueryException { return tenantIdentifierWithStorage.getThirdPartyStorage() - .getThirdPartyUserInfoUsingId(tenantIdentifierWithStorage, thirdPartyId, thirdPartyUserId); + .getPrimaryUserByThirdPartyInfo(tenantIdentifierWithStorage, thirdPartyId, thirdPartyUserId); } @TestOnly - public static UserInfo getUser(Main main, String thirdPartyId, String thirdPartyUserId) + public static AuthRecipeUserInfo getUser(Main main, String thirdPartyId, String thirdPartyUserId) throws StorageQueryException { Storage storage = StorageLayer.getStorage(main); return getUser( diff --git a/src/main/java/io/supertokens/webserver/api/thirdparty/UserAPI.java b/src/main/java/io/supertokens/webserver/api/thirdparty/UserAPI.java index cca5c2540..fd49b0457 100644 --- a/src/main/java/io/supertokens/webserver/api/thirdparty/UserAPI.java +++ b/src/main/java/io/supertokens/webserver/api/thirdparty/UserAPI.java @@ -20,10 +20,10 @@ import io.supertokens.AppIdentifierWithStorageAndUserIdMapping; import io.supertokens.Main; import io.supertokens.pluginInterface.RECIPE_ID; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; -import io.supertokens.pluginInterface.thirdparty.UserInfo; import io.supertokens.thirdparty.ThirdParty; import io.supertokens.useridmapping.UserIdMapping; import io.supertokens.useridmapping.UserIdType; @@ -70,7 +70,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO } try { - UserInfo user = null; + AuthRecipeUserInfo user = null; if (userId != null) { // Query by userId try { diff --git a/src/test/java/io/supertokens/test/AuthRecipeTest.java b/src/test/java/io/supertokens/test/AuthRecipeTest.java index 42e0ce0db..de4b1e717 100644 --- a/src/test/java/io/supertokens/test/AuthRecipeTest.java +++ b/src/test/java/io/supertokens/test/AuthRecipeTest.java @@ -66,7 +66,7 @@ public void beforeEach() { @Test public void getUsersCount() throws Exception { - String[] args = { "../" }; + String[] args = {"../"}; TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); @@ -89,17 +89,17 @@ public void getUsersCount() throws Exception { } { - long count = AuthRecipe.getUsersCount(process.getProcess(), new RECIPE_ID[] { RECIPE_ID.EMAIL_PASSWORD }); + long count = AuthRecipe.getUsersCount(process.getProcess(), new RECIPE_ID[]{RECIPE_ID.EMAIL_PASSWORD}); assert (count == 2); } { - long count = AuthRecipe.getUsersCount(process.getProcess(), new RECIPE_ID[] { RECIPE_ID.THIRD_PARTY }); + long count = AuthRecipe.getUsersCount(process.getProcess(), new RECIPE_ID[]{RECIPE_ID.THIRD_PARTY}); assert (count == 0); } { - long count = AuthRecipe.getUsersCount(process.getProcess(), new RECIPE_ID[] { RECIPE_ID.PASSWORDLESS }); + long count = AuthRecipe.getUsersCount(process.getProcess(), new RECIPE_ID[]{RECIPE_ID.PASSWORDLESS}); assert (count == 0); } @@ -110,28 +110,28 @@ public void getUsersCount() throws Exception { ThirdParty.signInUp(process.getProcess(), thirdPartyId, thirdPartyUserId_1, email_1); { - long count = AuthRecipe.getUsersCount(process.getProcess(), new RECIPE_ID[] {}); + long count = AuthRecipe.getUsersCount(process.getProcess(), new RECIPE_ID[]{}); assert (count == 3); } { long count = AuthRecipe.getUsersCount(process.getProcess(), - new RECIPE_ID[] { RECIPE_ID.EMAIL_PASSWORD, RECIPE_ID.THIRD_PARTY }); + new RECIPE_ID[]{RECIPE_ID.EMAIL_PASSWORD, RECIPE_ID.THIRD_PARTY}); assert (count == 3); } { - long count = AuthRecipe.getUsersCount(process.getProcess(), new RECIPE_ID[] { RECIPE_ID.EMAIL_PASSWORD }); + long count = AuthRecipe.getUsersCount(process.getProcess(), new RECIPE_ID[]{RECIPE_ID.EMAIL_PASSWORD}); assert (count == 2); } { - long count = AuthRecipe.getUsersCount(process.getProcess(), new RECIPE_ID[] { RECIPE_ID.THIRD_PARTY }); + long count = AuthRecipe.getUsersCount(process.getProcess(), new RECIPE_ID[]{RECIPE_ID.THIRD_PARTY}); assert (count == 1); } { - long count = AuthRecipe.getUsersCount(process.getProcess(), new RECIPE_ID[] { RECIPE_ID.PASSWORDLESS }); + long count = AuthRecipe.getUsersCount(process.getProcess(), new RECIPE_ID[]{RECIPE_ID.PASSWORDLESS}); assert (count == 0); } @@ -147,28 +147,28 @@ public void getUsersCount() throws Exception { } { - long count = AuthRecipe.getUsersCount(process.getProcess(), new RECIPE_ID[] {}); + long count = AuthRecipe.getUsersCount(process.getProcess(), new RECIPE_ID[]{}); assert (count == 5); } { long count = AuthRecipe.getUsersCount(process.getProcess(), - new RECIPE_ID[] { RECIPE_ID.EMAIL_PASSWORD, RECIPE_ID.PASSWORDLESS }); + new RECIPE_ID[]{RECIPE_ID.EMAIL_PASSWORD, RECIPE_ID.PASSWORDLESS}); assert (count == 4); } { - long count = AuthRecipe.getUsersCount(process.getProcess(), new RECIPE_ID[] { RECIPE_ID.EMAIL_PASSWORD }); + long count = AuthRecipe.getUsersCount(process.getProcess(), new RECIPE_ID[]{RECIPE_ID.EMAIL_PASSWORD}); assert (count == 2); } { - long count = AuthRecipe.getUsersCount(process.getProcess(), new RECIPE_ID[] { RECIPE_ID.THIRD_PARTY }); + long count = AuthRecipe.getUsersCount(process.getProcess(), new RECIPE_ID[]{RECIPE_ID.THIRD_PARTY}); assert (count == 1); } { - long count = AuthRecipe.getUsersCount(process.getProcess(), new RECIPE_ID[] { RECIPE_ID.PASSWORDLESS }); + long count = AuthRecipe.getUsersCount(process.getProcess(), new RECIPE_ID[]{RECIPE_ID.PASSWORDLESS}); assert (count == 2); } process.kill(); @@ -177,7 +177,7 @@ public void getUsersCount() throws Exception { @Test public void paginationTest() throws Exception { - String[] args = { "../" }; + String[] args = {"../"}; TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); @@ -194,7 +194,7 @@ public void paginationTest() throws Exception { { UserPaginationContainer users = AuthRecipe.getUsers(process.getProcess(), 100, "ASC", null, - new RECIPE_ID[] { RECIPE_ID.EMAIL_PASSWORD, RECIPE_ID.THIRD_PARTY }, null); + new RECIPE_ID[]{RECIPE_ID.EMAIL_PASSWORD, RECIPE_ID.THIRD_PARTY}, null); assert (users.nextPaginationToken == null); assert (users.users.length == 0); } @@ -206,7 +206,7 @@ public void paginationTest() throws Exception { { UserPaginationContainer users = AuthRecipe.getUsers(process.getProcess(), 100, "ASC", null, - new RECIPE_ID[] { RECIPE_ID.EMAIL_PASSWORD }, null); + new RECIPE_ID[]{RECIPE_ID.EMAIL_PASSWORD}, null); assert (users.nextPaginationToken == null); assert (users.users.length == 4); assert (users.users[0].recipeId.equals("emailpassword")); @@ -222,7 +222,7 @@ public void paginationTest() throws Exception { { UserPaginationContainer users = AuthRecipe.getUsers(process.getProcess(), 100, "ASC", null, - new RECIPE_ID[] { RECIPE_ID.EMAIL_PASSWORD, RECIPE_ID.THIRD_PARTY }, null); + new RECIPE_ID[]{RECIPE_ID.EMAIL_PASSWORD, RECIPE_ID.THIRD_PARTY}, null); assert (users.nextPaginationToken == null); assert (users.users.length == 4); assert (users.users[0].recipeId.equals("emailpassword")); @@ -237,7 +237,7 @@ public void paginationTest() throws Exception { { UserPaginationContainer users = AuthRecipe.getUsers(process.getProcess(), 100, "DESC", null, - new RECIPE_ID[] { RECIPE_ID.EMAIL_PASSWORD }, null); + new RECIPE_ID[]{RECIPE_ID.EMAIL_PASSWORD}, null); assert (users.nextPaginationToken == null); assert (users.users.length == 4); assert (users.users[3].recipeId.equals("emailpassword")); @@ -253,7 +253,7 @@ public void paginationTest() throws Exception { { UserPaginationContainer users = AuthRecipe.getUsers(process.getProcess(), 100, "DESC", null, - new RECIPE_ID[] { RECIPE_ID.THIRD_PARTY, RECIPE_ID.EMAIL_PASSWORD }, null); + new RECIPE_ID[]{RECIPE_ID.THIRD_PARTY, RECIPE_ID.EMAIL_PASSWORD}, null); assert (users.nextPaginationToken == null); assert (users.users.length == 4); assert (users.users[3].recipeId.equals("emailpassword")); @@ -268,14 +268,14 @@ public void paginationTest() throws Exception { { UserPaginationContainer users = AuthRecipe.getUsers(process.getProcess(), 100, "DESC", null, - new RECIPE_ID[] { RECIPE_ID.THIRD_PARTY, RECIPE_ID.SESSION }, null); + new RECIPE_ID[]{RECIPE_ID.THIRD_PARTY, RECIPE_ID.SESSION}, null); assert (users.nextPaginationToken == null); assert (users.users.length == 0); } { UserPaginationContainer users = AuthRecipe.getUsers(process.getProcess(), 2, "DESC", null, - new RECIPE_ID[] { RECIPE_ID.THIRD_PARTY, RECIPE_ID.EMAIL_PASSWORD }, null); + new RECIPE_ID[]{RECIPE_ID.THIRD_PARTY, RECIPE_ID.EMAIL_PASSWORD}, null); assert (users.nextPaginationToken != null); assert (users.users.length == 2); assert (users.users[1].recipeId.equals("emailpassword")); @@ -286,7 +286,7 @@ public void paginationTest() throws Exception { { UserPaginationContainer users = AuthRecipe.getUsers(process.getProcess(), 3, "ASC", null, - new RECIPE_ID[] { RECIPE_ID.THIRD_PARTY, RECIPE_ID.EMAIL_PASSWORD }, null); + new RECIPE_ID[]{RECIPE_ID.THIRD_PARTY, RECIPE_ID.EMAIL_PASSWORD}, null); assert (users.nextPaginationToken != null); assert (users.users.length == 3); assert (users.users[0].recipeId.equals("emailpassword")); @@ -303,12 +303,12 @@ public void paginationTest() throws Exception { String thirdPartyUserId_1 = "thirdPartyUserIdA"; String email_1 = "testA@example.com"; - io.supertokens.pluginInterface.thirdparty.UserInfo user5 = ThirdParty.signInUp(process.getProcess(), + AuthRecipeUserInfo user5 = ThirdParty.signInUp(process.getProcess(), thirdPartyId, thirdPartyUserId_1, email_1).user; { UserPaginationContainer users = AuthRecipe.getUsers(process.getProcess(), 100, "ASC", null, - new RECIPE_ID[] { RECIPE_ID.EMAIL_PASSWORD }, null); + new RECIPE_ID[]{RECIPE_ID.EMAIL_PASSWORD}, null); assert (users.nextPaginationToken == null); assert (users.users.length == 4); assert (users.users[0].recipeId.equals("emailpassword")); @@ -324,7 +324,7 @@ public void paginationTest() throws Exception { { UserPaginationContainer users = AuthRecipe.getUsers(process.getProcess(), 100, "ASC", null, - new RECIPE_ID[] { RECIPE_ID.THIRD_PARTY }, null); + new RECIPE_ID[]{RECIPE_ID.THIRD_PARTY}, null); assert (users.nextPaginationToken == null); assert (users.users.length == 1); assert (users.users[0].recipeId.equals("thirdparty")); @@ -334,7 +334,7 @@ public void paginationTest() throws Exception { { UserPaginationContainer users = AuthRecipe.getUsers(process.getProcess(), 100, "ASC", null, - new RECIPE_ID[] { RECIPE_ID.EMAIL_PASSWORD, RECIPE_ID.THIRD_PARTY }, null); + new RECIPE_ID[]{RECIPE_ID.EMAIL_PASSWORD, RECIPE_ID.THIRD_PARTY}, null); assert (users.nextPaginationToken == null); assert (users.users.length == 5); assert (users.users[0].recipeId.equals("emailpassword")); @@ -351,7 +351,7 @@ public void paginationTest() throws Exception { { UserPaginationContainer users = AuthRecipe.getUsers(process.getProcess(), 100, "DESC", null, - new RECIPE_ID[] { RECIPE_ID.EMAIL_PASSWORD }, null); + new RECIPE_ID[]{RECIPE_ID.EMAIL_PASSWORD}, null); assert (users.nextPaginationToken == null); assert (users.users.length == 4); assert (users.users[3].recipeId.equals("emailpassword")); @@ -367,7 +367,7 @@ public void paginationTest() throws Exception { { UserPaginationContainer users = AuthRecipe.getUsers(process.getProcess(), 100, "DESC", null, - new RECIPE_ID[] { RECIPE_ID.THIRD_PARTY }, null); + new RECIPE_ID[]{RECIPE_ID.THIRD_PARTY}, null); assert (users.nextPaginationToken == null); assert (users.users.length == 1); assert (users.users[0].recipeId.equals("thirdparty")); @@ -377,7 +377,7 @@ public void paginationTest() throws Exception { { UserPaginationContainer users = AuthRecipe.getUsers(process.getProcess(), 100, "DESC", null, - new RECIPE_ID[] {}, null); + new RECIPE_ID[]{}, null); assert (users.nextPaginationToken == null); assert (users.users.length == 5); assert (users.users[4].recipeId.equals("emailpassword")); @@ -394,14 +394,14 @@ public void paginationTest() throws Exception { { UserPaginationContainer users = AuthRecipe.getUsers(process.getProcess(), 100, "DESC", null, - new RECIPE_ID[] { RECIPE_ID.SESSION }, null); + new RECIPE_ID[]{RECIPE_ID.SESSION}, null); assert (users.nextPaginationToken == null); assert (users.users.length == 0); } { UserPaginationContainer users = AuthRecipe.getUsers(process.getProcess(), 2, "DESC", null, - new RECIPE_ID[] { RECIPE_ID.THIRD_PARTY, RECIPE_ID.EMAIL_PASSWORD }, null); + new RECIPE_ID[]{RECIPE_ID.THIRD_PARTY, RECIPE_ID.EMAIL_PASSWORD}, null); assert (users.nextPaginationToken != null); assert (users.users.length == 2); assert (users.users[1].recipeId.equals("emailpassword")); @@ -412,7 +412,7 @@ public void paginationTest() throws Exception { { UserPaginationContainer users = AuthRecipe.getUsers(process.getProcess(), 3, "ASC", null, - new RECIPE_ID[] { RECIPE_ID.THIRD_PARTY, RECIPE_ID.EMAIL_PASSWORD }, null); + new RECIPE_ID[]{RECIPE_ID.THIRD_PARTY, RECIPE_ID.EMAIL_PASSWORD}, null); assert (users.nextPaginationToken != null); assert (users.users.length == 3); assert (users.users[0].recipeId.equals("emailpassword")); @@ -430,8 +430,8 @@ public void paginationTest() throws Exception { @Test public void randomPaginationTest() throws Exception { int numberOfUsers = 500; - int[] limits = new int[] { 10, 14, 20, 23, 50, 100, 110, 150, 200, 510 }; - String[] args = { "../" }; + int[] limits = new int[]{10, 14, 20, 23, 50, 100, 110, 150, 200, 510}; + String[] args = {"../"}; TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); @@ -576,7 +576,7 @@ public void randomPaginationTest() throws Exception { @Test public void deleteUserTest() throws Exception { - String[] args = { "../" }; + String[] args = {"../"}; TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); @@ -610,16 +610,16 @@ public void deleteUserTest() throws Exception { String emailVerificationToken2 = EmailVerification.generateEmailVerificationToken(process.getProcess(), user2.id, "email"); - assertEquals(2, AuthRecipe.getUsersCount(process.getProcess(), new RECIPE_ID[] { user1.getRecipeId() })); + assertEquals(2, AuthRecipe.getUsersCount(process.getProcess(), new RECIPE_ID[]{user1.getRecipeId()})); AuthRecipe.deleteUser(process.getProcess(), user1.id); - assertEquals(1, AuthRecipe.getUsersCount(process.getProcess(), new RECIPE_ID[] { user1.getRecipeId() })); + assertEquals(1, AuthRecipe.getUsersCount(process.getProcess(), new RECIPE_ID[]{user1.getRecipeId()})); assertEquals(0, Session.getAllNonExpiredSessionHandlesForUser(process.getProcess(), user1.id).length); assertEquals(1, Session.getAllNonExpiredSessionHandlesForUser(process.getProcess(), user2.id).length); assertFalse(EmailVerification.isEmailVerified(process.getProcess(), user1.id, "email")); assertEquals(0, UserMetadata.getUserMetadata(process.getProcess(), user1.id).entrySet().size()); AuthRecipe.deleteUser(process.getProcess(), user2.id); - assertEquals(0, AuthRecipe.getUsersCount(process.getProcess(), new RECIPE_ID[] { user1.getRecipeId() })); + assertEquals(0, AuthRecipe.getUsersCount(process.getProcess(), new RECIPE_ID[]{user1.getRecipeId()})); assertEquals(0, Session.getAllNonExpiredSessionHandlesForUser(process.getProcess(), user2.id).length); assertEquals(0, UserMetadata.getUserMetadata(process.getProcess(), user2.id).entrySet().size()); diff --git a/src/test/java/io/supertokens/test/multitenant/TestAppData.java b/src/test/java/io/supertokens/test/multitenant/TestAppData.java index 9bee53052..15c6d3133 100644 --- a/src/test/java/io/supertokens/test/multitenant/TestAppData.java +++ b/src/test/java/io/supertokens/test/multitenant/TestAppData.java @@ -94,7 +94,8 @@ public void testThatDeletingAppDeleteDataFromAllTables() throws Exception { TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); FeatureFlagTestContent.getInstance(process.getProcess()) - .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{EE_FEATURES.MULTI_TENANCY, EE_FEATURES.TOTP}); + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, + new EE_FEATURES[]{EE_FEATURES.MULTI_TENANCY, EE_FEATURES.TOTP}); process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); @@ -114,7 +115,8 @@ public void testThatDeletingAppDeleteDataFromAllTables() throws Exception { new JsonObject() ), false); - TenantIdentifierWithStorage appWithStorage = app.withStorage(StorageLayer.getStorage(app, process.getProcess())); + TenantIdentifierWithStorage appWithStorage = app.withStorage( + StorageLayer.getStorage(app, process.getProcess())); String[] allTableNames = appWithStorage.getStorage().getAllTablesInTheDatabase(); allTableNames = removeStrings(allTableNames, tablesToIgnore); @@ -124,7 +126,8 @@ public void testThatDeletingAppDeleteDataFromAllTables() throws Exception { UserInfo epUser = EmailPassword.signUp(appWithStorage, process.getProcess(), "test@example.com", "password"); EmailPassword.generatePasswordResetToken(appWithStorage, process.getProcess(), epUser.id); - ThirdParty.SignInUpResponse tpUser = ThirdParty.signInUp(appWithStorage, process.getProcess(), "google", "googleid", "test@example.com"); + ThirdParty.SignInUpResponse tpUser = ThirdParty.signInUp(appWithStorage, process.getProcess(), "google", + "googleid", "test@example.com"); Passwordless.CreateCodeResponse code = Passwordless.createCode(appWithStorage, process.getProcess(), "test@example.com", null, null, null); @@ -132,28 +135,37 @@ public void testThatDeletingAppDeleteDataFromAllTables() throws Exception { code.deviceId, code.deviceIdHash, code.userInputCode, null); Passwordless.createCode(appWithStorage, process.getProcess(), "test@example.com", null, null, null); - Dashboard.signUpDashboardUser(appWithStorage.toAppIdentifierWithStorage(), process.getProcess(), "user@example.com", "password"); - Dashboard.signInDashboardUser(appWithStorage.toAppIdentifierWithStorage(), process.getProcess(), "user@example.com", "password"); + Dashboard.signUpDashboardUser(appWithStorage.toAppIdentifierWithStorage(), process.getProcess(), + "user@example.com", "password"); + Dashboard.signInDashboardUser(appWithStorage.toAppIdentifierWithStorage(), process.getProcess(), + "user@example.com", "password"); - String evToken = EmailVerification.generateEmailVerificationToken(appWithStorage, process.getProcess(), epUser.id, epUser.email); + String evToken = EmailVerification.generateEmailVerificationToken(appWithStorage, process.getProcess(), + epUser.id, epUser.email); EmailVerification.verifyEmail(appWithStorage, evToken); - EmailVerification.generateEmailVerificationToken(appWithStorage, process.getProcess(), tpUser.user.id, tpUser.user.email); + EmailVerification.generateEmailVerificationToken(appWithStorage, process.getProcess(), tpUser.user.id, + tpUser.user.loginMethods[0].email); Session.createNewSession(appWithStorage, process.getProcess(), epUser.id, new JsonObject(), new JsonObject()); - UserRoles.createNewRoleOrModifyItsPermissions(appWithStorage.toAppIdentifierWithStorage(), "role", new String[]{"permission1", "permission2"}); + UserRoles.createNewRoleOrModifyItsPermissions(appWithStorage.toAppIdentifierWithStorage(), "role", + new String[]{"permission1", "permission2"}); UserRoles.addRoleToUser(appWithStorage, epUser.id, "role"); - TOTPDevice totpDevice = Totp.registerDevice(appWithStorage.toAppIdentifierWithStorage(), process.getProcess(), epUser.id, "test", 1, 3); - Totp.verifyCode(appWithStorage, process.getProcess(), epUser.id, generateTotpCode(process.getProcess(), totpDevice, 0), true); + TOTPDevice totpDevice = Totp.registerDevice(appWithStorage.toAppIdentifierWithStorage(), process.getProcess(), + epUser.id, "test", 1, 3); + Totp.verifyCode(appWithStorage, process.getProcess(), epUser.id, + generateTotpCode(process.getProcess(), totpDevice, 0), true); ActiveUsers.updateLastActive(appWithStorage.toAppIdentifierWithStorage(), process.getProcess(), epUser.id); UserMetadata.updateUserMetadata(appWithStorage.toAppIdentifierWithStorage(), epUser.id, new JsonObject()); - UserIdMapping.createUserIdMapping(process.getProcess(), appWithStorage.toAppIdentifierWithStorage(), plUser.user.id, "externalid", null, false); + UserIdMapping.createUserIdMapping(process.getProcess(), appWithStorage.toAppIdentifierWithStorage(), + plUser.user.id, "externalid", null, false); - String[] tablesThatHaveData = appWithStorage.getStorage().getAllTablesInTheDatabaseThatHasDataForAppId(app.getAppId()); + String[] tablesThatHaveData = appWithStorage.getStorage() + .getAllTablesInTheDatabaseThatHasDataForAppId(app.getAppId()); tablesThatHaveData = removeStrings(tablesThatHaveData, tablesToIgnore); Arrays.sort(tablesThatHaveData); @@ -166,7 +178,7 @@ public void testThatDeletingAppDeleteDataFromAllTables() throws Exception { tablesThatHaveData = appWithStorage.getStorage().getAllTablesInTheDatabaseThatHasDataForAppId(app.getAppId()); tablesThatHaveData = removeStrings(tablesThatHaveData, tablesToIgnore); assertEquals(0, tablesThatHaveData.length); - + process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); } diff --git a/src/test/java/io/supertokens/test/multitenant/api/TestTenantUserAssociation.java b/src/test/java/io/supertokens/test/multitenant/api/TestTenantUserAssociation.java index 040aef3e1..c82235e2a 100644 --- a/src/test/java/io/supertokens/test/multitenant/api/TestTenantUserAssociation.java +++ b/src/test/java/io/supertokens/test/multitenant/api/TestTenantUserAssociation.java @@ -380,11 +380,11 @@ public void testThirdPartyUsersHaveTenantIds() throws Exception { assertArrayEquals(new String[]{"t1"}, signInUpResponse.user.tenantIds); Multitenancy.addUserIdToTenant(process.getProcess(), t2WithStorage, signInUpResponse.user.id); - io.supertokens.pluginInterface.thirdparty.UserInfo user = ThirdParty.getUser( + AuthRecipeUserInfo user = ThirdParty.getUser( t1WithStorage.toAppIdentifierWithStorage(), signInUpResponse.user.id); Utils.assertArrayEqualsIgnoreOrder(new String[]{"t1", "t2"}, user.tenantIds); - user = ThirdParty.getUsersByEmail(t1WithStorage, signInUpResponse.user.email)[0]; + user = ThirdParty.getUsersByEmail(t1WithStorage, signInUpResponse.user.loginMethods[0].email)[0]; Utils.assertArrayEqualsIgnoreOrder(new String[]{"t1", "t2"}, user.tenantIds); user = ThirdParty.getUser(t1WithStorage, "google", "googleid"); diff --git a/src/test/java/io/supertokens/test/thirdparty/ThirdPartyTest.java b/src/test/java/io/supertokens/test/thirdparty/ThirdPartyTest.java index 92ae01792..b2946c0c3 100644 --- a/src/test/java/io/supertokens/test/thirdparty/ThirdPartyTest.java +++ b/src/test/java/io/supertokens/test/thirdparty/ThirdPartyTest.java @@ -19,6 +19,7 @@ import io.supertokens.ProcessState; import io.supertokens.emailverification.EmailVerification; import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.authRecipe.LoginMethod; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.thirdparty.UserInfo; @@ -104,7 +105,7 @@ public void testSignUpWithEmailNotVerifiedAndCheckEmailIsNotVerified() throws Ex checkSignInUpResponse(signUpResponse, thirdPartyUserId, thirdPartyId, email, true); assertFalse(EmailVerification.isEmailVerified(process.getProcess(), signUpResponse.user.id, - signUpResponse.user.email)); + signUpResponse.user.loginMethods[0].email)); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -156,7 +157,7 @@ public void testSignUpWithFalseVerifiedEmailAndSignInWithVerifiedEmail() throws checkSignInUpResponse(signUpResponse, thirdPartyUserId, thirdPartyId, email, true); assertFalse(EmailVerification.isEmailVerified(process.getProcess(), signUpResponse.user.id, - signUpResponse.user.email)); + signUpResponse.user.loginMethods[0].email)); ThirdParty.SignInUpResponse signInResponse = ThirdParty.signInUp(process.getProcess(), thirdPartyId, thirdPartyUserId, email); @@ -194,10 +195,10 @@ public void testUpdatingEmailAndCheckVerification() throws Exception { checkSignInUpResponse(signInResponse, thirdPartyUserId, thirdPartyId, email_2, false); assertFalse(EmailVerification.isEmailVerified(process.getProcess(), signInResponse.user.id, - signInResponse.user.email)); + signInResponse.user.loginMethods[0].email)); - UserInfo updatedUserInfo = ThirdParty.getUser(process.getProcess(), thirdPartyId, thirdPartyUserId); - assertEquals(updatedUserInfo.email, email_2); + AuthRecipeUserInfo updatedUserInfo = ThirdParty.getUser(process.getProcess(), thirdPartyId, thirdPartyUserId); + assertEquals(updatedUserInfo.loginMethods[0].email, email_2); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -391,17 +392,20 @@ public void testGetUserFunctions() throws Exception { UserInfo getUserInfoFromId = ThirdParty.getUser(process.getProcess(), signUpResponse.user.id); assertEquals(getUserInfoFromId.id, signUpResponse.user.id); assertEquals(getUserInfoFromId.timeJoined, signUpResponse.user.timeJoined); - assertEquals(getUserInfoFromId.email, signUpResponse.user.email); - assertEquals(getUserInfoFromId.thirdParty.userId, signUpResponse.user.thirdParty.userId); - assertEquals(getUserInfoFromId.thirdParty.id, signUpResponse.user.thirdParty.id); + assertEquals(getUserInfoFromId.email, signUpResponse.user.loginMethods[0].email); + assertEquals(getUserInfoFromId.thirdParty.userId, signUpResponse.user.loginMethods[0].thirdParty.userId); + assertEquals(getUserInfoFromId.thirdParty.id, signUpResponse.user.loginMethods[0].thirdParty.id); - UserInfo getUserInfoFromThirdParty = ThirdParty.getUser(process.getProcess(), signUpResponse.user.thirdParty.id, - signUpResponse.user.thirdParty.userId); + AuthRecipeUserInfo getUserInfoFromThirdParty = ThirdParty.getUser(process.getProcess(), + signUpResponse.user.loginMethods[0].thirdParty.id, + signUpResponse.user.loginMethods[0].thirdParty.userId); assertEquals(getUserInfoFromThirdParty.id, signUpResponse.user.id); assertEquals(getUserInfoFromThirdParty.timeJoined, signUpResponse.user.timeJoined); - assertEquals(getUserInfoFromThirdParty.email, signUpResponse.user.email); - assertEquals(getUserInfoFromThirdParty.thirdParty.userId, signUpResponse.user.thirdParty.userId); - assertEquals(getUserInfoFromThirdParty.thirdParty.id, signUpResponse.user.thirdParty.id); + assertEquals(getUserInfoFromThirdParty.loginMethods[0].email, signUpResponse.user.loginMethods[0].email); + assertEquals(getUserInfoFromThirdParty.loginMethods[0].thirdParty.userId, + signUpResponse.user.loginMethods[0].thirdParty.userId); + assertEquals(getUserInfoFromThirdParty.loginMethods[0].thirdParty.id, + signUpResponse.user.loginMethods[0].thirdParty.id); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -411,9 +415,9 @@ public static void checkSignInUpResponse(ThirdParty.SignInUpResponse response, S String thirdPartyId, String email, boolean createNewUser) { assertEquals(response.createdNewUser, createNewUser); assertNotNull(response.user.id); - assertEquals(response.user.thirdParty.userId, thirdPartyUserId); - assertEquals(response.user.thirdParty.id, thirdPartyId); - assertEquals(response.user.email, email); + assertEquals(response.user.loginMethods[0].thirdParty.userId, thirdPartyUserId); + assertEquals(response.user.loginMethods[0].thirdParty.id, thirdPartyId); + assertEquals(response.user.loginMethods[0].email, email); } } diff --git a/src/test/java/io/supertokens/test/thirdparty/ThirdPartyTest2_7.java b/src/test/java/io/supertokens/test/thirdparty/ThirdPartyTest2_7.java index b054bf890..e9c619eeb 100644 --- a/src/test/java/io/supertokens/test/thirdparty/ThirdPartyTest2_7.java +++ b/src/test/java/io/supertokens/test/thirdparty/ThirdPartyTest2_7.java @@ -19,6 +19,7 @@ import io.supertokens.ProcessState; import io.supertokens.emailverification.EmailVerification; import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.authRecipe.LoginMethod; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.thirdparty.UserInfo; @@ -104,7 +105,7 @@ public void testSignUpWithEmailNotVerifiedAndCheckEmailIsNotVerified() throws Ex checkSignInUpResponse(signUpResponse, thirdPartyUserId, thirdPartyId, email, true); assertFalse(EmailVerification.isEmailVerified(process.getProcess(), signUpResponse.user.id, - signUpResponse.user.email)); + signUpResponse.user.loginMethods[0].email)); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -130,7 +131,7 @@ public void testSignUpWithVerifiedEmailTrueAndCheckSignUp() throws Exception { thirdPartyUserId, email, true); checkSignInUpResponse(signUpResponse, thirdPartyUserId, thirdPartyId, email, true); assertTrue(EmailVerification.isEmailVerified(process.getProcess(), signUpResponse.user.id, - signUpResponse.user.email)); + signUpResponse.user.loginMethods[0].email)); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -158,14 +159,14 @@ public void testSignUpWithFalseVerifiedEmailAndSignInWithVerifiedEmail() throws checkSignInUpResponse(signUpResponse, thirdPartyUserId, thirdPartyId, email, true); assertFalse(EmailVerification.isEmailVerified(process.getProcess(), signUpResponse.user.id, - signUpResponse.user.email)); + signUpResponse.user.loginMethods[0].email)); ThirdParty.SignInUpResponse signInResponse = ThirdParty.signInUp2_7(process.getProcess(), thirdPartyId, thirdPartyUserId, email, true); checkSignInUpResponse(signInResponse, thirdPartyUserId, thirdPartyId, email, false); assertTrue(EmailVerification.isEmailVerified(process.getProcess(), signInResponse.user.id, - signInResponse.user.email)); + signInResponse.user.loginMethods[0].email)); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -195,17 +196,17 @@ public void testUpdatingEmailAndCheckVerification() throws Exception { checkSignInUpResponse(signUpResponse, thirdPartyUserId, thirdPartyId, email_1, true); assertTrue(EmailVerification.isEmailVerified(process.getProcess(), signUpResponse.user.id, - signUpResponse.user.email)); + signUpResponse.user.loginMethods[0].email)); ThirdParty.SignInUpResponse signInResponse = ThirdParty.signInUp2_7(process.getProcess(), thirdPartyId, thirdPartyUserId, email_2, false); checkSignInUpResponse(signInResponse, thirdPartyUserId, thirdPartyId, email_2, false); assertFalse(EmailVerification.isEmailVerified(process.getProcess(), signInResponse.user.id, - signInResponse.user.email)); + signInResponse.user.loginMethods[0].email)); - UserInfo updatedUserInfo = ThirdParty.getUser(process.getProcess(), thirdPartyId, thirdPartyUserId); - assertEquals(updatedUserInfo.email, email_2); + AuthRecipeUserInfo updatedUserInfo = ThirdParty.getUser(process.getProcess(), thirdPartyId, thirdPartyUserId); + assertEquals(updatedUserInfo.loginMethods[0].email, email_2); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -365,14 +366,14 @@ public void testSignUpWithVerifiedEmailSignInWithUnVerifiedEmail() throws Except checkSignInUpResponse(signUpResponse, thirdPartyUserId, thirdPartyId, email, true); assertTrue(EmailVerification.isEmailVerified(process.getProcess(), signUpResponse.user.id, - signUpResponse.user.email)); + signUpResponse.user.loginMethods[0].email)); ThirdParty.SignInUpResponse signInResponse = ThirdParty.signInUp2_7(process.getProcess(), thirdPartyId, thirdPartyUserId, email, false); checkSignInUpResponse(signInResponse, thirdPartyUserId, thirdPartyId, email, false); assertTrue(EmailVerification.isEmailVerified(process.getProcess(), signInResponse.user.id, - signInResponse.user.email)); + signInResponse.user.loginMethods[0].email)); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -403,17 +404,20 @@ public void testGetUserFunctions() throws Exception { UserInfo getUserInfoFromId = ThirdParty.getUser(process.getProcess(), signUpResponse.user.id); assertEquals(getUserInfoFromId.id, signUpResponse.user.id); assertEquals(getUserInfoFromId.timeJoined, signUpResponse.user.timeJoined); - assertEquals(getUserInfoFromId.email, signUpResponse.user.email); - assertEquals(getUserInfoFromId.thirdParty.userId, signUpResponse.user.thirdParty.userId); - assertEquals(getUserInfoFromId.thirdParty.id, signUpResponse.user.thirdParty.id); + assertEquals(getUserInfoFromId.email, signUpResponse.user.loginMethods[0].email); + assertEquals(getUserInfoFromId.thirdParty.userId, signUpResponse.user.loginMethods[0].thirdParty.userId); + assertEquals(getUserInfoFromId.thirdParty.id, signUpResponse.user.loginMethods[0].thirdParty.id); - UserInfo getUserInfoFromThirdParty = ThirdParty.getUser(process.getProcess(), signUpResponse.user.thirdParty.id, - signUpResponse.user.thirdParty.userId); + AuthRecipeUserInfo getUserInfoFromThirdParty = ThirdParty.getUser(process.getProcess(), + signUpResponse.user.loginMethods[0].thirdParty.id, + signUpResponse.user.loginMethods[0].thirdParty.userId); assertEquals(getUserInfoFromThirdParty.id, signUpResponse.user.id); assertEquals(getUserInfoFromThirdParty.timeJoined, signUpResponse.user.timeJoined); - assertEquals(getUserInfoFromThirdParty.email, signUpResponse.user.email); - assertEquals(getUserInfoFromThirdParty.thirdParty.userId, signUpResponse.user.thirdParty.userId); - assertEquals(getUserInfoFromThirdParty.thirdParty.id, signUpResponse.user.thirdParty.id); + assertEquals(getUserInfoFromThirdParty.loginMethods[0].email, signUpResponse.user.loginMethods[0].email); + assertEquals(getUserInfoFromThirdParty.loginMethods[0].thirdParty.userId, + signUpResponse.user.loginMethods[0].thirdParty.userId); + assertEquals(getUserInfoFromThirdParty.loginMethods[0].thirdParty.id, + signUpResponse.user.loginMethods[0].thirdParty.id); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -423,9 +427,9 @@ public static void checkSignInUpResponse(ThirdParty.SignInUpResponse response, S String thirdPartyId, String email, boolean createNewUser) { assertEquals(response.createdNewUser, createNewUser); assertNotNull(response.user.id); - assertEquals(response.user.thirdParty.userId, thirdPartyUserId); - assertEquals(response.user.thirdParty.id, thirdPartyId); - assertEquals(response.user.email, email); + assertEquals(response.user.loginMethods[0].thirdParty.userId, thirdPartyUserId); + assertEquals(response.user.loginMethods[0].thirdParty.id, thirdPartyId); + assertEquals(response.user.loginMethods[0].email, email); } } From 132ae3f12480a6300ee2c2be6c5c941e679fa09d Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Fri, 14 Jul 2023 13:21:42 +0530 Subject: [PATCH 018/131] removes unused interface func --- .../java/io/supertokens/inmemorydb/Start.java | 11 ---- .../inmemorydb/queries/GeneralQueries.java | 9 +++- .../inmemorydb/queries/ThirdPartyQueries.java | 50 ++++++++----------- .../io/supertokens/thirdparty/ThirdParty.java | 20 ++++++-- .../api/thirdparty/GetUsersByEmailAPI.java | 6 +-- 5 files changed, 47 insertions(+), 49 deletions(-) diff --git a/src/main/java/io/supertokens/inmemorydb/Start.java b/src/main/java/io/supertokens/inmemorydb/Start.java index 01150255d..a1935bc03 100644 --- a/src/main/java/io/supertokens/inmemorydb/Start.java +++ b/src/main/java/io/supertokens/inmemorydb/Start.java @@ -1148,17 +1148,6 @@ public void deleteThirdPartyUser(AppIdentifier appIdentifier, String userId) thr } } - @Override - public io.supertokens.pluginInterface.thirdparty.UserInfo[] getThirdPartyUsersByEmail( - TenantIdentifier tenantIdentifier, @NotNull String email) - throws StorageQueryException { - try { - return ThirdPartyQueries.getThirdPartyUsersByEmail(this, tenantIdentifier, email); - } catch (SQLException e) { - throw new StorageQueryException(e); - } - } - @Override public long getUsersCount(TenantIdentifier tenantIdentifier, RECIPE_ID[] includeRecipeIds) throws StorageQueryException { diff --git a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java index 51c5d2813..8d87182e4 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java @@ -871,11 +871,18 @@ public static AuthRecipeUserInfo[] listPrimaryUsersByEmail(Start start, TenantId userIds.add(passwordlessUserId); } - // TODO:add support for other recipes. + userIds.addAll(ThirdPartyQueries.getPrimaryUserIdUsingEmail(start, tenantIdentifier, email)); + + // remove duplicates from userIds + Set userIdsSet = new HashSet<>(userIds); + userIds = new ArrayList<>(userIdsSet); List result = getPrimaryUserInfoForUserIds(start, tenantIdentifier.toAppIdentifier(), userIds); + // this is going to order them based on oldest that joined to newest that joined. + result.sort(Comparator.comparingLong(o -> o.timeJoined)); + return result.toArray(new AuthRecipeUserInfo[0]); } diff --git a/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java index 91b81d330..bb4ea350b 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java @@ -28,7 +28,6 @@ import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.thirdparty.UserInfo; -import org.jetbrains.annotations.NotNull; import java.sql.Connection; import java.sql.ResultSet; @@ -297,36 +296,27 @@ private static UserInfoPartial getUserInfoUsingUserId(Start start, Connection co }); } - public static UserInfo[] getThirdPartyUsersByEmail(Start start, TenantIdentifier tenantIdentifier, - @NotNull String email) - throws SQLException, StorageQueryException { + public static List getPrimaryUserIdUsingEmail(Start start, TenantIdentifier tenantIdentifier, String email) + throws StorageQueryException, SQLException { + String QUERY = "SELECT DISTINCT all_users.primary_or_recipe_user_id AS user_id " + + "FROM " + getConfig(start).getThirdPartyUsersTable() + " AS tp" + + " JOIN " + getConfig(start).getUsersTable() + " AS all_users" + + " ON tp.app_id = all_users.app_id AND tp.user_id = all_users.user_id" + + " JOIN " + getConfig(start).getThirdPartyUserToTenantTable() + " AS tp_tenants" + + " ON tp_tenants.app_id = all_users.app_id AND tp_tenants.user_id = all_users.user_id" + + " WHERE tp.app_id = ? AND tp_tenants.tenant_id = ? AND tp.email = ?"; - String QUERY = "SELECT tp_users.user_id as user_id, tp_users.third_party_id as third_party_id, " - + "tp_users.third_party_user_id as third_party_user_id, tp_users.email as email, " - + "tp_users.time_joined as time_joined " - + "FROM " + getConfig(start).getThirdPartyUsersTable() + " AS tp_users " - + "JOIN " + getConfig(start).getThirdPartyUserToTenantTable() + " AS tp_users_to_tenant " - + "ON tp_users.app_id = tp_users_to_tenant.app_id AND tp_users.user_id = tp_users_to_tenant.user_id " - + "WHERE tp_users_to_tenant.app_id = ? AND tp_users_to_tenant.tenant_id = ? AND tp_users.email = ? " - + "ORDER BY time_joined"; - - try (Connection con = ConnectionPool.getConnection(start)) { - List userInfos = execute(con, QUERY.toString(), pst -> { - pst.setString(1, tenantIdentifier.getAppId()); - pst.setString(2, tenantIdentifier.getTenantId()); - pst.setString(3, email); - }, result -> { - List finalResult = new ArrayList<>(); - while (result.next()) { - finalResult.add(UserInfoRowMapper.getInstance().mapOrThrow(result)); - } - return finalResult; - }); - fillUserInfoWithTenantIds_transaction(start, con, tenantIdentifier.toAppIdentifier(), userInfos); - fillUserInfoWithVerified_transaction(start, con, tenantIdentifier.toAppIdentifier(), userInfos); - return userInfos.stream().map(x -> new UserInfo(x.id, false, x.toLoginMethod())) - .toArray(UserInfo[]::new); - } + return execute(start, QUERY, pst -> { + pst.setString(1, tenantIdentifier.getAppId()); + pst.setString(2, tenantIdentifier.getTenantId()); + pst.setString(3, email); + }, result -> { + List finalResult = new ArrayList<>(); + while (result.next()) { + finalResult.add(result.getString("user_id")); + } + return finalResult; + }); } public static boolean addUserIdToTenant_Transaction(Start start, Connection sqlCon, diff --git a/src/main/java/io/supertokens/thirdparty/ThirdParty.java b/src/main/java/io/supertokens/thirdparty/ThirdParty.java index e88203cee..c23158492 100644 --- a/src/main/java/io/supertokens/thirdparty/ThirdParty.java +++ b/src/main/java/io/supertokens/thirdparty/ThirdParty.java @@ -19,6 +19,7 @@ import io.supertokens.Main; import io.supertokens.multitenancy.Multitenancy; import io.supertokens.multitenancy.exception.BadPermissionException; +import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.authRecipe.LoginMethod; @@ -35,8 +36,10 @@ import org.jetbrains.annotations.TestOnly; import javax.annotation.Nonnull; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; +import java.util.List; public class ThirdParty { @@ -240,11 +243,20 @@ public static AuthRecipeUserInfo getUser(Main main, String thirdPartyId, String thirdPartyId, thirdPartyUserId); } - public static UserInfo[] getUsersByEmail(TenantIdentifierWithStorage tenantIdentifierWithStorage, - @Nonnull String email) + public static AuthRecipeUserInfo[] getUsersByEmail(TenantIdentifierWithStorage tenantIdentifierWithStorage, + @Nonnull String email) throws StorageQueryException { - return tenantIdentifierWithStorage.getThirdPartyStorage() - .getThirdPartyUsersByEmail(tenantIdentifierWithStorage, email); + AuthRecipeUserInfo[] users = tenantIdentifierWithStorage.getThirdPartyStorage() + .listPrimaryUsersByEmail(tenantIdentifierWithStorage, email); + List result = new ArrayList<>(); + for (AuthRecipeUserInfo user : users) { + for (LoginMethod lM : user.loginMethods) { + if (lM.recipeId == RECIPE_ID.THIRD_PARTY && lM.email.equals(email)) { + result.add(user); + } + } + } + return result.toArray(new AuthRecipeUserInfo[0]); } public static void verifyThirdPartyProvidersArray(ThirdPartyConfig.Provider[] providers) diff --git a/src/main/java/io/supertokens/webserver/api/thirdparty/GetUsersByEmailAPI.java b/src/main/java/io/supertokens/webserver/api/thirdparty/GetUsersByEmailAPI.java index ece2f2efe..d13a6184a 100644 --- a/src/main/java/io/supertokens/webserver/api/thirdparty/GetUsersByEmailAPI.java +++ b/src/main/java/io/supertokens/webserver/api/thirdparty/GetUsersByEmailAPI.java @@ -21,11 +21,11 @@ import com.google.gson.JsonObject; import io.supertokens.Main; import io.supertokens.pluginInterface.RECIPE_ID; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; -import io.supertokens.pluginInterface.thirdparty.UserInfo; import io.supertokens.thirdparty.ThirdParty; import io.supertokens.useridmapping.UserIdMapping; import io.supertokens.useridmapping.UserIdType; @@ -61,7 +61,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO String email = InputParser.getQueryParamOrThrowError(req, "email", false); email = Utils.normaliseEmail(email); - UserInfo[] users = ThirdParty.getUsersByEmail(tenantIdentifierWithStorage, email); + AuthRecipeUserInfo[] users = ThirdParty.getUsersByEmail(tenantIdentifierWithStorage, email); // return the externalUserId if a mapping exists for a user for (int i = 0; i < users.length; i++) { @@ -77,7 +77,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO JsonObject result = new JsonObject(); result.addProperty("status", "OK"); JsonArray usersJson = new JsonArray(); - for (UserInfo userInfo : users) { + for (AuthRecipeUserInfo userInfo : users) { usersJson.add(getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v4_0) ? userInfo.toJson() : userInfo.toJsonWithoutAccountLinking()); } From 55f0a8776edb0837337b4a2b74ab0aee0449a987 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Sat, 15 Jul 2023 12:31:15 +0530 Subject: [PATCH 019/131] adds migration script to changelog --- CHANGELOG.md | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index de4d32a0c..e07f0fa9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,23 +7,46 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [unreleased] +### Db schema changes: -- Db schema changes: - - Added new index `all_auth_recipe_users_primary_user_id_index`. - - Added new index `all_auth_recipe_users_primary_user_id_and_tenant_id_index`. - - Modified `all_auth_recipe_users_pagination_index` index to be on `primary_or_recipe_user_id` instead of `user_id` - - Added a two new columns in `all_auth_recipe_users`: +- Added new index `all_auth_recipe_users_primary_user_id_index`. +- Added new index `all_auth_recipe_users_primary_user_id_and_tenant_id_index`. +- Modified `all_auth_recipe_users_pagination_index` index to be on `primary_or_recipe_user_id` instead of `user_id` +- Added a two new columns in `all_auth_recipe_users`: - `primary_or_recipe_user_id` (default value is equal to `user_id` column) - `is_linked_or_is_a_primary_user` (default value is false) +### DB migration: + +```sql +ALTER TABLE all_auth_recipe_users + ADD COLUMN primary_or_recipe_user_id CHAR(36) NOT NULL DEFAULT ('0'); + +ALTER TABLE all_auth_recipe_users + ADD COLUMN is_linked_or_is_a_primary_user BOOLEAN NOT NULL DEFAULT FALSE; + +UPDATE all_auth_recipe_users +SET primary_or_recipe_user_id = user_id +WHERE primary_or_recipe_user_id = '0'; + +ALTER TABLE all_auth_recipe_users + ALTER primary_or_recipe_user_id DROP DEFAULT; + +DROP INDEX all_auth_recipe_users_pagination_index; +CREATE INDEX all_auth_recipe_users_pagination_index ON all_auth_recipe_users (time_joined DESC, + primary_or_recipe_user_id DESC, tenant_id + DESC, app_id DESC); +CREATE INDEX all_auth_recipe_users_primary_user_id_index ON all_auth_recipe_users (app_id, primary_or_recipe_user_id); +CREATE INDEX all_auth_recipe_users_primary_user_id_and_tenant_id_index ON all_auth_recipe_users (app_id, tenant_id, primary_or_recipe_user_id); +``` ## [6.0.4] - 2023-07-13 + - Fixes tenant prefix in stack trace log - `supertokens_default_cdi_version` config renamed to `supertokens_max_cdi_version` - Fixes `/apiversion` GET to return versions until `supertokens_max_cdi_version` if set - Fixes `/recipe/multitenancy/tenant` GET to return `TENANT_NOT_FOUND_ERROR` with 200 status when tenant was not found - ## [6.0.3] - 2023-07-11 - Fixes duplicate users in users search queries when user is associated to multiple tenants From 9a8b9ca9f468582fe7a1cb5a9a144cc4cea90704 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Sat, 15 Jul 2023 14:00:23 +0530 Subject: [PATCH 020/131] small changes --- .../inmemorydb/queries/GeneralQueries.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java index 8d87182e4..4f2e659f1 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java @@ -90,11 +90,20 @@ static String getQueryToCreateUserPaginationIndex(Start start) { } static String getQueryToCreatePrimaryUserIdIndex(Start start) { + /* + * Used in: + * + * */ return "CREATE INDEX all_auth_recipe_users_primary_user_id_index ON " + Config.getConfig(start).getUsersTable() + "(app_id, primary_or_recipe_user_id);"; } static String getQueryToCreatePrimaryUserIdAndTenantIndex(Start start) { + /* + * Used in: + * - user count query + * + * */ return "CREATE INDEX all_auth_recipe_users_primary_user_id_and_tenant_id_index ON " + Config.getConfig(start).getUsersTable() + "(app_id, tenant_id, primary_or_recipe_user_id);"; @@ -836,7 +845,7 @@ public static AuthRecipeUserInfo[] getUsers(Start start, TenantIdentifier tenant // we give the userId[] for each recipe to fetch all those user's details for (RECIPE_ID recipeId : recipeIdToUserIdListMap.keySet()) { - List users = getPrimaryUserInfoForUserIds(start, + List users = getPrimaryUserInfoForUserIds(start, tenantIdentifier.toAppIdentifier(), recipeIdToUserIdListMap.get(recipeId)); From 8d4e2e33908a635268dae3e212937b50b64f3064 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Sat, 15 Jul 2023 14:15:13 +0530 Subject: [PATCH 021/131] refactor --- .../emailpassword/EmailPassword.java | 15 +++------ .../queries/EmailPasswordQueries.java | 4 +-- .../queries/PasswordlessQueries.java | 32 +------------------ .../inmemorydb/queries/ThirdPartyQueries.java | 4 +-- 4 files changed, 7 insertions(+), 48 deletions(-) diff --git a/src/main/java/io/supertokens/emailpassword/EmailPassword.java b/src/main/java/io/supertokens/emailpassword/EmailPassword.java index 4ae9795ee..f53eaf2db 100644 --- a/src/main/java/io/supertokens/emailpassword/EmailPassword.java +++ b/src/main/java/io/supertokens/emailpassword/EmailPassword.java @@ -51,7 +51,6 @@ import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.spec.InvalidKeySpecException; -import java.util.Arrays; public class EmailPassword { @@ -175,9 +174,7 @@ public static ImportUserResponse importUserWithPasswordHash(TenantIdentifierWith LoginMethod loginMethod = null; for (AuthRecipeUserInfo currUser : allUsers) { for (LoginMethod currLM : currUser.loginMethods) { - if (currLM.email.equals(email) && currLM.recipeId == RECIPE_ID.EMAIL_PASSWORD && - Arrays.stream(currLM.tenantIds) - .anyMatch(s -> s.equals(tenantIdentifierWithStorage.getTenantId()))) { + if (currLM.email.equals(email) && currLM.recipeId == RECIPE_ID.EMAIL_PASSWORD) { userInfoToBeUpdated = currUser; loginMethod = currLM; break; @@ -246,16 +243,14 @@ public static AuthRecipeUserInfo signIn(TenantIdentifierWithStorage tenantIdenti LoginMethod lM = null; for (AuthRecipeUserInfo currUser : users) { for (LoginMethod currLM : currUser.loginMethods) { - if (currLM.recipeId == RECIPE_ID.EMAIL_PASSWORD && currLM.email.equals(email) && - Arrays.stream(currLM.tenantIds) - .anyMatch(s -> s.equals(tenantIdentifierWithStorage.getTenantId()))) { + if (currLM.recipeId == RECIPE_ID.EMAIL_PASSWORD && currLM.email.equals(email)) { user = currUser; lM = currLM; } } } - if (user == null || lM == null) { + if (user == null) { throw new WrongCredentialsException(); } @@ -511,9 +506,7 @@ public static AuthRecipeUserInfo getUserUsingEmail(TenantIdentifierWithStorage t // filter used based on login method for (AuthRecipeUserInfo user : users) { for (LoginMethod lM : user.loginMethods) { - if (lM.email.equals(email) && lM.recipeId == RECIPE_ID.EMAIL_PASSWORD && - Arrays.stream(lM.tenantIds).anyMatch( - tenantId -> tenantId.equals(tenantIdentifierWithStorage.getTenantId()))) { + if (lM.email.equals(email) && lM.recipeId == RECIPE_ID.EMAIL_PASSWORD) { return user; } } diff --git a/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java index 36c76f1af..0bfcf782b 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java @@ -315,10 +315,8 @@ public static UserInfo signUp(Start start, TenantIdentifier tenantIdentifier, St UserInfoPartial userInfo = new UserInfoPartial(userId, email, passwordHash, timeJoined); fillUserInfoWithTenantIds_transaction(start, sqlCon, tenantIdentifier.toAppIdentifier(), userInfo); fillUserInfoWithVerified_transaction(start, sqlCon, tenantIdentifier.toAppIdentifier(), userInfo); - fillUserInfoWithIsPrimaryUserBoolean_transaction(start, sqlCon, tenantIdentifier.toAppIdentifier(), - userInfo); sqlCon.commit(); - return new UserInfo(userId, userInfo.isPrimary, userInfo.toLoginMethod()); + return new UserInfo(userId, false, userInfo.toLoginMethod()); } catch (SQLException throwables) { throw new StorageTransactionLogicException(throwables); } diff --git a/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java index 86d41dc84..83fd169b1 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java @@ -423,10 +423,8 @@ public static UserInfo createUser(Start start, TenantIdentifier tenantIdentifier UserInfoPartial userInfo = new UserInfoPartial(id, email, phoneNumber, timeJoined); fillUserInfoWithTenantIds_transaction(start, sqlCon, tenantIdentifier.toAppIdentifier(), userInfo); fillUserInfoWithVerified_transaction(start, sqlCon, tenantIdentifier.toAppIdentifier(), userInfo); - fillUserInfoWithIsPrimaryUserBoolean_transaction(start, sqlCon, tenantIdentifier.toAppIdentifier(), - userInfo); sqlCon.commit(); - return new UserInfo(id, userInfo.isPrimary, + return new UserInfo(id, false, userInfo.toLoginMethod()); } catch (SQLException throwables) { throw new StorageTransactionLogicException(throwables); @@ -896,34 +894,6 @@ private static List fillUserInfoWithTenantIds_transaction(Start return userInfos; } - private static UserInfoPartial fillUserInfoWithIsPrimaryUserBoolean_transaction(Start start, Connection sqlCon, - AppIdentifier appIdentifier, - UserInfoPartial userInfo) - throws SQLException, StorageQueryException { - if (userInfo == null) return null; - return fillUserInfoWithIsPrimaryUserBoolean_transaction(start, sqlCon, appIdentifier, - Arrays.asList(userInfo)).get(0); - } - - private static List fillUserInfoWithIsPrimaryUserBoolean_transaction(Start start, - Connection sqlCon, - AppIdentifier appIdentifier, - List userInfos) - throws SQLException, StorageQueryException { - String[] userIds = new String[userInfos.size()]; - for (int i = 0; i < userInfos.size(); i++) { - userIds[i] = userInfos.get(i).id; - } - - Map isPrimaryUserForUserIds = GeneralQueries.getIsPrimaryUserBoolean_transaction(start, sqlCon, - appIdentifier, - userIds); - for (UserInfoPartial userInfo : userInfos) { - userInfo.isPrimary = isPrimaryUserForUserIds.get(userInfo.id); - } - return userInfos; - } - private static class PasswordlessDeviceRowMapper implements RowMapper { private static final PasswordlessDeviceRowMapper INSTANCE = new PasswordlessDeviceRowMapper(); diff --git a/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java index bb4ea350b..461bdd4f6 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java @@ -144,10 +144,8 @@ public static UserInfo signUp(Start start, TenantIdentifier tenantIdentifier, St UserInfoPartial userInfo = new UserInfoPartial(id, email, thirdParty, timeJoined); fillUserInfoWithTenantIds_transaction(start, sqlCon, tenantIdentifier.toAppIdentifier(), userInfo); fillUserInfoWithVerified_transaction(start, sqlCon, tenantIdentifier.toAppIdentifier(), userInfo); - fillUserInfoWithIsPrimaryUserBoolean_transaction(start, sqlCon, tenantIdentifier.toAppIdentifier(), - userInfo); sqlCon.commit(); - return new UserInfo(id, userInfo.isPrimary, userInfo.toLoginMethod()); + return new UserInfo(id, false, userInfo.toLoginMethod()); } catch (SQLException throwables) { throw new StorageTransactionLogicException(throwables); From 2e2feead2d85f9b1d96393fb8a531601acc237fd Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Sat, 15 Jul 2023 14:31:32 +0530 Subject: [PATCH 022/131] removes unneeded functions --- .../emailpassword/EmailPassword.java | 5 +- .../java/io/supertokens/inmemorydb/Start.java | 10 ++-- .../queries/EmailPasswordQueries.java | 51 +++---------------- .../inmemorydb/queries/GeneralQueries.java | 38 -------------- .../inmemorydb/queries/ThirdPartyQueries.java | 48 +++-------------- .../io/supertokens/thirdparty/ThirdParty.java | 13 ++--- .../api/TestTenantUserAssociation.java | 36 ++++++------- 7 files changed, 43 insertions(+), 158 deletions(-) diff --git a/src/main/java/io/supertokens/emailpassword/EmailPassword.java b/src/main/java/io/supertokens/emailpassword/EmailPassword.java index f53eaf2db..0f1d8266b 100644 --- a/src/main/java/io/supertokens/emailpassword/EmailPassword.java +++ b/src/main/java/io/supertokens/emailpassword/EmailPassword.java @@ -431,10 +431,11 @@ public static void updateUsersEmailOrPassword(AppIdentifierWithStorage appIdenti try { storage.startTransaction(transaction -> { try { - UserInfo userInfo = storage.getUserInfoUsingId_Transaction(appIdentifierWithStorage, transaction, + boolean exists = storage.lockEmailPasswordTableUsingId_Transaction(appIdentifierWithStorage, + transaction, userId); - if (userInfo == null) { + if (!exists) { throw new StorageTransactionLogicException(new UnknownUserIdException()); } diff --git a/src/main/java/io/supertokens/inmemorydb/Start.java b/src/main/java/io/supertokens/inmemorydb/Start.java index a1935bc03..b870e2137 100644 --- a/src/main/java/io/supertokens/inmemorydb/Start.java +++ b/src/main/java/io/supertokens/inmemorydb/Start.java @@ -870,12 +870,12 @@ public void updateUsersEmail_Transaction(AppIdentifier appIdentifier, Transactio } @Override - public UserInfo getUserInfoUsingId_Transaction(AppIdentifier appIdentifier, TransactionConnection con, - String userId) + public boolean lockEmailPasswordTableUsingId_Transaction(AppIdentifier appIdentifier, TransactionConnection con, + String userId) throws StorageQueryException { Connection sqlCon = (Connection) con.getConnection(); try { - return EmailPasswordQueries.getUserInfoUsingId_Transaction(this, sqlCon, appIdentifier, userId); + return EmailPasswordQueries.lockEmailPasswordTableUsingId_Transaction(this, sqlCon, appIdentifier, userId); } catch (SQLException e) { throw new StorageQueryException(e); } @@ -1065,14 +1065,14 @@ public void deleteExpiredPasswordResetTokens() throws StorageQueryException { } @Override - public io.supertokens.pluginInterface.thirdparty.UserInfo getUserInfoUsingId_Transaction( + public String getEmailUsingThirdPartyInfo_Transaction( AppIdentifier appIdentifier, TransactionConnection con, String thirdPartyId, String thirdPartyUserId) throws StorageQueryException { Connection sqlCon = (Connection) con.getConnection(); try { - return ThirdPartyQueries.getUserInfoUsingId_Transaction(this, sqlCon, appIdentifier, thirdPartyId, + return ThirdPartyQueries.getEmailUsingThirdPartyInfo_Transaction(this, sqlCon, appIdentifier, thirdPartyId, thirdPartyUserId); } catch (SQLException e) { throw new StorageQueryException(e); diff --git a/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java index 0bfcf782b..247015f1b 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java @@ -199,32 +199,21 @@ public static PasswordResetTokenInfo[] getAllPasswordResetTokenInfoForUser_Trans }); } - public static UserInfo getUserInfoUsingId_Transaction(Start start, Connection con, AppIdentifier appIdentifier, - String id) + public static boolean lockEmailPasswordTableUsingId_Transaction(Start start, Connection con, + AppIdentifier appIdentifier, + String id) throws SQLException, StorageQueryException { ((ConnectionWithLocks) con).lock( appIdentifier.getAppId() + "~" + id + Config.getConfig(start).getEmailPasswordUsersTable()); - String QUERY = "SELECT user_id, email, password_hash, time_joined FROM " + String QUERY = "SELECT user_id FROM " + getConfig(start).getEmailPasswordUsersTable() + " WHERE app_id = ? AND user_id = ?"; - UserInfoPartial userInfo = execute(con, QUERY, pst -> { + return execute(con, QUERY, pst -> { pst.setString(1, appIdentifier.getAppId()); pst.setString(2, id); - }, result -> { - if (result.next()) { - return UserInfoRowMapper.getInstance().mapOrThrow(result); - } - return null; - }); - if (userInfo == null) { - return null; - } - fillUserInfoWithTenantIds_transaction(start, con, appIdentifier, userInfo); - fillUserInfoWithVerified_transaction(start, con, appIdentifier, userInfo); - fillUserInfoWithIsPrimaryUserBoolean_transaction(start, con, appIdentifier, userInfo); - return new UserInfo(userInfo.id, userInfo.isPrimary, userInfo.toLoginMethod()); + }, ResultSet::next); } public static PasswordResetTokenInfo getPasswordResetTokenInfo(Start start, AppIdentifier appIdentifier, @@ -526,34 +515,6 @@ private static List fillUserInfoWithTenantIds_transaction(Start return userInfos; } - private static UserInfoPartial fillUserInfoWithIsPrimaryUserBoolean_transaction(Start start, Connection sqlCon, - AppIdentifier appIdentifier, - UserInfoPartial userInfo) - throws SQLException, StorageQueryException { - if (userInfo == null) return null; - return fillUserInfoWithIsPrimaryUserBoolean_transaction(start, sqlCon, appIdentifier, - Arrays.asList(userInfo)).get(0); - } - - private static List fillUserInfoWithIsPrimaryUserBoolean_transaction(Start start, - Connection sqlCon, - AppIdentifier appIdentifier, - List userInfos) - throws SQLException, StorageQueryException { - String[] userIds = new String[userInfos.size()]; - for (int i = 0; i < userInfos.size(); i++) { - userIds[i] = userInfos.get(i).id; - } - - Map isPrimaryUserForUserIds = GeneralQueries.getIsPrimaryUserBoolean_transaction(start, sqlCon, - appIdentifier, - userIds); - for (UserInfoPartial userInfo : userInfos) { - userInfo.isPrimary = isPrimaryUserForUserIds.get(userInfo.id); - } - return userInfos; - } - private static class UserInfoPartial { public final String id; public final long timeJoined; diff --git a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java index 4f2e659f1..5ada36145 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java @@ -1082,44 +1082,6 @@ public static Map> getTenantIdsForUserIds_transaction(Start return new HashMap<>(); } - public static Map getIsPrimaryUserBoolean_transaction(Start start, Connection sqlCon, - AppIdentifier appIdentifier, - String[] userIds) - throws SQLException, StorageQueryException { - if (userIds != null && userIds.length > 0) { - StringBuilder QUERY = new StringBuilder("SELECT user_id, is_linked_or_is_a_primary_user " - + "FROM " + getConfig(start).getUsersTable()); - QUERY.append(" WHERE user_id IN ("); - for (int i = 0; i < userIds.length; i++) { - - QUERY.append("?"); - if (i != userIds.length - 1) { - // not the last element - QUERY.append(","); - } - } - QUERY.append(") AND app_id = ?"); - - return execute(sqlCon, QUERY.toString(), pst -> { - for (int i = 0; i < userIds.length; i++) { - // i+1 cause this starts with 1 and not 0 - pst.setString(i + 1, userIds[i]); - } - pst.setString(userIds.length + 1, appIdentifier.getAppId()); - }, result -> { - Map finalResult = new HashMap<>(); - while (result.next()) { - String userId = result.getString("user_id").trim(); - Boolean isLinkedOrPrimaryUser = result.getBoolean("is_linked_or_is_a_primary_user"); - finalResult.put(userId, isLinkedOrPrimaryUser); - } - return finalResult; - }); - } - - return new HashMap<>(); - } - @TestOnly public static String[] getAllTablesInTheDatabase(Start start) throws SQLException, StorageQueryException { if (!Start.isTesting) { diff --git a/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java index 461bdd4f6..fd788c364 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java @@ -243,35 +243,27 @@ public static void updateUserEmail_Transaction(Start start, Connection con, AppI }); } - public static UserInfo getUserInfoUsingId_Transaction(Start start, Connection con, - AppIdentifier appIdentifier, String thirdPartyId, - String thirdPartyUserId) + public static String getEmailUsingThirdPartyInfo_Transaction(Start start, Connection con, + AppIdentifier appIdentifier, String thirdPartyId, + String thirdPartyUserId) throws SQLException, StorageQueryException { ((ConnectionWithLocks) con).lock(appIdentifier.getAppId() + "~" + thirdPartyId + "~" + thirdPartyUserId + Config.getConfig(start).getThirdPartyUsersTable()); - String QUERY = "SELECT user_id, third_party_id, third_party_user_id, email, time_joined FROM " + String QUERY = "SELECT email FROM " + getConfig(start).getThirdPartyUsersTable() + " WHERE app_id = ? AND third_party_id = ? AND third_party_user_id = ?"; - UserInfoPartial userInfo = execute(con, QUERY, pst -> { + return execute(con, QUERY, pst -> { pst.setString(1, appIdentifier.getAppId()); pst.setString(2, thirdPartyId); pst.setString(3, thirdPartyUserId); }, result -> { if (result.next()) { - return UserInfoRowMapper.getInstance().mapOrThrow(result); + return result.getString("email"); } return null; }); - if (userInfo == null) { - return null; - } - fillUserInfoWithTenantIds_transaction(start, con, appIdentifier, userInfo); - fillUserInfoWithVerified_transaction(start, con, appIdentifier, userInfo); - fillUserInfoWithIsPrimaryUserBoolean_transaction(start, con, appIdentifier, - userInfo); - return new UserInfo(userInfo.id, userInfo.isPrimary, userInfo.toLoginMethod()); } private static UserInfoPartial getUserInfoUsingUserId(Start start, Connection con, @@ -428,34 +420,6 @@ private static List fillUserInfoWithTenantIds_transaction(Start return userInfos; } - private static UserInfoPartial fillUserInfoWithIsPrimaryUserBoolean_transaction(Start start, Connection sqlCon, - AppIdentifier appIdentifier, - UserInfoPartial userInfo) - throws SQLException, StorageQueryException { - if (userInfo == null) return null; - return fillUserInfoWithIsPrimaryUserBoolean_transaction(start, sqlCon, appIdentifier, - Arrays.asList(userInfo)).get(0); - } - - private static List fillUserInfoWithIsPrimaryUserBoolean_transaction(Start start, - Connection sqlCon, - AppIdentifier appIdentifier, - List userInfos) - throws SQLException, StorageQueryException { - String[] userIds = new String[userInfos.size()]; - for (int i = 0; i < userInfos.size(); i++) { - userIds[i] = userInfos.get(i).id; - } - - Map isPrimaryUserForUserIds = GeneralQueries.getIsPrimaryUserBoolean_transaction(start, sqlCon, - appIdentifier, - userIds); - for (UserInfoPartial userInfo : userInfos) { - userInfo.isPrimary = isPrimaryUserForUserIds.get(userInfo.id); - } - return userInfos; - } - private static class UserInfoPartial { public final String id; public final String email; diff --git a/src/main/java/io/supertokens/thirdparty/ThirdParty.java b/src/main/java/io/supertokens/thirdparty/ThirdParty.java index c23158492..8e7c51b42 100644 --- a/src/main/java/io/supertokens/thirdparty/ThirdParty.java +++ b/src/main/java/io/supertokens/thirdparty/ThirdParty.java @@ -162,7 +162,6 @@ private static SignInUpResponse signInUpHelper(TenantIdentifierWithStorage tenan } // we try to get user and update their email - SignInUpResponse response = null; try { // We should update the user email based on thirdPartyId and thirdPartyUserId across all apps, // so we iterate through all the app storages and do the update. @@ -173,15 +172,17 @@ private static SignInUpResponse signInUpHelper(TenantIdentifierWithStorage tenan Storage[] storages = StorageLayer.getStoragesForApp(main, appIdentifier); for (Storage st : storages) { storage.startTransaction(con -> { - UserInfo user = storage.getUserInfoUsingId_Transaction(appIdentifier.withStorage(st), con, + String emailFromDb = storage.getEmailUsingThirdPartyInfo_Transaction( + appIdentifier.withStorage(st), + con, thirdPartyId, thirdPartyUserId); - if (user == null) { + if (emailFromDb == null) { storage.commitTransaction(con); return null; } - if (!email.equals(user.email)) { + if (!email.equals(emailFromDb)) { storage.updateUserEmail_Transaction(appIdentifier.withStorage(st), con, thirdPartyId, thirdPartyUserId, email); } @@ -196,10 +197,6 @@ private static SignInUpResponse signInUpHelper(TenantIdentifierWithStorage tenan } catch (StorageTransactionLogicException ignored) { } - if (response != null) { - return response; - } - // retry.. } } diff --git a/src/test/java/io/supertokens/test/multitenant/api/TestTenantUserAssociation.java b/src/test/java/io/supertokens/test/multitenant/api/TestTenantUserAssociation.java index c82235e2a..c9859b18b 100644 --- a/src/test/java/io/supertokens/test/multitenant/api/TestTenantUserAssociation.java +++ b/src/test/java/io/supertokens/test/multitenant/api/TestTenantUserAssociation.java @@ -277,19 +277,19 @@ public void testEmailPasswordUsersHaveTenantIds() throws Exception { AuthRecipeUserInfo user = EmailPassword.signUp(t1WithStorage, process.getProcess(), "user@example.com", "password"); - assertArrayEquals(new String[]{"t1"}, user.tenantIds); + assertArrayEquals(new String[]{"t1"}, user.tenantIds.toArray()); Multitenancy.addUserIdToTenant(process.getProcess(), t2WithStorage, user.id); user = EmailPassword.getUserUsingId(t1WithStorage.toAppIdentifierWithStorage(), user.id); - Utils.assertArrayEqualsIgnoreOrder(new String[]{"t1", "t2"}, user.tenantIds); + Utils.assertArrayEqualsIgnoreOrder(new String[]{"t1", "t2"}, user.tenantIds.toArray()); user = EmailPassword.getUserUsingEmail(t1WithStorage, user.loginMethods[0].email); - Utils.assertArrayEqualsIgnoreOrder(new String[]{"t1", "t2"}, user.tenantIds); + Utils.assertArrayEqualsIgnoreOrder(new String[]{"t1", "t2"}, user.tenantIds.toArray()); Multitenancy.removeUserIdFromTenant(process.getProcess(), t1WithStorage, user.id); user = EmailPassword.getUserUsingId(t1WithStorage.toAppIdentifierWithStorage(), user.id); - assertArrayEquals(new String[]{"t2"}, user.tenantIds); + assertArrayEquals(new String[]{"t2"}, user.tenantIds.toArray()); } @Test @@ -311,19 +311,19 @@ public void testPasswordlessUsersHaveTenantIds1() throws Exception { Passwordless.ConsumeCodeResponse consumeCodeResponse = Passwordless.consumeCode(t1WithStorage, process.getProcess(), createCodeResponse.deviceId, createCodeResponse.deviceIdHash, createCodeResponse.userInputCode, null); - assertArrayEquals(new String[]{"t1"}, consumeCodeResponse.user.tenantIds); + assertArrayEquals(new String[]{"t1"}, consumeCodeResponse.user.tenantIds.toArray()); AuthRecipeUserInfo user; Multitenancy.addUserIdToTenant(process.getProcess(), t2WithStorage, consumeCodeResponse.user.id); user = Passwordless.getUserById(t1WithStorage.toAppIdentifierWithStorage(), consumeCodeResponse.user.id); - Utils.assertArrayEqualsIgnoreOrder(new String[]{"t1", "t2"}, user.tenantIds); + Utils.assertArrayEqualsIgnoreOrder(new String[]{"t1", "t2"}, user.tenantIds.toArray()); user = Passwordless.getUserByEmail(t1WithStorage, consumeCodeResponse.user.loginMethods[0].email); - Utils.assertArrayEqualsIgnoreOrder(new String[]{"t1", "t2"}, user.tenantIds); + Utils.assertArrayEqualsIgnoreOrder(new String[]{"t1", "t2"}, user.tenantIds.toArray()); Multitenancy.removeUserIdFromTenant(process.getProcess(), t1WithStorage, consumeCodeResponse.user.id); user = Passwordless.getUserById(t1WithStorage.toAppIdentifierWithStorage(), consumeCodeResponse.user.id); - assertArrayEquals(new String[]{"t2"}, user.tenantIds); + assertArrayEquals(new String[]{"t2"}, user.tenantIds.toArray()); } @Test @@ -345,19 +345,19 @@ public void testPasswordlessUsersHaveTenantIds2() throws Exception { Passwordless.ConsumeCodeResponse consumeCodeResponse = Passwordless.consumeCode(t1WithStorage, process.getProcess(), createCodeResponse.deviceId, createCodeResponse.deviceIdHash, createCodeResponse.userInputCode, null); - assertArrayEquals(new String[]{"t1"}, consumeCodeResponse.user.tenantIds); + assertArrayEquals(new String[]{"t1"}, consumeCodeResponse.user.tenantIds.toArray()); AuthRecipeUserInfo user; Multitenancy.addUserIdToTenant(process.getProcess(), t2WithStorage, consumeCodeResponse.user.id); user = Passwordless.getUserById(t1WithStorage.toAppIdentifierWithStorage(), consumeCodeResponse.user.id); - Utils.assertArrayEqualsIgnoreOrder(new String[]{"t1", "t2"}, user.tenantIds); + Utils.assertArrayEqualsIgnoreOrder(new String[]{"t1", "t2"}, user.tenantIds.toArray()); user = Passwordless.getUserByPhoneNumber(t1WithStorage, consumeCodeResponse.user.loginMethods[0].phoneNumber); - Utils.assertArrayEqualsIgnoreOrder(new String[]{"t1", "t2"}, user.tenantIds); + Utils.assertArrayEqualsIgnoreOrder(new String[]{"t1", "t2"}, user.tenantIds.toArray()); Multitenancy.removeUserIdFromTenant(process.getProcess(), t1WithStorage, consumeCodeResponse.user.id); user = Passwordless.getUserById(t1WithStorage.toAppIdentifierWithStorage(), consumeCodeResponse.user.id); - assertArrayEquals(new String[]{"t2"}, user.tenantIds); + assertArrayEquals(new String[]{"t2"}, user.tenantIds.toArray()); } @Test @@ -377,25 +377,25 @@ public void testThirdPartyUsersHaveTenantIds() throws Exception { ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(t1WithStorage, process.getProcess(), "google", "googleid", "user@example.com"); - assertArrayEquals(new String[]{"t1"}, signInUpResponse.user.tenantIds); + assertArrayEquals(new String[]{"t1"}, signInUpResponse.user.tenantIds.toArray()); Multitenancy.addUserIdToTenant(process.getProcess(), t2WithStorage, signInUpResponse.user.id); AuthRecipeUserInfo user = ThirdParty.getUser( t1WithStorage.toAppIdentifierWithStorage(), signInUpResponse.user.id); - Utils.assertArrayEqualsIgnoreOrder(new String[]{"t1", "t2"}, user.tenantIds); + Utils.assertArrayEqualsIgnoreOrder(new String[]{"t1", "t2"}, user.tenantIds.toArray()); user = ThirdParty.getUsersByEmail(t1WithStorage, signInUpResponse.user.loginMethods[0].email)[0]; - Utils.assertArrayEqualsIgnoreOrder(new String[]{"t1", "t2"}, user.tenantIds); + Utils.assertArrayEqualsIgnoreOrder(new String[]{"t1", "t2"}, user.tenantIds.toArray()); user = ThirdParty.getUser(t1WithStorage, "google", "googleid"); - Utils.assertArrayEqualsIgnoreOrder(new String[]{"t1", "t2"}, user.tenantIds); + Utils.assertArrayEqualsIgnoreOrder(new String[]{"t1", "t2"}, user.tenantIds.toArray()); user = ThirdParty.getUser(t2WithStorage, "google", "googleid"); - Utils.assertArrayEqualsIgnoreOrder(new String[]{"t1", "t2"}, user.tenantIds); + Utils.assertArrayEqualsIgnoreOrder(new String[]{"t1", "t2"}, user.tenantIds.toArray()); Multitenancy.removeUserIdFromTenant(process.getProcess(), t1WithStorage, signInUpResponse.user.id); user = ThirdParty.getUser(t1WithStorage.toAppIdentifierWithStorage(), signInUpResponse.user.id); - assertArrayEquals(new String[]{"t2"}, user.tenantIds); + assertArrayEquals(new String[]{"t2"}, user.tenantIds.toArray()); } @Test From a86a63fbe29c5ef2b4e8a8e631154ffa7b6a2853 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Mon, 17 Jul 2023 10:44:08 +0530 Subject: [PATCH 023/131] adds user get API --- .../io/supertokens/authRecipe/AuthRecipe.java | 14 +++ .../emailpassword/EmailPassword.java | 3 + .../passwordless/Passwordless.java | 3 + .../io/supertokens/thirdparty/ThirdParty.java | 5 +- .../io/supertokens/webserver/Webserver.java | 7 +- .../webserver/api/core/GetUserByIdAPI.java | 100 ++++++++++++++++++ .../api/core/ListUsersByAccountInfoAPI.java | 83 +++++++++++++++ .../webserver/api/emailpassword/UserAPI.java | 1 + .../webserver/api/passwordless/UserAPI.java | 1 + .../api/thirdparty/GetUsersByEmailAPI.java | 1 + .../webserver/api/thirdparty/UserAPI.java | 1 + 11 files changed, 214 insertions(+), 5 deletions(-) create mode 100644 src/main/java/io/supertokens/webserver/api/core/GetUserByIdAPI.java create mode 100644 src/main/java/io/supertokens/webserver/api/core/ListUsersByAccountInfoAPI.java diff --git a/src/main/java/io/supertokens/authRecipe/AuthRecipe.java b/src/main/java/io/supertokens/authRecipe/AuthRecipe.java index eef8e7c63..6ff11db5d 100644 --- a/src/main/java/io/supertokens/authRecipe/AuthRecipe.java +++ b/src/main/java/io/supertokens/authRecipe/AuthRecipe.java @@ -23,6 +23,7 @@ import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.authRecipe.AuthRecipeStorage; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; +import io.supertokens.pluginInterface.authRecipe.LoginMethod; import io.supertokens.pluginInterface.dashboard.DashboardSearchTags; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; @@ -43,6 +44,19 @@ public class AuthRecipe { public static final int USER_PAGINATION_LIMIT = 500; + public static AuthRecipeUserInfo getUserById(AppIdentifierWithStorage appIdentifierWithStorage, String userId) + throws StorageQueryException { + return appIdentifierWithStorage.getAuthRecipeStorage().getPrimaryUserById(appIdentifierWithStorage, userId); + } + + public static AuthRecipeUserInfo[] getUsersByAccountInfo(TenantIdentifierWithStorage tenantIdentifier, + boolean doUnionOfAccountInfo, String email, + String phoneNumber, LoginMethod.ThirdParty thirdParty) + throws StorageQueryException { + // TODO:.. + return new AuthRecipeUserInfo[0]; + } + public static long getUsersCountForTenant(TenantIdentifierWithStorage tenantIdentifier, RECIPE_ID[] includeRecipeIds) throws StorageQueryException, diff --git a/src/main/java/io/supertokens/emailpassword/EmailPassword.java b/src/main/java/io/supertokens/emailpassword/EmailPassword.java index 0f1d8266b..0156cbeaf 100644 --- a/src/main/java/io/supertokens/emailpassword/EmailPassword.java +++ b/src/main/java/io/supertokens/emailpassword/EmailPassword.java @@ -473,6 +473,7 @@ public static void updateUsersEmailOrPassword(AppIdentifierWithStorage appIdenti } } + @Deprecated @TestOnly public static UserInfo getUserUsingId(Main main, String userId) throws StorageQueryException { @@ -484,6 +485,7 @@ public static UserInfo getUserUsingId(Main main, String userId) } } + @Deprecated public static UserInfo getUserUsingId(AppIdentifierWithStorage appIdentifierWithStorage, String userId) throws StorageQueryException, TenantOrAppNotFoundException { AuthRecipeUserInfo result = appIdentifierWithStorage.getAuthRecipeStorage() @@ -499,6 +501,7 @@ public static UserInfo getUserUsingId(AppIdentifierWithStorage appIdentifierWith return null; } + @Deprecated public static AuthRecipeUserInfo getUserUsingEmail(TenantIdentifierWithStorage tenantIdentifierWithStorage, String email) throws StorageQueryException, TenantOrAppNotFoundException { diff --git a/src/main/java/io/supertokens/passwordless/Passwordless.java b/src/main/java/io/supertokens/passwordless/Passwordless.java index d8d32cbbf..01e98bb04 100644 --- a/src/main/java/io/supertokens/passwordless/Passwordless.java +++ b/src/main/java/io/supertokens/passwordless/Passwordless.java @@ -555,6 +555,7 @@ public static AuthRecipeUserInfo getUserByPhoneNumber(Main main, phoneNumber); } + @Deprecated public static AuthRecipeUserInfo getUserByPhoneNumber(TenantIdentifierWithStorage tenantIdentifierWithStorage, String phoneNumber) throws StorageQueryException { AuthRecipeUserInfo[] users = tenantIdentifierWithStorage.getPasswordlessStorage() @@ -569,6 +570,7 @@ public static AuthRecipeUserInfo getUserByPhoneNumber(TenantIdentifierWithStorag return null; } + @Deprecated @TestOnly public static AuthRecipeUserInfo getUserByEmail(Main main, String email) throws StorageQueryException { @@ -577,6 +579,7 @@ public static AuthRecipeUserInfo getUserByEmail(Main main, String email) new TenantIdentifierWithStorage(null, null, null, storage), email); } + @Deprecated public static AuthRecipeUserInfo getUserByEmail(TenantIdentifierWithStorage tenantIdentifierWithStorage, String email) throws StorageQueryException { diff --git a/src/main/java/io/supertokens/thirdparty/ThirdParty.java b/src/main/java/io/supertokens/thirdparty/ThirdParty.java index 8e7c51b42..0eda2689d 100644 --- a/src/main/java/io/supertokens/thirdparty/ThirdParty.java +++ b/src/main/java/io/supertokens/thirdparty/ThirdParty.java @@ -201,6 +201,7 @@ private static SignInUpResponse signInUpHelper(TenantIdentifierWithStorage tenan } } + @Deprecated public static UserInfo getUser(AppIdentifierWithStorage appIdentifierWithStorage, String userId) throws StorageQueryException { AuthRecipeUserInfo result = appIdentifierWithStorage.getAuthRecipeStorage() @@ -217,6 +218,7 @@ public static UserInfo getUser(AppIdentifierWithStorage appIdentifierWithStorage return null; } + @Deprecated @TestOnly public static UserInfo getUser(Main main, String userId) throws StorageQueryException { Storage storage = StorageLayer.getStorage(main); @@ -230,7 +232,7 @@ public static AuthRecipeUserInfo getUser(TenantIdentifierWithStorage tenantIdent return tenantIdentifierWithStorage.getThirdPartyStorage() .getPrimaryUserByThirdPartyInfo(tenantIdentifierWithStorage, thirdPartyId, thirdPartyUserId); } - + @TestOnly public static AuthRecipeUserInfo getUser(Main main, String thirdPartyId, String thirdPartyUserId) throws StorageQueryException { @@ -240,6 +242,7 @@ public static AuthRecipeUserInfo getUser(Main main, String thirdPartyId, String thirdPartyId, thirdPartyUserId); } + @Deprecated public static AuthRecipeUserInfo[] getUsersByEmail(TenantIdentifierWithStorage tenantIdentifierWithStorage, @Nonnull String email) throws StorageQueryException { diff --git a/src/main/java/io/supertokens/webserver/Webserver.java b/src/main/java/io/supertokens/webserver/Webserver.java index 8184bde18..5704a4d10 100644 --- a/src/main/java/io/supertokens/webserver/Webserver.java +++ b/src/main/java/io/supertokens/webserver/Webserver.java @@ -37,10 +37,6 @@ import io.supertokens.webserver.api.jwt.JWKSAPI; import io.supertokens.webserver.api.jwt.JWTSigningAPI; import io.supertokens.webserver.api.multitenancy.*; -import io.supertokens.webserver.api.multitenancy.CreateOrUpdateAppAPI; -import io.supertokens.webserver.api.multitenancy.CreateOrUpdateConnectionUriDomainAPI; -import io.supertokens.webserver.api.multitenancy.CreateOrUpdateTenantOrGetTenantAPI; -import io.supertokens.webserver.api.multitenancy.RemoveTenantAPI; import io.supertokens.webserver.api.multitenancy.thirdparty.CreateOrUpdateThirdPartyConfigAPI; import io.supertokens.webserver.api.multitenancy.thirdparty.RemoveThirdPartyConfigAPI; import io.supertokens.webserver.api.passwordless.*; @@ -250,6 +246,9 @@ private void setupRoutes() { addAPI(new AssociateUserToTenantAPI(main)); addAPI(new DisassociateUserFromTenant(main)); + addAPI(new GetUserByIdAPI(main)); + addAPI(new ListUsersByAccountInfoAPI(main)); + StandardContext context = tomcatReference.getContext(); Tomcat tomcat = tomcatReference.getTomcat(); diff --git a/src/main/java/io/supertokens/webserver/api/core/GetUserByIdAPI.java b/src/main/java/io/supertokens/webserver/api/core/GetUserByIdAPI.java new file mode 100644 index 000000000..58db6959f --- /dev/null +++ b/src/main/java/io/supertokens/webserver/api/core/GetUserByIdAPI.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2023, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.webserver.api.core; + +import com.google.gson.JsonObject; +import io.supertokens.AppIdentifierWithStorageAndUserIdMapping; +import io.supertokens.Main; +import io.supertokens.authRecipe.AuthRecipe; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; +import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; +import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; +import io.supertokens.useridmapping.UserIdMapping; +import io.supertokens.useridmapping.UserIdType; +import io.supertokens.webserver.InputParser; +import io.supertokens.webserver.WebserverAPI; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import java.io.IOException; + +public class GetUserByIdAPI extends WebserverAPI { + + public GetUserByIdAPI(Main main) { + super(main, ""); + } + + @Override + public String getPath() { + return "/user/id"; + } + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { + // API is app specific + String userId = InputParser.getQueryParamOrThrowError(req, "userId", false); + + try { + AuthRecipeUserInfo user = null; + + try { + AppIdentifierWithStorageAndUserIdMapping appIdentifierWithStorageAndUserIdMapping = + this.getAppIdentifierWithStorageAndUserIdMappingFromRequest(req, userId, UserIdType.ANY); + // if a userIdMapping exists, pass the superTokensUserId to the getUserUsingId function + if (appIdentifierWithStorageAndUserIdMapping.userIdMapping != null) { + userId = appIdentifierWithStorageAndUserIdMapping.userIdMapping.superTokensUserId; + } + + user = AuthRecipe.getUserById(appIdentifierWithStorageAndUserIdMapping.appIdentifierWithStorage, + userId); + + // if a userIdMapping exists, set the userId in the response to the externalUserId + if (user != null) { + io.supertokens.pluginInterface.useridmapping.UserIdMapping userIdMapping = + UserIdMapping.getUserIdMapping( + getAppIdentifierWithStorage(req), user.id, UserIdType.SUPERTOKENS); + if (userIdMapping != null) { + user.setExternalUserId(userIdMapping.externalUserId); + } + } + + } catch (UnknownUserIdException e) { + // ignore the error so that the use can remain a null + } + + if (user == null) { + JsonObject result = new JsonObject(); + result.addProperty("status", "UNKNOWN_USER_ID_ERROR"); + super.sendJsonResponse(200, result, resp); + + } else { + JsonObject result = new JsonObject(); + result.addProperty("status", "OK"); + JsonObject userJson = user.toJson(); + + result.add("user", userJson); + super.sendJsonResponse(200, result, resp); + } + + } catch (StorageQueryException | TenantOrAppNotFoundException e) { + throw new ServletException(e); + } + + } +} diff --git a/src/main/java/io/supertokens/webserver/api/core/ListUsersByAccountInfoAPI.java b/src/main/java/io/supertokens/webserver/api/core/ListUsersByAccountInfoAPI.java new file mode 100644 index 000000000..119c5e7aa --- /dev/null +++ b/src/main/java/io/supertokens/webserver/api/core/ListUsersByAccountInfoAPI.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2023, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.webserver.api.core; + +import io.supertokens.Main; +import io.supertokens.authRecipe.AuthRecipe; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; +import io.supertokens.pluginInterface.authRecipe.LoginMethod; +import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; +import io.supertokens.utils.Utils; +import io.supertokens.webserver.InputParser; +import io.supertokens.webserver.WebserverAPI; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import java.io.IOException; + +public class ListUsersByAccountInfoAPI extends WebserverAPI { + + public ListUsersByAccountInfoAPI(Main main) { + super(main, ""); + } + + @Override + public String getPath() { + return "/users"; + } + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { + // API is tenant specific. + String email = InputParser.getQueryParamOrThrowError(req, "email", true); + String phoneNumber = InputParser.getQueryParamOrThrowError(req, "phoneNumber", true); + String thirdPartyId = InputParser.getQueryParamOrThrowError(req, "thirdPartyId", true); + String thirdPartyUserId = InputParser.getQueryParamOrThrowError(req, "thirdPartyUserId", true); + + String doUnionOfAccountInfoStr = InputParser.getQueryParamOrThrowError(req, "doUnionOfAccountInfo", false); + if (!(doUnionOfAccountInfoStr.equals("false") || doUnionOfAccountInfoStr.equals("true"))) { + throw new ServletException(new BadRequestException( + "'doUnionOfAccountInfo' should be either 'true' or 'false'")); + } + boolean doUnionOfAccountInfo = doUnionOfAccountInfoStr.equals("true"); + + if (email != null) { + email = Utils.normaliseEmail(email); + } + if (thirdPartyId != null || thirdPartyUserId != null) { + if (thirdPartyId == null || thirdPartyUserId == null) { + throw new ServletException(new BadRequestException( + "If 'thirdPartyId' is provided, 'thirdPartyUserId' must also be provided, and vice versa")); + } + } + + try { + AuthRecipeUserInfo[] result = AuthRecipe.getUsersByAccountInfo( + this.getTenantIdentifierWithStorageFromRequest( + req), doUnionOfAccountInfo, email, phoneNumber, + new LoginMethod.ThirdParty(thirdPartyId, thirdPartyUserId)); + + // TODO:... + + } catch (StorageQueryException | TenantOrAppNotFoundException e) { + throw new ServletException(e); + } + + } +} diff --git a/src/main/java/io/supertokens/webserver/api/emailpassword/UserAPI.java b/src/main/java/io/supertokens/webserver/api/emailpassword/UserAPI.java index bd8297532..8c002373b 100644 --- a/src/main/java/io/supertokens/webserver/api/emailpassword/UserAPI.java +++ b/src/main/java/io/supertokens/webserver/api/emailpassword/UserAPI.java @@ -55,6 +55,7 @@ public String getPath() { return "/recipe/user"; } + @Deprecated @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { // API is tenant specific for get by Email and app specific for get by UserId diff --git a/src/main/java/io/supertokens/webserver/api/passwordless/UserAPI.java b/src/main/java/io/supertokens/webserver/api/passwordless/UserAPI.java index 8966b0ba4..b3c06f560 100644 --- a/src/main/java/io/supertokens/webserver/api/passwordless/UserAPI.java +++ b/src/main/java/io/supertokens/webserver/api/passwordless/UserAPI.java @@ -56,6 +56,7 @@ public String getPath() { return "/recipe/user"; } + @Deprecated @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { // API is tenant specific for get by email or phone diff --git a/src/main/java/io/supertokens/webserver/api/thirdparty/GetUsersByEmailAPI.java b/src/main/java/io/supertokens/webserver/api/thirdparty/GetUsersByEmailAPI.java index d13a6184a..e3d6fcce9 100644 --- a/src/main/java/io/supertokens/webserver/api/thirdparty/GetUsersByEmailAPI.java +++ b/src/main/java/io/supertokens/webserver/api/thirdparty/GetUsersByEmailAPI.java @@ -39,6 +39,7 @@ import java.io.IOException; +@Deprecated public class GetUsersByEmailAPI extends WebserverAPI { private static final long serialVersionUID = -4413719941975228004L; diff --git a/src/main/java/io/supertokens/webserver/api/thirdparty/UserAPI.java b/src/main/java/io/supertokens/webserver/api/thirdparty/UserAPI.java index fd49b0457..84d3c1ab9 100644 --- a/src/main/java/io/supertokens/webserver/api/thirdparty/UserAPI.java +++ b/src/main/java/io/supertokens/webserver/api/thirdparty/UserAPI.java @@ -49,6 +49,7 @@ public String getPath() { return "/recipe/user"; } + @Deprecated @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { // API is tenant specific for get by thirdPartyUserId From 858b12913570550ca870cdb8cef214558ca75189 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Mon, 17 Jul 2023 12:18:52 +0530 Subject: [PATCH 024/131] get users by account info API --- .../io/supertokens/authRecipe/AuthRecipe.java | 54 +++++++++++++++++-- .../api/core/ListUsersByAccountInfoAPI.java | 34 +++++++++--- 2 files changed, 79 insertions(+), 9 deletions(-) diff --git a/src/main/java/io/supertokens/authRecipe/AuthRecipe.java b/src/main/java/io/supertokens/authRecipe/AuthRecipe.java index 6ff11db5d..a3ec17d96 100644 --- a/src/main/java/io/supertokens/authRecipe/AuthRecipe.java +++ b/src/main/java/io/supertokens/authRecipe/AuthRecipe.java @@ -37,6 +37,10 @@ import org.jetbrains.annotations.TestOnly; import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; /*This files contains functions that are common for all auth recipes*/ @@ -51,10 +55,54 @@ public static AuthRecipeUserInfo getUserById(AppIdentifierWithStorage appIdentif public static AuthRecipeUserInfo[] getUsersByAccountInfo(TenantIdentifierWithStorage tenantIdentifier, boolean doUnionOfAccountInfo, String email, - String phoneNumber, LoginMethod.ThirdParty thirdParty) + String phoneNumber, String thirdPartyId, + String thirdPartyUserId) throws StorageQueryException { - // TODO:.. - return new AuthRecipeUserInfo[0]; + Set result = new HashSet<>(); + + if (email != null) { + AuthRecipeUserInfo[] users = tenantIdentifier.getAuthRecipeStorage() + .listPrimaryUsersByEmail(tenantIdentifier, email); + result.addAll(List.of(users)); + } + if (phoneNumber != null) { + AuthRecipeUserInfo[] users = tenantIdentifier.getAuthRecipeStorage() + .listPrimaryUsersByPhoneNumber(tenantIdentifier, phoneNumber); + result.addAll(List.of(users)); + } + if (thirdPartyId != null && thirdPartyUserId != null) { + AuthRecipeUserInfo user = tenantIdentifier.getAuthRecipeStorage() + .getPrimaryUserByThirdPartyInfo(tenantIdentifier, thirdPartyId, thirdPartyUserId); + result.add(user); + } + + if (doUnionOfAccountInfo) { + return result.toArray(new AuthRecipeUserInfo[0]); + } else { + List finalList = new ArrayList<>(); + for (AuthRecipeUserInfo user : result) { + boolean emailMatch = email == null; + boolean phoneNumberMatch = phoneNumber == null; + boolean thirdPartyMatch = thirdPartyId == null; + for (LoginMethod lM : user.loginMethods) { + if (email != null && email.equals(lM.email)) { + emailMatch = true; + } + if (phoneNumber != null && phoneNumber.equals(lM.phoneNumber)) { + phoneNumberMatch = true; + } + if (thirdPartyId != null && + (new LoginMethod.ThirdParty(thirdPartyId, thirdPartyUserId)).equals(lM.thirdParty)) { + thirdPartyMatch = true; + } + } + if (emailMatch && phoneNumberMatch && thirdPartyMatch) { + finalList.add(user); + } + } + return finalList.toArray(new AuthRecipeUserInfo[0]); + } + } public static long getUsersCountForTenant(TenantIdentifierWithStorage tenantIdentifier, diff --git a/src/main/java/io/supertokens/webserver/api/core/ListUsersByAccountInfoAPI.java b/src/main/java/io/supertokens/webserver/api/core/ListUsersByAccountInfoAPI.java index 119c5e7aa..6cfeeb4ed 100644 --- a/src/main/java/io/supertokens/webserver/api/core/ListUsersByAccountInfoAPI.java +++ b/src/main/java/io/supertokens/webserver/api/core/ListUsersByAccountInfoAPI.java @@ -16,12 +16,16 @@ package io.supertokens.webserver.api.core; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; import io.supertokens.Main; import io.supertokens.authRecipe.AuthRecipe; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; -import io.supertokens.pluginInterface.authRecipe.LoginMethod; import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; +import io.supertokens.useridmapping.UserIdMapping; +import io.supertokens.useridmapping.UserIdType; import io.supertokens.utils.Utils; import io.supertokens.webserver.InputParser; import io.supertokens.webserver.WebserverAPI; @@ -68,12 +72,30 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO } try { - AuthRecipeUserInfo[] result = AuthRecipe.getUsersByAccountInfo( + AppIdentifierWithStorage appIdentifierWithStorage = this.getAppIdentifierWithStorage(req); + AuthRecipeUserInfo[] users = AuthRecipe.getUsersByAccountInfo( this.getTenantIdentifierWithStorageFromRequest( - req), doUnionOfAccountInfo, email, phoneNumber, - new LoginMethod.ThirdParty(thirdPartyId, thirdPartyUserId)); - - // TODO:... + req), doUnionOfAccountInfo, email, phoneNumber, thirdPartyId, thirdPartyUserId); + + for (int i = 0; i < users.length; i++) { + // we intentionally do not use the function that accepts an array of user IDs to get the mapping cause + // this is simpler to use, and cause there shouldn't be that many userIds per email anyway + io.supertokens.pluginInterface.useridmapping.UserIdMapping userIdMapping = UserIdMapping + .getUserIdMapping(appIdentifierWithStorage, users[i].id, UserIdType.SUPERTOKENS); + if (userIdMapping != null) { + users[i].setExternalUserId(userIdMapping.externalUserId); + } + } + + JsonObject result = new JsonObject(); + result.addProperty("status", "OK"); + JsonArray usersJson = new JsonArray(); + for (AuthRecipeUserInfo userInfo : users) { + usersJson.add(userInfo.toJson()); + } + + result.add("users", usersJson); + super.sendJsonResponse(200, result, resp); } catch (StorageQueryException | TenantOrAppNotFoundException e) { throw new ServletException(e); From fc8d47bfa205c50456c79f41aaf9de35cf3f351d Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Mon, 17 Jul 2023 15:12:29 +0530 Subject: [PATCH 025/131] adds new func signature --- .../io/supertokens/authRecipe/AuthRecipe.java | 20 ++++++++++++++ ...atedWithAnotherPrimaryUserIdException.java | 26 +++++++++++++++++++ ...readyLinkedWithPrimaryUserIdException.java | 26 +++++++++++++++++++ 3 files changed, 72 insertions(+) create mode 100644 src/main/java/io/supertokens/authRecipe/exception/AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException.java create mode 100644 src/main/java/io/supertokens/authRecipe/exception/RecipeUserIdAlreadyLinkedWithPrimaryUserIdException.java diff --git a/src/main/java/io/supertokens/authRecipe/AuthRecipe.java b/src/main/java/io/supertokens/authRecipe/AuthRecipe.java index a3ec17d96..542fa1b13 100644 --- a/src/main/java/io/supertokens/authRecipe/AuthRecipe.java +++ b/src/main/java/io/supertokens/authRecipe/AuthRecipe.java @@ -17,6 +17,8 @@ package io.supertokens.authRecipe; import io.supertokens.Main; +import io.supertokens.authRecipe.exception.AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException; +import io.supertokens.authRecipe.exception.RecipeUserIdAlreadyLinkedWithPrimaryUserIdException; import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.STORAGE_TYPE; @@ -53,6 +55,24 @@ public static AuthRecipeUserInfo getUserById(AppIdentifierWithStorage appIdentif return appIdentifierWithStorage.getAuthRecipeStorage().getPrimaryUserById(appIdentifierWithStorage, userId); } + public static class CreatePrimaryUserResult { + AuthRecipeUserInfo user; + boolean wasAlreadyAPrimaryUser; + + public CreatePrimaryUserResult(AuthRecipeUserInfo user, boolean wasAlreadyAPrimaryUser) { + this.user = user; + this.wasAlreadyAPrimaryUser = wasAlreadyAPrimaryUser; + } + } + + public static CreatePrimaryUserResult createPrimaryUser(AppIdentifierWithStorage appIdentifierWithStorage, + String recipeUserId) + throws StorageQueryException, AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException, + RecipeUserIdAlreadyLinkedWithPrimaryUserIdException { + // TODO.. + return null; + } + public static AuthRecipeUserInfo[] getUsersByAccountInfo(TenantIdentifierWithStorage tenantIdentifier, boolean doUnionOfAccountInfo, String email, String phoneNumber, String thirdPartyId, diff --git a/src/main/java/io/supertokens/authRecipe/exception/AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException.java b/src/main/java/io/supertokens/authRecipe/exception/AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException.java new file mode 100644 index 000000000..7a2805f1e --- /dev/null +++ b/src/main/java/io/supertokens/authRecipe/exception/AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2023, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.authRecipe.exception; + +public class AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException extends Exception { + public final String primaryUserId; + + public AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException(String primaryUserId, String description) { + super(description); + this.primaryUserId = primaryUserId; + } +} diff --git a/src/main/java/io/supertokens/authRecipe/exception/RecipeUserIdAlreadyLinkedWithPrimaryUserIdException.java b/src/main/java/io/supertokens/authRecipe/exception/RecipeUserIdAlreadyLinkedWithPrimaryUserIdException.java new file mode 100644 index 000000000..7d0bfe990 --- /dev/null +++ b/src/main/java/io/supertokens/authRecipe/exception/RecipeUserIdAlreadyLinkedWithPrimaryUserIdException.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2023, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.authRecipe.exception; + +public class RecipeUserIdAlreadyLinkedWithPrimaryUserIdException extends Exception { + public final String primaryUserId; + + public RecipeUserIdAlreadyLinkedWithPrimaryUserIdException(String primaryUserId, String description) { + super(description); + this.primaryUserId = primaryUserId; + } +} From 1f488036b3e70156d6ab6a84381a3d5c2155a439 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Mon, 17 Jul 2023 18:23:10 +0530 Subject: [PATCH 026/131] creates new indices --- CHANGELOG.md | 7 ++++ .../java/io/supertokens/inmemorydb/Start.java | 33 ++++++++++++++++++- .../queries/EmailPasswordQueries.java | 5 +++ .../inmemorydb/queries/GeneralQueries.java | 3 ++ .../queries/PasswordlessQueries.java | 10 ++++++ .../api/core/ListUsersByAccountInfoAPI.java | 2 +- 6 files changed, 58 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e07f0fa9f..95daa3c70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,9 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - Added new index `all_auth_recipe_users_primary_user_id_index`. - Added new index `all_auth_recipe_users_primary_user_id_and_tenant_id_index`. +- Added new index `emailpassword_email_index` +- Added new index `passwordless_users_email_index` +- Added new index `passwordless_users_phone_number_index` - Modified `all_auth_recipe_users_pagination_index` index to be on `primary_or_recipe_user_id` instead of `user_id` - Added a two new columns in `all_auth_recipe_users`: - `primary_or_recipe_user_id` (default value is equal to `user_id` column) @@ -38,6 +41,10 @@ CREATE INDEX all_auth_recipe_users_pagination_index ON all_auth_recipe_users (ti DESC, app_id DESC); CREATE INDEX all_auth_recipe_users_primary_user_id_index ON all_auth_recipe_users (app_id, primary_or_recipe_user_id); CREATE INDEX all_auth_recipe_users_primary_user_id_and_tenant_id_index ON all_auth_recipe_users (app_id, tenant_id, primary_or_recipe_user_id); + +CREATE INDEX emailpassword_email_index ON emailpassword_users (app_id, email); +CREATE INDEX passwordless_users_email_index ON passwordless_users (app_id, email); +CREATE INDEX passwordless_users_phone_number_index ON passwordless_users (app_id, phone_number); ``` ## [6.0.4] - 2023-07-13 diff --git a/src/main/java/io/supertokens/inmemorydb/Start.java b/src/main/java/io/supertokens/inmemorydb/Start.java index b870e2137..4010a120d 100644 --- a/src/main/java/io/supertokens/inmemorydb/Start.java +++ b/src/main/java/io/supertokens/inmemorydb/Start.java @@ -25,6 +25,7 @@ import io.supertokens.pluginInterface.*; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.authRecipe.LoginMethod; +import io.supertokens.pluginInterface.authRecipe.sqlStorage.AuthRecipeSQLStorage; import io.supertokens.pluginInterface.dashboard.DashboardSearchTags; import io.supertokens.pluginInterface.dashboard.DashboardSessionInfo; import io.supertokens.pluginInterface.dashboard.DashboardUser; @@ -102,7 +103,7 @@ public class Start implements SessionSQLStorage, EmailPasswordSQLStorage, EmailVerificationSQLStorage, ThirdPartySQLStorage, JWTRecipeSQLStorage, PasswordlessSQLStorage, UserMetadataSQLStorage, UserRolesSQLStorage, UserIdMappingStorage, - MultitenancyStorage, TOTPSQLStorage, ActiveUsersStorage, DashboardSQLStorage { + MultitenancyStorage, TOTPSQLStorage, ActiveUsersStorage, DashboardSQLStorage, AuthRecipeSQLStorage { private static final Object appenderLock = new Object(); private static final String APP_ID_KEY_NAME = "app_id"; @@ -2769,4 +2770,34 @@ public String[] getAllTablesInTheDatabaseThatHasDataForAppId(String appId) throw throw new StorageQueryException(e); } } + + @Override + public AuthRecipeUserInfo getPrimaryUserById_Transaction(AppIdentifier appIdentifier, String userId) + throws StorageQueryException { + // TODO:.. + return null; + } + + @Override + public AuthRecipeUserInfo[] listPrimaryUsersByEmail_Transaction(AppIdentifier appIdentifier, String email) + throws StorageQueryException { + // TODO:.. + return new AuthRecipeUserInfo[0]; + } + + @Override + public AuthRecipeUserInfo[] listPrimaryUsersByPhoneNumber_Transaction(AppIdentifier appIdentifier, + String phoneNumber) + throws StorageQueryException { + // TODO:.. + return new AuthRecipeUserInfo[0]; + } + + @Override + public AuthRecipeUserInfo getPrimaryUserByThirdPartyInfo_Transaction(AppIdentifier appIdentifier, + String thirdPartyId, String thirdPartyUserId) + throws StorageQueryException { + // TODO:.. + return null; + } } diff --git a/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java index 247015f1b..8ccf15c88 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java @@ -58,6 +58,11 @@ static String getQueryToCreateUsersTable(Start start) { + ");"; } + static String getQueryToCreateEmailIndex(Start start) { + return "CREATE INDEX emailpassword_email_index ON " + + Config.getConfig(start).getEmailPasswordUsersTable() + "(app_id, email);"; + } + static String getQueryToCreateEmailPasswordUserToTenantTable(Start start) { String emailPasswordUserToTenantTable = Config.getConfig(start).getEmailPasswordUserToTenantTable(); // @formatter:off diff --git a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java index a58364eac..18cbf67c7 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java @@ -231,6 +231,7 @@ public static void createTablesIfNotExists(Start start, Main main) throws SQLExc if (!doesTableExists(start, Config.getConfig(start).getEmailPasswordUsersTable())) { getInstance(main).addState(CREATING_NEW_TABLE, null); update(start, EmailPasswordQueries.getQueryToCreateUsersTable(start), NO_OP_SETTER); + update(start, EmailPasswordQueries.getQueryToCreateEmailIndex(start), NO_OP_SETTER); } if (!doesTableExists(start, Config.getConfig(start).getEmailPasswordUserToTenantTable())) { @@ -279,6 +280,8 @@ public static void createTablesIfNotExists(Start start, Main main) throws SQLExc if (!doesTableExists(start, Config.getConfig(start).getPasswordlessUsersTable())) { getInstance(main).addState(CREATING_NEW_TABLE, null); update(start, PasswordlessQueries.getQueryToCreateUsersTable(start), NO_OP_SETTER); + update(start, PasswordlessQueries.getQueryToCreateEmailIndex(start), NO_OP_SETTER); + update(start, PasswordlessQueries.getQueryToCreatePhoneNumberIndex(start), NO_OP_SETTER); } if (!doesTableExists(start, Config.getConfig(start).getPasswordlessUserToTenantTable())) { diff --git a/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java index 83fd169b1..7a11e76a3 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java @@ -59,6 +59,16 @@ public static String getQueryToCreateUsersTable(Start start) { + ");"; } + static String getQueryToCreateEmailIndex(Start start) { + return "CREATE INDEX passwordless_users_email_index ON " + + Config.getConfig(start).getPasswordlessUsersTable() + "(app_id, email);"; + } + + static String getQueryToCreatePhoneNumberIndex(Start start) { + return "CREATE INDEX passwordless_users_phone_number_index ON " + + Config.getConfig(start).getPasswordlessUsersTable() + "(app_id, phone_number);"; + } + static String getQueryToCreatePasswordlessUserToTenantTable(Start start) { String passwordlessUserToTenantTable = Config.getConfig(start).getPasswordlessUserToTenantTable(); // @formatter:off diff --git a/src/main/java/io/supertokens/webserver/api/core/ListUsersByAccountInfoAPI.java b/src/main/java/io/supertokens/webserver/api/core/ListUsersByAccountInfoAPI.java index 6cfeeb4ed..3f5320dc2 100644 --- a/src/main/java/io/supertokens/webserver/api/core/ListUsersByAccountInfoAPI.java +++ b/src/main/java/io/supertokens/webserver/api/core/ListUsersByAccountInfoAPI.java @@ -43,7 +43,7 @@ public ListUsersByAccountInfoAPI(Main main) { @Override public String getPath() { - return "/users"; + return "/users/by-accountinfo"; } @Override From a49d73e5809ae690c8a340b95beeff377a02fdc2 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Mon, 17 Jul 2023 20:14:49 +0530 Subject: [PATCH 027/131] adds impl for creating a primary user --- CHANGELOG.md | 7 -- .../io/supertokens/authRecipe/AuthRecipe.java | 99 ++++++++++++++++++- .../java/io/supertokens/inmemorydb/Start.java | 21 +++- .../queries/EmailPasswordQueries.java | 5 - .../inmemorydb/queries/GeneralQueries.java | 3 - .../queries/PasswordlessQueries.java | 10 -- 6 files changed, 111 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95daa3c70..e07f0fa9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,9 +11,6 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - Added new index `all_auth_recipe_users_primary_user_id_index`. - Added new index `all_auth_recipe_users_primary_user_id_and_tenant_id_index`. -- Added new index `emailpassword_email_index` -- Added new index `passwordless_users_email_index` -- Added new index `passwordless_users_phone_number_index` - Modified `all_auth_recipe_users_pagination_index` index to be on `primary_or_recipe_user_id` instead of `user_id` - Added a two new columns in `all_auth_recipe_users`: - `primary_or_recipe_user_id` (default value is equal to `user_id` column) @@ -41,10 +38,6 @@ CREATE INDEX all_auth_recipe_users_pagination_index ON all_auth_recipe_users (ti DESC, app_id DESC); CREATE INDEX all_auth_recipe_users_primary_user_id_index ON all_auth_recipe_users (app_id, primary_or_recipe_user_id); CREATE INDEX all_auth_recipe_users_primary_user_id_and_tenant_id_index ON all_auth_recipe_users (app_id, tenant_id, primary_or_recipe_user_id); - -CREATE INDEX emailpassword_email_index ON emailpassword_users (app_id, email); -CREATE INDEX passwordless_users_email_index ON passwordless_users (app_id, email); -CREATE INDEX passwordless_users_phone_number_index ON passwordless_users (app_id, phone_number); ``` ## [6.0.4] - 2023-07-13 diff --git a/src/main/java/io/supertokens/authRecipe/AuthRecipe.java b/src/main/java/io/supertokens/authRecipe/AuthRecipe.java index 542fa1b13..46cc63a26 100644 --- a/src/main/java/io/supertokens/authRecipe/AuthRecipe.java +++ b/src/main/java/io/supertokens/authRecipe/AuthRecipe.java @@ -26,10 +26,13 @@ import io.supertokens.pluginInterface.authRecipe.AuthRecipeStorage; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.authRecipe.LoginMethod; +import io.supertokens.pluginInterface.authRecipe.sqlStorage.AuthRecipeSQLStorage; import io.supertokens.pluginInterface.dashboard.DashboardSearchTags; +import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.totp.sqlStorage.TOTPSQLStorage; @@ -65,12 +68,100 @@ public CreatePrimaryUserResult(AuthRecipeUserInfo user, boolean wasAlreadyAPrima } } - public static CreatePrimaryUserResult createPrimaryUser(AppIdentifierWithStorage appIdentifierWithStorage, + public static CreatePrimaryUserResult createPrimaryUser(Main main, + AppIdentifierWithStorage appIdentifierWithStorage, String recipeUserId) throws StorageQueryException, AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException, - RecipeUserIdAlreadyLinkedWithPrimaryUserIdException { - // TODO.. - return null; + RecipeUserIdAlreadyLinkedWithPrimaryUserIdException, UnknownUserIdException { + AuthRecipeSQLStorage storage = (AuthRecipeSQLStorage) appIdentifierWithStorage.getAuthRecipeStorage(); + try { + return storage.startTransaction(con -> { + + AuthRecipeUserInfo targetUser = storage.getPrimaryUserById_Transaction(appIdentifierWithStorage, con, + recipeUserId); + if (targetUser == null) { + throw new StorageTransactionLogicException(new UnknownUserIdException()); + } + if (targetUser.isPrimaryUser) { + if (targetUser.id.equals(recipeUserId)) { + return new CreatePrimaryUserResult(targetUser, true); + } else { + throw new StorageTransactionLogicException( + new RecipeUserIdAlreadyLinkedWithPrimaryUserIdException(targetUser.id, + "This user ID is already linked to another user ID")); + } + } + + // this means that the user has only one login method since it's not a primary user + // nor is it linked to a primary user + assert (targetUser.loginMethods.length == 1); + LoginMethod loginMethod = targetUser.loginMethods[0]; + + for (String tenantId : targetUser.tenantIds) { + TenantIdentifier tenantIdentifier = new TenantIdentifier( + appIdentifierWithStorage.getConnectionUriDomain(), appIdentifierWithStorage.getAppId(), + tenantId); + // we do not bother with getting the tenantIdentifierWithStorage 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. + + if (loginMethod.email != null) { + AuthRecipeUserInfo[] usersWithSameEmail = storage + .listPrimaryUsersByEmail_Transaction(tenantIdentifier, con, + loginMethod.email); + for (AuthRecipeUserInfo user : usersWithSameEmail) { + if (user.isPrimaryUser) { + throw new StorageTransactionLogicException( + new AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException(user.id, + "This user's email is already associated with another user ID")); + } + } + } + + if (loginMethod.phoneNumber != null) { + AuthRecipeUserInfo[] usersWithSamePhoneNumber = storage + .listPrimaryUsersByPhoneNumber_Transaction(tenantIdentifier, con, + loginMethod.phoneNumber); + for (AuthRecipeUserInfo user : usersWithSamePhoneNumber) { + if (user.isPrimaryUser) { + throw new StorageTransactionLogicException( + new AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException(user.id, + "This user's phone number is already associated with another user" + + " ID")); + } + } + } + + if (loginMethod.thirdParty != null) { + AuthRecipeUserInfo userWithSameThirdParty = storage + .getPrimaryUsersByThirdPartyInfo_Transaction(tenantIdentifier, con, + loginMethod.thirdParty.id, loginMethod.thirdParty.userId); + if (userWithSameThirdParty.isPrimaryUser) { + throw new StorageTransactionLogicException( + new AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException( + userWithSameThirdParty.id, + "This user's third party login is already associated with another" + + " user ID")); + } + } + } + + storage.makePrimaryUser_Transaction(appIdentifierWithStorage, con, targetUser.id); + + storage.commitTransaction(con); + + return new CreatePrimaryUserResult(targetUser, false); + }); + } catch (StorageTransactionLogicException e) { + if (e.actualException instanceof UnknownUserIdException) { + throw (UnknownUserIdException) e.actualException; + } else if (e.actualException instanceof RecipeUserIdAlreadyLinkedWithPrimaryUserIdException) { + throw (RecipeUserIdAlreadyLinkedWithPrimaryUserIdException) e.actualException; + } else if (e.actualException instanceof AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException) { + throw (AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException) e.actualException; + } + throw new RuntimeException(e); + } } public static AuthRecipeUserInfo[] getUsersByAccountInfo(TenantIdentifierWithStorage tenantIdentifier, diff --git a/src/main/java/io/supertokens/inmemorydb/Start.java b/src/main/java/io/supertokens/inmemorydb/Start.java index 4010a120d..49ec77cfa 100644 --- a/src/main/java/io/supertokens/inmemorydb/Start.java +++ b/src/main/java/io/supertokens/inmemorydb/Start.java @@ -2772,21 +2772,24 @@ public String[] getAllTablesInTheDatabaseThatHasDataForAppId(String appId) throw } @Override - public AuthRecipeUserInfo getPrimaryUserById_Transaction(AppIdentifier appIdentifier, String userId) + public AuthRecipeUserInfo getPrimaryUserById_Transaction(AppIdentifier appIdentifier, TransactionConnection con, + String userId) throws StorageQueryException { // TODO:.. return null; } @Override - public AuthRecipeUserInfo[] listPrimaryUsersByEmail_Transaction(AppIdentifier appIdentifier, String email) + public AuthRecipeUserInfo[] listPrimaryUsersByEmail_Transaction(TenantIdentifier tenantIdentifier, + TransactionConnection con, String email) throws StorageQueryException { // TODO:.. return new AuthRecipeUserInfo[0]; } @Override - public AuthRecipeUserInfo[] listPrimaryUsersByPhoneNumber_Transaction(AppIdentifier appIdentifier, + public AuthRecipeUserInfo[] listPrimaryUsersByPhoneNumber_Transaction(TenantIdentifier tenantIdentifier, + TransactionConnection con, String phoneNumber) throws StorageQueryException { // TODO:.. @@ -2794,10 +2797,18 @@ public AuthRecipeUserInfo[] listPrimaryUsersByPhoneNumber_Transaction(AppIdentif } @Override - public AuthRecipeUserInfo getPrimaryUserByThirdPartyInfo_Transaction(AppIdentifier appIdentifier, - String thirdPartyId, String thirdPartyUserId) + public AuthRecipeUserInfo getPrimaryUsersByThirdPartyInfo_Transaction(TenantIdentifier tenantIdentifier, + TransactionConnection con, + String thirdPartyId, + String thirdPartyUserId) throws StorageQueryException { // TODO:.. return null; } + + @Override + public void makePrimaryUser_Transaction(AppIdentifier appIdentifier, TransactionConnection con, String userId) + throws StorageQueryException { + // TODO... + } } diff --git a/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java index 8ccf15c88..247015f1b 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java @@ -58,11 +58,6 @@ static String getQueryToCreateUsersTable(Start start) { + ");"; } - static String getQueryToCreateEmailIndex(Start start) { - return "CREATE INDEX emailpassword_email_index ON " - + Config.getConfig(start).getEmailPasswordUsersTable() + "(app_id, email);"; - } - static String getQueryToCreateEmailPasswordUserToTenantTable(Start start) { String emailPasswordUserToTenantTable = Config.getConfig(start).getEmailPasswordUserToTenantTable(); // @formatter:off diff --git a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java index 18cbf67c7..a58364eac 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java @@ -231,7 +231,6 @@ public static void createTablesIfNotExists(Start start, Main main) throws SQLExc if (!doesTableExists(start, Config.getConfig(start).getEmailPasswordUsersTable())) { getInstance(main).addState(CREATING_NEW_TABLE, null); update(start, EmailPasswordQueries.getQueryToCreateUsersTable(start), NO_OP_SETTER); - update(start, EmailPasswordQueries.getQueryToCreateEmailIndex(start), NO_OP_SETTER); } if (!doesTableExists(start, Config.getConfig(start).getEmailPasswordUserToTenantTable())) { @@ -280,8 +279,6 @@ public static void createTablesIfNotExists(Start start, Main main) throws SQLExc if (!doesTableExists(start, Config.getConfig(start).getPasswordlessUsersTable())) { getInstance(main).addState(CREATING_NEW_TABLE, null); update(start, PasswordlessQueries.getQueryToCreateUsersTable(start), NO_OP_SETTER); - update(start, PasswordlessQueries.getQueryToCreateEmailIndex(start), NO_OP_SETTER); - update(start, PasswordlessQueries.getQueryToCreatePhoneNumberIndex(start), NO_OP_SETTER); } if (!doesTableExists(start, Config.getConfig(start).getPasswordlessUserToTenantTable())) { diff --git a/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java index 7a11e76a3..83fd169b1 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java @@ -59,16 +59,6 @@ public static String getQueryToCreateUsersTable(Start start) { + ");"; } - static String getQueryToCreateEmailIndex(Start start) { - return "CREATE INDEX passwordless_users_email_index ON " - + Config.getConfig(start).getPasswordlessUsersTable() + "(app_id, email);"; - } - - static String getQueryToCreatePhoneNumberIndex(Start start) { - return "CREATE INDEX passwordless_users_phone_number_index ON " - + Config.getConfig(start).getPasswordlessUsersTable() + "(app_id, phone_number);"; - } - static String getQueryToCreatePasswordlessUserToTenantTable(Start start) { String passwordlessUserToTenantTable = Config.getConfig(start).getPasswordlessUserToTenantTable(); // @formatter:off From c54475a91ed928816bbe47efde261b92b4ba509f Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Mon, 17 Jul 2023 22:40:18 +0530 Subject: [PATCH 028/131] implements one of the functions --- .../java/io/supertokens/inmemorydb/Start.java | 8 ++- .../queries/EmailPasswordQueries.java | 49 ++++++++++------- .../inmemorydb/queries/GeneralQueries.java | 54 +++++++++++++++++++ .../queries/PasswordlessQueries.java | 45 +++++++++------- .../inmemorydb/queries/ThirdPartyQueries.java | 47 +++++++++------- 5 files changed, 144 insertions(+), 59 deletions(-) diff --git a/src/main/java/io/supertokens/inmemorydb/Start.java b/src/main/java/io/supertokens/inmemorydb/Start.java index 49ec77cfa..1d39ea5d5 100644 --- a/src/main/java/io/supertokens/inmemorydb/Start.java +++ b/src/main/java/io/supertokens/inmemorydb/Start.java @@ -2775,8 +2775,12 @@ public String[] getAllTablesInTheDatabaseThatHasDataForAppId(String appId) throw public AuthRecipeUserInfo getPrimaryUserById_Transaction(AppIdentifier appIdentifier, TransactionConnection con, String userId) throws StorageQueryException { - // TODO:.. - return null; + try { + Connection sqlCon = (Connection) con.getConnection(); + return GeneralQueries.getPrimaryUserInfoForUserId_Transaction(this, sqlCon, appIdentifier, userId); + } catch (SQLException e) { + throw new StorageQueryException(e); + } } @Override diff --git a/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java index 247015f1b..f0a8e0358 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java @@ -353,7 +353,8 @@ public static UserInfoPartial getUserInfoUsingId(Start start, Connection sqlCon, }); } - public static List getUsersInfoUsingIdList(Start start, Set ids, AppIdentifier appIdentifier) + public static List getUsersInfoUsingIdList_Transaction(Start start, Connection con, Set ids, + AppIdentifier appIdentifier) throws SQLException, StorageQueryException { if (ids.size() > 0) { // No need to filter based on tenantId because the id list is already filtered for a tenant @@ -361,28 +362,36 @@ public static List getUsersInfoUsingIdList(Start start, Set + "FROM " + getConfig(start).getEmailPasswordUsersTable() + " WHERE user_id IN (" + Utils.generateCommaSeperatedQuestionMarks(ids.size()) + ") AND app_id = ?"; + List userInfos = execute(con, QUERY, pst -> { + int index = 1; + for (String id : ids) { + pst.setString(index, id); + index++; + } + pst.setString(index, appIdentifier.getAppId()); + }, result -> { + List finalResult = new ArrayList<>(); + while (result.next()) { + finalResult.add(UserInfoRowMapper.getInstance().mapOrThrow(result)); + } + return finalResult; + }); + fillUserInfoWithTenantIds_transaction(start, con, appIdentifier, userInfos); + fillUserInfoWithVerified_transaction(start, con, appIdentifier, userInfos); + return userInfos.stream().map(UserInfoPartial::toLoginMethod) + .collect(Collectors.toList()); + } + return Collections.emptyList(); + } + + public static List getUsersInfoUsingIdList(Start start, Set ids, AppIdentifier appIdentifier) + throws SQLException, StorageQueryException { + if (ids.size() > 0) { try (Connection con = ConnectionPool.getConnection(start)) { - List userInfos = execute(con, QUERY, pst -> { - int index = 1; - for (String id : ids) { - pst.setString(index, id); - index++; - } - pst.setString(index, appIdentifier.getAppId()); - }, result -> { - List finalResult = new ArrayList<>(); - while (result.next()) { - finalResult.add(UserInfoRowMapper.getInstance().mapOrThrow(result)); - } - return finalResult; - }); - fillUserInfoWithTenantIds_transaction(start, con, appIdentifier, userInfos); - fillUserInfoWithVerified_transaction(start, con, appIdentifier, userInfos); - return userInfos.stream().map(UserInfoPartial::toLoginMethod) - .collect(Collectors.toList()); + return getUsersInfoUsingIdList_Transaction(start, con, ids, appIdentifier); } } - return Collections.emptyList(); + return null; } public static String getPrimaryUserIdUsingEmail(Start start, TenantIdentifier tenantIdentifier, String email) diff --git a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java index a58364eac..d84edad39 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java @@ -926,6 +926,60 @@ public static AuthRecipeUserInfo getPrimaryUserByThirdPartyInfo(Start start, Ten return null; } + public static AuthRecipeUserInfo getPrimaryUserInfoForUserId_Transaction(Start start, Connection sqlCon, + AppIdentifier appIdentifier, String id) + throws SQLException, StorageQueryException { + ((ConnectionWithLocks) sqlCon).lock( + appIdentifier + "~" + id + Config.getConfig(start).getUsersTable()); + + // We use SELECT FOR UPDATE in the query below for other plugins. + String QUERY = "SELECT * FROM " + getConfig(start).getUsersTable() + + " WHERE (user_id = ? OR primary_or_recipe_user_id = ?) AND app_id = ?"; + + AllAuthRecipeUsersResultHolder allAuthUsersResult = execute(sqlCon, QUERY, pst -> { + pst.setString(1, id); + pst.setString(2, id); + pst.setString(3, appIdentifier.getAppId()); + }, result -> { + AllAuthRecipeUsersResultHolder finalResult = null; + if (result.next()) { + finalResult = new AllAuthRecipeUsersResultHolder(result.getString("user_id"), + result.getString("tenant_id"), + result.getString("primary_or_recipe_user_id"), + result.getBoolean("is_linked_or_is_a_primary_user"), + result.getString("recipe_id"), + result.getLong("time_joined")); + } + return finalResult; + }); + + if (allAuthUsersResult == null) { + return null; + } + + // Now we form the userIds again, but based on the user_id in the result from above. + Set recipeUserIdsToFetch = new HashSet<>(); + recipeUserIdsToFetch.add(allAuthUsersResult.userId); + + List loginMethods = new ArrayList<>(); + loginMethods.addAll( + EmailPasswordQueries.getUsersInfoUsingIdList_Transaction(start, sqlCon, recipeUserIdsToFetch, + appIdentifier)); + loginMethods.addAll(ThirdPartyQueries.getUsersInfoUsingIdList_Transaction(start, sqlCon, recipeUserIdsToFetch, + appIdentifier)); + loginMethods.addAll(PasswordlessQueries.getUsersInfoUsingIdList_Transaction(start, sqlCon, recipeUserIdsToFetch, + appIdentifier)); + + // we do this in such a strange way cause the create function takes just one login method at the moment. + AuthRecipeUserInfo result = AuthRecipeUserInfo.create(allAuthUsersResult.primaryOrRecipeUserId, + allAuthUsersResult.isLinkedOrIsAPrimaryUser, loginMethods.get(0)); + for (int i = 1; i < loginMethods.size(); i++) { + result.addLoginMethod(loginMethods.get(i)); + } + + return result; + } + public static AuthRecipeUserInfo getPrimaryUserInfoForUserId(Start start, AppIdentifier appIdentifier, String id) throws SQLException, StorageQueryException { List ids = new ArrayList<>(); diff --git a/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java index 83fd169b1..fcd43e2b4 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java @@ -678,7 +678,8 @@ public static PasswordlessCode getCodeByLinkCodeHash(Start start, TenantIdentifi } } - public static List getUsersInfoUsingIdList(Start start, Set ids, AppIdentifier appIdentifier) + public static List getUsersInfoUsingIdList_Transaction(Start start, Connection con, Set ids, + AppIdentifier appIdentifier) throws SQLException, StorageQueryException { if (ids.size() > 0) { // No need to filter based on tenantId because the id list is already filtered for a tenant @@ -686,24 +687,32 @@ public static List getUsersInfoUsingIdList(Start start, Set + "FROM " + getConfig(start).getPasswordlessUsersTable() + " WHERE user_id IN (" + Utils.generateCommaSeperatedQuestionMarks(ids.size()) + ") AND app_id = ?"; + List userInfos = execute(con, QUERY, pst -> { + int index = 1; + for (String id : ids) { + pst.setString(index, id); + index++; + } + pst.setString(index, appIdentifier.getAppId()); + }, result -> { + List finalResult = new ArrayList<>(); + while (result.next()) { + finalResult.add(UserInfoRowMapper.getInstance().mapOrThrow(result)); + } + return finalResult; + }); + fillUserInfoWithTenantIds_transaction(start, con, appIdentifier, userInfos); + fillUserInfoWithVerified_transaction(start, con, appIdentifier, userInfos); + return userInfos.stream().map(UserInfoPartial::toLoginMethod).collect(Collectors.toList()); + } + return Collections.emptyList(); + } + + public static List getUsersInfoUsingIdList(Start start, Set ids, AppIdentifier appIdentifier) + throws SQLException, StorageQueryException { + if (ids.size() > 0) { try (Connection con = ConnectionPool.getConnection(start)) { - List userInfos = execute(con, QUERY, pst -> { - int index = 1; - for (String id : ids) { - pst.setString(index, id); - index++; - } - pst.setString(index, appIdentifier.getAppId()); - }, result -> { - List finalResult = new ArrayList<>(); - while (result.next()) { - finalResult.add(UserInfoRowMapper.getInstance().mapOrThrow(result)); - } - return finalResult; - }); - fillUserInfoWithTenantIds_transaction(start, con, appIdentifier, userInfos); - fillUserInfoWithVerified_transaction(start, con, appIdentifier, userInfos); - return userInfos.stream().map(UserInfoPartial::toLoginMethod).collect(Collectors.toList()); + return getUsersInfoUsingIdList_Transaction(start, con, ids, appIdentifier); } } return Collections.emptyList(); diff --git a/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java index fd788c364..b2b251b1a 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java @@ -176,32 +176,41 @@ public static void deleteUser(Start start, AppIdentifier appIdentifier, String u }); } - public static List getUsersInfoUsingIdList(Start start, Set ids, AppIdentifier appIdentifier) + public static List getUsersInfoUsingIdList_Transaction(Start start, Connection con, Set ids, + AppIdentifier appIdentifier) throws SQLException, StorageQueryException { if (ids.size() > 0) { String QUERY = "SELECT user_id, third_party_id, third_party_user_id, email, time_joined " + "FROM " + getConfig(start).getThirdPartyUsersTable() + " WHERE user_id IN (" + Utils.generateCommaSeperatedQuestionMarks(ids.size()) + ") AND app_id = ?"; + List userInfos = execute(con, QUERY, pst -> { + int index = 1; + for (String id : ids) { + pst.setString(index, id); + index++; + } + pst.setString(index, appIdentifier.getAppId()); + }, result -> { + List finalResult = new ArrayList<>(); + while (result.next()) { + finalResult.add(UserInfoRowMapper.getInstance().mapOrThrow(result)); + } + return finalResult; + }); + + fillUserInfoWithTenantIds_transaction(start, con, appIdentifier, userInfos); + fillUserInfoWithVerified_transaction(start, con, appIdentifier, userInfos); + return userInfos.stream().map(UserInfoPartial::toLoginMethod).collect(Collectors.toList()); + } + return Collections.emptyList(); + } + + public static List getUsersInfoUsingIdList(Start start, Set ids, AppIdentifier appIdentifier) + throws SQLException, StorageQueryException { + if (ids.size() > 0) { try (Connection con = ConnectionPool.getConnection(start)) { - List userInfos = execute(con, QUERY, pst -> { - int index = 1; - for (String id : ids) { - pst.setString(index, id); - index++; - } - pst.setString(index, appIdentifier.getAppId()); - }, result -> { - List finalResult = new ArrayList<>(); - while (result.next()) { - finalResult.add(UserInfoRowMapper.getInstance().mapOrThrow(result)); - } - return finalResult; - }); - - fillUserInfoWithTenantIds_transaction(start, con, appIdentifier, userInfos); - fillUserInfoWithVerified_transaction(start, con, appIdentifier, userInfos); - return userInfos.stream().map(UserInfoPartial::toLoginMethod).collect(Collectors.toList()); + return getUsersInfoUsingIdList_Transaction(start, con, ids, appIdentifier); } } return Collections.emptyList(); From 9f7e9acd2d9f97f7ed9c99712d782260e384f54b Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Fri, 21 Jul 2023 14:30:41 +0530 Subject: [PATCH 029/131] adds more query impls --- .../java/io/supertokens/inmemorydb/Start.java | 35 +++- .../queries/EmailPasswordQueries.java | 35 ++-- .../inmemorydb/queries/GeneralQueries.java | 153 +++++++++++++++--- .../queries/PasswordlessQueries.java | 67 ++++++-- .../inmemorydb/queries/ThirdPartyQueries.java | 80 +++++++-- 5 files changed, 299 insertions(+), 71 deletions(-) diff --git a/src/main/java/io/supertokens/inmemorydb/Start.java b/src/main/java/io/supertokens/inmemorydb/Start.java index 1d39ea5d5..1b9fd8f86 100644 --- a/src/main/java/io/supertokens/inmemorydb/Start.java +++ b/src/main/java/io/supertokens/inmemorydb/Start.java @@ -2787,8 +2787,12 @@ public AuthRecipeUserInfo getPrimaryUserById_Transaction(AppIdentifier appIdenti public AuthRecipeUserInfo[] listPrimaryUsersByEmail_Transaction(TenantIdentifier tenantIdentifier, TransactionConnection con, String email) throws StorageQueryException { - // TODO:.. - return new AuthRecipeUserInfo[0]; + try { + Connection sqlCon = (Connection) con.getConnection(); + return GeneralQueries.listPrimaryUsersByEmail_Transaction(this, sqlCon, tenantIdentifier, email); + } catch (SQLException e) { + throw new StorageQueryException(e); + } } @Override @@ -2796,8 +2800,13 @@ public AuthRecipeUserInfo[] listPrimaryUsersByPhoneNumber_Transaction(TenantIden TransactionConnection con, String phoneNumber) throws StorageQueryException { - // TODO:.. - return new AuthRecipeUserInfo[0]; + try { + Connection sqlCon = (Connection) con.getConnection(); + return GeneralQueries.listPrimaryUsersByPhoneNumber_Transaction(this, sqlCon, tenantIdentifier, + phoneNumber); + } catch (SQLException e) { + throw new StorageQueryException(e); + } } @Override @@ -2806,13 +2815,25 @@ public AuthRecipeUserInfo getPrimaryUsersByThirdPartyInfo_Transaction(TenantIden String thirdPartyId, String thirdPartyUserId) throws StorageQueryException { - // TODO:.. - return null; + try { + Connection sqlCon = (Connection) con.getConnection(); + return GeneralQueries.getPrimaryUsersByThirdPartyInfo_Transaction(this, sqlCon, tenantIdentifier, + thirdPartyId, thirdPartyUserId); + } catch (SQLException e) { + throw new StorageQueryException(e); + } } @Override public void makePrimaryUser_Transaction(AppIdentifier appIdentifier, TransactionConnection con, String userId) throws StorageQueryException { - // TODO... + try { + Connection sqlCon = (Connection) con.getConnection(); + // we do not bother returning if a row was updated here or not, cause it's happening + // in a transaction anyway. + GeneralQueries.makePrimaryUser_Transaction(this, sqlCon, userId); + } catch (SQLException e) { + throw new StorageQueryException(e); + } } } diff --git a/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java index f0a8e0358..20f973155 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java @@ -16,7 +16,6 @@ package io.supertokens.inmemorydb.queries; -import io.supertokens.inmemorydb.ConnectionPool; import io.supertokens.inmemorydb.ConnectionWithLocks; import io.supertokens.inmemorydb.Start; import io.supertokens.inmemorydb.Utils; @@ -353,8 +352,8 @@ public static UserInfoPartial getUserInfoUsingId(Start start, Connection sqlCon, }); } - public static List getUsersInfoUsingIdList_Transaction(Start start, Connection con, Set ids, - AppIdentifier appIdentifier) + public static List getUsersInfoUsingIdList(Start start, Connection con, Set ids, + AppIdentifier appIdentifier) throws SQLException, StorageQueryException { if (ids.size() > 0) { // No need to filter based on tenantId because the id list is already filtered for a tenant @@ -384,17 +383,29 @@ public static List getUsersInfoUsingIdList_Transaction(Start start, return Collections.emptyList(); } - public static List getUsersInfoUsingIdList(Start start, Set ids, AppIdentifier appIdentifier) - throws SQLException, StorageQueryException { - if (ids.size() > 0) { - try (Connection con = ConnectionPool.getConnection(start)) { - return getUsersInfoUsingIdList_Transaction(start, con, ids, appIdentifier); + public static String lockEmailAndTenant_Transaction(Start start, Connection con, TenantIdentifier tenantIdentifier, + String email) throws SQLException, StorageQueryException { + // normally the query below will use a for update, but sqlite doesn't support it. + ((ConnectionWithLocks) con).lock( + tenantIdentifier.getAppId() + tenantIdentifier.getTenantId() + "~" + email + + Config.getConfig(start).getEmailPasswordUserToTenantTable()); + + String QUERY = "SELECT user_id FROM " + getConfig(start).getEmailPasswordUserToTenantTable() + + " WHERE app_id = ? AND tenant_id = ? AND email = ?"; + return execute(con, QUERY, pst -> { + pst.setString(1, tenantIdentifier.getAppId()); + pst.setString(2, tenantIdentifier.getTenantId()); + pst.setString(3, email); + }, result -> { + if (result.next()) { + return result.getString("user_id"); } - } - return null; + return null; + }); } - public static String getPrimaryUserIdUsingEmail(Start start, TenantIdentifier tenantIdentifier, String email) + public static String getPrimaryUserIdUsingEmail(Start start, Connection con, TenantIdentifier tenantIdentifier, + String email) throws StorageQueryException, SQLException { String QUERY = "SELECT DISTINCT all_users.primary_or_recipe_user_id AS user_id " + "FROM " + getConfig(start).getEmailPasswordUserToTenantTable() + " AS ep" + @@ -402,7 +413,7 @@ public static String getPrimaryUserIdUsingEmail(Start start, TenantIdentifier te " ON ep.app_id = all_users.app_id AND ep.user_id = all_users.user_id" + " WHERE ep.app_id = ? AND ep.tenant_id = ? AND ep.email = ?"; - return execute(start, QUERY, pst -> { + return execute(con, QUERY, pst -> { pst.setString(1, tenantIdentifier.getAppId()); pst.setString(2, tenantIdentifier.getTenantId()); pst.setString(3, email); diff --git a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java index d84edad39..554893012 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java @@ -865,28 +865,97 @@ public static AuthRecipeUserInfo[] getUsers(Start start, TenantIdentifier tenant return finalResult; } + public static void makePrimaryUser_Transaction(Start start, Connection sqlCon, String userId) + throws SQLException, StorageQueryException { + String QUERY = "UPDATE " + getConfig(start).getUsersTable() + + " SET is_linked_or_is_a_primary_user = true WHERE user_id = ?"; + + execute(sqlCon, QUERY, pst -> { + pst.setString(1, userId); + }, result -> null); + } + + public static AuthRecipeUserInfo[] listPrimaryUsersByPhoneNumber_Transaction(Start start, Connection sqlCon, + TenantIdentifier tenantIdentifier, + String phoneNumber) + throws SQLException, StorageQueryException { + // we first lock on the table based on phoneNumber and tenant - this will ensure that any other + // query happening related to the account linking on this phone number / tenant will wait for this to finish, + // and vice versa. + + PasswordlessQueries.lockPhoneAndTenant_Transaction(start, sqlCon, tenantIdentifier, phoneNumber); + + // now that we have locks on all the relevant tables, we can read from them safely + return listPrimaryUsersByPhoneNumberHelper(start, sqlCon, tenantIdentifier, phoneNumber); + } + + public static AuthRecipeUserInfo getPrimaryUsersByThirdPartyInfo_Transaction(Start start, Connection sqlCon, + TenantIdentifier tenantIdentifier, + String thirdPartyId, + String thirdPartyUserId) + throws SQLException, StorageQueryException { + // we first lock on the table based on thirdparty info and tenant - this will ensure that any other + // query happening related to the account linking on this third party info / tenant will wait for this to + // finish, + // and vice versa. + + ThirdPartyQueries.lockThirdPartyInfoAndTenant_Transaction(start, sqlCon, tenantIdentifier, thirdPartyId, + thirdPartyUserId); + + // now that we have locks on all the relevant tables, we can read from them safely + return getPrimaryUserByThirdPartyInfoHelper(start, sqlCon, tenantIdentifier, thirdPartyId, thirdPartyUserId); + } + + public static AuthRecipeUserInfo[] listPrimaryUsersByEmail_Transaction(Start start, Connection sqlCon, + TenantIdentifier tenantIdentifier, + String email) + throws SQLException, StorageQueryException { + // we first lock on the three tables based on email and tenant - this will ensure that any other + // query happening related to the account linking on this email / tenant will wait for this to finish, + // and vice versa. + + EmailPasswordQueries.lockEmailAndTenant_Transaction(start, sqlCon, tenantIdentifier, email); + + ThirdPartyQueries.lockEmailAndTenant_Transaction(start, sqlCon, tenantIdentifier, email); + + PasswordlessQueries.lockEmailAndTenant_Transaction(start, sqlCon, tenantIdentifier, email); + + // now that we have locks on all the relevant tables, we can read from them safely + return listPrimaryUsersByEmailHelper(start, sqlCon, tenantIdentifier, email); + } + public static AuthRecipeUserInfo[] listPrimaryUsersByEmail(Start start, TenantIdentifier tenantIdentifier, String email) throws StorageQueryException, SQLException { - List userIds = new ArrayList<>(); + try (Connection con = ConnectionPool.getConnection(start)) { + return listPrimaryUsersByEmailHelper(start, con, tenantIdentifier, email); + } + } - String emailPasswordUserId = EmailPasswordQueries.getPrimaryUserIdUsingEmail(start, tenantIdentifier, email); + private static AuthRecipeUserInfo[] listPrimaryUsersByEmailHelper(Start start, Connection con, + TenantIdentifier tenantIdentifier, + String email) + throws StorageQueryException, SQLException { + List userIds = new ArrayList<>(); + String emailPasswordUserId = EmailPasswordQueries.getPrimaryUserIdUsingEmail(start, con, tenantIdentifier, + email); if (emailPasswordUserId != null) { userIds.add(emailPasswordUserId); } - String passwordlessUserId = PasswordlessQueries.getPrimaryUserIdUsingEmail(start, tenantIdentifier, email); + String passwordlessUserId = PasswordlessQueries.getPrimaryUserIdUsingEmail(start, con, tenantIdentifier, + email); if (passwordlessUserId != null) { userIds.add(passwordlessUserId); } - userIds.addAll(ThirdPartyQueries.getPrimaryUserIdUsingEmail(start, tenantIdentifier, email)); + userIds.addAll(ThirdPartyQueries.getPrimaryUserIdUsingEmail(start, con, tenantIdentifier, email)); // remove duplicates from userIds Set userIdsSet = new HashSet<>(userIds); userIds = new ArrayList<>(userIdsSet); - List result = getPrimaryUserInfoForUserIds(start, tenantIdentifier.toAppIdentifier(), + List result = getPrimaryUserInfoForUserIds(start, con, tenantIdentifier.toAppIdentifier(), userIds); // this is going to order them based on oldest that joined to newest that joined. @@ -895,32 +964,53 @@ public static AuthRecipeUserInfo[] listPrimaryUsersByEmail(Start start, TenantId return result.toArray(new AuthRecipeUserInfo[0]); } - public static AuthRecipeUserInfo[] listPrimaryUsersByPhoneNumber(Start start, TenantIdentifier tenantIdentifier, + public static AuthRecipeUserInfo[] listPrimaryUsersByPhoneNumber(Start start, + TenantIdentifier tenantIdentifier, String phoneNumber) throws StorageQueryException, SQLException { + try (Connection con = ConnectionPool.getConnection(start)) { + return listPrimaryUsersByPhoneNumberHelper(start, con, tenantIdentifier, phoneNumber); + } + } + + private static AuthRecipeUserInfo[] listPrimaryUsersByPhoneNumberHelper(Start start, Connection con, + TenantIdentifier tenantIdentifier, + String phoneNumber) + throws StorageQueryException, SQLException { List userIds = new ArrayList<>(); - String passwordlessUserId = PasswordlessQueries.getPrimaryUserByPhoneNumber(start, tenantIdentifier, + String passwordlessUserId = PasswordlessQueries.getPrimaryUserByPhoneNumber(start, con, tenantIdentifier, phoneNumber); if (passwordlessUserId != null) { userIds.add(passwordlessUserId); } - List result = getPrimaryUserInfoForUserIds(start, tenantIdentifier.toAppIdentifier(), + List result = getPrimaryUserInfoForUserIds(start, con, tenantIdentifier.toAppIdentifier(), userIds); return result.toArray(new AuthRecipeUserInfo[0]); } - public static AuthRecipeUserInfo getPrimaryUserByThirdPartyInfo(Start start, TenantIdentifier tenantIdentifier, + public static AuthRecipeUserInfo getPrimaryUserByThirdPartyInfo(Start start, + TenantIdentifier tenantIdentifier, String thirdPartyId, String thirdPartyUserId) throws StorageQueryException, SQLException { + try (Connection con = ConnectionPool.getConnection(start)) { + return getPrimaryUserByThirdPartyInfoHelper(start, con, tenantIdentifier, thirdPartyId, thirdPartyUserId); + } + } + + private static AuthRecipeUserInfo getPrimaryUserByThirdPartyInfoHelper(Start start, Connection con, + TenantIdentifier tenantIdentifier, + String thirdPartyId, + String thirdPartyUserId) + throws StorageQueryException, SQLException { - String userId = ThirdPartyQueries.getThirdPartyUserInfoUsingId(start, tenantIdentifier, + String userId = ThirdPartyQueries.getThirdPartyUserInfoUsingId(start, con, tenantIdentifier, thirdPartyId, thirdPartyUserId); if (userId != null) { - return getPrimaryUserInfoForUserId(start, tenantIdentifier.toAppIdentifier(), + return getPrimaryUserInfoForUserId(start, con, tenantIdentifier.toAppIdentifier(), userId); } return null; @@ -963,11 +1053,11 @@ public static AuthRecipeUserInfo getPrimaryUserInfoForUserId_Transaction(Start s List loginMethods = new ArrayList<>(); loginMethods.addAll( - EmailPasswordQueries.getUsersInfoUsingIdList_Transaction(start, sqlCon, recipeUserIdsToFetch, + EmailPasswordQueries.getUsersInfoUsingIdList(start, sqlCon, recipeUserIdsToFetch, appIdentifier)); - loginMethods.addAll(ThirdPartyQueries.getUsersInfoUsingIdList_Transaction(start, sqlCon, recipeUserIdsToFetch, + loginMethods.addAll(ThirdPartyQueries.getUsersInfoUsingIdList(start, sqlCon, recipeUserIdsToFetch, appIdentifier)); - loginMethods.addAll(PasswordlessQueries.getUsersInfoUsingIdList_Transaction(start, sqlCon, recipeUserIdsToFetch, + loginMethods.addAll(PasswordlessQueries.getUsersInfoUsingIdList(start, sqlCon, recipeUserIdsToFetch, appIdentifier)); // we do this in such a strange way cause the create function takes just one login method at the moment. @@ -982,9 +1072,17 @@ public static AuthRecipeUserInfo getPrimaryUserInfoForUserId_Transaction(Start s public static AuthRecipeUserInfo getPrimaryUserInfoForUserId(Start start, AppIdentifier appIdentifier, String id) throws SQLException, StorageQueryException { + try (Connection con = ConnectionPool.getConnection(start)) { + return getPrimaryUserInfoForUserId(start, con, appIdentifier, id); + } + } + + private static AuthRecipeUserInfo getPrimaryUserInfoForUserId(Start start, Connection con, + AppIdentifier appIdentifier, String id) + throws SQLException, StorageQueryException { List ids = new ArrayList<>(); ids.add(id); - List result = getPrimaryUserInfoForUserIds(start, appIdentifier, ids); + List result = getPrimaryUserInfoForUserIds(start, con, appIdentifier, ids); if (result.isEmpty()) { return null; } @@ -992,6 +1090,7 @@ public static AuthRecipeUserInfo getPrimaryUserInfoForUserId(Start start, AppIde } private static List getPrimaryUserInfoForUserIds(Start start, + Connection con, AppIdentifier appIdentifier, List userIds) throws StorageQueryException, SQLException { @@ -1009,7 +1108,7 @@ private static List getPrimaryUserInfoForUserIds(Start start io.supertokens.inmemorydb.Utils.generateCommaSeperatedQuestionMarks(userIds.size()) + ")) AND app_id = ?"; - List allAuthUsersResult = execute(start, QUERY, pst -> { + List allAuthUsersResult = execute(con, QUERY, pst -> { // IN user_id int index = 1; for (int i = 0; i < userIds.size(); i++, index++) { @@ -1042,9 +1141,11 @@ private static List getPrimaryUserInfoForUserIds(Start start } List loginMethods = new ArrayList<>(); - loginMethods.addAll(EmailPasswordQueries.getUsersInfoUsingIdList(start, recipeUserIdsToFetch, appIdentifier)); - loginMethods.addAll(ThirdPartyQueries.getUsersInfoUsingIdList(start, recipeUserIdsToFetch, appIdentifier)); - loginMethods.addAll(PasswordlessQueries.getUsersInfoUsingIdList(start, recipeUserIdsToFetch, appIdentifier)); + loginMethods.addAll( + EmailPasswordQueries.getUsersInfoUsingIdList(start, con, recipeUserIdsToFetch, appIdentifier)); + loginMethods.addAll(ThirdPartyQueries.getUsersInfoUsingIdList(start, con, recipeUserIdsToFetch, appIdentifier)); + loginMethods.addAll( + PasswordlessQueries.getUsersInfoUsingIdList(start, con, recipeUserIdsToFetch, appIdentifier)); Map recipeUserIdToLoginMethodMap = new HashMap<>(); for (LoginMethod loginMethod : loginMethods) { @@ -1073,6 +1174,20 @@ private static List getPrimaryUserInfoForUserIds(Start start .collect(Collectors.toList()); } + private static List getPrimaryUserInfoForUserIds(Start start, + AppIdentifier appIdentifier, + List userIds) + throws StorageQueryException, SQLException { + if (userIds.size() == 0) { + return new ArrayList<>(); + } + + try (Connection con = ConnectionPool.getConnection(start)) { + return getPrimaryUserInfoForUserIds(start, con, appIdentifier, userIds); + } + + } + public static String getRecipeIdForUser_Transaction(Start start, Connection sqlCon, TenantIdentifier tenantIdentifier, String userId) throws SQLException, StorageQueryException { diff --git a/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java index fcd43e2b4..0e51af72b 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java @@ -678,8 +678,8 @@ public static PasswordlessCode getCodeByLinkCodeHash(Start start, TenantIdentifi } } - public static List getUsersInfoUsingIdList_Transaction(Start start, Connection con, Set ids, - AppIdentifier appIdentifier) + public static List getUsersInfoUsingIdList(Start start, Connection con, Set ids, + AppIdentifier appIdentifier) throws SQLException, StorageQueryException { if (ids.size() > 0) { // No need to filter based on tenantId because the id list is already filtered for a tenant @@ -708,16 +708,6 @@ public static List getUsersInfoUsingIdList_Transaction(Start start, return Collections.emptyList(); } - public static List getUsersInfoUsingIdList(Start start, Set ids, AppIdentifier appIdentifier) - throws SQLException, StorageQueryException { - if (ids.size() > 0) { - try (Connection con = ConnectionPool.getConnection(start)) { - return getUsersInfoUsingIdList_Transaction(start, con, ids, appIdentifier); - } - } - return Collections.emptyList(); - } - public static UserInfoPartial getUserById(Start start, Connection sqlCon, AppIdentifier appIdentifier, String userId) throws StorageQueryException, SQLException { // we don't need a LOCK here because this is already part of a transaction, and locked on app_id_to_user_id @@ -737,7 +727,52 @@ public static UserInfoPartial getUserById(Start start, Connection sqlCon, AppIde }); } - public static String getPrimaryUserIdUsingEmail(Start start, TenantIdentifier tenantIdentifier, String email) + public static String lockEmailAndTenant_Transaction(Start start, Connection con, TenantIdentifier tenantIdentifier, + String email) throws SQLException, StorageQueryException { + // normally the query below will use a for update, but sqlite doesn't support it. + ((ConnectionWithLocks) con).lock( + tenantIdentifier.getAppId() + tenantIdentifier.getTenantId() + "~" + email + + Config.getConfig(start).getPasswordlessUserToTenantTable()); + + String QUERY = "SELECT user_id FROM " + getConfig(start).getPasswordlessUserToTenantTable() + + " WHERE app_id = ? AND tenant_id = ? AND email = ?"; + return execute(con, QUERY, pst -> { + pst.setString(1, tenantIdentifier.getAppId()); + pst.setString(2, tenantIdentifier.getTenantId()); + pst.setString(3, email); + }, result -> { + if (result.next()) { + return result.getString("user_id"); + } + return null; + }); + } + + public static String lockPhoneAndTenant_Transaction(Start start, Connection con, + TenantIdentifier tenantIdentifier, + String phoneNumber) + throws SQLException, StorageQueryException { + // normally the query below will use a for update, but sqlite doesn't support it. + ((ConnectionWithLocks) con).lock( + tenantIdentifier.getAppId() + tenantIdentifier.getTenantId() + "~" + phoneNumber + + Config.getConfig(start).getPasswordlessUserToTenantTable()); + + String QUERY = "SELECT user_id FROM " + getConfig(start).getPasswordlessUserToTenantTable() + + " WHERE app_id = ? AND tenant_id = ? AND phone_number = ?"; + return execute(con, QUERY, pst -> { + pst.setString(1, tenantIdentifier.getAppId()); + pst.setString(2, tenantIdentifier.getTenantId()); + pst.setString(3, phoneNumber); + }, result -> { + if (result.next()) { + return result.getString("user_id"); + } + return null; + }); + } + + public static String getPrimaryUserIdUsingEmail(Start start, Connection con, TenantIdentifier tenantIdentifier, + String email) throws StorageQueryException, SQLException { String QUERY = "SELECT DISTINCT all_users.primary_or_recipe_user_id AS user_id " + "FROM " + getConfig(start).getPasswordlessUserToTenantTable() + " AS pless" + @@ -745,7 +780,7 @@ public static String getPrimaryUserIdUsingEmail(Start start, TenantIdentifier te " ON pless.app_id = all_users.app_id AND pless.user_id = all_users.user_id" + " WHERE pless.app_id = ? AND pless.tenant_id = ? AND pless.email = ?"; - return execute(start, QUERY, pst -> { + return execute(con, QUERY, pst -> { pst.setString(1, tenantIdentifier.getAppId()); pst.setString(2, tenantIdentifier.getTenantId()); pst.setString(3, email); @@ -757,7 +792,7 @@ public static String getPrimaryUserIdUsingEmail(Start start, TenantIdentifier te }); } - public static String getPrimaryUserByPhoneNumber(Start start, TenantIdentifier tenantIdentifier, + public static String getPrimaryUserByPhoneNumber(Start start, Connection con, TenantIdentifier tenantIdentifier, @Nonnull String phoneNumber) throws StorageQueryException, SQLException { String QUERY = "SELECT DISTINCT all_users.primary_or_recipe_user_id AS user_id " @@ -766,7 +801,7 @@ public static String getPrimaryUserByPhoneNumber(Start start, TenantIdentifier t " ON pless.app_id = all_users.app_id AND pless.user_id = all_users.user_id" + " WHERE pless.app_id = ? AND pless.tenant_id = ? AND pless.phone_number = ?"; - return execute(start, QUERY, pst -> { + return execute(con, QUERY, pst -> { pst.setString(1, tenantIdentifier.getAppId()); pst.setString(2, tenantIdentifier.getTenantId()); pst.setString(3, phoneNumber); diff --git a/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java index b2b251b1a..717d3062e 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java @@ -16,7 +16,6 @@ package io.supertokens.inmemorydb.queries; -import io.supertokens.inmemorydb.ConnectionPool; import io.supertokens.inmemorydb.ConnectionWithLocks; import io.supertokens.inmemorydb.Start; import io.supertokens.inmemorydb.Utils; @@ -176,8 +175,64 @@ public static void deleteUser(Start start, AppIdentifier appIdentifier, String u }); } - public static List getUsersInfoUsingIdList_Transaction(Start start, Connection con, Set ids, - AppIdentifier appIdentifier) + public static List lockEmailAndTenant_Transaction(Start start, Connection con, + TenantIdentifier tenantIdentifier, + String email) throws SQLException, StorageQueryException { + // normally the query below will use a for update, but sqlite doesn't support it. + ((ConnectionWithLocks) con).lock( + tenantIdentifier.getAppId() + tenantIdentifier.getTenantId() + "~" + email + + Config.getConfig(start).getThirdPartyUserToTenantTable()); + + // in psql / mysql dbs, this will lock the rows that are in both the tables that meet the ON criteria only. + String QUERY = "SELECT tp.user_id as user_id " + + "FROM " + getConfig(start).getThirdPartyUsersTable() + " AS tp" + + " JOIN " + getConfig(start).getThirdPartyUserToTenantTable() + " AS tp_tenants" + + " ON tp_tenants.app_id = tp.app_id AND tp_tenants.user_id = tp.user_id" + + " WHERE tp.app_id = ? AND tp_tenants.tenant_id = ? AND tp.email = ?"; + + return execute(start, QUERY, pst -> { + pst.setString(1, tenantIdentifier.getAppId()); + pst.setString(2, tenantIdentifier.getTenantId()); + pst.setString(3, email); + }, result -> { + List finalResult = new ArrayList<>(); + while (result.next()) { + finalResult.add(result.getString("user_id")); + } + return finalResult; + }); + } + + public static List lockThirdPartyInfoAndTenant_Transaction(Start start, Connection con, + TenantIdentifier tenantIdentifier, + String thirdPartyId, String thirdPartyUserId) + throws SQLException, StorageQueryException { + // normally the query below will use a for update, but sqlite doesn't support it. + ((ConnectionWithLocks) con).lock( + tenantIdentifier.getAppId() + tenantIdentifier.getTenantId() + "~" + thirdPartyId + thirdPartyUserId + + Config.getConfig(start).getThirdPartyUserToTenantTable()); + + // in psql / mysql dbs, this will lock the rows that are in both the tables that meet the ON criteria only. + String QUERY = "SELECT user_id " + + " FROM " + getConfig(start).getThirdPartyUserToTenantTable() + + " WHERE app_id = ? AND tenant_id = ? AND third_party_id = ? AND third_party_user_id = ?"; + + return execute(start, QUERY, pst -> { + pst.setString(1, tenantIdentifier.getAppId()); + pst.setString(2, tenantIdentifier.getTenantId()); + pst.setString(3, thirdPartyId); + pst.setString(4, thirdPartyUserId); + }, result -> { + List finalResult = new ArrayList<>(); + while (result.next()) { + finalResult.add(result.getString("user_id")); + } + return finalResult; + }); + } + + public static List getUsersInfoUsingIdList(Start start, Connection con, Set ids, + AppIdentifier appIdentifier) throws SQLException, StorageQueryException { if (ids.size() > 0) { String QUERY = "SELECT user_id, third_party_id, third_party_user_id, email, time_joined " @@ -206,17 +261,7 @@ public static List getUsersInfoUsingIdList_Transaction(Start start, return Collections.emptyList(); } - public static List getUsersInfoUsingIdList(Start start, Set ids, AppIdentifier appIdentifier) - throws SQLException, StorageQueryException { - if (ids.size() > 0) { - try (Connection con = ConnectionPool.getConnection(start)) { - return getUsersInfoUsingIdList_Transaction(start, con, ids, appIdentifier); - } - } - return Collections.emptyList(); - } - - public static String getThirdPartyUserInfoUsingId(Start start, TenantIdentifier tenantIdentifier, + public static String getThirdPartyUserInfoUsingId(Start start, Connection con, TenantIdentifier tenantIdentifier, String thirdPartyId, String thirdPartyUserId) throws SQLException, StorageQueryException { String QUERY = "SELECT DISTINCT all_users.primary_or_recipe_user_id AS user_id " @@ -225,7 +270,7 @@ public static String getThirdPartyUserInfoUsingId(Start start, TenantIdentifier " ON tp.app_id = all_users.app_id AND tp.user_id = all_users.user_id" + " WHERE tp.app_id = ? AND tp.tenant_id = ? AND tp.third_party_id = ? AND tp.third_party_user_id = ?"; - return execute(start, QUERY, pst -> { + return execute(con, QUERY, pst -> { pst.setString(1, tenantIdentifier.getAppId()); pst.setString(2, tenantIdentifier.getTenantId()); pst.setString(3, thirdPartyId); @@ -295,7 +340,8 @@ private static UserInfoPartial getUserInfoUsingUserId(Start start, Connection co }); } - public static List getPrimaryUserIdUsingEmail(Start start, TenantIdentifier tenantIdentifier, String email) + public static List getPrimaryUserIdUsingEmail(Start start, Connection con, + TenantIdentifier tenantIdentifier, String email) throws StorageQueryException, SQLException { String QUERY = "SELECT DISTINCT all_users.primary_or_recipe_user_id AS user_id " + "FROM " + getConfig(start).getThirdPartyUsersTable() + " AS tp" + @@ -305,7 +351,7 @@ public static List getPrimaryUserIdUsingEmail(Start start, TenantIdentif " ON tp_tenants.app_id = all_users.app_id AND tp_tenants.user_id = all_users.user_id" + " WHERE tp.app_id = ? AND tp_tenants.tenant_id = ? AND tp.email = ?"; - return execute(start, QUERY, pst -> { + return execute(con, QUERY, pst -> { pst.setString(1, tenantIdentifier.getAppId()); pst.setString(2, tenantIdentifier.getTenantId()); pst.setString(3, email); From 5e4568ec815b3c078f8b477fab697ffed8a09ac7 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Tue, 25 Jul 2023 14:06:40 +0530 Subject: [PATCH 030/131] fixes small issue --- .../io/supertokens/inmemorydb/queries/ThirdPartyQueries.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java index 717d3062e..3d325eb72 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java @@ -190,7 +190,7 @@ public static List lockEmailAndTenant_Transaction(Start start, Connectio " ON tp_tenants.app_id = tp.app_id AND tp_tenants.user_id = tp.user_id" + " WHERE tp.app_id = ? AND tp_tenants.tenant_id = ? AND tp.email = ?"; - return execute(start, QUERY, pst -> { + return execute(con, QUERY, pst -> { pst.setString(1, tenantIdentifier.getAppId()); pst.setString(2, tenantIdentifier.getTenantId()); pst.setString(3, email); @@ -217,7 +217,7 @@ public static List lockThirdPartyInfoAndTenant_Transaction(Start start, " FROM " + getConfig(start).getThirdPartyUserToTenantTable() + " WHERE app_id = ? AND tenant_id = ? AND third_party_id = ? AND third_party_user_id = ?"; - return execute(start, QUERY, pst -> { + return execute(con, QUERY, pst -> { pst.setString(1, tenantIdentifier.getAppId()); pst.setString(2, tenantIdentifier.getTenantId()); pst.setString(3, thirdPartyId); From af7b850ec98f686f192a050db966c44ee70ddd17 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Tue, 25 Jul 2023 21:01:27 +0530 Subject: [PATCH 031/131] starts working on account linking tests --- .../accountlinking/AccountLinkingTest.java | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 src/test/java/io/supertokens/test/accountlinking/AccountLinkingTest.java diff --git a/src/test/java/io/supertokens/test/accountlinking/AccountLinkingTest.java b/src/test/java/io/supertokens/test/accountlinking/AccountLinkingTest.java new file mode 100644 index 000000000..ed1b7d268 --- /dev/null +++ b/src/test/java/io/supertokens/test/accountlinking/AccountLinkingTest.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2023, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.test.accountlinking; + +import io.supertokens.ProcessState; +import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.storageLayer.StorageLayer; +import io.supertokens.test.TestingProcessManager; +import io.supertokens.test.Utils; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; + +import static org.junit.Assert.assertNotNull; + +public class AccountLinkingTest { + @Rule + public TestRule watchman = Utils.getOnFailure(); + + @AfterClass + public static void afterTesting() { + Utils.afterTesting(); + } + + @Before + public void beforeEach() { + Utils.reset(); + } + + @Test + public void testIfAUserIdIsASuperTokensUserId() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + // TODO:... + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + + } +} From 6c2f281530720328ffc9fab69a77f38848fca3d4 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Wed, 26 Jul 2023 13:41:35 +0530 Subject: [PATCH 032/131] adds a few tests --- .../io/supertokens/authRecipe/AuthRecipe.java | 47 ++++- .../inmemorydb/queries/GeneralQueries.java | 4 +- .../accountlinking/AccountLinkingTest.java | 62 ------ .../accountlinking/CreatePrimaryUserTest.java | 180 ++++++++++++++++++ 4 files changed, 222 insertions(+), 71 deletions(-) delete mode 100644 src/test/java/io/supertokens/test/accountlinking/AccountLinkingTest.java create mode 100644 src/test/java/io/supertokens/test/accountlinking/CreatePrimaryUserTest.java diff --git a/src/main/java/io/supertokens/authRecipe/AuthRecipe.java b/src/main/java/io/supertokens/authRecipe/AuthRecipe.java index 46cc63a26..bbaef081f 100644 --- a/src/main/java/io/supertokens/authRecipe/AuthRecipe.java +++ b/src/main/java/io/supertokens/authRecipe/AuthRecipe.java @@ -19,6 +19,9 @@ import io.supertokens.Main; import io.supertokens.authRecipe.exception.AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException; import io.supertokens.authRecipe.exception.RecipeUserIdAlreadyLinkedWithPrimaryUserIdException; +import io.supertokens.featureflag.EE_FEATURES; +import io.supertokens.featureflag.FeatureFlag; +import io.supertokens.featureflag.exceptions.FeatureNotEnabledException; import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.STORAGE_TYPE; @@ -42,10 +45,7 @@ import org.jetbrains.annotations.TestOnly; import javax.annotation.Nullable; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; +import java.util.*; /*This files contains functions that are common for all auth recipes*/ @@ -53,14 +53,22 @@ public class AuthRecipe { public static final int USER_PAGINATION_LIMIT = 500; + @TestOnly + public static AuthRecipeUserInfo getUserById(Main main, String userId) + throws StorageQueryException { + AppIdentifierWithStorage appId = new AppIdentifierWithStorage(null, null, + StorageLayer.getStorage(main)); + return getUserById(appId, userId); + } + public static AuthRecipeUserInfo getUserById(AppIdentifierWithStorage appIdentifierWithStorage, String userId) throws StorageQueryException { return appIdentifierWithStorage.getAuthRecipeStorage().getPrimaryUserById(appIdentifierWithStorage, userId); } public static class CreatePrimaryUserResult { - AuthRecipeUserInfo user; - boolean wasAlreadyAPrimaryUser; + public AuthRecipeUserInfo user; + public boolean wasAlreadyAPrimaryUser; public CreatePrimaryUserResult(AuthRecipeUserInfo user, boolean wasAlreadyAPrimaryUser) { this.user = user; @@ -68,11 +76,34 @@ public CreatePrimaryUserResult(AuthRecipeUserInfo user, boolean wasAlreadyAPrima } } + @TestOnly + public static CreatePrimaryUserResult createPrimaryUser(Main main, + String recipeUserId) + throws StorageQueryException, AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException, + RecipeUserIdAlreadyLinkedWithPrimaryUserIdException, UnknownUserIdException, + FeatureNotEnabledException { + AppIdentifierWithStorage appId = new AppIdentifierWithStorage(null, null, + StorageLayer.getStorage(main)); + try { + return createPrimaryUser(main, appId, recipeUserId); + } catch (TenantOrAppNotFoundException e) { + throw new RuntimeException(e); + } + } + public static CreatePrimaryUserResult createPrimaryUser(Main main, AppIdentifierWithStorage appIdentifierWithStorage, String recipeUserId) throws StorageQueryException, AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException, - RecipeUserIdAlreadyLinkedWithPrimaryUserIdException, UnknownUserIdException { + RecipeUserIdAlreadyLinkedWithPrimaryUserIdException, UnknownUserIdException, TenantOrAppNotFoundException, + FeatureNotEnabledException { + + if (Arrays.stream(FeatureFlag.getInstance(main, appIdentifierWithStorage).getEnabledFeatures()) + .noneMatch(t -> t == EE_FEATURES.ACCOUNT_LINKING)) { + throw new FeatureNotEnabledException( + "Account linking feature is not enabled for this app. Please contact support to enable it."); + } + AuthRecipeSQLStorage storage = (AuthRecipeSQLStorage) appIdentifierWithStorage.getAuthRecipeStorage(); try { return storage.startTransaction(con -> { @@ -150,6 +181,8 @@ public static CreatePrimaryUserResult createPrimaryUser(Main main, storage.commitTransaction(con); + targetUser.isPrimaryUser = true; + return new CreatePrimaryUserResult(targetUser, false); }); } catch (StorageTransactionLogicException e) { diff --git a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java index 554893012..097fc15c0 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java @@ -870,9 +870,9 @@ public static void makePrimaryUser_Transaction(Start start, Connection sqlCon, S String QUERY = "UPDATE " + getConfig(start).getUsersTable() + " SET is_linked_or_is_a_primary_user = true WHERE user_id = ?"; - execute(sqlCon, QUERY, pst -> { + update(sqlCon, QUERY, pst -> { pst.setString(1, userId); - }, result -> null); + }); } public static AuthRecipeUserInfo[] listPrimaryUsersByPhoneNumber_Transaction(Start start, Connection sqlCon, diff --git a/src/test/java/io/supertokens/test/accountlinking/AccountLinkingTest.java b/src/test/java/io/supertokens/test/accountlinking/AccountLinkingTest.java deleted file mode 100644 index ed1b7d268..000000000 --- a/src/test/java/io/supertokens/test/accountlinking/AccountLinkingTest.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2023, VRAI Labs and/or its affiliates. All rights reserved. - * - * This software is licensed under the Apache License, Version 2.0 (the - * "License") as published by the Apache Software Foundation. - * - * You may not use this file except in compliance with the License. You may - * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - */ - -package io.supertokens.test.accountlinking; - -import io.supertokens.ProcessState; -import io.supertokens.pluginInterface.STORAGE_TYPE; -import io.supertokens.storageLayer.StorageLayer; -import io.supertokens.test.TestingProcessManager; -import io.supertokens.test.Utils; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TestRule; - -import static org.junit.Assert.assertNotNull; - -public class AccountLinkingTest { - @Rule - public TestRule watchman = Utils.getOnFailure(); - - @AfterClass - public static void afterTesting() { - Utils.afterTesting(); - } - - @Before - public void beforeEach() { - Utils.reset(); - } - - @Test - public void testIfAUserIdIsASuperTokensUserId() throws Exception { - String[] args = {"../"}; - TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); - assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); - - if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { - return; - } - - // TODO:... - - process.kill(); - assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); - - } -} diff --git a/src/test/java/io/supertokens/test/accountlinking/CreatePrimaryUserTest.java b/src/test/java/io/supertokens/test/accountlinking/CreatePrimaryUserTest.java new file mode 100644 index 000000000..2abed1dfd --- /dev/null +++ b/src/test/java/io/supertokens/test/accountlinking/CreatePrimaryUserTest.java @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2023, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.test.accountlinking; + +import io.supertokens.ProcessState; +import io.supertokens.authRecipe.AuthRecipe; +import io.supertokens.emailpassword.EmailPassword; +import io.supertokens.featureflag.EE_FEATURES; +import io.supertokens.featureflag.FeatureFlagTestContent; +import io.supertokens.featureflag.exceptions.FeatureNotEnabledException; +import io.supertokens.passwordless.Passwordless; +import io.supertokens.pluginInterface.RECIPE_ID; +import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; +import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; +import io.supertokens.storageLayer.StorageLayer; +import io.supertokens.test.TestingProcessManager; +import io.supertokens.test.Utils; +import io.supertokens.thirdparty.ThirdParty; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; + +import static org.junit.Assert.assertNotNull; + +/* + * TODO: + * - locking test - make several requests for two users with the same email with making them primary and not primary + * and then check that the db state is always consistent. + * - locking test - make sure that deadlocks are resolved on their own. + * - check for making primary user across tenants logic. + * */ + +public class CreatePrimaryUserTest { + @Rule + public TestRule watchman = Utils.getOnFailure(); + + @AfterClass + public static void afterTesting() { + Utils.afterTesting(); + } + + @Before + public void beforeEach() { + Utils.reset(); + } + + @Test + public void testThatOnSignUpUserIsNotAPrimaryUser() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "abcd1234"); + assert (!user.isPrimaryUser); + assert (user.loginMethods.length == 1); + assert (user.loginMethods[0].recipeId == RECIPE_ID.EMAIL_PASSWORD); + assert (user.loginMethods[0].email.equals("test@example.com")); + assert (user.loginMethods[0].passwordHash != null); + assert (user.loginMethods[0].thirdParty == null); + assert (user.id.equals(user.loginMethods[0].recipeUserId)); + assert (user.loginMethods[0].phoneNumber == null); + + ThirdParty.SignInUpResponse resp = ThirdParty.signInUp(process.getProcess(), "google", "user-google", + "test@example.com"); + assert (!resp.user.isPrimaryUser); + assert (resp.user.loginMethods.length == 1); + assert (resp.user.loginMethods[0].recipeId == RECIPE_ID.THIRD_PARTY); + assert (resp.user.loginMethods[0].email.equals("test@example.com")); + assert (resp.user.loginMethods[0].thirdParty.userId.equals("user-google")); + assert (resp.user.loginMethods[0].thirdParty.id.equals("google")); + assert (resp.user.loginMethods[0].phoneNumber == null); + assert (resp.user.loginMethods[0].passwordHash == null); + assert (user.id.equals(user.loginMethods[0].recipeUserId)); + + { + Passwordless.CreateCodeResponse code = Passwordless.createCode(process.getProcess(), "u@e.com", null, null, + null); + Passwordless.ConsumeCodeResponse pResp = Passwordless.consumeCode(process.getProcess(), code.deviceId, + code.deviceIdHash, code.userInputCode, null); + assert (!pResp.user.isPrimaryUser); + assert (pResp.user.loginMethods.length == 1); + assert (pResp.user.loginMethods[0].recipeId == RECIPE_ID.PASSWORDLESS); + assert (pResp.user.loginMethods[0].email.equals("u@e.com")); + assert (pResp.user.loginMethods[0].passwordHash == null); + assert (pResp.user.loginMethods[0].thirdParty == null); + assert (pResp.user.loginMethods[0].phoneNumber == null); + assert (user.id.equals(user.loginMethods[0].recipeUserId)); + } + + { + Passwordless.CreateCodeResponse code = Passwordless.createCode(process.getProcess(), null, "12345", null, + null); + Passwordless.ConsumeCodeResponse pResp = Passwordless.consumeCode(process.getProcess(), code.deviceId, + code.deviceIdHash, code.userInputCode, null); + assert (!pResp.user.isPrimaryUser); + assert (pResp.user.loginMethods.length == 1); + assert (pResp.user.loginMethods[0].recipeId == RECIPE_ID.PASSWORDLESS); + assert (pResp.user.loginMethods[0].email == null); + assert (pResp.user.loginMethods[0].passwordHash == null); + assert (pResp.user.loginMethods[0].thirdParty == null); + assert (pResp.user.loginMethods[0].phoneNumber.equals("12345")); + assert (user.id.equals(user.loginMethods[0].recipeUserId)); + } + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testThatCreationOfPrimaryUserRequiresAccountLinkingFeatureToBeEnabled() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + try { + AuthRecipe.createPrimaryUser(process.main, + new AppIdentifierWithStorage(null, null, StorageLayer.getStorage(process.main)), ""); + assert (false); + } catch (FeatureNotEnabledException e) { + assert (e.getMessage() + .equals("Account linking feature is not enabled for this app. Please contact support to enable it" + + ".")); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void makePrimaryUserSuccess() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + AuthRecipeUserInfo emailPasswordUser = EmailPassword.signUp(process.getProcess(), "test@example.com", + "pass1234"); + + AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.createPrimaryUser(process.main, emailPasswordUser.id); + assert (!result.wasAlreadyAPrimaryUser); + assert (result.user.isPrimaryUser); + assert (result.user.loginMethods.length == 1); + assert (result.user.loginMethods[0].recipeId == RECIPE_ID.EMAIL_PASSWORD); + assert (result.user.loginMethods[0].email.equals("test@example.com")); + assert (result.user.loginMethods[0].passwordHash != null); + assert (result.user.loginMethods[0].thirdParty == null); + assert (result.user.id.equals(result.user.loginMethods[0].recipeUserId)); + assert (result.user.loginMethods[0].phoneNumber == null); + + AuthRecipeUserInfo refetchedUser = AuthRecipe.getUserById(process.main, result.user.id); + + assert (refetchedUser.equals(result.user)); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } +} From a3dafddb57b6c46af3bc4cd018e3309db9b1d40e Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Wed, 26 Jul 2023 14:04:27 +0530 Subject: [PATCH 033/131] adds more tests --- .../accountlinking/CreatePrimaryUserTest.java | 113 +++++++++++++++++- 1 file changed, 109 insertions(+), 4 deletions(-) diff --git a/src/test/java/io/supertokens/test/accountlinking/CreatePrimaryUserTest.java b/src/test/java/io/supertokens/test/accountlinking/CreatePrimaryUserTest.java index 2abed1dfd..b912f1614 100644 --- a/src/test/java/io/supertokens/test/accountlinking/CreatePrimaryUserTest.java +++ b/src/test/java/io/supertokens/test/accountlinking/CreatePrimaryUserTest.java @@ -91,7 +91,7 @@ public void testThatOnSignUpUserIsNotAPrimaryUser() throws Exception { assert (resp.user.loginMethods[0].thirdParty.id.equals("google")); assert (resp.user.loginMethods[0].phoneNumber == null); assert (resp.user.loginMethods[0].passwordHash == null); - assert (user.id.equals(user.loginMethods[0].recipeUserId)); + assert (resp.user.id.equals(resp.user.loginMethods[0].recipeUserId)); { Passwordless.CreateCodeResponse code = Passwordless.createCode(process.getProcess(), "u@e.com", null, null, @@ -105,7 +105,7 @@ public void testThatOnSignUpUserIsNotAPrimaryUser() throws Exception { assert (pResp.user.loginMethods[0].passwordHash == null); assert (pResp.user.loginMethods[0].thirdParty == null); assert (pResp.user.loginMethods[0].phoneNumber == null); - assert (user.id.equals(user.loginMethods[0].recipeUserId)); + assert (pResp.user.id.equals(pResp.user.loginMethods[0].recipeUserId)); } { @@ -120,7 +120,7 @@ public void testThatOnSignUpUserIsNotAPrimaryUser() throws Exception { assert (pResp.user.loginMethods[0].passwordHash == null); assert (pResp.user.loginMethods[0].thirdParty == null); assert (pResp.user.loginMethods[0].phoneNumber.equals("12345")); - assert (user.id.equals(user.loginMethods[0].recipeUserId)); + assert (pResp.user.id.equals(pResp.user.loginMethods[0].recipeUserId)); } process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -147,7 +147,7 @@ public void testThatCreationOfPrimaryUserRequiresAccountLinkingFeatureToBeEnable } @Test - public void makePrimaryUserSuccess() throws Exception { + public void makeEmailPasswordPrimaryUserSuccess() throws Exception { String[] args = {"../"}; TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); FeatureFlagTestContent.getInstance(process.getProcess()) @@ -177,4 +177,109 @@ public void makePrimaryUserSuccess() throws Exception { process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); } + + @Test + public void makeThirdPartyPrimaryUserSuccess() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + ThirdParty.SignInUpResponse signInUp = ThirdParty.signInUp(process.getProcess(), "google", + "user-google", + "test@example.com"); + + AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.createPrimaryUser(process.main, + signInUp.user.id); + assert (!result.wasAlreadyAPrimaryUser); + assert (result.user.isPrimaryUser); + assert (result.user.loginMethods.length == 1); + assert (result.user.loginMethods[0].recipeId == RECIPE_ID.THIRD_PARTY); + assert (result.user.loginMethods[0].email.equals("test@example.com")); + assert (result.user.loginMethods[0].thirdParty.userId.equals("user-google")); + assert (result.user.loginMethods[0].thirdParty.id.equals("google")); + assert (result.user.loginMethods[0].phoneNumber == null); + assert (result.user.loginMethods[0].passwordHash == null); + assert (result.user.id.equals(result.user.loginMethods[0].recipeUserId)); + + AuthRecipeUserInfo refetchedUser = AuthRecipe.getUserById(process.main, result.user.id); + + assert (refetchedUser.equals(result.user)); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void makePasswordlessEmailPrimaryUserSuccess() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + Passwordless.CreateCodeResponse code = Passwordless.createCode(process.getProcess(), "u@e.com", null, null, + null); + Passwordless.ConsumeCodeResponse pResp = Passwordless.consumeCode(process.getProcess(), code.deviceId, + code.deviceIdHash, code.userInputCode, null); + + AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.createPrimaryUser(process.main, + pResp.user.id); + assert (!result.wasAlreadyAPrimaryUser); + assert (result.user.isPrimaryUser); + assert (result.user.loginMethods.length == 1); + assert (result.user.loginMethods[0].recipeId == RECIPE_ID.PASSWORDLESS); + assert (result.user.loginMethods[0].email.equals("u@e.com")); + assert (result.user.loginMethods[0].passwordHash == null); + assert (result.user.loginMethods[0].thirdParty == null); + assert (result.user.loginMethods[0].phoneNumber == null); + assert (result.user.id.equals(result.user.loginMethods[0].recipeUserId)); + + AuthRecipeUserInfo refetchedUser = AuthRecipe.getUserById(process.main, result.user.id); + + assert (refetchedUser.equals(result.user)); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void makePasswordlessPhonePrimaryUserSuccess() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + Passwordless.CreateCodeResponse code = Passwordless.createCode(process.getProcess(), null, "1234", null, + null); + Passwordless.ConsumeCodeResponse pResp = Passwordless.consumeCode(process.getProcess(), code.deviceId, + code.deviceIdHash, code.userInputCode, null); + + AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.createPrimaryUser(process.main, + pResp.user.id); + assert (!result.wasAlreadyAPrimaryUser); + assert (result.user.isPrimaryUser); + assert (result.user.loginMethods.length == 1); + assert (result.user.loginMethods[0].recipeId == RECIPE_ID.PASSWORDLESS); + assert (result.user.loginMethods[0].email == null); + assert (result.user.loginMethods[0].passwordHash == null); + assert (result.user.loginMethods[0].thirdParty == null); + assert (result.user.loginMethods[0].phoneNumber.equals("1234")); + assert (result.user.id.equals(result.user.loginMethods[0].recipeUserId)); + + AuthRecipeUserInfo refetchedUser = AuthRecipe.getUserById(process.main, result.user.id); + + assert (refetchedUser.equals(result.user)); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } } From 7654819a3a323e3a7540daceddf0d7570e56b01c Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Wed, 26 Jul 2023 14:39:38 +0530 Subject: [PATCH 034/131] more tests --- .../accountlinking/CreatePrimaryUserTest.java | 193 +++++++++++++++++- 1 file changed, 192 insertions(+), 1 deletion(-) diff --git a/src/test/java/io/supertokens/test/accountlinking/CreatePrimaryUserTest.java b/src/test/java/io/supertokens/test/accountlinking/CreatePrimaryUserTest.java index b912f1614..398911206 100644 --- a/src/test/java/io/supertokens/test/accountlinking/CreatePrimaryUserTest.java +++ b/src/test/java/io/supertokens/test/accountlinking/CreatePrimaryUserTest.java @@ -16,17 +16,21 @@ package io.supertokens.test.accountlinking; +import com.google.gson.JsonObject; import io.supertokens.ProcessState; import io.supertokens.authRecipe.AuthRecipe; +import io.supertokens.authRecipe.exception.AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException; import io.supertokens.emailpassword.EmailPassword; import io.supertokens.featureflag.EE_FEATURES; import io.supertokens.featureflag.FeatureFlagTestContent; import io.supertokens.featureflag.exceptions.FeatureNotEnabledException; +import io.supertokens.multitenancy.Multitenancy; import io.supertokens.passwordless.Passwordless; import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.STORAGE_TYPE; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; -import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; +import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; +import io.supertokens.pluginInterface.multitenancy.*; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.TestingProcessManager; import io.supertokens.test.Utils; @@ -282,4 +286,191 @@ public void makePasswordlessPhonePrimaryUserSuccess() throws Exception { process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); } + + @Test + public void alreadyPrimaryUsertest() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + AuthRecipeUserInfo emailPasswordUser = EmailPassword.signUp(process.getProcess(), "test@example.com", + "pass1234"); + + AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.createPrimaryUser(process.main, emailPasswordUser.id); + assert (!result.wasAlreadyAPrimaryUser); + + result = AuthRecipe.createPrimaryUser(process.main, emailPasswordUser.id); + assert (result.wasAlreadyAPrimaryUser); + assert (result.user.id.equals(emailPasswordUser.id)); + assert (result.user.isPrimaryUser); + assert (result.user.loginMethods.length == 1); + assert (result.user.loginMethods[0].recipeId == RECIPE_ID.EMAIL_PASSWORD); + assert (result.user.loginMethods[0].email.equals("test@example.com")); + assert (result.user.loginMethods[0].passwordHash != null); + assert (result.user.loginMethods[0].thirdParty == null); + assert (result.user.id.equals(result.user.loginMethods[0].recipeUserId)); + assert (result.user.loginMethods[0].phoneNumber == null); + + AuthRecipeUserInfo refetchedUser = AuthRecipe.getUserById(process.main, result.user.id); + + assert (refetchedUser.equals(result.user)); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void makePrimaryUserFailsCauseAnotherAccountWithSameEmailAlreadyAPrimaryUser() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + AuthRecipeUserInfo emailPasswordUser = EmailPassword.signUp(process.getProcess(), "test@example.com", + "pass1234"); + + AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.createPrimaryUser(process.main, emailPasswordUser.id); + assert (!result.wasAlreadyAPrimaryUser); + + ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(process.main, "google", "user-google", + "test@example.com"); + + try { + AuthRecipe.createPrimaryUser(process.main, signInUpResponse.user.id); + assert (false); + } catch (AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException e) { + assert (e.primaryUserId.equals(emailPasswordUser.id)); + assert (e.getMessage().equals("This user's email is already associated with another user ID")); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void makePrimarySucceedsEvenIfAnotherAccountWithSameEmailButIsNotAPrimaryUser() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + AuthRecipeUserInfo emailPasswordUser = EmailPassword.signUp(process.getProcess(), "test@example.com", + "pass1234"); + + ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(process.main, "google", "user-google", + "test@example.com"); + + AuthRecipe.CreatePrimaryUserResult r = AuthRecipe.createPrimaryUser(process.main, signInUpResponse.user.id); + assert (!r.wasAlreadyAPrimaryUser); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void makePrimaryUserFailsCauseAnotherAccountWithSameEmailAlreadyAPrimaryUserInAnotherTenant() + throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + Multitenancy.addNewOrUpdateAppOrTenant(process.main, new TenantIdentifier(null, null, null), + new TenantConfig(new TenantIdentifier(null, null, "t1"), new EmailPasswordConfig(true), + new ThirdPartyConfig(true, new ThirdPartyConfig.Provider[0]), new PasswordlessConfig(true), + new JsonObject())); + + TenantIdentifierWithStorage tenantIdentifierWithStorage = new TenantIdentifierWithStorage(null, null, "t1", + StorageLayer.getStorage(process.main)); + AuthRecipeUserInfo emailPasswordUser = EmailPassword.signUp(tenantIdentifierWithStorage, process.getProcess(), + "test@example.com", + "pass1234"); + + AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.createPrimaryUser(process.main, emailPasswordUser.id); + assert (!result.wasAlreadyAPrimaryUser); + + ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(process.main, "google", "user-google", + "test@example.com"); + + Multitenancy.addUserIdToTenant(process.main, tenantIdentifierWithStorage, signInUpResponse.user.id); + + try { + AuthRecipe.createPrimaryUser(process.main, signInUpResponse.user.id); + assert (false); + } catch (AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException e) { + assert (e.primaryUserId.equals(emailPasswordUser.id)); + assert (e.getMessage().equals("This user's email is already associated with another user ID")); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void makePrimarySucceedsEvenIfAnotherAccountWithSameEmailButInADifferentTenant() + throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + Multitenancy.addNewOrUpdateAppOrTenant(process.main, new TenantIdentifier(null, null, null), + new TenantConfig(new TenantIdentifier(null, null, "t1"), new EmailPasswordConfig(true), + new ThirdPartyConfig(true, new ThirdPartyConfig.Provider[0]), new PasswordlessConfig(true), + new JsonObject())); + + TenantIdentifierWithStorage tenantIdentifierWithStorage = new TenantIdentifierWithStorage(null, null, "t1", + StorageLayer.getStorage(process.main)); + AuthRecipeUserInfo emailPasswordUser = EmailPassword.signUp(tenantIdentifierWithStorage, process.getProcess(), + "test@example.com", + "pass1234"); + + AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.createPrimaryUser(process.main, emailPasswordUser.id); + assert (!result.wasAlreadyAPrimaryUser); + + ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(process.main, "google", "user-google", + "test@example.com"); + + AuthRecipe.CreatePrimaryUserResult r = AuthRecipe.createPrimaryUser(process.main, signInUpResponse.user.id); + assert !r.wasAlreadyAPrimaryUser; + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void makePrimaryUserFailsCauseOfUnknownUserId() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + try { + AuthRecipe.createPrimaryUser(process.main, "random"); + assert (false); + } catch (UnknownUserIdException ignored) { + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } } From 38eb2519a5cd8a298844dec83cf6304879ffa211 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Wed, 26 Jul 2023 15:58:15 +0530 Subject: [PATCH 035/131] adds link account function --- CHANGELOG.md | 2 + .../io/supertokens/authRecipe/AuthRecipe.java | 137 +++++++++++++++++- ...InputUserIdIsNotAPrimaryUserException.java | 26 ++++ ...nkedWithAnotherPrimaryUserIdException.java | 26 ++++ .../java/io/supertokens/inmemorydb/Start.java | 8 +- .../inmemorydb/queries/GeneralQueries.java | 19 ++- 6 files changed, 213 insertions(+), 5 deletions(-) create mode 100644 src/main/java/io/supertokens/authRecipe/exception/InputUserIdIsNotAPrimaryUserException.java create mode 100644 src/main/java/io/supertokens/authRecipe/exception/RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException.java diff --git a/CHANGELOG.md b/CHANGELOG.md index e8ab1e1f4..d0b11ede6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - Added new index `all_auth_recipe_users_primary_user_id_index`. - Added new index `all_auth_recipe_users_primary_user_id_and_tenant_id_index`. - Modified `all_auth_recipe_users_pagination_index` index to be on `primary_or_recipe_user_id` instead of `user_id` +- Added new index `all_auth_recipe_users_user_id_index` - Added a two new columns in `all_auth_recipe_users`: - `primary_or_recipe_user_id` (default value is equal to `user_id` column) - `is_linked_or_is_a_primary_user` (default value is false) @@ -38,6 +39,7 @@ CREATE INDEX all_auth_recipe_users_pagination_index ON all_auth_recipe_users (ti DESC, app_id DESC); CREATE INDEX all_auth_recipe_users_primary_user_id_index ON all_auth_recipe_users (app_id, primary_or_recipe_user_id); CREATE INDEX all_auth_recipe_users_primary_user_id_and_tenant_id_index ON all_auth_recipe_users (app_id, tenant_id, primary_or_recipe_user_id); +CREATE INDEX all_auth_recipe_users_user_id_index ON all_auth_recipe_users(app_id, user_id); ``` ## [6.0.6] - 2023-07-24 diff --git a/src/main/java/io/supertokens/authRecipe/AuthRecipe.java b/src/main/java/io/supertokens/authRecipe/AuthRecipe.java index bbaef081f..8bec97d51 100644 --- a/src/main/java/io/supertokens/authRecipe/AuthRecipe.java +++ b/src/main/java/io/supertokens/authRecipe/AuthRecipe.java @@ -18,6 +18,8 @@ import io.supertokens.Main; import io.supertokens.authRecipe.exception.AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException; +import io.supertokens.authRecipe.exception.InputUserIdIsNotAPrimaryUserException; +import io.supertokens.authRecipe.exception.RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException; import io.supertokens.authRecipe.exception.RecipeUserIdAlreadyLinkedWithPrimaryUserIdException; import io.supertokens.featureflag.EE_FEATURES; import io.supertokens.featureflag.FeatureFlag; @@ -40,6 +42,7 @@ import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.totp.sqlStorage.TOTPSQLStorage; import io.supertokens.pluginInterface.useridmapping.UserIdMapping; +import io.supertokens.session.Session; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.useridmapping.UserIdType; import org.jetbrains.annotations.TestOnly; @@ -76,6 +79,138 @@ public CreatePrimaryUserResult(AuthRecipeUserInfo user, boolean wasAlreadyAPrima } } + public static boolean linkAccounts(Main main, AppIdentifierWithStorage appIdentifierWithStorage, + String _recipeUserId, String _primaryUserId) throws StorageQueryException, + AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException, + RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException, InputUserIdIsNotAPrimaryUserException, + UnknownUserIdException { + + AuthRecipeSQLStorage storage = (AuthRecipeSQLStorage) appIdentifierWithStorage.getAuthRecipeStorage(); + try { + boolean wasAlreadyLinked = storage.startTransaction(con -> { + AuthRecipeUserInfo primaryUser = storage.getPrimaryUserById_Transaction(appIdentifierWithStorage, con, + _primaryUserId); + + if (primaryUser == null) { + throw new StorageTransactionLogicException(new UnknownUserIdException()); + } + + if (!primaryUser.isPrimaryUser) { + throw new StorageTransactionLogicException( + new InputUserIdIsNotAPrimaryUserException(primaryUser.id)); + } + + AuthRecipeUserInfo recipeUser = storage.getPrimaryUserById_Transaction(appIdentifierWithStorage, con, + _recipeUserId); + if (recipeUser == null) { + throw new StorageTransactionLogicException(new UnknownUserIdException()); + } + + if (recipeUser.isPrimaryUser) { + if (recipeUser.id.equals(primaryUser.id)) { + return true; + } else { + throw new StorageTransactionLogicException( + new RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException(recipeUser.id, + "The input recipe user ID is already linked to another user ID")); + } + } + + // now we know that the recipe user ID is not a primary user, so we can focus on it's one + // login method + assert (recipeUser.loginMethods.length == 1); + LoginMethod recipeUserIdLM = recipeUser.loginMethods[0]; + + Set tenantIds = recipeUser.tenantIds; + tenantIds.addAll(primaryUser.tenantIds); + + // we loop through the union of both the user's tenantIds and check that the criteria for + // linking accounts is not violated in any of them. We do a union and not an intersection + // cause if we did an intersection, and that yields that account linking is allowed, it could + // result in one tenant having two primary users with the same email. For example: + // - tenant1 has u1 with email e, and u2 with email e, primary user (one is ep, one is tp) + // - tenant2 has u3 with email e, primary user (passwordless) + // now if we want to link u3 with u1, we have to deny it cause if we don't, it will result in + // u1 and u2 to be primary users with the same email in the same tenant. If we do an + // intersection, we will get an empty set, but if we do a union, we will get both the tenants and + // do the checks in both. + for (String tenantId : tenantIds) { + TenantIdentifier tenantIdentifier = new TenantIdentifier( + appIdentifierWithStorage.getConnectionUriDomain(), appIdentifierWithStorage.getAppId(), + tenantId); + // we do not bother with getting the tenantIdentifierWithStorage 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. + + if (recipeUserIdLM.email != null) { + AuthRecipeUserInfo[] usersWithSameEmail = storage + .listPrimaryUsersByEmail_Transaction(tenantIdentifier, con, + recipeUserIdLM.email); + for (AuthRecipeUserInfo user : usersWithSameEmail) { + if (user.isPrimaryUser && !user.id.equals(primaryUser.id)) { + throw new StorageTransactionLogicException( + new AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException(user.id, + "This user's email is already associated with another user ID")); + } + } + } + + if (recipeUserIdLM.phoneNumber != null) { + AuthRecipeUserInfo[] usersWithSamePhoneNumber = storage + .listPrimaryUsersByPhoneNumber_Transaction(tenantIdentifier, con, + recipeUserIdLM.phoneNumber); + for (AuthRecipeUserInfo user : usersWithSamePhoneNumber) { + if (user.isPrimaryUser && !user.id.equals(primaryUser.id)) { + throw new StorageTransactionLogicException( + new AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException(user.id, + "This user's phone number is already associated with another user" + + " ID")); + } + } + } + + if (recipeUserIdLM.thirdParty != null) { + AuthRecipeUserInfo userWithSameThirdParty = storage + .getPrimaryUsersByThirdPartyInfo_Transaction(tenantIdentifier, con, + recipeUserIdLM.thirdParty.id, recipeUserIdLM.thirdParty.userId); + if (userWithSameThirdParty.isPrimaryUser && !userWithSameThirdParty.id.equals(primaryUser.id)) { + throw new StorageTransactionLogicException( + new AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException( + userWithSameThirdParty.id, + "This user's third party login is already associated with another" + + " user ID")); + } + } + } + + // now we can link accounts in the db. + storage.linkAccounts_Transaction(appIdentifierWithStorage, con, recipeUser.id, primaryUser.id); + + storage.commitTransaction(con); + + return false; + }); + + if (!wasAlreadyLinked) { + // finally, we revoke all sessions of the recipeUser Id cause their user ID has changed. + Session.revokeAllSessionsForUser(main, appIdentifierWithStorage, _recipeUserId); + } + + return wasAlreadyLinked; + } catch (StorageTransactionLogicException e) { + if (e.actualException instanceof UnknownUserIdException) { + throw (UnknownUserIdException) e.actualException; + } else if (e.actualException instanceof InputUserIdIsNotAPrimaryUserException) { + throw (InputUserIdIsNotAPrimaryUserException) e.actualException; + } else if (e.actualException instanceof RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException) { + throw (RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException) e.actualException; + } else if (e.actualException instanceof AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException) { + throw (AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException) e.actualException; + } + throw new StorageQueryException(e); + } + } + @TestOnly public static CreatePrimaryUserResult createPrimaryUser(Main main, String recipeUserId) @@ -193,7 +328,7 @@ public static CreatePrimaryUserResult createPrimaryUser(Main main, } else if (e.actualException instanceof AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException) { throw (AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException) e.actualException; } - throw new RuntimeException(e); + throw new StorageQueryException(e); } } diff --git a/src/main/java/io/supertokens/authRecipe/exception/InputUserIdIsNotAPrimaryUserException.java b/src/main/java/io/supertokens/authRecipe/exception/InputUserIdIsNotAPrimaryUserException.java new file mode 100644 index 000000000..a248e499f --- /dev/null +++ b/src/main/java/io/supertokens/authRecipe/exception/InputUserIdIsNotAPrimaryUserException.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2023, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.authRecipe.exception; + +public class InputUserIdIsNotAPrimaryUserException extends Exception { + public final String primaryUserId; + + public InputUserIdIsNotAPrimaryUserException(String primaryUserId) { + super(); + this.primaryUserId = primaryUserId; + } +} diff --git a/src/main/java/io/supertokens/authRecipe/exception/RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException.java b/src/main/java/io/supertokens/authRecipe/exception/RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException.java new file mode 100644 index 000000000..901f021d8 --- /dev/null +++ b/src/main/java/io/supertokens/authRecipe/exception/RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2023, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.authRecipe.exception; + +public class RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException extends Exception { + public final String primaryUserId; + + public RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException(String primaryUserId, String description) { + super(description); + this.primaryUserId = primaryUserId; + } +} diff --git a/src/main/java/io/supertokens/inmemorydb/Start.java b/src/main/java/io/supertokens/inmemorydb/Start.java index 1b9fd8f86..5facc1458 100644 --- a/src/main/java/io/supertokens/inmemorydb/Start.java +++ b/src/main/java/io/supertokens/inmemorydb/Start.java @@ -2831,9 +2831,15 @@ public void makePrimaryUser_Transaction(AppIdentifier appIdentifier, Transaction Connection sqlCon = (Connection) con.getConnection(); // we do not bother returning if a row was updated here or not, cause it's happening // in a transaction anyway. - GeneralQueries.makePrimaryUser_Transaction(this, sqlCon, userId); + GeneralQueries.makePrimaryUser_Transaction(this, sqlCon, appIdentifier, userId); } catch (SQLException e) { throw new StorageQueryException(e); } } + + @Override + public void linkAccounts_Transaction(AppIdentifier appIdentifier, TransactionConnection con, String recipeUserId, + String primaryUserId) throws StorageQueryException { + // TODO:... + } } diff --git a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java index 097fc15c0..7b0d05a9f 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java @@ -98,6 +98,16 @@ static String getQueryToCreatePrimaryUserIdIndex(Start start) { + "(app_id, primary_or_recipe_user_id);"; } + static String getQueryToCreateUserIdIndex(Start start) { + /* + * Used in: + * - making user a primary user. + * + * */ + return "CREATE INDEX all_auth_recipe_users_user_id_index ON " + Config.getConfig(start).getUsersTable() + + "(app_id, user_id);"; + } + static String getQueryToCreatePrimaryUserIdAndTenantIndex(Start start) { /* * Used in: @@ -189,6 +199,7 @@ public static void createTablesIfNotExists(Start start, Main main) throws SQLExc // index update(start, getQueryToCreatePrimaryUserIdIndex(start), NO_OP_SETTER); + update(start, getQueryToCreateUserIdIndex(start), NO_OP_SETTER); update(start, getQueryToCreateUserPaginationIndex(start), NO_OP_SETTER); update(start, getQueryToCreatePrimaryUserIdAndTenantIndex(start), NO_OP_SETTER); } @@ -865,13 +876,15 @@ public static AuthRecipeUserInfo[] getUsers(Start start, TenantIdentifier tenant return finalResult; } - public static void makePrimaryUser_Transaction(Start start, Connection sqlCon, String userId) + public static void makePrimaryUser_Transaction(Start start, Connection sqlCon, AppIdentifier appIdentifier, + String userId) throws SQLException, StorageQueryException { String QUERY = "UPDATE " + getConfig(start).getUsersTable() + - " SET is_linked_or_is_a_primary_user = true WHERE user_id = ?"; + " SET is_linked_or_is_a_primary_user = true WHERE app_id = ? AND user_id = ?"; update(sqlCon, QUERY, pst -> { - pst.setString(1, userId); + pst.setString(1, appIdentifier.getAppId()); + pst.setString(2, userId); }); } From d8cd8032786d195cd485f237164191137688cab6 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Wed, 26 Jul 2023 16:03:21 +0530 Subject: [PATCH 036/131] removes unneeded index --- CHANGELOG.md | 2 -- .../inmemorydb/queries/GeneralQueries.java | 12 +----------- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0b11ede6..e8ab1e1f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,6 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - Added new index `all_auth_recipe_users_primary_user_id_index`. - Added new index `all_auth_recipe_users_primary_user_id_and_tenant_id_index`. - Modified `all_auth_recipe_users_pagination_index` index to be on `primary_or_recipe_user_id` instead of `user_id` -- Added new index `all_auth_recipe_users_user_id_index` - Added a two new columns in `all_auth_recipe_users`: - `primary_or_recipe_user_id` (default value is equal to `user_id` column) - `is_linked_or_is_a_primary_user` (default value is false) @@ -39,7 +38,6 @@ CREATE INDEX all_auth_recipe_users_pagination_index ON all_auth_recipe_users (ti DESC, app_id DESC); CREATE INDEX all_auth_recipe_users_primary_user_id_index ON all_auth_recipe_users (app_id, primary_or_recipe_user_id); CREATE INDEX all_auth_recipe_users_primary_user_id_and_tenant_id_index ON all_auth_recipe_users (app_id, tenant_id, primary_or_recipe_user_id); -CREATE INDEX all_auth_recipe_users_user_id_index ON all_auth_recipe_users(app_id, user_id); ``` ## [6.0.6] - 2023-07-24 diff --git a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java index 7b0d05a9f..a8e3dfb00 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java @@ -92,22 +92,13 @@ static String getQueryToCreateUserPaginationIndex(Start start) { static String getQueryToCreatePrimaryUserIdIndex(Start start) { /* * Used in: + * - does user exist * * */ return "CREATE INDEX all_auth_recipe_users_primary_user_id_index ON " + Config.getConfig(start).getUsersTable() + "(app_id, primary_or_recipe_user_id);"; } - static String getQueryToCreateUserIdIndex(Start start) { - /* - * Used in: - * - making user a primary user. - * - * */ - return "CREATE INDEX all_auth_recipe_users_user_id_index ON " + Config.getConfig(start).getUsersTable() - + "(app_id, user_id);"; - } - static String getQueryToCreatePrimaryUserIdAndTenantIndex(Start start) { /* * Used in: @@ -199,7 +190,6 @@ public static void createTablesIfNotExists(Start start, Main main) throws SQLExc // index update(start, getQueryToCreatePrimaryUserIdIndex(start), NO_OP_SETTER); - update(start, getQueryToCreateUserIdIndex(start), NO_OP_SETTER); update(start, getQueryToCreateUserPaginationIndex(start), NO_OP_SETTER); update(start, getQueryToCreatePrimaryUserIdAndTenantIndex(start), NO_OP_SETTER); } From 73846cad848126faeea854cb04b74c7c09db040e Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Wed, 26 Jul 2023 16:08:36 +0530 Subject: [PATCH 037/131] adds test only func and also adds feature flag check --- .../io/supertokens/authRecipe/AuthRecipe.java | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/supertokens/authRecipe/AuthRecipe.java b/src/main/java/io/supertokens/authRecipe/AuthRecipe.java index 8bec97d51..c0dc507f9 100644 --- a/src/main/java/io/supertokens/authRecipe/AuthRecipe.java +++ b/src/main/java/io/supertokens/authRecipe/AuthRecipe.java @@ -79,11 +79,32 @@ public CreatePrimaryUserResult(AuthRecipeUserInfo user, boolean wasAlreadyAPrima } } + @TestOnly + public static boolean linkAccounts(Main main, String recipeUserId, String primaryUserId) + throws StorageQueryException, AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException, + UnknownUserIdException, + FeatureNotEnabledException, InputUserIdIsNotAPrimaryUserException, + RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException { + AppIdentifierWithStorage appId = new AppIdentifierWithStorage(null, null, + StorageLayer.getStorage(main)); + try { + return linkAccounts(main, appId, recipeUserId, primaryUserId); + } catch (TenantOrAppNotFoundException e) { + throw new RuntimeException(e); + } + } + public static boolean linkAccounts(Main main, AppIdentifierWithStorage appIdentifierWithStorage, String _recipeUserId, String _primaryUserId) throws StorageQueryException, AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException, RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException, InputUserIdIsNotAPrimaryUserException, - UnknownUserIdException { + UnknownUserIdException, TenantOrAppNotFoundException, FeatureNotEnabledException { + + if (Arrays.stream(FeatureFlag.getInstance(main, appIdentifierWithStorage).getEnabledFeatures()) + .noneMatch(t -> t == EE_FEATURES.ACCOUNT_LINKING)) { + throw new FeatureNotEnabledException( + "Account linking feature is not enabled for this app. Please contact support to enable it."); + } AuthRecipeSQLStorage storage = (AuthRecipeSQLStorage) appIdentifierWithStorage.getAuthRecipeStorage(); try { From ffed7e1e988c63d96cb61a726c3bf7dbd73cb7a2 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Wed, 26 Jul 2023 16:31:54 +0530 Subject: [PATCH 038/131] more changes --- .../accountlinking/CreatePrimaryUserTest.java | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/test/java/io/supertokens/test/accountlinking/CreatePrimaryUserTest.java b/src/test/java/io/supertokens/test/accountlinking/CreatePrimaryUserTest.java index 398911206..a31b93bc0 100644 --- a/src/test/java/io/supertokens/test/accountlinking/CreatePrimaryUserTest.java +++ b/src/test/java/io/supertokens/test/accountlinking/CreatePrimaryUserTest.java @@ -20,6 +20,7 @@ import io.supertokens.ProcessState; import io.supertokens.authRecipe.AuthRecipe; import io.supertokens.authRecipe.exception.AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException; +import io.supertokens.authRecipe.exception.RecipeUserIdAlreadyLinkedWithPrimaryUserIdException; import io.supertokens.emailpassword.EmailPassword; import io.supertokens.featureflag.EE_FEATURES; import io.supertokens.featureflag.FeatureFlagTestContent; @@ -473,4 +474,34 @@ public void makePrimaryUserFailsCauseOfUnknownUserId() throws Exception { process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); } + + @Test + public void makingPrimaryUserFailsCauseAlreadyLinkedToAnotherAccount() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + AuthRecipeUserInfo emailPasswordUser1 = EmailPassword.signUp(process.getProcess(), "test@example.com", + "pass1234"); + AuthRecipeUserInfo emailPasswordUser2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", + "pass1234"); + + AuthRecipe.createPrimaryUser(process.main, emailPasswordUser1.id); + AuthRecipe.linkAccounts(process.main, emailPasswordUser2.id, emailPasswordUser1.id); + + try { + AuthRecipe.createPrimaryUser(process.main, emailPasswordUser2.id); + assert (false); + } catch (RecipeUserIdAlreadyLinkedWithPrimaryUserIdException e) { + assert (e.primaryUserId.equals(emailPasswordUser1.id)); + assert (e.getMessage().equals("This user ID is already linked to another user ID")); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } } From a753685bc51c327eb365b74be8457c0bf07f6618 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Wed, 26 Jul 2023 19:40:23 +0530 Subject: [PATCH 039/131] adds one account linking test --- .../accountlinking/CreatePrimaryUserTest.java | 7 -- .../test/accountlinking/LinkAccountsTest.java | 104 ++++++++++++++++++ 2 files changed, 104 insertions(+), 7 deletions(-) create mode 100644 src/test/java/io/supertokens/test/accountlinking/LinkAccountsTest.java diff --git a/src/test/java/io/supertokens/test/accountlinking/CreatePrimaryUserTest.java b/src/test/java/io/supertokens/test/accountlinking/CreatePrimaryUserTest.java index a31b93bc0..d728792b3 100644 --- a/src/test/java/io/supertokens/test/accountlinking/CreatePrimaryUserTest.java +++ b/src/test/java/io/supertokens/test/accountlinking/CreatePrimaryUserTest.java @@ -44,13 +44,6 @@ import static org.junit.Assert.assertNotNull; -/* - * TODO: - * - locking test - make several requests for two users with the same email with making them primary and not primary - * and then check that the db state is always consistent. - * - locking test - make sure that deadlocks are resolved on their own. - * - check for making primary user across tenants logic. - * */ public class CreatePrimaryUserTest { @Rule diff --git a/src/test/java/io/supertokens/test/accountlinking/LinkAccountsTest.java b/src/test/java/io/supertokens/test/accountlinking/LinkAccountsTest.java new file mode 100644 index 000000000..288c954ee --- /dev/null +++ b/src/test/java/io/supertokens/test/accountlinking/LinkAccountsTest.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2023, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.test.accountlinking; + +import com.google.gson.JsonObject; +import io.supertokens.ProcessState; +import io.supertokens.authRecipe.AuthRecipe; +import io.supertokens.emailpassword.EmailPassword; +import io.supertokens.featureflag.EE_FEATURES; +import io.supertokens.featureflag.FeatureFlagTestContent; +import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; +import io.supertokens.session.Session; +import io.supertokens.storageLayer.StorageLayer; +import io.supertokens.test.TestingProcessManager; +import io.supertokens.test.Utils; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; + +import static org.junit.Assert.assertNotNull; + + +public class LinkAccountsTest { + @Rule + public TestRule watchman = Utils.getOnFailure(); + + @AfterClass + public static void afterTesting() { + Utils.afterTesting(); + } + + @Before + public void beforeEach() { + Utils.reset(); + } + + @Test + public void linkAccountSuccess() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "password"); + assert (!user.isPrimaryUser); + + Thread.sleep(1000); + + AuthRecipeUserInfo user2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "password"); + assert (!user2.isPrimaryUser); + + AuthRecipe.createPrimaryUser(process.main, user.id); + + Session.createNewSession(process.main, user2.id, new JsonObject(), new JsonObject()); + String[] sessions = Session.getAllNonExpiredSessionHandlesForUser(process.main, user2.id); + assert (sessions.length == 1); + + boolean wasAlreadyLinked = AuthRecipe.linkAccounts(process.main, user2.id, user.id); + assert (!wasAlreadyLinked); + + AuthRecipeUserInfo refetchUser2 = AuthRecipe.getUserById(process.main, user2.id); + AuthRecipeUserInfo refetchUser = AuthRecipe.getUserById(process.main, user.id); + assert (refetchUser2.equals(refetchUser)); + assert (refetchUser2.loginMethods.length == 2); + assert (refetchUser.loginMethods[0].equals(user.loginMethods[0])); + assert (refetchUser.loginMethods[1].equals(user2.loginMethods[0])); + assert (refetchUser.tenantIds.size() == 1); + assert (refetchUser.isPrimaryUser); + assert (refetchUser.id.equals(user.id)); + + // cause linkAccounts revokes sessions for the recipe user ID + sessions = Session.getAllNonExpiredSessionHandlesForUser(process.main, user2.id); + assert (sessions.length == 0); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + // TODO: require ee feature to be enabled.. +} From 7f16304ec69f138e399ca48b97fdc079f9b3e8f9 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Wed, 26 Jul 2023 19:56:03 +0530 Subject: [PATCH 040/131] adds more tests --- .../test/accountlinking/LinkAccountsTest.java | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/test/java/io/supertokens/test/accountlinking/LinkAccountsTest.java b/src/test/java/io/supertokens/test/accountlinking/LinkAccountsTest.java index 288c954ee..f839b3472 100644 --- a/src/test/java/io/supertokens/test/accountlinking/LinkAccountsTest.java +++ b/src/test/java/io/supertokens/test/accountlinking/LinkAccountsTest.java @@ -22,6 +22,7 @@ import io.supertokens.emailpassword.EmailPassword; import io.supertokens.featureflag.EE_FEATURES; import io.supertokens.featureflag.FeatureFlagTestContent; +import io.supertokens.featureflag.exceptions.FeatureNotEnabledException; import io.supertokens.pluginInterface.STORAGE_TYPE; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.session.Session; @@ -100,5 +101,22 @@ public void linkAccountSuccess() throws Exception { assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); } - // TODO: require ee feature to be enabled.. + @Test + public void testThatLinkingAccountsRequiresAccountLinkingFeatureToBeEnabled() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + try { + AuthRecipe.linkAccounts(process.main, "", ""); + assert (false); + } catch (FeatureNotEnabledException e) { + assert (e.getMessage() + .equals("Account linking feature is not enabled for this app. Please contact support to enable it" + + ".")); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } } From d1c7c758e53140342aeae3553d14b74c921c8e87 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Wed, 26 Jul 2023 20:26:36 +0530 Subject: [PATCH 041/131] more tests --- .../test/accountlinking/LinkAccountsTest.java | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/src/test/java/io/supertokens/test/accountlinking/LinkAccountsTest.java b/src/test/java/io/supertokens/test/accountlinking/LinkAccountsTest.java index f839b3472..b7ef5334b 100644 --- a/src/test/java/io/supertokens/test/accountlinking/LinkAccountsTest.java +++ b/src/test/java/io/supertokens/test/accountlinking/LinkAccountsTest.java @@ -29,6 +29,7 @@ import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.TestingProcessManager; import io.supertokens.test.Utils; +import io.supertokens.thirdparty.ThirdParty; import org.junit.AfterClass; import org.junit.Before; import org.junit.Rule; @@ -101,6 +102,58 @@ public void linkAccountSuccess() throws Exception { assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); } + @Test + public void linkAccountSuccessWithSameEmail() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "password"); + assert (!user.isPrimaryUser); + + Thread.sleep(1000); + + ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(process.getProcess(), "google", + "user-google", + "test@example.com"); + AuthRecipeUserInfo user2 = signInUpResponse.user; + assert (!user2.isPrimaryUser); + + AuthRecipe.createPrimaryUser(process.main, user.id); + + Session.createNewSession(process.main, user2.id, new JsonObject(), new JsonObject()); + String[] sessions = Session.getAllNonExpiredSessionHandlesForUser(process.main, user2.id); + assert (sessions.length == 1); + + boolean wasAlreadyLinked = AuthRecipe.linkAccounts(process.main, user2.id, user.id); + assert (!wasAlreadyLinked); + + AuthRecipeUserInfo refetchUser2 = AuthRecipe.getUserById(process.main, user2.id); + AuthRecipeUserInfo refetchUser = AuthRecipe.getUserById(process.main, user.id); + assert (refetchUser2.equals(refetchUser)); + assert (refetchUser2.loginMethods.length == 2); + assert (refetchUser.loginMethods[0].equals(user.loginMethods[0])); + assert (refetchUser.loginMethods[1].equals(user2.loginMethods[0])); + assert (refetchUser.tenantIds.size() == 1); + assert (refetchUser.isPrimaryUser); + assert (refetchUser.id.equals(user.id)); + + // cause linkAccounts revokes sessions for the recipe user ID + sessions = Session.getAllNonExpiredSessionHandlesForUser(process.main, user2.id); + assert (sessions.length == 0); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + @Test public void testThatLinkingAccountsRequiresAccountLinkingFeatureToBeEnabled() throws Exception { String[] args = {"../"}; From ab8206d687dc53edbf1a044ae6de803f2f0e6a1c Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Thu, 27 Jul 2023 11:23:09 +0530 Subject: [PATCH 042/131] more tests --- ...InputUserIdIsNotAPrimaryUserException.java | 6 +- .../test/accountlinking/LinkAccountsTest.java | 272 +++++++++++++++++- 2 files changed, 273 insertions(+), 5 deletions(-) diff --git a/src/main/java/io/supertokens/authRecipe/exception/InputUserIdIsNotAPrimaryUserException.java b/src/main/java/io/supertokens/authRecipe/exception/InputUserIdIsNotAPrimaryUserException.java index a248e499f..b0fdc333a 100644 --- a/src/main/java/io/supertokens/authRecipe/exception/InputUserIdIsNotAPrimaryUserException.java +++ b/src/main/java/io/supertokens/authRecipe/exception/InputUserIdIsNotAPrimaryUserException.java @@ -17,10 +17,10 @@ package io.supertokens.authRecipe.exception; public class InputUserIdIsNotAPrimaryUserException extends Exception { - public final String primaryUserId; + public final String userId; - public InputUserIdIsNotAPrimaryUserException(String primaryUserId) { + public InputUserIdIsNotAPrimaryUserException(String userId) { super(); - this.primaryUserId = primaryUserId; + this.userId = userId; } } diff --git a/src/test/java/io/supertokens/test/accountlinking/LinkAccountsTest.java b/src/test/java/io/supertokens/test/accountlinking/LinkAccountsTest.java index b7ef5334b..7de410add 100644 --- a/src/test/java/io/supertokens/test/accountlinking/LinkAccountsTest.java +++ b/src/test/java/io/supertokens/test/accountlinking/LinkAccountsTest.java @@ -19,12 +19,16 @@ import com.google.gson.JsonObject; import io.supertokens.ProcessState; import io.supertokens.authRecipe.AuthRecipe; +import io.supertokens.authRecipe.exception.AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException; +import io.supertokens.authRecipe.exception.InputUserIdIsNotAPrimaryUserException; +import io.supertokens.authRecipe.exception.RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException; import io.supertokens.emailpassword.EmailPassword; import io.supertokens.featureflag.EE_FEATURES; import io.supertokens.featureflag.FeatureFlagTestContent; import io.supertokens.featureflag.exceptions.FeatureNotEnabledException; import io.supertokens.pluginInterface.STORAGE_TYPE; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; +import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.session.Session; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.TestingProcessManager; @@ -70,7 +74,7 @@ public void linkAccountSuccess() throws Exception { AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "password"); assert (!user.isPrimaryUser); - Thread.sleep(1000); + Thread.sleep(50); AuthRecipeUserInfo user2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "password"); assert (!user2.isPrimaryUser); @@ -119,7 +123,7 @@ public void linkAccountSuccessWithSameEmail() throws Exception { AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "password"); assert (!user.isPrimaryUser); - Thread.sleep(1000); + Thread.sleep(50); ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(process.getProcess(), "google", "user-google", @@ -172,4 +176,268 @@ public void testThatLinkingAccountsRequiresAccountLinkingFeatureToBeEnabled() th process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); } + + @Test + public void linkAccountSuccessEvenIfUsingRecipeUserIdThatIsLinkedToPrimaryUser() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "password"); + assert (!user.isPrimaryUser); + + Thread.sleep(50); + + AuthRecipeUserInfo user2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "password"); + assert (!user2.isPrimaryUser); + + AuthRecipe.createPrimaryUser(process.main, user.id); + + boolean wasAlreadyLinked = AuthRecipe.linkAccounts(process.main, user2.id, user.id); + assert (!wasAlreadyLinked); + + AuthRecipeUserInfo user3 = EmailPassword.signUp(process.getProcess(), "test3@example.com", "password"); + assert (!user3.isPrimaryUser); + + wasAlreadyLinked = AuthRecipe.linkAccounts(process.main, user3.id, user2.id); + assert (!wasAlreadyLinked); + + AuthRecipeUserInfo refetchUser = AuthRecipe.getUserById(process.main, user.id); + assert (refetchUser.loginMethods.length == 3); + assert (refetchUser.loginMethods[0].equals(user.loginMethods[0])); + assert (refetchUser.loginMethods[1].equals(user2.loginMethods[0])); + assert (refetchUser.loginMethods[2].equals(user3.loginMethods[0])); + assert (refetchUser.tenantIds.size() == 1); + assert (refetchUser.isPrimaryUser); + assert (refetchUser.id.equals(user.id)); + + AuthRecipeUserInfo refetchUser3 = AuthRecipe.getUserById(process.main, user3.id); + assert (refetchUser3.equals(refetchUser)); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void alreadyLinkAccountLinkAgain() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "password"); + assert (!user.isPrimaryUser); + + Thread.sleep(50); + + ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(process.getProcess(), "google", + "user-google", + "test@example.com"); + AuthRecipeUserInfo user2 = signInUpResponse.user; + assert (!user2.isPrimaryUser); + + AuthRecipe.createPrimaryUser(process.main, user.id); + + boolean wasAlreadyLinked = AuthRecipe.linkAccounts(process.main, user2.id, user.id); + assert (!wasAlreadyLinked); + + Session.createNewSession(process.main, user2.id, new JsonObject(), new JsonObject()); + String[] sessions = Session.getAllNonExpiredSessionHandlesForUser(process.main, user2.id); + assert (sessions.length == 1); + + wasAlreadyLinked = AuthRecipe.linkAccounts(process.main, user2.id, user.id); + assert (wasAlreadyLinked); + + // cause linkAccounts revokes sessions for the recipe user ID + sessions = Session.getAllNonExpiredSessionHandlesForUser(process.main, user2.id); + assert (sessions.length == 1); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void linkAccountFailureCauseRecipeUserIdLinkedWithAnotherPrimaryUser() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "password"); + assert (!user.isPrimaryUser); + + Thread.sleep(50); + + ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(process.getProcess(), "google", + "user-google", + "test@example.com"); + AuthRecipeUserInfo user2 = signInUpResponse.user; + assert (!user2.isPrimaryUser); + + AuthRecipe.createPrimaryUser(process.main, user.id); + + boolean wasAlreadyLinked = AuthRecipe.linkAccounts(process.main, user2.id, user.id); + assert (!wasAlreadyLinked); + + AuthRecipeUserInfo user3 = EmailPassword.signUp(process.getProcess(), "test3@example.com", "password"); + assert (!user.isPrimaryUser); + AuthRecipe.createPrimaryUser(process.main, user3.id); + + try { + AuthRecipe.linkAccounts(process.main, user2.id, user3.id); + assert (false); + } catch (RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException e) { + assert (e.primaryUserId.equals(user.id)); + assert (e.getMessage().equals("The input recipe user ID is already linked to another user ID")); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void linkAccountFailureInputUserIsNotAPrimaryUser() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "password"); + assert (!user.isPrimaryUser); + + ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(process.getProcess(), "google", + "user-google", + "test@example.com"); + AuthRecipeUserInfo user2 = signInUpResponse.user; + assert (!user2.isPrimaryUser); + + try { + AuthRecipe.linkAccounts(process.main, user2.id, user.id); + assert (false); + } catch (InputUserIdIsNotAPrimaryUserException e) { + assert (e.userId.equals(user.id)); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void linkAccountFailureUserDoesNotExist() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "password"); + assert (!user.isPrimaryUser); + + AuthRecipe.createPrimaryUser(process.main, user.id); + + ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(process.getProcess(), "google", + "user-google", + "test@example.com"); + AuthRecipeUserInfo user2 = signInUpResponse.user; + assert (!user2.isPrimaryUser); + + try { + AuthRecipe.linkAccounts(process.main, user2.id, "random"); + assert (false); + } catch (UnknownUserIdException e) { + } + + try { + AuthRecipe.linkAccounts(process.main, "random2", user.id); + assert (false); + } catch (UnknownUserIdException e) { + } + + try { + AuthRecipe.linkAccounts(process.main, "random2", "random"); + assert (false); + } catch (UnknownUserIdException e) { + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void linkAccountFailureCauseAccountInfoAssociatedWithAPrimaryUser() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "password"); + assert (!user.isPrimaryUser); + AuthRecipe.createPrimaryUser(process.main, user.id); + + Thread.sleep(50); + + ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(process.getProcess(), "google", + "user-google", + "test@example.com"); + AuthRecipeUserInfo user2 = signInUpResponse.user; + assert (!user2.isPrimaryUser); + + AuthRecipeUserInfo otherPrimaryUser = EmailPassword.signUp(process.getProcess(), "test3@example.com", + "password"); + + AuthRecipe.createPrimaryUser(process.main, otherPrimaryUser.id); + + try { + AuthRecipe.linkAccounts(process.main, user2.id, otherPrimaryUser.id); + assert (false); + } catch (AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException e) { + assert (e.primaryUserId.equals(user.id)); + assert (e.getMessage().equals("This user's email is already associated with another user ID")); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } } From 772ebc1d6caa4f2983f6bac2098303e932ba6df6 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Thu, 27 Jul 2023 11:52:23 +0530 Subject: [PATCH 043/131] more tests --- .../io/supertokens/authRecipe/AuthRecipe.java | 5 +- .../test/accountlinking/LinkAccountsTest.java | 111 ++++++++++++++++++ 2 files changed, 114 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/supertokens/authRecipe/AuthRecipe.java b/src/main/java/io/supertokens/authRecipe/AuthRecipe.java index c0dc507f9..ef270b92e 100644 --- a/src/main/java/io/supertokens/authRecipe/AuthRecipe.java +++ b/src/main/java/io/supertokens/authRecipe/AuthRecipe.java @@ -194,7 +194,8 @@ public static boolean linkAccounts(Main main, AppIdentifierWithStorage appIdenti AuthRecipeUserInfo userWithSameThirdParty = storage .getPrimaryUsersByThirdPartyInfo_Transaction(tenantIdentifier, con, recipeUserIdLM.thirdParty.id, recipeUserIdLM.thirdParty.userId); - if (userWithSameThirdParty.isPrimaryUser && !userWithSameThirdParty.id.equals(primaryUser.id)) { + if (userWithSameThirdParty != null && userWithSameThirdParty.isPrimaryUser && + !userWithSameThirdParty.id.equals(primaryUser.id)) { throw new StorageTransactionLogicException( new AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException( userWithSameThirdParty.id, @@ -323,7 +324,7 @@ public static CreatePrimaryUserResult createPrimaryUser(Main main, AuthRecipeUserInfo userWithSameThirdParty = storage .getPrimaryUsersByThirdPartyInfo_Transaction(tenantIdentifier, con, loginMethod.thirdParty.id, loginMethod.thirdParty.userId); - if (userWithSameThirdParty.isPrimaryUser) { + if (userWithSameThirdParty != null && userWithSameThirdParty.isPrimaryUser) { throw new StorageTransactionLogicException( new AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException( userWithSameThirdParty.id, diff --git a/src/test/java/io/supertokens/test/accountlinking/LinkAccountsTest.java b/src/test/java/io/supertokens/test/accountlinking/LinkAccountsTest.java index 7de410add..fb811424c 100644 --- a/src/test/java/io/supertokens/test/accountlinking/LinkAccountsTest.java +++ b/src/test/java/io/supertokens/test/accountlinking/LinkAccountsTest.java @@ -26,9 +26,11 @@ import io.supertokens.featureflag.EE_FEATURES; import io.supertokens.featureflag.FeatureFlagTestContent; import io.supertokens.featureflag.exceptions.FeatureNotEnabledException; +import io.supertokens.multitenancy.Multitenancy; import io.supertokens.pluginInterface.STORAGE_TYPE; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; +import io.supertokens.pluginInterface.multitenancy.*; import io.supertokens.session.Session; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.TestingProcessManager; @@ -437,6 +439,115 @@ public void linkAccountFailureCauseAccountInfoAssociatedWithAPrimaryUser() throw assert (e.getMessage().equals("This user's email is already associated with another user ID")); } + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void linkAccountFailureCauseAccountInfoAssociatedWithAPrimaryUserEvenIfInDifferentTenant() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + Multitenancy.addNewOrUpdateAppOrTenant(process.main, new TenantIdentifier(null, null, null), + new TenantConfig(new TenantIdentifier(null, null, "t1"), new EmailPasswordConfig(true), + new ThirdPartyConfig(true, new ThirdPartyConfig.Provider[0]), new PasswordlessConfig(true), + new JsonObject())); + + TenantIdentifierWithStorage tenantIdentifierWithStorage = new TenantIdentifierWithStorage(null, null, "t1", + StorageLayer.getStorage(process.main)); + + AuthRecipeUserInfo user = EmailPassword.signUp(tenantIdentifierWithStorage, process.getProcess(), + "test@example.com", "password"); + assert (!user.isPrimaryUser); + AuthRecipe.createPrimaryUser(process.main, user.id); + + Thread.sleep(50); + + ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(tenantIdentifierWithStorage, + process.getProcess(), "google", + "user-google", + "test@example.com"); + AuthRecipeUserInfo user2 = signInUpResponse.user; + assert (!user2.isPrimaryUser); + + AuthRecipeUserInfo otherPrimaryUser = EmailPassword.signUp(process.getProcess(), "test3@example.com", + "password"); + + AuthRecipe.createPrimaryUser(process.main, otherPrimaryUser.id); + + try { + AuthRecipe.linkAccounts(process.main, user2.id, otherPrimaryUser.id); + assert (false); + } catch (AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException e) { + assert (e.primaryUserId.equals(user.id)); + assert (e.getMessage().equals("This user's email is already associated with another user ID")); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void linkAccountSuccessAcrossTenants() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + Multitenancy.addNewOrUpdateAppOrTenant(process.main, new TenantIdentifier(null, null, null), + new TenantConfig(new TenantIdentifier(null, null, "t1"), new EmailPasswordConfig(true), + new ThirdPartyConfig(true, new ThirdPartyConfig.Provider[0]), new PasswordlessConfig(true), + new JsonObject())); + + TenantIdentifierWithStorage tenantIdentifierWithStorage = new TenantIdentifierWithStorage(null, null, "t1", + StorageLayer.getStorage(process.main)); + + AuthRecipeUserInfo user = EmailPassword.signUp(tenantIdentifierWithStorage, process.getProcess(), + "test@example.com", "password"); + assert (!user.isPrimaryUser); + AuthRecipe.createPrimaryUser(process.main, user.id); + + Thread.sleep(50); + + ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp( + process.getProcess(), "google", + "user-google", + "test@example.com"); + AuthRecipeUserInfo user2 = signInUpResponse.user; + assert (!user2.isPrimaryUser); + + boolean wasAlreadyLinked = AuthRecipe.linkAccounts(process.main, user2.id, user.id); + assert (!wasAlreadyLinked); + + AuthRecipeUserInfo refetchedUser1 = AuthRecipe.getUserById(process.main, user.id); + AuthRecipeUserInfo refetchedUser2 = AuthRecipe.getUserById(process.main, user2.id); + assert (refetchedUser1.id.equals(refetchedUser2.id)); + assert refetchedUser1.loginMethods.length == 2; + assert refetchedUser1.tenantIds.size() == 2; + assert refetchedUser1.tenantIds.contains("t1"); + assert refetchedUser1.tenantIds.contains("public"); + assert refetchedUser1.id.equals(user.id); + assert refetchedUser1.isPrimaryUser; + assert refetchedUser1.loginMethods[0].recipeUserId.equals(user.loginMethods[0].recipeUserId); + assert refetchedUser1.loginMethods[1].recipeUserId.equals(user2.loginMethods[0].recipeUserId); + + process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); } From 3ef4c42fe48ecfd5af5ba8db10282d19a7f24651 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Thu, 27 Jul 2023 12:12:10 +0530 Subject: [PATCH 044/131] more tests --- .../inmemorydb/queries/GeneralQueries.java | 3 ++ .../test/accountlinking/LinkAccountsTest.java | 49 +++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java index a8e3dfb00..45ab3baaa 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java @@ -991,6 +991,9 @@ private static AuthRecipeUserInfo[] listPrimaryUsersByPhoneNumberHelper(Start st List result = getPrimaryUserInfoForUserIds(start, con, tenantIdentifier.toAppIdentifier(), userIds); + // this is going to order them based on oldest that joined to newest that joined. + result.sort(Comparator.comparingLong(o -> o.timeJoined)); + return result.toArray(new AuthRecipeUserInfo[0]); } diff --git a/src/test/java/io/supertokens/test/accountlinking/LinkAccountsTest.java b/src/test/java/io/supertokens/test/accountlinking/LinkAccountsTest.java index fb811424c..7052e123e 100644 --- a/src/test/java/io/supertokens/test/accountlinking/LinkAccountsTest.java +++ b/src/test/java/io/supertokens/test/accountlinking/LinkAccountsTest.java @@ -27,6 +27,7 @@ import io.supertokens.featureflag.FeatureFlagTestContent; import io.supertokens.featureflag.exceptions.FeatureNotEnabledException; import io.supertokens.multitenancy.Multitenancy; +import io.supertokens.passwordless.Passwordless; import io.supertokens.pluginInterface.STORAGE_TYPE; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; @@ -548,6 +549,54 @@ public void linkAccountSuccessAcrossTenants() throws Exception { assert refetchedUser1.loginMethods[1].recipeUserId.equals(user2.loginMethods[0].recipeUserId); + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void linkAccountSuccessWithPasswordlessEmailAndPhoneNumber() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "password"); + assert (!user.isPrimaryUser); + + Thread.sleep(50); + + Passwordless.CreateCodeResponse code = Passwordless.createCode(process.getProcess(), "u@e.com", null, null, + null); + Passwordless.ConsumeCodeResponse pResp = Passwordless.consumeCode(process.getProcess(), code.deviceId, + code.deviceIdHash, code.userInputCode, null); + AuthRecipeUserInfo user2 = pResp.user; + assert (!user2.isPrimaryUser); + + AuthRecipe.createPrimaryUser(process.main, user.id); + + Passwordless.updateUser(process.main, user2.id, null, new Passwordless.FieldUpdate("1234")); + user2 = AuthRecipe.getUserById(process.main, user2.id); + + boolean wasAlreadyLinked = AuthRecipe.linkAccounts(process.main, user2.id, user.id); + assert (!wasAlreadyLinked); + + AuthRecipeUserInfo refetchUser2 = AuthRecipe.getUserById(process.main, user2.id); + AuthRecipeUserInfo refetchUser = AuthRecipe.getUserById(process.main, user.id); + assert (refetchUser2.equals(refetchUser)); + assert (refetchUser2.loginMethods.length == 2); + assert (refetchUser.loginMethods[0].equals(user.loginMethods[0])); + assert (refetchUser.loginMethods[1].equals(user2.loginMethods[0])); + assert (refetchUser.tenantIds.size() == 1); + assert (refetchUser.isPrimaryUser); + assert (refetchUser.id.equals(user.id)); + process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); } From 9b7a593c8a4c9f0b8f9319392173d5440e86444b Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Thu, 27 Jul 2023 15:18:24 +0530 Subject: [PATCH 045/131] mods delete function --- .../io/supertokens/authRecipe/AuthRecipe.java | 98 +++++++++++++++++-- .../webserver/api/core/DeleteUserAPI.java | 7 ++ 2 files changed, 99 insertions(+), 6 deletions(-) diff --git a/src/main/java/io/supertokens/authRecipe/AuthRecipe.java b/src/main/java/io/supertokens/authRecipe/AuthRecipe.java index ef270b92e..da390bb61 100644 --- a/src/main/java/io/supertokens/authRecipe/AuthRecipe.java +++ b/src/main/java/io/supertokens/authRecipe/AuthRecipe.java @@ -40,6 +40,7 @@ import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; +import io.supertokens.pluginInterface.sqlStorage.TransactionConnection; import io.supertokens.pluginInterface.totp.sqlStorage.TOTPSQLStorage; import io.supertokens.pluginInterface.useridmapping.UserIdMapping; import io.supertokens.session.Session; @@ -497,9 +498,36 @@ public static UserPaginationContainer getUsers(Main main, } } + @TestOnly + public static void deleteUser(AppIdentifierWithStorage appIdentifierWithStorage, String userId, + UserIdMapping userIdMapping) + throws StorageQueryException, StorageTransactionLogicException { + deleteUser(appIdentifierWithStorage, userId, true, userIdMapping); + } + public static void deleteUser(AppIdentifierWithStorage appIdentifierWithStorage, String userId, + boolean removeAllLinkedAccounts, UserIdMapping userIdMapping) throws StorageQueryException, StorageTransactionLogicException { + AuthRecipeSQLStorage storage = (AuthRecipeSQLStorage) appIdentifierWithStorage.getAuthRecipeStorage(); + + storage.startTransaction(con -> { + deleteUserHelper(con, appIdentifierWithStorage, userId, removeAllLinkedAccounts, userIdMapping); + storage.commitTransaction(con); + return null; + }); + } + + private static void deleteUserHelper(TransactionConnection con, AppIdentifierWithStorage appIdentifierWithStorage, + String userId, + boolean removeAllLinkedAccounts, + UserIdMapping userIdMapping) + throws StorageQueryException { + AuthRecipeSQLStorage storage = (AuthRecipeSQLStorage) appIdentifierWithStorage.getAuthRecipeStorage(); + + String userIdToDeleteForNonAuthRecipeForRecipeUserId; + String userIdToDeleteForAuthRecipe; + // We clean up the user last so that if anything before that throws an error, then that will throw a // 500 to the // developer. In this case, they expect that the user has not been deleted (which will be true). This @@ -529,17 +557,75 @@ public static void deleteUser(AppIdentifierWithStorage appIdentifierWithStorage, .doesUserIdExist(appIdentifierWithStorage, userIdMapping.externalUserId)) { // db is in state A4 // delete only from auth tables - deleteAuthRecipeUser(appIdentifierWithStorage, userId); + userIdToDeleteForAuthRecipe = userId; + userIdToDeleteForNonAuthRecipeForRecipeUserId = null; } else { // db is in state A3 // delete user from non-auth tables with externalUserId - deleteNonAuthRecipeUser(appIdentifierWithStorage, userIdMapping.externalUserId); - // delete user from auth tables with superTokensUserId - deleteAuthRecipeUser(appIdentifierWithStorage, userIdMapping.superTokensUserId); + userIdToDeleteForAuthRecipe = userIdMapping.superTokensUserId; + userIdToDeleteForNonAuthRecipeForRecipeUserId = userIdMapping.externalUserId; } } else { - deleteNonAuthRecipeUser(appIdentifierWithStorage, userId); - deleteAuthRecipeUser(appIdentifierWithStorage, userId); + userIdToDeleteForAuthRecipe = userId; + userIdToDeleteForNonAuthRecipeForRecipeUserId = userId; + } + + assert (userIdToDeleteForAuthRecipe != null); + + // this user ID represents the non auth recipe stuff to delete for the primary user id + String primaryUserIdToDeleteNonAuthRecipe = null; + + AuthRecipeUserInfo userToDelete = storage.getPrimaryUserById_Transaction(appIdentifierWithStorage, con, + userIdToDeleteForAuthRecipe); + + if (userToDelete == null) { + return; + } + + if (removeAllLinkedAccounts || userToDelete.loginMethods.length == 1) { + if (userToDelete.id.equals(userIdToDeleteForAuthRecipe)) { + primaryUserIdToDeleteNonAuthRecipe = userIdToDeleteForNonAuthRecipeForRecipeUserId; + } else { + primaryUserIdToDeleteNonAuthRecipe = userToDelete.id; + // this is always type supertokens user ID cause it's from a user from the database. + io.supertokens.pluginInterface.useridmapping.UserIdMapping mappingResult = + io.supertokens.useridmapping.UserIdMapping.getUserIdMapping( + appIdentifierWithStorage, + userToDelete.id, UserIdType.SUPERTOKENS); + if (mappingResult != null) { + primaryUserIdToDeleteNonAuthRecipe = mappingResult.externalUserId; + } else { + primaryUserIdToDeleteNonAuthRecipe = userToDelete.id; + } + + } + } else { + if (userToDelete.id.equals(userIdToDeleteForAuthRecipe)) { + // this means we are deleting the primary user itself, but keeping other linked accounts + // so we keep the non auth recipe info of this user since other linked accounts can use it + userIdToDeleteForNonAuthRecipeForRecipeUserId = null; + } + } + + if (!removeAllLinkedAccounts) { + // TODO: remove userIdToDeleteForAuthRecipe + + if (userIdToDeleteForNonAuthRecipeForRecipeUserId != null) { + // TODO: delete non auth recipe for this user ID + } + + if (primaryUserIdToDeleteNonAuthRecipe != null) { + // TODO: delete non auth recipe for this user ID + } + } else { + for (LoginMethod lM : userToDelete.loginMethods) { + io.supertokens.pluginInterface.useridmapping.UserIdMapping mappingResult = lM.recipeUserId.equals( + userIdToDeleteForAuthRecipe) ? userIdMapping : + io.supertokens.useridmapping.UserIdMapping.getUserIdMapping( + appIdentifierWithStorage, + lM.recipeUserId, UserIdType.SUPERTOKENS); + deleteUserHelper(con, appIdentifierWithStorage, lM.recipeUserId, false, mappingResult); + } } } diff --git a/src/main/java/io/supertokens/webserver/api/core/DeleteUserAPI.java b/src/main/java/io/supertokens/webserver/api/core/DeleteUserAPI.java index 166b361da..ce51410f3 100644 --- a/src/main/java/io/supertokens/webserver/api/core/DeleteUserAPI.java +++ b/src/main/java/io/supertokens/webserver/api/core/DeleteUserAPI.java @@ -51,11 +51,18 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I // this API is app specific JsonObject input = InputParser.parseJsonObjectOrThrowError(req); String userId = InputParser.parseStringOrThrowError(input, "userId", false); + Boolean removeAllLinkedAccounts = InputParser.parseBooleanOrThrowError(input, "removeAllLinkedAccounts", true); + + if (removeAllLinkedAccounts == null) { + removeAllLinkedAccounts = true; + } + try { AppIdentifierWithStorageAndUserIdMapping appIdentifierWithStorageAndUserIdMapping = this.getAppIdentifierWithStorageAndUserIdMappingFromRequest(req, userId, UserIdType.ANY); AuthRecipe.deleteUser(appIdentifierWithStorageAndUserIdMapping.appIdentifierWithStorage, userId, + removeAllLinkedAccounts, appIdentifierWithStorageAndUserIdMapping.userIdMapping); } catch (StorageQueryException | TenantOrAppNotFoundException | StorageTransactionLogicException e) { throw new ServletException(e); From 26daade59f7a96d474adc5019a520502262d7a19 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Fri, 28 Jul 2023 18:05:01 +0530 Subject: [PATCH 046/131] small file change --- addDevTag | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/addDevTag b/addDevTag index 3c1688378..6efd047f2 100755 --- a/addDevTag +++ b/addDevTag @@ -1,4 +1,9 @@ #!/bin/bash +# check if we need to merge master into this branch------------ +if [[ $(git log origin/master ^HEAD) ]]; then + echo "You need to merge master into this branch. Exiting" + exit 1 +fi # get version------------ version=`cat build.gradle | grep -e "version =" -e "version="` From 5c29004337b2eb2ad7bd7763dfe2b51222ac6dfc Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Mon, 31 Jul 2023 14:06:02 +0530 Subject: [PATCH 047/131] delete function change --- .../io/supertokens/authRecipe/AuthRecipe.java | 73 +++--- .../java/io/supertokens/inmemorydb/Start.java | 97 ++++--- .../test/accountlinking/DeleteUserTest.java | 236 ++++++++++++++++++ 3 files changed, 326 insertions(+), 80 deletions(-) create mode 100644 src/test/java/io/supertokens/test/accountlinking/DeleteUserTest.java diff --git a/src/main/java/io/supertokens/authRecipe/AuthRecipe.java b/src/main/java/io/supertokens/authRecipe/AuthRecipe.java index da390bb61..c65c0570f 100644 --- a/src/main/java/io/supertokens/authRecipe/AuthRecipe.java +++ b/src/main/java/io/supertokens/authRecipe/AuthRecipe.java @@ -40,8 +40,8 @@ import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; +import io.supertokens.pluginInterface.session.sqlStorage.SessionSQLStorage; import io.supertokens.pluginInterface.sqlStorage.TransactionConnection; -import io.supertokens.pluginInterface.totp.sqlStorage.TOTPSQLStorage; import io.supertokens.pluginInterface.useridmapping.UserIdMapping; import io.supertokens.session.Session; import io.supertokens.storageLayer.StorageLayer; @@ -586,7 +586,6 @@ private static void deleteUserHelper(TransactionConnection con, AppIdentifierWit if (userToDelete.id.equals(userIdToDeleteForAuthRecipe)) { primaryUserIdToDeleteNonAuthRecipe = userIdToDeleteForNonAuthRecipeForRecipeUserId; } else { - primaryUserIdToDeleteNonAuthRecipe = userToDelete.id; // this is always type supertokens user ID cause it's from a user from the database. io.supertokens.pluginInterface.useridmapping.UserIdMapping mappingResult = io.supertokens.useridmapping.UserIdMapping.getUserIdMapping( @@ -608,14 +607,20 @@ private static void deleteUserHelper(TransactionConnection con, AppIdentifierWit } if (!removeAllLinkedAccounts) { - // TODO: remove userIdToDeleteForAuthRecipe + deleteAuthRecipeUser(con, appIdentifierWithStorage, userIdToDeleteForAuthRecipe, + !userIdToDeleteForAuthRecipe.equals(userToDelete.id)); if (userIdToDeleteForNonAuthRecipeForRecipeUserId != null) { - // TODO: delete non auth recipe for this user ID + deleteNonAuthRecipeUser(con, appIdentifierWithStorage, userIdToDeleteForNonAuthRecipeForRecipeUserId); } if (primaryUserIdToDeleteNonAuthRecipe != null) { - // TODO: delete non auth recipe for this user ID + deleteNonAuthRecipeUser(con, appIdentifierWithStorage, primaryUserIdToDeleteNonAuthRecipe); + + // this is only done to also delete the user ID mapping in case it exists, since we do not delete in the + // previous call to deleteAuthRecipeUser above. + deleteAuthRecipeUser(con, appIdentifierWithStorage, userIdToDeleteForAuthRecipe, + true); } } else { for (LoginMethod lM : userToDelete.loginMethods) { @@ -629,6 +634,18 @@ private static void deleteUserHelper(TransactionConnection con, AppIdentifierWit } } + @TestOnly + public static void deleteUser(Main main, String userId, boolean removeAllLinkedAccounts) + throws StorageQueryException, StorageTransactionLogicException { + Storage storage = StorageLayer.getStorage(main); + AppIdentifierWithStorage appIdentifier = new AppIdentifierWithStorage( + null, null, storage); + UserIdMapping mapping = io.supertokens.useridmapping.UserIdMapping.getUserIdMapping(appIdentifier, + userId, UserIdType.ANY); + + deleteUser(appIdentifier, userId, removeAllLinkedAccounts, mapping); + } + @TestOnly public static void deleteUser(Main main, String userId) throws StorageQueryException, StorageTransactionLogicException { @@ -651,26 +668,33 @@ public static void deleteUser(AppIdentifierWithStorage appIdentifierWithStorage, deleteUser(appIdentifierWithStorage, userId, mapping); } - private static void deleteNonAuthRecipeUser(AppIdentifierWithStorage - appIdentifierWithStorage, String userId) - throws StorageQueryException, StorageTransactionLogicException { + private static void deleteNonAuthRecipeUser(TransactionConnection con, AppIdentifierWithStorage + appIdentifierWithStorage, String userId) + throws StorageQueryException { appIdentifierWithStorage.getUserMetadataStorage() - .deleteUserMetadata(appIdentifierWithStorage, userId); - appIdentifierWithStorage.getSessionStorage() - .deleteSessionsOfUser(appIdentifierWithStorage, userId); + .deleteUserMetadata_Transaction(con, appIdentifierWithStorage, userId); + ((SessionSQLStorage) appIdentifierWithStorage.getSessionStorage()) + .deleteSessionsOfUser_Transaction(con, appIdentifierWithStorage, userId); appIdentifierWithStorage.getEmailVerificationStorage() - .deleteEmailVerificationUserInfo(appIdentifierWithStorage, userId); + .deleteEmailVerificationUserInfo_Transaction(con, appIdentifierWithStorage, userId); appIdentifierWithStorage.getUserRolesStorage() - .deleteAllRolesForUser(appIdentifierWithStorage, userId); + .deleteAllRolesForUser_Transaction(con, appIdentifierWithStorage, userId); appIdentifierWithStorage.getActiveUsersStorage() - .deleteUserActive(appIdentifierWithStorage, userId); + .deleteUserActive_Transaction(con, appIdentifierWithStorage, userId); + appIdentifierWithStorage.getTOTPStorage().removeUser_Transaction(con, appIdentifierWithStorage, userId); + } - TOTPSQLStorage storage = appIdentifierWithStorage.getTOTPStorage(); - storage.startTransaction(con -> { - storage.removeUser_Transaction(con, appIdentifierWithStorage, userId); - storage.commitTransaction(con); - return null; - }); + private static void deleteAuthRecipeUser(TransactionConnection con, + AppIdentifierWithStorage appIdentifierWithStorage, String + userId, boolean deleteUserIdMappingToo) + throws StorageQueryException { + // auth recipe deletions here only + appIdentifierWithStorage.getEmailPasswordStorage() + .deleteEmailPasswordUser_Transaction(con, appIdentifierWithStorage, userId, deleteUserIdMappingToo); + appIdentifierWithStorage.getThirdPartyStorage() + .deleteThirdPartyUser_Transaction(con, appIdentifierWithStorage, userId, deleteUserIdMappingToo); + appIdentifierWithStorage.getPasswordlessStorage() + .deletePasswordlessUser_Transaction(con, appIdentifierWithStorage, userId, deleteUserIdMappingToo); } public static boolean deleteNonAuthRecipeUser(TenantIdentifierWithStorage @@ -700,13 +724,4 @@ public static boolean deleteNonAuthRecipeUser(TenantIdentifierWithStorage return finalDidExist; } - - private static void deleteAuthRecipeUser(AppIdentifierWithStorage appIdentifierWithStorage, String - userId) - throws StorageQueryException { - // auth recipe deletions here only - appIdentifierWithStorage.getEmailPasswordStorage().deleteEmailPasswordUser(appIdentifierWithStorage, userId); - appIdentifierWithStorage.getThirdPartyStorage().deleteThirdPartyUser(appIdentifierWithStorage, userId); - appIdentifierWithStorage.getPasswordlessStorage().deletePasswordlessUser(appIdentifierWithStorage, userId); - } } diff --git a/src/main/java/io/supertokens/inmemorydb/Start.java b/src/main/java/io/supertokens/inmemorydb/Start.java index 5facc1458..77297ce63 100644 --- a/src/main/java/io/supertokens/inmemorydb/Start.java +++ b/src/main/java/io/supertokens/inmemorydb/Start.java @@ -531,6 +531,12 @@ public void updateSessionInfo_Transaction(TenantIdentifier tenantIdentifier, Tra } } + @Override + public void deleteSessionsOfUser_Transaction(TransactionConnection con, AppIdentifier appIdentifier, String userId) + throws StorageQueryException { + // TODO:.. + } + @Override public void setKeyValue_Transaction(TenantIdentifier tenantIdentifier, TransactionConnection con, String key, KeyValueInfo info) @@ -761,15 +767,6 @@ public UserInfo signUp(TenantIdentifier tenantIdentifier, String id, String emai } } - @Override - public void deleteEmailPasswordUser(AppIdentifier appIdentifier, String userId) throws StorageQueryException { - try { - EmailPasswordQueries.deleteUser(this, appIdentifier, userId); - } catch (StorageTransactionLogicException e) { - throw new StorageQueryException(e.actualException); - } - } - @Override public void addPasswordResetToken(AppIdentifier appIdentifier, PasswordResetTokenInfo passwordResetTokenInfo) throws StorageQueryException, UnknownUserIdException, DuplicatePasswordResetTokenException { @@ -882,6 +879,13 @@ public boolean lockEmailPasswordTableUsingId_Transaction(AppIdentifier appIdenti } } + @Override + public void deleteEmailPasswordUser_Transaction(TransactionConnection con, AppIdentifier appIdentifier, + String userId, boolean deleteUserIdMappingToo) + throws StorageQueryException { + // TODO.. + } + @Override public void deleteExpiredEmailVerificationTokens() throws StorageQueryException { try { @@ -955,13 +959,9 @@ public void updateIsEmailVerified_Transaction(AppIdentifier appIdentifier, Trans } @Override - public void deleteEmailVerificationUserInfo(AppIdentifier appIdentifier, String userId) - throws StorageQueryException { - try { - EmailVerificationQueries.deleteUserInfo(this, appIdentifier, userId); - } catch (StorageTransactionLogicException e) { - throw new StorageQueryException(e.actualException); - } + public void deleteEmailVerificationUserInfo_Transaction(TransactionConnection con, AppIdentifier appIdentifier, + String userId) throws StorageQueryException { + // TODO.. } @Override @@ -1093,6 +1093,13 @@ public void updateUserEmail_Transaction(AppIdentifier appIdentifier, Transaction } } + @Override + public void deleteThirdPartyUser_Transaction(TransactionConnection con, AppIdentifier appIdentifier, String userId, + boolean deleteUserIdMappingToo) + throws StorageQueryException { + // TODO.. + } + @Override public io.supertokens.pluginInterface.thirdparty.UserInfo signUp( TenantIdentifier tenantIdentifier, String id, String email, @@ -1140,15 +1147,6 @@ public io.supertokens.pluginInterface.thirdparty.UserInfo signUp( } } - @Override - public void deleteThirdPartyUser(AppIdentifier appIdentifier, String userId) throws StorageQueryException { - try { - ThirdPartyQueries.deleteUser(this, appIdentifier, userId); - } catch (StorageTransactionLogicException e) { - throw new StorageQueryException(e.actualException); - } - } - @Override public long getUsersCount(TenantIdentifier tenantIdentifier, RECIPE_ID[] includeRecipeIds) throws StorageQueryException { @@ -1230,12 +1228,9 @@ public int countUsersEnabledTotpAndActiveSince(AppIdentifier appIdentifier, long } @Override - public void deleteUserActive(AppIdentifier appIdentifier, String userId) throws StorageQueryException { - try { - ActiveUsersQueries.deleteUserActive(this, appIdentifier, userId); - } catch (SQLException e) { - throw new StorageQueryException(e); - } + public void deleteUserActive_Transaction(TransactionConnection con, AppIdentifier appIdentifier, String userId) + throws StorageQueryException { + // TODO:.. } @Override @@ -1560,6 +1555,13 @@ public void updateUserPhoneNumber_Transaction(AppIdentifier appIdentifier, Trans } } + @Override + public void deletePasswordlessUser_Transaction(TransactionConnection con, AppIdentifier appIdentifier, + String userId, boolean deleteUserIdMappingToo) + throws StorageQueryException { + // TODO.. + } + @Override public void createDeviceWithCode(TenantIdentifier tenantIdentifier, @Nullable String email, @Nullable String phoneNumber, @NotNull String linkCodeSalt, @@ -1694,16 +1696,6 @@ public io.supertokens.pluginInterface.passwordless.UserInfo createUser(TenantIde } } - @Override - public void deletePasswordlessUser(AppIdentifier appIdentifier, String userId) throws - StorageQueryException { - try { - PasswordlessQueries.deleteUser(this, appIdentifier, userId); - } catch (StorageTransactionLogicException e) { - throw new StorageQueryException(e.actualException); - } - } - @Override public PasswordlessDevice getDevice(TenantIdentifier tenantIdentifier, String deviceIdHash) throws StorageQueryException { @@ -1820,6 +1812,13 @@ public int setUserMetadata_Transaction(AppIdentifier appIdentifier, TransactionC } } + @Override + public int deleteUserMetadata_Transaction(TransactionConnection con, AppIdentifier appIdentifier, String userId) + throws StorageQueryException { + // TODO:.. + return 0; + } + @Override public int deleteUserMetadata(AppIdentifier appIdentifier, String userId) throws StorageQueryException { try { @@ -1950,16 +1949,6 @@ public int deleteAllRolesForUser(TenantIdentifier tenantIdentifier, String userI } } - @Override - public void deleteAllRolesForUser(AppIdentifier appIdentifier, String userId) throws - StorageQueryException { - try { - UserRoleQueries.deleteAllRolesForUser(this, appIdentifier, userId); - } catch (SQLException e) { - throw new StorageQueryException(e); - } - } - @Override public boolean deleteRoleForUser_Transaction(TenantIdentifier tenantIdentifier, TransactionConnection con, String userId, String role) @@ -2060,6 +2049,12 @@ public boolean doesRoleExist_Transaction(AppIdentifier appIdentifier, Transactio } } + @Override + public void deleteAllRolesForUser_Transaction(TransactionConnection con, AppIdentifier appIdentifier, String userId) + throws StorageQueryException { + // TODO.. + } + @Override public void createUserIdMapping(AppIdentifier appIdentifier, String superTokensUserId, String externalUserId, @org.jetbrains.annotations.Nullable String externalUserIdInfo) diff --git a/src/test/java/io/supertokens/test/accountlinking/DeleteUserTest.java b/src/test/java/io/supertokens/test/accountlinking/DeleteUserTest.java new file mode 100644 index 000000000..1fa5858ff --- /dev/null +++ b/src/test/java/io/supertokens/test/accountlinking/DeleteUserTest.java @@ -0,0 +1,236 @@ +/* + * Copyright (c) 2023, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.test.accountlinking; + +import com.google.gson.JsonObject; +import io.supertokens.ProcessState; +import io.supertokens.authRecipe.AuthRecipe; +import io.supertokens.emailpassword.EmailPassword; +import io.supertokens.featureflag.EE_FEATURES; +import io.supertokens.featureflag.FeatureFlagTestContent; +import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; +import io.supertokens.storageLayer.StorageLayer; +import io.supertokens.test.TestingProcessManager; +import io.supertokens.test.Utils; +import io.supertokens.useridmapping.UserIdMapping; +import io.supertokens.usermetadata.UserMetadata; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; + +import static org.junit.Assert.*; + + +public class DeleteUserTest { + @Rule + public TestRule watchman = Utils.getOnFailure(); + + @AfterClass + public static void afterTesting() { + Utils.afterTesting(); + } + + @Before + public void beforeEach() { + Utils.reset(); + } + + @Test + public void deleteUserTestWithUserIdMapping1() throws Exception { + /* + * recipe user r1 is mapped to e1 which has some metadata with e1 as the key. r1 gets linked to r2 which is + * mapped to e2 with some metadata associated with it. Now we want to delete r1. This should clear r1 entry, + * e1 entry, and e1 metadata, but should not clear e2 stuff at all. + * */ + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo r1 = EmailPassword.signUp(process.main, "test@example.com", "pass123"); + UserIdMapping.createUserIdMapping(process.main, r1.id, "e1", null, false); + JsonObject metadata = new JsonObject(); + metadata.addProperty("k1", "v1"); + UserMetadata.updateUserMetadata(process.main, "e1", metadata); + + AuthRecipeUserInfo r2 = EmailPassword.signUp(process.main, "test2@example.com", "pass123"); + UserIdMapping.createUserIdMapping(process.main, r2.id, "e2", null, false); + UserMetadata.updateUserMetadata(process.main, "e2", metadata); + + AuthRecipe.createPrimaryUser(process.main, r2.id); + + assert (!AuthRecipe.linkAccounts(process.main, r1.id, r2.id)); + + AuthRecipe.deleteUser(process.main, r1.id, false); + + assertNull(AuthRecipe.getUserById(process.main, r1.id)); + + assertNull(AuthRecipe.getUserById(process.main, "e2")); + + assertNotNull(AuthRecipe.getUserById(process.main, r2.id)); + + assertEquals(UserMetadata.getUserMetadata(process.main, "e1"), new JsonObject()); + assertEquals(UserMetadata.getUserMetadata(process.main, r1.id), new JsonObject()); + assertEquals(UserMetadata.getUserMetadata(process.main, "e2"), metadata); + assertEquals(UserMetadata.getUserMetadata(process.main, r2.id), new JsonObject()); + + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void deleteUserTestWithUserIdMapping2() throws Exception { + /* + * recipe user r1 exists. r1 gets linked to r2 which is mapped to e2 with some metadata associated with it. + * Now we want to delete r1 with linked all recipes as true. This should clear r1, r2 entry clear e2 metadata. + * */ + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo r1 = EmailPassword.signUp(process.main, "test@example.com", "pass123"); + UserIdMapping.createUserIdMapping(process.main, r1.id, "e1", null, false); + JsonObject metadata = new JsonObject(); + metadata.addProperty("k1", "v1"); + UserMetadata.updateUserMetadata(process.main, "e1", metadata); + + AuthRecipeUserInfo r2 = EmailPassword.signUp(process.main, "test2@example.com", "pass123"); + UserIdMapping.createUserIdMapping(process.main, r2.id, "e2", null, false); + UserMetadata.updateUserMetadata(process.main, "e2", metadata); + + AuthRecipe.createPrimaryUser(process.main, r2.id); + + assert (!AuthRecipe.linkAccounts(process.main, r1.id, r2.id)); + + AuthRecipe.deleteUser(process.main, r1.id); + + assertNull(AuthRecipe.getUserById(process.main, r1.id)); + + assertNull(AuthRecipe.getUserById(process.main, "e2")); + + assertNull(AuthRecipe.getUserById(process.main, r2.id)); + + assertEquals(UserMetadata.getUserMetadata(process.main, "e1"), new JsonObject()); + assertEquals(UserMetadata.getUserMetadata(process.main, r1.id), new JsonObject()); + assertEquals(UserMetadata.getUserMetadata(process.main, "e2"), new JsonObject()); + assertEquals(UserMetadata.getUserMetadata(process.main, r2.id), new JsonObject()); + + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void deleteUserTestWithUserIdMapping3() throws Exception { + /* + * three recipes are linked, r1, r2, r3 (primary user is r1). We have external user ID mapping for all three + * with some metadata. First we delete r1. This should not delete metadata and linked accounts. Then we + * delete r2 - this should delete metadata of r2 and r2. The we delete r3 - this should delete metadata of r3 + * and r1 + * */ + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo r1 = EmailPassword.signUp(process.main, "test@example.com", "pass123"); + UserIdMapping.createUserIdMapping(process.main, r1.id, "e1", null, false); + JsonObject metadata = new JsonObject(); + metadata.addProperty("k1", "v1"); + UserMetadata.updateUserMetadata(process.main, "e1", metadata); + + AuthRecipeUserInfo r2 = EmailPassword.signUp(process.main, "test2@example.com", "pass123"); + UserIdMapping.createUserIdMapping(process.main, r2.id, "e2", null, false); + UserMetadata.updateUserMetadata(process.main, "e2", metadata); + + AuthRecipeUserInfo r3 = EmailPassword.signUp(process.main, "test3@example.com", "pass123"); + UserIdMapping.createUserIdMapping(process.main, r3.id, "e3", null, false); + UserMetadata.updateUserMetadata(process.main, "e3", metadata); + + AuthRecipe.createPrimaryUser(process.main, r2.id); + + assert (!AuthRecipe.linkAccounts(process.main, r1.id, r2.id)); + assert (!AuthRecipe.linkAccounts(process.main, r3.id, r1.id)); + + AuthRecipe.deleteUser(process.main, r1.id, false); + + assertNull(AuthRecipe.getUserById(process.main, r1.id)); + + assertEquals(UserMetadata.getUserMetadata(process.main, "e1"), new JsonObject()); + assertEquals(UserMetadata.getUserMetadata(process.main, r1.id), new JsonObject()); + + { + AuthRecipeUserInfo userR2 = AuthRecipe.getUserById(process.main, r2.id); + AuthRecipeUserInfo userR3 = AuthRecipe.getUserById(process.main, r3.id); + assert (userR2.equals(userR3)); + assert (userR2.loginMethods.length == 2); + assertEquals(UserMetadata.getUserMetadata(process.main, "e2"), metadata); + assertEquals(UserMetadata.getUserMetadata(process.main, "e3"), metadata); + } + + AuthRecipe.deleteUser(process.main, r2.id, false); + + { + AuthRecipeUserInfo userR2 = AuthRecipe.getUserById(process.main, r2.id); + AuthRecipeUserInfo userR3 = AuthRecipe.getUserById(process.main, r3.id); + assert (userR2.equals(userR3)); + assert (userR2.loginMethods.length == 1); + assertEquals(UserMetadata.getUserMetadata(process.main, "e2"), metadata); + assertEquals(UserMetadata.getUserMetadata(process.main, "e3"), metadata); + } + + AuthRecipe.deleteUser(process.main, r3.id, false); + + { + AuthRecipeUserInfo userR2 = AuthRecipe.getUserById(process.main, r2.id); + AuthRecipeUserInfo userR3 = AuthRecipe.getUserById(process.main, r3.id); + assert (userR2 == null && userR3 == null); + assertEquals(UserMetadata.getUserMetadata(process.main, "e2"), new JsonObject()); + assertEquals(UserMetadata.getUserMetadata(process.main, "e3"), new JsonObject()); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + +} From 64e9ca203f901ac0c1c2fc94c9527bd4e4b916cb Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Mon, 31 Jul 2023 15:36:01 +0530 Subject: [PATCH 048/131] fixes a bug --- .../io/supertokens/authRecipe/AuthRecipe.java | 2 +- .../test/accountlinking/DeleteUserTest.java | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/supertokens/authRecipe/AuthRecipe.java b/src/main/java/io/supertokens/authRecipe/AuthRecipe.java index c65c0570f..6ebdf0b36 100644 --- a/src/main/java/io/supertokens/authRecipe/AuthRecipe.java +++ b/src/main/java/io/supertokens/authRecipe/AuthRecipe.java @@ -619,7 +619,7 @@ private static void deleteUserHelper(TransactionConnection con, AppIdentifierWit // this is only done to also delete the user ID mapping in case it exists, since we do not delete in the // previous call to deleteAuthRecipeUser above. - deleteAuthRecipeUser(con, appIdentifierWithStorage, userIdToDeleteForAuthRecipe, + deleteAuthRecipeUser(con, appIdentifierWithStorage, userToDelete.id, true); } } else { diff --git a/src/test/java/io/supertokens/test/accountlinking/DeleteUserTest.java b/src/test/java/io/supertokens/test/accountlinking/DeleteUserTest.java index 1fa5858ff..842ccdf19 100644 --- a/src/test/java/io/supertokens/test/accountlinking/DeleteUserTest.java +++ b/src/test/java/io/supertokens/test/accountlinking/DeleteUserTest.java @@ -28,6 +28,7 @@ import io.supertokens.test.TestingProcessManager; import io.supertokens.test.Utils; import io.supertokens.useridmapping.UserIdMapping; +import io.supertokens.useridmapping.UserIdType; import io.supertokens.usermetadata.UserMetadata; import org.junit.AfterClass; import org.junit.Before; @@ -97,6 +98,8 @@ public void deleteUserTestWithUserIdMapping1() throws Exception { assertEquals(UserMetadata.getUserMetadata(process.main, r1.id), new JsonObject()); assertEquals(UserMetadata.getUserMetadata(process.main, "e2"), metadata); assertEquals(UserMetadata.getUserMetadata(process.main, r2.id), new JsonObject()); + assert (UserIdMapping.getUserIdMapping(process.main, r2.id, UserIdType.SUPERTOKENS) != null); + assert (UserIdMapping.getUserIdMapping(process.main, r1.id, UserIdType.SUPERTOKENS) == null); process.kill(); @@ -147,6 +150,8 @@ public void deleteUserTestWithUserIdMapping2() throws Exception { assertEquals(UserMetadata.getUserMetadata(process.main, r1.id), new JsonObject()); assertEquals(UserMetadata.getUserMetadata(process.main, "e2"), new JsonObject()); assertEquals(UserMetadata.getUserMetadata(process.main, r2.id), new JsonObject()); + assert (UserIdMapping.getUserIdMapping(process.main, r2.id, UserIdType.SUPERTOKENS) == null); + assert (UserIdMapping.getUserIdMapping(process.main, r1.id, UserIdType.SUPERTOKENS) == null); process.kill(); @@ -206,6 +211,9 @@ public void deleteUserTestWithUserIdMapping3() throws Exception { assert (userR2.loginMethods.length == 2); assertEquals(UserMetadata.getUserMetadata(process.main, "e2"), metadata); assertEquals(UserMetadata.getUserMetadata(process.main, "e3"), metadata); + assert (UserIdMapping.getUserIdMapping(process.main, r2.id, UserIdType.SUPERTOKENS) != null); + assert (UserIdMapping.getUserIdMapping(process.main, r3.id, UserIdType.SUPERTOKENS) != null); + assert (UserIdMapping.getUserIdMapping(process.main, r1.id, UserIdType.SUPERTOKENS) == null); } AuthRecipe.deleteUser(process.main, r2.id, false); @@ -217,6 +225,9 @@ public void deleteUserTestWithUserIdMapping3() throws Exception { assert (userR2.loginMethods.length == 1); assertEquals(UserMetadata.getUserMetadata(process.main, "e2"), metadata); assertEquals(UserMetadata.getUserMetadata(process.main, "e3"), metadata); + assert (UserIdMapping.getUserIdMapping(process.main, r2.id, UserIdType.SUPERTOKENS) != null); + assert (UserIdMapping.getUserIdMapping(process.main, r3.id, UserIdType.SUPERTOKENS) != null); + assert (UserIdMapping.getUserIdMapping(process.main, r1.id, UserIdType.SUPERTOKENS) == null); } AuthRecipe.deleteUser(process.main, r3.id, false); @@ -227,8 +238,11 @@ public void deleteUserTestWithUserIdMapping3() throws Exception { assert (userR2 == null && userR3 == null); assertEquals(UserMetadata.getUserMetadata(process.main, "e2"), new JsonObject()); assertEquals(UserMetadata.getUserMetadata(process.main, "e3"), new JsonObject()); + assert (UserIdMapping.getUserIdMapping(process.main, r2.id, UserIdType.SUPERTOKENS) == null); + assert (UserIdMapping.getUserIdMapping(process.main, r3.id, UserIdType.SUPERTOKENS) == null); + assert (UserIdMapping.getUserIdMapping(process.main, r1.id, UserIdType.SUPERTOKENS) == null); } - + process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); } From 4aa16dc19a3a591cc3a163d58454b70e9217a051 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Mon, 31 Jul 2023 17:01:07 +0530 Subject: [PATCH 049/131] adds more tests --- .../test/accountlinking/DeleteUserTest.java | 144 ++++++++++++++++++ 1 file changed, 144 insertions(+) diff --git a/src/test/java/io/supertokens/test/accountlinking/DeleteUserTest.java b/src/test/java/io/supertokens/test/accountlinking/DeleteUserTest.java index 842ccdf19..ae67c3db7 100644 --- a/src/test/java/io/supertokens/test/accountlinking/DeleteUserTest.java +++ b/src/test/java/io/supertokens/test/accountlinking/DeleteUserTest.java @@ -53,6 +53,150 @@ public void beforeEach() { Utils.reset(); } + @Test + public void deleteLinkedUserWithoutRemovingAllUsers() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo r1 = EmailPassword.signUp(process.main, "test@example.com", "pass123"); + + AuthRecipeUserInfo r2 = EmailPassword.signUp(process.main, "test2@example.com", "pass123"); + + AuthRecipe.createPrimaryUser(process.main, r2.id); + + assert (!AuthRecipe.linkAccounts(process.main, r1.id, r2.id)); + + AuthRecipe.deleteUser(process.main, r1.id, false); + + assertNull(AuthRecipe.getUserById(process.main, r1.id)); + + AuthRecipeUserInfo user = AuthRecipe.getUserById(process.main, r2.id); + + assert (user.loginMethods.length == 1); + assert (user.isPrimaryUser); + assert (user.id.equals(r2.id)); + assert (user.loginMethods[0].recipeUserId.equals(r2.id)); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void deleteLinkedPrimaryUserWithoutRemovingAllUsers() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo r1 = EmailPassword.signUp(process.main, "test@example.com", "pass123"); + + AuthRecipeUserInfo r2 = EmailPassword.signUp(process.main, "test2@example.com", "pass123"); + + AuthRecipe.createPrimaryUser(process.main, r2.id); + + assert (!AuthRecipe.linkAccounts(process.main, r1.id, r2.id)); + + AuthRecipe.deleteUser(process.main, r2.id, false); + + AuthRecipeUserInfo userP = AuthRecipe.getUserById(process.main, r2.id); + + AuthRecipeUserInfo user = AuthRecipe.getUserById(process.main, r1.id); + + assert (user.loginMethods.length == 1); + assert (user.isPrimaryUser); + assert (user.id.equals(r2.id)); + assert (user.loginMethods[0].recipeUserId.equals(r1.id)); + assert (userP.equals(user)); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void deleteLinkedPrimaryUserRemovingAllUsers() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo r1 = EmailPassword.signUp(process.main, "test@example.com", "pass123"); + + AuthRecipeUserInfo r2 = EmailPassword.signUp(process.main, "test2@example.com", "pass123"); + + AuthRecipe.createPrimaryUser(process.main, r2.id); + + assert (!AuthRecipe.linkAccounts(process.main, r1.id, r2.id)); + + AuthRecipe.deleteUser(process.main, r2.id); + + AuthRecipeUserInfo userP = AuthRecipe.getUserById(process.main, r2.id); + + AuthRecipeUserInfo user = AuthRecipe.getUserById(process.main, r1.id); + + assert (user == null && userP == null); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void deleteLinkedPrimaryUserRemovingAllUsers2() throws Exception { + + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo r1 = EmailPassword.signUp(process.main, "test@example.com", "pass123"); + + AuthRecipeUserInfo r2 = EmailPassword.signUp(process.main, "test2@example.com", "pass123"); + + AuthRecipe.createPrimaryUser(process.main, r2.id); + + assert (!AuthRecipe.linkAccounts(process.main, r1.id, r2.id)); + + AuthRecipe.deleteUser(process.main, r1.id); + + AuthRecipeUserInfo userP = AuthRecipe.getUserById(process.main, r2.id); + + AuthRecipeUserInfo user = AuthRecipe.getUserById(process.main, r1.id); + + assert (user == null && userP == null); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + @Test public void deleteUserTestWithUserIdMapping1() throws Exception { /* From e770c2fa30aa7d05f8a8ed88102f7c50a4af921f Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Mon, 31 Jul 2023 18:28:31 +0530 Subject: [PATCH 050/131] adds unliking accounts function --- .../io/supertokens/authRecipe/AuthRecipe.java | 63 +++++ .../java/io/supertokens/inmemorydb/Start.java | 6 + .../accountlinking/UnlinkAccountsTest.java | 251 ++++++++++++++++++ 3 files changed, 320 insertions(+) create mode 100644 src/test/java/io/supertokens/test/accountlinking/UnlinkAccountsTest.java diff --git a/src/main/java/io/supertokens/authRecipe/AuthRecipe.java b/src/main/java/io/supertokens/authRecipe/AuthRecipe.java index 6ebdf0b36..5e762dbad 100644 --- a/src/main/java/io/supertokens/authRecipe/AuthRecipe.java +++ b/src/main/java/io/supertokens/authRecipe/AuthRecipe.java @@ -57,6 +57,69 @@ public class AuthRecipe { public static final int USER_PAGINATION_LIMIT = 500; + @TestOnly + public static boolean unlinkAccounts(Main main, String recipeUserId) + throws StorageQueryException, UnknownUserIdException, InputUserIdIsNotAPrimaryUserException { + AppIdentifierWithStorage appId = new AppIdentifierWithStorage(null, null, + StorageLayer.getStorage(main)); + return unlinkAccounts(main, appId, recipeUserId); + } + + + // returns true if the input user ID was deleted - which can happens if it was a primary user id and + // there were other accounts linked to it as well. + public static boolean unlinkAccounts(Main main, AppIdentifierWithStorage appIdentifierWithStorage, + String recipeUserId) + throws StorageQueryException, UnknownUserIdException, InputUserIdIsNotAPrimaryUserException { + AuthRecipeSQLStorage storage = (AuthRecipeSQLStorage) appIdentifierWithStorage.getAuthRecipeStorage(); + try { + return storage.startTransaction(con -> { + AuthRecipeUserInfo primaryUser = storage.getPrimaryUserById_Transaction(appIdentifierWithStorage, con, + recipeUserId); + if (primaryUser == null) { + throw new StorageTransactionLogicException(new UnknownUserIdException()); + } + + if (!primaryUser.isPrimaryUser) { + throw new StorageTransactionLogicException(new InputUserIdIsNotAPrimaryUserException(recipeUserId)); + } + + if (primaryUser.id.equals(recipeUserId)) { + // we are trying to unlink the user ID which is the same as the primary one. + if (primaryUser.loginMethods.length == 1) { + storage.unlinkAccounts_Transaction(appIdentifierWithStorage, con, recipeUserId); + Session.revokeAllSessionsForUser(main, appIdentifierWithStorage, recipeUserId); + return false; + } else { + // Here we delete the recipe user id cause if we just unlink, then there will be two + // distinct users with the same ID - which is a broken state. + // The delete will also cause the automatic unlinking. + io.supertokens.pluginInterface.useridmapping.UserIdMapping mappingResult = + io.supertokens.useridmapping.UserIdMapping.getUserIdMapping( + appIdentifierWithStorage, + recipeUserId, UserIdType.SUPERTOKENS); + // We need to make sure that it only deletes sessions for recipeUserId and not other linked + // users who have their sessions for primaryUserId (that is equal to the recipeUserId) + Session.revokeAllSessionsForUser(main, appIdentifierWithStorage, recipeUserId); + deleteUserHelper(con, appIdentifierWithStorage, recipeUserId, false, mappingResult); + return true; + } + } else { + storage.unlinkAccounts_Transaction(appIdentifierWithStorage, con, recipeUserId); + Session.revokeAllSessionsForUser(main, appIdentifierWithStorage, recipeUserId); + return false; + } + }); + } catch (StorageTransactionLogicException e) { + if (e.actualException instanceof UnknownUserIdException) { + throw (UnknownUserIdException) e.actualException; + } else if (e.actualException instanceof InputUserIdIsNotAPrimaryUserException) { + throw (InputUserIdIsNotAPrimaryUserException) e.actualException; + } + throw new RuntimeException(e); + } + } + @TestOnly public static AuthRecipeUserInfo getUserById(Main main, String userId) throws StorageQueryException { diff --git a/src/main/java/io/supertokens/inmemorydb/Start.java b/src/main/java/io/supertokens/inmemorydb/Start.java index 77297ce63..8b6b61d1d 100644 --- a/src/main/java/io/supertokens/inmemorydb/Start.java +++ b/src/main/java/io/supertokens/inmemorydb/Start.java @@ -2837,4 +2837,10 @@ public void linkAccounts_Transaction(AppIdentifier appIdentifier, TransactionCon String primaryUserId) throws StorageQueryException { // TODO:... } + + @Override + public void unlinkAccounts_Transaction(AppIdentifier appIdentifier, TransactionConnection con, String recipeUserId) + throws StorageQueryException { + // TODO:.. + } } diff --git a/src/test/java/io/supertokens/test/accountlinking/UnlinkAccountsTest.java b/src/test/java/io/supertokens/test/accountlinking/UnlinkAccountsTest.java new file mode 100644 index 000000000..163a8b7f7 --- /dev/null +++ b/src/test/java/io/supertokens/test/accountlinking/UnlinkAccountsTest.java @@ -0,0 +1,251 @@ +/* + * Copyright (c) 2023, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.test.accountlinking; + +import com.google.gson.JsonObject; +import io.supertokens.ProcessState; +import io.supertokens.authRecipe.AuthRecipe; +import io.supertokens.authRecipe.exception.InputUserIdIsNotAPrimaryUserException; +import io.supertokens.emailpassword.EmailPassword; +import io.supertokens.featureflag.EE_FEATURES; +import io.supertokens.featureflag.FeatureFlagTestContent; +import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; +import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; +import io.supertokens.session.Session; +import io.supertokens.storageLayer.StorageLayer; +import io.supertokens.test.TestingProcessManager; +import io.supertokens.test.Utils; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; + +import static org.junit.Assert.assertNotNull; + + +public class UnlinkAccountsTest { + @Rule + public TestRule watchman = Utils.getOnFailure(); + + @AfterClass + public static void afterTesting() { + Utils.afterTesting(); + } + + @Before + public void beforeEach() { + Utils.reset(); + } + + @Test + public void unlinkAccountSuccess() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "password"); + assert (!user.isPrimaryUser); + + Thread.sleep(50); + + AuthRecipeUserInfo user2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "password"); + assert (!user2.isPrimaryUser); + + AuthRecipe.createPrimaryUser(process.main, user.id); + + AuthRecipe.linkAccounts(process.main, user2.id, user.id); + + Session.createNewSession(process.main, user2.id, new JsonObject(), new JsonObject()); + String[] sessions = Session.getAllNonExpiredSessionHandlesForUser(process.main, user2.id); + assert (sessions.length == 1); + + boolean didDelete = AuthRecipe.unlinkAccounts(process.main, user2.id); + assert (!didDelete); + + AuthRecipeUserInfo refetchUser2 = AuthRecipe.getUserById(process.main, user2.id); + assert (!refetchUser2.isPrimaryUser); + assert (refetchUser2.id.equals(user2.id)); + assert (refetchUser2.loginMethods.length == 1); + assert (refetchUser2.loginMethods[0].recipeUserId.equals(user2.id)); + + AuthRecipeUserInfo refetchUser = AuthRecipe.getUserById(process.main, user.id); + assert (!refetchUser2.equals(refetchUser)); + assert (refetchUser.isPrimaryUser); + assert (refetchUser.loginMethods.length == 1); + assert (refetchUser.loginMethods[0].recipeUserId.equals(user.id)); + + // cause linkAccounts revokes sessions for the recipe user ID + sessions = Session.getAllNonExpiredSessionHandlesForUser(process.main, user2.id); + assert (sessions.length == 0); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void unlinkAccountWithoutPrimaryUserId() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "password"); + assert (!user.isPrimaryUser); + + try { + AuthRecipe.unlinkAccounts(process.main, user.id); + assert (false); + } catch (InputUserIdIsNotAPrimaryUserException e) { + assert (e.userId.equals(user.id)); + } + + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void unlinkAccountWithoutUnkownUserId() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + try { + AuthRecipe.unlinkAccounts(process.main, "random"); + assert (false); + } catch (UnknownUserIdException e) { + } + + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void unlinkAccountWithPrimaryUserBecomesRecipeUser() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "password"); + assert (!user.isPrimaryUser); + + AuthRecipe.createPrimaryUser(process.main, user.id); + + Session.createNewSession(process.main, user.id, new JsonObject(), new JsonObject()); + String[] sessions = Session.getAllNonExpiredSessionHandlesForUser(process.main, user.id); + assert (sessions.length == 1); + + boolean didDelete = AuthRecipe.unlinkAccounts(process.main, user.id); + assert (!didDelete); + + AuthRecipeUserInfo refetchUser = AuthRecipe.getUserById(process.main, user.id); + assert (!refetchUser.isPrimaryUser); + assert (refetchUser.loginMethods.length == 1); + assert (refetchUser.loginMethods[0].recipeUserId.equals(user.id)); + + // cause linkAccounts revokes sessions for the recipe user ID + sessions = Session.getAllNonExpiredSessionHandlesForUser(process.main, user.id); + assert (sessions.length == 0); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void unlinkAccountSuccessButDeletesUser() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "password"); + assert (!user.isPrimaryUser); + + Thread.sleep(50); + + AuthRecipeUserInfo user2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "password"); + assert (!user2.isPrimaryUser); + + AuthRecipe.createPrimaryUser(process.main, user.id); + + AuthRecipe.linkAccounts(process.main, user2.id, user.id); + + Session.createNewSession(process.main, user.id, new JsonObject(), new JsonObject()); + String[] sessions = Session.getAllNonExpiredSessionHandlesForUser(process.main, user.id); + assert (sessions.length == 1); + + boolean didDelete = AuthRecipe.unlinkAccounts(process.main, user.id); + assert (didDelete); + + AuthRecipeUserInfo refetchUser2 = AuthRecipe.getUserById(process.main, user2.id); + assert (refetchUser2.isPrimaryUser); + assert (refetchUser2.id.equals(user.id)); + assert (refetchUser2.loginMethods.length == 1); + assert (refetchUser2.loginMethods[0].recipeUserId.equals(user2.id)); + + AuthRecipeUserInfo refetchUser = AuthRecipe.getUserById(process.main, user.id); + assert (refetchUser2.equals(refetchUser)); + + // cause linkAccounts revokes sessions for the recipe user ID + sessions = Session.getAllNonExpiredSessionHandlesForUser(process.main, user.id); + assert (sessions.length == 0); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } +} From 4b9d85efb3844d7dbaab8676ffe1b394188aa071 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Mon, 31 Jul 2023 19:02:46 +0530 Subject: [PATCH 051/131] refactors for link accounts function --- .../io/supertokens/authRecipe/AuthRecipe.java | 267 +++++++++++------- 1 file changed, 172 insertions(+), 95 deletions(-) diff --git a/src/main/java/io/supertokens/authRecipe/AuthRecipe.java b/src/main/java/io/supertokens/authRecipe/AuthRecipe.java index 5e762dbad..8268c7fa4 100644 --- a/src/main/java/io/supertokens/authRecipe/AuthRecipe.java +++ b/src/main/java/io/supertokens/authRecipe/AuthRecipe.java @@ -143,6 +143,165 @@ public CreatePrimaryUserResult(AuthRecipeUserInfo user, boolean wasAlreadyAPrima } } + private static class CanLinkAccountsResult { + public String recipeUserId; + public String primaryUserId; + + public boolean alreadyLinked; + + public CanLinkAccountsResult(String recipeUserId, String primaryUserId, boolean alreadyLinked) { + this.recipeUserId = recipeUserId; + this.primaryUserId = primaryUserId; + this.alreadyLinked = alreadyLinked; + } + } + + @TestOnly + public static CanLinkAccountsResult canLinkAccounts(Main main, String recipeUserId, String primaryUserId) + throws StorageQueryException, UnknownUserIdException, InputUserIdIsNotAPrimaryUserException, + RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException, + AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException { + AppIdentifierWithStorage appId = new AppIdentifierWithStorage(null, null, + StorageLayer.getStorage(main)); + return canLinkAccounts(appId, recipeUserId, primaryUserId); + } + + public static CanLinkAccountsResult canLinkAccounts(AppIdentifierWithStorage appIdentifierWithStorage, + String recipeUserId, String primaryUserId) + throws StorageQueryException, UnknownUserIdException, InputUserIdIsNotAPrimaryUserException, + RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException, + AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException { + AuthRecipeSQLStorage storage = (AuthRecipeSQLStorage) appIdentifierWithStorage.getAuthRecipeStorage(); + try { + return storage.startTransaction(con -> { + try { + CanLinkAccountsResult result = canLinkAccountsHelper(con, appIdentifierWithStorage, + recipeUserId, primaryUserId); + + storage.commitTransaction(con); + + return result; + } catch (UnknownUserIdException | InputUserIdIsNotAPrimaryUserException | + RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException | + AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException e) { + throw new StorageTransactionLogicException(e); + } + }); + } catch (StorageTransactionLogicException e) { + if (e.actualException instanceof UnknownUserIdException) { + throw (UnknownUserIdException) e.actualException; + } else if (e.actualException instanceof InputUserIdIsNotAPrimaryUserException) { + throw (InputUserIdIsNotAPrimaryUserException) e.actualException; + } else if (e.actualException instanceof RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException) { + throw (RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException) e.actualException; + } else if (e.actualException instanceof AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException) { + throw (AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException) e.actualException; + } + throw new StorageQueryException(e); + } + } + + private static CanLinkAccountsResult canLinkAccountsHelper(TransactionConnection con, + AppIdentifierWithStorage appIdentifierWithStorage, + String _recipeUserId, String _primaryUserId) + throws StorageQueryException, UnknownUserIdException, InputUserIdIsNotAPrimaryUserException, + RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException, + AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException { + AuthRecipeSQLStorage storage = (AuthRecipeSQLStorage) appIdentifierWithStorage.getAuthRecipeStorage(); + AuthRecipeUserInfo primaryUser = storage.getPrimaryUserById_Transaction(appIdentifierWithStorage, con, + _primaryUserId); + + if (primaryUser == null) { + throw new UnknownUserIdException(); + } + + if (!primaryUser.isPrimaryUser) { + throw new InputUserIdIsNotAPrimaryUserException(primaryUser.id); + } + + AuthRecipeUserInfo recipeUser = storage.getPrimaryUserById_Transaction(appIdentifierWithStorage, con, + _recipeUserId); + if (recipeUser == null) { + throw new UnknownUserIdException(); + } + + if (recipeUser.isPrimaryUser) { + if (recipeUser.id.equals(primaryUser.id)) { + return new CanLinkAccountsResult(recipeUser.id, primaryUser.id, true); + } else { + throw new RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException(recipeUser.id, + "The input recipe user ID is already linked to another user ID"); + } + } + + // now we know that the recipe user ID is not a primary user, so we can focus on it's one + // login method + assert (recipeUser.loginMethods.length == 1); + LoginMethod recipeUserIdLM = recipeUser.loginMethods[0]; + + Set tenantIds = recipeUser.tenantIds; + tenantIds.addAll(primaryUser.tenantIds); + + // we loop through the union of both the user's tenantIds and check that the criteria for + // linking accounts is not violated in any of them. We do a union and not an intersection + // cause if we did an intersection, and that yields that account linking is allowed, it could + // result in one tenant having two primary users with the same email. For example: + // - tenant1 has u1 with email e, and u2 with email e, primary user (one is ep, one is tp) + // - tenant2 has u3 with email e, primary user (passwordless) + // now if we want to link u3 with u1, we have to deny it cause if we don't, it will result in + // u1 and u2 to be primary users with the same email in the same tenant. If we do an + // intersection, we will get an empty set, but if we do a union, we will get both the tenants and + // do the checks in both. + for (String tenantId : tenantIds) { + TenantIdentifier tenantIdentifier = new TenantIdentifier( + appIdentifierWithStorage.getConnectionUriDomain(), appIdentifierWithStorage.getAppId(), + tenantId); + // we do not bother with getting the tenantIdentifierWithStorage 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. + + if (recipeUserIdLM.email != null) { + AuthRecipeUserInfo[] usersWithSameEmail = storage + .listPrimaryUsersByEmail_Transaction(tenantIdentifier, con, + recipeUserIdLM.email); + for (AuthRecipeUserInfo user : usersWithSameEmail) { + if (user.isPrimaryUser && !user.id.equals(primaryUser.id)) { + throw new AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException(user.id, + "This user's email is already associated with another user ID"); + } + } + } + + if (recipeUserIdLM.phoneNumber != null) { + AuthRecipeUserInfo[] usersWithSamePhoneNumber = storage + .listPrimaryUsersByPhoneNumber_Transaction(tenantIdentifier, con, + recipeUserIdLM.phoneNumber); + for (AuthRecipeUserInfo user : usersWithSamePhoneNumber) { + if (user.isPrimaryUser && !user.id.equals(primaryUser.id)) { + throw new AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException(user.id, + "This user's phone number is already associated with another user" + + " ID"); + } + } + } + + if (recipeUserIdLM.thirdParty != null) { + AuthRecipeUserInfo userWithSameThirdParty = storage + .getPrimaryUsersByThirdPartyInfo_Transaction(tenantIdentifier, con, + recipeUserIdLM.thirdParty.id, recipeUserIdLM.thirdParty.userId); + if (userWithSameThirdParty != null && userWithSameThirdParty.isPrimaryUser && + !userWithSameThirdParty.id.equals(primaryUser.id)) { + throw new AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException( + userWithSameThirdParty.id, + "This user's third party login is already associated with another" + + " user ID"); + } + } + } + + return new CanLinkAccountsResult(recipeUser.id, primaryUser.id, false); + } + @TestOnly public static boolean linkAccounts(Main main, String recipeUserId, String primaryUserId) throws StorageQueryException, AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException, @@ -173,108 +332,26 @@ public static boolean linkAccounts(Main main, AppIdentifierWithStorage appIdenti AuthRecipeSQLStorage storage = (AuthRecipeSQLStorage) appIdentifierWithStorage.getAuthRecipeStorage(); try { boolean wasAlreadyLinked = storage.startTransaction(con -> { - AuthRecipeUserInfo primaryUser = storage.getPrimaryUserById_Transaction(appIdentifierWithStorage, con, - _primaryUserId); - - if (primaryUser == null) { - throw new StorageTransactionLogicException(new UnknownUserIdException()); - } - - if (!primaryUser.isPrimaryUser) { - throw new StorageTransactionLogicException( - new InputUserIdIsNotAPrimaryUserException(primaryUser.id)); - } - AuthRecipeUserInfo recipeUser = storage.getPrimaryUserById_Transaction(appIdentifierWithStorage, con, - _recipeUserId); - if (recipeUser == null) { - throw new StorageTransactionLogicException(new UnknownUserIdException()); - } + try { + CanLinkAccountsResult canLinkAccounts = canLinkAccountsHelper(con, appIdentifierWithStorage, + _recipeUserId, _primaryUserId); - if (recipeUser.isPrimaryUser) { - if (recipeUser.id.equals(primaryUser.id)) { + if (canLinkAccounts.alreadyLinked) { return true; - } else { - throw new StorageTransactionLogicException( - new RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException(recipeUser.id, - "The input recipe user ID is already linked to another user ID")); } - } + // now we can link accounts in the db. + storage.linkAccounts_Transaction(appIdentifierWithStorage, con, canLinkAccounts.recipeUserId, + canLinkAccounts.primaryUserId); - // now we know that the recipe user ID is not a primary user, so we can focus on it's one - // login method - assert (recipeUser.loginMethods.length == 1); - LoginMethod recipeUserIdLM = recipeUser.loginMethods[0]; - - Set tenantIds = recipeUser.tenantIds; - tenantIds.addAll(primaryUser.tenantIds); - - // we loop through the union of both the user's tenantIds and check that the criteria for - // linking accounts is not violated in any of them. We do a union and not an intersection - // cause if we did an intersection, and that yields that account linking is allowed, it could - // result in one tenant having two primary users with the same email. For example: - // - tenant1 has u1 with email e, and u2 with email e, primary user (one is ep, one is tp) - // - tenant2 has u3 with email e, primary user (passwordless) - // now if we want to link u3 with u1, we have to deny it cause if we don't, it will result in - // u1 and u2 to be primary users with the same email in the same tenant. If we do an - // intersection, we will get an empty set, but if we do a union, we will get both the tenants and - // do the checks in both. - for (String tenantId : tenantIds) { - TenantIdentifier tenantIdentifier = new TenantIdentifier( - appIdentifierWithStorage.getConnectionUriDomain(), appIdentifierWithStorage.getAppId(), - tenantId); - // we do not bother with getting the tenantIdentifierWithStorage 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. + storage.commitTransaction(con); - if (recipeUserIdLM.email != null) { - AuthRecipeUserInfo[] usersWithSameEmail = storage - .listPrimaryUsersByEmail_Transaction(tenantIdentifier, con, - recipeUserIdLM.email); - for (AuthRecipeUserInfo user : usersWithSameEmail) { - if (user.isPrimaryUser && !user.id.equals(primaryUser.id)) { - throw new StorageTransactionLogicException( - new AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException(user.id, - "This user's email is already associated with another user ID")); - } - } - } - - if (recipeUserIdLM.phoneNumber != null) { - AuthRecipeUserInfo[] usersWithSamePhoneNumber = storage - .listPrimaryUsersByPhoneNumber_Transaction(tenantIdentifier, con, - recipeUserIdLM.phoneNumber); - for (AuthRecipeUserInfo user : usersWithSamePhoneNumber) { - if (user.isPrimaryUser && !user.id.equals(primaryUser.id)) { - throw new StorageTransactionLogicException( - new AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException(user.id, - "This user's phone number is already associated with another user" + - " ID")); - } - } - } - - if (recipeUserIdLM.thirdParty != null) { - AuthRecipeUserInfo userWithSameThirdParty = storage - .getPrimaryUsersByThirdPartyInfo_Transaction(tenantIdentifier, con, - recipeUserIdLM.thirdParty.id, recipeUserIdLM.thirdParty.userId); - if (userWithSameThirdParty != null && userWithSameThirdParty.isPrimaryUser && - !userWithSameThirdParty.id.equals(primaryUser.id)) { - throw new StorageTransactionLogicException( - new AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException( - userWithSameThirdParty.id, - "This user's third party login is already associated with another" + - " user ID")); - } - } + return false; + } catch (UnknownUserIdException | InputUserIdIsNotAPrimaryUserException | + RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException | + AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException e) { + throw new StorageTransactionLogicException(e); } - - // now we can link accounts in the db. - storage.linkAccounts_Transaction(appIdentifierWithStorage, con, recipeUser.id, primaryUser.id); - - storage.commitTransaction(con); - - return false; }); if (!wasAlreadyLinked) { From eb01313fa46f4c43672240a95927df3db912a5b2 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Mon, 31 Jul 2023 19:16:19 +0530 Subject: [PATCH 052/131] more refactor --- .../io/supertokens/authRecipe/AuthRecipe.java | 197 +++++++++++------- 1 file changed, 126 insertions(+), 71 deletions(-) diff --git a/src/main/java/io/supertokens/authRecipe/AuthRecipe.java b/src/main/java/io/supertokens/authRecipe/AuthRecipe.java index 8268c7fa4..b6116f0a8 100644 --- a/src/main/java/io/supertokens/authRecipe/AuthRecipe.java +++ b/src/main/java/io/supertokens/authRecipe/AuthRecipe.java @@ -374,6 +374,120 @@ public static boolean linkAccounts(Main main, AppIdentifierWithStorage appIdenti } } + @TestOnly + public static CreatePrimaryUserResult canCreatePrimaryUser(Main main, + String recipeUserId) + throws StorageQueryException, AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException, + RecipeUserIdAlreadyLinkedWithPrimaryUserIdException, UnknownUserIdException { + AppIdentifierWithStorage appId = new AppIdentifierWithStorage(null, null, + StorageLayer.getStorage(main)); + return canCreatePrimaryUser(appId, recipeUserId); + } + + public static CreatePrimaryUserResult canCreatePrimaryUser(AppIdentifierWithStorage appIdentifierWithStorage, + String recipeUserId) + throws StorageQueryException, AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException, + RecipeUserIdAlreadyLinkedWithPrimaryUserIdException, UnknownUserIdException { + + AuthRecipeSQLStorage storage = (AuthRecipeSQLStorage) appIdentifierWithStorage.getAuthRecipeStorage(); + try { + return storage.startTransaction(con -> { + try { + return canCreatePrimaryUserHelper(con, appIdentifierWithStorage, + recipeUserId); + + } catch (UnknownUserIdException | RecipeUserIdAlreadyLinkedWithPrimaryUserIdException | + AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException e) { + throw new StorageTransactionLogicException(e); + } + }); + } catch (StorageTransactionLogicException e) { + if (e.actualException instanceof UnknownUserIdException) { + throw (UnknownUserIdException) e.actualException; + } else if (e.actualException instanceof RecipeUserIdAlreadyLinkedWithPrimaryUserIdException) { + throw (RecipeUserIdAlreadyLinkedWithPrimaryUserIdException) e.actualException; + } else if (e.actualException instanceof AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException) { + throw (AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException) e.actualException; + } + throw new StorageQueryException(e); + } + } + + private static CreatePrimaryUserResult canCreatePrimaryUserHelper(TransactionConnection con, + AppIdentifierWithStorage appIdentifierWithStorage, + String recipeUserId) + throws StorageQueryException, UnknownUserIdException, RecipeUserIdAlreadyLinkedWithPrimaryUserIdException, + AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException { + AuthRecipeSQLStorage storage = (AuthRecipeSQLStorage) appIdentifierWithStorage.getAuthRecipeStorage(); + + AuthRecipeUserInfo targetUser = storage.getPrimaryUserById_Transaction(appIdentifierWithStorage, con, + recipeUserId); + if (targetUser == null) { + throw new UnknownUserIdException(); + } + if (targetUser.isPrimaryUser) { + if (targetUser.id.equals(recipeUserId)) { + return new CreatePrimaryUserResult(targetUser, true); + } else { + throw new RecipeUserIdAlreadyLinkedWithPrimaryUserIdException(targetUser.id, + "This user ID is already linked to another user ID"); + } + } + + // this means that the user has only one login method since it's not a primary user + // nor is it linked to a primary user + assert (targetUser.loginMethods.length == 1); + LoginMethod loginMethod = targetUser.loginMethods[0]; + + for (String tenantId : targetUser.tenantIds) { + TenantIdentifier tenantIdentifier = new TenantIdentifier( + appIdentifierWithStorage.getConnectionUriDomain(), appIdentifierWithStorage.getAppId(), + tenantId); + // we do not bother with getting the tenantIdentifierWithStorage 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. + + if (loginMethod.email != null) { + AuthRecipeUserInfo[] usersWithSameEmail = storage + .listPrimaryUsersByEmail_Transaction(tenantIdentifier, con, + loginMethod.email); + for (AuthRecipeUserInfo user : usersWithSameEmail) { + if (user.isPrimaryUser) { + throw new AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException(user.id, + "This user's email is already associated with another user ID"); + } + } + } + + if (loginMethod.phoneNumber != null) { + AuthRecipeUserInfo[] usersWithSamePhoneNumber = storage + .listPrimaryUsersByPhoneNumber_Transaction(tenantIdentifier, con, + loginMethod.phoneNumber); + for (AuthRecipeUserInfo user : usersWithSamePhoneNumber) { + if (user.isPrimaryUser) { + throw new AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException(user.id, + "This user's phone number is already associated with another user" + + " ID"); + } + } + } + + if (loginMethod.thirdParty != null) { + AuthRecipeUserInfo userWithSameThirdParty = storage + .getPrimaryUsersByThirdPartyInfo_Transaction(tenantIdentifier, con, + loginMethod.thirdParty.id, loginMethod.thirdParty.userId); + if (userWithSameThirdParty != null && userWithSameThirdParty.isPrimaryUser) { + throw new AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException( + userWithSameThirdParty.id, + "This user's third party login is already associated with another" + + " user ID"); + } + } + } + + return new CreatePrimaryUserResult(targetUser, false); + } + @TestOnly public static CreatePrimaryUserResult createPrimaryUser(Main main, String recipeUserId) @@ -406,82 +520,23 @@ public static CreatePrimaryUserResult createPrimaryUser(Main main, try { return storage.startTransaction(con -> { - AuthRecipeUserInfo targetUser = storage.getPrimaryUserById_Transaction(appIdentifierWithStorage, con, - recipeUserId); - if (targetUser == null) { - throw new StorageTransactionLogicException(new UnknownUserIdException()); - } - if (targetUser.isPrimaryUser) { - if (targetUser.id.equals(recipeUserId)) { - return new CreatePrimaryUserResult(targetUser, true); - } else { - throw new StorageTransactionLogicException( - new RecipeUserIdAlreadyLinkedWithPrimaryUserIdException(targetUser.id, - "This user ID is already linked to another user ID")); + try { + CreatePrimaryUserResult result = canCreatePrimaryUserHelper(con, appIdentifierWithStorage, + recipeUserId); + if (result.wasAlreadyAPrimaryUser) { + return result; } - } + storage.makePrimaryUser_Transaction(appIdentifierWithStorage, con, result.user.id); - // this means that the user has only one login method since it's not a primary user - // nor is it linked to a primary user - assert (targetUser.loginMethods.length == 1); - LoginMethod loginMethod = targetUser.loginMethods[0]; - - for (String tenantId : targetUser.tenantIds) { - TenantIdentifier tenantIdentifier = new TenantIdentifier( - appIdentifierWithStorage.getConnectionUriDomain(), appIdentifierWithStorage.getAppId(), - tenantId); - // we do not bother with getting the tenantIdentifierWithStorage 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. - - if (loginMethod.email != null) { - AuthRecipeUserInfo[] usersWithSameEmail = storage - .listPrimaryUsersByEmail_Transaction(tenantIdentifier, con, - loginMethod.email); - for (AuthRecipeUserInfo user : usersWithSameEmail) { - if (user.isPrimaryUser) { - throw new StorageTransactionLogicException( - new AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException(user.id, - "This user's email is already associated with another user ID")); - } - } - } + storage.commitTransaction(con); - if (loginMethod.phoneNumber != null) { - AuthRecipeUserInfo[] usersWithSamePhoneNumber = storage - .listPrimaryUsersByPhoneNumber_Transaction(tenantIdentifier, con, - loginMethod.phoneNumber); - for (AuthRecipeUserInfo user : usersWithSamePhoneNumber) { - if (user.isPrimaryUser) { - throw new StorageTransactionLogicException( - new AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException(user.id, - "This user's phone number is already associated with another user" + - " ID")); - } - } - } + result.user.isPrimaryUser = true; - if (loginMethod.thirdParty != null) { - AuthRecipeUserInfo userWithSameThirdParty = storage - .getPrimaryUsersByThirdPartyInfo_Transaction(tenantIdentifier, con, - loginMethod.thirdParty.id, loginMethod.thirdParty.userId); - if (userWithSameThirdParty != null && userWithSameThirdParty.isPrimaryUser) { - throw new StorageTransactionLogicException( - new AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException( - userWithSameThirdParty.id, - "This user's third party login is already associated with another" + - " user ID")); - } - } + return result; + } catch (UnknownUserIdException | RecipeUserIdAlreadyLinkedWithPrimaryUserIdException | + AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException e) { + throw new StorageTransactionLogicException(e); } - - storage.makePrimaryUser_Transaction(appIdentifierWithStorage, con, targetUser.id); - - storage.commitTransaction(con); - - targetUser.isPrimaryUser = true; - - return new CreatePrimaryUserResult(targetUser, false); }); } catch (StorageTransactionLogicException e) { if (e.actualException instanceof UnknownUserIdException) { From 0816462afe2bc9da0db0faedfa879c2ec44da73b Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Tue, 1 Aug 2023 13:08:41 +0530 Subject: [PATCH 053/131] adds API for can create primary user --- .../io/supertokens/webserver/Webserver.java | 3 + .../supertokens/webserver/WebserverAPI.java | 3 +- .../CanCreatePrimaryUserAPI.java | 89 +++++++ .../api/CanCreatePrimaryUserAPITest.java | 230 ++++++++++++++++++ 4 files changed, 324 insertions(+), 1 deletion(-) create mode 100644 src/main/java/io/supertokens/webserver/api/accountlinking/CanCreatePrimaryUserAPI.java create mode 100644 src/test/java/io/supertokens/test/accountlinking/api/CanCreatePrimaryUserAPITest.java diff --git a/src/main/java/io/supertokens/webserver/Webserver.java b/src/main/java/io/supertokens/webserver/Webserver.java index 5704a4d10..5ee007817 100644 --- a/src/main/java/io/supertokens/webserver/Webserver.java +++ b/src/main/java/io/supertokens/webserver/Webserver.java @@ -26,6 +26,7 @@ import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; +import io.supertokens.webserver.api.accountlinking.CanCreatePrimaryUserAPI; import io.supertokens.webserver.api.core.*; import io.supertokens.webserver.api.dashboard.*; import io.supertokens.webserver.api.emailpassword.UserAPI; @@ -249,6 +250,8 @@ private void setupRoutes() { addAPI(new GetUserByIdAPI(main)); addAPI(new ListUsersByAccountInfoAPI(main)); + addAPI(new CanCreatePrimaryUserAPI(main)); + StandardContext context = tomcatReference.getContext(); Tomcat tomcat = tomcatReference.getTomcat(); diff --git a/src/main/java/io/supertokens/webserver/WebserverAPI.java b/src/main/java/io/supertokens/webserver/WebserverAPI.java index ec9c876ce..fb20f98fe 100644 --- a/src/main/java/io/supertokens/webserver/WebserverAPI.java +++ b/src/main/java/io/supertokens/webserver/WebserverAPI.java @@ -75,10 +75,11 @@ public abstract class WebserverAPI extends HttpServlet { supportedVersions.add(SemVer.v2_20); supportedVersions.add(SemVer.v2_21); supportedVersions.add(SemVer.v3_0); + supportedVersions.add(SemVer.v4_0); } public static SemVer getLatestCDIVersion() { - return SemVer.v3_0; + return SemVer.v4_0; } public SemVer getLatestCDIVersionForRequest(HttpServletRequest req) diff --git a/src/main/java/io/supertokens/webserver/api/accountlinking/CanCreatePrimaryUserAPI.java b/src/main/java/io/supertokens/webserver/api/accountlinking/CanCreatePrimaryUserAPI.java new file mode 100644 index 000000000..037555dd2 --- /dev/null +++ b/src/main/java/io/supertokens/webserver/api/accountlinking/CanCreatePrimaryUserAPI.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2023, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.webserver.api.accountlinking; + +import com.google.gson.JsonObject; +import io.supertokens.AppIdentifierWithStorageAndUserIdMapping; +import io.supertokens.Main; +import io.supertokens.authRecipe.AuthRecipe; +import io.supertokens.authRecipe.exception.AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException; +import io.supertokens.authRecipe.exception.RecipeUserIdAlreadyLinkedWithPrimaryUserIdException; +import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; +import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; +import io.supertokens.useridmapping.UserIdType; +import io.supertokens.webserver.InputParser; +import io.supertokens.webserver.WebserverAPI; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import java.io.IOException; + +public class CanCreatePrimaryUserAPI extends WebserverAPI { + + public CanCreatePrimaryUserAPI(Main main) { + super(main, ""); + } + + @Override + public String getPath() { + return "/recipe/accountlinking/user/primary/check"; + } + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { + // API is app specific + String inputRecipeUserId = InputParser.getQueryParamOrThrowError(req, "recipeUserId", false); + + try { + String userId = inputRecipeUserId; + AppIdentifierWithStorage appIdentifierWithStorage; + AppIdentifierWithStorageAndUserIdMapping mappingAndStorage = + getAppIdentifierWithStorageAndUserIdMappingFromRequest( + req, inputRecipeUserId, UserIdType.ANY); + if (mappingAndStorage.userIdMapping != null) { + userId = mappingAndStorage.userIdMapping.superTokensUserId; + } + appIdentifierWithStorage = mappingAndStorage.appIdentifierWithStorage; + + AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.canCreatePrimaryUser(appIdentifierWithStorage, + userId); + JsonObject response = new JsonObject(); + response.addProperty("status", "OK"); + response.addProperty("wasAlreadyAPrimaryUser", result.wasAlreadyAPrimaryUser); + super.sendJsonResponse(200, response, resp); + } catch (StorageQueryException | TenantOrAppNotFoundException e) { + throw new ServletException(e); + } catch (UnknownUserIdException e) { + throw new ServletException(new BadRequestException("Unknown user ID provided")); + } catch (AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException e) { + JsonObject response = new JsonObject(); + response.addProperty("status", "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"); + response.addProperty("primaryUserId", e.primaryUserId); + response.addProperty("description", e.getMessage()); + super.sendJsonResponse(200, response, resp); + } catch (RecipeUserIdAlreadyLinkedWithPrimaryUserIdException e) { + JsonObject response = new JsonObject(); + response.addProperty("status", "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR"); + response.addProperty("primaryUserId", e.primaryUserId); + response.addProperty("description", e.getMessage()); + super.sendJsonResponse(200, response, resp); + } + } +} diff --git a/src/test/java/io/supertokens/test/accountlinking/api/CanCreatePrimaryUserAPITest.java b/src/test/java/io/supertokens/test/accountlinking/api/CanCreatePrimaryUserAPITest.java new file mode 100644 index 000000000..eeda5e0c2 --- /dev/null +++ b/src/test/java/io/supertokens/test/accountlinking/api/CanCreatePrimaryUserAPITest.java @@ -0,0 +1,230 @@ +/* + * Copyright (c) 2023, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.test.accountlinking.api; + +import com.google.gson.JsonObject; +import io.supertokens.ProcessState; +import io.supertokens.authRecipe.AuthRecipe; +import io.supertokens.emailpassword.EmailPassword; +import io.supertokens.featureflag.EE_FEATURES; +import io.supertokens.featureflag.FeatureFlagTestContent; +import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; +import io.supertokens.storageLayer.StorageLayer; +import io.supertokens.test.TestingProcessManager; +import io.supertokens.test.Utils; +import io.supertokens.test.httpRequest.HttpRequestForTesting; +import io.supertokens.test.httpRequest.HttpResponseException; +import io.supertokens.thirdparty.ThirdParty; +import io.supertokens.webserver.WebserverAPI; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.*; + +public class CanCreatePrimaryUserAPITest { + @Rule + public TestRule watchman = Utils.getOnFailure(); + + @AfterClass + public static void afterTesting() { + Utils.afterTesting(); + } + + @Before + public void beforeEach() { + Utils.reset(); + } + + @Test + public void canCreateReturnsTrue() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "abcd1234"); + + { + Map params = new HashMap<>(); + params.put("recipeUserId", user.id); + + JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/primary/check", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assertEquals(2, response.entrySet().size()); + assertEquals("OK", response.get("status").getAsString()); + assertFalse(response.get("wasAlreadyAPrimaryUser").getAsBoolean()); + } + + AuthRecipe.createPrimaryUser(process.main, user.id); + + { + Map params = new HashMap<>(); + params.put("recipeUserId", user.id); + + JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/primary/check", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assertEquals(2, response.entrySet().size()); + assertEquals("OK", response.get("status").getAsString()); + assertTrue(response.get("wasAlreadyAPrimaryUser").getAsBoolean()); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void canCreatePrimaryUserBadInput() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + { + Map params = new HashMap<>(); + + try { + HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/primary/check", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assert (false); + } catch (HttpResponseException e) { + assert (e.statusCode == 400); + assert (e.getMessage() + .equals("Http error. Status Code: 400. Message: Field name 'recipeUserId' is missing in GET " + + "request")); + } + } + + { + Map params = new HashMap<>(); + params.put("recipeUserId", "random"); + + try { + HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/primary/check", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assert (false); + } catch (HttpResponseException e) { + assert (e.statusCode == 400); + assert (e.getMessage() + .equals("Http error. Status Code: 400. Message: Unknown user ID provided")); + } + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void makePrimaryUserFailsCauseAnotherAccountWithSameEmailAlreadyAPrimaryUser() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + AuthRecipeUserInfo emailPasswordUser = EmailPassword.signUp(process.getProcess(), "test@example.com", + "pass1234"); + + AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.createPrimaryUser(process.main, emailPasswordUser.id); + assert (!result.wasAlreadyAPrimaryUser); + + ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(process.main, "google", "user-google", + "test@example.com"); + + { + Map params = new HashMap<>(); + params.put("recipeUserId", signInUpResponse.user.id); + + JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/primary/check", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assertEquals(3, response.entrySet().size()); + assertEquals("ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR", + response.get("status").getAsString()); + assertEquals(emailPasswordUser.id, response.get("primaryUserId").getAsString()); + assertEquals("This user's email is already associated with another user ID", + response.get("description").getAsString()); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void makingPrimaryUserFailsCauseAlreadyLinkedToAnotherAccount() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + AuthRecipeUserInfo emailPasswordUser1 = EmailPassword.signUp(process.getProcess(), "test@example.com", + "pass1234"); + AuthRecipeUserInfo emailPasswordUser2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", + "pass1234"); + + AuthRecipe.createPrimaryUser(process.main, emailPasswordUser1.id); + AuthRecipe.linkAccounts(process.main, emailPasswordUser2.id, emailPasswordUser1.id); + + { + Map params = new HashMap<>(); + params.put("recipeUserId", emailPasswordUser2.id); + + JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/primary/check", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assertEquals(3, response.entrySet().size()); + assertEquals("RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR", + response.get("status").getAsString()); + assertEquals(emailPasswordUser1.id, response.get("primaryUserId").getAsString()); + assertEquals("This user ID is already linked to another user ID", + response.get("description").getAsString()); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + +} From 4ee69c615be247810d418f35a444d8068642e6eb Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Tue, 1 Aug 2023 14:18:10 +0530 Subject: [PATCH 054/131] adds create primary user API, except for converting tuser obj to json --- .../io/supertokens/webserver/Webserver.java | 2 + .../CanCreatePrimaryUserAPI.java | 45 ++- .../accountlinking/CreatePrimaryUserAPI.java | 118 ++++++ .../api/CanCreatePrimaryUserAPITest.java | 126 ++++++ .../api/CreatePrimaryUserAPITest.java | 373 ++++++++++++++++++ 5 files changed, 653 insertions(+), 11 deletions(-) create mode 100644 src/main/java/io/supertokens/webserver/api/accountlinking/CreatePrimaryUserAPI.java create mode 100644 src/test/java/io/supertokens/test/accountlinking/api/CreatePrimaryUserAPITest.java diff --git a/src/main/java/io/supertokens/webserver/Webserver.java b/src/main/java/io/supertokens/webserver/Webserver.java index 5ee007817..decf47031 100644 --- a/src/main/java/io/supertokens/webserver/Webserver.java +++ b/src/main/java/io/supertokens/webserver/Webserver.java @@ -27,6 +27,7 @@ import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.webserver.api.accountlinking.CanCreatePrimaryUserAPI; +import io.supertokens.webserver.api.accountlinking.CreatePrimaryUserAPI; import io.supertokens.webserver.api.core.*; import io.supertokens.webserver.api.dashboard.*; import io.supertokens.webserver.api.emailpassword.UserAPI; @@ -251,6 +252,7 @@ private void setupRoutes() { addAPI(new ListUsersByAccountInfoAPI(main)); addAPI(new CanCreatePrimaryUserAPI(main)); + addAPI(new CreatePrimaryUserAPI(main)); StandardContext context = tomcatReference.getContext(); Tomcat tomcat = tomcatReference.getTomcat(); diff --git a/src/main/java/io/supertokens/webserver/api/accountlinking/CanCreatePrimaryUserAPI.java b/src/main/java/io/supertokens/webserver/api/accountlinking/CanCreatePrimaryUserAPI.java index 037555dd2..327f9cc50 100644 --- a/src/main/java/io/supertokens/webserver/api/accountlinking/CanCreatePrimaryUserAPI.java +++ b/src/main/java/io/supertokens/webserver/api/accountlinking/CanCreatePrimaryUserAPI.java @@ -26,6 +26,7 @@ import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; +import io.supertokens.useridmapping.UserIdMapping; import io.supertokens.useridmapping.UserIdType; import io.supertokens.webserver.InputParser; import io.supertokens.webserver.WebserverAPI; @@ -51,9 +52,9 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO // API is app specific String inputRecipeUserId = InputParser.getQueryParamOrThrowError(req, "recipeUserId", false); + AppIdentifierWithStorage appIdentifierWithStorage = null; try { String userId = inputRecipeUserId; - AppIdentifierWithStorage appIdentifierWithStorage; AppIdentifierWithStorageAndUserIdMapping mappingAndStorage = getAppIdentifierWithStorageAndUserIdMappingFromRequest( req, inputRecipeUserId, UserIdType.ANY); @@ -73,17 +74,39 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO } catch (UnknownUserIdException e) { throw new ServletException(new BadRequestException("Unknown user ID provided")); } catch (AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException e) { - JsonObject response = new JsonObject(); - response.addProperty("status", "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"); - response.addProperty("primaryUserId", e.primaryUserId); - response.addProperty("description", e.getMessage()); - super.sendJsonResponse(200, response, resp); + try { + JsonObject response = new JsonObject(); + response.addProperty("status", "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"); + io.supertokens.pluginInterface.useridmapping.UserIdMapping result = UserIdMapping.getUserIdMapping( + appIdentifierWithStorage, e.primaryUserId, + UserIdType.SUPERTOKENS); + if (result != null) { + response.addProperty("primaryUserId", result.externalUserId); + } else { + response.addProperty("primaryUserId", e.primaryUserId); + } + response.addProperty("description", e.getMessage()); + super.sendJsonResponse(200, response, resp); + } catch (StorageQueryException ex) { + throw new ServletException(ex); + } } catch (RecipeUserIdAlreadyLinkedWithPrimaryUserIdException e) { - JsonObject response = new JsonObject(); - response.addProperty("status", "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR"); - response.addProperty("primaryUserId", e.primaryUserId); - response.addProperty("description", e.getMessage()); - super.sendJsonResponse(200, response, resp); + try { + JsonObject response = new JsonObject(); + response.addProperty("status", "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR"); + io.supertokens.pluginInterface.useridmapping.UserIdMapping result = UserIdMapping.getUserIdMapping( + appIdentifierWithStorage, e.primaryUserId, + UserIdType.SUPERTOKENS); + if (result != null) { + response.addProperty("primaryUserId", result.externalUserId); + } else { + response.addProperty("primaryUserId", e.primaryUserId); + } + response.addProperty("description", e.getMessage()); + super.sendJsonResponse(200, response, resp); + } catch (StorageQueryException ex) { + throw new ServletException(ex); + } } } } diff --git a/src/main/java/io/supertokens/webserver/api/accountlinking/CreatePrimaryUserAPI.java b/src/main/java/io/supertokens/webserver/api/accountlinking/CreatePrimaryUserAPI.java new file mode 100644 index 000000000..d7d57b749 --- /dev/null +++ b/src/main/java/io/supertokens/webserver/api/accountlinking/CreatePrimaryUserAPI.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2023, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.webserver.api.accountlinking; + +import com.google.gson.JsonObject; +import io.supertokens.AppIdentifierWithStorageAndUserIdMapping; +import io.supertokens.Main; +import io.supertokens.authRecipe.AuthRecipe; +import io.supertokens.authRecipe.exception.AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException; +import io.supertokens.authRecipe.exception.RecipeUserIdAlreadyLinkedWithPrimaryUserIdException; +import io.supertokens.featureflag.exceptions.FeatureNotEnabledException; +import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; +import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; +import io.supertokens.useridmapping.UserIdMapping; +import io.supertokens.useridmapping.UserIdType; +import io.supertokens.webserver.InputParser; +import io.supertokens.webserver.WebserverAPI; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import java.io.IOException; + +public class CreatePrimaryUserAPI extends WebserverAPI { + + public CreatePrimaryUserAPI(Main main) { + super(main, ""); + } + + @Override + public String getPath() { + return "/recipe/accountlinking/user/primary"; + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { + // API is app specific + JsonObject input = InputParser.parseJsonObjectOrThrowError(req); + String inputRecipeUserId = InputParser.parseStringOrThrowError(input, "recipeUserId", false); + + AppIdentifierWithStorage appIdentifierWithStorage = null; + try { + String userId = inputRecipeUserId; + AppIdentifierWithStorageAndUserIdMapping mappingAndStorage = + getAppIdentifierWithStorageAndUserIdMappingFromRequest( + req, inputRecipeUserId, UserIdType.ANY); + if (mappingAndStorage.userIdMapping != null) { + userId = mappingAndStorage.userIdMapping.superTokensUserId; + } + appIdentifierWithStorage = mappingAndStorage.appIdentifierWithStorage; + + AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.createPrimaryUser(main, appIdentifierWithStorage, + userId); + JsonObject response = new JsonObject(); + response.addProperty("status", "OK"); + response.addProperty("wasAlreadyAPrimaryUser", result.wasAlreadyAPrimaryUser); + if (mappingAndStorage.userIdMapping != null) { + result.user.setExternalUserId(mappingAndStorage.userIdMapping.externalUserId); + } + response.add("user", result.user.toJson()); + super.sendJsonResponse(200, response, resp); + } catch (StorageQueryException | TenantOrAppNotFoundException | FeatureNotEnabledException e) { + throw new ServletException(e); + } catch (UnknownUserIdException e) { + throw new ServletException(new BadRequestException("Unknown user ID provided")); + } catch (AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException e) { + try { + JsonObject response = new JsonObject(); + response.addProperty("status", "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"); + io.supertokens.pluginInterface.useridmapping.UserIdMapping result = UserIdMapping.getUserIdMapping( + appIdentifierWithStorage, e.primaryUserId, + UserIdType.SUPERTOKENS); + if (result != null) { + response.addProperty("primaryUserId", result.externalUserId); + } else { + response.addProperty("primaryUserId", e.primaryUserId); + } + response.addProperty("description", e.getMessage()); + super.sendJsonResponse(200, response, resp); + } catch (StorageQueryException ex) { + throw new ServletException(ex); + } + } catch (RecipeUserIdAlreadyLinkedWithPrimaryUserIdException e) { + try { + JsonObject response = new JsonObject(); + response.addProperty("status", "RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR"); + io.supertokens.pluginInterface.useridmapping.UserIdMapping result = UserIdMapping.getUserIdMapping( + appIdentifierWithStorage, e.primaryUserId, + UserIdType.SUPERTOKENS); + if (result != null) { + response.addProperty("primaryUserId", result.externalUserId); + } else { + response.addProperty("primaryUserId", e.primaryUserId); + } + response.addProperty("description", e.getMessage()); + super.sendJsonResponse(200, response, resp); + } catch (StorageQueryException ex) { + throw new ServletException(ex); + } + } + } +} diff --git a/src/test/java/io/supertokens/test/accountlinking/api/CanCreatePrimaryUserAPITest.java b/src/test/java/io/supertokens/test/accountlinking/api/CanCreatePrimaryUserAPITest.java index eeda5e0c2..e7492d88b 100644 --- a/src/test/java/io/supertokens/test/accountlinking/api/CanCreatePrimaryUserAPITest.java +++ b/src/test/java/io/supertokens/test/accountlinking/api/CanCreatePrimaryUserAPITest.java @@ -30,6 +30,7 @@ import io.supertokens.test.httpRequest.HttpRequestForTesting; import io.supertokens.test.httpRequest.HttpResponseException; import io.supertokens.thirdparty.ThirdParty; +import io.supertokens.useridmapping.UserIdMapping; import io.supertokens.webserver.WebserverAPI; import org.junit.AfterClass; import org.junit.Before; @@ -102,6 +103,53 @@ public void canCreateReturnsTrue() throws Exception { assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); } + @Test + public void canCreateReturnsTrueWithUserIdMapping() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "abcd1234"); + UserIdMapping.createUserIdMapping(process.main, user.id, "r1", null, false); + + { + Map params = new HashMap<>(); + params.put("recipeUserId", "r1"); + + JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/primary/check", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assertEquals(2, response.entrySet().size()); + assertEquals("OK", response.get("status").getAsString()); + assertFalse(response.get("wasAlreadyAPrimaryUser").getAsBoolean()); + } + + AuthRecipe.createPrimaryUser(process.main, user.id); + + { + Map params = new HashMap<>(); + params.put("recipeUserId", "r1"); + + JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/primary/check", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assertEquals(2, response.entrySet().size()); + assertEquals("OK", response.get("status").getAsString()); + assertTrue(response.get("wasAlreadyAPrimaryUser").getAsBoolean()); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + @Test public void canCreatePrimaryUserBadInput() throws Exception { String[] args = {"../"}; @@ -227,4 +275,82 @@ public void makingPrimaryUserFailsCauseAlreadyLinkedToAnotherAccount() throws Ex assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); } + @Test + public void makePrimaryUserFailsCauseAnotherAccountWithSameEmailAlreadyAPrimaryUserWithUserIdMapping() + throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + AuthRecipeUserInfo emailPasswordUser = EmailPassword.signUp(process.getProcess(), "test@example.com", + "pass1234"); + UserIdMapping.createUserIdMapping(process.main, emailPasswordUser.id, "r1", null, false); + + AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.createPrimaryUser(process.main, emailPasswordUser.id); + assert (!result.wasAlreadyAPrimaryUser); + + ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(process.main, "google", "user-google", + "test@example.com"); + + { + Map params = new HashMap<>(); + params.put("recipeUserId", signInUpResponse.user.id); + + JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/primary/check", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assertEquals(3, response.entrySet().size()); + assertEquals("ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR", + response.get("status").getAsString()); + assertEquals("r1", response.get("primaryUserId").getAsString()); + assertEquals("This user's email is already associated with another user ID", + response.get("description").getAsString()); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void makingPrimaryUserFailsCauseAlreadyLinkedToAnotherAccountWithUserIdMapping() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + AuthRecipeUserInfo emailPasswordUser1 = EmailPassword.signUp(process.getProcess(), "test@example.com", + "pass1234"); + UserIdMapping.createUserIdMapping(process.main, emailPasswordUser1.id, "r1", null, false); + AuthRecipeUserInfo emailPasswordUser2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", + "pass1234"); + + AuthRecipe.createPrimaryUser(process.main, emailPasswordUser1.id); + AuthRecipe.linkAccounts(process.main, emailPasswordUser2.id, emailPasswordUser1.id); + + { + Map params = new HashMap<>(); + params.put("recipeUserId", emailPasswordUser2.id); + + JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/primary/check", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assertEquals(3, response.entrySet().size()); + assertEquals("RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR", + response.get("status").getAsString()); + assertEquals("r1", response.get("primaryUserId").getAsString()); + assertEquals("This user ID is already linked to another user ID", + response.get("description").getAsString()); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + } diff --git a/src/test/java/io/supertokens/test/accountlinking/api/CreatePrimaryUserAPITest.java b/src/test/java/io/supertokens/test/accountlinking/api/CreatePrimaryUserAPITest.java new file mode 100644 index 000000000..75e9f84ab --- /dev/null +++ b/src/test/java/io/supertokens/test/accountlinking/api/CreatePrimaryUserAPITest.java @@ -0,0 +1,373 @@ +/* + * Copyright (c) 2023, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.test.accountlinking.api; + +import com.google.gson.JsonObject; +import io.supertokens.ProcessState; +import io.supertokens.authRecipe.AuthRecipe; +import io.supertokens.emailpassword.EmailPassword; +import io.supertokens.featureflag.EE_FEATURES; +import io.supertokens.featureflag.FeatureFlagTestContent; +import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; +import io.supertokens.storageLayer.StorageLayer; +import io.supertokens.test.TestingProcessManager; +import io.supertokens.test.Utils; +import io.supertokens.test.httpRequest.HttpRequestForTesting; +import io.supertokens.test.httpRequest.HttpResponseException; +import io.supertokens.thirdparty.ThirdParty; +import io.supertokens.useridmapping.UserIdMapping; +import io.supertokens.webserver.WebserverAPI; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.*; + +public class CreatePrimaryUserAPITest { + @Rule + public TestRule watchman = Utils.getOnFailure(); + + @AfterClass + public static void afterTesting() { + Utils.afterTesting(); + } + + @Before + public void beforeEach() { + Utils.reset(); + } + + @Test + public void createReturnsSucceeds() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "abcd1234"); + + { + JsonObject params = new JsonObject(); + params.addProperty("recipeUserId", user.id); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/primary", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assertEquals(3, response.entrySet().size()); + assertEquals("OK", response.get("status").getAsString()); + assertFalse(response.get("wasAlreadyAPrimaryUser").getAsBoolean()); + // TODO: compare user object.. + } + + AuthRecipe.createPrimaryUser(process.main, user.id); + + { + JsonObject params = new JsonObject(); + params.addProperty("recipeUserId", user.id); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/primary", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assertEquals(3, response.entrySet().size()); + assertEquals("OK", response.get("status").getAsString()); + assertTrue(response.get("wasAlreadyAPrimaryUser").getAsBoolean()); + // TODO: compare user object.. + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void createReturnsTrueWithUserIdMapping() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "abcd1234"); + UserIdMapping.createUserIdMapping(process.main, user.id, "r1", null, false); + + { + JsonObject params = new JsonObject(); + params.addProperty("recipeUserId", "r1"); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/primary", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assertEquals(3, response.entrySet().size()); + assertEquals("OK", response.get("status").getAsString()); + assertFalse(response.get("wasAlreadyAPrimaryUser").getAsBoolean()); + // TODO: compare user object.. + } + + AuthRecipe.createPrimaryUser(process.main, user.id); + + { + JsonObject params = new JsonObject(); + params.addProperty("recipeUserId", "r1"); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/primary", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assertEquals(3, response.entrySet().size()); + assertEquals("OK", response.get("status").getAsString()); + assertTrue(response.get("wasAlreadyAPrimaryUser").getAsBoolean()); + // TODO: compare user object.. + } + + { + JsonObject params = new JsonObject(); + params.addProperty("recipeUserId", user.id); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/primary", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assertEquals(3, response.entrySet().size()); + assertEquals("OK", response.get("status").getAsString()); + assertTrue(response.get("wasAlreadyAPrimaryUser").getAsBoolean()); + // TODO: compare user object.. + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void createPrimaryUserBadInput() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + { + Map params = new HashMap<>(); + + try { + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/primary", new JsonObject(), 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assert (false); + } catch (HttpResponseException e) { + assert (e.statusCode == 400); + assert (e.getMessage() + .equals("Http error. Status Code: 400. Message: Field name 'recipeUserId' is invalid in JSON " + + "input")); + } + } + + { + JsonObject params = new JsonObject(); + params.addProperty("recipeUserId", "random"); + + try { + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/primary", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assert (false); + } catch (HttpResponseException e) { + assert (e.statusCode == 400); + assert (e.getMessage() + .equals("Http error. Status Code: 400. Message: Unknown user ID provided")); + } + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void makePrimaryUserFailsCauseAnotherAccountWithSameEmailAlreadyAPrimaryUser() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + AuthRecipeUserInfo emailPasswordUser = EmailPassword.signUp(process.getProcess(), "test@example.com", + "pass1234"); + + AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.createPrimaryUser(process.main, emailPasswordUser.id); + assert (!result.wasAlreadyAPrimaryUser); + + ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(process.main, "google", "user-google", + "test@example.com"); + + { + JsonObject params = new JsonObject(); + params.addProperty("recipeUserId", signInUpResponse.user.id); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/primary", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assertEquals(3, response.entrySet().size()); + assertEquals("ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR", + response.get("status").getAsString()); + assertEquals(emailPasswordUser.id, response.get("primaryUserId").getAsString()); + assertEquals("This user's email is already associated with another user ID", + response.get("description").getAsString()); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void makingPrimaryUserFailsCauseAlreadyLinkedToAnotherAccount() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + AuthRecipeUserInfo emailPasswordUser1 = EmailPassword.signUp(process.getProcess(), "test@example.com", + "pass1234"); + AuthRecipeUserInfo emailPasswordUser2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", + "pass1234"); + + AuthRecipe.createPrimaryUser(process.main, emailPasswordUser1.id); + AuthRecipe.linkAccounts(process.main, emailPasswordUser2.id, emailPasswordUser1.id); + + { + JsonObject params = new JsonObject(); + params.addProperty("recipeUserId", emailPasswordUser2.id); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/primary", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assertEquals(3, response.entrySet().size()); + assertEquals("RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR", + response.get("status").getAsString()); + assertEquals(emailPasswordUser1.id, response.get("primaryUserId").getAsString()); + assertEquals("This user ID is already linked to another user ID", + response.get("description").getAsString()); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void makePrimaryUserFailsCauseAnotherAccountWithSameEmailAlreadyAPrimaryUserWithUserIdMapping() + throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + AuthRecipeUserInfo emailPasswordUser = EmailPassword.signUp(process.getProcess(), "test@example.com", + "pass1234"); + UserIdMapping.createUserIdMapping(process.main, emailPasswordUser.id, "r1", null, false); + + AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.createPrimaryUser(process.main, emailPasswordUser.id); + assert (!result.wasAlreadyAPrimaryUser); + + ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(process.main, "google", "user-google", + "test@example.com"); + + { + JsonObject params = new JsonObject(); + params.addProperty("recipeUserId", signInUpResponse.user.id); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/primary", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assertEquals(3, response.entrySet().size()); + assertEquals("ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR", + response.get("status").getAsString()); + assertEquals("r1", response.get("primaryUserId").getAsString()); + assertEquals("This user's email is already associated with another user ID", + response.get("description").getAsString()); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void makingPrimaryUserFailsCauseAlreadyLinkedToAnotherAccountWithUserIdMapping() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + AuthRecipeUserInfo emailPasswordUser1 = EmailPassword.signUp(process.getProcess(), "test@example.com", + "pass1234"); + UserIdMapping.createUserIdMapping(process.main, emailPasswordUser1.id, "r1", null, false); + AuthRecipeUserInfo emailPasswordUser2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", + "pass1234"); + + AuthRecipe.createPrimaryUser(process.main, emailPasswordUser1.id); + AuthRecipe.linkAccounts(process.main, emailPasswordUser2.id, emailPasswordUser1.id); + + { + JsonObject params = new JsonObject(); + params.addProperty("recipeUserId", emailPasswordUser2.id); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/primary", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assertEquals(3, response.entrySet().size()); + assertEquals("RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR", + response.get("status").getAsString()); + assertEquals("r1", response.get("primaryUserId").getAsString()); + assertEquals("This user ID is already linked to another user ID", + response.get("description").getAsString()); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + +} From 03b036c471143851956cc2f29618d1ca3d2e9752 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Tue, 1 Aug 2023 18:14:54 +0530 Subject: [PATCH 055/131] fixes older APIs --- .../api/accountlinking/CreatePrimaryUserAPI.java | 2 ++ .../webserver/api/core/GetUserByIdAPI.java | 2 ++ .../api/core/ListUsersByAccountInfoAPI.java | 2 ++ .../io/supertokens/webserver/api/core/UsersAPI.java | 2 ++ .../webserver/api/emailpassword/SignInAPI.java | 2 ++ .../webserver/api/emailpassword/UserAPI.java | 11 +++++++++-- .../webserver/api/passwordless/ConsumeCodeAPI.java | 2 ++ .../webserver/api/passwordless/UserAPI.java | 13 +++++++++++-- .../api/thirdparty/GetUsersByEmailAPI.java | 2 ++ .../webserver/api/thirdparty/SignInUpAPI.java | 2 ++ .../webserver/api/thirdparty/UserAPI.java | 11 +++++++++-- 11 files changed, 45 insertions(+), 6 deletions(-) diff --git a/src/main/java/io/supertokens/webserver/api/accountlinking/CreatePrimaryUserAPI.java b/src/main/java/io/supertokens/webserver/api/accountlinking/CreatePrimaryUserAPI.java index d7d57b749..0eb764238 100644 --- a/src/main/java/io/supertokens/webserver/api/accountlinking/CreatePrimaryUserAPI.java +++ b/src/main/java/io/supertokens/webserver/api/accountlinking/CreatePrimaryUserAPI.java @@ -72,6 +72,8 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I response.addProperty("wasAlreadyAPrimaryUser", result.wasAlreadyAPrimaryUser); if (mappingAndStorage.userIdMapping != null) { result.user.setExternalUserId(mappingAndStorage.userIdMapping.externalUserId); + } else { + result.user.setExternalUserId(null); } response.add("user", result.user.toJson()); super.sendJsonResponse(200, response, resp); diff --git a/src/main/java/io/supertokens/webserver/api/core/GetUserByIdAPI.java b/src/main/java/io/supertokens/webserver/api/core/GetUserByIdAPI.java index 58db6959f..debaca684 100644 --- a/src/main/java/io/supertokens/webserver/api/core/GetUserByIdAPI.java +++ b/src/main/java/io/supertokens/webserver/api/core/GetUserByIdAPI.java @@ -71,6 +71,8 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO getAppIdentifierWithStorage(req), user.id, UserIdType.SUPERTOKENS); if (userIdMapping != null) { user.setExternalUserId(userIdMapping.externalUserId); + } else { + user.setExternalUserId(null); } } diff --git a/src/main/java/io/supertokens/webserver/api/core/ListUsersByAccountInfoAPI.java b/src/main/java/io/supertokens/webserver/api/core/ListUsersByAccountInfoAPI.java index 3f5320dc2..834738f28 100644 --- a/src/main/java/io/supertokens/webserver/api/core/ListUsersByAccountInfoAPI.java +++ b/src/main/java/io/supertokens/webserver/api/core/ListUsersByAccountInfoAPI.java @@ -84,6 +84,8 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO .getUserIdMapping(appIdentifierWithStorage, users[i].id, UserIdType.SUPERTOKENS); if (userIdMapping != null) { users[i].setExternalUserId(userIdMapping.externalUserId); + } else { + users[i].setExternalUserId(null); } } diff --git a/src/main/java/io/supertokens/webserver/api/core/UsersAPI.java b/src/main/java/io/supertokens/webserver/api/core/UsersAPI.java index f3c4ac63c..337730ca2 100644 --- a/src/main/java/io/supertokens/webserver/api/core/UsersAPI.java +++ b/src/main/java/io/supertokens/webserver/api/core/UsersAPI.java @@ -178,6 +178,8 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO String externalId = userIdMapping.get(userIds.get(i)); if (externalId != null) { users.users[i].user.setExternalUserId(externalId); + } else { + users.users[i].user.setExternalUserId(null); } } } diff --git a/src/main/java/io/supertokens/webserver/api/emailpassword/SignInAPI.java b/src/main/java/io/supertokens/webserver/api/emailpassword/SignInAPI.java index cf21a49b1..c1aadefa2 100644 --- a/src/main/java/io/supertokens/webserver/api/emailpassword/SignInAPI.java +++ b/src/main/java/io/supertokens/webserver/api/emailpassword/SignInAPI.java @@ -86,6 +86,8 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I if (userIdMapping != null) { user.setExternalUserId(userIdMapping.externalUserId); + } else { + user.setExternalUserId(null); } JsonObject result = new JsonObject(); diff --git a/src/main/java/io/supertokens/webserver/api/emailpassword/UserAPI.java b/src/main/java/io/supertokens/webserver/api/emailpassword/UserAPI.java index 8c002373b..be9aaa9a4 100644 --- a/src/main/java/io/supertokens/webserver/api/emailpassword/UserAPI.java +++ b/src/main/java/io/supertokens/webserver/api/emailpassword/UserAPI.java @@ -90,8 +90,13 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO appIdentifierWithStorageAndUserIdMapping.appIdentifierWithStorage, userId); // if the userIdMapping exists set the userId in the response to the externalUserId - if (user != null && appIdentifierWithStorageAndUserIdMapping.userIdMapping != null) { - user.setExternalUserId(appIdentifierWithStorageAndUserIdMapping.userIdMapping.externalUserId); + if (user != null) { + if (appIdentifierWithStorageAndUserIdMapping.userIdMapping != null) { + user.setExternalUserId( + appIdentifierWithStorageAndUserIdMapping.userIdMapping.externalUserId); + } else { + user.setExternalUserId(null); + } } } else { @@ -110,6 +115,8 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO getAppIdentifierWithStorage(req), user.id, UserIdType.SUPERTOKENS); if (userIdMapping != null) { user.setExternalUserId(userIdMapping.externalUserId); + } else { + user.setExternalUserId(null); } } } diff --git a/src/main/java/io/supertokens/webserver/api/passwordless/ConsumeCodeAPI.java b/src/main/java/io/supertokens/webserver/api/passwordless/ConsumeCodeAPI.java index a44088aa3..9be86ec51 100644 --- a/src/main/java/io/supertokens/webserver/api/passwordless/ConsumeCodeAPI.java +++ b/src/main/java/io/supertokens/webserver/api/passwordless/ConsumeCodeAPI.java @@ -92,6 +92,8 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I consumeCodeResponse.user.id, UserIdType.ANY); if (userIdMapping != null) { consumeCodeResponse.user.setExternalUserId(userIdMapping.externalUserId); + } else { + consumeCodeResponse.user.setExternalUserId(null); } JsonObject result = new JsonObject(); diff --git a/src/main/java/io/supertokens/webserver/api/passwordless/UserAPI.java b/src/main/java/io/supertokens/webserver/api/passwordless/UserAPI.java index b3c06f560..d5e9c66c7 100644 --- a/src/main/java/io/supertokens/webserver/api/passwordless/UserAPI.java +++ b/src/main/java/io/supertokens/webserver/api/passwordless/UserAPI.java @@ -84,8 +84,13 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO userId); // if the userIdMapping exists set the userId in the response to the externalUserId - if (user != null && appIdentifierWithStorageAndUserIdMapping.userIdMapping != null) { - user.setExternalUserId(appIdentifierWithStorageAndUserIdMapping.userIdMapping.externalUserId); + if (user != null) { + if (appIdentifierWithStorageAndUserIdMapping.userIdMapping != null) { + user.setExternalUserId( + appIdentifierWithStorageAndUserIdMapping.userIdMapping.externalUserId); + } else { + user.setExternalUserId(null); + } } } catch (UnknownUserIdException e) { user = null; @@ -99,6 +104,8 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO user.id, UserIdType.SUPERTOKENS); if (userIdMapping != null) { user.setExternalUserId(userIdMapping.externalUserId); + } else { + user.setExternalUserId(null); } } } else { @@ -110,6 +117,8 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO user.id, UserIdType.SUPERTOKENS); if (userIdMapping != null) { user.setExternalUserId(userIdMapping.externalUserId); + } else { + user.setExternalUserId(null); } } } diff --git a/src/main/java/io/supertokens/webserver/api/thirdparty/GetUsersByEmailAPI.java b/src/main/java/io/supertokens/webserver/api/thirdparty/GetUsersByEmailAPI.java index e3d6fcce9..5c55d2aac 100644 --- a/src/main/java/io/supertokens/webserver/api/thirdparty/GetUsersByEmailAPI.java +++ b/src/main/java/io/supertokens/webserver/api/thirdparty/GetUsersByEmailAPI.java @@ -72,6 +72,8 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO .getUserIdMapping(appIdentifierWithStorage, users[i].id, UserIdType.SUPERTOKENS); if (userIdMapping != null) { users[i].setExternalUserId(userIdMapping.externalUserId); + } else { + users[i].setExternalUserId(null); } } diff --git a/src/main/java/io/supertokens/webserver/api/thirdparty/SignInUpAPI.java b/src/main/java/io/supertokens/webserver/api/thirdparty/SignInUpAPI.java index 4f8b34dd1..cbed5b1a1 100644 --- a/src/main/java/io/supertokens/webserver/api/thirdparty/SignInUpAPI.java +++ b/src/main/java/io/supertokens/webserver/api/thirdparty/SignInUpAPI.java @@ -126,6 +126,8 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I UserIdType.SUPERTOKENS); if (userIdMapping != null) { response.user.setExternalUserId(userIdMapping.externalUserId); + } else { + response.user.setExternalUserId(null); } JsonObject result = new JsonObject(); diff --git a/src/main/java/io/supertokens/webserver/api/thirdparty/UserAPI.java b/src/main/java/io/supertokens/webserver/api/thirdparty/UserAPI.java index 84d3c1ab9..5a79a8c5c 100644 --- a/src/main/java/io/supertokens/webserver/api/thirdparty/UserAPI.java +++ b/src/main/java/io/supertokens/webserver/api/thirdparty/UserAPI.java @@ -84,8 +84,13 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO user = ThirdParty.getUser(appIdentifierWithStorageAndUserIdMapping.appIdentifierWithStorage, userId); - if (user != null && appIdentifierWithStorageAndUserIdMapping.userIdMapping != null) { - user.setExternalUserId(appIdentifierWithStorageAndUserIdMapping.userIdMapping.externalUserId); + if (user != null) { + if (appIdentifierWithStorageAndUserIdMapping.userIdMapping != null) { + user.setExternalUserId( + appIdentifierWithStorageAndUserIdMapping.userIdMapping.externalUserId); + } else { + user.setExternalUserId(null); + } } } catch (UnknownUserIdException e) { // let the user be null @@ -98,6 +103,8 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO .getUserIdMapping(this.getAppIdentifierWithStorage(req), user.id, UserIdType.SUPERTOKENS); if (userIdMapping != null) { user.setExternalUserId(userIdMapping.externalUserId); + } else { + user.setExternalUserId(null); } } } From 840962d8529e69db6779391604a81f55f806ab57 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Tue, 1 Aug 2023 20:05:24 +0530 Subject: [PATCH 056/131] fixes tests --- .../api/emailpassword/SignUpAPI.java | 1 + .../api/CreatePrimaryUserAPITest.java | 47 +++++++++++++++++-- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/src/main/java/io/supertokens/webserver/api/emailpassword/SignUpAPI.java b/src/main/java/io/supertokens/webserver/api/emailpassword/SignUpAPI.java index ae69aa3a8..304468079 100644 --- a/src/main/java/io/supertokens/webserver/api/emailpassword/SignUpAPI.java +++ b/src/main/java/io/supertokens/webserver/api/emailpassword/SignUpAPI.java @@ -84,6 +84,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I JsonObject result = new JsonObject(); result.addProperty("status", "OK"); + user.setExternalUserId(null); JsonObject userJson = getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v4_0) ? user.toJson() : user.toJsonWithoutAccountLinking(); diff --git a/src/test/java/io/supertokens/test/accountlinking/api/CreatePrimaryUserAPITest.java b/src/test/java/io/supertokens/test/accountlinking/api/CreatePrimaryUserAPITest.java index 75e9f84ab..61a0ec8e7 100644 --- a/src/test/java/io/supertokens/test/accountlinking/api/CreatePrimaryUserAPITest.java +++ b/src/test/java/io/supertokens/test/accountlinking/api/CreatePrimaryUserAPITest.java @@ -73,6 +73,7 @@ public void createReturnsSucceeds() throws Exception { AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "abcd1234"); + JsonObject userObj; { JsonObject params = new JsonObject(); params.addProperty("recipeUserId", user.id); @@ -83,7 +84,25 @@ public void createReturnsSucceeds() throws Exception { assertEquals(3, response.entrySet().size()); assertEquals("OK", response.get("status").getAsString()); assertFalse(response.get("wasAlreadyAPrimaryUser").getAsBoolean()); - // TODO: compare user object.. + + // check user object + JsonObject jsonUser = response.get("user").getAsJsonObject(); + assert (jsonUser.get("id").getAsString().equals(user.id)); + assert (jsonUser.get("timeJoined").getAsLong() == user.timeJoined); + assert (jsonUser.get("isPrimaryUser").getAsBoolean()); + assert (jsonUser.get("emails").getAsJsonArray().size() == 1); + assert (jsonUser.get("emails").getAsJsonArray().get(0).getAsString().equals("test@example.com")); + assert (jsonUser.get("phoneNumbers").getAsJsonArray().size() == 0); + assert (jsonUser.get("thirdParty").getAsJsonArray().size() == 0); + assert (jsonUser.get("loginMethods").getAsJsonArray().size() == 1); + JsonObject lM = jsonUser.get("loginMethods").getAsJsonArray().get(0).getAsJsonObject(); + assertFalse(lM.get("verified").getAsBoolean()); + assertEquals(lM.get("timeJoined").getAsLong(), user.timeJoined); + assertEquals(lM.get("recipeUserId").getAsString(), user.id); + assertEquals(lM.get("recipeId").getAsString(), "emailpassword"); + assertEquals(lM.get("email").getAsString(), "test@example.com"); + assert (lM.entrySet().size() == 5); + userObj = jsonUser; } AuthRecipe.createPrimaryUser(process.main, user.id); @@ -98,7 +117,7 @@ public void createReturnsSucceeds() throws Exception { assertEquals(3, response.entrySet().size()); assertEquals("OK", response.get("status").getAsString()); assertTrue(response.get("wasAlreadyAPrimaryUser").getAsBoolean()); - // TODO: compare user object.. + assertEquals(response.get("user"), userObj); } process.kill(); @@ -122,6 +141,7 @@ public void createReturnsTrueWithUserIdMapping() throws Exception { AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "abcd1234"); UserIdMapping.createUserIdMapping(process.main, user.id, "r1", null, false); + JsonObject userObj; { JsonObject params = new JsonObject(); params.addProperty("recipeUserId", "r1"); @@ -132,7 +152,24 @@ public void createReturnsTrueWithUserIdMapping() throws Exception { assertEquals(3, response.entrySet().size()); assertEquals("OK", response.get("status").getAsString()); assertFalse(response.get("wasAlreadyAPrimaryUser").getAsBoolean()); - // TODO: compare user object.. + // check user object + JsonObject jsonUser = response.get("user").getAsJsonObject(); + assert (jsonUser.get("id").getAsString().equals("r1")); + assert (jsonUser.get("timeJoined").getAsLong() == user.timeJoined); + assert (jsonUser.get("isPrimaryUser").getAsBoolean()); + assert (jsonUser.get("emails").getAsJsonArray().size() == 1); + assert (jsonUser.get("emails").getAsJsonArray().get(0).getAsString().equals("test@example.com")); + assert (jsonUser.get("phoneNumbers").getAsJsonArray().size() == 0); + assert (jsonUser.get("thirdParty").getAsJsonArray().size() == 0); + assert (jsonUser.get("loginMethods").getAsJsonArray().size() == 1); + JsonObject lM = jsonUser.get("loginMethods").getAsJsonArray().get(0).getAsJsonObject(); + assertFalse(lM.get("verified").getAsBoolean()); + assertEquals(lM.get("timeJoined").getAsLong(), user.timeJoined); + assertEquals(lM.get("recipeUserId").getAsString(), "r1"); + assertEquals(lM.get("recipeId").getAsString(), "emailpassword"); + assertEquals(lM.get("email").getAsString(), "test@example.com"); + assert (lM.entrySet().size() == 5); + userObj = jsonUser; } AuthRecipe.createPrimaryUser(process.main, user.id); @@ -147,7 +184,7 @@ public void createReturnsTrueWithUserIdMapping() throws Exception { assertEquals(3, response.entrySet().size()); assertEquals("OK", response.get("status").getAsString()); assertTrue(response.get("wasAlreadyAPrimaryUser").getAsBoolean()); - // TODO: compare user object.. + assertEquals(response.get("user"), userObj); } { @@ -160,7 +197,7 @@ public void createReturnsTrueWithUserIdMapping() throws Exception { assertEquals(3, response.entrySet().size()); assertEquals("OK", response.get("status").getAsString()); assertTrue(response.get("wasAlreadyAPrimaryUser").getAsBoolean()); - // TODO: compare user object.. + assertEquals(response.get("user"), userObj); } process.kill(); From 246f439d2c76b9dc9dedd5217d62e5c4e94c1812 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Tue, 1 Aug 2023 21:57:52 +0530 Subject: [PATCH 057/131] removes unneeded file --- addDevTag | 99 ------------------------------------------------------- 1 file changed, 99 deletions(-) delete mode 100755 addDevTag diff --git a/addDevTag b/addDevTag deleted file mode 100755 index 6efd047f2..000000000 --- a/addDevTag +++ /dev/null @@ -1,99 +0,0 @@ -#!/bin/bash -# check if we need to merge master into this branch------------ -if [[ $(git log origin/master ^HEAD) ]]; then - echo "You need to merge master into this branch. Exiting" - exit 1 -fi - -# get version------------ -version=`cat build.gradle | grep -e "version =" -e "version="` - -while IFS='"' read -ra ADDR; do - counter=0 - for i in "${ADDR[@]}"; do - if [ $counter == 1 ] - then - version=$i - fi - counter=$(($counter+1)) - done -done <<< "$version" - -branch_name="$(git symbolic-ref HEAD 2>/dev/null)" || -branch_name="(unnamed branch)" # detached HEAD - -branch_name=${branch_name##refs/heads/} - -# check if branch is correct based on the version----------- - -if ! [[ $version == $branch_name* ]] -then - RED='\033[0;31m' - NC='\033[0m' # No Color - printf "${RED}Adding tag to wrong branch. Stopping process${NC}\n" - exit 1 -fi - - - -git fetch --prune --prune-tags - -# get current commit hash------------ -if [ $# -eq 0 ] -then - commit_hash=`git log --pretty=format:'%H' -n 1` -else - commit_hash=$1 -fi - - -# check if current commit already has a tag or not------------ - -if [[ `git tag -l --points-at $commit_hash` == "" ]] -then - continue=1 -else - RED='\033[0;31m' - NC='\033[0m' - printf "${RED}This commit already has a tag. Please remove that and re-run this script${NC}\n" - echo "git tag --delete " - echo "git push --delete origin " - exit 1 -fi - - -# check if release version of this tag exists------------ - -if git rev-parse v$version >/dev/null 2>&1 -then - RED='\033[0;31m' - NC='\033[0m' - printf "${RED}The released version of this tag already exists${NC}\n" - exit 1 -fi - -# add an empty commit if the user has not given a commit hash so that we are sure it's built------------ -if [ $# -eq 0 ] -then - ./runBuild - if [[ $? -ne 0 ]] - then - echo "Failed to build" - exit 1 - fi - - # We have to do git add twice like this other jar changes in ./jar/* do not get added - git add --all "./jar/*" > /dev/null 2>&1 - git add --all "./**/jar/*" > /dev/null 2>&1 - git commit --allow-empty -m"adding dev-v$version tag to this commit to ensure building" - git push - commit_hash=`git log --pretty=format:'%H' -n 1` -fi - -# tag this commit and push it------------ - -git tag dev-v$version $commit_hash - -git push --tags - -echo "Added dev tag. PLEASE BE SURE TO CHECK THAT THE LATEST JARS HAVE BEEN COMMITTED!" From cbd05ce1bfbf4b9449e600de94f036c07fcc29c0 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Wed, 2 Aug 2023 13:49:07 +0530 Subject: [PATCH 058/131] starts working on can link accounts api --- .../io/supertokens/authRecipe/AuthRecipe.java | 2 +- .../io/supertokens/webserver/Webserver.java | 2 + .../accountlinking/CanLinkAccountsAPI.java | 142 +++++++++ .../api/CanLinkAccountsAPITest.java | 290 ++++++++++++++++++ .../api/CreatePrimaryUserAPITest.java | 98 +++++- 5 files changed, 531 insertions(+), 3 deletions(-) create mode 100644 src/main/java/io/supertokens/webserver/api/accountlinking/CanLinkAccountsAPI.java create mode 100644 src/test/java/io/supertokens/test/accountlinking/api/CanLinkAccountsAPITest.java diff --git a/src/main/java/io/supertokens/authRecipe/AuthRecipe.java b/src/main/java/io/supertokens/authRecipe/AuthRecipe.java index b6116f0a8..4abf0b06e 100644 --- a/src/main/java/io/supertokens/authRecipe/AuthRecipe.java +++ b/src/main/java/io/supertokens/authRecipe/AuthRecipe.java @@ -143,7 +143,7 @@ public CreatePrimaryUserResult(AuthRecipeUserInfo user, boolean wasAlreadyAPrima } } - private static class CanLinkAccountsResult { + public static class CanLinkAccountsResult { public String recipeUserId; public String primaryUserId; diff --git a/src/main/java/io/supertokens/webserver/Webserver.java b/src/main/java/io/supertokens/webserver/Webserver.java index decf47031..112b73862 100644 --- a/src/main/java/io/supertokens/webserver/Webserver.java +++ b/src/main/java/io/supertokens/webserver/Webserver.java @@ -27,6 +27,7 @@ import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.webserver.api.accountlinking.CanCreatePrimaryUserAPI; +import io.supertokens.webserver.api.accountlinking.CanLinkAccountsAPI; import io.supertokens.webserver.api.accountlinking.CreatePrimaryUserAPI; import io.supertokens.webserver.api.core.*; import io.supertokens.webserver.api.dashboard.*; @@ -253,6 +254,7 @@ private void setupRoutes() { addAPI(new CanCreatePrimaryUserAPI(main)); addAPI(new CreatePrimaryUserAPI(main)); + addAPI(new CanLinkAccountsAPI(main)); StandardContext context = tomcatReference.getContext(); Tomcat tomcat = tomcatReference.getTomcat(); diff --git a/src/main/java/io/supertokens/webserver/api/accountlinking/CanLinkAccountsAPI.java b/src/main/java/io/supertokens/webserver/api/accountlinking/CanLinkAccountsAPI.java new file mode 100644 index 000000000..f408cfca2 --- /dev/null +++ b/src/main/java/io/supertokens/webserver/api/accountlinking/CanLinkAccountsAPI.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2023, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.webserver.api.accountlinking; + +import com.google.gson.JsonObject; +import io.supertokens.AppIdentifierWithStorageAndUserIdMapping; +import io.supertokens.Main; +import io.supertokens.authRecipe.AuthRecipe; +import io.supertokens.authRecipe.exception.AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException; +import io.supertokens.authRecipe.exception.InputUserIdIsNotAPrimaryUserException; +import io.supertokens.authRecipe.exception.RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException; +import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; +import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; +import io.supertokens.useridmapping.UserIdMapping; +import io.supertokens.useridmapping.UserIdType; +import io.supertokens.webserver.InputParser; +import io.supertokens.webserver.WebserverAPI; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import java.io.IOException; + +public class CanLinkAccountsAPI extends WebserverAPI { + + public CanLinkAccountsAPI(Main main) { + super(main, ""); + } + + @Override + public String getPath() { + return "/recipe/accountlinking/user/link/check"; + } + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { + // API is app specific + String inputRecipeUserId = InputParser.getQueryParamOrThrowError(req, "recipeUserId", false); + String inputPrimaryUserId = InputParser.getQueryParamOrThrowError(req, "primaryUserId", false); + + AppIdentifierWithStorage primaryUserIdAppIdentifierWithStorage = null; + AppIdentifierWithStorage recipeUserIdAppIdentifierWithStorage = null; + try { + String recipeUserId = inputRecipeUserId; + { + AppIdentifierWithStorageAndUserIdMapping mappingAndStorage = + getAppIdentifierWithStorageAndUserIdMappingFromRequest( + req, inputRecipeUserId, UserIdType.ANY); + if (mappingAndStorage.userIdMapping != null) { + recipeUserId = mappingAndStorage.userIdMapping.superTokensUserId; + } + recipeUserIdAppIdentifierWithStorage = mappingAndStorage.appIdentifierWithStorage; + } + String primaryUserId = inputPrimaryUserId; + { + AppIdentifierWithStorageAndUserIdMapping mappingAndStorage = + getAppIdentifierWithStorageAndUserIdMappingFromRequest( + req, inputPrimaryUserId, UserIdType.ANY); + if (mappingAndStorage.userIdMapping != null) { + primaryUserId = mappingAndStorage.userIdMapping.superTokensUserId; + } + primaryUserIdAppIdentifierWithStorage = mappingAndStorage.appIdentifierWithStorage; + } + + // we do a check based on user pool ID and not instance reference checks cause the user + // could be in the same db, but their storage layers may just have different + if (!primaryUserIdAppIdentifierWithStorage.getStorage().getUserPoolId().equals( + recipeUserIdAppIdentifierWithStorage.getStorage().getUserPoolId())) { + throw new ServletException( + new BadRequestException( + "Cannot link users that are parts of different databases. Different pool IDs: " + + primaryUserIdAppIdentifierWithStorage.getStorage().getUserPoolId() + " AND " + + recipeUserIdAppIdentifierWithStorage.getStorage().getUserPoolId())); + } + + AuthRecipe.CanLinkAccountsResult result = AuthRecipe.canLinkAccounts(primaryUserIdAppIdentifierWithStorage, + recipeUserId, primaryUserId); + JsonObject response = new JsonObject(); + response.addProperty("status", "OK"); + response.addProperty("accountsAlreadyLinked", result.alreadyLinked); + super.sendJsonResponse(200, response, resp); + } catch (StorageQueryException | TenantOrAppNotFoundException e) { + throw new ServletException(e); + } catch (UnknownUserIdException e) { + throw new ServletException(new BadRequestException("Unknown user ID provided")); + } catch (AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException e) { + try { + JsonObject response = new JsonObject(); + response.addProperty("status", "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"); + io.supertokens.pluginInterface.useridmapping.UserIdMapping result = UserIdMapping.getUserIdMapping( + primaryUserIdAppIdentifierWithStorage, e.primaryUserId, + UserIdType.SUPERTOKENS); + if (result != null) { + response.addProperty("primaryUserId", result.externalUserId); + } else { + response.addProperty("primaryUserId", e.primaryUserId); + } + response.addProperty("description", e.getMessage()); + super.sendJsonResponse(200, response, resp); + } catch (StorageQueryException ex) { + throw new ServletException(ex); + } + } catch (RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException e) { + try { + JsonObject response = new JsonObject(); + response.addProperty("status", "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"); + io.supertokens.pluginInterface.useridmapping.UserIdMapping result = UserIdMapping.getUserIdMapping( + recipeUserIdAppIdentifierWithStorage, e.primaryUserId, + UserIdType.SUPERTOKENS); + if (result != null) { + response.addProperty("primaryUserId", result.externalUserId); + } else { + response.addProperty("primaryUserId", e.primaryUserId); + } + response.addProperty("description", e.getMessage()); + super.sendJsonResponse(200, response, resp); + } catch (StorageQueryException ex) { + throw new ServletException(ex); + } + } catch (InputUserIdIsNotAPrimaryUserException e) { + JsonObject response = new JsonObject(); + response.addProperty("status", "INPUT_USER_IS_NOT_A_PRIMARY_USER"); + super.sendJsonResponse(200, response, resp); + } + } +} diff --git a/src/test/java/io/supertokens/test/accountlinking/api/CanLinkAccountsAPITest.java b/src/test/java/io/supertokens/test/accountlinking/api/CanLinkAccountsAPITest.java new file mode 100644 index 000000000..b5dcc26ab --- /dev/null +++ b/src/test/java/io/supertokens/test/accountlinking/api/CanLinkAccountsAPITest.java @@ -0,0 +1,290 @@ +/* + * Copyright (c) 2023, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.test.accountlinking.api; + +import io.supertokens.test.Utils; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Rule; +import org.junit.rules.TestRule; + +public class CanLinkAccountsAPITest { + @Rule + public TestRule watchman = Utils.getOnFailure(); + + @AfterClass + public static void afterTesting() { + Utils.afterTesting(); + } + + @Before + public void beforeEach() { + Utils.reset(); + } + +// @Test +// public void canCreateReturnsTrueWithUserIdMapping() throws Exception { +// String[] args = {"../"}; +// TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); +// FeatureFlagTestContent.getInstance(process.getProcess()) +// .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ +// EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); +// process.startProcess(); +// assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); +// +// if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { +// return; +// } +// +// AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "abcd1234"); +// UserIdMapping.createUserIdMapping(process.main, user.id, "r1", null, false); +// +// { +// Map params = new HashMap<>(); +// params.put("recipeUserId", "r1"); +// +// JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", +// "http://localhost:3567/recipe/accountlinking/user/primary/check", params, 1000, 1000, null, +// WebserverAPI.getLatestCDIVersion().get(), ""); +// assertEquals(2, response.entrySet().size()); +// assertEquals("OK", response.get("status").getAsString()); +// assertFalse(response.get("wasAlreadyAPrimaryUser").getAsBoolean()); +// } +// +// AuthRecipe.createPrimaryUser(process.main, user.id); +// +// { +// Map params = new HashMap<>(); +// params.put("recipeUserId", "r1"); +// +// JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", +// "http://localhost:3567/recipe/accountlinking/user/primary/check", params, 1000, 1000, null, +// WebserverAPI.getLatestCDIVersion().get(), ""); +// assertEquals(2, response.entrySet().size()); +// assertEquals("OK", response.get("status").getAsString()); +// assertTrue(response.get("wasAlreadyAPrimaryUser").getAsBoolean()); +// } +// +// process.kill(); +// assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); +// } +// +// @Test +// public void canCreatePrimaryUserBadInput() throws Exception { +// String[] args = {"../"}; +// TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); +// FeatureFlagTestContent.getInstance(process.getProcess()) +// .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ +// EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); +// process.startProcess(); +// assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); +// +// if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { +// return; +// } +// +// { +// Map params = new HashMap<>(); +// +// try { +// HttpRequestForTesting.sendGETRequest(process.getProcess(), "", +// "http://localhost:3567/recipe/accountlinking/user/primary/check", params, 1000, 1000, null, +// WebserverAPI.getLatestCDIVersion().get(), ""); +// assert (false); +// } catch (HttpResponseException e) { +// assert (e.statusCode == 400); +// assert (e.getMessage() +// .equals("Http error. Status Code: 400. Message: Field name 'recipeUserId' is missing in GET +// " + +// "request")); +// } +// } +// +// { +// Map params = new HashMap<>(); +// params.put("recipeUserId", "random"); +// +// try { +// HttpRequestForTesting.sendGETRequest(process.getProcess(), "", +// "http://localhost:3567/recipe/accountlinking/user/primary/check", params, 1000, 1000, null, +// WebserverAPI.getLatestCDIVersion().get(), ""); +// assert (false); +// } catch (HttpResponseException e) { +// assert (e.statusCode == 400); +// assert (e.getMessage() +// .equals("Http error. Status Code: 400. Message: Unknown user ID provided")); +// } +// } +// +// process.kill(); +// assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); +// } +// +// @Test +// public void makePrimaryUserFailsCauseAnotherAccountWithSameEmailAlreadyAPrimaryUser() throws Exception { +// String[] args = {"../"}; +// TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); +// FeatureFlagTestContent.getInstance(process.getProcess()) +// .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ +// EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); +// process.startProcess(); +// assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); +// +// AuthRecipeUserInfo emailPasswordUser = EmailPassword.signUp(process.getProcess(), "test@example.com", +// "pass1234"); +// +// AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.createPrimaryUser(process.main, emailPasswordUser.id); +// assert (!result.wasAlreadyAPrimaryUser); +// +// ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(process.main, "google", "user-google", +// "test@example.com"); +// +// { +// Map params = new HashMap<>(); +// params.put("recipeUserId", signInUpResponse.user.id); +// +// JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", +// "http://localhost:3567/recipe/accountlinking/user/primary/check", params, 1000, 1000, null, +// WebserverAPI.getLatestCDIVersion().get(), ""); +// assertEquals(3, response.entrySet().size()); +// assertEquals("ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR", +// response.get("status").getAsString()); +// assertEquals(emailPasswordUser.id, response.get("primaryUserId").getAsString()); +// assertEquals("This user's email is already associated with another user ID", +// response.get("description").getAsString()); +// } +// +// process.kill(); +// assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); +// } +// +// @Test +// public void makingPrimaryUserFailsCauseAlreadyLinkedToAnotherAccount() throws Exception { +// String[] args = {"../"}; +// TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); +// FeatureFlagTestContent.getInstance(process.getProcess()) +// .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ +// EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); +// process.startProcess(); +// assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); +// +// AuthRecipeUserInfo emailPasswordUser1 = EmailPassword.signUp(process.getProcess(), "test@example.com", +// "pass1234"); +// AuthRecipeUserInfo emailPasswordUser2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", +// "pass1234"); +// +// AuthRecipe.createPrimaryUser(process.main, emailPasswordUser1.id); +// AuthRecipe.linkAccounts(process.main, emailPasswordUser2.id, emailPasswordUser1.id); +// +// { +// Map params = new HashMap<>(); +// params.put("recipeUserId", emailPasswordUser2.id); +// +// JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", +// "http://localhost:3567/recipe/accountlinking/user/primary/check", params, 1000, 1000, null, +// WebserverAPI.getLatestCDIVersion().get(), ""); +// assertEquals(3, response.entrySet().size()); +// assertEquals("RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR", +// response.get("status").getAsString()); +// assertEquals(emailPasswordUser1.id, response.get("primaryUserId").getAsString()); +// assertEquals("This user ID is already linked to another user ID", +// response.get("description").getAsString()); +// } +// +// process.kill(); +// assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); +// } +// +// @Test +// public void makePrimaryUserFailsCauseAnotherAccountWithSameEmailAlreadyAPrimaryUserWithUserIdMapping() +// throws Exception { +// String[] args = {"../"}; +// TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); +// FeatureFlagTestContent.getInstance(process.getProcess()) +// .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ +// EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); +// process.startProcess(); +// assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); +// +// AuthRecipeUserInfo emailPasswordUser = EmailPassword.signUp(process.getProcess(), "test@example.com", +// "pass1234"); +// UserIdMapping.createUserIdMapping(process.main, emailPasswordUser.id, "r1", null, false); +// +// AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.createPrimaryUser(process.main, emailPasswordUser.id); +// assert (!result.wasAlreadyAPrimaryUser); +// +// ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(process.main, "google", "user-google", +// "test@example.com"); +// +// { +// Map params = new HashMap<>(); +// params.put("recipeUserId", signInUpResponse.user.id); +// +// JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", +// "http://localhost:3567/recipe/accountlinking/user/primary/check", params, 1000, 1000, null, +// WebserverAPI.getLatestCDIVersion().get(), ""); +// assertEquals(3, response.entrySet().size()); +// assertEquals("ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR", +// response.get("status").getAsString()); +// assertEquals("r1", response.get("primaryUserId").getAsString()); +// assertEquals("This user's email is already associated with another user ID", +// response.get("description").getAsString()); +// } +// +// process.kill(); +// assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); +// } +// +// @Test +// public void makingPrimaryUserFailsCauseAlreadyLinkedToAnotherAccountWithUserIdMapping() throws Exception { +// String[] args = {"../"}; +// TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); +// FeatureFlagTestContent.getInstance(process.getProcess()) +// .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ +// EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); +// process.startProcess(); +// assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); +// +// AuthRecipeUserInfo emailPasswordUser1 = EmailPassword.signUp(process.getProcess(), "test@example.com", +// "pass1234"); +// UserIdMapping.createUserIdMapping(process.main, emailPasswordUser1.id, "r1", null, false); +// AuthRecipeUserInfo emailPasswordUser2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", +// "pass1234"); +// +// AuthRecipe.createPrimaryUser(process.main, emailPasswordUser1.id); +// AuthRecipe.linkAccounts(process.main, emailPasswordUser2.id, emailPasswordUser1.id); +// +// { +// Map params = new HashMap<>(); +// params.put("recipeUserId", emailPasswordUser2.id); +// +// JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", +// "http://localhost:3567/recipe/accountlinking/user/primary/check", params, 1000, 1000, null, +// WebserverAPI.getLatestCDIVersion().get(), ""); +// assertEquals(3, response.entrySet().size()); +// assertEquals("RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR", +// response.get("status").getAsString()); +// assertEquals("r1", response.get("primaryUserId").getAsString()); +// assertEquals("This user ID is already linked to another user ID", +// response.get("description").getAsString()); +// } +// +// process.kill(); +// assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); +// } + +} diff --git a/src/test/java/io/supertokens/test/accountlinking/api/CreatePrimaryUserAPITest.java b/src/test/java/io/supertokens/test/accountlinking/api/CreatePrimaryUserAPITest.java index 61a0ec8e7..ecc588146 100644 --- a/src/test/java/io/supertokens/test/accountlinking/api/CreatePrimaryUserAPITest.java +++ b/src/test/java/io/supertokens/test/accountlinking/api/CreatePrimaryUserAPITest.java @@ -22,8 +22,10 @@ import io.supertokens.emailpassword.EmailPassword; import io.supertokens.featureflag.EE_FEATURES; import io.supertokens.featureflag.FeatureFlagTestContent; +import io.supertokens.multitenancy.Multitenancy; import io.supertokens.pluginInterface.STORAGE_TYPE; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; +import io.supertokens.pluginInterface.multitenancy.*; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.TestingProcessManager; import io.supertokens.test.Utils; @@ -101,7 +103,7 @@ public void createReturnsSucceeds() throws Exception { assertEquals(lM.get("recipeUserId").getAsString(), user.id); assertEquals(lM.get("recipeId").getAsString(), "emailpassword"); assertEquals(lM.get("email").getAsString(), "test@example.com"); - assert (lM.entrySet().size() == 5); + assert (lM.entrySet().size() == 6); userObj = jsonUser; } @@ -168,7 +170,7 @@ public void createReturnsTrueWithUserIdMapping() throws Exception { assertEquals(lM.get("recipeUserId").getAsString(), "r1"); assertEquals(lM.get("recipeId").getAsString(), "emailpassword"); assertEquals(lM.get("email").getAsString(), "test@example.com"); - assert (lM.entrySet().size() == 5); + assert (lM.entrySet().size() == 6); userObj = jsonUser; } @@ -407,4 +409,96 @@ public void makingPrimaryUserFailsCauseAlreadyLinkedToAnotherAccountWithUserIdMa assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); } + @Test + public void createPrimaryUserInTenantWithAnotherStorage() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + JsonObject coreConfig = new JsonObject(); + StorageLayer.getStorage(new TenantIdentifier(null, null, null), process.getProcess()) + .modifyConfigToAddANewUserPoolForTesting(coreConfig, 2); + + TenantIdentifier tenantIdentifier = new TenantIdentifier(null, null, "t1"); + Multitenancy.addNewOrUpdateAppOrTenant( + process.getProcess(), + new TenantIdentifier(null, null, null), + new TenantConfig( + tenantIdentifier, + new EmailPasswordConfig(true), + new ThirdPartyConfig(true, null), + new PasswordlessConfig(true), + coreConfig + ) + ); + + AuthRecipeUserInfo user = EmailPassword.signUp( + tenantIdentifier.withStorage(StorageLayer.getStorage(tenantIdentifier, process.main)), + process.getProcess(), "test@example.com", "abcd1234"); + + JsonObject userObj; + { + JsonObject params = new JsonObject(); + params.addProperty("recipeUserId", user.id); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/primary", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assertEquals(3, response.entrySet().size()); + assertEquals("OK", response.get("status").getAsString()); + assertFalse(response.get("wasAlreadyAPrimaryUser").getAsBoolean()); + + // check user object + JsonObject jsonUser = response.get("user").getAsJsonObject(); + assert (jsonUser.get("id").getAsString().equals(user.id)); + assert (jsonUser.get("tenantIds").getAsJsonArray().size() == 1); + assert (jsonUser.get("tenantIds").getAsJsonArray().get(0).getAsString().equals("t1")); + assert (jsonUser.get("timeJoined").getAsLong() == user.timeJoined); + assert (jsonUser.get("isPrimaryUser").getAsBoolean()); + assert (jsonUser.get("emails").getAsJsonArray().size() == 1); + assert (jsonUser.get("emails").getAsJsonArray().get(0).getAsString().equals("test@example.com")); + assert (jsonUser.get("phoneNumbers").getAsJsonArray().size() == 0); + assert (jsonUser.get("thirdParty").getAsJsonArray().size() == 0); + assert (jsonUser.get("loginMethods").getAsJsonArray().size() == 1); + JsonObject lM = jsonUser.get("loginMethods").getAsJsonArray().get(0).getAsJsonObject(); + assertFalse(lM.get("verified").getAsBoolean()); + assert (lM.get("tenantIds").getAsJsonArray().size() == 1); + assert (lM.get("tenantIds").getAsJsonArray().get(0).getAsString().equals("t1")); + assertEquals(lM.get("timeJoined").getAsLong(), user.timeJoined); + assertEquals(lM.get("recipeUserId").getAsString(), user.id); + assertEquals(lM.get("recipeId").getAsString(), "emailpassword"); + assertEquals(lM.get("email").getAsString(), "test@example.com"); + assert (lM.entrySet().size() == 6); + userObj = jsonUser; + } + + AuthRecipe.createPrimaryUser(process.main, + tenantIdentifier.toAppIdentifier().withStorage(StorageLayer.getStorage(tenantIdentifier, process.main)), + user.id); + + { + JsonObject params = new JsonObject(); + params.addProperty("recipeUserId", user.id); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/primary", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assertEquals(3, response.entrySet().size()); + assertEquals("OK", response.get("status").getAsString()); + assertTrue(response.get("wasAlreadyAPrimaryUser").getAsBoolean()); + assertEquals(response.get("user"), userObj); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + } From 7a97193b290a79969a74a0a6e74faabdf964e611 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Wed, 2 Aug 2023 14:27:05 +0530 Subject: [PATCH 059/131] adds more tests --- .../api/CanLinkAccountsAPITest.java | 118 +++++++++++------- 1 file changed, 72 insertions(+), 46 deletions(-) diff --git a/src/test/java/io/supertokens/test/accountlinking/api/CanLinkAccountsAPITest.java b/src/test/java/io/supertokens/test/accountlinking/api/CanLinkAccountsAPITest.java index b5dcc26ab..0f9dd18c3 100644 --- a/src/test/java/io/supertokens/test/accountlinking/api/CanLinkAccountsAPITest.java +++ b/src/test/java/io/supertokens/test/accountlinking/api/CanLinkAccountsAPITest.java @@ -16,12 +16,31 @@ package io.supertokens.test.accountlinking.api; +import com.google.gson.JsonObject; +import io.supertokens.ProcessState; +import io.supertokens.authRecipe.AuthRecipe; +import io.supertokens.emailpassword.EmailPassword; +import io.supertokens.featureflag.EE_FEATURES; +import io.supertokens.featureflag.FeatureFlagTestContent; +import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; +import io.supertokens.storageLayer.StorageLayer; +import io.supertokens.test.TestingProcessManager; import io.supertokens.test.Utils; +import io.supertokens.test.httpRequest.HttpRequestForTesting; +import io.supertokens.useridmapping.UserIdMapping; +import io.supertokens.webserver.WebserverAPI; import org.junit.AfterClass; import org.junit.Before; import org.junit.Rule; +import org.junit.Test; import org.junit.rules.TestRule; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.*; + public class CanLinkAccountsAPITest { @Rule public TestRule watchman = Utils.getOnFailure(); @@ -36,52 +55,59 @@ public void beforeEach() { Utils.reset(); } -// @Test -// public void canCreateReturnsTrueWithUserIdMapping() throws Exception { -// String[] args = {"../"}; -// TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); -// FeatureFlagTestContent.getInstance(process.getProcess()) -// .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ -// EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); -// process.startProcess(); -// assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); -// -// if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { -// return; -// } -// -// AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "abcd1234"); -// UserIdMapping.createUserIdMapping(process.main, user.id, "r1", null, false); -// -// { -// Map params = new HashMap<>(); -// params.put("recipeUserId", "r1"); -// -// JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", -// "http://localhost:3567/recipe/accountlinking/user/primary/check", params, 1000, 1000, null, -// WebserverAPI.getLatestCDIVersion().get(), ""); -// assertEquals(2, response.entrySet().size()); -// assertEquals("OK", response.get("status").getAsString()); -// assertFalse(response.get("wasAlreadyAPrimaryUser").getAsBoolean()); -// } -// -// AuthRecipe.createPrimaryUser(process.main, user.id); -// -// { -// Map params = new HashMap<>(); -// params.put("recipeUserId", "r1"); -// -// JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", -// "http://localhost:3567/recipe/accountlinking/user/primary/check", params, 1000, 1000, null, -// WebserverAPI.getLatestCDIVersion().get(), ""); -// assertEquals(2, response.entrySet().size()); -// assertEquals("OK", response.get("status").getAsString()); -// assertTrue(response.get("wasAlreadyAPrimaryUser").getAsBoolean()); -// } -// -// process.kill(); -// assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); -// } + @Test + public void canCreateReturnsTrueWithUserIdMapping() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "abcd1234"); + UserIdMapping.createUserIdMapping(process.main, user.id, "r1", null, false); + + AuthRecipeUserInfo user2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "abcd1234"); + UserIdMapping.createUserIdMapping(process.main, user2.id, "r2", null, false); + + AuthRecipe.createPrimaryUser(process.main, user2.id); + + { + Map params = new HashMap<>(); + params.put("recipeUserId", "r1"); + params.put("primaryUserId", "r2"); + + JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/link/check", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assertEquals(2, response.entrySet().size()); + assertEquals("OK", response.get("status").getAsString()); + assertFalse(response.get("accountsAlreadyLinked").getAsBoolean()); + } + + AuthRecipe.linkAccounts(process.main, user.id, user2.id); + + { + Map params = new HashMap<>(); + params.put("recipeUserId", "r1"); + params.put("primaryUserId", "r2"); + + JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/link/check", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assertEquals(2, response.entrySet().size()); + assertEquals("OK", response.get("status").getAsString()); + assertTrue(response.get("accountsAlreadyLinked").getAsBoolean()); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } // // @Test // public void canCreatePrimaryUserBadInput() throws Exception { From 7e72d7617352c8a9e645893c223a82a48eaa096e Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Wed, 2 Aug 2023 15:13:42 +0530 Subject: [PATCH 060/131] more tests --- .../api/CanLinkAccountsAPITest.java | 196 +++++++++++++----- 1 file changed, 144 insertions(+), 52 deletions(-) diff --git a/src/test/java/io/supertokens/test/accountlinking/api/CanLinkAccountsAPITest.java b/src/test/java/io/supertokens/test/accountlinking/api/CanLinkAccountsAPITest.java index 0f9dd18c3..3ffe458ea 100644 --- a/src/test/java/io/supertokens/test/accountlinking/api/CanLinkAccountsAPITest.java +++ b/src/test/java/io/supertokens/test/accountlinking/api/CanLinkAccountsAPITest.java @@ -28,6 +28,7 @@ import io.supertokens.test.TestingProcessManager; import io.supertokens.test.Utils; import io.supertokens.test.httpRequest.HttpRequestForTesting; +import io.supertokens.test.httpRequest.HttpResponseException; import io.supertokens.useridmapping.UserIdMapping; import io.supertokens.webserver.WebserverAPI; import org.junit.AfterClass; @@ -56,7 +57,59 @@ public void beforeEach() { } @Test - public void canCreateReturnsTrueWithUserIdMapping() throws Exception { + public void canLinkReturnsTrue() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "abcd1234"); + + AuthRecipeUserInfo user2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "abcd1234"); + + AuthRecipe.createPrimaryUser(process.main, user2.id); + + { + Map params = new HashMap<>(); + params.put("recipeUserId", user.id); + params.put("primaryUserId", user2.id); + + JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/link/check", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assertEquals(2, response.entrySet().size()); + assertEquals("OK", response.get("status").getAsString()); + assertFalse(response.get("accountsAlreadyLinked").getAsBoolean()); + } + + AuthRecipe.linkAccounts(process.main, user.id, user2.id); + + { + Map params = new HashMap<>(); + params.put("recipeUserId", user.id); + params.put("primaryUserId", user2.id); + + JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/link/check", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assertEquals(2, response.entrySet().size()); + assertEquals("OK", response.get("status").getAsString()); + assertTrue(response.get("accountsAlreadyLinked").getAsBoolean()); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void canLinkReturnsTrueWithUserIdMapping() throws Exception { String[] args = {"../"}; TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); FeatureFlagTestContent.getInstance(process.getProcess()) @@ -108,57 +161,96 @@ public void canCreateReturnsTrueWithUserIdMapping() throws Exception { process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); } -// -// @Test -// public void canCreatePrimaryUserBadInput() throws Exception { -// String[] args = {"../"}; -// TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); -// FeatureFlagTestContent.getInstance(process.getProcess()) -// .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ -// EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); -// process.startProcess(); -// assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); -// -// if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { -// return; -// } -// -// { -// Map params = new HashMap<>(); -// -// try { -// HttpRequestForTesting.sendGETRequest(process.getProcess(), "", -// "http://localhost:3567/recipe/accountlinking/user/primary/check", params, 1000, 1000, null, -// WebserverAPI.getLatestCDIVersion().get(), ""); -// assert (false); -// } catch (HttpResponseException e) { -// assert (e.statusCode == 400); -// assert (e.getMessage() -// .equals("Http error. Status Code: 400. Message: Field name 'recipeUserId' is missing in GET -// " + -// "request")); -// } -// } -// -// { -// Map params = new HashMap<>(); -// params.put("recipeUserId", "random"); -// -// try { -// HttpRequestForTesting.sendGETRequest(process.getProcess(), "", -// "http://localhost:3567/recipe/accountlinking/user/primary/check", params, 1000, 1000, null, -// WebserverAPI.getLatestCDIVersion().get(), ""); -// assert (false); -// } catch (HttpResponseException e) { -// assert (e.statusCode == 400); -// assert (e.getMessage() -// .equals("Http error. Status Code: 400. Message: Unknown user ID provided")); -// } -// } -// -// process.kill(); -// assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); -// } + + @Test + public void canLinkUserBadInput() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + { + Map params = new HashMap<>(); + + try { + HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/link/check", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assert (false); + } catch (HttpResponseException e) { + assert (e.statusCode == 400); + assert (e.getMessage() + .equals("Http error. Status Code: 400. Message: Field name 'recipeUserId' is missing in GET " + + "request")); + } + } + + { + Map params = new HashMap<>(); + params.put("recipeUserId", "random"); + + try { + HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/link/check", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assert (false); + } catch (HttpResponseException e) { + assert (e.statusCode == 400); + assert (e.getMessage() + .equals("Http error. Status Code: 400. Message: Field name 'primaryUserId' is missing in GET " + + "request")); + } + } + + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "abcd1234"); + AuthRecipe.createPrimaryUser(process.main, user.id); + + AuthRecipeUserInfo user2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "abcd1234"); + + { + Map params = new HashMap<>(); + params.put("recipeUserId", user2.id); + params.put("primaryUserId", "random"); + + try { + HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/link/check", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assert (false); + } catch (HttpResponseException e) { + assert (e.statusCode == 400); + assert (e.getMessage() + .equals("Http error. Status Code: 400. Message: Unknown user ID provided")); + } + } + + { + Map params = new HashMap<>(); + params.put("recipeUserId", "random"); + params.put("primaryUserId", user.id); + + try { + HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/link/check", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assert (false); + } catch (HttpResponseException e) { + assert (e.statusCode == 400); + assert (e.getMessage() + .equals("Http error. Status Code: 400. Message: Unknown user ID provided")); + } + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } // // @Test // public void makePrimaryUserFailsCauseAnotherAccountWithSameEmailAlreadyAPrimaryUser() throws Exception { From aa7cb1561ca68dc1837e3c8aee55694ddf711c8f Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Wed, 2 Aug 2023 17:19:03 +0530 Subject: [PATCH 061/131] new tests --- .../api/CanLinkAccountsAPITest.java | 372 +++++++++++------- 1 file changed, 219 insertions(+), 153 deletions(-) diff --git a/src/test/java/io/supertokens/test/accountlinking/api/CanLinkAccountsAPITest.java b/src/test/java/io/supertokens/test/accountlinking/api/CanLinkAccountsAPITest.java index 3ffe458ea..42c1a6e27 100644 --- a/src/test/java/io/supertokens/test/accountlinking/api/CanLinkAccountsAPITest.java +++ b/src/test/java/io/supertokens/test/accountlinking/api/CanLinkAccountsAPITest.java @@ -29,6 +29,7 @@ import io.supertokens.test.Utils; import io.supertokens.test.httpRequest.HttpRequestForTesting; import io.supertokens.test.httpRequest.HttpResponseException; +import io.supertokens.thirdparty.ThirdParty; import io.supertokens.useridmapping.UserIdMapping; import io.supertokens.webserver.WebserverAPI; import org.junit.AfterClass; @@ -251,158 +252,223 @@ public void canLinkUserBadInput() throws Exception { process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); } -// -// @Test -// public void makePrimaryUserFailsCauseAnotherAccountWithSameEmailAlreadyAPrimaryUser() throws Exception { -// String[] args = {"../"}; -// TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); -// FeatureFlagTestContent.getInstance(process.getProcess()) -// .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ -// EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); -// process.startProcess(); -// assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); -// -// AuthRecipeUserInfo emailPasswordUser = EmailPassword.signUp(process.getProcess(), "test@example.com", -// "pass1234"); -// -// AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.createPrimaryUser(process.main, emailPasswordUser.id); -// assert (!result.wasAlreadyAPrimaryUser); -// -// ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(process.main, "google", "user-google", -// "test@example.com"); -// -// { -// Map params = new HashMap<>(); -// params.put("recipeUserId", signInUpResponse.user.id); -// -// JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", -// "http://localhost:3567/recipe/accountlinking/user/primary/check", params, 1000, 1000, null, -// WebserverAPI.getLatestCDIVersion().get(), ""); -// assertEquals(3, response.entrySet().size()); -// assertEquals("ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR", -// response.get("status").getAsString()); -// assertEquals(emailPasswordUser.id, response.get("primaryUserId").getAsString()); -// assertEquals("This user's email is already associated with another user ID", -// response.get("description").getAsString()); -// } -// -// process.kill(); -// assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); -// } -// -// @Test -// public void makingPrimaryUserFailsCauseAlreadyLinkedToAnotherAccount() throws Exception { -// String[] args = {"../"}; -// TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); -// FeatureFlagTestContent.getInstance(process.getProcess()) -// .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ -// EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); -// process.startProcess(); -// assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); -// -// AuthRecipeUserInfo emailPasswordUser1 = EmailPassword.signUp(process.getProcess(), "test@example.com", -// "pass1234"); -// AuthRecipeUserInfo emailPasswordUser2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", -// "pass1234"); -// -// AuthRecipe.createPrimaryUser(process.main, emailPasswordUser1.id); -// AuthRecipe.linkAccounts(process.main, emailPasswordUser2.id, emailPasswordUser1.id); -// -// { -// Map params = new HashMap<>(); -// params.put("recipeUserId", emailPasswordUser2.id); -// -// JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", -// "http://localhost:3567/recipe/accountlinking/user/primary/check", params, 1000, 1000, null, -// WebserverAPI.getLatestCDIVersion().get(), ""); -// assertEquals(3, response.entrySet().size()); -// assertEquals("RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR", -// response.get("status").getAsString()); -// assertEquals(emailPasswordUser1.id, response.get("primaryUserId").getAsString()); -// assertEquals("This user ID is already linked to another user ID", -// response.get("description").getAsString()); -// } -// -// process.kill(); -// assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); -// } -// -// @Test -// public void makePrimaryUserFailsCauseAnotherAccountWithSameEmailAlreadyAPrimaryUserWithUserIdMapping() -// throws Exception { -// String[] args = {"../"}; -// TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); -// FeatureFlagTestContent.getInstance(process.getProcess()) -// .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ -// EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); -// process.startProcess(); -// assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); -// -// AuthRecipeUserInfo emailPasswordUser = EmailPassword.signUp(process.getProcess(), "test@example.com", -// "pass1234"); -// UserIdMapping.createUserIdMapping(process.main, emailPasswordUser.id, "r1", null, false); -// -// AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.createPrimaryUser(process.main, emailPasswordUser.id); -// assert (!result.wasAlreadyAPrimaryUser); -// -// ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(process.main, "google", "user-google", -// "test@example.com"); -// -// { -// Map params = new HashMap<>(); -// params.put("recipeUserId", signInUpResponse.user.id); -// -// JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", -// "http://localhost:3567/recipe/accountlinking/user/primary/check", params, 1000, 1000, null, -// WebserverAPI.getLatestCDIVersion().get(), ""); -// assertEquals(3, response.entrySet().size()); -// assertEquals("ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR", -// response.get("status").getAsString()); -// assertEquals("r1", response.get("primaryUserId").getAsString()); -// assertEquals("This user's email is already associated with another user ID", -// response.get("description").getAsString()); -// } -// -// process.kill(); -// assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); -// } -// -// @Test -// public void makingPrimaryUserFailsCauseAlreadyLinkedToAnotherAccountWithUserIdMapping() throws Exception { -// String[] args = {"../"}; -// TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); -// FeatureFlagTestContent.getInstance(process.getProcess()) -// .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ -// EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); -// process.startProcess(); -// assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); -// -// AuthRecipeUserInfo emailPasswordUser1 = EmailPassword.signUp(process.getProcess(), "test@example.com", -// "pass1234"); -// UserIdMapping.createUserIdMapping(process.main, emailPasswordUser1.id, "r1", null, false); -// AuthRecipeUserInfo emailPasswordUser2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", -// "pass1234"); -// -// AuthRecipe.createPrimaryUser(process.main, emailPasswordUser1.id); -// AuthRecipe.linkAccounts(process.main, emailPasswordUser2.id, emailPasswordUser1.id); -// -// { -// Map params = new HashMap<>(); -// params.put("recipeUserId", emailPasswordUser2.id); -// -// JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", -// "http://localhost:3567/recipe/accountlinking/user/primary/check", params, 1000, 1000, null, -// WebserverAPI.getLatestCDIVersion().get(), ""); -// assertEquals(3, response.entrySet().size()); -// assertEquals("RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR", -// response.get("status").getAsString()); -// assertEquals("r1", response.get("primaryUserId").getAsString()); -// assertEquals("This user ID is already linked to another user ID", -// response.get("description").getAsString()); -// } -// -// process.kill(); -// assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); -// } + + @Test + public void linkingUsersFailsCauseAnotherAccountWithSameEmailAlreadyAPrimaryUser() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + AuthRecipeUserInfo emailPasswordUser = EmailPassword.signUp(process.getProcess(), "test@example.com", + "pass1234"); + + AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.createPrimaryUser(process.main, emailPasswordUser.id); + assert (!result.wasAlreadyAPrimaryUser); + + ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(process.main, "google", "user-google", + "test2@example.com"); + + AuthRecipe.createPrimaryUser(process.main, signInUpResponse.user.id); + + ThirdParty.SignInUpResponse signInUpResponse2 = ThirdParty.signInUp(process.main, "fb", "user-fb", + "test@example.com"); + + + { + Map params = new HashMap<>(); + params.put("primaryUserId", signInUpResponse.user.id); + params.put("recipeUserId", signInUpResponse2.user.id); + + JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/link/check", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assertEquals(3, response.entrySet().size()); + assertEquals("ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR", + response.get("status").getAsString()); + assertEquals(emailPasswordUser.id, response.get("primaryUserId").getAsString()); + assertEquals("This user's email is already associated with another user ID", + response.get("description").getAsString()); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void linkingUsersFailsCauseAnotherAccountWithSameEmailAlreadyAPrimaryUserWithUserIdMapping() + throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + AuthRecipeUserInfo emailPasswordUser = EmailPassword.signUp(process.getProcess(), "test@example.com", + "pass1234"); + UserIdMapping.createUserIdMapping(process.main, emailPasswordUser.id, "e1", null, false); + + AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.createPrimaryUser(process.main, emailPasswordUser.id); + assert (!result.wasAlreadyAPrimaryUser); + + ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(process.main, "google", "user-google", + "test2@example.com"); + UserIdMapping.createUserIdMapping(process.main, signInUpResponse.user.id, "e2", null, false); + + AuthRecipe.createPrimaryUser(process.main, signInUpResponse.user.id); + + ThirdParty.SignInUpResponse signInUpResponse2 = ThirdParty.signInUp(process.main, "fb", "user-fb", + "test@example.com"); + UserIdMapping.createUserIdMapping(process.main, signInUpResponse2.user.id, "e3", null, false); + + + { + Map params = new HashMap<>(); + params.put("primaryUserId", "e2"); + params.put("recipeUserId", "e3"); + + JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/link/check", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assertEquals(3, response.entrySet().size()); + assertEquals("ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR", + response.get("status").getAsString()); + assertEquals("e1", response.get("primaryUserId").getAsString()); + assertEquals("This user's email is already associated with another user ID", + response.get("description").getAsString()); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void linkingUserFailsCauseAlreadyLinkedToAnotherAccount() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + AuthRecipeUserInfo emailPasswordUser1 = EmailPassword.signUp(process.getProcess(), "test@example.com", + "pass1234"); + AuthRecipeUserInfo emailPasswordUser2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", + "pass1234"); + + AuthRecipe.createPrimaryUser(process.main, emailPasswordUser1.id); + AuthRecipe.linkAccounts(process.main, emailPasswordUser2.id, emailPasswordUser1.id); + + AuthRecipeUserInfo emailPasswordUser3 = EmailPassword.signUp(process.getProcess(), "test3@example.com", + "pass1234"); + + AuthRecipe.createPrimaryUser(process.main, emailPasswordUser3.id); + + { + Map params = new HashMap<>(); + params.put("recipeUserId", emailPasswordUser2.id); + params.put("primaryUserId", emailPasswordUser3.id); + + JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/link/check", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assertEquals(3, response.entrySet().size()); + assertEquals("RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR", + response.get("status").getAsString()); + assertEquals(emailPasswordUser1.id, response.get("primaryUserId").getAsString()); + assertEquals("The input recipe user ID is already linked to another user ID", + response.get("description").getAsString()); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + + @Test + public void makingPrimaryUserFailsCauseAlreadyLinkedToAnotherAccountWithUserIdMapping() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + AuthRecipeUserInfo emailPasswordUser1 = EmailPassword.signUp(process.getProcess(), "test@example.com", + "pass1234"); + UserIdMapping.createUserIdMapping(process.main, emailPasswordUser1.id, "r1", null, false); + AuthRecipeUserInfo emailPasswordUser2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", + "pass1234"); + UserIdMapping.createUserIdMapping(process.main, emailPasswordUser2.id, "r2", null, false); + + AuthRecipe.createPrimaryUser(process.main, emailPasswordUser1.id); + AuthRecipe.linkAccounts(process.main, emailPasswordUser2.id, emailPasswordUser1.id); + + AuthRecipeUserInfo emailPasswordUser3 = EmailPassword.signUp(process.getProcess(), "test3@example.com", + "pass1234"); + UserIdMapping.createUserIdMapping(process.main, emailPasswordUser3.id, "r3", null, false); + + AuthRecipe.createPrimaryUser(process.main, emailPasswordUser3.id); + + { + Map params = new HashMap<>(); + params.put("recipeUserId", "r2"); + params.put("primaryUserId", "r3"); + + JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/link/check", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assertEquals(3, response.entrySet().size()); + assertEquals("RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR", + response.get("status").getAsString()); + assertEquals("r1", response.get("primaryUserId").getAsString()); + assertEquals("The input recipe user ID is already linked to another user ID", + response.get("description").getAsString()); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void inputUserIsNotAPrimaryUserTest() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "abcd1234"); + + AuthRecipeUserInfo user2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "abcd1234"); + + { + Map params = new HashMap<>(); + params.put("recipeUserId", user.id); + params.put("primaryUserId", user2.id); + + JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/link/check", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assertEquals(1, response.entrySet().size()); + assertEquals("INPUT_USER_IS_NOT_A_PRIMARY_USER", response.get("status").getAsString()); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } } From 50b9a5353ba4d01238c25af6ed486e76d4de5326 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Wed, 2 Aug 2023 19:41:04 +0530 Subject: [PATCH 062/131] adds tests for linking accounts API --- .../io/supertokens/webserver/Webserver.java | 2 + .../api/accountlinking/LinkAccountsAPI.java | 145 +++++ .../api/CreatePrimaryUserAPITest.java | 31 ++ .../api/LinkAccountsAPITest.java | 506 ++++++++++++++++++ 4 files changed, 684 insertions(+) create mode 100644 src/main/java/io/supertokens/webserver/api/accountlinking/LinkAccountsAPI.java create mode 100644 src/test/java/io/supertokens/test/accountlinking/api/LinkAccountsAPITest.java diff --git a/src/main/java/io/supertokens/webserver/Webserver.java b/src/main/java/io/supertokens/webserver/Webserver.java index 112b73862..fe1515275 100644 --- a/src/main/java/io/supertokens/webserver/Webserver.java +++ b/src/main/java/io/supertokens/webserver/Webserver.java @@ -29,6 +29,7 @@ import io.supertokens.webserver.api.accountlinking.CanCreatePrimaryUserAPI; import io.supertokens.webserver.api.accountlinking.CanLinkAccountsAPI; import io.supertokens.webserver.api.accountlinking.CreatePrimaryUserAPI; +import io.supertokens.webserver.api.accountlinking.LinkAccountsAPI; import io.supertokens.webserver.api.core.*; import io.supertokens.webserver.api.dashboard.*; import io.supertokens.webserver.api.emailpassword.UserAPI; @@ -255,6 +256,7 @@ private void setupRoutes() { addAPI(new CanCreatePrimaryUserAPI(main)); addAPI(new CreatePrimaryUserAPI(main)); addAPI(new CanLinkAccountsAPI(main)); + addAPI(new LinkAccountsAPI(main)); StandardContext context = tomcatReference.getContext(); Tomcat tomcat = tomcatReference.getTomcat(); diff --git a/src/main/java/io/supertokens/webserver/api/accountlinking/LinkAccountsAPI.java b/src/main/java/io/supertokens/webserver/api/accountlinking/LinkAccountsAPI.java new file mode 100644 index 000000000..06cad0a9f --- /dev/null +++ b/src/main/java/io/supertokens/webserver/api/accountlinking/LinkAccountsAPI.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2023, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.webserver.api.accountlinking; + +import com.google.gson.JsonObject; +import io.supertokens.AppIdentifierWithStorageAndUserIdMapping; +import io.supertokens.Main; +import io.supertokens.authRecipe.AuthRecipe; +import io.supertokens.authRecipe.exception.AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException; +import io.supertokens.authRecipe.exception.InputUserIdIsNotAPrimaryUserException; +import io.supertokens.authRecipe.exception.RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException; +import io.supertokens.featureflag.exceptions.FeatureNotEnabledException; +import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; +import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; +import io.supertokens.useridmapping.UserIdMapping; +import io.supertokens.useridmapping.UserIdType; +import io.supertokens.webserver.InputParser; +import io.supertokens.webserver.WebserverAPI; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import java.io.IOException; + +public class LinkAccountsAPI extends WebserverAPI { + + public LinkAccountsAPI(Main main) { + super(main, ""); + } + + @Override + public String getPath() { + return "/recipe/accountlinking/user/link"; + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { + // API is app specific + JsonObject input = InputParser.parseJsonObjectOrThrowError(req); + String inputRecipeUserId = InputParser.parseStringOrThrowError(input, "recipeUserId", false); + String inputPrimaryUserId = InputParser.parseStringOrThrowError(input, "primaryUserId", false); + + AppIdentifierWithStorage primaryUserIdAppIdentifierWithStorage = null; + AppIdentifierWithStorage recipeUserIdAppIdentifierWithStorage = null; + try { + String recipeUserId = inputRecipeUserId; + { + AppIdentifierWithStorageAndUserIdMapping mappingAndStorage = + getAppIdentifierWithStorageAndUserIdMappingFromRequest( + req, inputRecipeUserId, UserIdType.ANY); + if (mappingAndStorage.userIdMapping != null) { + recipeUserId = mappingAndStorage.userIdMapping.superTokensUserId; + } + recipeUserIdAppIdentifierWithStorage = mappingAndStorage.appIdentifierWithStorage; + } + String primaryUserId = inputPrimaryUserId; + { + AppIdentifierWithStorageAndUserIdMapping mappingAndStorage = + getAppIdentifierWithStorageAndUserIdMappingFromRequest( + req, inputPrimaryUserId, UserIdType.ANY); + if (mappingAndStorage.userIdMapping != null) { + primaryUserId = mappingAndStorage.userIdMapping.superTokensUserId; + } + primaryUserIdAppIdentifierWithStorage = mappingAndStorage.appIdentifierWithStorage; + } + + // we do a check based on user pool ID and not instance reference checks cause the user + // could be in the same db, but their storage layers may just have different + if (!primaryUserIdAppIdentifierWithStorage.getStorage().getUserPoolId().equals( + recipeUserIdAppIdentifierWithStorage.getStorage().getUserPoolId())) { + throw new ServletException( + new BadRequestException( + "Cannot link users that are parts of different databases. Different pool IDs: " + + primaryUserIdAppIdentifierWithStorage.getStorage().getUserPoolId() + " AND " + + recipeUserIdAppIdentifierWithStorage.getStorage().getUserPoolId())); + } + + boolean alreadyLinked = AuthRecipe.linkAccounts(main, + primaryUserIdAppIdentifierWithStorage, + recipeUserId, primaryUserId); + JsonObject response = new JsonObject(); + response.addProperty("status", "OK"); + response.addProperty("accountsAlreadyLinked", alreadyLinked); + super.sendJsonResponse(200, response, resp); + } catch (StorageQueryException | TenantOrAppNotFoundException | FeatureNotEnabledException e) { + throw new ServletException(e); + } catch (UnknownUserIdException e) { + throw new ServletException(new BadRequestException("Unknown user ID provided")); + } catch (AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException e) { + try { + JsonObject response = new JsonObject(); + response.addProperty("status", "ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"); + io.supertokens.pluginInterface.useridmapping.UserIdMapping result = UserIdMapping.getUserIdMapping( + primaryUserIdAppIdentifierWithStorage, e.primaryUserId, + UserIdType.SUPERTOKENS); + if (result != null) { + response.addProperty("primaryUserId", result.externalUserId); + } else { + response.addProperty("primaryUserId", e.primaryUserId); + } + response.addProperty("description", e.getMessage()); + super.sendJsonResponse(200, response, resp); + } catch (StorageQueryException ex) { + throw new ServletException(ex); + } + } catch (RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException e) { + try { + JsonObject response = new JsonObject(); + response.addProperty("status", "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"); + io.supertokens.pluginInterface.useridmapping.UserIdMapping result = UserIdMapping.getUserIdMapping( + recipeUserIdAppIdentifierWithStorage, e.primaryUserId, + UserIdType.SUPERTOKENS); + if (result != null) { + response.addProperty("primaryUserId", result.externalUserId); + } else { + response.addProperty("primaryUserId", e.primaryUserId); + } + response.addProperty("description", e.getMessage()); + super.sendJsonResponse(200, response, resp); + } catch (StorageQueryException ex) { + throw new ServletException(ex); + } + } catch (InputUserIdIsNotAPrimaryUserException e) { + JsonObject response = new JsonObject(); + response.addProperty("status", "INPUT_USER_IS_NOT_A_PRIMARY_USER"); + super.sendJsonResponse(200, response, resp); + } + } +} diff --git a/src/test/java/io/supertokens/test/accountlinking/api/CreatePrimaryUserAPITest.java b/src/test/java/io/supertokens/test/accountlinking/api/CreatePrimaryUserAPITest.java index ecc588146..5b6643302 100644 --- a/src/test/java/io/supertokens/test/accountlinking/api/CreatePrimaryUserAPITest.java +++ b/src/test/java/io/supertokens/test/accountlinking/api/CreatePrimaryUserAPITest.java @@ -501,4 +501,35 @@ public void createPrimaryUserInTenantWithAnotherStorage() throws Exception { assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); } + @Test + public void createReturnsFailsWithoutFeatureEnabled() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "abcd1234"); + + JsonObject userObj; + { + JsonObject params = new JsonObject(); + params.addProperty("recipeUserId", user.id); + + try { + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/primary", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assert (false); + } catch (HttpResponseException e) { + assertEquals(402, e.statusCode); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + } } diff --git a/src/test/java/io/supertokens/test/accountlinking/api/LinkAccountsAPITest.java b/src/test/java/io/supertokens/test/accountlinking/api/LinkAccountsAPITest.java new file mode 100644 index 000000000..b28e2aaf6 --- /dev/null +++ b/src/test/java/io/supertokens/test/accountlinking/api/LinkAccountsAPITest.java @@ -0,0 +1,506 @@ +/* + * Copyright (c) 2023, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.test.accountlinking.api; + +import com.google.gson.JsonObject; +import io.supertokens.ProcessState; +import io.supertokens.authRecipe.AuthRecipe; +import io.supertokens.emailpassword.EmailPassword; +import io.supertokens.featureflag.EE_FEATURES; +import io.supertokens.featureflag.FeatureFlagTestContent; +import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; +import io.supertokens.storageLayer.StorageLayer; +import io.supertokens.test.TestingProcessManager; +import io.supertokens.test.Utils; +import io.supertokens.test.httpRequest.HttpRequestForTesting; +import io.supertokens.test.httpRequest.HttpResponseException; +import io.supertokens.thirdparty.ThirdParty; +import io.supertokens.useridmapping.UserIdMapping; +import io.supertokens.webserver.WebserverAPI; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; + +import static org.junit.Assert.*; + +public class LinkAccountsAPITest { + @Rule + public TestRule watchman = Utils.getOnFailure(); + + @AfterClass + public static void afterTesting() { + Utils.afterTesting(); + } + + @Before + public void beforeEach() { + Utils.reset(); + } + + @Test + public void linkReturnsTrue() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "abcd1234"); + + AuthRecipeUserInfo user2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "abcd1234"); + + AuthRecipe.createPrimaryUser(process.main, user2.id); + + { + JsonObject params = new JsonObject(); + params.addProperty("recipeUserId", user.id); + params.addProperty("primaryUserId", user2.id); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/link", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assertEquals(2, response.entrySet().size()); + assertEquals("OK", response.get("status").getAsString()); + assertFalse(response.get("accountsAlreadyLinked").getAsBoolean()); + } + + AuthRecipe.linkAccounts(process.main, user.id, user2.id); + + { + JsonObject params = new JsonObject(); + params.addProperty("recipeUserId", user.id); + params.addProperty("primaryUserId", user2.id); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/link", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assertEquals(2, response.entrySet().size()); + assertEquals("OK", response.get("status").getAsString()); + assertTrue(response.get("accountsAlreadyLinked").getAsBoolean()); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void canLinkReturnsTrueWithUserIdMapping() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "abcd1234"); + UserIdMapping.createUserIdMapping(process.main, user.id, "r1", null, false); + + AuthRecipeUserInfo user2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "abcd1234"); + UserIdMapping.createUserIdMapping(process.main, user2.id, "r2", null, false); + + AuthRecipe.createPrimaryUser(process.main, user2.id); + + { + JsonObject params = new JsonObject(); + params.addProperty("recipeUserId", "r1"); + params.addProperty("primaryUserId", "r2"); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/link", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assertEquals(2, response.entrySet().size()); + assertEquals("OK", response.get("status").getAsString()); + assertFalse(response.get("accountsAlreadyLinked").getAsBoolean()); + } + + AuthRecipe.linkAccounts(process.main, user.id, user2.id); + + { + JsonObject params = new JsonObject(); + params.addProperty("recipeUserId", "r1"); + params.addProperty("primaryUserId", "r2"); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/link", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assertEquals(2, response.entrySet().size()); + assertEquals("OK", response.get("status").getAsString()); + assertTrue(response.get("accountsAlreadyLinked").getAsBoolean()); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void canLinkUserBadInput() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + { + JsonObject params = new JsonObject(); + + try { + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/link", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assert (false); + } catch (HttpResponseException e) { + assert (e.statusCode == 400); + assert (e.getMessage() + .equals("Http error. Status Code: 400. Message: Field name 'recipeUserId' is invalid in JSON " + + "input")); + } + } + + { + JsonObject params = new JsonObject(); + params.addProperty("recipeUserId", "random"); + + try { + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/link", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assert (false); + } catch (HttpResponseException e) { + assert (e.statusCode == 400); + assert (e.getMessage() + .equals("Http error. Status Code: 400. Message: Field name 'primaryUserId' is invalid in JSON" + + " input")); + } + } + + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "abcd1234"); + AuthRecipe.createPrimaryUser(process.main, user.id); + + AuthRecipeUserInfo user2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "abcd1234"); + + { + JsonObject params = new JsonObject(); + params.addProperty("recipeUserId", user2.id); + params.addProperty("primaryUserId", "random"); + + try { + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/link", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assert (false); + } catch (HttpResponseException e) { + assert (e.statusCode == 400); + assert (e.getMessage() + .equals("Http error. Status Code: 400. Message: Unknown user ID provided")); + } + } + + { + JsonObject params = new JsonObject(); + params.addProperty("recipeUserId", "random"); + params.addProperty("primaryUserId", user.id); + + try { + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/link", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assert (false); + } catch (HttpResponseException e) { + assert (e.statusCode == 400); + assert (e.getMessage() + .equals("Http error. Status Code: 400. Message: Unknown user ID provided")); + } + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void linkingUsersFailsCauseAnotherAccountWithSameEmailAlreadyAPrimaryUser() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + AuthRecipeUserInfo emailPasswordUser = EmailPassword.signUp(process.getProcess(), "test@example.com", + "pass1234"); + + AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.createPrimaryUser(process.main, emailPasswordUser.id); + assert (!result.wasAlreadyAPrimaryUser); + + ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(process.main, "google", "user-google", + "test2@example.com"); + + AuthRecipe.createPrimaryUser(process.main, signInUpResponse.user.id); + + ThirdParty.SignInUpResponse signInUpResponse2 = ThirdParty.signInUp(process.main, "fb", "user-fb", + "test@example.com"); + + + { + JsonObject params = new JsonObject(); + params.addProperty("primaryUserId", signInUpResponse.user.id); + params.addProperty("recipeUserId", signInUpResponse2.user.id); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/link", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assertEquals(3, response.entrySet().size()); + assertEquals("ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR", + response.get("status").getAsString()); + assertEquals(emailPasswordUser.id, response.get("primaryUserId").getAsString()); + assertEquals("This user's email is already associated with another user ID", + response.get("description").getAsString()); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void linkingUsersFailsCauseAnotherAccountWithSameEmailAlreadyAPrimaryUserWithUserIdMapping() + throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + AuthRecipeUserInfo emailPasswordUser = EmailPassword.signUp(process.getProcess(), "test@example.com", + "pass1234"); + UserIdMapping.createUserIdMapping(process.main, emailPasswordUser.id, "e1", null, false); + + AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.createPrimaryUser(process.main, emailPasswordUser.id); + assert (!result.wasAlreadyAPrimaryUser); + + ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(process.main, "google", "user-google", + "test2@example.com"); + UserIdMapping.createUserIdMapping(process.main, signInUpResponse.user.id, "e2", null, false); + + AuthRecipe.createPrimaryUser(process.main, signInUpResponse.user.id); + + ThirdParty.SignInUpResponse signInUpResponse2 = ThirdParty.signInUp(process.main, "fb", "user-fb", + "test@example.com"); + UserIdMapping.createUserIdMapping(process.main, signInUpResponse2.user.id, "e3", null, false); + + + { + JsonObject params = new JsonObject(); + params.addProperty("primaryUserId", "e2"); + params.addProperty("recipeUserId", "e3"); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/link", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assertEquals(3, response.entrySet().size()); + assertEquals("ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR", + response.get("status").getAsString()); + assertEquals("e1", response.get("primaryUserId").getAsString()); + assertEquals("This user's email is already associated with another user ID", + response.get("description").getAsString()); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void linkingUserFailsCauseAlreadyLinkedToAnotherAccount() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + AuthRecipeUserInfo emailPasswordUser1 = EmailPassword.signUp(process.getProcess(), "test@example.com", + "pass1234"); + AuthRecipeUserInfo emailPasswordUser2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", + "pass1234"); + + AuthRecipe.createPrimaryUser(process.main, emailPasswordUser1.id); + AuthRecipe.linkAccounts(process.main, emailPasswordUser2.id, emailPasswordUser1.id); + + AuthRecipeUserInfo emailPasswordUser3 = EmailPassword.signUp(process.getProcess(), "test3@example.com", + "pass1234"); + + AuthRecipe.createPrimaryUser(process.main, emailPasswordUser3.id); + + { + JsonObject params = new JsonObject(); + params.addProperty("recipeUserId", emailPasswordUser2.id); + params.addProperty("primaryUserId", emailPasswordUser3.id); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/link", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assertEquals(3, response.entrySet().size()); + assertEquals("RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR", + response.get("status").getAsString()); + assertEquals(emailPasswordUser1.id, response.get("primaryUserId").getAsString()); + assertEquals("The input recipe user ID is already linked to another user ID", + response.get("description").getAsString()); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + + @Test + public void makingPrimaryUserFailsCauseAlreadyLinkedToAnotherAccountWithUserIdMapping() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + AuthRecipeUserInfo emailPasswordUser1 = EmailPassword.signUp(process.getProcess(), "test@example.com", + "pass1234"); + UserIdMapping.createUserIdMapping(process.main, emailPasswordUser1.id, "r1", null, false); + AuthRecipeUserInfo emailPasswordUser2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", + "pass1234"); + UserIdMapping.createUserIdMapping(process.main, emailPasswordUser2.id, "r2", null, false); + + AuthRecipe.createPrimaryUser(process.main, emailPasswordUser1.id); + AuthRecipe.linkAccounts(process.main, emailPasswordUser2.id, emailPasswordUser1.id); + + AuthRecipeUserInfo emailPasswordUser3 = EmailPassword.signUp(process.getProcess(), "test3@example.com", + "pass1234"); + UserIdMapping.createUserIdMapping(process.main, emailPasswordUser3.id, "r3", null, false); + + AuthRecipe.createPrimaryUser(process.main, emailPasswordUser3.id); + + { + JsonObject params = new JsonObject(); + params.addProperty("recipeUserId", "r2"); + params.addProperty("primaryUserId", "r3"); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/link", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assertEquals(3, response.entrySet().size()); + assertEquals("RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR", + response.get("status").getAsString()); + assertEquals("r1", response.get("primaryUserId").getAsString()); + assertEquals("The input recipe user ID is already linked to another user ID", + response.get("description").getAsString()); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void inputUserIsNotAPrimaryUserTest() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "abcd1234"); + + AuthRecipeUserInfo user2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "abcd1234"); + + { + JsonObject params = new JsonObject(); + params.addProperty("recipeUserId", user.id); + params.addProperty("primaryUserId", user2.id); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/link", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assertEquals(1, response.entrySet().size()); + assertEquals("INPUT_USER_IS_NOT_A_PRIMARY_USER", response.get("status").getAsString()); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void linkReturnsFailsWithoutFeatureEnabled() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "abcd1234"); + + AuthRecipeUserInfo user2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "abcd1234"); + + JsonObject userObj; + { + JsonObject params = new JsonObject(); + params.addProperty("recipeUserId", user.id); + params.addProperty("primaryUserId", user2.id); + + try { + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/link", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assert (false); + } catch (HttpResponseException e) { + assertEquals(402, e.statusCode); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + } + +} From de5588f4c7e696086851bfee61e233f29df30a9d Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Thu, 3 Aug 2023 12:00:18 +0530 Subject: [PATCH 063/131] adds unlink accounts API --- .../io/supertokens/webserver/Webserver.java | 6 +- .../api/accountlinking/UnlinkAccountAPI.java | 84 +++++++++++++++++++ 2 files changed, 86 insertions(+), 4 deletions(-) create mode 100644 src/main/java/io/supertokens/webserver/api/accountlinking/UnlinkAccountAPI.java diff --git a/src/main/java/io/supertokens/webserver/Webserver.java b/src/main/java/io/supertokens/webserver/Webserver.java index fe1515275..a3295cba5 100644 --- a/src/main/java/io/supertokens/webserver/Webserver.java +++ b/src/main/java/io/supertokens/webserver/Webserver.java @@ -26,10 +26,7 @@ import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; -import io.supertokens.webserver.api.accountlinking.CanCreatePrimaryUserAPI; -import io.supertokens.webserver.api.accountlinking.CanLinkAccountsAPI; -import io.supertokens.webserver.api.accountlinking.CreatePrimaryUserAPI; -import io.supertokens.webserver.api.accountlinking.LinkAccountsAPI; +import io.supertokens.webserver.api.accountlinking.*; import io.supertokens.webserver.api.core.*; import io.supertokens.webserver.api.dashboard.*; import io.supertokens.webserver.api.emailpassword.UserAPI; @@ -257,6 +254,7 @@ private void setupRoutes() { addAPI(new CreatePrimaryUserAPI(main)); addAPI(new CanLinkAccountsAPI(main)); addAPI(new LinkAccountsAPI(main)); + addAPI(new UnlinkAccountAPI(main)); StandardContext context = tomcatReference.getContext(); Tomcat tomcat = tomcatReference.getTomcat(); diff --git a/src/main/java/io/supertokens/webserver/api/accountlinking/UnlinkAccountAPI.java b/src/main/java/io/supertokens/webserver/api/accountlinking/UnlinkAccountAPI.java new file mode 100644 index 000000000..5176b5e48 --- /dev/null +++ b/src/main/java/io/supertokens/webserver/api/accountlinking/UnlinkAccountAPI.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2023, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.webserver.api.accountlinking; + +import com.google.gson.JsonObject; +import io.supertokens.AppIdentifierWithStorageAndUserIdMapping; +import io.supertokens.Main; +import io.supertokens.authRecipe.AuthRecipe; +import io.supertokens.authRecipe.exception.InputUserIdIsNotAPrimaryUserException; +import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; +import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; +import io.supertokens.useridmapping.UserIdType; +import io.supertokens.webserver.InputParser; +import io.supertokens.webserver.WebserverAPI; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import java.io.IOException; + +public class UnlinkAccountAPI extends WebserverAPI { + + public UnlinkAccountAPI(Main main) { + super(main, ""); + } + + @Override + public String getPath() { + return "/recipe/accountlinking/user/unlink"; + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { + // API is app specific + JsonObject input = InputParser.parseJsonObjectOrThrowError(req); + String inputRecipeUserId = InputParser.parseStringOrThrowError(input, "recipeUserId", false); + + AppIdentifierWithStorage appIdentifierWithStorage = null; + try { + String userId = inputRecipeUserId; + AppIdentifierWithStorageAndUserIdMapping mappingAndStorage = + getAppIdentifierWithStorageAndUserIdMappingFromRequest( + req, inputRecipeUserId, UserIdType.ANY); + if (mappingAndStorage.userIdMapping != null) { + userId = mappingAndStorage.userIdMapping.superTokensUserId; + } + appIdentifierWithStorage = mappingAndStorage.appIdentifierWithStorage; + + boolean wasDeleted = AuthRecipe.unlinkAccounts(main, appIdentifierWithStorage, + userId); + JsonObject response = new JsonObject(); + response.addProperty("status", "OK"); + response.addProperty("wasRecipeUserDeleted", wasDeleted); + response.addProperty("wasLinked", true); + super.sendJsonResponse(200, response, resp); + } catch (StorageQueryException | TenantOrAppNotFoundException e) { + throw new ServletException(e); + } catch (UnknownUserIdException e) { + throw new ServletException(new BadRequestException("Unknown user ID provided")); + } catch (InputUserIdIsNotAPrimaryUserException e) { + JsonObject response = new JsonObject(); + response.addProperty("status", "OK"); + response.addProperty("wasRecipeUserDeleted", false); + response.addProperty("wasLinked", false); + super.sendJsonResponse(200, response, resp); + } + } +} From a7776b96e5e8673247fbbf73fa3f1eb877cf8982 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Thu, 3 Aug 2023 12:59:23 +0530 Subject: [PATCH 064/131] adds more tests --- .../accountlinking/UnlinkAccountsTest.java | 2 +- .../api/UnlinkAccountsAPITest.java | 315 ++++++++++++++++++ 2 files changed, 316 insertions(+), 1 deletion(-) create mode 100644 src/test/java/io/supertokens/test/accountlinking/api/UnlinkAccountsAPITest.java diff --git a/src/test/java/io/supertokens/test/accountlinking/UnlinkAccountsTest.java b/src/test/java/io/supertokens/test/accountlinking/UnlinkAccountsTest.java index 163a8b7f7..47d750ae3 100644 --- a/src/test/java/io/supertokens/test/accountlinking/UnlinkAccountsTest.java +++ b/src/test/java/io/supertokens/test/accountlinking/UnlinkAccountsTest.java @@ -136,7 +136,7 @@ public void unlinkAccountWithoutPrimaryUserId() throws Exception { } @Test - public void unlinkAccountWithoutUnkownUserId() throws Exception { + public void unlinkAccountWithUnknownUserId() throws Exception { String[] args = {"../"}; TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); FeatureFlagTestContent.getInstance(process.getProcess()) diff --git a/src/test/java/io/supertokens/test/accountlinking/api/UnlinkAccountsAPITest.java b/src/test/java/io/supertokens/test/accountlinking/api/UnlinkAccountsAPITest.java new file mode 100644 index 000000000..647f48a24 --- /dev/null +++ b/src/test/java/io/supertokens/test/accountlinking/api/UnlinkAccountsAPITest.java @@ -0,0 +1,315 @@ +/* + * Copyright (c) 2023, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.test.accountlinking.api; + +import com.google.gson.JsonObject; +import io.supertokens.ProcessState; +import io.supertokens.authRecipe.AuthRecipe; +import io.supertokens.emailpassword.EmailPassword; +import io.supertokens.featureflag.EE_FEATURES; +import io.supertokens.featureflag.FeatureFlagTestContent; +import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; +import io.supertokens.session.Session; +import io.supertokens.storageLayer.StorageLayer; +import io.supertokens.test.TestingProcessManager; +import io.supertokens.test.Utils; +import io.supertokens.test.httpRequest.HttpRequestForTesting; +import io.supertokens.test.httpRequest.HttpResponseException; +import io.supertokens.useridmapping.UserIdMapping; +import io.supertokens.webserver.WebserverAPI; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; + +import static org.junit.Assert.*; + + +public class UnlinkAccountsAPITest { + @Rule + public TestRule watchman = Utils.getOnFailure(); + + @AfterClass + public static void afterTesting() { + Utils.afterTesting(); + } + + @Before + public void beforeEach() { + Utils.reset(); + } + + @Test + public void unlinkAccountSuccess() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "password"); + assert (!user.isPrimaryUser); + + Thread.sleep(50); + + AuthRecipeUserInfo user2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "password"); + assert (!user2.isPrimaryUser); + + AuthRecipe.createPrimaryUser(process.main, user.id); + + AuthRecipe.linkAccounts(process.main, user2.id, user.id); + + Session.createNewSession(process.main, user2.id, new JsonObject(), new JsonObject()); + String[] sessions = Session.getAllNonExpiredSessionHandlesForUser(process.main, user2.id); + assert (sessions.length == 1); + + { + JsonObject params = new JsonObject(); + params.addProperty("recipeUserId", user2.id); + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/unlink", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assertEquals(3, response.entrySet().size()); + assertEquals("OK", response.get("status").getAsString()); + assertFalse(response.get("wasRecipeUserDeleted").getAsBoolean()); + assertTrue(response.get("wasLinked").getAsBoolean()); + } + + + AuthRecipeUserInfo refetchUser2 = AuthRecipe.getUserById(process.main, user2.id); + assert (!refetchUser2.isPrimaryUser); + assert (refetchUser2.id.equals(user2.id)); + assert (refetchUser2.loginMethods.length == 1); + assert (refetchUser2.loginMethods[0].recipeUserId.equals(user2.id)); + + AuthRecipeUserInfo refetchUser = AuthRecipe.getUserById(process.main, user.id); + assert (!refetchUser2.equals(refetchUser)); + assert (refetchUser.isPrimaryUser); + assert (refetchUser.loginMethods.length == 1); + assert (refetchUser.loginMethods[0].recipeUserId.equals(user.id)); + + // cause linkAccounts revokes sessions for the recipe user ID + sessions = Session.getAllNonExpiredSessionHandlesForUser(process.main, user2.id); + assert (sessions.length == 0); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + + @Test + public void unlinkAccountBadRequest() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + try { + JsonObject params = new JsonObject(); + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/unlink", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assert (false); + } catch (HttpResponseException e) { + assert (e.statusCode == 400); + assert (e.getMessage() + .equals("Http error. Status Code: 400. Message: Field name 'recipeUserId' is invalid in JSON " + + "input")); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void unlinkAccountSuccessWithUserIdMapping() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "password"); + assert (!user.isPrimaryUser); + + Thread.sleep(50); + + AuthRecipeUserInfo user2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "password"); + assert (!user2.isPrimaryUser); + UserIdMapping.createUserIdMapping(process.main, user2.id, "e2", null, false); + + AuthRecipe.createPrimaryUser(process.main, user.id); + + AuthRecipe.linkAccounts(process.main, user2.id, user.id); + + { + JsonObject params = new JsonObject(); + params.addProperty("recipeUserId", "e2"); + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/unlink", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assertEquals(3, response.entrySet().size()); + assertEquals("OK", response.get("status").getAsString()); + assertFalse(response.get("wasRecipeUserDeleted").getAsBoolean()); + assertTrue(response.get("wasLinked").getAsBoolean()); + } + + + AuthRecipeUserInfo refetchUser2 = AuthRecipe.getUserById(process.main, user2.id); + assert (!refetchUser2.isPrimaryUser); + assert (refetchUser2.id.equals(user2.id)); + assert (refetchUser2.loginMethods.length == 1); + assert (refetchUser2.loginMethods[0].recipeUserId.equals(user2.id)); + + AuthRecipeUserInfo refetchUser = AuthRecipe.getUserById(process.main, user.id); + assert (!refetchUser2.equals(refetchUser)); + assert (refetchUser.isPrimaryUser); + assert (refetchUser.loginMethods.length == 1); + assert (refetchUser.loginMethods[0].recipeUserId.equals(user.id)); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void unlinkAccountWithoutPrimaryUserId() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "password"); + assert (!user.isPrimaryUser); + + { + JsonObject params = new JsonObject(); + params.addProperty("recipeUserId", user.id); + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/unlink", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assertEquals(3, response.entrySet().size()); + assertEquals("OK", response.get("status").getAsString()); + assertFalse(response.get("wasRecipeUserDeleted").getAsBoolean()); + assertFalse(response.get("wasLinked").getAsBoolean()); + } + + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void unlinkAccountWithUnknownUserId() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + try { + JsonObject params = new JsonObject(); + params.addProperty("recipeUserId", "random"); + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/unlink", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assert (false); + } catch (HttpResponseException e) { + assert (e.statusCode == 400); + assert (e.getMessage().equals("Http error. Status Code: 400. Message: Unknown user ID provided")); + } + + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void unlinkAccountSuccessButDeletesUser() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "password"); + assert (!user.isPrimaryUser); + + Thread.sleep(50); + + AuthRecipeUserInfo user2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "password"); + assert (!user2.isPrimaryUser); + + AuthRecipe.createPrimaryUser(process.main, user.id); + + AuthRecipe.linkAccounts(process.main, user2.id, user.id); + + { + JsonObject params = new JsonObject(); + params.addProperty("recipeUserId", user.id); + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/unlink", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assertEquals(3, response.entrySet().size()); + assertEquals("OK", response.get("status").getAsString()); + assertTrue(response.get("wasRecipeUserDeleted").getAsBoolean()); + assertTrue(response.get("wasLinked").getAsBoolean()); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } +} From 3057fcccfcad4cff08bd744a136f580eb11f094a Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Thu, 3 Aug 2023 13:46:12 +0530 Subject: [PATCH 065/131] more changes --- .../CanCreatePrimaryUserAPI.java | 3 +- .../accountlinking/CanLinkAccountsAPI.java | 3 +- .../accountlinking/CreatePrimaryUserAPI.java | 3 +- .../api/accountlinking/LinkAccountsAPI.java | 3 +- .../api/accountlinking/UnlinkAccountAPI.java | 3 +- .../test/authRecipe/GetUserByIdAPITest.java | 231 ++++++++++++++++++ 6 files changed, 241 insertions(+), 5 deletions(-) create mode 100644 src/test/java/io/supertokens/test/authRecipe/GetUserByIdAPITest.java diff --git a/src/main/java/io/supertokens/webserver/api/accountlinking/CanCreatePrimaryUserAPI.java b/src/main/java/io/supertokens/webserver/api/accountlinking/CanCreatePrimaryUserAPI.java index 327f9cc50..9457ddd0d 100644 --- a/src/main/java/io/supertokens/webserver/api/accountlinking/CanCreatePrimaryUserAPI.java +++ b/src/main/java/io/supertokens/webserver/api/accountlinking/CanCreatePrimaryUserAPI.java @@ -22,6 +22,7 @@ import io.supertokens.authRecipe.AuthRecipe; import io.supertokens.authRecipe.exception.AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException; import io.supertokens.authRecipe.exception.RecipeUserIdAlreadyLinkedWithPrimaryUserIdException; +import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; @@ -39,7 +40,7 @@ public class CanCreatePrimaryUserAPI extends WebserverAPI { public CanCreatePrimaryUserAPI(Main main) { - super(main, ""); + super(main, RECIPE_ID.ACCOUNT_LINKING.toString()); } @Override diff --git a/src/main/java/io/supertokens/webserver/api/accountlinking/CanLinkAccountsAPI.java b/src/main/java/io/supertokens/webserver/api/accountlinking/CanLinkAccountsAPI.java index f408cfca2..93d1714ba 100644 --- a/src/main/java/io/supertokens/webserver/api/accountlinking/CanLinkAccountsAPI.java +++ b/src/main/java/io/supertokens/webserver/api/accountlinking/CanLinkAccountsAPI.java @@ -23,6 +23,7 @@ import io.supertokens.authRecipe.exception.AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException; import io.supertokens.authRecipe.exception.InputUserIdIsNotAPrimaryUserException; import io.supertokens.authRecipe.exception.RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException; +import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; @@ -40,7 +41,7 @@ public class CanLinkAccountsAPI extends WebserverAPI { public CanLinkAccountsAPI(Main main) { - super(main, ""); + super(main, RECIPE_ID.ACCOUNT_LINKING.toString()); } @Override diff --git a/src/main/java/io/supertokens/webserver/api/accountlinking/CreatePrimaryUserAPI.java b/src/main/java/io/supertokens/webserver/api/accountlinking/CreatePrimaryUserAPI.java index 0eb764238..8573650b7 100644 --- a/src/main/java/io/supertokens/webserver/api/accountlinking/CreatePrimaryUserAPI.java +++ b/src/main/java/io/supertokens/webserver/api/accountlinking/CreatePrimaryUserAPI.java @@ -23,6 +23,7 @@ import io.supertokens.authRecipe.exception.AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException; import io.supertokens.authRecipe.exception.RecipeUserIdAlreadyLinkedWithPrimaryUserIdException; import io.supertokens.featureflag.exceptions.FeatureNotEnabledException; +import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; @@ -40,7 +41,7 @@ public class CreatePrimaryUserAPI extends WebserverAPI { public CreatePrimaryUserAPI(Main main) { - super(main, ""); + super(main, RECIPE_ID.ACCOUNT_LINKING.toString()); } @Override diff --git a/src/main/java/io/supertokens/webserver/api/accountlinking/LinkAccountsAPI.java b/src/main/java/io/supertokens/webserver/api/accountlinking/LinkAccountsAPI.java index 06cad0a9f..279b5d189 100644 --- a/src/main/java/io/supertokens/webserver/api/accountlinking/LinkAccountsAPI.java +++ b/src/main/java/io/supertokens/webserver/api/accountlinking/LinkAccountsAPI.java @@ -24,6 +24,7 @@ import io.supertokens.authRecipe.exception.InputUserIdIsNotAPrimaryUserException; import io.supertokens.authRecipe.exception.RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException; import io.supertokens.featureflag.exceptions.FeatureNotEnabledException; +import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; @@ -41,7 +42,7 @@ public class LinkAccountsAPI extends WebserverAPI { public LinkAccountsAPI(Main main) { - super(main, ""); + super(main, RECIPE_ID.ACCOUNT_LINKING.toString()); } @Override diff --git a/src/main/java/io/supertokens/webserver/api/accountlinking/UnlinkAccountAPI.java b/src/main/java/io/supertokens/webserver/api/accountlinking/UnlinkAccountAPI.java index 5176b5e48..3abed0328 100644 --- a/src/main/java/io/supertokens/webserver/api/accountlinking/UnlinkAccountAPI.java +++ b/src/main/java/io/supertokens/webserver/api/accountlinking/UnlinkAccountAPI.java @@ -21,6 +21,7 @@ import io.supertokens.Main; import io.supertokens.authRecipe.AuthRecipe; import io.supertokens.authRecipe.exception.InputUserIdIsNotAPrimaryUserException; +import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; @@ -37,7 +38,7 @@ public class UnlinkAccountAPI extends WebserverAPI { public UnlinkAccountAPI(Main main) { - super(main, ""); + super(main, RECIPE_ID.ACCOUNT_LINKING.toString()); } @Override diff --git a/src/test/java/io/supertokens/test/authRecipe/GetUserByIdAPITest.java b/src/test/java/io/supertokens/test/authRecipe/GetUserByIdAPITest.java new file mode 100644 index 000000000..133ab4b6d --- /dev/null +++ b/src/test/java/io/supertokens/test/authRecipe/GetUserByIdAPITest.java @@ -0,0 +1,231 @@ +/* + * Copyright (c) 2023, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.test.authRecipe; + +import com.google.gson.JsonObject; +import io.supertokens.ProcessState; +import io.supertokens.authRecipe.AuthRecipe; +import io.supertokens.emailpassword.EmailPassword; +import io.supertokens.featureflag.EE_FEATURES; +import io.supertokens.featureflag.FeatureFlagTestContent; +import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; +import io.supertokens.storageLayer.StorageLayer; +import io.supertokens.test.TestingProcessManager; +import io.supertokens.test.Utils; +import io.supertokens.test.httpRequest.HttpRequestForTesting; +import io.supertokens.useridmapping.UserIdMapping; +import io.supertokens.webserver.WebserverAPI; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.*; + +public class GetUserByIdAPITest { + @Rule + public TestRule watchman = Utils.getOnFailure(); + + @AfterClass + public static void afterTesting() { + Utils.afterTesting(); + } + + @Before + public void beforeEach() { + Utils.reset(); + } + + @Test + public void getUserSuccess() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "password"); + assert (!user.isPrimaryUser); + + Thread.sleep(50); + + AuthRecipeUserInfo user2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "password"); + assert (!user2.isPrimaryUser); + + AuthRecipe.createPrimaryUser(process.main, user.id); + + AuthRecipe.linkAccounts(process.main, user2.id, user.id); + + { + Map params = new HashMap<>(); + params.put("userId", user2.id); + JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/user/id", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assertEquals(2, response.entrySet().size()); + assertEquals("OK", response.get("status").getAsString()); + JsonObject jsonUser = response.get("user").getAsJsonObject(); + assert (jsonUser.get("id").getAsString().equals(user.id)); + assert (jsonUser.get("timeJoined").getAsLong() == user.timeJoined); + assert (jsonUser.get("isPrimaryUser").getAsBoolean()); + assert (jsonUser.get("emails").getAsJsonArray().size() == 2); + assert (jsonUser.get("emails").getAsJsonArray().get(0).getAsString().equals("test@example.com") || + jsonUser.get("emails").getAsJsonArray().get(0).getAsString().equals("test2@example.com")); + assert (jsonUser.get("emails").getAsJsonArray().get(1).getAsString().equals("test@example.com") || + jsonUser.get("emails").getAsJsonArray().get(1).getAsString().equals("test2@example.com")); + assert (!jsonUser.get("emails").getAsJsonArray().get(1).getAsString() + .equals(jsonUser.get("emails").getAsJsonArray().get(0).getAsString())); + assert (jsonUser.get("phoneNumbers").getAsJsonArray().size() == 0); + assert (jsonUser.get("thirdParty").getAsJsonArray().size() == 0); + assert (jsonUser.get("loginMethods").getAsJsonArray().size() == 2); + { + JsonObject lM = jsonUser.get("loginMethods").getAsJsonArray().get(0).getAsJsonObject(); + assertFalse(lM.get("verified").getAsBoolean()); + assertEquals(lM.get("timeJoined").getAsLong(), user.timeJoined); + assertEquals(lM.get("recipeUserId").getAsString(), user.id); + assertEquals(lM.get("recipeId").getAsString(), "emailpassword"); + assertEquals(lM.get("email").getAsString(), "test@example.com"); + assert (lM.entrySet().size() == 6); + } + { + JsonObject lM = jsonUser.get("loginMethods").getAsJsonArray().get(1).getAsJsonObject(); + assertFalse(lM.get("verified").getAsBoolean()); + assertEquals(lM.get("timeJoined").getAsLong(), user2.timeJoined); + assertEquals(lM.get("recipeUserId").getAsString(), user2.id); + assertEquals(lM.get("recipeId").getAsString(), "emailpassword"); + assertEquals(lM.get("email").getAsString(), "test2@example.com"); + assert (lM.entrySet().size() == 6); + } + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void getUserSuccessWithUserIdMapping() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "password"); + assert (!user.isPrimaryUser); + UserIdMapping.createUserIdMapping(process.main, user.id, "e1", null, false); + + Thread.sleep(50); + + AuthRecipeUserInfo user2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "password"); + assert (!user2.isPrimaryUser); + UserIdMapping.createUserIdMapping(process.main, user2.id, "e2", null, false); + + AuthRecipe.createPrimaryUser(process.main, user.id); + + AuthRecipe.linkAccounts(process.main, user2.id, user.id); + + { + Map params = new HashMap<>(); + params.put("userId", "e2"); + JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/user/id", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assertEquals(2, response.entrySet().size()); + assertEquals("OK", response.get("status").getAsString()); + JsonObject jsonUser = response.get("user").getAsJsonObject(); + assert (jsonUser.get("id").getAsString().equals("e1")); + assert (jsonUser.get("timeJoined").getAsLong() == user.timeJoined); + assert (jsonUser.get("isPrimaryUser").getAsBoolean()); + assert (jsonUser.get("emails").getAsJsonArray().size() == 2); + assert (jsonUser.get("emails").getAsJsonArray().get(0).getAsString().equals("test@example.com") || + jsonUser.get("emails").getAsJsonArray().get(0).getAsString().equals("test2@example.com")); + assert (jsonUser.get("emails").getAsJsonArray().get(1).getAsString().equals("test@example.com") || + jsonUser.get("emails").getAsJsonArray().get(1).getAsString().equals("test2@example.com")); + assert (!jsonUser.get("emails").getAsJsonArray().get(1).getAsString() + .equals(jsonUser.get("emails").getAsJsonArray().get(0).getAsString())); + assert (jsonUser.get("phoneNumbers").getAsJsonArray().size() == 0); + assert (jsonUser.get("thirdParty").getAsJsonArray().size() == 0); + assert (jsonUser.get("loginMethods").getAsJsonArray().size() == 2); + { + JsonObject lM = jsonUser.get("loginMethods").getAsJsonArray().get(0).getAsJsonObject(); + assertFalse(lM.get("verified").getAsBoolean()); + assertEquals(lM.get("timeJoined").getAsLong(), user.timeJoined); + assertEquals(lM.get("recipeUserId").getAsString(), "e1"); + assertEquals(lM.get("recipeId").getAsString(), "emailpassword"); + assertEquals(lM.get("email").getAsString(), "test@example.com"); + assert (lM.entrySet().size() == 6); + } + { + JsonObject lM = jsonUser.get("loginMethods").getAsJsonArray().get(1).getAsJsonObject(); + assertFalse(lM.get("verified").getAsBoolean()); + assertEquals(lM.get("timeJoined").getAsLong(), user2.timeJoined); + assertEquals(lM.get("recipeUserId").getAsString(), user2.id); + assertEquals(lM.get("recipeId").getAsString(), "emailpassword"); + assertEquals(lM.get("email").getAsString(), "test2@example.com"); + assert (lM.entrySet().size() == 6); + } + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void getUnknownUser() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + { + Map params = new HashMap<>(); + params.put("userId", "random"); + JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/user/id", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assertEquals(1, response.entrySet().size()); + assertEquals("UNKNOWN_USER_ID_ERROR", response.get("status").getAsString()); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } +} From b5126b051c54e4480b1bc396e596097682f5c96e Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Thu, 3 Aug 2023 17:14:24 +0530 Subject: [PATCH 066/131] changes for password reset flow --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- CHANGELOG.md | 6 + .../emailpassword/EmailPassword.java | 47 ++++++- .../queries/EmailPasswordQueries.java | 2 +- .../GeneratePasswordResetTokenAPI.java | 18 ++- .../api/emailpassword/ResetPasswordAPI.java | 11 +- ...ExpiredPasswordResetTokensCronjobTest.java | 8 +- .../test/emailpassword/EmailPasswordTest.java | 120 ++++++++++++++++-- .../emailpassword/PasswordHashingTest.java | 4 +- .../test/multitenant/TestAppData.java | 2 +- 10 files changed, 188 insertions(+), 32 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index bf6de6dfb..94c0f5211 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -34,7 +34,7 @@ highlighting the necessary changes) - To know which one it is, run find the latest released tag (`git tag`) in the format `vX.Y.Z`, and then find the latest branch (`git branch --all`) whose `X.Y` is greater than the latest released tag. - If no such branch exists, then create one from the latest released branch. - +- [ ] If added a foreign key constraint on `app_id_to_user_id` table, make sure to delete from this table when deleting the user as well if `deleteUserIdMappingToo` is false. ## Remaining TODOs for this PR - [ ] Item1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 714986c46..b12a1e94a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - Added a two new columns in `all_auth_recipe_users`: - `primary_or_recipe_user_id` (default value is equal to `user_id` column) - `is_linked_or_is_a_primary_user` (default value is false) +- Dropped existing fkey constraint on `emailpassword_pswd_reset_tokens`, and added a new fkey constraint on `app_id_to_user_id` table. +- Added new column in `emailpassword_pswd_reset_tokens` to store email ### DB migration: @@ -38,6 +40,10 @@ CREATE INDEX all_auth_recipe_users_pagination_index ON all_auth_recipe_users (ti DESC, app_id DESC); CREATE INDEX all_auth_recipe_users_primary_user_id_index ON all_auth_recipe_users (app_id, primary_or_recipe_user_id); CREATE INDEX all_auth_recipe_users_primary_user_id_and_tenant_id_index ON all_auth_recipe_users (app_id, tenant_id, primary_or_recipe_user_id); + +ALTER TABLE emailpassword_pswd_reset_tokens DROP CONSTRAINT IF EXISTS emailpassword_pswd_reset_tokens_user_id_fkey; +ALTER TABLE emailpassword_pswd_reset_tokens ADD CONSTRAINT emailpassword_pswd_reset_tokens_user_id_fkey FOREIGN KEY (app_id, user_id) REFERENCES app_id_to_user_id (app_id, user_id) ON DELETE CASCADE; +ALTER TABLE emailpassword_pswd_reset_tokens ADD COLUMN email VARCHAR(256); ``` ## [6.0.8] - 2023-08-01 diff --git a/src/main/java/io/supertokens/emailpassword/EmailPassword.java b/src/main/java/io/supertokens/emailpassword/EmailPassword.java index 0156cbeaf..f245b9a6d 100644 --- a/src/main/java/io/supertokens/emailpassword/EmailPassword.java +++ b/src/main/java/io/supertokens/emailpassword/EmailPassword.java @@ -17,6 +17,7 @@ package io.supertokens.emailpassword; import io.supertokens.Main; +import io.supertokens.authRecipe.AuthRecipe; import io.supertokens.config.Config; import io.supertokens.config.CoreConfig; import io.supertokens.emailpassword.exceptions.ResetPasswordInvalidTokenException; @@ -44,6 +45,7 @@ import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.utils.Utils; +import io.supertokens.webserver.WebserverAPI; import org.jetbrains.annotations.TestOnly; import javax.annotation.Nonnull; @@ -276,20 +278,55 @@ public static AuthRecipeUserInfo signIn(TenantIdentifierWithStorage tenantIdenti } @TestOnly - public static String generatePasswordResetToken(Main main, String userId) + public static String generatePasswordResetTokenBeforeCdi4_0(Main main, String userId) throws InvalidKeySpecException, NoSuchAlgorithmException, StorageQueryException, UnknownUserIdException { try { Storage storage = StorageLayer.getStorage(main); - return generatePasswordResetToken( + return generatePasswordResetTokenBeforeCdi4_0( new TenantIdentifierWithStorage(null, null, null, storage), main, userId); + } catch (TenantOrAppNotFoundException | BadPermissionException | WebserverAPI.BadRequestException e) { + throw new IllegalStateException(e); + } + } + + @TestOnly + public static String generatePasswordResetToken(Main main, String userId, String email) + throws InvalidKeySpecException, NoSuchAlgorithmException, StorageQueryException, UnknownUserIdException { + try { + Storage storage = StorageLayer.getStorage(main); + return generatePasswordResetToken( + new TenantIdentifierWithStorage(null, null, null, storage), + main, userId, email); } catch (TenantOrAppNotFoundException | BadPermissionException e) { throw new IllegalStateException(e); } } + public static String generatePasswordResetTokenBeforeCdi4_0(TenantIdentifierWithStorage tenantIdentifierWithStorage, + Main main, + String userId) + throws InvalidKeySpecException, NoSuchAlgorithmException, StorageQueryException, UnknownUserIdException, + TenantOrAppNotFoundException, BadPermissionException, WebserverAPI.BadRequestException { + AppIdentifierWithStorage appIdentifierWithStorage = + tenantIdentifierWithStorage.toAppIdentifierWithStorage(); + AuthRecipeUserInfo user = AuthRecipe.getUserById(appIdentifierWithStorage, userId); + if (user == null) { + throw new UnknownUserIdException(); + } + if (user.loginMethods.length > 1) { + throw new WebserverAPI.BadRequestException("Please use CDI version >= 4.0"); + } + if (user.loginMethods[0].email == null || + user.loginMethods[0].recipeId != RECIPE_ID.EMAIL_PASSWORD) { + // this used to be the behaviour of the older CDI version and it was enforced via a fkey constraint + throw new UnknownUserIdException(); + } + return generatePasswordResetToken(tenantIdentifierWithStorage, main, userId, user.loginMethods[0].email); + } + public static String generatePasswordResetToken(TenantIdentifierWithStorage tenantIdentifierWithStorage, Main main, - String userId) + String userId, String email) throws InvalidKeySpecException, NoSuchAlgorithmException, StorageQueryException, UnknownUserIdException, TenantOrAppNotFoundException, BadPermissionException { @@ -326,7 +363,7 @@ public static String generatePasswordResetToken(TenantIdentifierWithStorage tena tenantIdentifierWithStorage.getEmailPasswordStorage().addPasswordResetToken( tenantIdentifierWithStorage.toAppIdentifier(), new PasswordResetTokenInfo(userId, hashedToken, System.currentTimeMillis() + - getPasswordResetTokenLifetime(tenantIdentifierWithStorage, main))); + getPasswordResetTokenLifetime(tenantIdentifierWithStorage, main), email)); return token; } catch (DuplicatePasswordResetTokenException ignored) { } @@ -334,6 +371,7 @@ public static String generatePasswordResetToken(TenantIdentifierWithStorage tena } @TestOnly + @Deprecated public static String resetPassword(Main main, String token, String password) throws ResetPasswordInvalidTokenException, NoSuchAlgorithmException, StorageQueryException, @@ -347,6 +385,7 @@ public static String resetPassword(Main main, String token, } } + @Deprecated public static String resetPassword(TenantIdentifierWithStorage tenantIdentifierWithStorage, Main main, String token, String password) throws ResetPasswordInvalidTokenException, NoSuchAlgorithmException, StorageQueryException, diff --git a/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java index 20f973155..19eb0821e 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java @@ -573,7 +573,7 @@ private static PasswordResetRowMapper getInstance() { public PasswordResetTokenInfo map(ResultSet result) throws StorageQueryException { try { return new PasswordResetTokenInfo(result.getString("user_id"), result.getString("token"), - result.getLong("token_expiry")); + result.getLong("token_expiry"), result.getString("email")); } catch (Exception e) { throw new StorageQueryException(e); } diff --git a/src/main/java/io/supertokens/webserver/api/emailpassword/GeneratePasswordResetTokenAPI.java b/src/main/java/io/supertokens/webserver/api/emailpassword/GeneratePasswordResetTokenAPI.java index b5d97500e..24f55a679 100644 --- a/src/main/java/io/supertokens/webserver/api/emailpassword/GeneratePasswordResetTokenAPI.java +++ b/src/main/java/io/supertokens/webserver/api/emailpassword/GeneratePasswordResetTokenAPI.java @@ -18,6 +18,7 @@ import com.google.gson.JsonObject; import io.supertokens.Main; +import io.supertokens.TenantIdentifierWithStorageAndUserIdMapping; import io.supertokens.emailpassword.EmailPassword; import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.output.Logging; @@ -25,10 +26,9 @@ import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; -import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; -import io.supertokens.TenantIdentifierWithStorageAndUserIdMapping; import io.supertokens.useridmapping.UserIdType; +import io.supertokens.utils.SemVer; import io.supertokens.utils.Utils; import io.supertokens.webserver.InputParser; import io.supertokens.webserver.WebserverAPI; @@ -58,7 +58,6 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I // API is tenant specific JsonObject input = InputParser.parseJsonObjectOrThrowError(req); String userId = InputParser.parseStringOrThrowError(input, "userId", false); - assert userId != null; // logic according to https://github.com/supertokens/supertokens-core/issues/106 TenantIdentifier tenantIdentifier = null; @@ -76,7 +75,15 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I userId = tenantIdentifierStorageAndMapping.userIdMapping.superTokensUserId; } - String token = EmailPassword.generatePasswordResetToken(tenantIdentifierStorageAndMapping.tenantIdentifierWithStorage, super.main, userId); + String token; + if (getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v4_0)) { + String email = InputParser.parseStringOrThrowError(input, "email", false); + token = EmailPassword.generatePasswordResetToken( + tenantIdentifierStorageAndMapping.tenantIdentifierWithStorage, super.main, userId, email); + } else { + token = EmailPassword.generatePasswordResetTokenBeforeCdi4_0( + tenantIdentifierStorageAndMapping.tenantIdentifierWithStorage, super.main, userId); + } JsonObject result = new JsonObject(); result.addProperty("status", "OK"); @@ -89,7 +96,8 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I result.addProperty("status", "UNKNOWN_USER_ID_ERROR"); super.sendJsonResponse(200, result, resp); - } catch (StorageQueryException | BadPermissionException | NoSuchAlgorithmException | InvalidKeySpecException | TenantOrAppNotFoundException e) { + } catch (StorageQueryException | BadPermissionException | NoSuchAlgorithmException | InvalidKeySpecException | + TenantOrAppNotFoundException | BadRequestException e) { throw new ServletException(e); } diff --git a/src/main/java/io/supertokens/webserver/api/emailpassword/ResetPasswordAPI.java b/src/main/java/io/supertokens/webserver/api/emailpassword/ResetPasswordAPI.java index a46b58905..05d354676 100644 --- a/src/main/java/io/supertokens/webserver/api/emailpassword/ResetPasswordAPI.java +++ b/src/main/java/io/supertokens/webserver/api/emailpassword/ResetPasswordAPI.java @@ -24,7 +24,6 @@ import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; -import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.useridmapping.UserIdMapping; @@ -40,6 +39,7 @@ import java.io.IOException; import java.security.NoSuchAlgorithmException; +@Deprecated public class ResetPasswordAPI extends WebserverAPI { private static final long serialVersionUID = -7529428297450682549L; @@ -94,8 +94,10 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I JsonObject result = new JsonObject(); result.addProperty("status", "OK"); - if (!(super.getVersionFromRequest(req).equals(SemVer.v2_7) || super.getVersionFromRequest(req).equals(SemVer.v2_8) - || super.getVersionFromRequest(req).equals(SemVer.v2_9) || super.getVersionFromRequest(req).equals(SemVer.v2_10) + if (!(super.getVersionFromRequest(req).equals(SemVer.v2_7) || + super.getVersionFromRequest(req).equals(SemVer.v2_8) + || super.getVersionFromRequest(req).equals(SemVer.v2_9) || + super.getVersionFromRequest(req).equals(SemVer.v2_10) || super.getVersionFromRequest(req).equals(SemVer.v2_11))) { // >= 2.12 result.addProperty("userId", userId); @@ -109,7 +111,8 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I result.addProperty("status", "RESET_PASSWORD_INVALID_TOKEN_ERROR"); super.sendJsonResponse(200, result, resp); - } catch (StorageQueryException | NoSuchAlgorithmException | StorageTransactionLogicException | TenantOrAppNotFoundException e) { + } catch (StorageQueryException | NoSuchAlgorithmException | StorageTransactionLogicException | + TenantOrAppNotFoundException e) { throw new ServletException(e); } diff --git a/src/test/java/io/supertokens/test/emailpassword/DeleteExpiredPasswordResetTokensCronjobTest.java b/src/test/java/io/supertokens/test/emailpassword/DeleteExpiredPasswordResetTokensCronjobTest.java index 4bbb630d5..838b37993 100644 --- a/src/test/java/io/supertokens/test/emailpassword/DeleteExpiredPasswordResetTokensCronjobTest.java +++ b/src/test/java/io/supertokens/test/emailpassword/DeleteExpiredPasswordResetTokensCronjobTest.java @@ -66,13 +66,13 @@ public void checkingCronJob() throws Exception { UserInfo user = EmailPassword.signUp(process.getProcess(), "test1@example.com", "password"); - String tok = EmailPassword.generatePasswordResetToken(process.getProcess(), user.id); - String tok2 = EmailPassword.generatePasswordResetToken(process.getProcess(), user.id); + String tok = EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.id); + String tok2 = EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.id); Thread.sleep(2000); - String tok3 = EmailPassword.generatePasswordResetToken(process.getProcess(), user.id); - String tok4 = EmailPassword.generatePasswordResetToken(process.getProcess(), user.id); + String tok3 = EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.id); + String tok4 = EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.id); assert (((EmailPasswordSQLStorage) StorageLayer.getStorage(process.getProcess())) .getAllPasswordResetTokenInfoForUser(new AppIdentifier(null, null), user.id).length == 4); diff --git a/src/test/java/io/supertokens/test/emailpassword/EmailPasswordTest.java b/src/test/java/io/supertokens/test/emailpassword/EmailPasswordTest.java index cf4484b0a..0bb044430 100644 --- a/src/test/java/io/supertokens/test/emailpassword/EmailPasswordTest.java +++ b/src/test/java/io/supertokens/test/emailpassword/EmailPasswordTest.java @@ -17,11 +17,14 @@ package io.supertokens.test.emailpassword; import io.supertokens.ProcessState; +import io.supertokens.authRecipe.AuthRecipe; import io.supertokens.config.Config; import io.supertokens.emailpassword.EmailPassword; import io.supertokens.emailpassword.PasswordHashing; import io.supertokens.emailpassword.exceptions.ResetPasswordInvalidTokenException; import io.supertokens.emailpassword.exceptions.WrongCredentialsException; +import io.supertokens.featureflag.EE_FEATURES; +import io.supertokens.featureflag.FeatureFlagTestContent; import io.supertokens.pluginInterface.STORAGE_TYPE; import io.supertokens.pluginInterface.authRecipe.AuthRecipeStorage; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; @@ -38,6 +41,7 @@ import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.TestingProcessManager; import io.supertokens.test.Utils; +import io.supertokens.thirdparty.ThirdParty; import org.junit.AfterClass; import org.junit.Before; import org.junit.Rule; @@ -148,7 +152,8 @@ public void testResetPasswordToken() throws Exception { assertNotNull(userInfo.id); for (int i = 0; i < 100; i++) { - String generatedResetToken = EmailPassword.generatePasswordResetToken(process.getProcess(), userInfo.id); + String generatedResetToken = EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), + userInfo.id); assertEquals(generatedResetToken.length(), 128); assertFalse(generatedResetToken.contains("+")); @@ -201,7 +206,7 @@ public void testThatAfterResetPasswordGenerateTokenTheTokenIsHashedInTheDatabase UserInfo user = EmailPassword.signUp(process.getProcess(), "random@gmail.com", "validPass123"); - String resetToken = EmailPassword.generatePasswordResetToken(process.getProcess(), user.id); + String resetToken = EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.id); PasswordResetTokenInfo resetTokenInfo = ((EmailPasswordSQLStorage) StorageLayer.getStorage( process.getProcess())) .getPasswordResetTokenInfo(new AppIdentifier(null, null), @@ -230,7 +235,7 @@ public void testThatAfterResetPasswordIsCompletedThePasswordIsHashedInTheDatabas UserInfo user = EmailPassword.signUp(process.getProcess(), "random@gmail.com", "validPass123"); - String resetToken = EmailPassword.generatePasswordResetToken(process.getProcess(), user.id); + String resetToken = EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.id); EmailPassword.resetPassword(process.getProcess(), resetToken, "newValidPass123"); @@ -260,7 +265,7 @@ public void passwordResetTokenExpiredCheck() throws Exception { UserInfo user = EmailPassword.signUp(process.getProcess(), "test1@example.com", "password"); - String tok = EmailPassword.generatePasswordResetToken(process.getProcess(), user.id); + String tok = EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.id); assert (((EmailPasswordSQLStorage) StorageLayer.getStorage(process.getProcess())) .getAllPasswordResetTokenInfoForUser(new AppIdentifier(null, null), user.id).length == 1); @@ -294,9 +299,9 @@ public void multiplePasswordResetTokensPerUserAndThenVerifyWithSignin() throws E UserInfo user = EmailPassword.signUp(process.getProcess(), "test1@example.com", "password"); - EmailPassword.generatePasswordResetToken(process.getProcess(), user.id); - String tok = EmailPassword.generatePasswordResetToken(process.getProcess(), user.id); - EmailPassword.generatePasswordResetToken(process.getProcess(), user.id); + EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.id); + String tok = EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.id); + EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.id); PasswordResetTokenInfo[] tokens = ((EmailPasswordSQLStorage) StorageLayer.getStorage(process.getProcess())) .getAllPasswordResetTokenInfoForUser(new AppIdentifier(null, null), user.id); @@ -384,13 +389,13 @@ public void clashingPassowordResetToken() throws Exception { .addPasswordResetToken(new AppIdentifier(null, null), new PasswordResetTokenInfo( user.id, "token", System.currentTimeMillis() + - Config.getConfig(process.getProcess()).getPasswordResetTokenLifetime())); + Config.getConfig(process.getProcess()).getPasswordResetTokenLifetime(), "email")); try { ((EmailPasswordSQLStorage) StorageLayer.getStorage(process.getProcess())) .addPasswordResetToken(new AppIdentifier(null, null), new PasswordResetTokenInfo(user.id, "token", System.currentTimeMillis() - + Config.getConfig(process.getProcess()).getPasswordResetTokenLifetime())); + + Config.getConfig(process.getProcess()).getPasswordResetTokenLifetime(), "email")); assert (false); } catch (DuplicatePasswordResetTokenException ignored) { @@ -412,7 +417,8 @@ public void unknownUserIdWhileGeneratingPasswordResetToken() throws Exception { } try { - EmailPassword.generatePasswordResetToken(process.getProcess(), "8ed86166-bfd8-4234-9dfe-abca9606dbd5"); + EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), + "8ed86166-bfd8-4234-9dfe-abca9606dbd5"); assert (false); } catch (UnknownUserIdException ignored) { @@ -609,4 +615,98 @@ public void changePasswordResetLifetimeTest() throws Exception { assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); } } + + @Test + public void testGeneratingResetPasswordTokenForNonEPUserNonPrimary() throws Exception { + String[] args = {"../"}; + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(process.getProcess(), "google", + "user-google", + "test@example.com"); + + EmailPassword.generatePasswordResetToken(process.main, signInUpResponse.user.id, + "test@example.com"); + + // TODO: call consume + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testGeneratingResetPasswordTokenForNonEPUserPrimary() throws Exception { + String[] args = {"../"}; + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(process.getProcess(), "google", + "user-google", + "test@example.com"); + + AuthRecipe.createPrimaryUser(process.main, signInUpResponse.user.id); + + EmailPassword.generatePasswordResetToken(process.main, signInUpResponse.user.id, + "test@example.com"); + + // TODO: call consume + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testGeneratingResetPasswordTokenForNonEPUserPrimaryButDeletedWithOtherLinked() throws Exception { + String[] args = {"../"}; + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(process.getProcess(), "google", + "user-google", + "test@example.com"); + + ThirdParty.SignInUpResponse signInUpResponse2 = ThirdParty.signInUp(process.getProcess(), "fb", + "user-fb", + "test2@example.com"); + + AuthRecipe.createPrimaryUser(process.main, signInUpResponse.user.id); + AuthRecipe.linkAccounts(process.main, signInUpResponse2.user.id, signInUpResponse.user.id); + assert (AuthRecipe.unlinkAccounts(process.main, signInUpResponse.user.id)); + + EmailPassword.generatePasswordResetToken(process.main, signInUpResponse.user.id, + "test@example.com"); + + // TODO: call consume + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } } diff --git a/src/test/java/io/supertokens/test/emailpassword/PasswordHashingTest.java b/src/test/java/io/supertokens/test/emailpassword/PasswordHashingTest.java index f5e2ab27c..d37e8ad54 100644 --- a/src/test/java/io/supertokens/test/emailpassword/PasswordHashingTest.java +++ b/src/test/java/io/supertokens/test/emailpassword/PasswordHashingTest.java @@ -530,7 +530,7 @@ public void hashAndVerifyWithBcryptChangeToArgonPasswordWithResetFlow() throws E Config.getConfig(process.getProcess()).setPasswordHashingAlg(CoreConfig.PASSWORD_HASHING_ALG.ARGON2); - String token = EmailPassword.generatePasswordResetToken(process.getProcess(), user.id); + String token = EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.id); EmailPassword.resetPassword(process.getProcess(), token, "somePass2"); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.PASSWORD_HASH_ARGON)); @@ -561,7 +561,7 @@ public void hashAndVerifyWithArgonChangeToBcryptPasswordWithResetFlow() throws E Config.getConfig(process.getProcess()).setPasswordHashingAlg(CoreConfig.PASSWORD_HASHING_ALG.BCRYPT); - String token = EmailPassword.generatePasswordResetToken(process.getProcess(), user.id); + String token = EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.id); EmailPassword.resetPassword(process.getProcess(), token, "somePass2"); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.PASSWORD_HASH_BCRYPT)); diff --git a/src/test/java/io/supertokens/test/multitenant/TestAppData.java b/src/test/java/io/supertokens/test/multitenant/TestAppData.java index 15c6d3133..61a9ad9ab 100644 --- a/src/test/java/io/supertokens/test/multitenant/TestAppData.java +++ b/src/test/java/io/supertokens/test/multitenant/TestAppData.java @@ -124,7 +124,7 @@ public void testThatDeletingAppDeleteDataFromAllTables() throws Exception { // Add all recipe data UserInfo epUser = EmailPassword.signUp(appWithStorage, process.getProcess(), "test@example.com", "password"); - EmailPassword.generatePasswordResetToken(appWithStorage, process.getProcess(), epUser.id); + EmailPassword.generatePasswordResetTokenBeforeCdi4_0(appWithStorage, process.getProcess(), epUser.id); ThirdParty.SignInUpResponse tpUser = ThirdParty.signInUp(appWithStorage, process.getProcess(), "google", "googleid", "test@example.com"); From e57d7e3ae40c8aaf1e51654f93fc59155329a41f Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Thu, 3 Aug 2023 19:51:55 +0530 Subject: [PATCH 067/131] more tests --- .../GeneratePasswordResetTokenAPITest2_7.java | 34 ++- .../GeneratePasswordResetTokenAPITest4_0.java | 243 ++++++++++++++++++ 2 files changed, 274 insertions(+), 3 deletions(-) create mode 100644 src/test/java/io/supertokens/test/emailpassword/api/GeneratePasswordResetTokenAPITest4_0.java diff --git a/src/test/java/io/supertokens/test/emailpassword/api/GeneratePasswordResetTokenAPITest2_7.java b/src/test/java/io/supertokens/test/emailpassword/api/GeneratePasswordResetTokenAPITest2_7.java index 765602a61..85cf266e4 100644 --- a/src/test/java/io/supertokens/test/emailpassword/api/GeneratePasswordResetTokenAPITest2_7.java +++ b/src/test/java/io/supertokens/test/emailpassword/api/GeneratePasswordResetTokenAPITest2_7.java @@ -23,6 +23,7 @@ import io.supertokens.test.TestingProcessManager; import io.supertokens.test.Utils; import io.supertokens.test.httpRequest.HttpRequestForTesting; +import io.supertokens.thirdparty.ThirdParty; import io.supertokens.utils.SemVer; import org.junit.AfterClass; import org.junit.Before; @@ -50,7 +51,7 @@ public void beforeEach() { // Check for bad input (missing fields) @Test public void testBadInput() throws Exception { - String[] args = { "../" }; + String[] args = {"../"}; TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); @@ -106,7 +107,7 @@ public void testBadInput() throws Exception { // Check good input works @Test public void testGoodInput() throws Exception { - String[] args = { "../" }; + String[] args = {"../"}; TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); @@ -139,7 +140,7 @@ public void testGoodInput() throws Exception { // Failure condition: passing a valid userId will cause the test to fail @Test public void testForAllTypesOfOutput() throws Exception { - String[] args = { "../" }; + String[] args = {"../"}; TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); @@ -161,4 +162,31 @@ public void testForAllTypesOfOutput() throws Exception { process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); } + + @Test + public void testUnknownUserWithUserIdFromNonEp() throws Exception { + String[] args = {"../"}; + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + ThirdParty.SignInUpResponse res = ThirdParty.signInUp(process.main, "google", "ug", "t@example.com"); + + JsonObject requestBody = new JsonObject(); + requestBody.addProperty("userId", res.user.id); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/user/password/reset/token", requestBody, 1000, 1000, null, + SemVer.v2_7.get(), "emailpassword"); + + assertEquals(response.get("status").getAsString(), "UNKNOWN_USER_ID_ERROR"); + assertEquals(response.entrySet().size(), 1); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } } diff --git a/src/test/java/io/supertokens/test/emailpassword/api/GeneratePasswordResetTokenAPITest4_0.java b/src/test/java/io/supertokens/test/emailpassword/api/GeneratePasswordResetTokenAPITest4_0.java new file mode 100644 index 000000000..54f28dbfc --- /dev/null +++ b/src/test/java/io/supertokens/test/emailpassword/api/GeneratePasswordResetTokenAPITest4_0.java @@ -0,0 +1,243 @@ +/* + * Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.test.emailpassword.api; + +import com.google.gson.JsonObject; +import io.supertokens.ProcessState; +import io.supertokens.emailpassword.EmailPassword; +import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; +import io.supertokens.storageLayer.StorageLayer; +import io.supertokens.test.TestingProcessManager; +import io.supertokens.test.Utils; +import io.supertokens.test.httpRequest.HttpRequestForTesting; +import io.supertokens.thirdparty.ThirdParty; +import io.supertokens.useridmapping.UserIdMapping; +import io.supertokens.utils.SemVer; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; + +import static org.junit.Assert.*; + +public class GeneratePasswordResetTokenAPITest4_0 { + + @Rule + public TestRule watchman = Utils.getOnFailure(); + + @AfterClass + public static void afterTesting() { + Utils.afterTesting(); + } + + @Before + public void beforeEach() { + Utils.reset(); + } + + // Check for bad input (missing fields) + @Test + public void testBadInput() throws Exception { + String[] args = {"../"}; + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + { + try { + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/user/password/reset/token", null, 1000, 1000, null, + SemVer.v4_0.get(), "emailpassword"); + throw new Exception("Should not come here"); + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + assertTrue(e.statusCode == 400 + && e.getMessage().equals("Http error. Status Code: 400. Message: Invalid Json Input")); + } + } + + { + JsonObject requestBody = new JsonObject(); + try { + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/user/password/reset/token", requestBody, 1000, 1000, null, + SemVer.v4_0.get(), "emailpassword"); + throw new Exception("Should not come here"); + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + assertTrue(e.statusCode == 400 && e.getMessage().equals( + "Http error. Status Code: 400. Message: Field name 'userId' is invalid in " + "JSON input")); + + } + } + + { + JsonObject requestBody = new JsonObject(); + requestBody.add("userId", null); + try { + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/user/password/reset/token", requestBody, 1000, 1000, null, + SemVer.v4_0.get(), "emailpassword"); + throw new Exception("Should not come here"); + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + assertTrue(e.statusCode == 400 && e.getMessage().equals( + "Http error. Status Code: 400. Message: Field name 'userId' is invalid in JSON input")); + } + } + + { + AuthRecipeUserInfo user = EmailPassword.signUp(process.main, "a@a.com", "p1234"); + JsonObject requestBody = new JsonObject(); + requestBody.addProperty("userId", user.id); + try { + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/user/password/reset/token", requestBody, 1000, 1000, null, + SemVer.v4_0.get(), "emailpassword"); + throw new Exception("Should not come here"); + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + assertTrue(e.statusCode == 400 && e.getMessage().equals( + "Http error. Status Code: 400. Message: Field name 'email' is invalid in JSON input")); + } + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + // Check good input works + @Test + public void testGoodInput() throws Exception { + String[] args = {"../"}; + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + JsonObject signUpResponse = Utils.signUpRequest_2_4(process, "random@gmail.com", "validPass123"); + assertEquals(signUpResponse.get("status").getAsString(), "OK"); + assertEquals(signUpResponse.entrySet().size(), 2); + String userId = signUpResponse.getAsJsonObject("user").get("id").getAsString(); + + JsonObject requestBody = new JsonObject(); + requestBody.addProperty("userId", userId); + requestBody.addProperty("email", "random@gmail.com"); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/user/password/reset/token", requestBody, 1000, 1000, null, + SemVer.v4_0.get(), "emailpassword"); + + assertEquals(response.get("status").getAsString(), "OK"); + assertNotNull(response.get("token")); + assertEquals(response.entrySet().size(), 2); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testGoodInputWithUserIdMapping() throws Exception { + String[] args = {"../"}; + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo user = EmailPassword.signUp(process.main, "a@a.com", "p1234"); + UserIdMapping.createUserIdMapping(process.main, user.id, "e1", null, false); + + JsonObject requestBody = new JsonObject(); + requestBody.addProperty("userId", "e1"); + requestBody.addProperty("email", "random@gmail.com"); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/user/password/reset/token", requestBody, 1000, 1000, null, + SemVer.v4_0.get(), "emailpassword"); + + assertEquals(response.get("status").getAsString(), "OK"); + assertNotNull(response.get("token")); + assertEquals(response.entrySet().size(), 2); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + // Check for all types of output + // Failure condition: passing a valid userId will cause the test to fail + @Test + public void testForAllTypesOfOutput() throws Exception { + String[] args = {"../"}; + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + JsonObject requestBody = new JsonObject(); + requestBody.addProperty("userId", "randomUserId"); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/user/password/reset/token", requestBody, 1000, 1000, null, + SemVer.v4_0.get(), "emailpassword"); + + assertEquals(response.get("status").getAsString(), "UNKNOWN_USER_ID_ERROR"); + assertEquals(response.entrySet().size(), 1); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testUnknownUserWithUserIdFromNonEp() throws Exception { + String[] args = {"../"}; + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + ThirdParty.SignInUpResponse res = ThirdParty.signInUp(process.main, "google", "ug", "t@example.com"); + + JsonObject requestBody = new JsonObject(); + requestBody.addProperty("userId", res.user.id); + requestBody.addProperty("email", res.user.loginMethods[0].email); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/user/password/reset/token", requestBody, 1000, 1000, null, + SemVer.v4_0.get(), "emailpassword"); + + assertEquals(response.get("status").getAsString(), "OK"); + assertNotNull(response.get("token")); + assertEquals(response.entrySet().size(), 2); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } +} From ec19d79496778a5804b8341be59543f66c068efc Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Thu, 3 Aug 2023 20:01:57 +0530 Subject: [PATCH 068/131] more tests --- .../test/emailpassword/EmailPasswordTest.java | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/test/java/io/supertokens/test/emailpassword/EmailPasswordTest.java b/src/test/java/io/supertokens/test/emailpassword/EmailPasswordTest.java index 0bb044430..3ed1cbed6 100644 --- a/src/test/java/io/supertokens/test/emailpassword/EmailPasswordTest.java +++ b/src/test/java/io/supertokens/test/emailpassword/EmailPasswordTest.java @@ -709,4 +709,40 @@ public void testGeneratingResetPasswordTokenForNonEPUserPrimaryButDeletedWithOth process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); } + + @Test + public void deletionOfTpUserDeletesPasswordResetToken() throws Exception { + String[] args = {"../"}; + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(process.getProcess(), "google", + "user-google", + "test@example.com"); + + + String token = EmailPassword.generatePasswordResetToken(process.main, signInUpResponse.user.id, + "test@example.com"); + token = io.supertokens.utils.Utils.hashSHA256(token); + + assertNotNull(((EmailPasswordSQLStorage) StorageLayer.getStorage(process.main)).getPasswordResetTokenInfo( + new AppIdentifier(null, null), token)); + + AuthRecipe.deleteUser(process.main, signInUpResponse.user.id); + + assertNull(((EmailPasswordSQLStorage) StorageLayer.getStorage(process.main)).getPasswordResetTokenInfo( + new AppIdentifier(null, null), token)); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } } From 4ff84c428c5a327ca1abf1592810be2081456e5d Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Thu, 3 Aug 2023 21:12:50 +0530 Subject: [PATCH 069/131] implements consumeCode function --- .../emailpassword/EmailPassword.java | 97 +++++++++++++++++++ .../test/emailpassword/EmailPasswordTest.java | 28 ++++-- 2 files changed, 119 insertions(+), 6 deletions(-) diff --git a/src/main/java/io/supertokens/emailpassword/EmailPassword.java b/src/main/java/io/supertokens/emailpassword/EmailPassword.java index f245b9a6d..2aea4a60e 100644 --- a/src/main/java/io/supertokens/emailpassword/EmailPassword.java +++ b/src/main/java/io/supertokens/emailpassword/EmailPassword.java @@ -446,6 +446,103 @@ public static String resetPassword(TenantIdentifierWithStorage tenantIdentifierW } } + @TestOnly + public static ConsumeResetPasswordTokenResult consumeResetPasswordToken(Main main, String token) + throws ResetPasswordInvalidTokenException, NoSuchAlgorithmException, StorageQueryException, + StorageTransactionLogicException { + try { + Storage storage = StorageLayer.getStorage(main); + return consumeResetPasswordToken(new TenantIdentifierWithStorage(null, null, null, storage), + token); + } catch (TenantOrAppNotFoundException e) { + throw new IllegalStateException(e); + } + } + + public static class ConsumeResetPasswordTokenResult { + public String userId; + public String email; + + public ConsumeResetPasswordTokenResult(String userId, String email) { + this.userId = userId; + this.email = email; + } + } + + public static ConsumeResetPasswordTokenResult consumeResetPasswordToken( + TenantIdentifierWithStorage tenantIdentifierWithStorage, String token) + throws ResetPasswordInvalidTokenException, NoSuchAlgorithmException, StorageQueryException, + StorageTransactionLogicException, TenantOrAppNotFoundException { + String hashedToken = Utils.hashSHA256(token); + + EmailPasswordSQLStorage storage = tenantIdentifierWithStorage.getEmailPasswordStorage(); + + PasswordResetTokenInfo resetInfo = storage.getPasswordResetTokenInfo( + tenantIdentifierWithStorage.toAppIdentifier(), hashedToken); + + if (resetInfo == null) { + throw new ResetPasswordInvalidTokenException(); + } + + final String userId = resetInfo.userId; + + try { + return storage.startTransaction(con -> { + + PasswordResetTokenInfo[] allTokens = storage.getAllPasswordResetTokenInfoForUser_Transaction( + tenantIdentifierWithStorage.toAppIdentifier(), con, + userId); + + PasswordResetTokenInfo matchedToken = null; + for (PasswordResetTokenInfo tok : allTokens) { + if (tok.token.equals(hashedToken)) { + matchedToken = tok; + break; + } + } + + if (matchedToken == null) { + throw new StorageTransactionLogicException(new ResetPasswordInvalidTokenException()); + } + + storage.deleteAllPasswordResetTokensForUser_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), + con, + userId); + + if (matchedToken.tokenExpiry < System.currentTimeMillis()) { + storage.commitTransaction(con); + throw new StorageTransactionLogicException(new ResetPasswordInvalidTokenException()); + } + + storage.commitTransaction(con); + if (matchedToken.email == null) { + // this is possible if the token was generated before migration, and then consumed + // after migration + AppIdentifierWithStorage appIdentifierWithStorage = + tenantIdentifierWithStorage.toAppIdentifierWithStorage(); + AuthRecipeUserInfo user = AuthRecipe.getUserById(appIdentifierWithStorage, userId); + if (user == null) { + throw new StorageTransactionLogicException(new ResetPasswordInvalidTokenException()); + } + if (user.loginMethods.length > 1) { + throw new StorageTransactionLogicException(new ResetPasswordInvalidTokenException()); + } + if (user.loginMethods[0].email == null || + user.loginMethods[0].recipeId != RECIPE_ID.EMAIL_PASSWORD) { + throw new StorageTransactionLogicException(new ResetPasswordInvalidTokenException()); + } + return new ConsumeResetPasswordTokenResult(matchedToken.userId, user.loginMethods[0].email); + } + return new ConsumeResetPasswordTokenResult(matchedToken.userId, matchedToken.email); + }); + } catch (StorageTransactionLogicException e) { + if (e.actualException instanceof ResetPasswordInvalidTokenException) { + throw (ResetPasswordInvalidTokenException) e.actualException; + } + throw e; + } + } + @TestOnly public static void updateUsersEmailOrPassword(Main main, @Nonnull String userId, @Nullable String email, diff --git a/src/test/java/io/supertokens/test/emailpassword/EmailPasswordTest.java b/src/test/java/io/supertokens/test/emailpassword/EmailPasswordTest.java index 3ed1cbed6..02c3ee83a 100644 --- a/src/test/java/io/supertokens/test/emailpassword/EmailPasswordTest.java +++ b/src/test/java/io/supertokens/test/emailpassword/EmailPasswordTest.java @@ -635,10 +635,20 @@ public void testGeneratingResetPasswordTokenForNonEPUserNonPrimary() throws Exce "user-google", "test@example.com"); - EmailPassword.generatePasswordResetToken(process.main, signInUpResponse.user.id, + try { + EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.main, signInUpResponse.user.id); + assert false; + } catch (UnknownUserIdException ignored) { + + } + + String token = EmailPassword.generatePasswordResetToken(process.main, signInUpResponse.user.id, "test@example.com"); - // TODO: call consume + EmailPassword.ConsumeResetPasswordTokenResult res = EmailPassword.consumeResetPasswordToken(process.main, + token); + assert (res.email.equals("test@example.com")); + assert (res.userId.equals(signInUpResponse.user.id)); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -665,10 +675,13 @@ public void testGeneratingResetPasswordTokenForNonEPUserPrimary() throws Excepti AuthRecipe.createPrimaryUser(process.main, signInUpResponse.user.id); - EmailPassword.generatePasswordResetToken(process.main, signInUpResponse.user.id, + String token = EmailPassword.generatePasswordResetToken(process.main, signInUpResponse.user.id, "test@example.com"); - // TODO: call consume + EmailPassword.ConsumeResetPasswordTokenResult res = EmailPassword.consumeResetPasswordToken(process.main, + token); + assert (res.email.equals("test@example.com")); + assert (res.userId.equals(signInUpResponse.user.id)); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -701,10 +714,13 @@ public void testGeneratingResetPasswordTokenForNonEPUserPrimaryButDeletedWithOth AuthRecipe.linkAccounts(process.main, signInUpResponse2.user.id, signInUpResponse.user.id); assert (AuthRecipe.unlinkAccounts(process.main, signInUpResponse.user.id)); - EmailPassword.generatePasswordResetToken(process.main, signInUpResponse.user.id, + String token = EmailPassword.generatePasswordResetToken(process.main, signInUpResponse.user.id, "test@example.com"); - // TODO: call consume + EmailPassword.ConsumeResetPasswordTokenResult res = EmailPassword.consumeResetPasswordToken(process.main, + token); + assert (res.email.equals("test@example.com")); + assert (res.userId.equals(signInUpResponse.user.id)); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); From d28cb7cab95163edf4529429661c4c4b2b6189f5 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Thu, 3 Aug 2023 23:28:33 +0530 Subject: [PATCH 070/131] adds API to consume reset password code --- .../io/supertokens/webserver/Webserver.java | 1 + .../ConsumeResetPasswordAPI.java | 99 +++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 src/main/java/io/supertokens/webserver/api/emailpassword/ConsumeResetPasswordAPI.java diff --git a/src/main/java/io/supertokens/webserver/Webserver.java b/src/main/java/io/supertokens/webserver/Webserver.java index a3295cba5..5e4a4ff8c 100644 --- a/src/main/java/io/supertokens/webserver/Webserver.java +++ b/src/main/java/io/supertokens/webserver/Webserver.java @@ -255,6 +255,7 @@ private void setupRoutes() { addAPI(new CanLinkAccountsAPI(main)); addAPI(new LinkAccountsAPI(main)); addAPI(new UnlinkAccountAPI(main)); + addAPI(new ConsumeResetPasswordAPI(main)); StandardContext context = tomcatReference.getContext(); Tomcat tomcat = tomcatReference.getTomcat(); diff --git a/src/main/java/io/supertokens/webserver/api/emailpassword/ConsumeResetPasswordAPI.java b/src/main/java/io/supertokens/webserver/api/emailpassword/ConsumeResetPasswordAPI.java new file mode 100644 index 000000000..362ce9020 --- /dev/null +++ b/src/main/java/io/supertokens/webserver/api/emailpassword/ConsumeResetPasswordAPI.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2020, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.webserver.api.emailpassword; + +import com.google.gson.JsonObject; +import io.supertokens.Main; +import io.supertokens.emailpassword.EmailPassword; +import io.supertokens.emailpassword.exceptions.ResetPasswordInvalidTokenException; +import io.supertokens.output.Logging; +import io.supertokens.pluginInterface.RECIPE_ID; +import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; +import io.supertokens.useridmapping.UserIdMapping; +import io.supertokens.useridmapping.UserIdType; +import io.supertokens.utils.Utils; +import io.supertokens.webserver.InputParser; +import io.supertokens.webserver.WebserverAPI; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import java.io.IOException; +import java.security.NoSuchAlgorithmException; + +public class ConsumeResetPasswordAPI extends WebserverAPI { + private static final long serialVersionUID = -7529428297450682549L; + + public ConsumeResetPasswordAPI(Main main) { + super(main, RECIPE_ID.EMAIL_PASSWORD.toString()); + } + + @Override + public String getPath() { + return "/recipe/user/password/reset/token/consume"; + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { + // API is tenant specific + JsonObject input = InputParser.parseJsonObjectOrThrowError(req); + String token = InputParser.parseStringOrThrowError(input, "token", false); + assert token != null; + + TenantIdentifierWithStorage tenantIdentifierWithStorage = null; + try { + tenantIdentifierWithStorage = getTenantIdentifierWithStorageFromRequest(req); + } catch (TenantOrAppNotFoundException e) { + throw new ServletException(e); + } + + try { + EmailPassword.ConsumeResetPasswordTokenResult result = EmailPassword.consumeResetPasswordToken( + tenantIdentifierWithStorage, token); + + io.supertokens.pluginInterface.useridmapping.UserIdMapping userIdMapping = UserIdMapping.getUserIdMapping( + tenantIdentifierWithStorage.toAppIdentifierWithStorage(), result.userId, UserIdType.SUPERTOKENS); + + // if userIdMapping exists, pass the externalUserId to the response + if (userIdMapping != null) { + result.userId = userIdMapping.externalUserId; + } + + JsonObject resultJson = new JsonObject(); + resultJson.addProperty("status", "OK"); + resultJson.addProperty("userId", result.userId); + resultJson.addProperty("email", result.email); + + super.sendJsonResponse(200, resultJson, resp); + + } catch (ResetPasswordInvalidTokenException e) { + Logging.debug(main, tenantIdentifierWithStorage, Utils.exceptionStacktraceToString(e)); + JsonObject result = new JsonObject(); + result.addProperty("status", "RESET_PASSWORD_INVALID_TOKEN_ERROR"); + super.sendJsonResponse(200, result, resp); + + } catch (StorageQueryException | NoSuchAlgorithmException | StorageTransactionLogicException | + TenantOrAppNotFoundException e) { + throw new ServletException(e); + } + + } + +} From 9b3b0639acb65a5d07427c160ff1549d1a848120 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Fri, 4 Aug 2023 12:35:02 +0530 Subject: [PATCH 071/131] adds more tests --- .../emailpassword/EmailPassword.java | 13 ++ .../test/emailpassword/EmailPasswordTest.java | 124 ++++++++++++ .../api/ConsumeResetPasswordAPITest4_0.java | 186 ++++++++++++++++++ 3 files changed, 323 insertions(+) create mode 100644 src/test/java/io/supertokens/test/emailpassword/api/ConsumeResetPasswordAPITest4_0.java diff --git a/src/main/java/io/supertokens/emailpassword/EmailPassword.java b/src/main/java/io/supertokens/emailpassword/EmailPassword.java index 2aea4a60e..5203e76de 100644 --- a/src/main/java/io/supertokens/emailpassword/EmailPassword.java +++ b/src/main/java/io/supertokens/emailpassword/EmailPassword.java @@ -290,6 +290,19 @@ public static String generatePasswordResetTokenBeforeCdi4_0(Main main, String us } } + @TestOnly + public static String generatePasswordResetTokenBeforeCdi4_0WithoutAddingEmail(Main main, String userId) + throws InvalidKeySpecException, NoSuchAlgorithmException, StorageQueryException, UnknownUserIdException { + try { + Storage storage = StorageLayer.getStorage(main); + return generatePasswordResetToken( + new TenantIdentifierWithStorage(null, null, null, storage), + main, userId, null); + } catch (TenantOrAppNotFoundException | BadPermissionException e) { + throw new IllegalStateException(e); + } + } + @TestOnly public static String generatePasswordResetToken(Main main, String userId, String email) throws InvalidKeySpecException, NoSuchAlgorithmException, StorageQueryException, UnknownUserIdException { diff --git a/src/test/java/io/supertokens/test/emailpassword/EmailPasswordTest.java b/src/test/java/io/supertokens/test/emailpassword/EmailPasswordTest.java index 02c3ee83a..0c6fc840c 100644 --- a/src/test/java/io/supertokens/test/emailpassword/EmailPasswordTest.java +++ b/src/test/java/io/supertokens/test/emailpassword/EmailPasswordTest.java @@ -761,4 +761,128 @@ public void deletionOfTpUserDeletesPasswordResetToken() throws Exception { process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); } + + @Test + public void passwordResetTokenExpiredCheckWithConsumeCode() throws Exception { + String[] args = {"../"}; + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + Utils.setValueInConfig("password_reset_token_lifetime", "10"); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + UserInfo user = EmailPassword.signUp(process.getProcess(), "test1@example.com", "password"); + + String tok = EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.id); + + assert (((EmailPasswordSQLStorage) StorageLayer.getStorage(process.getProcess())) + .getAllPasswordResetTokenInfoForUser(new AppIdentifier(null, null), user.id).length == 1); + + Thread.sleep(20); + + try { + EmailPassword.consumeResetPasswordToken(process.getProcess(), tok); + assert (false); + } catch (ResetPasswordInvalidTokenException ignored) { + + } + + assert (((EmailPasswordSQLStorage) StorageLayer.getStorage(process.getProcess())) + .getAllPasswordResetTokenInfoForUser(new AppIdentifier(null, null), user.id).length == 0); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void multiplePasswordResetTokensPerUserAndThenVerifyWithSigninWithConsumeCode() throws Exception { + String[] args = {"../"}; + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + UserInfo user = EmailPassword.signUp(process.getProcess(), "test1@example.com", "password"); + + EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.id); + String tok = EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.id); + EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.id); + + PasswordResetTokenInfo[] tokens = ((EmailPasswordSQLStorage) StorageLayer.getStorage(process.getProcess())) + .getAllPasswordResetTokenInfoForUser(new AppIdentifier(null, null), user.id); + + assert (tokens.length == 3); + + EmailPassword.consumeResetPasswordToken(process.getProcess(), tok); + + tokens = ((EmailPasswordSQLStorage) StorageLayer.getStorage(process.getProcess())) + .getAllPasswordResetTokenInfoForUser(new AppIdentifier(null, null), user.id); + assert (tokens.length == 0); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void wrongPasswordResetTokenWithConsumeCode() throws Exception { + String[] args = {"../"}; + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + try { + EmailPassword.consumeResetPasswordToken(process.getProcess(), "token"); + assert (false); + } catch (ResetPasswordInvalidTokenException ignored) { + + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void consumeCodeCorrectlySetsTheUserEmailForOlderTokens() throws Exception { + String[] args = {"../"}; + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + UserInfo user = EmailPassword.signUp(process.getProcess(), "test1@example.com", "password"); + + String tok = EmailPassword.generatePasswordResetTokenBeforeCdi4_0WithoutAddingEmail(process.getProcess(), + user.id); + + assert (((EmailPasswordSQLStorage) StorageLayer.getStorage(process.getProcess())) + .getAllPasswordResetTokenInfoForUser(new AppIdentifier(null, null), user.id).length == 1); + assert (((EmailPasswordSQLStorage) StorageLayer.getStorage(process.getProcess())) + .getAllPasswordResetTokenInfoForUser(new AppIdentifier(null, null), user.id)[0].email == null); + + EmailPassword.ConsumeResetPasswordTokenResult result = EmailPassword.consumeResetPasswordToken( + process.getProcess(), tok); + assert (result.email.equals("test1@example.com")); + assert (result.userId.equals(user.id)); + + assert (((EmailPasswordSQLStorage) StorageLayer.getStorage(process.getProcess())) + .getAllPasswordResetTokenInfoForUser(new AppIdentifier(null, null), user.id).length == 0); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } } diff --git a/src/test/java/io/supertokens/test/emailpassword/api/ConsumeResetPasswordAPITest4_0.java b/src/test/java/io/supertokens/test/emailpassword/api/ConsumeResetPasswordAPITest4_0.java new file mode 100644 index 000000000..cb7385d8b --- /dev/null +++ b/src/test/java/io/supertokens/test/emailpassword/api/ConsumeResetPasswordAPITest4_0.java @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.test.emailpassword.api; + +import com.google.gson.JsonObject; +import io.supertokens.ProcessState; +import io.supertokens.emailpassword.EmailPassword; +import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; +import io.supertokens.storageLayer.StorageLayer; +import io.supertokens.test.TestingProcessManager; +import io.supertokens.test.Utils; +import io.supertokens.test.httpRequest.HttpRequestForTesting; +import io.supertokens.useridmapping.UserIdMapping; +import io.supertokens.utils.SemVer; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; + +import static org.junit.Assert.*; + +public class ConsumeResetPasswordAPITest4_0 { + + @Rule + public TestRule watchman = Utils.getOnFailure(); + + @AfterClass + public static void afterTesting() { + Utils.afterTesting(); + } + + @Before + public void beforeEach() { + Utils.reset(); + } + + // Check for bad input (missing fields) + @Test + public void testBadInput() throws Exception { + String[] args = {"../"}; + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + { + try { + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/user/password/reset/token/consume", null, 1000, 1000, null, + SemVer.v4_0.get(), "emailpassword"); + throw new Exception("Should not come here"); + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + assertTrue(e.statusCode == 400 + && e.getMessage().equals("Http error. Status Code: 400. Message: Invalid Json Input")); + } + } + + { + JsonObject requestBody = new JsonObject(); + try { + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/user/password/reset/token/consume", requestBody, 1000, 1000, null, + SemVer.v4_0.get(), "emailpassword"); + throw new Exception("Should not come here"); + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + assertTrue(e.statusCode == 400 && e.getMessage().equals( + "Http error. Status Code: 400. Message: Field name 'token' is invalid in " + "JSON input")); + } + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + // Check good input works + @Test + public void testGoodInput() throws Exception { + String[] args = {"../"}; + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo user = EmailPassword.signUp(process.main, "random@gmail.com", "validPass123"); + + String userId = user.id; + + String token = EmailPassword.generatePasswordResetToken(process.main, userId, "random@gmail.com"); + + JsonObject resetPasswordBody = new JsonObject(); + resetPasswordBody.addProperty("token", token); + + JsonObject passwordResetResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/user/password/reset/token/consume", resetPasswordBody, 1000, 1000, null, + SemVer.v4_0.get(), "emailpassword"); + assertEquals(passwordResetResponse.get("status").getAsString(), "OK"); + assertEquals(passwordResetResponse.get("email").getAsString(), "random@gmail.com"); + assertEquals(passwordResetResponse.get("userId").getAsString(), user.id); + assertEquals(passwordResetResponse.entrySet().size(), 3); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testGoodInputWithUserIdMapping() throws Exception { + String[] args = {"../"}; + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo user = EmailPassword.signUp(process.main, "random@gmail.com", "validPass123"); + UserIdMapping.createUserIdMapping(process.main, user.id, "e1", null, false); + + String userId = user.id; + + String token = EmailPassword.generatePasswordResetToken(process.main, userId, "random@gmail.com"); + + JsonObject resetPasswordBody = new JsonObject(); + resetPasswordBody.addProperty("token", token); + + JsonObject passwordResetResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/user/password/reset/token/consume", resetPasswordBody, 1000, 1000, null, + SemVer.v4_0.get(), "emailpassword"); + assertEquals(passwordResetResponse.get("status").getAsString(), "OK"); + assertEquals(passwordResetResponse.get("email").getAsString(), "random@gmail.com"); + assertEquals(passwordResetResponse.get("userId").getAsString(), "e1"); + assertEquals(passwordResetResponse.entrySet().size(), 3); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + // Check for all types of output + // Failure condition: passing a valid password reset token will fail the test + @Test + public void testALLTypesOfOutPut() throws Exception { + String[] args = {"../"}; + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + JsonObject resetPasswordBody = new JsonObject(); + resetPasswordBody.addProperty("token", "randomToken"); + + JsonObject passwordResetResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/user/password/reset/token/consume", resetPasswordBody, 1000, 1000, null, + SemVer.v4_0.get(), "emailpassword"); + + assertEquals(passwordResetResponse.get("status").getAsString(), "RESET_PASSWORD_INVALID_TOKEN_ERROR"); + assertEquals(passwordResetResponse.entrySet().size(), 1); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } +} From b864e2f5a7de01e9ad273c36f9deaf79fbba61b4 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Fri, 4 Aug 2023 13:23:25 +0530 Subject: [PATCH 072/131] adds more tests --- .../emailpassword/api/SignInAPITest4_0.java | 250 ++++++++++++++++++ 1 file changed, 250 insertions(+) create mode 100644 src/test/java/io/supertokens/test/emailpassword/api/SignInAPITest4_0.java diff --git a/src/test/java/io/supertokens/test/emailpassword/api/SignInAPITest4_0.java b/src/test/java/io/supertokens/test/emailpassword/api/SignInAPITest4_0.java new file mode 100644 index 000000000..17d3fab9c --- /dev/null +++ b/src/test/java/io/supertokens/test/emailpassword/api/SignInAPITest4_0.java @@ -0,0 +1,250 @@ +/* + * Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.test.emailpassword.api; + +import com.google.gson.JsonObject; +import io.supertokens.ActiveUsers; +import io.supertokens.ProcessState; +import io.supertokens.authRecipe.AuthRecipe; +import io.supertokens.emailpassword.EmailPassword; +import io.supertokens.featureflag.EE_FEATURES; +import io.supertokens.featureflag.FeatureFlagTestContent; +import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; +import io.supertokens.storageLayer.StorageLayer; +import io.supertokens.test.TestingProcessManager; +import io.supertokens.test.Utils; +import io.supertokens.test.httpRequest.HttpRequestForTesting; +import io.supertokens.useridmapping.UserIdMapping; +import io.supertokens.utils.SemVer; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; + +import static org.junit.Assert.*; + +/* + * TODO: + * - Check for bad input (missing fields) + * - Check good input works + * - Test that sign in with unnormalised email like Test@gmail.com should also work + * - Test that giving an empty password, empty email, invalid email, random email or wrong password throws a wrong + * credentials error + * - Test that an empty password yields a WRONG_CREDENTIALS_ERROR output. + * */ + +public class SignInAPITest4_0 { + + @Rule + public TestRule watchman = Utils.getOnFailure(); + + @AfterClass + public static void afterTesting() { + Utils.afterTesting(); + } + + @Before + public void beforeEach() { + Utils.reset(); + } + + // Check good input works + @Test + public void testGoodInput() throws Exception { + String[] args = {"../"}; + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo user = EmailPassword.signUp(process.main, "random@gmail.com", "validPass123"); + + JsonObject responseBody = new JsonObject(); + responseBody.addProperty("email", "random@gmail.com"); + responseBody.addProperty("password", "validPass123"); + + Thread.sleep(1); // add a small delay to ensure a unique timestamp + long beforeSignIn = System.currentTimeMillis(); + + JsonObject signInResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signin", responseBody, 1000, 1000, null, SemVer.v4_0.get(), + "emailpassword"); + + assertEquals(signInResponse.get("status").getAsString(), "OK"); + assertEquals(signInResponse.entrySet().size(), 2); + + JsonObject jsonUser = signInResponse.get("user").getAsJsonObject(); + assert (jsonUser.get("id").getAsString().equals(user.id)); + assert (jsonUser.get("timeJoined").getAsLong() == user.timeJoined); + assert (!jsonUser.get("isPrimaryUser").getAsBoolean()); + assert (jsonUser.get("emails").getAsJsonArray().size() == 1); + assert (jsonUser.get("emails").getAsJsonArray().get(0).getAsString().equals("random@gmail.com")); + assert (jsonUser.get("phoneNumbers").getAsJsonArray().size() == 0); + assert (jsonUser.get("thirdParty").getAsJsonArray().size() == 0); + assert (jsonUser.get("loginMethods").getAsJsonArray().size() == 1); + JsonObject lM = jsonUser.get("loginMethods").getAsJsonArray().get(0).getAsJsonObject(); + assertFalse(lM.get("verified").getAsBoolean()); + assertEquals(lM.get("timeJoined").getAsLong(), user.timeJoined); + assertEquals(lM.get("recipeUserId").getAsString(), user.id); + assertEquals(lM.get("recipeId").getAsString(), "emailpassword"); + assertEquals(lM.get("email").getAsString(), "random@gmail.com"); + assert (lM.entrySet().size() == 6); + + int activeUsers = ActiveUsers.countUsersActiveSince(process.getProcess(), beforeSignIn); + assert (activeUsers == 1); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testGoodInputWithUserIdMapping() throws Exception { + String[] args = {"../"}; + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo user = EmailPassword.signUp(process.main, "random@gmail.com", "validPass123"); + UserIdMapping.createUserIdMapping(process.main, user.id, "e1", null, false); + + JsonObject responseBody = new JsonObject(); + responseBody.addProperty("email", "random@gmail.com"); + responseBody.addProperty("password", "validPass123"); + + Thread.sleep(1); // add a small delay to ensure a unique timestamp + long beforeSignIn = System.currentTimeMillis(); + + JsonObject signInResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signin", responseBody, 1000, 1000, null, SemVer.v4_0.get(), + "emailpassword"); + + assertEquals(signInResponse.get("status").getAsString(), "OK"); + assertEquals(signInResponse.entrySet().size(), 2); + + JsonObject jsonUser = signInResponse.get("user").getAsJsonObject(); + assert (jsonUser.get("id").getAsString().equals("e1")); + assert (jsonUser.get("timeJoined").getAsLong() == user.timeJoined); + assert (!jsonUser.get("isPrimaryUser").getAsBoolean()); + assert (jsonUser.get("emails").getAsJsonArray().size() == 1); + assert (jsonUser.get("emails").getAsJsonArray().get(0).getAsString().equals("random@gmail.com")); + assert (jsonUser.get("phoneNumbers").getAsJsonArray().size() == 0); + assert (jsonUser.get("thirdParty").getAsJsonArray().size() == 0); + assert (jsonUser.get("loginMethods").getAsJsonArray().size() == 1); + JsonObject lM = jsonUser.get("loginMethods").getAsJsonArray().get(0).getAsJsonObject(); + assertFalse(lM.get("verified").getAsBoolean()); + assertEquals(lM.get("timeJoined").getAsLong(), user.timeJoined); + assertEquals(lM.get("recipeUserId").getAsString(), "e1"); + assertEquals(lM.get("recipeId").getAsString(), "emailpassword"); + assertEquals(lM.get("email").getAsString(), "random@gmail.com"); + assert (lM.entrySet().size() == 6); + + int activeUsers = ActiveUsers.countUsersActiveSince(process.getProcess(), beforeSignIn); + assert (activeUsers == 1); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testGoodInputWithUserIdMappingAndMultipleLinkedAccounts() throws Exception { + String[] args = {"../"}; + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo user0 = EmailPassword.signUp(process.main, "random1@gmail.com", "validPass123"); + UserIdMapping.createUserIdMapping(process.main, user0.id, "e0", null, false); + + Thread.sleep(1); // add a small delay to ensure a unique timestamp + + AuthRecipeUserInfo user = EmailPassword.signUp(process.main, "random@gmail.com", "validPass123"); + UserIdMapping.createUserIdMapping(process.main, user.id, "e1", null, false); + AuthRecipe.createPrimaryUser(process.main, user.id); + AuthRecipe.linkAccounts(process.main, user0.id, user.id); + + JsonObject responseBody = new JsonObject(); + responseBody.addProperty("email", "random@gmail.com"); + responseBody.addProperty("password", "validPass123"); + + Thread.sleep(1); // add a small delay to ensure a unique timestamp + long beforeSignIn = System.currentTimeMillis(); + + JsonObject signInResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signin", responseBody, 1000, 1000, null, SemVer.v4_0.get(), + "emailpassword"); + + assertEquals(signInResponse.get("status").getAsString(), "OK"); + assertEquals(signInResponse.entrySet().size(), 2); + + JsonObject jsonUser = signInResponse.get("user").getAsJsonObject(); + assert (jsonUser.get("id").getAsString().equals("e1")); + assert (jsonUser.get("timeJoined").getAsLong() == user0.timeJoined); + assert (jsonUser.get("isPrimaryUser").getAsBoolean()); + assert (jsonUser.get("emails").getAsJsonArray().size() == 2); + assert (jsonUser.get("emails").getAsJsonArray().get(0).getAsString().equals("random@gmail.com") || + jsonUser.get("emails").getAsJsonArray().get(1).getAsString().equals("random@gmail.com")); + assert (jsonUser.get("emails").getAsJsonArray().get(0).getAsString().equals("random1@gmail.com") || + jsonUser.get("emails").getAsJsonArray().get(1).getAsString().equals("random1@gmail.com")); + assert (!jsonUser.get("emails").getAsJsonArray().get(0).getAsString() + .equals(jsonUser.get("emails").getAsJsonArray().get(1).getAsString())); + assert (jsonUser.get("phoneNumbers").getAsJsonArray().size() == 0); + assert (jsonUser.get("thirdParty").getAsJsonArray().size() == 0); + assert (jsonUser.get("loginMethods").getAsJsonArray().size() == 2); + { + JsonObject lM = jsonUser.get("loginMethods").getAsJsonArray().get(1).getAsJsonObject(); + assertFalse(lM.get("verified").getAsBoolean()); + assertEquals(lM.get("timeJoined").getAsLong(), user.timeJoined); + assertEquals(lM.get("recipeUserId").getAsString(), "e1"); + assertEquals(lM.get("recipeId").getAsString(), "emailpassword"); + assertEquals(lM.get("email").getAsString(), "random@gmail.com"); + assert (lM.entrySet().size() == 6); + } + { + JsonObject lM = jsonUser.get("loginMethods").getAsJsonArray().get(0).getAsJsonObject(); + assertFalse(lM.get("verified").getAsBoolean()); + assertEquals(lM.get("timeJoined").getAsLong(), user0.timeJoined); + assertEquals(lM.get("recipeUserId").getAsString(), user0.id); + assertEquals(lM.get("recipeId").getAsString(), "emailpassword"); + assertEquals(lM.get("email").getAsString(), "random1@gmail.com"); + assert (lM.entrySet().size() == 6); + } + + int activeUsers = ActiveUsers.countUsersActiveSince(process.getProcess(), beforeSignIn); + assert (activeUsers == 1); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + +} From 2c7637c649a4a867543897f82de5762eeef1a6dc Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Fri, 4 Aug 2023 13:24:08 +0530 Subject: [PATCH 073/131] removes unnecessary comment --- .../test/emailpassword/api/SignInAPITest4_0.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/test/java/io/supertokens/test/emailpassword/api/SignInAPITest4_0.java b/src/test/java/io/supertokens/test/emailpassword/api/SignInAPITest4_0.java index 17d3fab9c..dfb2dc334 100644 --- a/src/test/java/io/supertokens/test/emailpassword/api/SignInAPITest4_0.java +++ b/src/test/java/io/supertokens/test/emailpassword/api/SignInAPITest4_0.java @@ -39,15 +39,6 @@ import static org.junit.Assert.*; -/* - * TODO: - * - Check for bad input (missing fields) - * - Check good input works - * - Test that sign in with unnormalised email like Test@gmail.com should also work - * - Test that giving an empty password, empty email, invalid email, random email or wrong password throws a wrong - * credentials error - * - Test that an empty password yields a WRONG_CREDENTIALS_ERROR output. - * */ public class SignInAPITest4_0 { From 2f868cffa924388467ae70f752512917bb50eb0c Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Fri, 4 Aug 2023 13:26:22 +0530 Subject: [PATCH 074/131] adds more tests --- .../emailpassword/api/SignUpAPITest4_0.java | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 src/test/java/io/supertokens/test/emailpassword/api/SignUpAPITest4_0.java diff --git a/src/test/java/io/supertokens/test/emailpassword/api/SignUpAPITest4_0.java b/src/test/java/io/supertokens/test/emailpassword/api/SignUpAPITest4_0.java new file mode 100644 index 000000000..223b37267 --- /dev/null +++ b/src/test/java/io/supertokens/test/emailpassword/api/SignUpAPITest4_0.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.test.emailpassword.api; + +import com.google.gson.JsonObject; +import io.supertokens.ActiveUsers; +import io.supertokens.ProcessState; +import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.storageLayer.StorageLayer; +import io.supertokens.test.TestingProcessManager; +import io.supertokens.test.Utils; +import io.supertokens.test.httpRequest.HttpRequestForTesting; +import io.supertokens.utils.SemVer; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; + +import static org.junit.Assert.*; + + +public class SignUpAPITest4_0 { + + @Rule + public TestRule watchman = Utils.getOnFailure(); + + @AfterClass + public static void afterTesting() { + Utils.afterTesting(); + } + + @Before + public void beforeEach() { + Utils.reset(); + } + + // Check good input works + @Test + public void testGoodInput() throws Exception { + String[] args = {"../"}; + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + JsonObject responseBody = new JsonObject(); + responseBody.addProperty("email", "random@gmail.com"); + responseBody.addProperty("password", "validPass123"); + + Thread.sleep(1); // add a small delay to ensure a unique timestamp + long beforeSignIn = System.currentTimeMillis(); + + JsonObject signInResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signup", responseBody, 1000, 1000, null, SemVer.v4_0.get(), + "emailpassword"); + + assertEquals(signInResponse.get("status").getAsString(), "OK"); + assertEquals(signInResponse.entrySet().size(), 2); + + JsonObject jsonUser = signInResponse.get("user").getAsJsonObject(); + assertNotNull(jsonUser.get("id")); + assertNotNull(jsonUser.get("timeJoined")); + assert (!jsonUser.get("isPrimaryUser").getAsBoolean()); + assert (jsonUser.get("emails").getAsJsonArray().size() == 1); + assert (jsonUser.get("emails").getAsJsonArray().get(0).getAsString().equals("random@gmail.com")); + assert (jsonUser.get("phoneNumbers").getAsJsonArray().size() == 0); + assert (jsonUser.get("thirdParty").getAsJsonArray().size() == 0); + assert (jsonUser.get("loginMethods").getAsJsonArray().size() == 1); + JsonObject lM = jsonUser.get("loginMethods").getAsJsonArray().get(0).getAsJsonObject(); + assertFalse(lM.get("verified").getAsBoolean()); + assertNotNull(lM.get("timeJoined")); + assertNotNull(lM.get("recipeUserId")); + assertEquals(lM.get("recipeId").getAsString(), "emailpassword"); + assertEquals(lM.get("email").getAsString(), "random@gmail.com"); + assert (lM.entrySet().size() == 6); + + int activeUsers = ActiveUsers.countUsersActiveSince(process.getProcess(), beforeSignIn); + assert (activeUsers == 1); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + +} From 33b6f27cc24d5266af6b1290f1b9e16b22f6b54e Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Fri, 4 Aug 2023 14:11:01 +0530 Subject: [PATCH 075/131] updates to updatemeailorpassword function --- .../emailpassword/EmailPassword.java | 41 ++++++++++++++++--- .../EmailChangeNotAllowedException.java | 21 ++++++++++ .../java/io/supertokens/inmemorydb/Start.java | 12 ------ .../queries/EmailPasswordQueries.java | 17 -------- .../webserver/api/emailpassword/UserAPI.java | 7 ++++ 5 files changed, 63 insertions(+), 35 deletions(-) create mode 100644 src/main/java/io/supertokens/emailpassword/exceptions/EmailChangeNotAllowedException.java diff --git a/src/main/java/io/supertokens/emailpassword/EmailPassword.java b/src/main/java/io/supertokens/emailpassword/EmailPassword.java index 5203e76de..91b52a305 100644 --- a/src/main/java/io/supertokens/emailpassword/EmailPassword.java +++ b/src/main/java/io/supertokens/emailpassword/EmailPassword.java @@ -20,6 +20,7 @@ import io.supertokens.authRecipe.AuthRecipe; import io.supertokens.config.Config; import io.supertokens.config.CoreConfig; +import io.supertokens.emailpassword.exceptions.EmailChangeNotAllowedException; import io.supertokens.emailpassword.exceptions.ResetPasswordInvalidTokenException; import io.supertokens.emailpassword.exceptions.UnsupportedPasswordHashingFormatException; import io.supertokens.emailpassword.exceptions.WrongCredentialsException; @@ -29,6 +30,7 @@ import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.authRecipe.LoginMethod; +import io.supertokens.pluginInterface.authRecipe.sqlStorage.AuthRecipeSQLStorage; import io.supertokens.pluginInterface.emailpassword.PasswordResetTokenInfo; import io.supertokens.pluginInterface.emailpassword.UserInfo; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; @@ -561,7 +563,7 @@ public static void updateUsersEmailOrPassword(Main main, @Nonnull String userId, @Nullable String email, @Nullable String password) throws StorageQueryException, StorageTransactionLogicException, - UnknownUserIdException, DuplicateEmailException { + UnknownUserIdException, DuplicateEmailException, EmailChangeNotAllowedException { try { Storage storage = StorageLayer.getStorage(main); updateUsersEmailOrPassword(new AppIdentifierWithStorage(null, null, storage), @@ -575,20 +577,45 @@ public static void updateUsersEmailOrPassword(AppIdentifierWithStorage appIdenti @Nonnull String userId, @Nullable String email, @Nullable String password) throws StorageQueryException, StorageTransactionLogicException, - UnknownUserIdException, DuplicateEmailException, TenantOrAppNotFoundException { + UnknownUserIdException, DuplicateEmailException, TenantOrAppNotFoundException, + EmailChangeNotAllowedException { EmailPasswordSQLStorage storage = appIdentifierWithStorage.getEmailPasswordStorage(); + AuthRecipeSQLStorage authRecipeStorage = (AuthRecipeSQLStorage) appIdentifierWithStorage.getAuthRecipeStorage(); try { storage.startTransaction(transaction -> { try { - boolean exists = storage.lockEmailPasswordTableUsingId_Transaction(appIdentifierWithStorage, - transaction, - userId); + AuthRecipeUserInfo user = authRecipeStorage.getPrimaryUserById_Transaction(appIdentifierWithStorage, + transaction, userId); - if (!exists) { + if (user == null) { throw new StorageTransactionLogicException(new UnknownUserIdException()); } if (email != null) { + if (user.isPrimaryUser) { + for (String tenantId : user.tenantIds) { + // we do not bother with getting the tenantIdentifierWithStorage 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. + TenantIdentifier tenantIdentifier = new TenantIdentifier( + appIdentifierWithStorage.getConnectionUriDomain(), + appIdentifierWithStorage.getAppId(), + tenantId); + + AuthRecipeUserInfo[] existingUsersWithNewEmail = + authRecipeStorage.listPrimaryUsersByEmail_Transaction( + tenantIdentifier, transaction, + email); + + for (AuthRecipeUserInfo userWithSameEmail : existingUsersWithNewEmail) { + if (userWithSameEmail.isPrimaryUser && !userWithSameEmail.id.equals(user.id)) { + throw new StorageTransactionLogicException( + new EmailChangeNotAllowedException()); + } + } + } + } + try { storage.updateUsersEmail_Transaction(appIdentifierWithStorage, transaction, userId, email); @@ -617,6 +644,8 @@ public static void updateUsersEmailOrPassword(AppIdentifierWithStorage appIdenti throw (DuplicateEmailException) e.actualException; } else if (e.actualException instanceof TenantOrAppNotFoundException) { throw (TenantOrAppNotFoundException) e.actualException; + } else if (e.actualException instanceof EmailChangeNotAllowedException) { + throw (EmailChangeNotAllowedException) e.actualException; } throw e; } diff --git a/src/main/java/io/supertokens/emailpassword/exceptions/EmailChangeNotAllowedException.java b/src/main/java/io/supertokens/emailpassword/exceptions/EmailChangeNotAllowedException.java new file mode 100644 index 000000000..e8660b592 --- /dev/null +++ b/src/main/java/io/supertokens/emailpassword/exceptions/EmailChangeNotAllowedException.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2020, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.emailpassword.exceptions; + +public class EmailChangeNotAllowedException extends Exception { + private static final long serialVersionUID = -7205953190075543040L; +} diff --git a/src/main/java/io/supertokens/inmemorydb/Start.java b/src/main/java/io/supertokens/inmemorydb/Start.java index 8b6b61d1d..a90666648 100644 --- a/src/main/java/io/supertokens/inmemorydb/Start.java +++ b/src/main/java/io/supertokens/inmemorydb/Start.java @@ -867,18 +867,6 @@ public void updateUsersEmail_Transaction(AppIdentifier appIdentifier, Transactio } } - @Override - public boolean lockEmailPasswordTableUsingId_Transaction(AppIdentifier appIdentifier, TransactionConnection con, - String userId) - throws StorageQueryException { - Connection sqlCon = (Connection) con.getConnection(); - try { - return EmailPasswordQueries.lockEmailPasswordTableUsingId_Transaction(this, sqlCon, appIdentifier, userId); - } catch (SQLException e) { - throw new StorageQueryException(e); - } - } - @Override public void deleteEmailPasswordUser_Transaction(TransactionConnection con, AppIdentifier appIdentifier, String userId, boolean deleteUserIdMappingToo) diff --git a/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java index 19eb0821e..3069a60fb 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java @@ -198,23 +198,6 @@ public static PasswordResetTokenInfo[] getAllPasswordResetTokenInfoForUser_Trans }); } - public static boolean lockEmailPasswordTableUsingId_Transaction(Start start, Connection con, - AppIdentifier appIdentifier, - String id) - throws SQLException, StorageQueryException { - - ((ConnectionWithLocks) con).lock( - appIdentifier.getAppId() + "~" + id + Config.getConfig(start).getEmailPasswordUsersTable()); - - String QUERY = "SELECT user_id FROM " - + getConfig(start).getEmailPasswordUsersTable() - + " WHERE app_id = ? AND user_id = ?"; - return execute(con, QUERY, pst -> { - pst.setString(1, appIdentifier.getAppId()); - pst.setString(2, id); - }, ResultSet::next); - } - public static PasswordResetTokenInfo getPasswordResetTokenInfo(Start start, AppIdentifier appIdentifier, String token) throws SQLException, StorageQueryException { diff --git a/src/main/java/io/supertokens/webserver/api/emailpassword/UserAPI.java b/src/main/java/io/supertokens/webserver/api/emailpassword/UserAPI.java index be9aaa9a4..bdfc432b8 100644 --- a/src/main/java/io/supertokens/webserver/api/emailpassword/UserAPI.java +++ b/src/main/java/io/supertokens/webserver/api/emailpassword/UserAPI.java @@ -20,6 +20,7 @@ import io.supertokens.AppIdentifierWithStorageAndUserIdMapping; import io.supertokens.Main; import io.supertokens.emailpassword.EmailPassword; +import io.supertokens.emailpassword.exceptions.EmailChangeNotAllowedException; import io.supertokens.output.Logging; import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; @@ -202,6 +203,12 @@ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IO JsonObject result = new JsonObject(); result.addProperty("status", "EMAIL_ALREADY_EXISTS_ERROR"); super.sendJsonResponse(200, result, resp); + } catch (EmailChangeNotAllowedException e) { + Logging.debug(main, appIdentifier.getAsPublicTenantIdentifier(), Utils.exceptionStacktraceToString(e)); + JsonObject result = new JsonObject(); + result.addProperty("status", "EMAIL_CHANGE_NOT_ALLOWED_ERROR"); + result.addProperty("reason", "New email is associated with another primary user ID"); + super.sendJsonResponse(200, result, resp); } } } From 63bf189a41f217ba2455d95d0cb1a8a6d4c8f3eb Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Fri, 4 Aug 2023 14:47:57 +0530 Subject: [PATCH 076/131] adds more tests --- .../test/emailpassword/EmailPasswordTest.java | 120 +++++++++++++++++- .../MultitenantEmailPasswordTest.java | 4 +- .../emailpassword/api/UserPutAPITest4_0.java | 91 +++++++++++++ 3 files changed, 211 insertions(+), 4 deletions(-) create mode 100644 src/test/java/io/supertokens/test/emailpassword/api/UserPutAPITest4_0.java diff --git a/src/test/java/io/supertokens/test/emailpassword/EmailPasswordTest.java b/src/test/java/io/supertokens/test/emailpassword/EmailPasswordTest.java index 0c6fc840c..2e3a7e8a7 100644 --- a/src/test/java/io/supertokens/test/emailpassword/EmailPasswordTest.java +++ b/src/test/java/io/supertokens/test/emailpassword/EmailPasswordTest.java @@ -16,15 +16,18 @@ package io.supertokens.test.emailpassword; +import com.google.gson.JsonObject; import io.supertokens.ProcessState; import io.supertokens.authRecipe.AuthRecipe; import io.supertokens.config.Config; import io.supertokens.emailpassword.EmailPassword; import io.supertokens.emailpassword.PasswordHashing; +import io.supertokens.emailpassword.exceptions.EmailChangeNotAllowedException; import io.supertokens.emailpassword.exceptions.ResetPasswordInvalidTokenException; import io.supertokens.emailpassword.exceptions.WrongCredentialsException; import io.supertokens.featureflag.EE_FEATURES; import io.supertokens.featureflag.FeatureFlagTestContent; +import io.supertokens.multitenancy.Multitenancy; import io.supertokens.pluginInterface.STORAGE_TYPE; import io.supertokens.pluginInterface.authRecipe.AuthRecipeStorage; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; @@ -35,9 +38,7 @@ import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateUserIdException; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.emailpassword.sqlStorage.EmailPasswordSQLStorage; -import io.supertokens.pluginInterface.multitenancy.AppIdentifier; -import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; -import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.*; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.TestingProcessManager; import io.supertokens.test.Utils; @@ -885,4 +886,117 @@ public void consumeCodeCorrectlySetsTheUserEmailForOlderTokens() throws Exceptio process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); } + + + @Test + public void updateEmailFailsIfEmailUsedByOtherPrimaryUserInSameTenant() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + UserInfo user0 = EmailPassword.signUp(process.getProcess(), "someemail1@gmail.com", "somePass"); + AuthRecipe.createPrimaryUser(process.main, user0.id); + + UserInfo user = EmailPassword.signUp(process.getProcess(), "someemail@gmail.com", "somePass"); + AuthRecipe.createPrimaryUser(process.main, user.id); + + try { + EmailPassword.updateUsersEmailOrPassword(process.main, user.id, "someemail1@gmail.com", null); + assert (false); + } catch (EmailChangeNotAllowedException ignored) { + + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void updateEmailSucceedsIfEmailUsedByOtherPrimaryUserInDifferentTenantWhichThisUserIsNotAPartOf() + throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + Multitenancy.addNewOrUpdateAppOrTenant(process.main, new TenantIdentifier(null, null, null), + new TenantConfig(new TenantIdentifier(null, null, "t1"), new EmailPasswordConfig(true), + new ThirdPartyConfig(true, new ThirdPartyConfig.Provider[0]), new PasswordlessConfig(true), + new JsonObject())); + + TenantIdentifierWithStorage tenantIdentifierWithStorage = new TenantIdentifierWithStorage(null, null, "t1", + StorageLayer.getStorage(process.main)); + AuthRecipeUserInfo user0 = EmailPassword.signUp(tenantIdentifierWithStorage, process.getProcess(), + "someemail1@gmail.com", + "pass1234"); + + AuthRecipe.createPrimaryUser(process.main, user0.id); + + UserInfo user = EmailPassword.signUp(process.getProcess(), "someemail@gmail.com", "somePass"); + AuthRecipe.createPrimaryUser(process.main, user.id); + + EmailPassword.updateUsersEmailOrPassword(process.main, user.id, "someemail1@gmail.com", null); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void updateEmailFailsIfEmailUsedByOtherPrimaryUserInDifferentTenant() + throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + Multitenancy.addNewOrUpdateAppOrTenant(process.main, new TenantIdentifier(null, null, null), + new TenantConfig(new TenantIdentifier(null, null, "t1"), new EmailPasswordConfig(true), + new ThirdPartyConfig(true, new ThirdPartyConfig.Provider[0]), new PasswordlessConfig(true), + new JsonObject())); + + TenantIdentifierWithStorage tenantIdentifierWithStorage = new TenantIdentifierWithStorage(null, null, "t1", + StorageLayer.getStorage(process.main)); + AuthRecipeUserInfo user0 = EmailPassword.signUp(tenantIdentifierWithStorage, process.getProcess(), + "someemail1@gmail.com", + "pass1234"); + + AuthRecipe.createPrimaryUser(process.main, user0.id); + + UserInfo user = EmailPassword.signUp(process.getProcess(), "someemail@gmail.com", "somePass"); + AuthRecipe.createPrimaryUser(process.main, user.id); + + Multitenancy.addUserIdToTenant(process.main, tenantIdentifierWithStorage, user.id); + + try { + EmailPassword.updateUsersEmailOrPassword(process.main, user.id, "someemail1@gmail.com", null); + assert (false); + } catch (EmailChangeNotAllowedException ignored) { + + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } } diff --git a/src/test/java/io/supertokens/test/emailpassword/MultitenantEmailPasswordTest.java b/src/test/java/io/supertokens/test/emailpassword/MultitenantEmailPasswordTest.java index e5a81bcde..5414e404e 100644 --- a/src/test/java/io/supertokens/test/emailpassword/MultitenantEmailPasswordTest.java +++ b/src/test/java/io/supertokens/test/emailpassword/MultitenantEmailPasswordTest.java @@ -19,6 +19,7 @@ import com.google.gson.JsonObject; import io.supertokens.ProcessState; import io.supertokens.emailpassword.EmailPassword; +import io.supertokens.emailpassword.exceptions.EmailChangeNotAllowedException; import io.supertokens.emailpassword.exceptions.WrongCredentialsException; import io.supertokens.featureflag.EE_FEATURES; import io.supertokens.featureflag.FeatureFlagTestContent; @@ -351,7 +352,8 @@ public void testUpdatePasswordWorksCorrectlyAcrossAllTenants() throws InterruptedException, InvalidProviderConfigException, StorageQueryException, FeatureNotEnabledException, TenantOrAppNotFoundException, IOException, InvalidConfigException, CannotModifyBaseConfigException, BadPermissionException, DuplicateEmailException, - UnknownUserIdException, StorageTransactionLogicException, WrongCredentialsException { + UnknownUserIdException, StorageTransactionLogicException, WrongCredentialsException, + EmailChangeNotAllowedException { String[] args = {"../"}; TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); diff --git a/src/test/java/io/supertokens/test/emailpassword/api/UserPutAPITest4_0.java b/src/test/java/io/supertokens/test/emailpassword/api/UserPutAPITest4_0.java new file mode 100644 index 000000000..66b6893c8 --- /dev/null +++ b/src/test/java/io/supertokens/test/emailpassword/api/UserPutAPITest4_0.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.test.emailpassword.api; + +import com.google.gson.JsonObject; +import io.supertokens.ProcessState; +import io.supertokens.authRecipe.AuthRecipe; +import io.supertokens.emailpassword.EmailPassword; +import io.supertokens.featureflag.EE_FEATURES; +import io.supertokens.featureflag.FeatureFlagTestContent; +import io.supertokens.pluginInterface.RECIPE_ID; +import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.emailpassword.UserInfo; +import io.supertokens.storageLayer.StorageLayer; +import io.supertokens.test.TestingProcessManager; +import io.supertokens.test.Utils; +import io.supertokens.test.httpRequest.HttpRequestForTesting; +import io.supertokens.utils.SemVer; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +public class UserPutAPITest4_0 { + @Rule + public TestRule watchman = Utils.getOnFailure(); + + @AfterClass + public static void afterTesting() { + Utils.afterTesting(); + } + + @Before + public void beforeEach() { + Utils.reset(); + } + + @Test + public void testThatAPIReturnsEmailUpdateNotPossibleWithSingleTenant() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + UserInfo user0 = EmailPassword.signUp(process.getProcess(), "someemail1@gmail.com", "somePass"); + AuthRecipe.createPrimaryUser(process.main, user0.id); + + UserInfo user = EmailPassword.signUp(process.getProcess(), "someemail@gmail.com", "somePass"); + AuthRecipe.createPrimaryUser(process.main, user.id); + + JsonObject body = new JsonObject(); + body.addProperty("userId", user.id); + body.addProperty("email", "someemail1@gmail.com"); + + JsonObject response = HttpRequestForTesting.sendJsonPUTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/user", body, 1000, 1000, null, SemVer.v4_0.get(), + RECIPE_ID.EMAIL_PASSWORD.toString()); + + assertEquals("EMAIL_CHANGE_NOT_ALLOWED_ERROR", response.get("status").getAsString()); + assertEquals("New email is associated with another primary user ID", response.get("reason").getAsString()); + assertEquals(2, response.entrySet().size()); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } +} From 3b1af33453eb4c33945b3bfc2f57c88af3b74480 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Fri, 4 Aug 2023 15:49:01 +0530 Subject: [PATCH 077/131] updates tp sign in to check for email update allowance --- .../java/io/supertokens/inmemorydb/Start.java | 15 --- .../inmemorydb/queries/ThirdPartyQueries.java | 23 ---- .../io/supertokens/thirdparty/ThirdParty.java | 111 +++++++++++++----- .../webserver/api/thirdparty/SignInUpAPI.java | 10 +- 4 files changed, 86 insertions(+), 73 deletions(-) diff --git a/src/main/java/io/supertokens/inmemorydb/Start.java b/src/main/java/io/supertokens/inmemorydb/Start.java index a90666648..1cd5a6b34 100644 --- a/src/main/java/io/supertokens/inmemorydb/Start.java +++ b/src/main/java/io/supertokens/inmemorydb/Start.java @@ -1053,21 +1053,6 @@ public void deleteExpiredPasswordResetTokens() throws StorageQueryException { } } - @Override - public String getEmailUsingThirdPartyInfo_Transaction( - AppIdentifier appIdentifier, TransactionConnection con, - String thirdPartyId, - String thirdPartyUserId) - throws StorageQueryException { - Connection sqlCon = (Connection) con.getConnection(); - try { - return ThirdPartyQueries.getEmailUsingThirdPartyInfo_Transaction(this, sqlCon, appIdentifier, thirdPartyId, - thirdPartyUserId); - } catch (SQLException e) { - throw new StorageQueryException(e); - } - } - @Override public void updateUserEmail_Transaction(AppIdentifier appIdentifier, TransactionConnection con, String thirdPartyId, String thirdPartyUserId, diff --git a/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java index 3d325eb72..9e94b4814 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java @@ -297,29 +297,6 @@ public static void updateUserEmail_Transaction(Start start, Connection con, AppI }); } - public static String getEmailUsingThirdPartyInfo_Transaction(Start start, Connection con, - AppIdentifier appIdentifier, String thirdPartyId, - String thirdPartyUserId) - throws SQLException, StorageQueryException { - - ((ConnectionWithLocks) con).lock(appIdentifier.getAppId() + "~" + thirdPartyId + "~" + thirdPartyUserId + - Config.getConfig(start).getThirdPartyUsersTable()); - - String QUERY = "SELECT email FROM " - + getConfig(start).getThirdPartyUsersTable() - + " WHERE app_id = ? AND third_party_id = ? AND third_party_user_id = ?"; - return execute(con, QUERY, pst -> { - pst.setString(1, appIdentifier.getAppId()); - pst.setString(2, thirdPartyId); - pst.setString(3, thirdPartyUserId); - }, result -> { - if (result.next()) { - return result.getString("email"); - } - return null; - }); - } - private static UserInfoPartial getUserInfoUsingUserId(Start start, Connection con, AppIdentifier appIdentifier, String userId) throws SQLException, StorageQueryException { diff --git a/src/main/java/io/supertokens/thirdparty/ThirdParty.java b/src/main/java/io/supertokens/thirdparty/ThirdParty.java index 0eda2689d..28a55f8cc 100644 --- a/src/main/java/io/supertokens/thirdparty/ThirdParty.java +++ b/src/main/java/io/supertokens/thirdparty/ThirdParty.java @@ -17,12 +17,14 @@ package io.supertokens.thirdparty; import io.supertokens.Main; +import io.supertokens.emailpassword.exceptions.EmailChangeNotAllowedException; import io.supertokens.multitenancy.Multitenancy; import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.authRecipe.LoginMethod; +import io.supertokens.pluginInterface.authRecipe.sqlStorage.AuthRecipeSQLStorage; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; import io.supertokens.pluginInterface.multitenancy.*; @@ -61,19 +63,25 @@ public static SignInUpResponse signInUp2_7(TenantIdentifierWithStorage tenantIde String thirdPartyId, String thirdPartyUserId, String email, boolean isEmailVerified) throws StorageQueryException, TenantOrAppNotFoundException { - SignInUpResponse response = signInUpHelper(tenantIdentifierWithStorage, main, thirdPartyId, thirdPartyUserId, - email); + SignInUpResponse response = null; + try { + response = signInUpHelper(tenantIdentifierWithStorage, main, thirdPartyId, thirdPartyUserId, + email); + } catch (EmailChangeNotAllowedException e) { + throw new RuntimeException(e); + } if (isEmailVerified) { try { + SignInUpResponse finalResponse = response; tenantIdentifierWithStorage.getEmailVerificationStorage().startTransaction(con -> { try { // this assert is there cause this function should only be used for older CDIs in which // account linking was not available. So loginMethod length will always be 1. - assert (response.user.loginMethods.length == 1); + assert (finalResponse.user.loginMethods.length == 1); tenantIdentifierWithStorage.getEmailVerificationStorage() .updateIsEmailVerified_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), con, - response.user.id, response.user.loginMethods[0].email, true); + finalResponse.user.id, finalResponse.user.loginMethods[0].email, true); tenantIdentifierWithStorage.getEmailVerificationStorage() .commitTransaction(con); return null; @@ -108,7 +116,7 @@ public static SignInUpResponse signInUp2_7(Main main, @TestOnly public static SignInUpResponse signInUp(Main main, String thirdPartyId, String thirdPartyUserId, String email) - throws StorageQueryException { + throws StorageQueryException, EmailChangeNotAllowedException { try { Storage storage = StorageLayer.getStorage(main); return signInUp( @@ -122,7 +130,8 @@ public static SignInUpResponse signInUp(Main main, String thirdPartyId, String t public static SignInUpResponse signInUp(TenantIdentifierWithStorage tenantIdentifierWithStorage, Main main, String thirdPartyId, String thirdPartyUserId, String email) - throws StorageQueryException, TenantOrAppNotFoundException, BadPermissionException { + throws StorageQueryException, TenantOrAppNotFoundException, BadPermissionException, + EmailChangeNotAllowedException { TenantConfig config = Multitenancy.getTenantInfo(main, tenantIdentifierWithStorage); if (config == null) { @@ -138,7 +147,7 @@ public static SignInUpResponse signInUp(TenantIdentifierWithStorage tenantIdenti private static SignInUpResponse signInUpHelper(TenantIdentifierWithStorage tenantIdentifierWithStorage, Main main, String thirdPartyId, String thirdPartyUserId, String email) throws StorageQueryException, - TenantOrAppNotFoundException { + TenantOrAppNotFoundException, EmailChangeNotAllowedException { ThirdPartySQLStorage storage = tenantIdentifierWithStorage.getThirdPartyStorage(); while (true) { // loop for sign in + sign up @@ -163,38 +172,76 @@ private static SignInUpResponse signInUpHelper(TenantIdentifierWithStorage tenan // we try to get user and update their email try { - // We should update the user email based on thirdPartyId and thirdPartyUserId across all apps, - // so we iterate through all the app storages and do the update. - // Note that we are only locking for each storage, and no global app wide lock, but should be okay - // because same user parallelly logging into different tenants at the same time with different email - // is a rare situation AppIdentifier appIdentifier = tenantIdentifierWithStorage.toAppIdentifier(); - Storage[] storages = StorageLayer.getStoragesForApp(main, appIdentifier); - for (Storage st : storages) { - storage.startTransaction(con -> { - String emailFromDb = storage.getEmailUsingThirdPartyInfo_Transaction( - appIdentifier.withStorage(st), - con, - thirdPartyId, thirdPartyUserId); - - if (emailFromDb == null) { - storage.commitTransaction(con); - return null; - } + AuthRecipeSQLStorage authRecipeStorage = + (AuthRecipeSQLStorage) tenantIdentifierWithStorage.getAuthRecipeStorage(); - if (!email.equals(emailFromDb)) { - storage.updateUserEmail_Transaction(appIdentifier.withStorage(st), con, - thirdPartyId, thirdPartyUserId, email); - } + storage.startTransaction(con -> { + AuthRecipeUserInfo userFromDb = authRecipeStorage.getPrimaryUsersByThirdPartyInfo_Transaction( + tenantIdentifierWithStorage, + con, + thirdPartyId, thirdPartyUserId); + if (userFromDb == null) { storage.commitTransaction(con); return null; - }); - } + } + + LoginMethod lM = null; + for (LoginMethod loginMethod : userFromDb.loginMethods) { + if (loginMethod.thirdParty != null && loginMethod.thirdParty.id.equals(thirdPartyId) && + loginMethod.thirdParty.userId.equals(thirdPartyUserId)) { + lM = loginMethod; + break; + } + } + + if (lM == null) { + throw new IllegalStateException("Should never come here"); + } + + + if (!email.equals(lM.email)) { + // before updating the email, we must check for if another primary user has the same + // email, and if they do, then we do not allow the update. + if (userFromDb.isPrimaryUser) { + for (String tenantId : userFromDb.tenantIds) { + // we do not bother with getting the tenantIdentifierWithStorage 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. + TenantIdentifier tenantIdentifier = new TenantIdentifier( + tenantIdentifierWithStorage.getConnectionUriDomain(), + tenantIdentifierWithStorage.getAppId(), + tenantId); + + AuthRecipeUserInfo[] userBasedOnEmail = + authRecipeStorage.listPrimaryUsersByEmail_Transaction( + tenantIdentifier, con, email + ); + for (AuthRecipeUserInfo userWithSameEmail : userBasedOnEmail) { + if (userWithSameEmail.isPrimaryUser && + !userWithSameEmail.id.equals(userFromDb.id)) { + throw new StorageTransactionLogicException( + new EmailChangeNotAllowedException()); + } + } + } + } + storage.updateUserEmail_Transaction(appIdentifier, con, + thirdPartyId, thirdPartyUserId, email); + } + + storage.commitTransaction(con); + return null; + }); AuthRecipeUserInfo user = getUser(tenantIdentifierWithStorage, thirdPartyId, thirdPartyUserId); return new SignInUpResponse(false, user); - } catch (StorageTransactionLogicException ignored) { + } catch (StorageTransactionLogicException e) { + if (e.actualException instanceof EmailChangeNotAllowedException) { + throw (EmailChangeNotAllowedException) e.actualException; + } + throw new StorageQueryException(e); } // retry.. @@ -232,7 +279,7 @@ public static AuthRecipeUserInfo getUser(TenantIdentifierWithStorage tenantIdent return tenantIdentifierWithStorage.getThirdPartyStorage() .getPrimaryUserByThirdPartyInfo(tenantIdentifierWithStorage, thirdPartyId, thirdPartyUserId); } - + @TestOnly public static AuthRecipeUserInfo getUser(Main main, String thirdPartyId, String thirdPartyUserId) throws StorageQueryException { diff --git a/src/main/java/io/supertokens/webserver/api/thirdparty/SignInUpAPI.java b/src/main/java/io/supertokens/webserver/api/thirdparty/SignInUpAPI.java index cbed5b1a1..db04cf5db 100644 --- a/src/main/java/io/supertokens/webserver/api/thirdparty/SignInUpAPI.java +++ b/src/main/java/io/supertokens/webserver/api/thirdparty/SignInUpAPI.java @@ -19,6 +19,7 @@ import com.google.gson.JsonObject; import io.supertokens.ActiveUsers; import io.supertokens.Main; +import io.supertokens.emailpassword.exceptions.EmailChangeNotAllowedException; import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.exceptions.StorageQueryException; @@ -81,9 +82,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I JsonObject result = new JsonObject(); result.addProperty("status", "OK"); result.addProperty("createdNewUser", response.createdNewUser); - JsonObject userJson = - getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v4_0) ? response.user.toJson() : - response.user.toJsonWithoutAccountLinking(); + JsonObject userJson = response.user.toJsonWithoutAccountLinking(); if (getVersionFromRequest(req).lesserThan(SemVer.v3_0)) { userJson.remove("tenantIds"); @@ -146,6 +145,11 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I } catch (StorageQueryException | TenantOrAppNotFoundException | BadPermissionException e) { throw new ServletException(e); + } catch (EmailChangeNotAllowedException e) { + JsonObject result = new JsonObject(); + result.addProperty("status", "OK"); + result.addProperty("reason", "Email already associated with another primary user."); + super.sendJsonResponse(200, result, resp); } } From f08a9a458a3e8ae09f8d06c7cfec1ec79c9cc808 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Fri, 4 Aug 2023 16:39:08 +0530 Subject: [PATCH 078/131] more tests --- .../webserver/api/thirdparty/SignInUpAPI.java | 2 +- .../test/authRecipe/MultitenantAPITest.java | 26 ++- .../test/authRecipe/UserPaginationTest.java | 36 ++-- .../TestTenantIdIsNotPresentForOlderCDI.java | 45 +++-- .../api/ThirdPartySignInUpAPITest4_0.java | 161 ++++++++++++++++++ 5 files changed, 234 insertions(+), 36 deletions(-) create mode 100644 src/test/java/io/supertokens/test/thirdparty/api/ThirdPartySignInUpAPITest4_0.java diff --git a/src/main/java/io/supertokens/webserver/api/thirdparty/SignInUpAPI.java b/src/main/java/io/supertokens/webserver/api/thirdparty/SignInUpAPI.java index db04cf5db..1ea445584 100644 --- a/src/main/java/io/supertokens/webserver/api/thirdparty/SignInUpAPI.java +++ b/src/main/java/io/supertokens/webserver/api/thirdparty/SignInUpAPI.java @@ -147,7 +147,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I throw new ServletException(e); } catch (EmailChangeNotAllowedException e) { JsonObject result = new JsonObject(); - result.addProperty("status", "OK"); + result.addProperty("status", "EMAIL_CHANGE_NOT_ALLOWED_ERROR"); result.addProperty("reason", "Email already associated with another primary user."); super.sendJsonResponse(200, result, resp); } diff --git a/src/test/java/io/supertokens/test/authRecipe/MultitenantAPITest.java b/src/test/java/io/supertokens/test/authRecipe/MultitenantAPITest.java index 51056e96b..ddc0d1cff 100644 --- a/src/test/java/io/supertokens/test/authRecipe/MultitenantAPITest.java +++ b/src/test/java/io/supertokens/test/authRecipe/MultitenantAPITest.java @@ -21,6 +21,7 @@ import com.google.gson.JsonObject; import io.supertokens.ProcessState; import io.supertokens.emailpassword.EmailPassword; +import io.supertokens.emailpassword.exceptions.EmailChangeNotAllowedException; import io.supertokens.featureflag.EE_FEATURES; import io.supertokens.featureflag.FeatureFlagTestContent; import io.supertokens.featureflag.exceptions.FeatureNotEnabledException; @@ -175,7 +176,8 @@ private void createUsers() throws TenantOrAppNotFoundException, DuplicateEmailException, StorageQueryException, BadPermissionException, DuplicateLinkCodeHashException, NoSuchAlgorithmException, IOException, RestartFlowException, InvalidKeyException, Base64EncodingException, DeviceIdHashMismatchException, - StorageTransactionLogicException, IncorrectUserInputCodeException, ExpiredUserInputCodeException { + StorageTransactionLogicException, IncorrectUserInputCodeException, ExpiredUserInputCodeException, + EmailChangeNotAllowedException { tenantToUsers = new HashMap<>(); recipeToUsers = new HashMap<>(); @@ -236,7 +238,8 @@ private void createUsers() null, null, "abcd" ); - Passwordless.ConsumeCodeResponse response = Passwordless.consumeCode(tenantIdentifierWithStorage, process.getProcess(), codeResponse.deviceId, + Passwordless.ConsumeCodeResponse response = Passwordless.consumeCode(tenantIdentifierWithStorage, + process.getProcess(), codeResponse.deviceId, codeResponse.deviceIdHash, "abcd", null); tenantToUsers.get(tenant).add(response.user.id); recipeToUsers.get("passwordless").add(response.user.id); @@ -249,7 +252,8 @@ private void createUsers() "+1234567890", null, "abcd" ); - Passwordless.ConsumeCodeResponse response = Passwordless.consumeCode(tenantIdentifierWithStorage, process.getProcess(), codeResponse.deviceId, + Passwordless.ConsumeCodeResponse response = Passwordless.consumeCode(tenantIdentifierWithStorage, + process.getProcess(), codeResponse.deviceId, codeResponse.deviceIdHash, "abcd", null); tenantToUsers.get(tenant).add(response.user.id); recipeToUsers.get("passwordless").add(response.user.id); @@ -262,7 +266,8 @@ private void createUsers() "+9876543210", null, "abcd" ); - Passwordless.ConsumeCodeResponse response = Passwordless.consumeCode(tenantIdentifierWithStorage, process.getProcess(), codeResponse.deviceId, + Passwordless.ConsumeCodeResponse response = Passwordless.consumeCode(tenantIdentifierWithStorage, + process.getProcess(), codeResponse.deviceId, codeResponse.deviceIdHash, "abcd", null); tenantToUsers.get(tenant).add(response.user.id); recipeToUsers.get("passwordless").add(response.user.id); @@ -303,7 +308,7 @@ private void createUsers() } } - private long getUserCount(TenantIdentifier tenantIdentifier, String []recipeIds, boolean includeAllTenants) + private long getUserCount(TenantIdentifier tenantIdentifier, String[] recipeIds, boolean includeAllTenants) throws HttpResponseException, IOException { HashMap params = new HashMap<>(); if (recipeIds != null) { @@ -323,7 +328,7 @@ private long getUserCount(TenantIdentifier tenantIdentifier, String []recipeIds, return response.get("count").getAsLong(); } - private String[] getUsers(TenantIdentifier tenantIdentifier, String []recipeIds) + private String[] getUsers(TenantIdentifier tenantIdentifier, String[] recipeIds) throws HttpResponseException, IOException { HashMap params = new HashMap<>(); if (recipeIds != null) { @@ -347,7 +352,8 @@ private String[] getUsers(TenantIdentifier tenantIdentifier, String []recipeIds) return userIds; } - private String[] getUsers(TenantIdentifier tenantIdentifier, String[] emails, String[] phoneNumbers, String[] providers) + private String[] getUsers(TenantIdentifier tenantIdentifier, String[] emails, String[] phoneNumbers, + String[] providers) throws HttpResponseException, IOException { HashMap params = new HashMap<>(); if (emails != null) { @@ -447,7 +453,8 @@ public void testGetUsers() throws Exception { String[] users = getUsers(tenant, new String[]{"emailpassword", "passwordless"}); for (String user : users) { assertTrue(tenantToUsers.get(tenant).contains(user)); - assertTrue(recipeToUsers.get("emailpassword").contains(user) || recipeToUsers.get("passwordless").contains(user)); + assertTrue(recipeToUsers.get("emailpassword").contains(user) || + recipeToUsers.get("passwordless").contains(user)); } } @@ -455,7 +462,8 @@ public void testGetUsers() throws Exception { String[] users = getUsers(tenant, new String[]{"thirdparty", "passwordless"}); for (String user : users) { assertTrue(tenantToUsers.get(tenant).contains(user)); - assertTrue(recipeToUsers.get("thirdparty").contains(user) || recipeToUsers.get("passwordless").contains(user)); + assertTrue(recipeToUsers.get("thirdparty").contains(user) || + recipeToUsers.get("passwordless").contains(user)); } } } diff --git a/src/test/java/io/supertokens/test/authRecipe/UserPaginationTest.java b/src/test/java/io/supertokens/test/authRecipe/UserPaginationTest.java index 5db09f1f6..8f875cd2c 100644 --- a/src/test/java/io/supertokens/test/authRecipe/UserPaginationTest.java +++ b/src/test/java/io/supertokens/test/authRecipe/UserPaginationTest.java @@ -21,6 +21,7 @@ import com.google.gson.JsonObject; import io.supertokens.ProcessState; import io.supertokens.emailpassword.EmailPassword; +import io.supertokens.emailpassword.exceptions.EmailChangeNotAllowedException; import io.supertokens.featureflag.EE_FEATURES; import io.supertokens.featureflag.FeatureFlagTestContent; import io.supertokens.featureflag.exceptions.FeatureNotEnabledException; @@ -44,13 +45,18 @@ import io.supertokens.test.multitenant.api.TestMultitenancyAPIHelper; import io.supertokens.thirdparty.InvalidProviderConfigException; import io.supertokens.thirdparty.ThirdParty; -import org.junit.*; -import org.junit.rules.TestRule; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Test; import java.io.IOException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; import static org.junit.Assert.*; @@ -168,13 +174,15 @@ private void createUsers(TenantIdentifier tenantIdentifier, int numUsers, String throws TenantOrAppNotFoundException, DuplicateEmailException, StorageQueryException, BadPermissionException, DuplicateLinkCodeHashException, NoSuchAlgorithmException, IOException, RestartFlowException, InvalidKeyException, Base64EncodingException, DeviceIdHashMismatchException, - StorageTransactionLogicException, IncorrectUserInputCodeException, ExpiredUserInputCodeException { + StorageTransactionLogicException, IncorrectUserInputCodeException, ExpiredUserInputCodeException, + EmailChangeNotAllowedException { if (tenantToUsers.get(tenantIdentifier) == null) { tenantToUsers.put(tenantIdentifier, new ArrayList<>()); } - TenantIdentifierWithStorage tenantIdentifierWithStorage = tenantIdentifier.withStorage(StorageLayer.getStorage(tenantIdentifier, process.getProcess())); + TenantIdentifierWithStorage tenantIdentifierWithStorage = tenantIdentifier.withStorage( + StorageLayer.getStorage(tenantIdentifier, process.getProcess())); for (int i = 0; i < numUsers; i++) { { UserInfo user = EmailPassword.signUp( @@ -241,7 +249,8 @@ public void testUserPaginationWorksCorrectlyForEachTenant() throws Exception { { // All recipes Set userIdSet = new HashSet<>(); - JsonObject userList = TestMultitenancyAPIHelper.listUsers(tenantIdentifier, null, "10", null, process.getProcess()); + JsonObject userList = TestMultitenancyAPIHelper.listUsers(tenantIdentifier, null, "10", null, + process.getProcess()); String paginationToken = userList.get("nextPaginationToken").getAsString(); JsonArray users = userList.get("users").getAsJsonArray(); @@ -254,7 +263,8 @@ public void testUserPaginationWorksCorrectlyForEachTenant() throws Exception { } while (paginationToken != null) { - userList = TestMultitenancyAPIHelper.listUsers(tenantIdentifier, paginationToken, "10", null, process.getProcess()); + userList = TestMultitenancyAPIHelper.listUsers(tenantIdentifier, paginationToken, "10", null, + process.getProcess()); users = userList.get("users").getAsJsonArray(); for (JsonElement user : users) { @@ -275,7 +285,8 @@ public void testUserPaginationWorksCorrectlyForEachTenant() throws Exception { } { // recipe combinations - String[] combinations = new String[]{"emailpassword", "passwordless", "thirdparty", "emailpassword,passwordless", "emailpassword,thirdparty", "passwordless,thirdparty"}; + String[] combinations = new String[]{"emailpassword", "passwordless", "thirdparty", + "emailpassword,passwordless", "emailpassword,thirdparty", "passwordless,thirdparty"}; int[] userCounts = new int[]{50, 50, 100, 100, 150, 150}; for (int i = 0; i < combinations.length; i++) { @@ -284,7 +295,8 @@ public void testUserPaginationWorksCorrectlyForEachTenant() throws Exception { Set userIdSet = new HashSet<>(); - JsonObject userList = TestMultitenancyAPIHelper.listUsers(tenantIdentifier, null, "10", includeRecipeIds, process.getProcess()); + JsonObject userList = TestMultitenancyAPIHelper.listUsers(tenantIdentifier, null, "10", + includeRecipeIds, process.getProcess()); String paginationToken = userList.get("nextPaginationToken").getAsString(); JsonArray users = userList.get("users").getAsJsonArray(); @@ -300,11 +312,13 @@ public void testUserPaginationWorksCorrectlyForEachTenant() throws Exception { } while (paginationToken != null) { - userList = TestMultitenancyAPIHelper.listUsers(tenantIdentifier, paginationToken, "10", includeRecipeIds, process.getProcess()); + userList = TestMultitenancyAPIHelper.listUsers(tenantIdentifier, paginationToken, "10", + includeRecipeIds, process.getProcess()); users = userList.get("users").getAsJsonArray(); for (JsonElement user : users) { - String userId = user.getAsJsonObject().get("user").getAsJsonObject().get("id").getAsString(); + String userId = user.getAsJsonObject().get("user").getAsJsonObject().get("id") + .getAsString(); String recipeId = user.getAsJsonObject().get("recipeId").getAsString(); assertFalse(userIdSet.contains(userId)); userIdSet.add(userId); diff --git a/src/test/java/io/supertokens/test/multitenant/api/TestTenantIdIsNotPresentForOlderCDI.java b/src/test/java/io/supertokens/test/multitenant/api/TestTenantIdIsNotPresentForOlderCDI.java index 3bfc81caf..98bccfaa4 100644 --- a/src/test/java/io/supertokens/test/multitenant/api/TestTenantIdIsNotPresentForOlderCDI.java +++ b/src/test/java/io/supertokens/test/multitenant/api/TestTenantIdIsNotPresentForOlderCDI.java @@ -22,6 +22,7 @@ import io.supertokens.Main; import io.supertokens.ProcessState; import io.supertokens.emailpassword.EmailPassword; +import io.supertokens.emailpassword.exceptions.EmailChangeNotAllowedException; import io.supertokens.featureflag.EE_FEATURES; import io.supertokens.featureflag.FeatureFlagTestContent; import io.supertokens.featureflag.exceptions.FeatureNotEnabledException; @@ -270,7 +271,8 @@ private void createUsers(TenantIdentifier tenantIdentifier, int numUsers, String throws TenantOrAppNotFoundException, DuplicateEmailException, StorageQueryException, BadPermissionException, DuplicateLinkCodeHashException, NoSuchAlgorithmException, IOException, RestartFlowException, InvalidKeyException, Base64EncodingException, DeviceIdHashMismatchException, - StorageTransactionLogicException, IncorrectUserInputCodeException, ExpiredUserInputCodeException { + StorageTransactionLogicException, IncorrectUserInputCodeException, ExpiredUserInputCodeException, + EmailChangeNotAllowedException { HashMap> tenantToUsers = new HashMap<>(); HashMap> recipeToUsers = new HashMap<>(); @@ -279,7 +281,8 @@ private void createUsers(TenantIdentifier tenantIdentifier, int numUsers, String tenantToUsers.put(tenantIdentifier, new ArrayList<>()); } - TenantIdentifierWithStorage tenantIdentifierWithStorage = tenantIdentifier.withStorage(StorageLayer.getStorage(tenantIdentifier, process.getProcess())); + TenantIdentifierWithStorage tenantIdentifierWithStorage = tenantIdentifier.withStorage( + StorageLayer.getStorage(tenantIdentifier, process.getProcess())); for (int i = 0; i < numUsers; i++) { { UserInfo user = EmailPassword.signUp( @@ -327,7 +330,8 @@ private void createUsers(TenantIdentifier tenantIdentifier, int numUsers, String } } - public static JsonObject listUsers(TenantIdentifier sourceTenant, String paginationToken, String limit, String includeRecipeIds, Main main) + public static JsonObject listUsers(TenantIdentifier sourceTenant, String paginationToken, String limit, + String includeRecipeIds, Main main) throws HttpResponseException, IOException { Map params = new HashMap<>(); if (paginationToken != null) { @@ -396,7 +400,8 @@ public void testUserPaginationUserObjectsDontHaveTenantIdsInOlderCDIVersion() th } { // recipe combinations - String[] combinations = new String[]{"emailpassword", "passwordless", "thirdparty", "emailpassword,passwordless", "emailpassword,thirdparty", "passwordless,thirdparty"}; + String[] combinations = new String[]{"emailpassword", "passwordless", "thirdparty", + "emailpassword,passwordless", "emailpassword,thirdparty", "passwordless,thirdparty"}; int[] userCounts = new int[]{50, 50, 100, 100, 150, 150}; for (int i = 0; i < combinations.length; i++) { @@ -405,7 +410,8 @@ public void testUserPaginationUserObjectsDontHaveTenantIdsInOlderCDIVersion() th Set userIdSet = new HashSet<>(); - JsonObject userList = listUsers(tenantIdentifier, null, "10", includeRecipeIds, process.getProcess()); + JsonObject userList = listUsers(tenantIdentifier, null, "10", includeRecipeIds, + process.getProcess()); String paginationToken = userList.get("nextPaginationToken").getAsString(); JsonArray users = userList.get("users").getAsJsonArray(); @@ -419,11 +425,13 @@ public void testUserPaginationUserObjectsDontHaveTenantIdsInOlderCDIVersion() th } while (paginationToken != null) { - userList = listUsers(tenantIdentifier, paginationToken, "10", includeRecipeIds, process.getProcess()); + userList = listUsers(tenantIdentifier, paginationToken, "10", includeRecipeIds, + process.getProcess()); users = userList.get("users").getAsJsonArray(); for (JsonElement user : users) { - String userId = user.getAsJsonObject().get("user").getAsJsonObject().get("id").getAsString(); + String userId = user.getAsJsonObject().get("user").getAsJsonObject().get("id") + .getAsString(); String recipeId = user.getAsJsonObject().get("recipeId").getAsString(); assertFalse(userIdSet.contains(userId)); userIdSet.add(userId); @@ -504,7 +512,8 @@ private JsonObject consumeCode(TenantIdentifier tenantIdentifier, String preAuth return response.get("user").getAsJsonObject(); } - private JsonObject consumeCode(TenantIdentifier tenantIdentifier, String deviceId, String preAuthSessionId, String userInputCode) + private JsonObject consumeCode(TenantIdentifier tenantIdentifier, String deviceId, String preAuthSessionId, + String userInputCode) throws HttpResponseException, IOException { JsonObject consumeCodeRequestBody = new JsonObject(); consumeCodeRequestBody.addProperty("deviceId", deviceId); @@ -522,25 +531,29 @@ private JsonObject consumeCode(TenantIdentifier tenantIdentifier, String deviceI private JsonObject signInUpEmailUsingLinkCode(TenantIdentifier tenantIdentifier, String email) throws HttpResponseException, IOException { JsonObject code = createCodeWithEmail(tenantIdentifier, email); - return consumeCode(tenantIdentifier, code.get("preAuthSessionId").getAsString(), code.get("linkCode").getAsString()); + return consumeCode(tenantIdentifier, code.get("preAuthSessionId").getAsString(), + code.get("linkCode").getAsString()); } private JsonObject signInUpEmailUsingUserInputCode(TenantIdentifier tenantIdentifier, String email) throws HttpResponseException, IOException { JsonObject code = createCodeWithEmail(tenantIdentifier, email); - return consumeCode(tenantIdentifier, code.get("deviceId").getAsString(), code.get("preAuthSessionId").getAsString(), code.get("userInputCode").getAsString()); + return consumeCode(tenantIdentifier, code.get("deviceId").getAsString(), + code.get("preAuthSessionId").getAsString(), code.get("userInputCode").getAsString()); } private JsonObject signInUpNumberUsingLinkCode(TenantIdentifier tenantIdentifier, String phoneNumber) throws HttpResponseException, IOException { JsonObject code = createCodeWithNumber(tenantIdentifier, phoneNumber); - return consumeCode(tenantIdentifier, code.get("preAuthSessionId").getAsString(), code.get("linkCode").getAsString()); + return consumeCode(tenantIdentifier, code.get("preAuthSessionId").getAsString(), + code.get("linkCode").getAsString()); } private JsonObject signInUpNumberUsingUserInputCode(TenantIdentifier tenantIdentifier, String phoneNumber) throws HttpResponseException, IOException { JsonObject code = createCodeWithNumber(tenantIdentifier, phoneNumber); - return consumeCode(tenantIdentifier, code.get("deviceId").getAsString(), code.get("preAuthSessionId").getAsString(), code.get("userInputCode").getAsString()); + return consumeCode(tenantIdentifier, code.get("deviceId").getAsString(), + code.get("preAuthSessionId").getAsString(), code.get("userInputCode").getAsString()); } private JsonObject plessGetUserUsingId(TenantIdentifier tenantIdentifier, String userId) @@ -622,7 +635,8 @@ public void testPlessUsersDontHaveTenantIdsInOlderCDI() throws Exception { } } - public JsonObject signInUp(TenantIdentifier tenantIdentifier, String thirdPartyId, String thirdPartyUserId, String email) + public JsonObject signInUp(TenantIdentifier tenantIdentifier, String thirdPartyId, String thirdPartyUserId, + String email) throws HttpResponseException, IOException { JsonObject emailObject = new JsonObject(); emailObject.addProperty("id", email); @@ -654,7 +668,8 @@ private JsonObject tpGetUserUsingId(TenantIdentifier tenantIdentifier, String us return userResponse.getAsJsonObject("user"); } - private JsonObject getUserUsingThirdPartyUserId(TenantIdentifier tenantIdentifier, String thirdPartyId, String thirdPartyUserId) + private JsonObject getUserUsingThirdPartyUserId(TenantIdentifier tenantIdentifier, String thirdPartyId, + String thirdPartyUserId) throws HttpResponseException, IOException { HashMap map = new HashMap<>(); map.put("thirdPartyId", thirdPartyId); @@ -694,7 +709,7 @@ public void testTpUsersDontHaveTenantIdsForOlderCDI() throws Exception { return; } - for (TenantIdentifier t: new TenantIdentifier[]{t1, t2, t3}) { + for (TenantIdentifier t : new TenantIdentifier[]{t1, t2, t3}) { JsonObject user1 = signInUp(t, "google", "google-user-id", "user@gmail.com"); JsonObject user2 = signInUp(t, "facebook", "fb-user-id", "user@gmail.com"); diff --git a/src/test/java/io/supertokens/test/thirdparty/api/ThirdPartySignInUpAPITest4_0.java b/src/test/java/io/supertokens/test/thirdparty/api/ThirdPartySignInUpAPITest4_0.java new file mode 100644 index 000000000..1fe296cd2 --- /dev/null +++ b/src/test/java/io/supertokens/test/thirdparty/api/ThirdPartySignInUpAPITest4_0.java @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.test.thirdparty.api; + +import com.google.gson.JsonObject; +import io.supertokens.ActiveUsers; +import io.supertokens.ProcessState; +import io.supertokens.authRecipe.AuthRecipe; +import io.supertokens.emailpassword.EmailPassword; +import io.supertokens.featureflag.EE_FEATURES; +import io.supertokens.featureflag.FeatureFlagTestContent; +import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.emailpassword.UserInfo; +import io.supertokens.storageLayer.StorageLayer; +import io.supertokens.test.TestingProcessManager; +import io.supertokens.test.Utils; +import io.supertokens.test.httpRequest.HttpRequestForTesting; +import io.supertokens.thirdparty.ThirdParty; +import io.supertokens.utils.SemVer; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; + +import static org.junit.Assert.*; + + +public class ThirdPartySignInUpAPITest4_0 { + + @Rule + public TestRule watchman = Utils.getOnFailure(); + + @AfterClass + public static void afterTesting() { + Utils.afterTesting(); + } + + @Before + public void beforeEach() { + Utils.reset(); + } + + // good input + // failure condition: test fails if signinup response does not match api spec + @Test + public void testGoodInput() throws Exception { + String[] args = {"../"}; + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + long startTs = System.currentTimeMillis(); + + JsonObject emailObject = new JsonObject(); + emailObject.addProperty("id", "test@example.com"); + + JsonObject signUpRequestBody = new JsonObject(); + signUpRequestBody.addProperty("thirdPartyId", "google"); + signUpRequestBody.addProperty("thirdPartyUserId", "google-user"); + signUpRequestBody.add("email", emailObject); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signinup", signUpRequestBody, 1000, 1000, null, + SemVer.v4_0.get(), "thirdparty"); + + { + assert (response.get("status").getAsString().equals("OK")); + assert (response.get("createdNewUser").getAsBoolean()); + JsonObject jsonUser = response.get("user").getAsJsonObject(); + assertNotNull(jsonUser.get("id")); + assertNotNull(jsonUser.get("timeJoined")); + assert (!jsonUser.get("isPrimaryUser").getAsBoolean()); + assert (jsonUser.get("emails").getAsJsonArray().size() == 1); + assert (jsonUser.get("emails").getAsJsonArray().get(0).getAsString().equals("test@example.com")); + assert (jsonUser.get("phoneNumbers").getAsJsonArray().size() == 0); + assert (jsonUser.get("thirdParty").getAsJsonArray().size() == 1); + assert (jsonUser.get("thirdParty").getAsJsonArray().get(0).getAsJsonObject().get("id").getAsString() + .equals("google")); + assert (jsonUser.get("thirdParty").getAsJsonArray().get(0).getAsJsonObject().get("userId").getAsString() + .equals("google-user")); + assert (jsonUser.get("loginMethods").getAsJsonArray().size() == 1); + JsonObject lM = jsonUser.get("loginMethods").getAsJsonArray().get(0).getAsJsonObject(); + assertFalse(lM.get("verified").getAsBoolean()); + assertNotNull(lM.get("timeJoined")); + assertNotNull(lM.get("recipeUserId")); + assertEquals(lM.get("recipeId").getAsString(), "thirdparty"); + assertEquals(lM.get("email").getAsString(), "test@example.com"); + assert (lM.get("thirdParty").getAsJsonObject().get("id").getAsString() + .equals("google")); + assert (lM.get("thirdParty").getAsJsonObject().get("userId").getAsString() + .equals("google-user")); + assert (lM.entrySet().size() == 7); + } + + int activeUsers = ActiveUsers.countUsersActiveSince(process.getProcess(), startTs); + assert (activeUsers == 1); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testNotAllowedUpdateOfEmail() throws Exception { + String[] args = {"../"}; + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + UserInfo user0 = EmailPassword.signUp(process.getProcess(), "someemail1@gmail.com", "somePass"); + AuthRecipe.createPrimaryUser(process.main, user0.id); + + ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(process.getProcess(), "google", "user", + "someemail@gmail.com"); + AuthRecipe.createPrimaryUser(process.main, signInUpResponse.user.id); + + JsonObject emailObject = new JsonObject(); + emailObject.addProperty("id", "someemail1@gmail.com"); + + JsonObject signUpRequestBody = new JsonObject(); + signUpRequestBody.addProperty("thirdPartyId", "google"); + signUpRequestBody.addProperty("thirdPartyUserId", "user"); + signUpRequestBody.add("email", emailObject); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signinup", signUpRequestBody, 1000, 1000, null, + SemVer.v4_0.get(), "thirdparty"); + + assert (response.get("status").getAsString().equals("EMAIL_CHANGE_NOT_ALLOWED_ERROR")); + assert (response.get("reason").getAsString().equals("Email already associated with another primary user.")); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } +} From 0f7152d6fa5967c2e2c3152ff0d7cec010791101 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Fri, 4 Aug 2023 19:57:38 +0530 Subject: [PATCH 079/131] adds more tests --- .../test/authRecipe/GetUserByIdAPITest.java | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/src/test/java/io/supertokens/test/authRecipe/GetUserByIdAPITest.java b/src/test/java/io/supertokens/test/authRecipe/GetUserByIdAPITest.java index 133ab4b6d..2d9ad5b23 100644 --- a/src/test/java/io/supertokens/test/authRecipe/GetUserByIdAPITest.java +++ b/src/test/java/io/supertokens/test/authRecipe/GetUserByIdAPITest.java @@ -28,6 +28,7 @@ import io.supertokens.test.TestingProcessManager; import io.supertokens.test.Utils; import io.supertokens.test.httpRequest.HttpRequestForTesting; +import io.supertokens.thirdparty.ThirdParty; import io.supertokens.useridmapping.UserIdMapping; import io.supertokens.webserver.WebserverAPI; import org.junit.AfterClass; @@ -201,6 +202,76 @@ public void getUserSuccessWithUserIdMapping() throws Exception { assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); } + @Test + public void getUserSuccess2() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "password"); + assert (!user.isPrimaryUser); + + Thread.sleep(50); + + + ThirdParty.SignInUpResponse signInUpRespone = ThirdParty.signInUp(process.getProcess(), "google", "google-user", + "test@example.com"); + AuthRecipeUserInfo user2 = signInUpRespone.user; + assert (!user2.isPrimaryUser); + + AuthRecipe.createPrimaryUser(process.main, user2.id); + + AuthRecipe.linkAccounts(process.main, user.id, user2.id); + + { + Map params = new HashMap<>(); + params.put("userId", user2.id); + JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/user/id", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assertEquals(2, response.entrySet().size()); + assertEquals("OK", response.get("status").getAsString()); + JsonObject jsonUser = response.get("user").getAsJsonObject(); + assert (jsonUser.get("id").getAsString().equals(user2.id)); + assert (jsonUser.get("timeJoined").getAsLong() == user.timeJoined); + assert (jsonUser.get("isPrimaryUser").getAsBoolean()); + assert (jsonUser.get("emails").getAsJsonArray().size() == 1); + assert (jsonUser.get("emails").getAsJsonArray().get(0).getAsString().equals("test@example.com")); + assert (jsonUser.get("phoneNumbers").getAsJsonArray().size() == 0); + assert (jsonUser.get("thirdParty").getAsJsonArray().size() == 1); + assert (jsonUser.get("loginMethods").getAsJsonArray().size() == 2); + { + JsonObject lM = jsonUser.get("loginMethods").getAsJsonArray().get(0).getAsJsonObject(); + assertFalse(lM.get("verified").getAsBoolean()); + assertEquals(lM.get("timeJoined").getAsLong(), user.timeJoined); + assertEquals(lM.get("recipeUserId").getAsString(), user.id); + assertEquals(lM.get("recipeId").getAsString(), "emailpassword"); + assertEquals(lM.get("email").getAsString(), "test@example.com"); + assert (lM.entrySet().size() == 6); + } + { + JsonObject lM = jsonUser.get("loginMethods").getAsJsonArray().get(1).getAsJsonObject(); + assertFalse(lM.get("verified").getAsBoolean()); + assertEquals(lM.get("timeJoined").getAsLong(), user2.timeJoined); + assertEquals(lM.get("recipeUserId").getAsString(), user2.id); + assertEquals(lM.get("recipeId").getAsString(), "thirdparty"); + assertEquals(lM.get("email").getAsString(), "test@example.com"); + assert (lM.entrySet().size() == 7); + } + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + @Test public void getUnknownUser() throws Exception { String[] args = {"../"}; From 7f4e46a2cb046b3582547cef47c9d629c067243f Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Sat, 5 Aug 2023 01:59:28 +0530 Subject: [PATCH 080/131] fixes a bug --- src/main/java/io/supertokens/authRecipe/AuthRecipe.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/supertokens/authRecipe/AuthRecipe.java b/src/main/java/io/supertokens/authRecipe/AuthRecipe.java index 4abf0b06e..6931cf8bf 100644 --- a/src/main/java/io/supertokens/authRecipe/AuthRecipe.java +++ b/src/main/java/io/supertokens/authRecipe/AuthRecipe.java @@ -570,7 +570,9 @@ public static AuthRecipeUserInfo[] getUsersByAccountInfo(TenantIdentifierWithSto if (thirdPartyId != null && thirdPartyUserId != null) { AuthRecipeUserInfo user = tenantIdentifier.getAuthRecipeStorage() .getPrimaryUserByThirdPartyInfo(tenantIdentifier, thirdPartyId, thirdPartyUserId); - result.add(user); + if (user != null) { + result.add(user); + } } if (doUnionOfAccountInfo) { From c5fddf47125ab7ae27214fa1254267613d1519b7 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Sat, 5 Aug 2023 12:34:27 +0530 Subject: [PATCH 081/131] adds recipe user id in session --- .../TokenTheftDetectedException.java | 8 +- .../java/io/supertokens/inmemorydb/Start.java | 7 + .../inmemorydb/queries/SessionQueries.java | 10 +- .../java/io/supertokens/session/Session.java | 146 ++++++++++++------ .../session/accessToken/AccessToken.java | 57 +++++-- .../supertokens/session/info/SessionInfo.java | 8 +- .../api/session/RefreshSessionAPI.java | 15 +- .../webserver/api/session/SessionAPI.java | 19 ++- .../api/session/SessionRegenerateAPI.java | 3 + .../api/session/VerifySessionAPI.java | 12 +- .../test/session/AccessTokenTest.java | 12 +- .../test/session/SessionTest2.java | 35 ++--- 12 files changed, 223 insertions(+), 109 deletions(-) diff --git a/src/main/java/io/supertokens/exceptions/TokenTheftDetectedException.java b/src/main/java/io/supertokens/exceptions/TokenTheftDetectedException.java index a4c2ddfef..700793cf7 100644 --- a/src/main/java/io/supertokens/exceptions/TokenTheftDetectedException.java +++ b/src/main/java/io/supertokens/exceptions/TokenTheftDetectedException.java @@ -21,10 +21,12 @@ public class TokenTheftDetectedException extends Exception { private static final long serialVersionUID = -7964000536695705071L; public final String sessionHandle; - public final String userId; + public final String recipeUserId; + public final String primaryUserId; - public TokenTheftDetectedException(String sessionHandle, String userId) { + public TokenTheftDetectedException(String sessionHandle, String recipeUserId, String primaryUserId) { this.sessionHandle = sessionHandle; - this.userId = userId; + this.recipeUserId = recipeUserId; + this.primaryUserId = primaryUserId; } } diff --git a/src/main/java/io/supertokens/inmemorydb/Start.java b/src/main/java/io/supertokens/inmemorydb/Start.java index 1cd5a6b34..8fd4f3db8 100644 --- a/src/main/java/io/supertokens/inmemorydb/Start.java +++ b/src/main/java/io/supertokens/inmemorydb/Start.java @@ -1226,6 +1226,13 @@ public AuthRecipeUserInfo getPrimaryUserById(AppIdentifier appIdentifier, String } } + @Override + public String getPrimaryUserIdStrForUserId(AppIdentifier appIdentifier, String userId) + throws StorageQueryException { + // TODO:... + return null; + } + @Override public AuthRecipeUserInfo[] listPrimaryUsersByEmail(TenantIdentifier tenantIdentifier, String email) throws StorageQueryException { diff --git a/src/main/java/io/supertokens/inmemorydb/queries/SessionQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/SessionQueries.java index 357985eeb..3e21293de 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/SessionQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/SessionQueries.java @@ -19,7 +19,6 @@ import com.google.gson.JsonObject; import com.google.gson.JsonParser; import io.supertokens.inmemorydb.ConnectionWithLocks; -import io.supertokens.inmemorydb.QueryExecutorTemplate; import io.supertokens.inmemorydb.Start; import io.supertokens.inmemorydb.config.Config; import io.supertokens.pluginInterface.KeyValueInfo; @@ -36,7 +35,6 @@ import java.util.ArrayList; import java.util.List; -import static io.supertokens.inmemorydb.PreparedStatementValueSetter.NO_OP_SETTER; import static io.supertokens.inmemorydb.QueryExecutorTemplate.execute; import static io.supertokens.inmemorydb.QueryExecutorTemplate.update; import static io.supertokens.inmemorydb.config.Config.getConfig; @@ -105,7 +103,9 @@ public static SessionInfo getSessionInfo_Transaction(Start start, Connection con String sessionHandle) throws SQLException, StorageQueryException { - ((ConnectionWithLocks) con).lock(tenantIdentifier.getAppId() + "~" + tenantIdentifier.getTenantId() + "~" + sessionHandle + Config.getConfig(start).getSessionInfoTable()); + ((ConnectionWithLocks) con).lock( + tenantIdentifier.getAppId() + "~" + tenantIdentifier.getTenantId() + "~" + sessionHandle + + Config.getConfig(start).getSessionInfoTable()); String QUERY = "SELECT session_handle, user_id, refresh_token_hash_2, session_data, expires_at, " + "created_at_time, jwt_user_payload, use_static_key FROM " + getConfig(start).getSessionInfoTable() @@ -326,7 +326,8 @@ public static void addAccessTokenSigningKey_Transaction(Start start, Connection public static KeyValueInfo[] getAccessTokenSigningKeys_Transaction(Start start, Connection con, AppIdentifier appIdentifier) throws SQLException, StorageQueryException { - ((ConnectionWithLocks) con).lock(appIdentifier.getAppId() + Config.getConfig(start).getAccessTokenSigningKeysTable()); + ((ConnectionWithLocks) con).lock( + appIdentifier.getAppId() + Config.getConfig(start).getAccessTokenSigningKeysTable()); String QUERY = "SELECT * FROM " + getConfig(start).getAccessTokenSigningKeysTable() + " WHERE app_id = ?"; @@ -371,6 +372,7 @@ private static SessionInfoRowMapper getInstance() { public SessionInfo map(ResultSet result) throws Exception { JsonParser jp = new JsonParser(); return new SessionInfo(result.getString("session_handle"), result.getString("user_id"), + result.getString("user_id"), result.getString("refresh_token_hash_2"), jp.parse(result.getString("session_data")).getAsJsonObject(), result.getLong("expires_at"), jp.parse(result.getString("jwt_user_payload")).getAsJsonObject(), diff --git a/src/main/java/io/supertokens/session/Session.java b/src/main/java/io/supertokens/session/Session.java index 0d2194570..a01056b6d 100644 --- a/src/main/java/io/supertokens/session/Session.java +++ b/src/main/java/io/supertokens/session/Session.java @@ -63,7 +63,7 @@ public class Session { @TestOnly public static SessionInformationHolder createNewSession(TenantIdentifierWithStorage tenantIdentifierWithStorage, Main main, - @Nonnull String userId, + @Nonnull String recipeUserId, @Nonnull JsonObject userDataInJWT, @Nonnull JsonObject userDataInDatabase) throws NoSuchAlgorithmException, StorageQueryException, InvalidKeyException, @@ -71,7 +71,8 @@ public static SessionInformationHolder createNewSession(TenantIdentifierWithStor BadPaddingException, InvalidAlgorithmParameterException, NoSuchPaddingException, UnauthorisedException, JWT.JWTException, UnsupportedJWTSigningAlgorithmException, AccessTokenPayloadError { try { - return createNewSession(tenantIdentifierWithStorage, main, userId, userDataInJWT, userDataInDatabase, false, + return createNewSession(tenantIdentifierWithStorage, main, recipeUserId, userDataInJWT, userDataInDatabase, + false, AccessToken.getLatestVersion(), false); } catch (TenantOrAppNotFoundException e) { throw new IllegalStateException(e); @@ -80,7 +81,7 @@ public static SessionInformationHolder createNewSession(TenantIdentifierWithStor @TestOnly public static SessionInformationHolder createNewSession(Main main, - @Nonnull String userId, + @Nonnull String recipeUserId, @Nonnull JsonObject userDataInJWT, @Nonnull JsonObject userDataInDatabase) throws NoSuchAlgorithmException, StorageQueryException, InvalidKeyException, @@ -91,14 +92,14 @@ public static SessionInformationHolder createNewSession(Main main, try { return createNewSession( new TenantIdentifierWithStorage(null, null, null, storage), main, - userId, userDataInJWT, userDataInDatabase, false, AccessToken.getLatestVersion(), false); + recipeUserId, userDataInJWT, userDataInDatabase, false, AccessToken.getLatestVersion(), false); } catch (TenantOrAppNotFoundException e) { throw new IllegalStateException(e); } } @TestOnly - public static SessionInformationHolder createNewSession(Main main, @Nonnull String userId, + public static SessionInformationHolder createNewSession(Main main, @Nonnull String recipeUserId, @Nonnull JsonObject userDataInJWT, @Nonnull JsonObject userDataInDatabase, boolean enableAntiCsrf, AccessToken.VERSION version, @@ -111,14 +112,14 @@ public static SessionInformationHolder createNewSession(Main main, @Nonnull Stri try { return createNewSession( new TenantIdentifierWithStorage(null, null, null, storage), main, - userId, userDataInJWT, userDataInDatabase, enableAntiCsrf, version, useStaticKey); + recipeUserId, userDataInJWT, userDataInDatabase, enableAntiCsrf, version, useStaticKey); } catch (TenantOrAppNotFoundException e) { throw new IllegalStateException(e); } } public static SessionInformationHolder createNewSession(TenantIdentifierWithStorage tenantIdentifierWithStorage, - Main main, @Nonnull String userId, + Main main, @Nonnull String recipeUserId, @Nonnull JsonObject userDataInJWT, @Nonnull JsonObject userDataInDatabase, boolean enableAntiCsrf, AccessToken.VERSION version, @@ -132,23 +133,32 @@ public static SessionInformationHolder createNewSession(TenantIdentifierWithStor sessionHandle += "_" + tenantIdentifierWithStorage.getTenantId(); } + String primaryUserId = tenantIdentifierWithStorage.getAuthRecipeStorage() + .getPrimaryUserIdStrForUserId(tenantIdentifierWithStorage.toAppIdentifier(), recipeUserId); + if (primaryUserId == null) { + primaryUserId = recipeUserId; + } + String antiCsrfToken = enableAntiCsrf ? UUID.randomUUID().toString() : null; final TokenInfo refreshToken = RefreshToken.createNewRefreshToken(tenantIdentifierWithStorage, main, - sessionHandle, userId, null, + sessionHandle, recipeUserId, null, antiCsrfToken); TokenInfo accessToken = AccessToken.createNewAccessToken(tenantIdentifierWithStorage, main, sessionHandle, - userId, Utils.hashSHA256(refreshToken.token), null, userDataInJWT, antiCsrfToken, + recipeUserId, primaryUserId, Utils.hashSHA256(refreshToken.token), null, userDataInJWT, antiCsrfToken, null, version, useStaticKey); tenantIdentifierWithStorage.getSessionStorage() - .createNewSession(tenantIdentifierWithStorage, sessionHandle, userId, + .createNewSession(tenantIdentifierWithStorage, sessionHandle, recipeUserId, Utils.hashSHA256(Utils.hashSHA256(refreshToken.token)), userDataInDatabase, refreshToken.expiry, userDataInJWT, refreshToken.createdTime, useStaticKey); TokenInfo idRefreshToken = new TokenInfo(UUID.randomUUID().toString(), refreshToken.expiry, refreshToken.createdTime); - return new SessionInformationHolder(new SessionInfo(sessionHandle, userId, userDataInJWT, tenantIdentifierWithStorage.getTenantId()), accessToken, + return new SessionInformationHolder( + new SessionInfo(sessionHandle, primaryUserId, recipeUserId, userDataInJWT, + tenantIdentifierWithStorage.getTenantId()), + accessToken, refreshToken, idRefreshToken, antiCsrfToken); } @@ -200,7 +210,8 @@ public static SessionInformationHolder regenerateToken(AppIdentifier appIdentifi accessToken.sessionHandle); JsonObject newJWTUserPayload = userDataInJWT == null ? sessionInfo.userDataInJWT : userDataInJWT; - updateSession(tenantIdentifierWithStorage, accessToken.sessionHandle, null, newJWTUserPayload, accessToken.version); + updateSession(tenantIdentifierWithStorage, accessToken.sessionHandle, null, newJWTUserPayload, + accessToken.version); // if the above succeeds but the below fails, it's OK since the client will get server error and will try // again. In this case, the JWT data will be updated again since the API will get the old JWT. In case there @@ -209,17 +220,21 @@ public static SessionInformationHolder regenerateToken(AppIdentifier appIdentifi // in this case, we set the should not set the access token in the response since they will have to call // the refresh API anyway. return new SessionInformationHolder( - new SessionInfo(accessToken.sessionHandle, accessToken.userId, newJWTUserPayload, tenantIdentifierWithStorage.getTenantId()), null, null, null, + new SessionInfo(accessToken.sessionHandle, accessToken.primaryUserId, accessToken.recipeUserId, + newJWTUserPayload, + tenantIdentifierWithStorage.getTenantId()), null, null, null, null); } TokenInfo newAccessToken = AccessToken.createNewAccessToken(tenantIdentifierWithStorage, main, - accessToken.sessionHandle, accessToken.userId, + accessToken.sessionHandle, accessToken.recipeUserId, accessToken.primaryUserId, accessToken.refreshTokenHash1, accessToken.parentRefreshTokenHash1, newJWTUserPayload, accessToken.antiCsrfToken, accessToken.expiryTime, accessToken.version, sessionInfo.useStaticKey); return new SessionInformationHolder( - new SessionInfo(accessToken.sessionHandle, accessToken.userId, newJWTUserPayload, tenantIdentifierWithStorage.getTenantId()), + new SessionInfo(accessToken.sessionHandle, accessToken.primaryUserId, accessToken.recipeUserId, + newJWTUserPayload, + tenantIdentifierWithStorage.getTenantId()), new TokenInfo(newAccessToken.token, newAccessToken.expiry, newAccessToken.createdTime), null, null, null); } @@ -254,18 +269,22 @@ public static SessionInformationHolder regenerateTokenBeforeCDI2_21(AppIdentifie // in this case, we set the should not set the access token in the response since they will have to call // the refresh API anyway. return new SessionInformationHolder( - new SessionInfo(accessToken.sessionHandle, accessToken.userId, newJWTUserPayload, tenantIdentifierWithStorage.getTenantId()), null, null, null, + new SessionInfo(accessToken.sessionHandle, accessToken.primaryUserId, accessToken.recipeUserId, + newJWTUserPayload, + tenantIdentifierWithStorage.getTenantId()), null, null, null, null); } TokenInfo newAccessToken = AccessToken.createNewAccessToken(accessToken.tenantIdentifier, main, accessToken.sessionHandle, - accessToken.userId, + accessToken.recipeUserId, accessToken.primaryUserId, accessToken.refreshTokenHash1, accessToken.parentRefreshTokenHash1, newJWTUserPayload, accessToken.antiCsrfToken, accessToken.expiryTime, accessToken.version, sessionInfo.useStaticKey); return new SessionInformationHolder( - new SessionInfo(accessToken.sessionHandle, accessToken.userId, newJWTUserPayload, tenantIdentifierWithStorage.getTenantId()), + new SessionInfo(accessToken.sessionHandle, accessToken.primaryUserId, accessToken.recipeUserId, + newJWTUserPayload, + tenantIdentifierWithStorage.getTenantId()), new TokenInfo(newAccessToken.token, newAccessToken.expiry, newAccessToken.createdTime), null, null, null); } @@ -318,7 +337,9 @@ public static SessionInformationHolder getSession(AppIdentifier appIdentifier, M // this means that the refresh token associated with this access token is // already the parent - and JWT payload doesn't need to be updated. return new SessionInformationHolder( - new SessionInfo(accessToken.sessionHandle, accessToken.userId, accessToken.userData, tenantIdentifierWithStorage.getTenantId()), null, null, + new SessionInfo(accessToken.sessionHandle, accessToken.primaryUserId, accessToken.recipeUserId, + accessToken.userData, + tenantIdentifierWithStorage.getTenantId()), null, null, null, null); } @@ -359,18 +380,20 @@ public static SessionInformationHolder getSession(AppIdentifier appIdentifier, M newAccessToken = AccessToken.createNewAccessTokenV1(tenantIdentifierWithStorage, main, accessToken.sessionHandle, - accessToken.userId, accessToken.refreshTokenHash1, null, + accessToken.recipeUserId, accessToken.refreshTokenHash1, null, sessionInfo.userDataInJWT, accessToken.antiCsrfToken); } else { newAccessToken = AccessToken.createNewAccessToken(tenantIdentifierWithStorage, main, accessToken.sessionHandle, - accessToken.userId, accessToken.refreshTokenHash1, null, + accessToken.recipeUserId, accessToken.primaryUserId, + accessToken.refreshTokenHash1, null, sessionInfo.userDataInJWT, accessToken.antiCsrfToken, null, accessToken.version, sessionInfo.useStaticKey); } return new SessionInformationHolder( - new SessionInfo(accessToken.sessionHandle, accessToken.userId, + new SessionInfo(accessToken.sessionHandle, accessToken.primaryUserId, + accessToken.recipeUserId, sessionInfo.userDataInJWT, tenantIdentifierWithStorage.getTenantId()), new TokenInfo(newAccessToken.token, newAccessToken.expiry, newAccessToken.createdTime), @@ -379,13 +402,16 @@ public static SessionInformationHolder getSession(AppIdentifier appIdentifier, M storage.commitTransaction(con); return new SessionInformationHolder( - new SessionInfo(accessToken.sessionHandle, accessToken.userId, accessToken.userData, tenantIdentifierWithStorage.getTenantId()), + new SessionInfo(accessToken.sessionHandle, accessToken.primaryUserId, + accessToken.recipeUserId, accessToken.userData, + tenantIdentifierWithStorage.getTenantId()), // here we purposely use accessToken.userData instead of sessionInfo.userDataInJWT // because we are not returning a new access token null, null, null, null); } catch (UnauthorisedException | NoSuchAlgorithmException | - InvalidKeyException | InvalidKeySpecException | SignatureException | - UnsupportedJWTSigningAlgorithmException | AccessTokenPayloadError | TenantOrAppNotFoundException e) { + InvalidKeyException | InvalidKeySpecException | SignatureException | + UnsupportedJWTSigningAlgorithmException | AccessTokenPayloadError | + TenantOrAppNotFoundException e) { throw new StorageTransactionLogicException(e); } }); @@ -432,29 +458,34 @@ public static SessionInformationHolder getSession(AppIdentifier appIdentifier, M if (accessToken.version == AccessToken.VERSION.V1) { newAccessToken = AccessToken.createNewAccessTokenV1(tenantIdentifierWithStorage, main, accessToken.sessionHandle, - accessToken.userId, accessToken.refreshTokenHash1, null, sessionInfo.userDataInJWT, + accessToken.recipeUserId, accessToken.refreshTokenHash1, null, + sessionInfo.userDataInJWT, accessToken.antiCsrfToken); } else { newAccessToken = AccessToken.createNewAccessToken(tenantIdentifierWithStorage, main, accessToken.sessionHandle, - accessToken.userId, accessToken.refreshTokenHash1, null, sessionInfo.userDataInJWT, + accessToken.recipeUserId, accessToken.primaryUserId, accessToken.refreshTokenHash1, + null, sessionInfo.userDataInJWT, accessToken.antiCsrfToken, null, accessToken.version, sessionInfo.useStaticKey); } return new SessionInformationHolder( - new SessionInfo(accessToken.sessionHandle, accessToken.userId, + new SessionInfo(accessToken.sessionHandle, accessToken.primaryUserId, + accessToken.recipeUserId, sessionInfo.userDataInJWT, tenantIdentifierWithStorage.getTenantId()), new TokenInfo(newAccessToken.token, newAccessToken.expiry, newAccessToken.createdTime), null, null, null); } return new SessionInformationHolder( - new SessionInfo(accessToken.sessionHandle, accessToken.userId, accessToken.userData, tenantIdentifierWithStorage.getTenantId()), + new SessionInfo(accessToken.sessionHandle, accessToken.primaryUserId, + accessToken.recipeUserId, accessToken.userData, + tenantIdentifierWithStorage.getTenantId()), // here we purposely use accessToken.userData instead of sessionInfo.userDataInJWT // because we are not returning a new access token null, null, null, null); } catch (NoSuchAlgorithmException | InvalidKeyException - | InvalidKeySpecException | SignatureException e) { + | InvalidKeySpecException | SignatureException e) { throw new StorageTransactionLogicException(e); } } @@ -532,14 +563,16 @@ private static SessionInformationHolder refreshSessionHelper( if (sessionInfo.refreshTokenHash2.equals(Utils.hashSHA256(Utils.hashSHA256(refreshToken)))) { // at this point, the input refresh token is the parent one. storage.commitTransaction(con); + String antiCsrfToken = enableAntiCsrf ? UUID.randomUUID().toString() : null; final TokenInfo newRefreshToken = RefreshToken.createNewRefreshToken( tenantIdentifierWithStorage, main, sessionHandle, - sessionInfo.userId, Utils.hashSHA256(refreshToken), antiCsrfToken); + sessionInfo.recipeUserId, Utils.hashSHA256(refreshToken), antiCsrfToken); TokenInfo newAccessToken = AccessToken.createNewAccessToken(tenantIdentifierWithStorage, main, sessionHandle, - sessionInfo.userId, Utils.hashSHA256(newRefreshToken.token), + sessionInfo.recipeUserId, sessionInfo.userId, + Utils.hashSHA256(newRefreshToken.token), Utils.hashSHA256(refreshToken), sessionInfo.userDataInJWT, antiCsrfToken, null, accessTokenVersion, sessionInfo.useStaticKey); @@ -547,7 +580,9 @@ private static SessionInformationHolder refreshSessionHelper( newRefreshToken.expiry, newRefreshToken.createdTime); return new SessionInformationHolder( - new SessionInfo(sessionHandle, sessionInfo.userId, sessionInfo.userDataInJWT, tenantIdentifierWithStorage.getTenantId()), + new SessionInfo(sessionHandle, sessionInfo.userId, sessionInfo.recipeUserId, + sessionInfo.userDataInJWT, + tenantIdentifierWithStorage.getTenantId()), newAccessToken, newRefreshToken, idRefreshToken, antiCsrfToken); } @@ -570,13 +605,15 @@ private static SessionInformationHolder refreshSessionHelper( storage.commitTransaction(con); - throw new TokenTheftDetectedException(sessionHandle, sessionInfo.userId); + throw new TokenTheftDetectedException(sessionHandle, sessionInfo.recipeUserId, + sessionInfo.userId); } catch (UnauthorisedException | NoSuchAlgorithmException | InvalidKeyException - | AccessTokenPayloadError | TokenTheftDetectedException | InvalidKeySpecException - | SignatureException | NoSuchPaddingException | InvalidAlgorithmParameterException - | IllegalBlockSizeException | BadPaddingException | UnsupportedJWTSigningAlgorithmException | - TenantOrAppNotFoundException e) { + | AccessTokenPayloadError | TokenTheftDetectedException | InvalidKeySpecException + | SignatureException | NoSuchPaddingException | InvalidAlgorithmParameterException + | IllegalBlockSizeException | BadPaddingException | + UnsupportedJWTSigningAlgorithmException | + TenantOrAppNotFoundException e) { throw new StorageTransactionLogicException(e); } }); @@ -618,10 +655,10 @@ private static SessionInformationHolder refreshSessionHelper( final TokenInfo newRefreshToken = RefreshToken.createNewRefreshToken( tenantIdentifierWithStorage, main, sessionHandle, - sessionInfo.userId, Utils.hashSHA256(refreshToken), antiCsrfToken); + sessionInfo.recipeUserId, Utils.hashSHA256(refreshToken), antiCsrfToken); TokenInfo newAccessToken = AccessToken.createNewAccessToken(tenantIdentifierWithStorage, main, sessionHandle, - sessionInfo.userId, Utils.hashSHA256(newRefreshToken.token), + sessionInfo.recipeUserId, sessionInfo.userId, Utils.hashSHA256(newRefreshToken.token), Utils.hashSHA256(refreshToken), sessionInfo.userDataInJWT, antiCsrfToken, null, accessTokenVersion, sessionInfo.useStaticKey); @@ -629,7 +666,9 @@ private static SessionInformationHolder refreshSessionHelper( newRefreshToken.createdTime); return new SessionInformationHolder( - new SessionInfo(sessionHandle, sessionInfo.userId, sessionInfo.userDataInJWT, tenantIdentifierWithStorage.getTenantId()), + new SessionInfo(sessionHandle, sessionInfo.userId, sessionInfo.recipeUserId, + sessionInfo.userDataInJWT, + tenantIdentifierWithStorage.getTenantId()), newAccessToken, newRefreshToken, idRefreshToken, antiCsrfToken); } @@ -652,11 +691,11 @@ private static SessionInformationHolder refreshSessionHelper( accessTokenVersion); } - throw new TokenTheftDetectedException(sessionHandle, sessionInfo.userId); + throw new TokenTheftDetectedException(sessionHandle, sessionInfo.recipeUserId, sessionInfo.userId); } catch (NoSuchAlgorithmException | InvalidKeyException - | InvalidKeySpecException | SignatureException | NoSuchPaddingException - | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) { + | InvalidKeySpecException | SignatureException | NoSuchPaddingException + | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) { throw new StorageTransactionLogicException(e); } } @@ -676,7 +715,8 @@ public static String[] revokeSessionUsingSessionHandles(Main main, sessionHandles); } - public static String[] revokeSessionUsingSessionHandles(Main main, AppIdentifierWithStorage appIdentifierWithStorage, + public static String[] revokeSessionUsingSessionHandles(Main main, + AppIdentifierWithStorage appIdentifierWithStorage, String[] sessionHandles) throws StorageQueryException { @@ -699,7 +739,8 @@ public static String[] revokeSessionUsingSessionHandles(Main main, AppIdentifier for (String tenantId : sessionHandleMap.keySet()) { String[] sessionHandlesForTenant = sessionHandleMap.get(tenantId).toArray(new String[0]); - TenantIdentifier tenantIdentifier = new TenantIdentifier(appIdentifierWithStorage.getConnectionUriDomain(), appIdentifierWithStorage.getAppId(), tenantId); + TenantIdentifier tenantIdentifier = new TenantIdentifier(appIdentifierWithStorage.getConnectionUriDomain(), + appIdentifierWithStorage.getAppId(), tenantId); TenantIdentifierWithStorage tenantIdentifierWithStorage = null; try { tenantIdentifierWithStorage = tenantIdentifier.withStorage( @@ -718,13 +759,14 @@ public static String[] revokeSessionUsingSessionHandles(Main main, AppIdentifier } private static String[] revokeSessionUsingSessionHandles(TenantIdentifierWithStorage tenantIdentifierWithStorage, - String[] sessionHandles) + String[] sessionHandles) throws StorageQueryException { Set validHandles = new HashSet<>(); if (sessionHandles.length > 1) { // we need to identify which sessionHandles are valid if there are more than one sessionHandles to revoke - // if there is only one sessionHandle to revoke, we would know if it was valid by the number of revoked sessions + // if there is only one sessionHandle to revoke, we would know if it was valid by the number of revoked + // sessions for (String sessionHandle : sessionHandles) { if (tenantIdentifierWithStorage.getSessionStorage() .getSession(tenantIdentifierWithStorage, sessionHandle) != null) { @@ -772,7 +814,8 @@ public static String[] revokeAllSessionsForUser(Main main, AppIdentifierWithStor public static String[] revokeAllSessionsForUser(Main main, TenantIdentifierWithStorage tenantIdentifierWithStorage, String userId) throws StorageQueryException { String[] sessionHandles = getAllNonExpiredSessionHandlesForUser(tenantIdentifierWithStorage, userId); - return revokeSessionUsingSessionHandles(main, tenantIdentifierWithStorage.toAppIdentifierWithStorage(), sessionHandles); + return revokeSessionUsingSessionHandles(main, tenantIdentifierWithStorage.toAppIdentifierWithStorage(), + sessionHandles); } @TestOnly @@ -791,7 +834,7 @@ public static String[] getAllNonExpiredSessionHandlesForUser( List sessionHandles = new ArrayList<>(); - for (TenantConfig tenant: tenants) { + for (TenantConfig tenant : tenants) { TenantIdentifierWithStorage tenantIdentifierWithStorage = null; try { tenantIdentifierWithStorage = tenant.tenantIdentifier.withStorage( @@ -899,7 +942,8 @@ public static void updateSession(TenantIdentifierWithStorage tenantIdentifierWit String sessionHandle, @Nullable JsonObject sessionData, @Nullable JsonObject jwtData, AccessToken.VERSION version) throws StorageQueryException, UnauthorisedException, AccessTokenPayloadError { - if (jwtData != null && Arrays.stream(AccessTokenInfo.getRequiredAndProtectedProps(version)).anyMatch(jwtData::has)) { + if (jwtData != null && + Arrays.stream(AccessTokenInfo.getRequiredAndProtectedProps(version)).anyMatch(jwtData::has)) { throw new AccessTokenPayloadError("The user payload contains protected field"); } diff --git a/src/main/java/io/supertokens/session/accessToken/AccessToken.java b/src/main/java/io/supertokens/session/accessToken/AccessToken.java index ad5fcb847..f009022fc 100644 --- a/src/main/java/io/supertokens/session/accessToken/AccessToken.java +++ b/src/main/java/io/supertokens/session/accessToken/AccessToken.java @@ -209,7 +209,7 @@ public static TokenInfo createNewAccessToken(@Nonnull Main main, NoSuchAlgorithmException, InvalidKeySpecException, SignatureException, UnsupportedJWTSigningAlgorithmException, AccessTokenPayloadError { try { - return createNewAccessToken(new TenantIdentifier(null, null, null), main, sessionHandle, userId, + return createNewAccessToken(new TenantIdentifier(null, null, null), main, sessionHandle, userId, userId, refreshTokenHash1, parentRefreshTokenHash1, userData, antiCsrfToken, expiryTime, version, useStaticKey); @@ -220,7 +220,8 @@ public static TokenInfo createNewAccessToken(@Nonnull Main main, public static TokenInfo createNewAccessToken(TenantIdentifier tenantIdentifier, @Nonnull Main main, @Nonnull String sessionHandle, - @Nonnull String userId, @Nonnull String refreshTokenHash1, + @Nonnull String recipeUserId, @Nonnull String primaryUserId, + @Nonnull String refreshTokenHash1, @Nullable String parentRefreshTokenHash1, @Nonnull JsonObject userData, @Nullable String antiCsrfToken, @Nullable Long expiryTime, VERSION version, boolean useStaticKey) @@ -237,7 +238,8 @@ public static TokenInfo createNewAccessToken(TenantIdentifier tenantIdentifier, } else { expires = now + Config.getConfig(tenantIdentifier, main).getAccessTokenValidity(); } - AccessTokenInfo accessToken = new AccessTokenInfo(sessionHandle, userId, refreshTokenHash1, expires, + AccessTokenInfo accessToken = new AccessTokenInfo(sessionHandle, recipeUserId, primaryUserId, refreshTokenHash1, + expires, parentRefreshTokenHash1, userData, antiCsrfToken, now, version, tenantIdentifier); JWTSigningKeyInfo keyToUse; @@ -296,7 +298,8 @@ public static TokenInfo createNewAccessTokenV1(TenantIdentifier tenantIdentifier AccessTokenInfo accessToken; long expiryTime = now + Config.getConfig(tenantIdentifier, main).getAccessTokenValidity(); - accessToken = new AccessTokenInfo(sessionHandle, userId, refreshTokenHash1, expiryTime, parentRefreshTokenHash1, + accessToken = new AccessTokenInfo(sessionHandle, userId, userId, refreshTokenHash1, expiryTime, + parentRefreshTokenHash1, userData, antiCsrfToken, now, VERSION.V1, tenantIdentifier); String token = JWT.createAndSignLegacyAccessToken(accessToken.toJSON(), signingKey.privateKey, @@ -310,6 +313,9 @@ public static VERSION getAccessTokenVersion(AccessTokenInfo accessToken) { } public static VERSION getAccessTokenVersionForCDI(SemVer version) { + if (version.greaterThanOrEqualTo(SemVer.v4_0)) { + return AccessToken.VERSION.V5; + } if (version.greaterThanOrEqualTo(SemVer.v3_0)) { return AccessToken.VERSION.V4; } @@ -354,11 +360,24 @@ public static class AccessTokenInfo { "antiCsrfToken", "tId" }; + static String[] requiredAndProtectedPropsV5 = { + "sub", + "exp", + "iat", + "sessionHandle", + "refreshTokenHash1", + "parentRefreshTokenHash1", + "antiCsrfToken", + "tId", + "recipesub" + }; @Nonnull public final String sessionHandle; @Nonnull - public final String userId; + public final String recipeUserId; + @Nonnull + public final String primaryUserId; @Nonnull public final String refreshTokenHash1; @Nullable @@ -376,12 +395,14 @@ public static class AccessTokenInfo { @Nonnull public TenantIdentifier tenantIdentifier; - AccessTokenInfo(@Nonnull String sessionHandle, @Nonnull String userId, @Nonnull String refreshTokenHash1, + AccessTokenInfo(@Nonnull String sessionHandle, @Nonnull String recipeUserId, @Nonnull String primaryUserId, + @Nonnull String refreshTokenHash1, long expiryTime, @Nullable String parentRefreshTokenHash1, @Nonnull JsonObject userData, @Nullable String antiCsrfToken, long timeCreated, @Nonnull VERSION version, TenantIdentifier tenantIdentifier) { this.sessionHandle = sessionHandle; - this.userId = userId; + this.recipeUserId = recipeUserId; + this.primaryUserId = primaryUserId; this.refreshTokenHash1 = refreshTokenHash1; if (version == VERSION.V2 || version == VERSION.V1) { this.expiryTime = expiryTime; @@ -431,9 +452,17 @@ static AccessTokenInfo fromJSON(AppIdentifier appIdentifier, JsonObject payload, appIdentifier.getAppId(), payload.get("tId").getAsString()); } + String primaryUserId = payload.get("sub").getAsString(); + String recipeUserId = payload.get("sub").getAsString(); + if (version != VERSION.V3 && version != VERSION.V4) { + // this means >= v5 + recipeUserId = payload.get("recipesub").getAsString(); + } + return new AccessTokenInfo( payload.get("sessionHandle").getAsString(), - payload.get("sub").getAsString(), + recipeUserId, + primaryUserId, payload.get("refreshTokenHash1").getAsString(), payload.get("exp").getAsLong() * 1000, parentRefreshTokenHash == null || parentRefreshTokenHash.isJsonNull() ? null : @@ -448,6 +477,7 @@ static AccessTokenInfo fromJSON(AppIdentifier appIdentifier, JsonObject payload, return new AccessTokenInfo( payload.get("sessionHandle").getAsString(), payload.get("userId").getAsString(), + payload.get("userId").getAsString(), payload.get("refreshTokenHash1").getAsString(), payload.get("expiryTime").getAsLong(), parentRefreshTokenHash == null || parentRefreshTokenHash.isJsonNull() ? null : @@ -465,15 +495,19 @@ static AccessTokenInfo fromJSON(AppIdentifier appIdentifier, JsonObject payload, JsonObject toJSON() throws AccessTokenPayloadError { JsonObject res = new JsonObject(); if (this.version != VERSION.V1 && this.version != VERSION.V2) { - res.addProperty("sub", this.userId); + res.addProperty("sub", this.primaryUserId); res.addProperty("exp", this.expiryTime / 1000); res.addProperty("iat", this.timeCreated / 1000); if (this.version != VERSION.V3) { res.addProperty("tId", this.tenantIdentifier.getTenantId()); } + if (this.version != VERSION.V3 && this.version != VERSION.V4) { + // this means >= v5 + res.addProperty("recipesub", this.recipeUserId); + } } else { - res.addProperty("userId", this.userId); + res.addProperty("userId", this.primaryUserId); res.addProperty("expiryTime", this.expiryTime); res.addProperty("timeCreated", this.timeCreated); } @@ -513,6 +547,7 @@ public static String[] getRequiredAndProtectedProps(VERSION version) { case V1, V2 -> requiredAndProtectedPropsV2; case V3 -> requiredAndProtectedPropsV3; case V4 -> requiredAndProtectedPropsV4; + case V5 -> requiredAndProtectedPropsV5; default -> throw new IllegalArgumentException("Unknown version: " + version); }; } @@ -541,6 +576,6 @@ public static String getVersionStringFromAccessTokenVersion(VERSION version) { } public enum VERSION { - V1, V2, V3, V4 + V1, V2, V3, V4, V5 } } diff --git a/src/main/java/io/supertokens/session/info/SessionInfo.java b/src/main/java/io/supertokens/session/info/SessionInfo.java index cbb8e8719..82ed161d4 100644 --- a/src/main/java/io/supertokens/session/info/SessionInfo.java +++ b/src/main/java/io/supertokens/session/info/SessionInfo.java @@ -27,16 +27,22 @@ public class SessionInfo { @Nonnull public final String userId; + @Nonnull + public final String recipeUserId; + @Nonnull public final JsonObject userDataInJWT; @Nonnull public final String tenantId; - public SessionInfo(@Nonnull String handle, @Nonnull String userId, @Nonnull JsonObject userDataInJWT, @Nonnull String tenantId) { + public SessionInfo(@Nonnull String handle, @Nonnull String userId, @Nonnull String recipeUserId, + @Nonnull JsonObject userDataInJWT, + @Nonnull String tenantId) { this.handle = handle; this.userId = userId; this.userDataInJWT = userDataInJWT; this.tenantId = tenantId; + this.recipeUserId = recipeUserId; } } diff --git a/src/main/java/io/supertokens/webserver/api/session/RefreshSessionAPI.java b/src/main/java/io/supertokens/webserver/api/session/RefreshSessionAPI.java index 33e92983f..ee92da0bd 100644 --- a/src/main/java/io/supertokens/webserver/api/session/RefreshSessionAPI.java +++ b/src/main/java/io/supertokens/webserver/api/session/RefreshSessionAPI.java @@ -109,24 +109,31 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I if (version.lesserThan(SemVer.v3_0)) { result.get("session").getAsJsonObject().remove("tenantId"); } + if (version.lesserThan(SemVer.v4_0)) { + result.get("session").getAsJsonObject().remove("recipeUserId"); + } result.addProperty("status", "OK"); super.sendJsonResponse(200, result, resp); - } catch (StorageQueryException | StorageTransactionLogicException | TenantOrAppNotFoundException | UnsupportedJWTSigningAlgorithmException e) { + } catch (StorageQueryException | StorageTransactionLogicException | TenantOrAppNotFoundException | + UnsupportedJWTSigningAlgorithmException e) { throw new ServletException(e); } catch (AccessTokenPayloadError | UnauthorisedException e) { - Logging.debug(main, appIdentifierWithStorage.getAsPublicTenantIdentifier(), Utils.exceptionStacktraceToString(e)); + Logging.debug(main, appIdentifierWithStorage.getAsPublicTenantIdentifier(), + Utils.exceptionStacktraceToString(e)); JsonObject reply = new JsonObject(); reply.addProperty("status", "UNAUTHORISED"); reply.addProperty("message", e.getMessage()); super.sendJsonResponse(200, reply, resp); } catch (TokenTheftDetectedException e) { - Logging.debug(main, appIdentifierWithStorage.getAsPublicTenantIdentifier(), Utils.exceptionStacktraceToString(e)); + Logging.debug(main, appIdentifierWithStorage.getAsPublicTenantIdentifier(), + Utils.exceptionStacktraceToString(e)); JsonObject reply = new JsonObject(); reply.addProperty("status", "TOKEN_THEFT_DETECTED"); JsonObject session = new JsonObject(); session.addProperty("handle", e.sessionHandle); - session.addProperty("userId", e.userId); + session.addProperty("userId", e.primaryUserId); + session.addProperty("recipeUserId", e.recipeUserId); reply.add("session", session); super.sendJsonResponse(200, reply, resp); diff --git a/src/main/java/io/supertokens/webserver/api/session/SessionAPI.java b/src/main/java/io/supertokens/webserver/api/session/SessionAPI.java index 0ea0b0be9..4263285c2 100644 --- a/src/main/java/io/supertokens/webserver/api/session/SessionAPI.java +++ b/src/main/java/io/supertokens/webserver/api/session/SessionAPI.java @@ -34,7 +34,6 @@ import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.session.SessionInfo; -import io.supertokens.pluginInterface.useridmapping.UserIdMapping; import io.supertokens.session.Session; import io.supertokens.session.accessToken.AccessToken; import io.supertokens.session.info.SessionInformationHolder; @@ -108,8 +107,8 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I try { io.supertokens.pluginInterface.useridmapping.UserIdMapping userIdMapping = io.supertokens.useridmapping.UserIdMapping.getUserIdMapping( - this.getAppIdentifierWithStorage(req), - sessionInfo.session.userId, UserIdType.ANY); + this.getAppIdentifierWithStorage(req), + sessionInfo.session.userId, UserIdType.ANY); if (userIdMapping != null) { ActiveUsers.updateLastActive(this.getAppIdentifierWithStorage(req), main, userIdMapping.superTokensUserId); @@ -126,6 +125,9 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I if (getVersionFromRequest(req).lesserThan(SemVer.v3_0)) { result.get("session").getAsJsonObject().remove("tenantId"); } + if (version.lesserThan(SemVer.v4_0)) { + result.get("session").getAsJsonObject().remove("recipeUserId"); + } result.addProperty("status", "OK"); @@ -139,7 +141,10 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I super.sendJsonResponse(200, result, resp); } catch (AccessTokenPayloadError e) { throw new ServletException(new BadRequestException(e.getMessage())); - } catch (NoSuchAlgorithmException | StorageQueryException | InvalidKeyException | InvalidKeySpecException | StorageTransactionLogicException | SignatureException | IllegalBlockSizeException | BadPaddingException | InvalidAlgorithmParameterException | NoSuchPaddingException | TenantOrAppNotFoundException | UnsupportedJWTSigningAlgorithmException e) { + } catch (NoSuchAlgorithmException | StorageQueryException | InvalidKeyException | InvalidKeySpecException | + StorageTransactionLogicException | SignatureException | IllegalBlockSizeException | + BadPaddingException | InvalidAlgorithmParameterException | NoSuchPaddingException | + TenantOrAppNotFoundException | UnsupportedJWTSigningAlgorithmException e) { throw new ServletException(e); } } @@ -153,7 +158,8 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO TenantIdentifierWithStorage tenantIdentifierWithStorage = null; try { AppIdentifierWithStorage appIdentifier = getAppIdentifierWithStorage(req); - TenantIdentifier tenantIdentifier = new TenantIdentifier(appIdentifier.getConnectionUriDomain(), appIdentifier.getAppId(), Session.getTenantIdFromSessionHandle(sessionHandle)); + TenantIdentifier tenantIdentifier = new TenantIdentifier(appIdentifier.getConnectionUriDomain(), + appIdentifier.getAppId(), Session.getTenantIdFromSessionHandle(sessionHandle)); tenantIdentifierWithStorage = tenantIdentifier.withStorage(StorageLayer.getStorage(tenantIdentifier, main)); } catch (TenantOrAppNotFoundException e) { throw new ServletException(e); @@ -171,6 +177,9 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO if (getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v3_0)) { result.addProperty("tenantId", tenantIdentifierWithStorage.getTenantId()); } + if (getVersionFromRequest(req).lesserThan(SemVer.v4_0)) { + result.remove("recipeUserId"); + } super.sendJsonResponse(200, result, resp); diff --git a/src/main/java/io/supertokens/webserver/api/session/SessionRegenerateAPI.java b/src/main/java/io/supertokens/webserver/api/session/SessionRegenerateAPI.java index 471518e3e..0f2843e65 100644 --- a/src/main/java/io/supertokens/webserver/api/session/SessionRegenerateAPI.java +++ b/src/main/java/io/supertokens/webserver/api/session/SessionRegenerateAPI.java @@ -86,6 +86,9 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I if (getVersionFromRequest(req).lesserThan(SemVer.v3_0)) { result.get("session").getAsJsonObject().remove("tenantId"); } + if (getVersionFromRequest(req).lesserThan(SemVer.v4_0)) { + result.get("session").getAsJsonObject().remove("recipeUserId"); + } result.addProperty("status", "OK"); super.sendJsonResponse(200, result, resp); diff --git a/src/main/java/io/supertokens/webserver/api/session/VerifySessionAPI.java b/src/main/java/io/supertokens/webserver/api/session/VerifySessionAPI.java index a1da647bc..8b3fd7d84 100644 --- a/src/main/java/io/supertokens/webserver/api/session/VerifySessionAPI.java +++ b/src/main/java/io/supertokens/webserver/api/session/VerifySessionAPI.java @@ -95,7 +95,8 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I if (!super.getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v2_21)) { result.addProperty("jwtSigningPublicKey", - new Utils.PubPriKey(SigningKeys.getInstance(appIdentifier, main).getLatestIssuedDynamicKey().value).publicKey); + new Utils.PubPriKey(SigningKeys.getInstance(appIdentifier, main) + .getLatestIssuedDynamicKey().value).publicKey); result.addProperty("jwtSigningPublicKeyExpiryTime", SigningKeys.getInstance(appIdentifier, main).getDynamicSigningKeyExpiryTime()); @@ -106,9 +107,13 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I if (getVersionFromRequest(req).lesserThan(SemVer.v3_0)) { result.get("session").getAsJsonObject().remove("tenantId"); } + if (getVersionFromRequest(req).lesserThan(SemVer.v4_0)) { + result.get("session").getAsJsonObject().remove("recipeUserId"); + } super.sendJsonResponse(200, result, resp); - } catch (StorageQueryException | StorageTransactionLogicException | TenantOrAppNotFoundException | UnsupportedJWTSigningAlgorithmException e) { + } catch (StorageQueryException | StorageTransactionLogicException | TenantOrAppNotFoundException | + UnsupportedJWTSigningAlgorithmException e) { throw new ServletException(e); } catch (AccessTokenPayloadError e) { throw new ServletException(new BadRequestException(e.getMessage())); @@ -136,7 +141,8 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I reply.addProperty("message", e.getMessage()); super.sendJsonResponse(200, reply, resp); - } catch (StorageQueryException | StorageTransactionLogicException | TenantOrAppNotFoundException | UnsupportedJWTSigningAlgorithmException e2) { + } catch (StorageQueryException | StorageTransactionLogicException | TenantOrAppNotFoundException | + UnsupportedJWTSigningAlgorithmException e2) { throw new ServletException(e2); } } diff --git a/src/test/java/io/supertokens/test/session/AccessTokenTest.java b/src/test/java/io/supertokens/test/session/AccessTokenTest.java index 4a8235380..968c2a460 100644 --- a/src/test/java/io/supertokens/test/session/AccessTokenTest.java +++ b/src/test/java/io/supertokens/test/session/AccessTokenTest.java @@ -105,7 +105,7 @@ public void testCreateSessionWithDataExpireGetAccessTokenAndCheckPayload() throw // check payload is fine assertEquals(accessTokenInfo.userData, userDataInJWT); - assertEquals(accessTokenInfo.userId, userId); + assertEquals(accessTokenInfo.recipeUserId, userId); process.kill(); assertNotNull(process.checkOrWaitForEvent(PROCESS_STATE.STOPPED)); @@ -142,7 +142,7 @@ public void testCreateSessionV2WithDataExpireGetAccessTokenAndCheckPayload() thr // check payload is fine assertEquals(accessTokenInfo.userData, userDataInJWT); - assertEquals(accessTokenInfo.userId, userId); + assertEquals(accessTokenInfo.recipeUserId, userId); process.kill(); assertNotNull(process.checkOrWaitForEvent(PROCESS_STATE.STOPPED)); @@ -265,7 +265,7 @@ public void inputOutputTest() throws Exception { AccessToken.getLatestVersion(), false); AccessTokenInfo info = AccessToken.getInfoFromAccessToken(process.getProcess(), newToken.token, true); assertEquals("sessionHandle", info.sessionHandle); - assertEquals("userId", info.userId); + assertEquals("userId", info.recipeUserId); assertEquals("refreshTokenHash1", info.refreshTokenHash1); assertEquals("parentRefreshTokenHash1", info.parentRefreshTokenHash1); assertEquals("value", info.userData.get("key").getAsString()); @@ -295,7 +295,7 @@ public void inputOutputTestStatic() throws Exception { AccessToken.getLatestVersion(), true); AccessTokenInfo info = AccessToken.getInfoFromAccessToken(process.getProcess(), newToken.token, true); assertEquals("sessionHandle", info.sessionHandle); - assertEquals("userId", info.userId); + assertEquals("userId", info.recipeUserId); assertEquals("refreshTokenHash1", info.refreshTokenHash1); assertEquals("parentRefreshTokenHash1", info.parentRefreshTokenHash1); assertEquals("value", info.userData.get("key").getAsString()); @@ -324,7 +324,7 @@ public void inputOutputTestV2() throws Exception { AccessToken.VERSION.V2, false); AccessTokenInfo info = AccessToken.getInfoFromAccessToken(process.getProcess(), newToken.token, true); assertEquals("sessionHandle", info.sessionHandle); - assertEquals("userId", info.userId); + assertEquals("userId", info.recipeUserId); assertEquals("refreshTokenHash1", info.refreshTokenHash1); assertEquals("parentRefreshTokenHash1", info.parentRefreshTokenHash1); assertEquals("value", info.userData.get("key").getAsString()); @@ -350,7 +350,7 @@ public void inputOutputTestv1() throws InterruptedException, InvalidKeyException "refreshTokenHash1", "parentRefreshTokenHash1", jsonObj, "antiCsrfToken"); AccessTokenInfo info = AccessToken.getInfoFromAccessToken(process.getProcess(), newToken.token, true); assertEquals("sessionHandle", info.sessionHandle); - assertEquals("userId", info.userId); + assertEquals("userId", info.recipeUserId); assertEquals("refreshTokenHash1", info.refreshTokenHash1); assertEquals("parentRefreshTokenHash1", info.parentRefreshTokenHash1); assertEquals("value", info.userData.get("key").getAsString()); diff --git a/src/test/java/io/supertokens/test/session/SessionTest2.java b/src/test/java/io/supertokens/test/session/SessionTest2.java index 43717bdf3..5643cfc16 100644 --- a/src/test/java/io/supertokens/test/session/SessionTest2.java +++ b/src/test/java/io/supertokens/test/session/SessionTest2.java @@ -21,10 +21,7 @@ import io.supertokens.Main; import io.supertokens.ProcessState; import io.supertokens.exceptions.TokenTheftDetectedException; -import io.supertokens.exceptions.TryRefreshTokenException; import io.supertokens.exceptions.UnauthorisedException; -import io.supertokens.pluginInterface.exceptions.StorageQueryException; -import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.session.SessionStorage; import io.supertokens.session.Session; @@ -39,16 +36,6 @@ import org.junit.Test; import org.junit.rules.TestRule; -import javax.crypto.BadPaddingException; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.NoSuchPaddingException; -import java.io.IOException; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.SignatureException; -import java.security.spec.InvalidKeySpecException; - import static junit.framework.TestCase.assertEquals; import static org.junit.Assert.*; @@ -82,7 +69,8 @@ public void tokenTheft_S1_R1_S2_R1() throws Exception { JsonObject userDataInDatabase = new JsonObject(); userDataInDatabase.addProperty("key", "value"); - SessionInformationHolder sessionInfo = Session.createNewSession(main, userId, userDataInJWT, userDataInDatabase); + SessionInformationHolder sessionInfo = Session.createNewSession(main, userId, userDataInJWT, + userDataInDatabase); assert sessionInfo.refreshToken != null; assert sessionInfo.accessToken != null; @@ -97,10 +85,11 @@ public void tokenTheft_S1_R1_S2_R1() throws Exception { assertNotEquals(sessionObj.accessToken.token, newRefreshedSession.accessToken.token); try { - Session.refreshSession(main, sessionInfo.refreshToken.token, sessionInfo.antiCsrfToken, false, AccessToken.getLatestVersion()); + Session.refreshSession(main, sessionInfo.refreshToken.token, sessionInfo.antiCsrfToken, false, + AccessToken.getLatestVersion()); } catch (TokenTheftDetectedException e) { assertEquals(e.sessionHandle, sessionInfo.session.handle); - assertEquals(e.userId, sessionInfo.session.userId); + assertEquals(e.recipeUserId, sessionInfo.session.userId); } process.kill(); @@ -123,7 +112,8 @@ public void tokenTheft_S1_R1_R2_R1() throws Exception { JsonObject userDataInDatabase = new JsonObject(); userDataInDatabase.addProperty("key", "value"); - SessionInformationHolder sessionInfo = Session.createNewSession(main, userId, userDataInJWT, userDataInDatabase); + SessionInformationHolder sessionInfo = Session.createNewSession(main, userId, userDataInJWT, + userDataInDatabase); assert sessionInfo.refreshToken != null; assert sessionInfo.accessToken != null; @@ -133,15 +123,17 @@ public void tokenTheft_S1_R1_R2_R1() throws Exception { assert newRefreshedSession1.accessToken != null; SessionInformationHolder newRefreshedSession2 = Session.refreshSession(main, - newRefreshedSession1.refreshToken.token, newRefreshedSession1.antiCsrfToken, false, AccessToken.getLatestVersion()); + newRefreshedSession1.refreshToken.token, newRefreshedSession1.antiCsrfToken, false, + AccessToken.getLatestVersion()); assert newRefreshedSession2.refreshToken != null; assert newRefreshedSession2.accessToken != null; try { - Session.refreshSession(main, sessionInfo.refreshToken.token, sessionInfo.antiCsrfToken, false, AccessToken.getLatestVersion()); + Session.refreshSession(main, sessionInfo.refreshToken.token, sessionInfo.antiCsrfToken, false, + AccessToken.getLatestVersion()); } catch (TokenTheftDetectedException e) { assertEquals(e.sessionHandle, sessionInfo.session.handle); - assertEquals(e.userId, sessionInfo.session.userId); + assertEquals(e.recipeUserId, sessionInfo.session.userId); } process.kill(); @@ -174,7 +166,8 @@ public void updateSessionInfo() throws Exception { JsonArray arr = new JsonArray(); userDataInDatabase2.add("key3", arr); - Session.updateSession(process.getProcess(), sessionInfo.session.handle, userDataInDatabase2, null, AccessToken.getLatestVersion()); + Session.updateSession(process.getProcess(), sessionInfo.session.handle, userDataInDatabase2, null, + AccessToken.getLatestVersion()); JsonObject sessionDataAfterUpdate = Session.getSessionData(process.getProcess(), sessionInfo.session.handle); assertEquals(userDataInDatabase2.toString(), sessionDataAfterUpdate.toString()); From 38ef5dbed6ac72197f2cb4678ae9a91a11c52601 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Sat, 5 Aug 2023 12:38:54 +0530 Subject: [PATCH 082/131] adds session changes to changelog --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b12a1e94a..baca16392 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,15 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [unreleased] +- Support for CDI version 4.0 + +### Session recipe changes: +- New access token version: v5, which contains a required prop: `recipesub`. This contains the recipe user ID that belongs to the login method that the user used to login. The `sub` claim in the access token payload is now the primary user ID. +- APIs that return `SessionInformation` (like GET `/recipe/session`) contains userId, recipeUserId in the response. +- Apis that create / modify / refresh a session return the `recipeUserId` in the `session` object in the response. +- Token theft detected response returns userId and recipeUserId + + ### Db schema changes: - Added new index `all_auth_recipe_users_primary_user_id_index`. From 6f148bf083fdc9c74a2bda829dcb675db95ae3db Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Sat, 5 Aug 2023 17:19:53 +0530 Subject: [PATCH 083/131] changes claim ro rsub --- CHANGELOG.md | 2 +- .../io/supertokens/session/accessToken/AccessToken.java | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index baca16392..eb6a91080 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - Support for CDI version 4.0 ### Session recipe changes: -- New access token version: v5, which contains a required prop: `recipesub`. This contains the recipe user ID that belongs to the login method that the user used to login. The `sub` claim in the access token payload is now the primary user ID. +- New access token version: v5, which contains a required prop: `rsub`. This contains the recipe user ID that belongs to the login method that the user used to login. The `sub` claim in the access token payload is now the primary user ID. - APIs that return `SessionInformation` (like GET `/recipe/session`) contains userId, recipeUserId in the response. - Apis that create / modify / refresh a session return the `recipeUserId` in the `session` object in the response. - Token theft detected response returns userId and recipeUserId diff --git a/src/main/java/io/supertokens/session/accessToken/AccessToken.java b/src/main/java/io/supertokens/session/accessToken/AccessToken.java index f009022fc..d343a8f71 100644 --- a/src/main/java/io/supertokens/session/accessToken/AccessToken.java +++ b/src/main/java/io/supertokens/session/accessToken/AccessToken.java @@ -369,7 +369,7 @@ public static class AccessTokenInfo { "parentRefreshTokenHash1", "antiCsrfToken", "tId", - "recipesub" + "rsub" }; @Nonnull @@ -456,7 +456,7 @@ static AccessTokenInfo fromJSON(AppIdentifier appIdentifier, JsonObject payload, String recipeUserId = payload.get("sub").getAsString(); if (version != VERSION.V3 && version != VERSION.V4) { // this means >= v5 - recipeUserId = payload.get("recipesub").getAsString(); + recipeUserId = payload.get("rsub").getAsString(); } return new AccessTokenInfo( @@ -504,7 +504,7 @@ JsonObject toJSON() throws AccessTokenPayloadError { } if (this.version != VERSION.V3 && this.version != VERSION.V4) { // this means >= v5 - res.addProperty("recipesub", this.recipeUserId); + res.addProperty("rsub", this.recipeUserId); } } else { res.addProperty("userId", this.primaryUserId); From 1424ad494250951797bef6d0439cb3d13d85aaa8 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Sat, 5 Aug 2023 17:27:59 +0530 Subject: [PATCH 084/131] changes API spec --- .../io/supertokens/emailpassword/EmailPassword.java | 10 ++++++++++ .../webserver/api/emailpassword/UserAPI.java | 8 +++++++- .../test/emailpassword/api/UserPutAPITest4_0.java | 2 +- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/supertokens/emailpassword/EmailPassword.java b/src/main/java/io/supertokens/emailpassword/EmailPassword.java index 91b52a305..1b9b1a28e 100644 --- a/src/main/java/io/supertokens/emailpassword/EmailPassword.java +++ b/src/main/java/io/supertokens/emailpassword/EmailPassword.java @@ -590,6 +590,16 @@ public static void updateUsersEmailOrPassword(AppIdentifierWithStorage appIdenti if (user == null) { throw new StorageTransactionLogicException(new UnknownUserIdException()); } + boolean foundEmailPasswordLoginMethod = false; + for (LoginMethod lm : user.loginMethods) { + if (lm.recipeId == RECIPE_ID.EMAIL_PASSWORD) { + foundEmailPasswordLoginMethod = true; + break; + } + } + if (!foundEmailPasswordLoginMethod) { + throw new StorageTransactionLogicException(new UnknownUserIdException()); + } if (email != null) { if (user.isPrimaryUser) { diff --git a/src/main/java/io/supertokens/webserver/api/emailpassword/UserAPI.java b/src/main/java/io/supertokens/webserver/api/emailpassword/UserAPI.java index bdfc432b8..17466dc8a 100644 --- a/src/main/java/io/supertokens/webserver/api/emailpassword/UserAPI.java +++ b/src/main/java/io/supertokens/webserver/api/emailpassword/UserAPI.java @@ -155,7 +155,13 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { // API is app specific JsonObject input = InputParser.parseJsonObjectOrThrowError(req); - String userId = InputParser.parseStringOrThrowError(input, "userId", false); + String userId; + + if (getVersionFromRequest(req).lesserThan(SemVer.v4_0)) { + userId = InputParser.parseStringOrThrowError(input, "userId", true); + } else { + userId = InputParser.parseStringOrThrowError(input, "recipeUserId", true); + } String email = InputParser.parseStringOrThrowError(input, "email", true); String password = InputParser.parseStringOrThrowError(input, "password", true); diff --git a/src/test/java/io/supertokens/test/emailpassword/api/UserPutAPITest4_0.java b/src/test/java/io/supertokens/test/emailpassword/api/UserPutAPITest4_0.java index 66b6893c8..cce87f29a 100644 --- a/src/test/java/io/supertokens/test/emailpassword/api/UserPutAPITest4_0.java +++ b/src/test/java/io/supertokens/test/emailpassword/api/UserPutAPITest4_0.java @@ -74,7 +74,7 @@ public void testThatAPIReturnsEmailUpdateNotPossibleWithSingleTenant() throws Ex AuthRecipe.createPrimaryUser(process.main, user.id); JsonObject body = new JsonObject(); - body.addProperty("userId", user.id); + body.addProperty("recipeUserId", user.id); body.addProperty("email", "someemail1@gmail.com"); JsonObject response = HttpRequestForTesting.sendJsonPUTRequest(process.getProcess(), "", From f1b91caa76b05c7ad065c07379e506cb538223e1 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Sat, 5 Aug 2023 19:14:35 +0530 Subject: [PATCH 085/131] changes API spec --- .../io/supertokens/authRecipe/AuthRecipe.java | 29 ++++--- .../java/io/supertokens/session/Session.java | 77 ++++++++++++++----- .../api/session/SessionRemoveAPI.java | 19 ++++- .../webserver/api/session/SessionUserAPI.java | 13 +++- 4 files changed, 102 insertions(+), 36 deletions(-) diff --git a/src/main/java/io/supertokens/authRecipe/AuthRecipe.java b/src/main/java/io/supertokens/authRecipe/AuthRecipe.java index 6931cf8bf..8909e944d 100644 --- a/src/main/java/io/supertokens/authRecipe/AuthRecipe.java +++ b/src/main/java/io/supertokens/authRecipe/AuthRecipe.java @@ -84,29 +84,34 @@ public static boolean unlinkAccounts(Main main, AppIdentifierWithStorage appIden throw new StorageTransactionLogicException(new InputUserIdIsNotAPrimaryUserException(recipeUserId)); } + io.supertokens.pluginInterface.useridmapping.UserIdMapping mappingResult = + io.supertokens.useridmapping.UserIdMapping.getUserIdMapping( + appIdentifierWithStorage, + recipeUserId, UserIdType.SUPERTOKENS); + if (primaryUser.id.equals(recipeUserId)) { // we are trying to unlink the user ID which is the same as the primary one. if (primaryUser.loginMethods.length == 1) { storage.unlinkAccounts_Transaction(appIdentifierWithStorage, con, recipeUserId); - Session.revokeAllSessionsForUser(main, appIdentifierWithStorage, recipeUserId); + Session.revokeAllSessionsForUser(main, appIdentifierWithStorage, + mappingResult == null ? recipeUserId : mappingResult.externalUserId, + false); return false; } else { // Here we delete the recipe user id cause if we just unlink, then there will be two // distinct users with the same ID - which is a broken state. // The delete will also cause the automatic unlinking. - io.supertokens.pluginInterface.useridmapping.UserIdMapping mappingResult = - io.supertokens.useridmapping.UserIdMapping.getUserIdMapping( - appIdentifierWithStorage, - recipeUserId, UserIdType.SUPERTOKENS); // We need to make sure that it only deletes sessions for recipeUserId and not other linked // users who have their sessions for primaryUserId (that is equal to the recipeUserId) - Session.revokeAllSessionsForUser(main, appIdentifierWithStorage, recipeUserId); + Session.revokeAllSessionsForUser(main, appIdentifierWithStorage, + mappingResult == null ? recipeUserId : mappingResult.externalUserId, false); deleteUserHelper(con, appIdentifierWithStorage, recipeUserId, false, mappingResult); return true; } } else { storage.unlinkAccounts_Transaction(appIdentifierWithStorage, con, recipeUserId); - Session.revokeAllSessionsForUser(main, appIdentifierWithStorage, recipeUserId); + Session.revokeAllSessionsForUser(main, appIdentifierWithStorage, + mappingResult == null ? recipeUserId : mappingResult.externalUserId, false); return false; } }); @@ -318,7 +323,8 @@ public static boolean linkAccounts(Main main, String recipeUserId, String primar } public static boolean linkAccounts(Main main, AppIdentifierWithStorage appIdentifierWithStorage, - String _recipeUserId, String _primaryUserId) throws StorageQueryException, + String _recipeUserId, String _primaryUserId) + throws StorageQueryException, AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException, RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException, InputUserIdIsNotAPrimaryUserException, UnknownUserIdException, TenantOrAppNotFoundException, FeatureNotEnabledException { @@ -355,8 +361,13 @@ public static boolean linkAccounts(Main main, AppIdentifierWithStorage appIdenti }); if (!wasAlreadyLinked) { + io.supertokens.pluginInterface.useridmapping.UserIdMapping mappingResult = + io.supertokens.useridmapping.UserIdMapping.getUserIdMapping( + appIdentifierWithStorage, + _recipeUserId, UserIdType.SUPERTOKENS); // finally, we revoke all sessions of the recipeUser Id cause their user ID has changed. - Session.revokeAllSessionsForUser(main, appIdentifierWithStorage, _recipeUserId); + Session.revokeAllSessionsForUser(main, appIdentifierWithStorage, + mappingResult == null ? _recipeUserId : mappingResult.externalUserId, false); } return wasAlreadyLinked; diff --git a/src/main/java/io/supertokens/session/Session.java b/src/main/java/io/supertokens/session/Session.java index a01056b6d..a34c68ea7 100644 --- a/src/main/java/io/supertokens/session/Session.java +++ b/src/main/java/io/supertokens/session/Session.java @@ -29,6 +29,8 @@ import io.supertokens.multitenancy.Multitenancy; import io.supertokens.pluginInterface.STORAGE_TYPE; import io.supertokens.pluginInterface.Storage; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; +import io.supertokens.pluginInterface.authRecipe.LoginMethod; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; import io.supertokens.pluginInterface.multitenancy.*; @@ -802,18 +804,22 @@ private static String[] revokeSessionUsingSessionHandles(TenantIdentifierWithSto public static String[] revokeAllSessionsForUser(Main main, String userId) throws StorageQueryException { Storage storage = StorageLayer.getStorage(main); return revokeAllSessionsForUser(main, - new AppIdentifierWithStorage(null, null, storage), userId); + new AppIdentifierWithStorage(null, null, storage), userId, true); } public static String[] revokeAllSessionsForUser(Main main, AppIdentifierWithStorage appIdentifierWithStorage, - String userId) throws StorageQueryException { - String[] sessionHandles = getAllNonExpiredSessionHandlesForUser(main, appIdentifierWithStorage, userId); + String userId, boolean revokeSessionsForLinkedAccounts) + throws StorageQueryException { + String[] sessionHandles = getAllNonExpiredSessionHandlesForUser(main, appIdentifierWithStorage, userId, + revokeSessionsForLinkedAccounts); return revokeSessionUsingSessionHandles(main, appIdentifierWithStorage, sessionHandles); } public static String[] revokeAllSessionsForUser(Main main, TenantIdentifierWithStorage tenantIdentifierWithStorage, - String userId) throws StorageQueryException { - String[] sessionHandles = getAllNonExpiredSessionHandlesForUser(tenantIdentifierWithStorage, userId); + String userId, boolean revokeSessionsForLinkedAccounts) + throws StorageQueryException { + String[] sessionHandles = getAllNonExpiredSessionHandlesForUser(tenantIdentifierWithStorage, userId, + revokeSessionsForLinkedAccounts); return revokeSessionUsingSessionHandles(main, tenantIdentifierWithStorage.toAppIdentifierWithStorage(), sessionHandles); } @@ -823,28 +829,43 @@ public static String[] getAllNonExpiredSessionHandlesForUser(Main main, String u throws StorageQueryException { Storage storage = StorageLayer.getStorage(main); return getAllNonExpiredSessionHandlesForUser(main, - new AppIdentifierWithStorage(null, null, storage), userId); + new AppIdentifierWithStorage(null, null, storage), userId, true); } public static String[] getAllNonExpiredSessionHandlesForUser( - Main main, AppIdentifierWithStorage appIdentifierWithStorage, String userId) + Main main, AppIdentifierWithStorage appIdentifierWithStorage, String userId, + boolean fetchSessionsForAllLinkedAccounts) throws StorageQueryException { TenantConfig[] tenants = Multitenancy.getAllTenantsForApp( appIdentifierWithStorage, main); List sessionHandles = new ArrayList<>(); - for (TenantConfig tenant : tenants) { - TenantIdentifierWithStorage tenantIdentifierWithStorage = null; - try { - tenantIdentifierWithStorage = tenant.tenantIdentifier.withStorage( - StorageLayer.getStorage(tenant.tenantIdentifier, main)); - sessionHandles.addAll(Arrays.asList(getAllNonExpiredSessionHandlesForUser( - tenantIdentifierWithStorage, userId))); + Set userIds = new HashSet<>(); + userIds.add(userId); + if (fetchSessionsForAllLinkedAccounts) { + AuthRecipeUserInfo primaryUser = appIdentifierWithStorage.getAuthRecipeStorage() + .getPrimaryUserById(appIdentifierWithStorage, userId); + if (primaryUser != null) { + for (LoginMethod lM : primaryUser.loginMethods) { + userIds.add(lM.recipeUserId); + } + } + } - } 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 + for (String currUserId : userIds) { + for (TenantConfig tenant : tenants) { + TenantIdentifierWithStorage tenantIdentifierWithStorage = null; + try { + tenantIdentifierWithStorage = tenant.tenantIdentifier.withStorage( + StorageLayer.getStorage(tenant.tenantIdentifier, main)); + sessionHandles.addAll(Arrays.asList(getAllNonExpiredSessionHandlesForUser( + tenantIdentifierWithStorage, 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 + } } } @@ -852,10 +873,26 @@ public static String[] getAllNonExpiredSessionHandlesForUser( } public static String[] getAllNonExpiredSessionHandlesForUser( - TenantIdentifierWithStorage tenantIdentifierWithStorage, String userId) + TenantIdentifierWithStorage tenantIdentifierWithStorage, String userId, + boolean fetchSessionsForAllLinkedAccounts) throws StorageQueryException { - return tenantIdentifierWithStorage.getSessionStorage() - .getAllNonExpiredSessionHandlesForUser(tenantIdentifierWithStorage, userId); + Set userIds = new HashSet<>(); + userIds.add(userId); + if (fetchSessionsForAllLinkedAccounts) { + AuthRecipeUserInfo primaryUser = tenantIdentifierWithStorage.getAuthRecipeStorage() + .getPrimaryUserById(tenantIdentifierWithStorage.toAppIdentifier(), userId); + if (primaryUser != null) { + for (LoginMethod lM : primaryUser.loginMethods) { + userIds.add(lM.recipeUserId); + } + } + } + List sessionHandles = new ArrayList<>(); + for (String currUserId : userIds) { + sessionHandles.addAll(List.of(tenantIdentifierWithStorage.getSessionStorage() + .getAllNonExpiredSessionHandlesForUser(tenantIdentifierWithStorage, currUserId))); + } + return sessionHandles.toArray(new String[0]); } @TestOnly diff --git a/src/main/java/io/supertokens/webserver/api/session/SessionRemoveAPI.java b/src/main/java/io/supertokens/webserver/api/session/SessionRemoveAPI.java index 8446e20a9..127e0b5fc 100644 --- a/src/main/java/io/supertokens/webserver/api/session/SessionRemoveAPI.java +++ b/src/main/java/io/supertokens/webserver/api/session/SessionRemoveAPI.java @@ -80,22 +80,33 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I Boolean revokeAcrossAllTenants = InputParser.parseBooleanOrThrowError(input, "revokeAcrossAllTenants", true); if (userId == null && revokeAcrossAllTenants != null) { - throw new ServletException(new BadRequestException("Invalid JSON input - revokeAcrossAllTenants can only be set if userId is set")); + throw new ServletException(new BadRequestException( + "Invalid JSON input - revokeAcrossAllTenants can only be set if userId is set")); } - if (revokeAcrossAllTenants == null) { revokeAcrossAllTenants = true; } + Boolean revokeSessionsForLinkedAccounts = InputParser.parseBooleanOrThrowError(input, + "revokeSessionsForLinkedAccounts", true); + if (userId == null && revokeSessionsForLinkedAccounts != null) { + throw new ServletException(new BadRequestException( + "Invalid JSON input - revokeSessionsForLinkedAccounts can only be set if userId is set")); + } + if (revokeSessionsForLinkedAccounts == null) { + revokeSessionsForLinkedAccounts = true; + } + if (userId != null) { try { String[] sessionHandlesRevoked; if (revokeAcrossAllTenants) { sessionHandlesRevoked = Session.revokeAllSessionsForUser( - main, this.getAppIdentifierWithStorage(req), userId); + main, this.getAppIdentifierWithStorage(req), userId, revokeSessionsForLinkedAccounts); } else { sessionHandlesRevoked = Session.revokeAllSessionsForUser( - main, this.getTenantIdentifierWithStorageFromRequest(req), userId); + main, this.getTenantIdentifierWithStorageFromRequest(req), userId, + revokeSessionsForLinkedAccounts); } if (StorageLayer.getStorage(this.getTenantIdentifierWithStorageFromRequest(req), main).getType() == diff --git a/src/main/java/io/supertokens/webserver/api/session/SessionUserAPI.java b/src/main/java/io/supertokens/webserver/api/session/SessionUserAPI.java index 443594c7c..e31c32ee1 100644 --- a/src/main/java/io/supertokens/webserver/api/session/SessionUserAPI.java +++ b/src/main/java/io/supertokens/webserver/api/session/SessionUserAPI.java @@ -20,9 +20,9 @@ import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; import io.supertokens.Main; -import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.session.Session; import io.supertokens.webserver.InputParser; import io.supertokens.webserver.WebserverAPI; @@ -58,14 +58,21 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO fetchAcrossAllTenants = fetchAcrossAllTenantsString.toLowerCase().equals("true"); } + String fetchSessionsForAllLinkedAccountsString = InputParser.getQueryParamOrThrowError(req, + "fetchSessionsForAllLinkedAccounts", true); + boolean fetchSessionsForAllLinkedAccounts = true; + if (fetchSessionsForAllLinkedAccountsString != null) { + fetchSessionsForAllLinkedAccounts = fetchSessionsForAllLinkedAccountsString.toLowerCase().equals("true"); + } + try { String[] sessionHandles; if (fetchAcrossAllTenants) { sessionHandles = Session.getAllNonExpiredSessionHandlesForUser( - main, this.getAppIdentifierWithStorage(req), userId); + main, this.getAppIdentifierWithStorage(req), userId, fetchSessionsForAllLinkedAccounts); } else { sessionHandles = Session.getAllNonExpiredSessionHandlesForUser( - this.getTenantIdentifierWithStorageFromRequest(req), userId); + this.getTenantIdentifierWithStorageFromRequest(req), userId, fetchSessionsForAllLinkedAccounts); } JsonObject result = new JsonObject(); From 2b57539a96fd85183875362d835793baed99dec3 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Sat, 5 Aug 2023 21:05:04 +0530 Subject: [PATCH 086/131] changes to passwordless recipe update user --- .../passwordless/Passwordless.java | 67 ++++++++++++++++--- .../webserver/api/emailpassword/UserAPI.java | 4 +- .../webserver/api/passwordless/UserAPI.java | 14 +++- .../api/PasswordlessUserPutAPITest2_11.java | 24 +++---- 4 files changed, 83 insertions(+), 26 deletions(-) diff --git a/src/main/java/io/supertokens/passwordless/Passwordless.java b/src/main/java/io/supertokens/passwordless/Passwordless.java index 01e98bb04..86e524cf0 100644 --- a/src/main/java/io/supertokens/passwordless/Passwordless.java +++ b/src/main/java/io/supertokens/passwordless/Passwordless.java @@ -17,7 +17,9 @@ package io.supertokens.passwordless; import io.supertokens.Main; +import io.supertokens.authRecipe.AuthRecipe; import io.supertokens.config.Config; +import io.supertokens.emailpassword.exceptions.EmailChangeNotAllowedException; import io.supertokens.multitenancy.Multitenancy; import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.passwordless.exceptions.*; @@ -25,6 +27,7 @@ import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.authRecipe.LoginMethod; +import io.supertokens.pluginInterface.authRecipe.sqlStorage.AuthRecipeSQLStorage; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateUserIdException; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; @@ -32,6 +35,7 @@ import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.TenantConfig; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.passwordless.PasswordlessCode; @@ -49,6 +53,7 @@ import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.stream.Stream; @@ -523,6 +528,7 @@ public static void removeCodesByPhoneNumber(TenantIdentifierWithStorage tenantId } @TestOnly + @Deprecated public static UserInfo getUserById(Main main, String userId) throws StorageQueryException { Storage storage = StorageLayer.getStorage(main); @@ -530,6 +536,7 @@ public static UserInfo getUserById(Main main, String userId) new AppIdentifierWithStorage(null, null, storage), userId); } + @Deprecated public static UserInfo getUserById(AppIdentifierWithStorage appIdentifierWithStorage, String userId) throws StorageQueryException { AuthRecipeUserInfo result = appIdentifierWithStorage.getAuthRecipeStorage() @@ -599,7 +606,7 @@ public static AuthRecipeUserInfo getUserByEmail(TenantIdentifierWithStorage tena public static void updateUser(Main main, String userId, FieldUpdate emailUpdate, FieldUpdate phoneNumberUpdate) throws StorageQueryException, UnknownUserIdException, DuplicateEmailException, - DuplicatePhoneNumberException, UserWithoutContactInfoException { + DuplicatePhoneNumberException, UserWithoutContactInfoException, EmailChangeNotAllowedException { Storage storage = StorageLayer.getStorage(main); updateUser(new AppIdentifierWithStorage(null, null, storage), userId, emailUpdate, phoneNumberUpdate); @@ -608,32 +615,66 @@ public static void updateUser(Main main, String userId, public static void updateUser(AppIdentifierWithStorage appIdentifierWithStorage, String userId, FieldUpdate emailUpdate, FieldUpdate phoneNumberUpdate) throws StorageQueryException, UnknownUserIdException, DuplicateEmailException, - DuplicatePhoneNumberException, UserWithoutContactInfoException { + DuplicatePhoneNumberException, UserWithoutContactInfoException, EmailChangeNotAllowedException { PasswordlessSQLStorage storage = appIdentifierWithStorage.getPasswordlessStorage(); // We do not lock the user here, because we decided that even if the device cleanup used outdated information // it wouldn't leave the system in an incosistent state/cause problems. - UserInfo user = Passwordless.getUserById(appIdentifierWithStorage, userId); + AuthRecipeUserInfo user = AuthRecipe.getUserById(appIdentifierWithStorage, userId); if (user == null) { throw new UnknownUserIdException(); } - boolean emailWillBeDefined = emailUpdate != null ? emailUpdate.newValue != null : user.email != null; + + LoginMethod lM = Arrays.stream(user.loginMethods) + .filter(currlM -> currlM.recipeUserId.equals(userId) && currlM.recipeId == RECIPE_ID.PASSWORDLESS) + .findFirst().orElse(null); + + if (lM == null) { + throw new UnknownUserIdException(); + } + + boolean emailWillBeDefined = emailUpdate != null ? emailUpdate.newValue != null : lM.email != null; boolean phoneNumberWillBeDefined = phoneNumberUpdate != null ? phoneNumberUpdate.newValue != null - : user.phoneNumber != null; + : lM.phoneNumber != null; if (!emailWillBeDefined && !phoneNumberWillBeDefined) { throw new UserWithoutContactInfoException(); } try { + AuthRecipeSQLStorage authRecipeSQLStorage = + (AuthRecipeSQLStorage) appIdentifierWithStorage.getAuthRecipeStorage(); storage.startTransaction(con -> { - if (emailUpdate != null && !Objects.equals(emailUpdate.newValue, user.email)) { + if (emailUpdate != null && !Objects.equals(emailUpdate.newValue, lM.email)) { + if (user.isPrimaryUser) { + for (String tenantId : user.tenantIds) { + // we do not bother with getting the tenantIdentifierWithStorage 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. + TenantIdentifier tenantIdentifier = new TenantIdentifier( + appIdentifierWithStorage.getConnectionUriDomain(), + appIdentifierWithStorage.getAppId(), + tenantId); + + AuthRecipeUserInfo[] existingUsersWithNewEmail = + authRecipeSQLStorage.listPrimaryUsersByEmail_Transaction( + tenantIdentifier, con, + emailUpdate.newValue); + + for (AuthRecipeUserInfo userWithSameEmail : existingUsersWithNewEmail) { + if (userWithSameEmail.isPrimaryUser && !userWithSameEmail.id.equals(user.id)) { + throw new StorageTransactionLogicException( + new EmailChangeNotAllowedException()); + } + } + } + } try { storage.updateUserEmail_Transaction(appIdentifierWithStorage, con, userId, emailUpdate.newValue); } catch (UnknownUserIdException | DuplicateEmailException e) { throw new StorageTransactionLogicException(e); } - if (user.email != null) { - storage.deleteDevicesByEmail_Transaction(appIdentifierWithStorage, con, user.email, + if (lM.email != null) { + storage.deleteDevicesByEmail_Transaction(appIdentifierWithStorage, con, lM.email, userId); } if (emailUpdate.newValue != null) { @@ -641,16 +682,16 @@ public static void updateUser(AppIdentifierWithStorage appIdentifierWithStorage, emailUpdate.newValue, userId); } } - if (phoneNumberUpdate != null && !Objects.equals(phoneNumberUpdate.newValue, user.phoneNumber)) { + if (phoneNumberUpdate != null && !Objects.equals(phoneNumberUpdate.newValue, lM.phoneNumber)) { try { storage.updateUserPhoneNumber_Transaction(appIdentifierWithStorage, con, userId, phoneNumberUpdate.newValue); } catch (UnknownUserIdException | DuplicatePhoneNumberException e) { throw new StorageTransactionLogicException(e); } - if (user.phoneNumber != null) { + if (lM.phoneNumber != null) { storage.deleteDevicesByPhoneNumber_Transaction(appIdentifierWithStorage, con, - user.phoneNumber, userId); + lM.phoneNumber, userId); } if (phoneNumberUpdate.newValue != null) { storage.deleteDevicesByPhoneNumber_Transaction(appIdentifierWithStorage, con, @@ -672,6 +713,10 @@ public static void updateUser(AppIdentifierWithStorage appIdentifierWithStorage, if (e.actualException instanceof DuplicatePhoneNumberException) { throw (DuplicatePhoneNumberException) e.actualException; } + + if (e.actualException instanceof EmailChangeNotAllowedException) { + throw (EmailChangeNotAllowedException) e.actualException; + } } } diff --git a/src/main/java/io/supertokens/webserver/api/emailpassword/UserAPI.java b/src/main/java/io/supertokens/webserver/api/emailpassword/UserAPI.java index 17466dc8a..430b8d993 100644 --- a/src/main/java/io/supertokens/webserver/api/emailpassword/UserAPI.java +++ b/src/main/java/io/supertokens/webserver/api/emailpassword/UserAPI.java @@ -158,9 +158,9 @@ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IO String userId; if (getVersionFromRequest(req).lesserThan(SemVer.v4_0)) { - userId = InputParser.parseStringOrThrowError(input, "userId", true); + userId = InputParser.parseStringOrThrowError(input, "userId", false); } else { - userId = InputParser.parseStringOrThrowError(input, "recipeUserId", true); + userId = InputParser.parseStringOrThrowError(input, "recipeUserId", false); } String email = InputParser.parseStringOrThrowError(input, "email", true); String password = InputParser.parseStringOrThrowError(input, "password", true); diff --git a/src/main/java/io/supertokens/webserver/api/passwordless/UserAPI.java b/src/main/java/io/supertokens/webserver/api/passwordless/UserAPI.java index d5e9c66c7..7ed5e003e 100644 --- a/src/main/java/io/supertokens/webserver/api/passwordless/UserAPI.java +++ b/src/main/java/io/supertokens/webserver/api/passwordless/UserAPI.java @@ -19,6 +19,7 @@ import com.google.gson.JsonObject; import io.supertokens.AppIdentifierWithStorageAndUserIdMapping; import io.supertokens.Main; +import io.supertokens.emailpassword.exceptions.EmailChangeNotAllowedException; import io.supertokens.passwordless.Passwordless; import io.supertokens.passwordless.Passwordless.FieldUpdate; import io.supertokens.passwordless.exceptions.UserWithoutContactInfoException; @@ -153,7 +154,13 @@ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IO // API is app specific // logic based on: https://app.code2flow.com/TXloWHJOwWKg JsonObject input = InputParser.parseJsonObjectOrThrowError(req); - String userId = InputParser.parseStringOrThrowError(input, "userId", false); + String userId; + + if (getVersionFromRequest(req).lesserThan(SemVer.v4_0)) { + userId = InputParser.parseStringOrThrowError(input, "userId", false); + } else { + userId = InputParser.parseStringOrThrowError(input, "recipeUserId", false); + } FieldUpdate emailUpdate = !input.has("email") ? null : new FieldUpdate(input.get("email").isJsonNull() ? null @@ -194,6 +201,11 @@ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IO } catch (UserWithoutContactInfoException e) { throw new ServletException( new BadRequestException("You cannot clear both email and phone number of a user")); + } catch (EmailChangeNotAllowedException e) { + JsonObject result = new JsonObject(); + result.addProperty("status", "EMAIL_CHANGE_NOT_ALLOWED_ERROR"); + result.addProperty("reason", "New email is associated with another primary user ID"); + super.sendJsonResponse(200, result, resp); } } } diff --git a/src/test/java/io/supertokens/test/passwordless/api/PasswordlessUserPutAPITest2_11.java b/src/test/java/io/supertokens/test/passwordless/api/PasswordlessUserPutAPITest2_11.java index 5495f5437..f620edc0f 100644 --- a/src/test/java/io/supertokens/test/passwordless/api/PasswordlessUserPutAPITest2_11.java +++ b/src/test/java/io/supertokens/test/passwordless/api/PasswordlessUserPutAPITest2_11.java @@ -62,7 +62,7 @@ public void testBadInput() throws Exception { return; } - String userId = "userId"; + String userId = "6347c997-4cc9-4f95-94c9-b96e2c65aefc"; String email = "test@example.com"; String email2 = "test2@example.com"; @@ -146,7 +146,7 @@ public void testEmailToPhone() throws Exception { return; } - String userId = "userId"; + String userId = "6347c997-4cc9-4f95-94c9-b96e2c65aefc"; String phoneNumber = "+442071838750"; PasswordlessStorage storage = (PasswordlessStorage) StorageLayer.getStorage(process.getProcess()); @@ -187,7 +187,7 @@ public void testPhoneToEmail() throws Exception { return; } - String userId = "userId"; + String userId = "6347c997-4cc9-4f95-94c9-b96e2c65aefc"; String phoneNumber = "+442071838750"; String email = "email"; @@ -228,7 +228,7 @@ public void testPhoneAndEmail() throws Exception { return; } - String userId = "userId"; + String userId = "6347c997-4cc9-4f95-94c9-b96e2c65aefc"; String phoneNumber = "+442071838750"; String email = "email"; String updatedPhoneNumber = "+442071838751"; @@ -277,7 +277,7 @@ public void clearEmailAndPhone() throws Exception { return; } - String userId = "userId"; + String userId = "6347c997-4cc9-4f95-94c9-b96e2c65aefc"; String phoneNumber = "+442071838750"; String email = "email"; @@ -326,7 +326,7 @@ public void clearEmailOfEmailOnlyUser() throws Exception { return; } - String userId = "userId"; + String userId = "6347c997-4cc9-4f95-94c9-b96e2c65aefc"; String email = "email"; PasswordlessStorage storage = (PasswordlessStorage) StorageLayer.getStorage(process.getProcess()); @@ -374,7 +374,7 @@ public void clearPhoneNUmberOfPhoneNumberOnlyUser() throws Exception { return; } - String userId = "userId"; + String userId = "6347c997-4cc9-4f95-94c9-b96e2c65aefc"; String phoneNumber = "+91898989898"; PasswordlessStorage storage = (PasswordlessStorage) StorageLayer.getStorage(process.getProcess()); @@ -421,7 +421,7 @@ public void clearEmail() throws Exception { return; } - String userId = "userId"; + String userId = "6347c997-4cc9-4f95-94c9-b96e2c65aefc"; String email = "email"; String phoneNumber = "+9189898989"; @@ -462,7 +462,7 @@ public void clearPhone() throws Exception { return; } - String userId = "userId"; + String userId = "6347c997-4cc9-4f95-94c9-b96e2c65aefc"; String email = "email"; String phoneNumber = "+9189898989"; @@ -503,7 +503,7 @@ public void updateNothing() throws Exception { return; } - String userId = "userId"; + String userId = "6347c997-4cc9-4f95-94c9-b96e2c65aefc"; String email = "email"; String phoneNumber = "+9189898989"; @@ -543,7 +543,7 @@ public void testUpdateEmail() throws Exception { return; } - String userId = "userId"; + String userId = "6347c997-4cc9-4f95-94c9-b96e2c65aefc"; PasswordlessStorage storage = (PasswordlessStorage) StorageLayer.getStorage(process.getProcess()); String email = "email"; @@ -584,7 +584,7 @@ public void testUpdatePhoneNumber() throws Exception { return; } - String userId = "userId"; + String userId = "6347c997-4cc9-4f95-94c9-b96e2c65aefc"; String phoneNumber = "+442071838750"; String updatedPhoneNumber = "+442071838751"; From fc4fba09928451a58218035dff3498c1be96d68a Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Sat, 5 Aug 2023 21:08:39 +0530 Subject: [PATCH 087/131] renames variable --- .../supertokens/passwordless/Passwordless.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/io/supertokens/passwordless/Passwordless.java b/src/main/java/io/supertokens/passwordless/Passwordless.java index 86e524cf0..9dd392c18 100644 --- a/src/main/java/io/supertokens/passwordless/Passwordless.java +++ b/src/main/java/io/supertokens/passwordless/Passwordless.java @@ -612,7 +612,7 @@ public static void updateUser(Main main, String userId, userId, emailUpdate, phoneNumberUpdate); } - public static void updateUser(AppIdentifierWithStorage appIdentifierWithStorage, String userId, + public static void updateUser(AppIdentifierWithStorage appIdentifierWithStorage, String recipeUserId, FieldUpdate emailUpdate, FieldUpdate phoneNumberUpdate) throws StorageQueryException, UnknownUserIdException, DuplicateEmailException, DuplicatePhoneNumberException, UserWithoutContactInfoException, EmailChangeNotAllowedException { @@ -620,13 +620,13 @@ public static void updateUser(AppIdentifierWithStorage appIdentifierWithStorage, // We do not lock the user here, because we decided that even if the device cleanup used outdated information // it wouldn't leave the system in an incosistent state/cause problems. - AuthRecipeUserInfo user = AuthRecipe.getUserById(appIdentifierWithStorage, userId); + AuthRecipeUserInfo user = AuthRecipe.getUserById(appIdentifierWithStorage, recipeUserId); if (user == null) { throw new UnknownUserIdException(); } LoginMethod lM = Arrays.stream(user.loginMethods) - .filter(currlM -> currlM.recipeUserId.equals(userId) && currlM.recipeId == RECIPE_ID.PASSWORDLESS) + .filter(currlM -> currlM.recipeUserId.equals(recipeUserId) && currlM.recipeId == RECIPE_ID.PASSWORDLESS) .findFirst().orElse(null); if (lM == null) { @@ -668,34 +668,34 @@ public static void updateUser(AppIdentifierWithStorage appIdentifierWithStorage, } } try { - storage.updateUserEmail_Transaction(appIdentifierWithStorage, con, userId, + storage.updateUserEmail_Transaction(appIdentifierWithStorage, con, recipeUserId, emailUpdate.newValue); } catch (UnknownUserIdException | DuplicateEmailException e) { throw new StorageTransactionLogicException(e); } if (lM.email != null) { storage.deleteDevicesByEmail_Transaction(appIdentifierWithStorage, con, lM.email, - userId); + recipeUserId); } if (emailUpdate.newValue != null) { storage.deleteDevicesByEmail_Transaction(appIdentifierWithStorage, con, - emailUpdate.newValue, userId); + emailUpdate.newValue, recipeUserId); } } if (phoneNumberUpdate != null && !Objects.equals(phoneNumberUpdate.newValue, lM.phoneNumber)) { try { - storage.updateUserPhoneNumber_Transaction(appIdentifierWithStorage, con, userId, + storage.updateUserPhoneNumber_Transaction(appIdentifierWithStorage, con, recipeUserId, phoneNumberUpdate.newValue); } catch (UnknownUserIdException | DuplicatePhoneNumberException e) { throw new StorageTransactionLogicException(e); } if (lM.phoneNumber != null) { storage.deleteDevicesByPhoneNumber_Transaction(appIdentifierWithStorage, con, - lM.phoneNumber, userId); + lM.phoneNumber, recipeUserId); } if (phoneNumberUpdate.newValue != null) { storage.deleteDevicesByPhoneNumber_Transaction(appIdentifierWithStorage, con, - phoneNumberUpdate.newValue, userId); + phoneNumberUpdate.newValue, recipeUserId); } } storage.commitTransaction(con); From 06764473381f1343ce843ddbed26bdbb88d74589 Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Wed, 16 Aug 2023 12:43:26 +0530 Subject: [PATCH 088/131] fix: account linking tests (#764) * fix: tests * fix: tests * fix: tests * fix: tests * fix: pr comments * fix: pr comments --- .../GetUserByAccountInfoTest.java | 378 +++++++++++++ .../test/accountlinking/GetUserByIdTest.java | 212 ++++++++ .../api/GetUserByAccountInfoTest.java | 446 ++++++++++++++++ .../accountlinking/api/GetUserByIdTest.java | 501 ++++++++++++++++++ 4 files changed, 1537 insertions(+) create mode 100644 src/test/java/io/supertokens/test/accountlinking/GetUserByAccountInfoTest.java create mode 100644 src/test/java/io/supertokens/test/accountlinking/GetUserByIdTest.java create mode 100644 src/test/java/io/supertokens/test/accountlinking/api/GetUserByAccountInfoTest.java create mode 100644 src/test/java/io/supertokens/test/accountlinking/api/GetUserByIdTest.java diff --git a/src/test/java/io/supertokens/test/accountlinking/GetUserByAccountInfoTest.java b/src/test/java/io/supertokens/test/accountlinking/GetUserByAccountInfoTest.java new file mode 100644 index 000000000..8563b920d --- /dev/null +++ b/src/test/java/io/supertokens/test/accountlinking/GetUserByAccountInfoTest.java @@ -0,0 +1,378 @@ +/* + * Copyright (c) 2023, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.test.accountlinking; + +import io.supertokens.Main; +import io.supertokens.ProcessState; +import io.supertokens.authRecipe.AuthRecipe; +import io.supertokens.emailpassword.EmailPassword; +import io.supertokens.emailpassword.exceptions.EmailChangeNotAllowedException; +import io.supertokens.featureflag.EE_FEATURES; +import io.supertokens.featureflag.FeatureFlagTestContent; +import io.supertokens.passwordless.Passwordless; +import io.supertokens.passwordless.exceptions.*; +import io.supertokens.pluginInterface.RECIPE_ID; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; +import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; +import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; +import io.supertokens.pluginInterface.passwordless.exception.DuplicateLinkCodeHashException; +import io.supertokens.storageLayer.StorageLayer; +import io.supertokens.test.TestingProcessManager; +import io.supertokens.test.Utils; +import io.supertokens.thirdparty.ThirdParty; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; + +import java.io.IOException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; + +import static org.junit.Assert.*; + +public class GetUserByAccountInfoTest { + @Rule + public TestRule watchman = Utils.getOnFailure(); + + @AfterClass + public static void afterTesting() { + Utils.afterTesting(); + } + + @Before + public void beforeEach() { + Utils.reset(); + } + + AuthRecipeUserInfo createEmailPasswordUser(Main main, String email, String password) + throws DuplicateEmailException, StorageQueryException { + return EmailPassword.signUp(main, email, password); + } + + AuthRecipeUserInfo createThirdPartyUser(Main main, String thirdPartyId, String thirdPartyUserId, String email) + throws EmailChangeNotAllowedException, StorageQueryException { + return ThirdParty.signInUp(main, thirdPartyId, thirdPartyUserId, email).user; + } + + AuthRecipeUserInfo createPasswordlessUserWithEmail(Main main, String email) + throws DuplicateLinkCodeHashException, StorageQueryException, NoSuchAlgorithmException, IOException, + RestartFlowException, InvalidKeyException, Base64EncodingException, DeviceIdHashMismatchException, + StorageTransactionLogicException, IncorrectUserInputCodeException, ExpiredUserInputCodeException { + Passwordless.CreateCodeResponse code = Passwordless.createCode(main, email, null, + null, "123456"); + return Passwordless.consumeCode(main, code.deviceId, code.deviceIdHash, + code.userInputCode, null).user; + } + + AuthRecipeUserInfo createPasswordlessUserWithPhone(Main main, String phone) + throws DuplicateLinkCodeHashException, StorageQueryException, NoSuchAlgorithmException, IOException, + RestartFlowException, InvalidKeyException, Base64EncodingException, DeviceIdHashMismatchException, + StorageTransactionLogicException, IncorrectUserInputCodeException, ExpiredUserInputCodeException { + Passwordless.CreateCodeResponse code = Passwordless.createCode(main, null, phone, + null, "123456"); + return Passwordless.consumeCode(main, code.deviceId, code.deviceIdHash, + code.userInputCode, null).user; + } + + @Test + public void testListUsersByAccountInfoForUnlinkedAccounts() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + AuthRecipeUserInfo user1 = createEmailPasswordUser(process.getProcess(), "test1@example.com", "password1"); + AuthRecipeUserInfo user2 = createThirdPartyUser(process.getProcess(), "google", "userid1", "test2@example.com"); + AuthRecipeUserInfo user3 = createPasswordlessUserWithEmail(process.getProcess(), "test3@example.com"); + AuthRecipeUserInfo user4 = createPasswordlessUserWithPhone(process.getProcess(), "+919876543210"); + + TenantIdentifierWithStorage tenantIdentifierWithStorage = TenantIdentifier.BASE_TENANT.withStorage(StorageLayer.getBaseStorage(process.getProcess())); + + AuthRecipeUserInfo userToTest = AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, + "test1@example.com", null, null, null)[0]; + assertNotNull(userToTest.id); + assertFalse(userToTest.isPrimaryUser); + assertEquals(1, userToTest.loginMethods.length); + assertEquals("test1@example.com", userToTest.loginMethods[0].email); + assertEquals(RECIPE_ID.EMAIL_PASSWORD, userToTest.loginMethods[0].recipeId); + assertEquals(user1.id, userToTest.loginMethods[0].recipeUserId); + assertFalse(userToTest.loginMethods[0].verified); + assert(userToTest.loginMethods[0].timeJoined > 0); + + // test for result + assertEquals(user1, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, "test1@example.com", null, null, null)[0]); + assertEquals(user2, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, null, null, "google", "userid1")[0]); + assertEquals(user2, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, "test2@example.com", null, "google", "userid1")[0]); + assertEquals(user3, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, "test3@example.com", null, null, null)[0]); + assertEquals(user4, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, null, "+919876543210", null, null)[0]); + + // test for no result + assertEquals(0, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, "test1@example.com", "+919876543210", null, null).length); + assertEquals(0, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, "test2@example.com", "+919876543210", null, null).length); + assertEquals(0, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, "test3@example.com", "+919876543210", null, null).length); + assertEquals(0, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, null, "+919876543210", "google", "userid1").length); + assertEquals(0, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, "test1@gmail.com", null, "google", "userid1").length); + assertEquals(0, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, "test3@gmail.com", null, "google", "userid1").length); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testListUsersByAccountInfoForUnlinkedAccountsWithUnionOption() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + AuthRecipeUserInfo user1 = createEmailPasswordUser(process.getProcess(), "test1@example.com", "password1"); + AuthRecipeUserInfo user2 = createThirdPartyUser(process.getProcess(), "google", "userid1", "test2@example.com"); + AuthRecipeUserInfo user3 = createPasswordlessUserWithEmail(process.getProcess(), "test3@example.com"); + AuthRecipeUserInfo user4 = createPasswordlessUserWithPhone(process.getProcess(), "+919876543210"); + + TenantIdentifierWithStorage tenantIdentifierWithStorage = TenantIdentifier.BASE_TENANT.withStorage(StorageLayer.getBaseStorage(process.getProcess())); + { + AuthRecipeUserInfo[] users = AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, true, "test1@example.com", "+919876543210", null, null); + assertEquals(2, users.length); + assertTrue(Arrays.asList(users).contains(user1)); + assertTrue(Arrays.asList(users).contains(user4)); + } + { + AuthRecipeUserInfo[] users = AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, true, "test1@example.com", null, "google", "userid1"); + assertEquals(2, users.length); + assertTrue(Arrays.asList(users).contains(user1)); + assertTrue(Arrays.asList(users).contains(user2)); + } + { + AuthRecipeUserInfo[] users = AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, true, null, "+919876543210", "google", "userid1"); + assertEquals(2, users.length); + assertTrue(Arrays.asList(users).contains(user4)); + assertTrue(Arrays.asList(users).contains(user2)); + } + { + AuthRecipeUserInfo[] users = AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, true, "test1@example.com", "+919876543210", "google", "userid1"); + assertEquals(3, users.length); + assertTrue(Arrays.asList(users).contains(user1)); + assertTrue(Arrays.asList(users).contains(user2)); + assertTrue(Arrays.asList(users).contains(user4)); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testUnknownAccountInfo() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + TenantIdentifierWithStorage tenantIdentifierWithStorage = TenantIdentifier.BASE_TENANT.withStorage(StorageLayer.getBaseStorage(process.getProcess())); + assertEquals(0, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, "test1@example.com", null, null, null).length); + assertEquals(0, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, null, null, "google", "userid1").length); + assertEquals(0, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, "test3@example.com", null, null, null).length); + assertEquals(0, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, null, "+919876543210", null, null).length); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testListUserByAccountInfoWhenAccountsAreLinked1() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + AuthRecipeUserInfo user1 = EmailPassword.signUp(process.getProcess(), "test1@example.com", "password1"); + Thread.sleep(50); + AuthRecipeUserInfo user2 = ThirdParty.signInUp(process.getProcess(), "google", "userid1", "test2@example.com").user; + + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.id).user; + AuthRecipe.linkAccounts(process.getProcess(), user2.id, primaryUser.id); + + TenantIdentifierWithStorage tenantIdentifierWithStorage = TenantIdentifier.BASE_TENANT.withStorage( + StorageLayer.getBaseStorage(process.getProcess())); + + primaryUser = AuthRecipe.getUserById(process.getProcess(), user1.id); + + assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, + "test1@example.com", null, null, null)[0]); + assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, + "test2@example.com", null, null, null)[0]); + assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, + null, null, "google", "userid1")[0]); + assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, + "test1@example.com", null, "google", "userid1")[0]); + assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, + "test2@example.com", null, "google", "userid1")[0]); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testListUserByAccountInfoWhenAccountsAreLinked2() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + AuthRecipeUserInfo user1 = EmailPassword.signUp(process.getProcess(), "test1@example.com", "password1"); + Thread.sleep(50); + AuthRecipeUserInfo user2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "password2"); + + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.id).user; + AuthRecipe.linkAccounts(process.getProcess(), user2.id, primaryUser.id); + + TenantIdentifierWithStorage tenantIdentifierWithStorage = TenantIdentifier.BASE_TENANT.withStorage( + StorageLayer.getBaseStorage(process.getProcess())); + + primaryUser = AuthRecipe.getUserById(process.getProcess(), user1.id); + + assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, + "test1@example.com", null, null, null)[0]); + assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, + "test2@example.com", null, null, null)[0]); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testListUserByAccountInfoWhenAccountsAreLinked3() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + AuthRecipeUserInfo user1 = createEmailPasswordUser(process.getProcess(), "test1@example.com", "password1"); + Thread.sleep(50); + AuthRecipeUserInfo user2 = createPasswordlessUserWithEmail(process.getProcess(), "test2@example.com"); + + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.id).user; + AuthRecipe.linkAccounts(process.getProcess(), user2.id, primaryUser.id); + + TenantIdentifierWithStorage tenantIdentifierWithStorage = TenantIdentifier.BASE_TENANT.withStorage( + StorageLayer.getBaseStorage(process.getProcess())); + + primaryUser = AuthRecipe.getUserById(process.getProcess(), user1.id); + + assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, + "test1@example.com", null, null, null)[0]); + assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, + "test2@example.com", null, null, null)[0]); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testListUserByAccountInfoWhenAccountsAreLinked4() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + AuthRecipeUserInfo user1 = createEmailPasswordUser(process.getProcess(), "test1@example.com", "password1"); + Thread.sleep(50); + AuthRecipeUserInfo user2 = createPasswordlessUserWithPhone(process.getProcess(), "+919876543210"); + + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.id).user; + AuthRecipe.linkAccounts(process.getProcess(), user2.id, primaryUser.id); + + TenantIdentifierWithStorage tenantIdentifierWithStorage = TenantIdentifier.BASE_TENANT.withStorage( + StorageLayer.getBaseStorage(process.getProcess())); + + primaryUser = AuthRecipe.getUserById(process.getProcess(), user1.id); + + assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, + "test1@example.com", null, null, null)[0]); + assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, + null, "+919876543210", null, null)[0]); + assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, + "test1@example.com", "+919876543210", null, null)[0]); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testListUserByAccountInfoWhenAccountsAreLinked5() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + AuthRecipeUserInfo user1 = createEmailPasswordUser(process.getProcess(), "test1@example.com", "password1"); + Thread.sleep(50); + AuthRecipeUserInfo user2 = createThirdPartyUser(process.getProcess(), "google", "userid1", "test2@example.com"); + + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.id).user; + AuthRecipe.linkAccounts(process.getProcess(), user2.id, primaryUser.id); + + TenantIdentifierWithStorage tenantIdentifierWithStorage = TenantIdentifier.BASE_TENANT.withStorage( + StorageLayer.getBaseStorage(process.getProcess())); + + primaryUser = AuthRecipe.getUserById(process.getProcess(), user1.id); + + assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, + "test1@example.com", null, null, null)[0]); + assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, + "test2@example.com", null, null, null)[0]); + assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, + null, null, "google", "userid1")[0]); + assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, + "test1@example.com", null, "google", "userid1")[0]); + assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, + "test2@example.com", null, "google", "userid1")[0]); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } +} diff --git a/src/test/java/io/supertokens/test/accountlinking/GetUserByIdTest.java b/src/test/java/io/supertokens/test/accountlinking/GetUserByIdTest.java new file mode 100644 index 000000000..ff142cafb --- /dev/null +++ b/src/test/java/io/supertokens/test/accountlinking/GetUserByIdTest.java @@ -0,0 +1,212 @@ +/* + * Copyright (c) 2023, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.test.accountlinking; + +import io.supertokens.Main; +import io.supertokens.ProcessState; +import io.supertokens.authRecipe.AuthRecipe; +import io.supertokens.emailpassword.EmailPassword; +import io.supertokens.emailpassword.exceptions.EmailChangeNotAllowedException; +import io.supertokens.featureflag.EE_FEATURES; +import io.supertokens.featureflag.FeatureFlagTestContent; +import io.supertokens.passwordless.Passwordless; +import io.supertokens.passwordless.exceptions.*; +import io.supertokens.pluginInterface.RECIPE_ID; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; +import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; +import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; +import io.supertokens.pluginInterface.passwordless.exception.DuplicateLinkCodeHashException; +import io.supertokens.test.TestingProcessManager; +import io.supertokens.test.Utils; +import io.supertokens.thirdparty.ThirdParty; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; + +import java.io.IOException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.Collections; + +import static org.junit.Assert.*; + +public class GetUserByIdTest { + @Rule + public TestRule watchman = Utils.getOnFailure(); + + @AfterClass + public static void afterTesting() { + Utils.afterTesting(); + } + + @Before + public void beforeEach() { + Utils.reset(); + } + + AuthRecipeUserInfo createEmailPasswordUser(Main main, String email, String password) + throws DuplicateEmailException, StorageQueryException { + return EmailPassword.signUp(main, email, password); + } + + AuthRecipeUserInfo createThirdPartyUser(Main main, String thirdPartyId, String thirdPartyUserId, String email) + throws EmailChangeNotAllowedException, StorageQueryException { + return ThirdParty.signInUp(main, thirdPartyId, thirdPartyUserId, email).user; + } + + AuthRecipeUserInfo createPasswordlessUserWithEmail(Main main, String email) + throws DuplicateLinkCodeHashException, StorageQueryException, NoSuchAlgorithmException, IOException, + RestartFlowException, InvalidKeyException, Base64EncodingException, DeviceIdHashMismatchException, + StorageTransactionLogicException, IncorrectUserInputCodeException, ExpiredUserInputCodeException { + Passwordless.CreateCodeResponse code = Passwordless.createCode(main, email, null, + null, "123456"); + return Passwordless.consumeCode(main, code.deviceId, code.deviceIdHash, + code.userInputCode, null).user; + } + + AuthRecipeUserInfo createPasswordlessUserWithPhone(Main main, String phone) + throws DuplicateLinkCodeHashException, StorageQueryException, NoSuchAlgorithmException, IOException, + RestartFlowException, InvalidKeyException, Base64EncodingException, DeviceIdHashMismatchException, + StorageTransactionLogicException, IncorrectUserInputCodeException, ExpiredUserInputCodeException { + Passwordless.CreateCodeResponse code = Passwordless.createCode(main, null, phone, + null, "123456"); + return Passwordless.consumeCode(main, code.deviceId, code.deviceIdHash, + code.userInputCode, null).user; + } + + @Test + public void testAllLoginMethods() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + AuthRecipeUserInfo user1 = createEmailPasswordUser(process.getProcess(), "test@example.com", "password1"); + Thread.sleep(50); + AuthRecipeUserInfo user2 = createThirdPartyUser(process.getProcess(), "google", "userid1", "test@example.com"); + Thread.sleep(50); + AuthRecipeUserInfo user3 = createPasswordlessUserWithEmail(process.getProcess(), "test@example.com"); + Thread.sleep(50); + AuthRecipeUserInfo user4 = createPasswordlessUserWithPhone(process.getProcess(), "+919876543210"); + + assertFalse(user1.isPrimaryUser); + assertFalse(user2.isPrimaryUser); + assertFalse(user3.isPrimaryUser); + assertFalse(user4.isPrimaryUser); + + AuthRecipeUserInfo userToTest = AuthRecipe.getUserById(process.getProcess(), user1.id); + assertNotNull(userToTest.id); + assertFalse(userToTest.isPrimaryUser); + assertEquals(1, userToTest.loginMethods.length); + assertEquals("test@example.com", userToTest.loginMethods[0].email); + assertEquals(RECIPE_ID.EMAIL_PASSWORD, userToTest.loginMethods[0].recipeId); + assertEquals(user1.id, userToTest.loginMethods[0].recipeUserId); + assertFalse(userToTest.loginMethods[0].verified); + assert(userToTest.loginMethods[0].timeJoined > 0); + + assertEquals(user1, AuthRecipe.getUserById(process.getProcess(), user1.id)); + assertEquals(user2, AuthRecipe.getUserById(process.getProcess(), user2.id)); + assertEquals(user3, AuthRecipe.getUserById(process.getProcess(), user3.id)); + assertEquals(user4, AuthRecipe.getUserById(process.getProcess(), user4.id)); + + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user4.id).user; + AuthRecipe.linkAccounts(process.getProcess(), user3.id, primaryUser.id); + AuthRecipe.linkAccounts(process.getProcess(), user2.id, primaryUser.id); + AuthRecipe.linkAccounts(process.getProcess(), user1.id, primaryUser.id); + + for (String userId : new String[]{user1.id, user2.id, user3.id, user4.id}) { + AuthRecipeUserInfo result = AuthRecipe.getUserById(process.getProcess(), userId); + assertTrue(result.isPrimaryUser); + + assertEquals(4, result.loginMethods.length); + assertEquals(user1.loginMethods[0], result.loginMethods[0]); + assertEquals(user2.loginMethods[0], result.loginMethods[1]); + assertEquals(user3.loginMethods[0], result.loginMethods[2]); + assertEquals(user4.loginMethods[0], result.loginMethods[3]); + + assertEquals(user1.timeJoined, result.timeJoined); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testUnknownUserIdReturnsNull() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + assertNull(AuthRecipe.getUserById(process.getProcess(), "unknownid")); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testLoginMethodsAreSortedByTime() throws Exception { + for (int i = 0; i < 10; i++) { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + // Create users + AuthRecipeUserInfo user4 = createPasswordlessUserWithPhone(process.getProcess(), "+919876543210"); + Thread.sleep(50); + AuthRecipeUserInfo user2 = createThirdPartyUser(process.getProcess(), "google", "userid1", "test@example.com"); + Thread.sleep(50); + AuthRecipeUserInfo user3 = createPasswordlessUserWithEmail(process.getProcess(), "test@example.com"); + Thread.sleep(50); + AuthRecipeUserInfo user1 = createEmailPasswordUser(process.getProcess(), "test@example.com", "password1"); + + // Link accounts randomly + String[] userIds = new String[]{user1.id, user2.id, user3.id, user4.id}; + Collections.shuffle(Arrays.asList(userIds)); + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), userIds[0]).user; + AuthRecipe.linkAccounts(process.getProcess(), userIds[1], primaryUser.id); + AuthRecipe.linkAccounts(process.getProcess(), userIds[2], primaryUser.id); + AuthRecipe.linkAccounts(process.getProcess(), userIds[3], primaryUser.id); + + for (String userId : userIds) { + AuthRecipeUserInfo result = AuthRecipe.getUserById(process.getProcess(), userId); + assertTrue(result.isPrimaryUser); + + assertEquals(4, result.loginMethods.length); + assert(result.loginMethods[0].timeJoined <= result.loginMethods[1].timeJoined); + assert(result.loginMethods[1].timeJoined <= result.loginMethods[2].timeJoined); + assert(result.loginMethods[2].timeJoined <= result.loginMethods[3].timeJoined); + } + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + } +} diff --git a/src/test/java/io/supertokens/test/accountlinking/api/GetUserByAccountInfoTest.java b/src/test/java/io/supertokens/test/accountlinking/api/GetUserByAccountInfoTest.java new file mode 100644 index 000000000..50d2e1320 --- /dev/null +++ b/src/test/java/io/supertokens/test/accountlinking/api/GetUserByAccountInfoTest.java @@ -0,0 +1,446 @@ +/* + * Copyright (c) 2023, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.test.accountlinking.api; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import io.supertokens.Main; +import io.supertokens.ProcessState; +import io.supertokens.authRecipe.AuthRecipe; +import io.supertokens.emailpassword.EmailPassword; +import io.supertokens.emailpassword.exceptions.EmailChangeNotAllowedException; +import io.supertokens.featureflag.EE_FEATURES; +import io.supertokens.featureflag.FeatureFlagTestContent; +import io.supertokens.passwordless.Passwordless; +import io.supertokens.passwordless.exceptions.*; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; +import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; +import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; +import io.supertokens.pluginInterface.passwordless.exception.DuplicateLinkCodeHashException; +import io.supertokens.storageLayer.StorageLayer; +import io.supertokens.test.TestingProcessManager; +import io.supertokens.test.Utils; +import io.supertokens.test.httpRequest.HttpRequestForTesting; +import io.supertokens.thirdparty.ThirdParty; +import io.supertokens.useridmapping.UserIdMapping; +import io.supertokens.webserver.WebserverAPI; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; + +import java.io.IOException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.*; + +public class GetUserByAccountInfoTest { + @Rule + public TestRule watchman = Utils.getOnFailure(); + + @AfterClass + public static void afterTesting() { + Utils.afterTesting(); + } + + @Before + public void beforeEach() { + Utils.reset(); + } + + AuthRecipeUserInfo createEmailPasswordUser(Main main, String email, String password) + throws DuplicateEmailException, StorageQueryException { + return EmailPassword.signUp(main, email, password); + } + + AuthRecipeUserInfo createThirdPartyUser(Main main, String thirdPartyId, String thirdPartyUserId, String email) + throws EmailChangeNotAllowedException, StorageQueryException { + return ThirdParty.signInUp(main, thirdPartyId, thirdPartyUserId, email).user; + } + + AuthRecipeUserInfo createPasswordlessUserWithEmail(Main main, String email) + throws DuplicateLinkCodeHashException, StorageQueryException, NoSuchAlgorithmException, IOException, + RestartFlowException, InvalidKeyException, Base64EncodingException, DeviceIdHashMismatchException, + StorageTransactionLogicException, IncorrectUserInputCodeException, ExpiredUserInputCodeException { + Passwordless.CreateCodeResponse code = Passwordless.createCode(main, email, null, + null, "123456"); + return Passwordless.consumeCode(main, code.deviceId, code.deviceIdHash, + code.userInputCode, null).user; + } + + AuthRecipeUserInfo createPasswordlessUserWithPhone(Main main, String phone) + throws DuplicateLinkCodeHashException, StorageQueryException, NoSuchAlgorithmException, IOException, + RestartFlowException, InvalidKeyException, Base64EncodingException, DeviceIdHashMismatchException, + StorageTransactionLogicException, IncorrectUserInputCodeException, ExpiredUserInputCodeException { + Passwordless.CreateCodeResponse code = Passwordless.createCode(main, null, phone, + null, "123456"); + return Passwordless.consumeCode(main, code.deviceId, code.deviceIdHash, + code.userInputCode, null).user; + } + + private JsonObject getUserById(Main main, String userId) throws Exception { + Map params = new HashMap<>(); + params.put("userId", userId); + JsonObject response = HttpRequestForTesting.sendGETRequest(main, "", + "http://localhost:3567/user/id", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + return response.get("user").getAsJsonObject(); + } + + private JsonArray getUsersByAccountInfo(Main main, boolean doUnionOfAccountInfo, String email, String phoneNumber, String thirdPartyId, String thirdPartyUserId) throws Exception { + Map params = new HashMap<>(); + params.put("doUnionOfAccountInfo", String.valueOf(doUnionOfAccountInfo)); + if (email != null) { + params.put("email", email); + } + if (phoneNumber != null) { + params.put("phoneNumber", phoneNumber); + } + if (thirdPartyId != null) { + params.put("thirdPartyId", thirdPartyId); + } + if (thirdPartyUserId != null) { + params.put("thirdPartyUserId", thirdPartyUserId); + } + JsonObject response = HttpRequestForTesting.sendGETRequest(main, "", + "http://localhost:3567/users/by-accountinfo", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + return response.get("users").getAsJsonArray(); + } + + @Test + public void testListUsersByAccountInfoForUnlinkedAccounts() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + AuthRecipeUserInfo user1 = createEmailPasswordUser(process.getProcess(), "test1@example.com", "password1"); + AuthRecipeUserInfo user2 = createThirdPartyUser(process.getProcess(), "google", "userid1", "test2@example.com"); + AuthRecipeUserInfo user3 = createPasswordlessUserWithEmail(process.getProcess(), "test3@example.com"); + AuthRecipeUserInfo user4 = createPasswordlessUserWithPhone(process.getProcess(), "+919876543210"); + + JsonObject user1json = getUserById(process.getProcess(), user1.id); + JsonObject user2json = getUserById(process.getProcess(), user2.id); + JsonObject user3json = getUserById(process.getProcess(), user3.id); + JsonObject user4json = getUserById(process.getProcess(), user4.id); + + // test for result + assertEquals(user1json, getUsersByAccountInfo(process.getProcess(), false, "test1@example.com", null, null, null).get(0)); + assertEquals(user2json, getUsersByAccountInfo(process.getProcess(), false, null, null, "google", "userid1").get(0)); + assertEquals(user2json, getUsersByAccountInfo(process.getProcess(), false, "test2@example.com", null, "google", "userid1").get(0)); + assertEquals(user3json, getUsersByAccountInfo(process.getProcess(), false, "test3@example.com", null, null, null).get(0)); + assertEquals(user4json, getUsersByAccountInfo(process.getProcess(), false, null, "+919876543210", null, null).get(0)); + + // test for no result + assertEquals(0, getUsersByAccountInfo(process.getProcess(), false, "test1@example.com", "+919876543210", null, null).size()); + assertEquals(0, getUsersByAccountInfo(process.getProcess(), false, "test2@example.com", "+919876543210", null, null).size()); + assertEquals(0, getUsersByAccountInfo(process.getProcess(), false, "test3@example.com", "+919876543210", null, null).size()); + assertEquals(0, getUsersByAccountInfo(process.getProcess(), false, null, "+919876543210", "google", "userid1").size()); + assertEquals(0, getUsersByAccountInfo(process.getProcess(), false, "test1@gmail.com", null, "google", "userid1").size()); + assertEquals(0, getUsersByAccountInfo(process.getProcess(), false, "test3@gmail.com", null, "google", "userid1").size()); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testListUsersByAccountInfoForUnlinkedAccountsWithUnionOption() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + AuthRecipeUserInfo user1 = createEmailPasswordUser(process.getProcess(), "test1@example.com", "password1"); + AuthRecipeUserInfo user2 = createThirdPartyUser(process.getProcess(), "google", "userid1", "test2@example.com"); + AuthRecipeUserInfo user3 = createPasswordlessUserWithEmail(process.getProcess(), "test3@example.com"); + AuthRecipeUserInfo user4 = createPasswordlessUserWithPhone(process.getProcess(), "+919876543210"); + + JsonObject user1json = getUserById(process.getProcess(), user1.id); + JsonObject user2json = getUserById(process.getProcess(), user2.id); + JsonObject user3json = getUserById(process.getProcess(), user3.id); + JsonObject user4json = getUserById(process.getProcess(), user4.id); + + TenantIdentifierWithStorage tenantIdentifierWithStorage = TenantIdentifier.BASE_TENANT.withStorage(StorageLayer.getBaseStorage(process.getProcess())); + { + JsonArray users = getUsersByAccountInfo(process.getProcess(), true, "test1@example.com", "+919876543210", null, null); + assertEquals(2, users.size()); + users.contains(user1json); + users.contains(user4json); + } + { + JsonArray users = getUsersByAccountInfo(process.getProcess(), true, "test1@example.com", null, "google", "userid1"); + assertEquals(2, users.size()); + users.contains(user1json); + users.contains(user2json); + } + { + JsonArray users = getUsersByAccountInfo(process.getProcess(), true, null, "+919876543210", "google", "userid1"); + assertEquals(2, users.size()); + users.contains(user4json); + users.contains(user2json); + } + { + JsonArray users = getUsersByAccountInfo(process.getProcess(), true, "test1@example.com", "+919876543210", "google", "userid1"); + assertEquals(3, users.size()); + users.contains(user1json); + users.contains(user2json); + users.contains(user4json); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testUnknownAccountInfo() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + TenantIdentifierWithStorage tenantIdentifierWithStorage = TenantIdentifier.BASE_TENANT.withStorage(StorageLayer.getBaseStorage(process.getProcess())); + assertEquals(0, getUsersByAccountInfo(process.getProcess(), false, "test1@example.com", null, null, null).size()); + assertEquals(0, getUsersByAccountInfo(process.getProcess(), false, null, null, "google", "userid1").size()); + assertEquals(0, getUsersByAccountInfo(process.getProcess(), false, "test3@example.com", null, null, null).size()); + assertEquals(0, getUsersByAccountInfo(process.getProcess(), false, null, "+919876543210", null, null).size()); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testListUserByAccountInfoWhenAccountsAreLinked1() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + AuthRecipeUserInfo user1 = EmailPassword.signUp(process.getProcess(), "test1@example.com", "password1"); + Thread.sleep(50); + AuthRecipeUserInfo user2 = ThirdParty.signInUp(process.getProcess(), "google", "userid1", "test2@example.com").user; + + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.id).user; + AuthRecipe.linkAccounts(process.getProcess(), user2.id, primaryUser.id); + + TenantIdentifierWithStorage tenantIdentifierWithStorage = TenantIdentifier.BASE_TENANT.withStorage( + StorageLayer.getBaseStorage(process.getProcess())); + + JsonObject primaryUserJson = getUserById(process.getProcess(), user1.id); + + assertEquals(primaryUserJson, getUsersByAccountInfo(process.getProcess(), false, + "test1@example.com", null, null, null).get(0)); + assertEquals(primaryUserJson, getUsersByAccountInfo(process.getProcess(), false, + "test2@example.com", null, null, null).get(0)); + assertEquals(primaryUserJson, getUsersByAccountInfo(process.getProcess(), false, + null, null, "google", "userid1").get(0)); + assertEquals(primaryUserJson, getUsersByAccountInfo(process.getProcess(), false, + "test1@example.com", null, "google", "userid1").get(0)); + assertEquals(primaryUserJson, getUsersByAccountInfo(process.getProcess(), false, + "test2@example.com", null, "google", "userid1").get(0)); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testListUserByAccountInfoWhenAccountsAreLinked2() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + AuthRecipeUserInfo user1 = EmailPassword.signUp(process.getProcess(), "test1@example.com", "password1"); + Thread.sleep(50); + AuthRecipeUserInfo user2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "password2"); + + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.id).user; + AuthRecipe.linkAccounts(process.getProcess(), user2.id, primaryUser.id); + + JsonObject primaryUserJson = getUserById(process.getProcess(), user1.id); + + assertEquals(primaryUserJson, getUsersByAccountInfo(process.getProcess(), false, + "test1@example.com", null, null, null).get(0)); + assertEquals(primaryUserJson, getUsersByAccountInfo(process.getProcess(), false, + "test2@example.com", null, null, null).get(0)); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testListUserByAccountInfoWhenAccountsAreLinked3() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + AuthRecipeUserInfo user1 = createEmailPasswordUser(process.getProcess(), "test1@example.com", "password1"); + Thread.sleep(50); + AuthRecipeUserInfo user2 = createPasswordlessUserWithEmail(process.getProcess(), "test2@example.com"); + + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.id).user; + AuthRecipe.linkAccounts(process.getProcess(), user2.id, primaryUser.id); + + TenantIdentifierWithStorage tenantIdentifierWithStorage = TenantIdentifier.BASE_TENANT.withStorage( + StorageLayer.getBaseStorage(process.getProcess())); + + JsonObject primaryUserJson = getUserById(process.getProcess(), user1.id); + + assertEquals(primaryUserJson, getUsersByAccountInfo(process.getProcess(), false, + "test1@example.com", null, null, null).get(0)); + assertEquals(primaryUserJson, getUsersByAccountInfo(process.getProcess(), false, + "test2@example.com", null, null, null).get(0)); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testListUserByAccountInfoWhenAccountsAreLinked4() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + AuthRecipeUserInfo user1 = createEmailPasswordUser(process.getProcess(), "test1@example.com", "password1"); + Thread.sleep(50); + AuthRecipeUserInfo user2 = createPasswordlessUserWithPhone(process.getProcess(), "+919876543210"); + + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.id).user; + AuthRecipe.linkAccounts(process.getProcess(), user2.id, primaryUser.id); + + TenantIdentifierWithStorage tenantIdentifierWithStorage = TenantIdentifier.BASE_TENANT.withStorage( + StorageLayer.getBaseStorage(process.getProcess())); + + JsonObject primaryUserJson = getUserById(process.getProcess(), user1.id); + + assertEquals(primaryUserJson, getUsersByAccountInfo(process.getProcess(), false, + "test1@example.com", null, null, null).get(0)); + assertEquals(primaryUserJson, getUsersByAccountInfo(process.getProcess(), false, + null, "+919876543210", null, null).get(0)); + assertEquals(primaryUserJson, getUsersByAccountInfo(process.getProcess(), false, + "test1@example.com", "+919876543210", null, null).get(0)); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testListUserByAccountInfoWhenAccountsAreLinked5() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + AuthRecipeUserInfo user1 = createEmailPasswordUser(process.getProcess(), "test1@example.com", "password1"); + Thread.sleep(50); + AuthRecipeUserInfo user2 = createThirdPartyUser(process.getProcess(), "google", "userid1", "test2@example.com"); + + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.id).user; + AuthRecipe.linkAccounts(process.getProcess(), user2.id, primaryUser.id); + + TenantIdentifierWithStorage tenantIdentifierWithStorage = TenantIdentifier.BASE_TENANT.withStorage( + StorageLayer.getBaseStorage(process.getProcess())); + + JsonObject primaryUserJson = getUserById(process.getProcess(), user1.id); + + assertEquals(primaryUserJson, getUsersByAccountInfo(process.getProcess(), false, + "test1@example.com", null, null, null).get(0)); + assertEquals(primaryUserJson, getUsersByAccountInfo(process.getProcess(), false, + "test2@example.com", null, null, null).get(0)); + assertEquals(primaryUserJson, getUsersByAccountInfo(process.getProcess(), false, + null, null, "google", "userid1").get(0)); + assertEquals(primaryUserJson, getUsersByAccountInfo(process.getProcess(), false, + "test1@example.com", null, "google", "userid1").get(0)); + assertEquals(primaryUserJson, getUsersByAccountInfo(process.getProcess(), false, + "test2@example.com", null, "google", "userid1").get(0)); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testWithUserIdMapping() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + AuthRecipeUserInfo user1 = createEmailPasswordUser(process.getProcess(), "test@example.com", "password1"); + Thread.sleep(50); + AuthRecipeUserInfo user2 = createThirdPartyUser(process.getProcess(), "google", "userid1", "test@example.com"); + Thread.sleep(50); + AuthRecipeUserInfo user3 = createPasswordlessUserWithEmail(process.getProcess(), "test@example.com"); + Thread.sleep(50); + AuthRecipeUserInfo user4 = createPasswordlessUserWithPhone(process.getProcess(), "+919876543210"); + + UserIdMapping.createUserIdMapping(process.getProcess(), user1.id, "ext1", "", false); + UserIdMapping.createUserIdMapping(process.getProcess(), user2.id, "ext2", "", false); + UserIdMapping.createUserIdMapping(process.getProcess(), user3.id, "ext3", "", false); + + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.id).user; + AuthRecipe.linkAccounts(process.getProcess(), user2.id, primaryUser.id); + AuthRecipe.linkAccounts(process.getProcess(), user3.id, primaryUser.id); + AuthRecipe.linkAccounts(process.getProcess(), user4.id, primaryUser.id); + + JsonObject primaryUserInfo = getUsersByAccountInfo(process.getProcess(), false, + "test@example.com", null, null, null).get(0).getAsJsonObject(); + assertEquals("ext1", primaryUserInfo.get("loginMethods").getAsJsonArray().get(0).getAsJsonObject().get("recipeUserId").getAsString()); + assertNotEquals("ext2", primaryUserInfo.get("loginMethods").getAsJsonArray().get(1).getAsJsonObject().get("recipeUserId").getAsString()); // TODO should be equal once userIdMapping is fixed + assertNotEquals("ext3", primaryUserInfo.get("loginMethods").getAsJsonArray().get(2).getAsJsonObject().get("recipeUserId").getAsString()); // TODO should be equal once userIdMapping is fixed + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } +} diff --git a/src/test/java/io/supertokens/test/accountlinking/api/GetUserByIdTest.java b/src/test/java/io/supertokens/test/accountlinking/api/GetUserByIdTest.java new file mode 100644 index 000000000..f63106c5d --- /dev/null +++ b/src/test/java/io/supertokens/test/accountlinking/api/GetUserByIdTest.java @@ -0,0 +1,501 @@ +/* + * Copyright (c) 2023, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.test.accountlinking.api; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import io.supertokens.Main; +import io.supertokens.ProcessState; +import io.supertokens.authRecipe.AuthRecipe; +import io.supertokens.emailpassword.EmailPassword; +import io.supertokens.emailpassword.exceptions.EmailChangeNotAllowedException; +import io.supertokens.featureflag.EE_FEATURES; +import io.supertokens.featureflag.FeatureFlagTestContent; +import io.supertokens.passwordless.Passwordless; +import io.supertokens.passwordless.exceptions.*; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; +import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; +import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; +import io.supertokens.pluginInterface.passwordless.exception.DuplicateLinkCodeHashException; +import io.supertokens.test.TestingProcessManager; +import io.supertokens.test.Utils; +import io.supertokens.test.httpRequest.HttpRequestForTesting; +import io.supertokens.thirdparty.ThirdParty; +import io.supertokens.useridmapping.UserIdMapping; +import io.supertokens.webserver.WebserverAPI; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; + +import java.io.IOException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import static org.junit.Assert.*; + +public class GetUserByIdTest { + @Rule + public TestRule watchman = Utils.getOnFailure(); + + @AfterClass + public static void afterTesting() { + Utils.afterTesting(); + } + + @Before + public void beforeEach() { + Utils.reset(); + } + + AuthRecipeUserInfo createEmailPasswordUser(Main main, String email, String password) + throws DuplicateEmailException, StorageQueryException { + return EmailPassword.signUp(main, email, password); + } + + AuthRecipeUserInfo createThirdPartyUser(Main main, String thirdPartyId, String thirdPartyUserId, String email) + throws EmailChangeNotAllowedException, StorageQueryException { + return ThirdParty.signInUp(main, thirdPartyId, thirdPartyUserId, email).user; + } + + AuthRecipeUserInfo createPasswordlessUserWithEmail(Main main, String email) + throws DuplicateLinkCodeHashException, StorageQueryException, NoSuchAlgorithmException, IOException, + RestartFlowException, InvalidKeyException, Base64EncodingException, DeviceIdHashMismatchException, + StorageTransactionLogicException, IncorrectUserInputCodeException, ExpiredUserInputCodeException { + Passwordless.CreateCodeResponse code = Passwordless.createCode(main, email, null, + null, "123456"); + return Passwordless.consumeCode(main, code.deviceId, code.deviceIdHash, + code.userInputCode, null).user; + } + + AuthRecipeUserInfo createPasswordlessUserWithPhone(Main main, String phone) + throws DuplicateLinkCodeHashException, StorageQueryException, NoSuchAlgorithmException, IOException, + RestartFlowException, InvalidKeyException, Base64EncodingException, DeviceIdHashMismatchException, + StorageTransactionLogicException, IncorrectUserInputCodeException, ExpiredUserInputCodeException { + Passwordless.CreateCodeResponse code = Passwordless.createCode(main, null, phone, + null, "123456"); + return Passwordless.consumeCode(main, code.deviceId, code.deviceIdHash, + code.userInputCode, null).user; + } + + @Test + public void testJsonStructure() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + AuthRecipeUserInfo user1 = createEmailPasswordUser(process.getProcess(), "test@example.com", "password1"); + Thread.sleep(50); + AuthRecipeUserInfo user2 = createThirdPartyUser(process.getProcess(), "google", "userid1", "test@example.com"); + Thread.sleep(50); + AuthRecipeUserInfo user3 = createPasswordlessUserWithEmail(process.getProcess(), "test@example.com"); + Thread.sleep(50); + AuthRecipeUserInfo user4 = createPasswordlessUserWithPhone(process.getProcess(), "+919876543210"); + + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.id).user; + AuthRecipe.linkAccounts(process.getProcess(), user2.id, primaryUser.id); + AuthRecipe.linkAccounts(process.getProcess(), user3.id, primaryUser.id); + AuthRecipe.linkAccounts(process.getProcess(), user4.id, primaryUser.id); + + for (String userId : new String[]{user1.id, user2.id, user3.id, user4.id}) { + Map params = new HashMap<>(); + params.put("userId", userId); + JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/user/id", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + JsonObject user = response.get("user").getAsJsonObject(); + assertEquals(user.entrySet().size(), 8); + assertTrue(user.get("isPrimaryUser").getAsBoolean()); + assertEquals(1, user.get("tenantIds").getAsJsonArray().size()); + assertEquals("public", user.get("tenantIds").getAsJsonArray().get(0).getAsString()); + assertEquals(1, user.get("thirdParty").getAsJsonArray().size()); + assertEquals(4, user.get("loginMethods").getAsJsonArray().size()); + for (JsonElement loginMethodElem : user.get("loginMethods").getAsJsonArray()) { + JsonObject loginMethod = loginMethodElem.getAsJsonObject(); + if (loginMethod.get("recipeId").getAsString().equals("thirdparty")) { + assertEquals(7, loginMethod.entrySet().size()); + assertTrue(loginMethod.has("thirdParty")); + assertEquals(2, loginMethod.get("thirdParty").getAsJsonObject().entrySet().size()); + } else { + assertEquals(6, loginMethod.entrySet().size()); + } + if (loginMethod.has("email")) { + assertEquals("test@example.com", loginMethod.get("email").getAsString()); + } else if (loginMethod.has("phoneNumber")) { + assertEquals("+919876543210", loginMethod.get("phoneNumber").getAsString()); + assertTrue(loginMethod.get("verified").getAsBoolean()); + } else { + fail(); + } + } + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testThatEmailIsAUnionOfLinkedAccounts1() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + AuthRecipeUserInfo user1 = createEmailPasswordUser(process.getProcess(), "test1@example.com", "password1"); + Thread.sleep(50); + AuthRecipeUserInfo user2 = createThirdPartyUser(process.getProcess(), "google", "userid1", "test2@example.com"); + Thread.sleep(50); + AuthRecipeUserInfo user3 = createPasswordlessUserWithEmail(process.getProcess(), "test3@example.com"); + + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.id).user; + AuthRecipe.linkAccounts(process.getProcess(), user2.id, primaryUser.id); + AuthRecipe.linkAccounts(process.getProcess(), user3.id, primaryUser.id); + + for (String userId : new String[]{user1.id, user2.id, user3.id}) { + Map params = new HashMap<>(); + params.put("userId", userId); + JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/user/id", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + JsonObject user = response.get("user").getAsJsonObject(); + + Set emails = new HashSet<>(); + for (JsonElement emailElem : user.get("emails").getAsJsonArray()) { + emails.add(emailElem.getAsString()); + } + assertEquals(3, emails.size()); + assertTrue(emails.contains("test1@example.com")); + assertTrue(emails.contains("test2@example.com")); + assertTrue(emails.contains("test3@example.com")); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testThatEmailIsAUnionOfLinkedAccounts2() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + AuthRecipeUserInfo user1 = createEmailPasswordUser(process.getProcess(), "test1@example.com", "password1"); + Thread.sleep(50); + AuthRecipeUserInfo user2 = createThirdPartyUser(process.getProcess(), "google", "userid1", "test2@example.com"); + + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.id).user; + AuthRecipe.linkAccounts(process.getProcess(), user2.id, primaryUser.id); + + for (String userId : new String[]{user1.id, user2.id}) { + Map params = new HashMap<>(); + params.put("userId", userId); + JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/user/id", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + JsonObject user = response.get("user").getAsJsonObject(); + + Set emails = new HashSet<>(); + for (JsonElement emailElem : user.get("emails").getAsJsonArray()) { + emails.add(emailElem.getAsString()); + } + assertEquals(2, emails.size()); + assertTrue(emails.contains("test1@example.com")); + assertTrue(emails.contains("test2@example.com")); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testThatEmailIsAUnionOfLinkedAccounts3() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + AuthRecipeUserInfo user1 = createEmailPasswordUser(process.getProcess(), "test1@example.com", "password1"); + Thread.sleep(50); + AuthRecipeUserInfo user2 = createPasswordlessUserWithEmail(process.getProcess(), "test2@example.com"); + + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.id).user; + AuthRecipe.linkAccounts(process.getProcess(), user2.id, primaryUser.id); + + for (String userId : new String[]{user1.id, user2.id}) { + Map params = new HashMap<>(); + params.put("userId", userId); + JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/user/id", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + JsonObject user = response.get("user").getAsJsonObject(); + + Set emails = new HashSet<>(); + for (JsonElement emailElem : user.get("emails").getAsJsonArray()) { + emails.add(emailElem.getAsString()); + } + assertEquals(2, emails.size()); + assertTrue(emails.contains("test1@example.com")); + assertTrue(emails.contains("test2@example.com")); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testThatEmailIsAUnionOfLinkedAccounts4() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + AuthRecipeUserInfo user1 = createThirdPartyUser(process.getProcess(), "google", "googleid", "test1@example.com"); + Thread.sleep(50); + AuthRecipeUserInfo user2 = createPasswordlessUserWithEmail(process.getProcess(), "test2@example.com"); + + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.id).user; + AuthRecipe.linkAccounts(process.getProcess(), user2.id, primaryUser.id); + + for (String userId : new String[]{user1.id, user2.id}) { + Map params = new HashMap<>(); + params.put("userId", userId); + JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/user/id", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + JsonObject user = response.get("user").getAsJsonObject(); + + Set emails = new HashSet<>(); + for (JsonElement emailElem : user.get("emails").getAsJsonArray()) { + emails.add(emailElem.getAsString()); + } + assertEquals(2, emails.size()); + assertTrue(emails.contains("test1@example.com")); + assertTrue(emails.contains("test2@example.com")); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testThatPhoneNumberIsUnionOfLinkedAccounts1() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + AuthRecipeUserInfo user1 = createPasswordlessUserWithPhone(process.getProcess(), "+919876543210"); + Thread.sleep(50); + AuthRecipeUserInfo user2 = createPasswordlessUserWithPhone(process.getProcess(), "+911234567890"); + + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.id).user; + AuthRecipe.linkAccounts(process.getProcess(), user2.id, primaryUser.id); + + for (String userId : new String[]{user1.id, user2.id}) { + Map params = new HashMap<>(); + params.put("userId", userId); + JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/user/id", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + JsonObject user = response.get("user").getAsJsonObject(); + + Set phoneNumbers = new HashSet<>(); + for (JsonElement phoneNumberElem : user.get("phoneNumbers").getAsJsonArray()) { + phoneNumbers.add(phoneNumberElem.getAsString()); + } + assertEquals(2, phoneNumbers.size()); + assertTrue(phoneNumbers.contains("+919876543210")); + assertTrue(phoneNumbers.contains("+911234567890")); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testThatPhoneNumberIsUnionOfLinkedAccounts2() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + AuthRecipeUserInfo user1 = createPasswordlessUserWithPhone(process.getProcess(), "+919876543210"); + Thread.sleep(50); + AuthRecipeUserInfo user2 = createPasswordlessUserWithPhone(process.getProcess(), "+911234567890"); + Thread.sleep(50); + AuthRecipeUserInfo user3 = createThirdPartyUser(process.getProcess(), "google", "googleid", "test@example.com"); + + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.id).user; + AuthRecipe.linkAccounts(process.getProcess(), user2.id, primaryUser.id); + AuthRecipe.linkAccounts(process.getProcess(), user3.id, primaryUser.id); + + for (String userId : new String[]{user1.id, user2.id}) { + Map params = new HashMap<>(); + params.put("userId", userId); + JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/user/id", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + JsonObject user = response.get("user").getAsJsonObject(); + + Set phoneNumbers = new HashSet<>(); + for (JsonElement phoneNumberElem : user.get("phoneNumbers").getAsJsonArray()) { + phoneNumbers.add(phoneNumberElem.getAsString()); + } + assertEquals(2, phoneNumbers.size()); + assertTrue(phoneNumbers.contains("+919876543210")); + assertTrue(phoneNumbers.contains("+911234567890")); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testThatPhoneNumberIsUnionOfLinkedAccounts3() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + AuthRecipeUserInfo user1 = createPasswordlessUserWithPhone(process.getProcess(), "+919876543210"); + Thread.sleep(50); + AuthRecipeUserInfo user2 = createPasswordlessUserWithPhone(process.getProcess(), "+911234567890"); + Thread.sleep(50); + AuthRecipeUserInfo user3 = createEmailPasswordUser(process.getProcess(), "test@example.com", "password"); + + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.id).user; + AuthRecipe.linkAccounts(process.getProcess(), user2.id, primaryUser.id); + AuthRecipe.linkAccounts(process.getProcess(), user3.id, primaryUser.id); + + for (String userId : new String[]{user1.id, user2.id}) { + Map params = new HashMap<>(); + params.put("userId", userId); + JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/user/id", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + JsonObject user = response.get("user").getAsJsonObject(); + + Set phoneNumbers = new HashSet<>(); + for (JsonElement phoneNumberElem : user.get("phoneNumbers").getAsJsonArray()) { + phoneNumbers.add(phoneNumberElem.getAsString()); + } + assertEquals(2, phoneNumbers.size()); + assertTrue(phoneNumbers.contains("+919876543210")); + assertTrue(phoneNumbers.contains("+911234567890")); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testWithUserIdMapping() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + AuthRecipeUserInfo user1 = createEmailPasswordUser(process.getProcess(), "test@example.com", "password1"); + Thread.sleep(50); + AuthRecipeUserInfo user2 = createThirdPartyUser(process.getProcess(), "google", "userid1", "test@example.com"); + Thread.sleep(50); + AuthRecipeUserInfo user3 = createPasswordlessUserWithEmail(process.getProcess(), "test@example.com"); + Thread.sleep(50); + AuthRecipeUserInfo user4 = createPasswordlessUserWithPhone(process.getProcess(), "+919876543210"); + + UserIdMapping.createUserIdMapping(process.getProcess(), user1.id, "ext1", "", false); + UserIdMapping.createUserIdMapping(process.getProcess(), user2.id, "ext2", "", false); + UserIdMapping.createUserIdMapping(process.getProcess(), user3.id, "ext3", "", false); + + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.id).user; + AuthRecipe.linkAccounts(process.getProcess(), user2.id, primaryUser.id); + AuthRecipe.linkAccounts(process.getProcess(), user3.id, primaryUser.id); + AuthRecipe.linkAccounts(process.getProcess(), user4.id, primaryUser.id); + + for (String userId : new String[]{user1.id, user2.id, user3.id, user4.id, "ext1", "ext2", "ext3"}) { + Map params = new HashMap<>(); + params.put("userId", userId); + JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/user/id", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + JsonObject user = response.get("user").getAsJsonObject(); + assertEquals("ext1", user.get("id").getAsString()); + assertEquals("ext1", user.get("loginMethods").getAsJsonArray().get(0).getAsJsonObject().get("recipeUserId").getAsString()); + assertNotEquals("ext2", user.get("loginMethods").getAsJsonArray().get(1).getAsJsonObject().get("recipeUserId").getAsString()); // TODO should be equal once userIdMapping is fixed + assertNotEquals("ext3", user.get("loginMethods").getAsJsonArray().get(2).getAsJsonObject().get("recipeUserId").getAsString()); // TODO should be equal once userIdMapping is fixed + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testUnknownUser() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + Map params = new HashMap<>(); + params.put("userId", "unknownid"); + JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/user/id", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assertEquals("UNKNOWN_USER_ID_ERROR", response.get("status").getAsString()); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } +} From 8adea4e3c56b8dd825287856560096638616ecf0 Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Wed, 16 Aug 2023 16:31:33 +0530 Subject: [PATCH 089/131] fix: user pagination changes (#766) * fix: user pagination changes * fix: user pagination changes --- .../io/supertokens/authRecipe/AuthRecipe.java | 4 +- .../authRecipe/UserPaginationContainer.java | 17 +- .../java/io/supertokens/inmemorydb/Start.java | 2 +- .../webserver/api/core/UsersAPI.java | 28 +-- .../io/supertokens/test/AuthRecipeTest.java | 196 +++++++++--------- .../GetUsersWithSearchTagsTest.java | 25 ++- 6 files changed, 130 insertions(+), 142 deletions(-) diff --git a/src/main/java/io/supertokens/authRecipe/AuthRecipe.java b/src/main/java/io/supertokens/authRecipe/AuthRecipe.java index 8909e944d..4ad5c4abc 100644 --- a/src/main/java/io/supertokens/authRecipe/AuthRecipe.java +++ b/src/main/java/io/supertokens/authRecipe/AuthRecipe.java @@ -92,7 +92,7 @@ public static boolean unlinkAccounts(Main main, AppIdentifierWithStorage appIden if (primaryUser.id.equals(recipeUserId)) { // we are trying to unlink the user ID which is the same as the primary one. if (primaryUser.loginMethods.length == 1) { - storage.unlinkAccounts_Transaction(appIdentifierWithStorage, con, recipeUserId); + storage.unlinkAccounts_Transaction(appIdentifierWithStorage, con, primaryUser.id, recipeUserId); Session.revokeAllSessionsForUser(main, appIdentifierWithStorage, mappingResult == null ? recipeUserId : mappingResult.externalUserId, false); @@ -109,7 +109,7 @@ public static boolean unlinkAccounts(Main main, AppIdentifierWithStorage appIden return true; } } else { - storage.unlinkAccounts_Transaction(appIdentifierWithStorage, con, recipeUserId); + storage.unlinkAccounts_Transaction(appIdentifierWithStorage, con, primaryUser.id, recipeUserId); Session.revokeAllSessionsForUser(main, appIdentifierWithStorage, mappingResult == null ? recipeUserId : mappingResult.externalUserId, false); return false; diff --git a/src/main/java/io/supertokens/authRecipe/UserPaginationContainer.java b/src/main/java/io/supertokens/authRecipe/UserPaginationContainer.java index f1eb37560..d4ef616aa 100644 --- a/src/main/java/io/supertokens/authRecipe/UserPaginationContainer.java +++ b/src/main/java/io/supertokens/authRecipe/UserPaginationContainer.java @@ -22,24 +22,11 @@ import javax.annotation.Nullable; public class UserPaginationContainer { - public final UsersContainer[] users; + public final AuthRecipeUserInfo[] users; public final String nextPaginationToken; public UserPaginationContainer(@Nonnull AuthRecipeUserInfo[] users, @Nullable String nextPaginationToken) { - this.users = new UsersContainer[users.length]; - for (int i = 0; i < users.length; i++) { - this.users[i] = new UsersContainer(users[i]); - } + this.users = users; this.nextPaginationToken = nextPaginationToken; } - - public static class UsersContainer { - public final AuthRecipeUserInfo user; - public final String recipeId; - - public UsersContainer(AuthRecipeUserInfo user) { - this.user = user; - this.recipeId = user.getRecipeId().toString(); - } - } } diff --git a/src/main/java/io/supertokens/inmemorydb/Start.java b/src/main/java/io/supertokens/inmemorydb/Start.java index 8fd4f3db8..3a8f95536 100644 --- a/src/main/java/io/supertokens/inmemorydb/Start.java +++ b/src/main/java/io/supertokens/inmemorydb/Start.java @@ -2819,7 +2819,7 @@ public void linkAccounts_Transaction(AppIdentifier appIdentifier, TransactionCon } @Override - public void unlinkAccounts_Transaction(AppIdentifier appIdentifier, TransactionConnection con, String recipeUserId) + public void unlinkAccounts_Transaction(AppIdentifier appIdentifier, TransactionConnection con, String primaryUserId, String recipeUserId) throws StorageQueryException { // TODO:.. } diff --git a/src/main/java/io/supertokens/webserver/api/core/UsersAPI.java b/src/main/java/io/supertokens/webserver/api/core/UsersAPI.java index 337730ca2..79c7247df 100644 --- a/src/main/java/io/supertokens/webserver/api/core/UsersAPI.java +++ b/src/main/java/io/supertokens/webserver/api/core/UsersAPI.java @@ -25,6 +25,7 @@ import io.supertokens.authRecipe.UserPaginationToken; import io.supertokens.output.Logging; import io.supertokens.pluginInterface.RECIPE_ID; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.dashboard.DashboardSearchTags; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; @@ -169,18 +170,17 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO ArrayList userIds = new ArrayList<>(); for (int i = 0; i < users.users.length; i++) { - userIds.add(users.users[i].user.id); + userIds.add(users.users[i].id); } HashMap userIdMapping = UserIdMapping.getUserIdMappingForSuperTokensUserIds( tenantIdentifierWithStorage, userIds); - if (!userIdMapping.isEmpty()) { - for (int i = 0; i < users.users.length; i++) { - String externalId = userIdMapping.get(userIds.get(i)); - if (externalId != null) { - users.users[i].user.setExternalUserId(externalId); - } else { - users.users[i].user.setExternalUserId(null); - } + + for (int i = 0; i < users.users.length; i++) { + String externalId = userIdMapping.get(userIds.get(i)); + if (externalId != null) { + users.users[i].setExternalUserId(externalId); + } else { + users.users[i].setExternalUserId(null); } } @@ -188,12 +188,14 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO result.addProperty("status", "OK"); JsonArray usersJson = new JsonArray(); - for (UserPaginationContainer.UsersContainer user : users.users) { + for (AuthRecipeUserInfo user : users.users) { JsonObject jsonObj = new JsonObject(); - jsonObj.addProperty("recipeId", user.recipeId); + if (getVersionFromRequest(req).lesserThan(SemVer.v4_0)) { + jsonObj.addProperty("recipeId", user.loginMethods[0].recipeId.toString()); + } JsonObject userJson = - getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v4_0) ? user.user.toJson() : - user.user.toJsonWithoutAccountLinking(); + getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v4_0) ? user.toJson() : + user.toJsonWithoutAccountLinking(); jsonObj.add("user", userJson); usersJson.add(jsonObj); } diff --git a/src/test/java/io/supertokens/test/AuthRecipeTest.java b/src/test/java/io/supertokens/test/AuthRecipeTest.java index de4b1e717..ca6ca95f5 100644 --- a/src/test/java/io/supertokens/test/AuthRecipeTest.java +++ b/src/test/java/io/supertokens/test/AuthRecipeTest.java @@ -209,14 +209,14 @@ public void paginationTest() throws Exception { new RECIPE_ID[]{RECIPE_ID.EMAIL_PASSWORD}, null); assert (users.nextPaginationToken == null); assert (users.users.length == 4); - assert (users.users[0].recipeId.equals("emailpassword")); - assert (users.users[0].user.equals(user1)); - assert (users.users[1].recipeId.equals("emailpassword")); - assert (users.users[1].user.equals(user2)); - assert (users.users[2].recipeId.equals("emailpassword")); - assert (users.users[2].user.equals(user3)); - assert (users.users[3].recipeId.equals("emailpassword")); - assert (users.users[3].user.equals(user4)); + assert (users.users[0].loginMethods[0].recipeId.toString().equals("emailpassword")); + assert (users.users[0].equals(user1)); + assert (users.users[1].loginMethods[0].recipeId.toString().equals("emailpassword")); + assert (users.users[1].equals(user2)); + assert (users.users[2].loginMethods[0].recipeId.toString().equals("emailpassword")); + assert (users.users[2].equals(user3)); + assert (users.users[3].loginMethods[0].recipeId.toString().equals("emailpassword")); + assert (users.users[3].equals(user4)); } @@ -225,14 +225,14 @@ public void paginationTest() throws Exception { new RECIPE_ID[]{RECIPE_ID.EMAIL_PASSWORD, RECIPE_ID.THIRD_PARTY}, null); assert (users.nextPaginationToken == null); assert (users.users.length == 4); - assert (users.users[0].recipeId.equals("emailpassword")); - assert (users.users[0].user.equals(user1)); - assert (users.users[1].recipeId.equals("emailpassword")); - assert (users.users[1].user.equals(user2)); - assert (users.users[2].recipeId.equals("emailpassword")); - assert (users.users[2].user.equals(user3)); - assert (users.users[3].recipeId.equals("emailpassword")); - assert (users.users[3].user.equals(user4)); + assert (users.users[0].loginMethods[0].recipeId.toString().equals("emailpassword")); + assert (users.users[0].equals(user1)); + assert (users.users[1].loginMethods[0].recipeId.toString().equals("emailpassword")); + assert (users.users[1].equals(user2)); + assert (users.users[2].loginMethods[0].recipeId.toString().equals("emailpassword")); + assert (users.users[2].equals(user3)); + assert (users.users[3].loginMethods[0].recipeId.toString().equals("emailpassword")); + assert (users.users[3].equals(user4)); } { @@ -240,14 +240,14 @@ public void paginationTest() throws Exception { new RECIPE_ID[]{RECIPE_ID.EMAIL_PASSWORD}, null); assert (users.nextPaginationToken == null); assert (users.users.length == 4); - assert (users.users[3].recipeId.equals("emailpassword")); - assert (users.users[3].user.equals(user1)); - assert (users.users[2].recipeId.equals("emailpassword")); - assert (users.users[2].user.equals(user2)); - assert (users.users[1].recipeId.equals("emailpassword")); - assert (users.users[1].user.equals(user3)); - assert (users.users[0].recipeId.equals("emailpassword")); - assert (users.users[0].user.equals(user4)); + assert (users.users[3].loginMethods[0].recipeId.toString().equals("emailpassword")); + assert (users.users[3].equals(user1)); + assert (users.users[2].loginMethods[0].recipeId.toString().equals("emailpassword")); + assert (users.users[2].equals(user2)); + assert (users.users[1].loginMethods[0].recipeId.toString().equals("emailpassword")); + assert (users.users[1].equals(user3)); + assert (users.users[0].loginMethods[0].recipeId.toString().equals("emailpassword")); + assert (users.users[0].equals(user4)); } @@ -256,14 +256,14 @@ public void paginationTest() throws Exception { new RECIPE_ID[]{RECIPE_ID.THIRD_PARTY, RECIPE_ID.EMAIL_PASSWORD}, null); assert (users.nextPaginationToken == null); assert (users.users.length == 4); - assert (users.users[3].recipeId.equals("emailpassword")); - assert (users.users[3].user.equals(user1)); - assert (users.users[2].recipeId.equals("emailpassword")); - assert (users.users[2].user.equals(user2)); - assert (users.users[1].recipeId.equals("emailpassword")); - assert (users.users[1].user.equals(user3)); - assert (users.users[0].recipeId.equals("emailpassword")); - assert (users.users[0].user.equals(user4)); + assert (users.users[3].loginMethods[0].recipeId.toString().equals("emailpassword")); + assert (users.users[3].equals(user1)); + assert (users.users[2].loginMethods[0].recipeId.toString().equals("emailpassword")); + assert (users.users[2].equals(user2)); + assert (users.users[1].loginMethods[0].recipeId.toString().equals("emailpassword")); + assert (users.users[1].equals(user3)); + assert (users.users[0].loginMethods[0].recipeId.toString().equals("emailpassword")); + assert (users.users[0].equals(user4)); } { @@ -278,10 +278,10 @@ public void paginationTest() throws Exception { new RECIPE_ID[]{RECIPE_ID.THIRD_PARTY, RECIPE_ID.EMAIL_PASSWORD}, null); assert (users.nextPaginationToken != null); assert (users.users.length == 2); - assert (users.users[1].recipeId.equals("emailpassword")); - assert (users.users[1].user.equals(user3)); - assert (users.users[0].recipeId.equals("emailpassword")); - assert (users.users[0].user.equals(user4)); + assert (users.users[1].loginMethods[0].recipeId.toString().equals("emailpassword")); + assert (users.users[1].equals(user3)); + assert (users.users[0].loginMethods[0].recipeId.toString().equals("emailpassword")); + assert (users.users[0].equals(user4)); } { @@ -289,12 +289,12 @@ public void paginationTest() throws Exception { new RECIPE_ID[]{RECIPE_ID.THIRD_PARTY, RECIPE_ID.EMAIL_PASSWORD}, null); assert (users.nextPaginationToken != null); assert (users.users.length == 3); - assert (users.users[0].recipeId.equals("emailpassword")); - assert (users.users[0].user.equals(user1)); - assert (users.users[1].recipeId.equals("emailpassword")); - assert (users.users[1].user.equals(user2)); - assert (users.users[2].recipeId.equals("emailpassword")); - assert (users.users[2].user.equals(user3)); + assert (users.users[0].loginMethods[0].recipeId.toString().equals("emailpassword")); + assert (users.users[0].equals(user1)); + assert (users.users[1].loginMethods[0].recipeId.toString().equals("emailpassword")); + assert (users.users[1].equals(user2)); + assert (users.users[2].loginMethods[0].recipeId.toString().equals("emailpassword")); + assert (users.users[2].equals(user3)); } /////////////////////////////////////////////////////////////////////////////////// @@ -311,14 +311,14 @@ public void paginationTest() throws Exception { new RECIPE_ID[]{RECIPE_ID.EMAIL_PASSWORD}, null); assert (users.nextPaginationToken == null); assert (users.users.length == 4); - assert (users.users[0].recipeId.equals("emailpassword")); - assert (users.users[0].user.equals(user1)); - assert (users.users[1].recipeId.equals("emailpassword")); - assert (users.users[1].user.equals(user2)); - assert (users.users[2].recipeId.equals("emailpassword")); - assert (users.users[2].user.equals(user3)); - assert (users.users[3].recipeId.equals("emailpassword")); - assert (users.users[3].user.equals(user4)); + assert (users.users[0].loginMethods[0].recipeId.toString().equals("emailpassword")); + assert (users.users[0].equals(user1)); + assert (users.users[1].loginMethods[0].recipeId.toString().equals("emailpassword")); + assert (users.users[1].equals(user2)); + assert (users.users[2].loginMethods[0].recipeId.toString().equals("emailpassword")); + assert (users.users[2].equals(user3)); + assert (users.users[3].loginMethods[0].recipeId.toString().equals("emailpassword")); + assert (users.users[3].equals(user4)); } @@ -327,8 +327,8 @@ public void paginationTest() throws Exception { new RECIPE_ID[]{RECIPE_ID.THIRD_PARTY}, null); assert (users.nextPaginationToken == null); assert (users.users.length == 1); - assert (users.users[0].recipeId.equals("thirdparty")); - assert (users.users[0].user.equals(user5)); + assert (users.users[0].loginMethods[0].recipeId.toString().equals("thirdparty")); + assert (users.users[0].equals(user5)); } @@ -337,16 +337,16 @@ public void paginationTest() throws Exception { new RECIPE_ID[]{RECIPE_ID.EMAIL_PASSWORD, RECIPE_ID.THIRD_PARTY}, null); assert (users.nextPaginationToken == null); assert (users.users.length == 5); - assert (users.users[0].recipeId.equals("emailpassword")); - assert (users.users[0].user.equals(user1)); - assert (users.users[1].recipeId.equals("emailpassword")); - assert (users.users[1].user.equals(user2)); - assert (users.users[2].recipeId.equals("emailpassword")); - assert (users.users[2].user.equals(user3)); - assert (users.users[3].recipeId.equals("emailpassword")); - assert (users.users[3].user.equals(user4)); - assert (users.users[4].recipeId.equals("thirdparty")); - assert (users.users[4].user.equals(user5)); + assert (users.users[0].loginMethods[0].recipeId.toString().equals("emailpassword")); + assert (users.users[0].equals(user1)); + assert (users.users[1].loginMethods[0].recipeId.toString().equals("emailpassword")); + assert (users.users[1].equals(user2)); + assert (users.users[2].loginMethods[0].recipeId.toString().equals("emailpassword")); + assert (users.users[2].equals(user3)); + assert (users.users[3].loginMethods[0].recipeId.toString().equals("emailpassword")); + assert (users.users[3].equals(user4)); + assert (users.users[4].loginMethods[0].recipeId.toString().equals("thirdparty")); + assert (users.users[4].equals(user5)); } { @@ -354,14 +354,14 @@ public void paginationTest() throws Exception { new RECIPE_ID[]{RECIPE_ID.EMAIL_PASSWORD}, null); assert (users.nextPaginationToken == null); assert (users.users.length == 4); - assert (users.users[3].recipeId.equals("emailpassword")); - assert (users.users[3].user.equals(user1)); - assert (users.users[2].recipeId.equals("emailpassword")); - assert (users.users[2].user.equals(user2)); - assert (users.users[1].recipeId.equals("emailpassword")); - assert (users.users[1].user.equals(user3)); - assert (users.users[0].recipeId.equals("emailpassword")); - assert (users.users[0].user.equals(user4)); + assert (users.users[3].loginMethods[0].recipeId.toString().equals("emailpassword")); + assert (users.users[3].equals(user1)); + assert (users.users[2].loginMethods[0].recipeId.toString().equals("emailpassword")); + assert (users.users[2].equals(user2)); + assert (users.users[1].loginMethods[0].recipeId.toString().equals("emailpassword")); + assert (users.users[1].equals(user3)); + assert (users.users[0].loginMethods[0].recipeId.toString().equals("emailpassword")); + assert (users.users[0].equals(user4)); } @@ -370,8 +370,8 @@ public void paginationTest() throws Exception { new RECIPE_ID[]{RECIPE_ID.THIRD_PARTY}, null); assert (users.nextPaginationToken == null); assert (users.users.length == 1); - assert (users.users[0].recipeId.equals("thirdparty")); - assert (users.users[0].user.equals(user5)); + assert (users.users[0].loginMethods[0].recipeId.toString().equals("thirdparty")); + assert (users.users[0].equals(user5)); } @@ -380,16 +380,16 @@ public void paginationTest() throws Exception { new RECIPE_ID[]{}, null); assert (users.nextPaginationToken == null); assert (users.users.length == 5); - assert (users.users[4].recipeId.equals("emailpassword")); - assert (users.users[4].user.equals(user1)); - assert (users.users[3].recipeId.equals("emailpassword")); - assert (users.users[3].user.equals(user2)); - assert (users.users[2].recipeId.equals("emailpassword")); - assert (users.users[2].user.equals(user3)); - assert (users.users[1].recipeId.equals("emailpassword")); - assert (users.users[1].user.equals(user4)); - assert (users.users[0].recipeId.equals("thirdparty")); - assert (users.users[0].user.equals(user5)); + assert (users.users[4].loginMethods[0].recipeId.toString().equals("emailpassword")); + assert (users.users[4].equals(user1)); + assert (users.users[3].loginMethods[0].recipeId.toString().equals("emailpassword")); + assert (users.users[3].equals(user2)); + assert (users.users[2].loginMethods[0].recipeId.toString().equals("emailpassword")); + assert (users.users[2].equals(user3)); + assert (users.users[1].loginMethods[0].recipeId.toString().equals("emailpassword")); + assert (users.users[1].equals(user4)); + assert (users.users[0].loginMethods[0].recipeId.toString().equals("thirdparty")); + assert (users.users[0].equals(user5)); } { @@ -404,10 +404,10 @@ public void paginationTest() throws Exception { new RECIPE_ID[]{RECIPE_ID.THIRD_PARTY, RECIPE_ID.EMAIL_PASSWORD}, null); assert (users.nextPaginationToken != null); assert (users.users.length == 2); - assert (users.users[1].recipeId.equals("emailpassword")); - assert (users.users[1].user.equals(user4)); - assert (users.users[0].recipeId.equals("thirdparty")); - assert (users.users[0].user.equals(user5)); + assert (users.users[1].loginMethods[0].recipeId.toString().equals("emailpassword")); + assert (users.users[1].equals(user4)); + assert (users.users[0].loginMethods[0].recipeId.toString().equals("thirdparty")); + assert (users.users[0].equals(user5)); } { @@ -415,12 +415,12 @@ public void paginationTest() throws Exception { new RECIPE_ID[]{RECIPE_ID.THIRD_PARTY, RECIPE_ID.EMAIL_PASSWORD}, null); assert (users.nextPaginationToken != null); assert (users.users.length == 3); - assert (users.users[0].recipeId.equals("emailpassword")); - assert (users.users[0].user.equals(user1)); - assert (users.users[1].recipeId.equals("emailpassword")); - assert (users.users[1].user.equals(user2)); - assert (users.users[2].recipeId.equals("emailpassword")); - assert (users.users[2].user.equals(user3)); + assert (users.users[0].loginMethods[0].recipeId.toString().equals("emailpassword")); + assert (users.users[0].equals(user1)); + assert (users.users[1].loginMethods[0].recipeId.toString().equals("emailpassword")); + assert (users.users[1].equals(user2)); + assert (users.users[2].loginMethods[0].recipeId.toString().equals("emailpassword")); + assert (users.users[2].equals(user3)); } process.kill(); @@ -516,11 +516,11 @@ public void randomPaginationTest() throws Exception { UserPaginationContainer users = AuthRecipe.getUsers(process.getProcess(), limit, "ASC", paginationToken, null, null); - for (UserPaginationContainer.UsersContainer uc : users.users) { + for (AuthRecipeUserInfo uc : users.users) { AuthRecipeUserInfo expected = usersCreated.get(indexIntoUsers); - AuthRecipeUserInfo actualUser = uc.user; + AuthRecipeUserInfo actualUser = uc; - assert (actualUser.equals(expected) && uc.recipeId.equals(expected.getRecipeId().toString())); + assert (actualUser.equals(expected) && uc.loginMethods[0].recipeId.toString().equals(expected.getRecipeId().toString())); indexIntoUsers++; } @@ -555,11 +555,11 @@ public void randomPaginationTest() throws Exception { UserPaginationContainer users = AuthRecipe.getUsers(process.getProcess(), limit, "DESC", paginationToken, null, null); - for (UserPaginationContainer.UsersContainer uc : users.users) { + for (AuthRecipeUserInfo uc : users.users) { AuthRecipeUserInfo expected = usersCreated.get(indexIntoUsers); - AuthRecipeUserInfo actualUser = uc.user; + AuthRecipeUserInfo actualUser = uc; - assert (actualUser.equals(expected) && uc.recipeId.equals(expected.getRecipeId().toString())); + assert (actualUser.equals(expected) && uc.loginMethods[0].recipeId.toString().equals(expected.getRecipeId().toString())); indexIntoUsers--; } diff --git a/src/test/java/io/supertokens/test/authRecipe/GetUsersWithSearchTagsTest.java b/src/test/java/io/supertokens/test/authRecipe/GetUsersWithSearchTagsTest.java index 6eef63f9e..bb335b9b1 100644 --- a/src/test/java/io/supertokens/test/authRecipe/GetUsersWithSearchTagsTest.java +++ b/src/test/java/io/supertokens/test/authRecipe/GetUsersWithSearchTagsTest.java @@ -31,7 +31,6 @@ import io.supertokens.ProcessState.PROCESS_STATE; import io.supertokens.authRecipe.AuthRecipe; import io.supertokens.authRecipe.UserPaginationContainer; -import io.supertokens.authRecipe.UserPaginationContainer.UsersContainer; import io.supertokens.emailpassword.EmailPassword; import io.supertokens.passwordless.Passwordless; import io.supertokens.passwordless.Passwordless.ConsumeCodeResponse; @@ -95,7 +94,7 @@ public void retriveUsersUsingSearchTags() throws Exception { UserPaginationContainer info = AuthRecipe.getUsers(process.getProcess(), 10, "ASC", null, null, tags); assertEquals(userIds.size(), info.users.length); for (int i = 0; i < info.users.length; i++) { - assertTrue(userIds.contains(info.users[i].user.id)); + assertTrue(userIds.contains(info.users[i].id)); } } @@ -109,8 +108,8 @@ public void retriveUsersUsingSearchTags() throws Exception { DashboardSearchTags tags = new DashboardSearchTags(arrayList, null, arrayList); UserPaginationContainer info = AuthRecipe.getUsers(process.getProcess(), 10, "ASC", null, null, tags); assertEquals(1, info.users.length); - assertEquals(userIds.get(2), info.users[0].user.id); - assertEquals("thirdparty", info.users[0].recipeId); + assertEquals(userIds.get(2), info.users[0].id); + assertEquals("thirdparty", info.users[0].loginMethods[0].recipeId.toString()); } @@ -124,8 +123,8 @@ public void retriveUsersUsingSearchTags() throws Exception { DashboardSearchTags tags = new DashboardSearchTags(null, arrayList, null); UserPaginationContainer info = AuthRecipe.getUsers(process.getProcess(), 10, "ASC", null, null, tags); assertEquals(1, info.users.length); - assertEquals(userIds.get(3), info.users[0].user.id); - assertEquals("passwordless", info.users[0].recipeId); + assertEquals(userIds.get(3), info.users[0].id); + assertEquals("passwordless", info.users[0].loginMethods[0].recipeId.toString()); } process.kill(); @@ -214,9 +213,9 @@ public void testSearchParamRegex() throws Exception { DashboardSearchTags tags = new DashboardSearchTags(emailList, null, null); UserPaginationContainer info = AuthRecipe.getUsers(process.getProcess(), 10, "ASC", null, null, tags); assertEquals(3, info.users.length); - assertEquals(userIds.get(0), info.users[0].user.id); - assertEquals(userIds.get(3), info.users[1].user.id); - assertEquals(userIds.get(4), info.users[2].user.id); + assertEquals(userIds.get(0), info.users[0].id); + assertEquals(userIds.get(3), info.users[1].id); + assertEquals(userIds.get(4), info.users[2].id); } // retrieve emails for users whose email starts with abc or have domain abc @@ -229,8 +228,8 @@ public void testSearchParamRegex() throws Exception { DashboardSearchTags tags = new DashboardSearchTags(emailList, null, null); UserPaginationContainer info = AuthRecipe.getUsers(process.getProcess(), 10, "ASC", null, null, tags); assertEquals(2, info.users.length); - assertEquals(userIds.get(1), info.users[0].user.id); - assertEquals(userIds.get(2), info.users[1].user.id); + assertEquals(userIds.get(1), info.users[0].id); + assertEquals(userIds.get(2), info.users[1].id); } @@ -244,7 +243,7 @@ public void testSearchParamRegex() throws Exception { UserPaginationContainer info = AuthRecipe.getUsers(process.getProcess(), 10, "ASC", null, null, tags); assertEquals(1, info.users.length); - assertEquals(userIds.get(4), info.users[0].user.id); + assertEquals(userIds.get(4), info.users[0].id); } } @@ -280,7 +279,7 @@ public void testThatQueryLimitIsCappedAt1000PerTable() throws Exception { UserPaginationContainer info = AuthRecipe.getUsers(process.getProcess(), 10, "ASC", null, null, tags); assertEquals(1000, info.users.length); for (int i = 0; i < info.users.length; i++) { - assertTrue(userIds.contains(info.users[i].user.id)); + assertTrue(userIds.contains(info.users[i].id)); } From 177efff6ef7a65971f05dc553b6e86523bbe0e6c Mon Sep 17 00:00:00 2001 From: Mihaly Lengyel Date: Fri, 18 Aug 2023 03:38:21 +0200 Subject: [PATCH 090/131] fix: remove extra wrapper around user objects in users list --- .../io/supertokens/webserver/api/core/UsersAPI.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/io/supertokens/webserver/api/core/UsersAPI.java b/src/main/java/io/supertokens/webserver/api/core/UsersAPI.java index 79c7247df..07d73fbb6 100644 --- a/src/main/java/io/supertokens/webserver/api/core/UsersAPI.java +++ b/src/main/java/io/supertokens/webserver/api/core/UsersAPI.java @@ -192,12 +192,12 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO JsonObject jsonObj = new JsonObject(); if (getVersionFromRequest(req).lesserThan(SemVer.v4_0)) { jsonObj.addProperty("recipeId", user.loginMethods[0].recipeId.toString()); + JsonObject userJson = user.toJsonWithoutAccountLinking(); + jsonObj.add("user", userJson); + usersJson.add(jsonObj); + } else { + usersJson.add(user.toJson()); } - JsonObject userJson = - getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v4_0) ? user.toJson() : - user.toJsonWithoutAccountLinking(); - jsonObj.add("user", userJson); - usersJson.add(jsonObj); } if (getVersionFromRequest(req).lesserThan(SemVer.v3_0)) { From d2af40426ad646373a55399cd1856c08b42bb87e Mon Sep 17 00:00:00 2001 From: Mihaly Lengyel Date: Fri, 18 Aug 2023 03:39:03 +0200 Subject: [PATCH 091/131] feat: update latest access token version --- .../java/io/supertokens/session/accessToken/AccessToken.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/supertokens/session/accessToken/AccessToken.java b/src/main/java/io/supertokens/session/accessToken/AccessToken.java index d343a8f71..efb87b2ae 100644 --- a/src/main/java/io/supertokens/session/accessToken/AccessToken.java +++ b/src/main/java/io/supertokens/session/accessToken/AccessToken.java @@ -568,7 +568,7 @@ private static void checkRequiredPropsExist(JsonObject obj, VERSION version) } public static VERSION getLatestVersion() { - return VERSION.V4; + return VERSION.V5; } public static String getVersionStringFromAccessTokenVersion(VERSION version) { From b4e9936a64cf256829db35b3b9cbac759ded2b6f Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Wed, 23 Aug 2023 18:08:21 +0530 Subject: [PATCH 092/131] fix: user pagination tests (#768) * fix: user pagination tests * fix: user pagination tests --- .../webserver/api/core/UsersAPI.java | 2 +- .../api/UserPaginationTest.java | 373 ++++++++++++++++++ 2 files changed, 374 insertions(+), 1 deletion(-) create mode 100644 src/test/java/io/supertokens/test/accountlinking/api/UserPaginationTest.java diff --git a/src/main/java/io/supertokens/webserver/api/core/UsersAPI.java b/src/main/java/io/supertokens/webserver/api/core/UsersAPI.java index 07d73fbb6..72b0ff878 100644 --- a/src/main/java/io/supertokens/webserver/api/core/UsersAPI.java +++ b/src/main/java/io/supertokens/webserver/api/core/UsersAPI.java @@ -189,8 +189,8 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO JsonArray usersJson = new JsonArray(); for (AuthRecipeUserInfo user : users.users) { - JsonObject jsonObj = new JsonObject(); if (getVersionFromRequest(req).lesserThan(SemVer.v4_0)) { + JsonObject jsonObj = new JsonObject(); jsonObj.addProperty("recipeId", user.loginMethods[0].recipeId.toString()); JsonObject userJson = user.toJsonWithoutAccountLinking(); jsonObj.add("user", userJson); diff --git a/src/test/java/io/supertokens/test/accountlinking/api/UserPaginationTest.java b/src/test/java/io/supertokens/test/accountlinking/api/UserPaginationTest.java new file mode 100644 index 000000000..6b6c90d6c --- /dev/null +++ b/src/test/java/io/supertokens/test/accountlinking/api/UserPaginationTest.java @@ -0,0 +1,373 @@ +/* + * Copyright (c) 2023, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.test.accountlinking.api; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import io.supertokens.Main; +import io.supertokens.ProcessState; +import io.supertokens.authRecipe.AuthRecipe; +import io.supertokens.emailpassword.EmailPassword; +import io.supertokens.emailpassword.exceptions.EmailChangeNotAllowedException; +import io.supertokens.featureflag.EE_FEATURES; +import io.supertokens.featureflag.FeatureFlagTestContent; +import io.supertokens.passwordless.Passwordless; +import io.supertokens.passwordless.exceptions.*; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; +import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; +import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; +import io.supertokens.pluginInterface.passwordless.exception.DuplicateLinkCodeHashException; +import io.supertokens.test.TestingProcessManager; +import io.supertokens.test.Utils; +import io.supertokens.test.httpRequest.HttpRequestForTesting; +import io.supertokens.thirdparty.ThirdParty; +import io.supertokens.utils.SemVer; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; + +import java.io.IOException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.*; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +public class UserPaginationTest { + @Rule + public TestRule watchman = Utils.getOnFailure(); + + @AfterClass + public static void afterTesting() { + Utils.afterTesting(); + } + + @Before + public void beforeEach() { + Utils.reset(); + } + + + AuthRecipeUserInfo createEmailPasswordUser(Main main, String email, String password) + throws DuplicateEmailException, StorageQueryException { + return EmailPassword.signUp(main, email, password); + } + + AuthRecipeUserInfo createThirdPartyUser(Main main, String thirdPartyId, String thirdPartyUserId, String email) + throws EmailChangeNotAllowedException, StorageQueryException { + return ThirdParty.signInUp(main, thirdPartyId, thirdPartyUserId, email).user; + } + + AuthRecipeUserInfo createPasswordlessUserWithEmail(Main main, String email) + throws DuplicateLinkCodeHashException, StorageQueryException, NoSuchAlgorithmException, IOException, + RestartFlowException, InvalidKeyException, Base64EncodingException, DeviceIdHashMismatchException, + StorageTransactionLogicException, IncorrectUserInputCodeException, ExpiredUserInputCodeException { + Passwordless.CreateCodeResponse code = Passwordless.createCode(main, email, null, + null, "123456"); + return Passwordless.consumeCode(main, code.deviceId, code.deviceIdHash, + code.userInputCode, null).user; + } + + AuthRecipeUserInfo createPasswordlessUserWithPhone(Main main, String phone) + throws DuplicateLinkCodeHashException, StorageQueryException, NoSuchAlgorithmException, IOException, + RestartFlowException, InvalidKeyException, Base64EncodingException, DeviceIdHashMismatchException, + StorageTransactionLogicException, IncorrectUserInputCodeException, ExpiredUserInputCodeException { + Passwordless.CreateCodeResponse code = Passwordless.createCode(main, null, phone, + null, "123456"); + return Passwordless.consumeCode(main, code.deviceId, code.deviceIdHash, + code.userInputCode, null).user; + } + + private JsonObject getUsers(Main main) throws Exception { + Map params = new HashMap<>(); + return HttpRequestForTesting.sendGETRequest(main, "", + "http://localhost:3567/users", params, 1000, 1000, null, + SemVer.v4_0.get(), ""); + } + + private JsonArray getUsersFromAllPages(Main main, int pageSize, String[] recipeFilters) throws Exception { + Map params = new HashMap<>(); + + if (recipeFilters != null) { + params.put("includeRecipeIds", String.join(",", recipeFilters)); + } + + params.put("limit", String.valueOf(pageSize)); + + JsonArray result = new JsonArray(); + + JsonObject response = HttpRequestForTesting.sendGETRequest(main, "", + "http://localhost:3567/users", params, 1000, 1000, null, + SemVer.v4_0.get(), ""); + + result.addAll(response.get("users").getAsJsonArray()); + while (response.get("nextPaginationToken") != null) { + String paginationToken = response.get("nextPaginationToken").getAsString(); + params.put("paginationToken", paginationToken); + + response = HttpRequestForTesting.sendGETRequest(main, "", + "http://localhost:3567/users", params, 1000, 1000, null, + SemVer.v4_0.get(), ""); + result.addAll(response.get("users").getAsJsonArray()); + } + + return result; + } + + @Test + public void testUserPaginationResultJson() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + AuthRecipeUserInfo user1 = createEmailPasswordUser(process.getProcess(), "test1@example.com", "password"); + AuthRecipeUserInfo user2 = createEmailPasswordUser(process.getProcess(), "test2@example.com", "password"); + AuthRecipeUserInfo user3 = createPasswordlessUserWithEmail(process.getProcess(), "test3@example.com"); + AuthRecipeUserInfo user4 = createPasswordlessUserWithEmail(process.getProcess(), "test4@example.com"); + AuthRecipeUserInfo user5 = createPasswordlessUserWithPhone(process.getProcess(), "+1234567890"); + AuthRecipeUserInfo user6 = createPasswordlessUserWithPhone(process.getProcess(), "+1234567891"); + AuthRecipeUserInfo user7 = createThirdPartyUser(process.getProcess(), "google", "test7", "test7@example.com"); + AuthRecipeUserInfo user8 = createThirdPartyUser(process.getProcess(), "google", "test8", "test8@example.com"); + + { + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.id).user; + AuthRecipe.linkAccounts(process.getProcess(), user3.id, primaryUser.id); + } + + { + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user2.id).user; + AuthRecipe.linkAccounts(process.getProcess(), user5.id, primaryUser.id); + AuthRecipe.linkAccounts(process.getProcess(), user7.id, primaryUser.id); + } + + { + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user6.id).user; + AuthRecipe.linkAccounts(process.getProcess(), user8.id, primaryUser.id); + } + + + Map params = new HashMap<>(); + JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/users", params, 1000, 1000, null, + SemVer.v4_0.get(), ""); + + JsonArray users = response.get("users").getAsJsonArray(); + assertEquals(4,users.size()); + + { + params = new HashMap<>(); + params.put("userId", user1.id); + JsonObject userResponse = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/user/id", params, 1000, 1000, null, + SemVer.v4_0.get(), ""); + userResponse.remove("status"); + assertEquals(userResponse.get("user"), users.get(0)); + } + + { + params = new HashMap<>(); + params.put("userId", user2.id); + JsonObject userResponse = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/user/id", params, 1000, 1000, null, + SemVer.v4_0.get(), ""); + userResponse.remove("status"); + assertEquals(userResponse.get("user"), users.get(1)); + } + + { + params = new HashMap<>(); + params.put("userId", user4.id); + JsonObject userResponse = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/user/id", params, 1000, 1000, null, + SemVer.v4_0.get(), ""); + userResponse.remove("status"); + assertEquals(userResponse.get("user"), users.get(2)); + } + + { + params = new HashMap<>(); + params.put("userId", user6.id); + JsonObject userResponse = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/user/id", params, 1000, 1000, null, + SemVer.v4_0.get(), ""); + userResponse.remove("status"); + assertEquals(userResponse.get("user"), users.get(3)); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testUserPaginationWithManyUsers() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + Map userInfoMap = new HashMap<>(); + Set userIds = new HashSet<>(); + Set emailPasswordUsers = new HashSet<>(); + Set passwordlessUsers = new HashSet<>(); + Set thirdPartyUsers = new HashSet<>(); + + // emailpassword users + for (int i=0; i < 200; i++) { + AuthRecipeUserInfo user = createEmailPasswordUser(process.getProcess(), "epuser" + i + "@gmail.com", "password" + i); + userInfoMap.put(user.id, user); + userIds.add(user.id); + emailPasswordUsers.add(user.id); + Thread.sleep(10); + } + + // passwordless users with email + for (int i=0; i < 200; i++) { + AuthRecipeUserInfo user = createPasswordlessUserWithEmail(process.getProcess(), "pluser" + i + "@gmail.com"); + userInfoMap.put(user.id, user); + userIds.add(user.id); + passwordlessUsers.add(user.id); + Thread.sleep(10); + } + + // passwordless users with phone + for (int i=0; i < 200; i++) { + AuthRecipeUserInfo user = createPasswordlessUserWithPhone(process.getProcess(), "+1234567890" + i); + userInfoMap.put(user.id, user); + userIds.add(user.id); + passwordlessUsers.add(user.id); + Thread.sleep(10); + } + + // thirdparty users + for (int i=0; i < 200; i++) { + AuthRecipeUserInfo user = createThirdPartyUser(process.getProcess(), "google", "tpuser" + i, "tpuser" + i + "@gmail.com"); + userInfoMap.put(user.id, user); + userIds.add(user.id); + thirdPartyUsers.add(user.id); + Thread.sleep(10); + } + + Map primaryUserIdMap = new HashMap<>(); + List primaryUserIds = new ArrayList<>(); + + // Randomly link accounts + Random rand = new Random(); + while (!userIds.isEmpty()) { + int numAccountsToLink = Math.min(rand.nextInt(3) + 1, userIds.size()); + List userIdsToLink = new ArrayList<>(); + + for (int i=0; i < numAccountsToLink; i++) { + String[] userIdsArray = userIds.toArray(new String[0]); + String userId = userIdsArray[rand.nextInt(userIds.size())]; + userIdsToLink.add(userId); + userIds.remove(userId); + } + + for (String userId : userIdsToLink) { + primaryUserIdMap.put(userId, userIdsToLink.get(0)); + } + + if (userIdsToLink.size() > 1) { + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), userIdsToLink.get(0)).user; + primaryUserIds.add(primaryUser.id); + + for (int i=1; i < userIdsToLink.size(); i++) { + AuthRecipe.linkAccounts(process.getProcess(), userIdsToLink.get(i), primaryUser.id); + } + } else { + primaryUserIds.add(userIdsToLink.get(0)); + } + } + + // Pagination tests + { + JsonArray usersResult = getUsersFromAllPages(process.getProcess(), 10, null); + assertEquals(primaryUserIds.size(), usersResult.size()); + } + + // Test pagination with recipe filters + { + JsonArray usersResult = getUsersFromAllPages(process.getProcess(), 20, new String[]{"emailpassword"}); + Set primaryUsers = new HashSet<>(); + for (String userId : emailPasswordUsers) { + primaryUsers.add(primaryUserIdMap.get(userId)); + } + + assertEquals(primaryUsers.size(), usersResult.size()); + } + { + JsonArray usersResult = getUsersFromAllPages(process.getProcess(), 20, new String[]{"passwordless"}); + Set primaryUsers = new HashSet<>(); + for (String userId : passwordlessUsers) { + primaryUsers.add(primaryUserIdMap.get(userId)); + } + + assertEquals(primaryUsers.size(), usersResult.size()); + } + { + JsonArray usersResult = getUsersFromAllPages(process.getProcess(), 20, new String[]{"thirdparty"}); + Set primaryUsers = new HashSet<>(); + for (String userId : thirdPartyUsers) { + primaryUsers.add(primaryUserIdMap.get(userId)); + } + + assertEquals(primaryUsers.size(), usersResult.size()); + } + { + JsonArray usersResult = getUsersFromAllPages(process.getProcess(), 20, new String[]{"emailpassword", "passwordless"}); + Set primaryUsers = new HashSet<>(); + for (String userId : emailPasswordUsers) { + primaryUsers.add(primaryUserIdMap.get(userId)); + } + for (String userId : passwordlessUsers) { + primaryUsers.add(primaryUserIdMap.get(userId)); + } + + assertEquals(primaryUsers.size(), usersResult.size()); + } + { + JsonArray usersResult = getUsersFromAllPages(process.getProcess(), 20, new String[]{"thirdparty", "passwordless"}); + Set primaryUsers = new HashSet<>(); + for (String userId : thirdPartyUsers) { + primaryUsers.add(primaryUserIdMap.get(userId)); + } + for (String userId : passwordlessUsers) { + primaryUsers.add(primaryUserIdMap.get(userId)); + } + + assertEquals(primaryUsers.size(), usersResult.size()); + } + { + JsonArray usersResult = getUsersFromAllPages(process.getProcess(), 20, new String[]{"thirdparty", "passwordless", "emailpassword"}); + assertEquals(primaryUserIds.size(), usersResult.size()); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } +} From cd3c36e25b3a158db731b350a1cdfe4411e30cd3 Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Thu, 24 Aug 2023 18:44:12 +0530 Subject: [PATCH 093/131] fix: recipeUserId in sign in/up related APIs (#769) * fix: recipeUserId in sign in/up related APIs * fix: phone and email case * fix: pr comments * fix: test with external user id * fix: pr comments * fix: pr comments * fix: pr comments * fix: pr comments --- .../io/supertokens/authRecipe/AuthRecipe.java | 60 +- .../emailpassword/EmailPassword.java | 8 +- .../inmemorydb/queries/GeneralQueries.java | 4 +- .../passwordless/Passwordless.java | 18 +- .../java/io/supertokens/session/Session.java | 4 +- .../io/supertokens/thirdparty/ThirdParty.java | 8 +- .../webserver/api/core/GetUserByIdAPI.java | 2 +- .../api/core/ListUsersByAccountInfoAPI.java | 2 +- .../webserver/api/core/UsersAPI.java | 2 +- .../api/emailpassword/SignInAPI.java | 14 +- .../api/emailpassword/SignUpAPI.java | 6 +- .../webserver/api/emailpassword/UserAPI.java | 2 +- .../api/passwordless/ConsumeCodeAPI.java | 16 +- .../webserver/api/passwordless/UserAPI.java | 4 +- .../api/thirdparty/GetUsersByEmailAPI.java | 2 +- .../webserver/api/thirdparty/SignInUpAPI.java | 28 +- .../webserver/api/thirdparty/UserAPI.java | 2 +- .../test/AuthRecipeAPITest2_10.java | 4 +- .../io/supertokens/test/AuthRecipeTest.java | 30 +- .../test/SuperTokensSaaSSecretTest.java | 6 +- .../accountlinking/CreatePrimaryUserTest.java | 70 +- .../test/accountlinking/DeleteUserTest.java | 144 ++-- .../GetUserByAccountInfoTest.java | 34 +- .../test/accountlinking/GetUserByIdTest.java | 32 +- .../test/accountlinking/LinkAccountsTest.java | 122 +-- .../accountlinking/UnlinkAccountsTest.java | 60 +- .../api/CanCreatePrimaryUserAPITest.java | 38 +- .../api/CanLinkAccountsAPITest.java | 81 +- .../api/CreatePrimaryUserAPITest.java | 56 +- .../api/GetUserByAccountInfoTest.java | 62 +- .../accountlinking/api/GetUserByIdTest.java | 78 +- .../api/LinkAccountsAPITest.java | 96 +-- .../api/TestRecipeUserIdInSignInUpAPIs.java | 765 ++++++++++++++++++ .../api/UnlinkAccountsAPITest.java | 46 +- .../authRecipe/AuthRecipeStorageTest.java | 2 +- .../DeleteUserAPIWithUserIdMappingTest.java | 35 +- .../test/authRecipe/GetUserByIdAPITest.java | 34 +- .../GetUsersAPIWithUserIdMappingTest.java | 4 +- .../GetUsersWithSearchTagsAPITest.java | 40 +- .../GetUsersWithSearchTagsTest.java | 51 +- .../test/authRecipe/MultitenantAPITest.java | 40 +- .../test/authRecipe/UserPaginationTest.java | 16 +- ...ExpiredPasswordResetTokensCronjobTest.java | 12 +- .../test/emailpassword/EmailPasswordTest.java | 102 +-- .../MultitenantEmailPasswordTest.java | 36 +- .../emailpassword/PasswordHashingTest.java | 8 +- .../UpdateUsersEmailAndPasswordTest.java | 14 +- .../test/emailpassword/UserMigrationTest.java | 6 +- .../api/ConsumeResetPasswordAPITest4_0.java | 8 +- .../GeneratePasswordResetTokenAPITest2_7.java | 2 +- .../GeneratePasswordResetTokenAPITest4_0.java | 6 +- .../ImportUserWithPasswordHashAPITest.java | 2 +- .../emailpassword/api/SignInAPITest4_0.java | 16 +- .../emailpassword/api/SignUpAPITest2_7.java | 2 +- .../emailpassword/api/UserPutAPITest2_8.java | 8 +- .../emailpassword/api/UserPutAPITest4_0.java | 6 +- ...redEmailVerificationTokensCronjobTest.java | 13 +- .../EmailVerificationTest.java | 48 +- .../test/multitenant/AppTenantUserTest.java | 8 +- .../test/multitenant/TestAppData.java | 20 +- .../TestTenantIdIsNotPresentForOlderCDI.java | 16 +- .../api/TestTenantUserAssociation.java | 32 +- .../PasswordlessConsumeCodeTest.java | 14 +- .../passwordless/PasswordlessGetUserTest.java | 8 +- .../PasswordlessUpdateUserTest.java | 36 +- .../test/thirdparty/ThirdPartyTest.java | 16 +- .../test/thirdparty/ThirdPartyTest2_7.java | 26 +- .../api/ThirdPartyGetUserAPITest2_7.java | 2 +- .../api/ThirdPartySignInUpAPITest4_0.java | 4 +- .../test/totp/api/TotpUserIdMappingTest.java | 4 +- .../UserIdMappingStorageTest.java | 56 +- .../test/userIdMapping/UserIdMappingTest.java | 78 +- .../api/CreateUserIdMappingAPITest.java | 19 +- .../api/GetUserIdMappingAPITest.java | 6 +- .../api/RemoveUserIdMappingAPITest.java | 10 +- .../api/UpdateExternalUserIdInfoTest.java | 6 +- .../recipe/EmailPasswordAPITest.java | 8 +- .../recipe/PasswordlessAPITest.java | 10 +- .../recipe/ThirdPartyAPITest.java | 10 +- .../test/userRoles/UserRolesTest.java | 10 +- 80 files changed, 1806 insertions(+), 1010 deletions(-) create mode 100644 src/test/java/io/supertokens/test/accountlinking/api/TestRecipeUserIdInSignInUpAPIs.java diff --git a/src/main/java/io/supertokens/authRecipe/AuthRecipe.java b/src/main/java/io/supertokens/authRecipe/AuthRecipe.java index 4ad5c4abc..d1cea2a54 100644 --- a/src/main/java/io/supertokens/authRecipe/AuthRecipe.java +++ b/src/main/java/io/supertokens/authRecipe/AuthRecipe.java @@ -89,10 +89,10 @@ public static boolean unlinkAccounts(Main main, AppIdentifierWithStorage appIden appIdentifierWithStorage, recipeUserId, UserIdType.SUPERTOKENS); - if (primaryUser.id.equals(recipeUserId)) { + if (primaryUser.getSupertokensUserId().equals(recipeUserId)) { // we are trying to unlink the user ID which is the same as the primary one. if (primaryUser.loginMethods.length == 1) { - storage.unlinkAccounts_Transaction(appIdentifierWithStorage, con, primaryUser.id, recipeUserId); + storage.unlinkAccounts_Transaction(appIdentifierWithStorage, con, primaryUser.getSupertokensUserId(), recipeUserId); Session.revokeAllSessionsForUser(main, appIdentifierWithStorage, mappingResult == null ? recipeUserId : mappingResult.externalUserId, false); @@ -109,7 +109,7 @@ public static boolean unlinkAccounts(Main main, AppIdentifierWithStorage appIden return true; } } else { - storage.unlinkAccounts_Transaction(appIdentifierWithStorage, con, primaryUser.id, recipeUserId); + storage.unlinkAccounts_Transaction(appIdentifierWithStorage, con, primaryUser.getSupertokensUserId(), recipeUserId); Session.revokeAllSessionsForUser(main, appIdentifierWithStorage, mappingResult == null ? recipeUserId : mappingResult.externalUserId, false); return false; @@ -221,7 +221,7 @@ private static CanLinkAccountsResult canLinkAccountsHelper(TransactionConnection } if (!primaryUser.isPrimaryUser) { - throw new InputUserIdIsNotAPrimaryUserException(primaryUser.id); + throw new InputUserIdIsNotAPrimaryUserException(primaryUser.getSupertokensUserId()); } AuthRecipeUserInfo recipeUser = storage.getPrimaryUserById_Transaction(appIdentifierWithStorage, con, @@ -231,10 +231,10 @@ private static CanLinkAccountsResult canLinkAccountsHelper(TransactionConnection } if (recipeUser.isPrimaryUser) { - if (recipeUser.id.equals(primaryUser.id)) { - return new CanLinkAccountsResult(recipeUser.id, primaryUser.id, true); + if (recipeUser.getSupertokensUserId().equals(primaryUser.getSupertokensUserId())) { + return new CanLinkAccountsResult(recipeUser.getSupertokensUserId(), primaryUser.getSupertokensUserId(), true); } else { - throw new RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException(recipeUser.id, + throw new RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException(recipeUser.getSupertokensUserId(), "The input recipe user ID is already linked to another user ID"); } } @@ -270,8 +270,8 @@ private static CanLinkAccountsResult canLinkAccountsHelper(TransactionConnection .listPrimaryUsersByEmail_Transaction(tenantIdentifier, con, recipeUserIdLM.email); for (AuthRecipeUserInfo user : usersWithSameEmail) { - if (user.isPrimaryUser && !user.id.equals(primaryUser.id)) { - throw new AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException(user.id, + if (user.isPrimaryUser && !user.getSupertokensUserId().equals(primaryUser.getSupertokensUserId())) { + throw new AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException(user.getSupertokensUserId(), "This user's email is already associated with another user ID"); } } @@ -282,8 +282,8 @@ private static CanLinkAccountsResult canLinkAccountsHelper(TransactionConnection .listPrimaryUsersByPhoneNumber_Transaction(tenantIdentifier, con, recipeUserIdLM.phoneNumber); for (AuthRecipeUserInfo user : usersWithSamePhoneNumber) { - if (user.isPrimaryUser && !user.id.equals(primaryUser.id)) { - throw new AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException(user.id, + if (user.isPrimaryUser && !user.getSupertokensUserId().equals(primaryUser.getSupertokensUserId())) { + throw new AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException(user.getSupertokensUserId(), "This user's phone number is already associated with another user" + " ID"); } @@ -295,16 +295,16 @@ private static CanLinkAccountsResult canLinkAccountsHelper(TransactionConnection .getPrimaryUsersByThirdPartyInfo_Transaction(tenantIdentifier, con, recipeUserIdLM.thirdParty.id, recipeUserIdLM.thirdParty.userId); if (userWithSameThirdParty != null && userWithSameThirdParty.isPrimaryUser && - !userWithSameThirdParty.id.equals(primaryUser.id)) { + !userWithSameThirdParty.getSupertokensUserId().equals(primaryUser.getSupertokensUserId())) { throw new AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException( - userWithSameThirdParty.id, + userWithSameThirdParty.getSupertokensUserId(), "This user's third party login is already associated with another" + " user ID"); } } } - return new CanLinkAccountsResult(recipeUser.id, primaryUser.id, false); + return new CanLinkAccountsResult(recipeUser.getSupertokensUserId(), primaryUser.getSupertokensUserId(), false); } @TestOnly @@ -437,10 +437,10 @@ private static CreatePrimaryUserResult canCreatePrimaryUserHelper(TransactionCon throw new UnknownUserIdException(); } if (targetUser.isPrimaryUser) { - if (targetUser.id.equals(recipeUserId)) { + if (targetUser.getSupertokensUserId().equals(recipeUserId)) { return new CreatePrimaryUserResult(targetUser, true); } else { - throw new RecipeUserIdAlreadyLinkedWithPrimaryUserIdException(targetUser.id, + throw new RecipeUserIdAlreadyLinkedWithPrimaryUserIdException(targetUser.getSupertokensUserId(), "This user ID is already linked to another user ID"); } } @@ -464,7 +464,7 @@ private static CreatePrimaryUserResult canCreatePrimaryUserHelper(TransactionCon loginMethod.email); for (AuthRecipeUserInfo user : usersWithSameEmail) { if (user.isPrimaryUser) { - throw new AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException(user.id, + throw new AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException(user.getSupertokensUserId(), "This user's email is already associated with another user ID"); } } @@ -476,7 +476,7 @@ private static CreatePrimaryUserResult canCreatePrimaryUserHelper(TransactionCon loginMethod.phoneNumber); for (AuthRecipeUserInfo user : usersWithSamePhoneNumber) { if (user.isPrimaryUser) { - throw new AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException(user.id, + throw new AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException(user.getSupertokensUserId(), "This user's phone number is already associated with another user" + " ID"); } @@ -489,7 +489,7 @@ private static CreatePrimaryUserResult canCreatePrimaryUserHelper(TransactionCon loginMethod.thirdParty.id, loginMethod.thirdParty.userId); if (userWithSameThirdParty != null && userWithSameThirdParty.isPrimaryUser) { throw new AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException( - userWithSameThirdParty.id, + userWithSameThirdParty.getSupertokensUserId(), "This user's third party login is already associated with another" + " user ID"); } @@ -537,7 +537,7 @@ public static CreatePrimaryUserResult createPrimaryUser(Main main, if (result.wasAlreadyAPrimaryUser) { return result; } - storage.makePrimaryUser_Transaction(appIdentifierWithStorage, con, result.user.id); + storage.makePrimaryUser_Transaction(appIdentifierWithStorage, con, result.user.getSupertokensUserId()); storage.commitTransaction(con); @@ -681,7 +681,7 @@ public static UserPaginationContainer getUsers(TenantIdentifierWithStorage tenan int maxLoop = users.length; if (users.length == limit + 1) { maxLoop = limit; - nextPaginationToken = new UserPaginationToken(users[limit].id, + nextPaginationToken = new UserPaginationToken(users[limit].getSupertokensUserId(), users[limit].timeJoined).generateToken(); } AuthRecipeUserInfo[] resultUsers = new AuthRecipeUserInfo[maxLoop]; @@ -791,23 +791,23 @@ private static void deleteUserHelper(TransactionConnection con, AppIdentifierWit } if (removeAllLinkedAccounts || userToDelete.loginMethods.length == 1) { - if (userToDelete.id.equals(userIdToDeleteForAuthRecipe)) { + if (userToDelete.getSupertokensUserId().equals(userIdToDeleteForAuthRecipe)) { primaryUserIdToDeleteNonAuthRecipe = userIdToDeleteForNonAuthRecipeForRecipeUserId; } else { // this is always type supertokens user ID cause it's from a user from the database. io.supertokens.pluginInterface.useridmapping.UserIdMapping mappingResult = io.supertokens.useridmapping.UserIdMapping.getUserIdMapping( appIdentifierWithStorage, - userToDelete.id, UserIdType.SUPERTOKENS); + userToDelete.getSupertokensUserId(), UserIdType.SUPERTOKENS); if (mappingResult != null) { primaryUserIdToDeleteNonAuthRecipe = mappingResult.externalUserId; } else { - primaryUserIdToDeleteNonAuthRecipe = userToDelete.id; + primaryUserIdToDeleteNonAuthRecipe = userToDelete.getSupertokensUserId(); } } } else { - if (userToDelete.id.equals(userIdToDeleteForAuthRecipe)) { + if (userToDelete.getSupertokensUserId().equals(userIdToDeleteForAuthRecipe)) { // this means we are deleting the primary user itself, but keeping other linked accounts // so we keep the non auth recipe info of this user since other linked accounts can use it userIdToDeleteForNonAuthRecipeForRecipeUserId = null; @@ -816,7 +816,7 @@ private static void deleteUserHelper(TransactionConnection con, AppIdentifierWit if (!removeAllLinkedAccounts) { deleteAuthRecipeUser(con, appIdentifierWithStorage, userIdToDeleteForAuthRecipe, - !userIdToDeleteForAuthRecipe.equals(userToDelete.id)); + !userIdToDeleteForAuthRecipe.equals(userToDelete.getSupertokensUserId())); if (userIdToDeleteForNonAuthRecipeForRecipeUserId != null) { deleteNonAuthRecipeUser(con, appIdentifierWithStorage, userIdToDeleteForNonAuthRecipeForRecipeUserId); @@ -827,17 +827,17 @@ private static void deleteUserHelper(TransactionConnection con, AppIdentifierWit // this is only done to also delete the user ID mapping in case it exists, since we do not delete in the // previous call to deleteAuthRecipeUser above. - deleteAuthRecipeUser(con, appIdentifierWithStorage, userToDelete.id, + deleteAuthRecipeUser(con, appIdentifierWithStorage, userToDelete.getSupertokensUserId(), true); } } else { for (LoginMethod lM : userToDelete.loginMethods) { - io.supertokens.pluginInterface.useridmapping.UserIdMapping mappingResult = lM.recipeUserId.equals( + io.supertokens.pluginInterface.useridmapping.UserIdMapping mappingResult = lM.getSupertokensUserId().equals( userIdToDeleteForAuthRecipe) ? userIdMapping : io.supertokens.useridmapping.UserIdMapping.getUserIdMapping( appIdentifierWithStorage, - lM.recipeUserId, UserIdType.SUPERTOKENS); - deleteUserHelper(con, appIdentifierWithStorage, lM.recipeUserId, false, mappingResult); + lM.getSupertokensUserId(), UserIdType.SUPERTOKENS); + deleteUserHelper(con, appIdentifierWithStorage, lM.getSupertokensUserId(), false, mappingResult); } } } diff --git a/src/main/java/io/supertokens/emailpassword/EmailPassword.java b/src/main/java/io/supertokens/emailpassword/EmailPassword.java index 1b9b1a28e..708d448fc 100644 --- a/src/main/java/io/supertokens/emailpassword/EmailPassword.java +++ b/src/main/java/io/supertokens/emailpassword/EmailPassword.java @@ -190,7 +190,7 @@ public static ImportUserResponse importUserWithPasswordHash(TenantIdentifierWith LoginMethod finalLoginMethod = loginMethod; storage.startTransaction(con -> { storage.updateUsersPassword_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), con, - finalLoginMethod.recipeUserId, passwordHash); + finalLoginMethod.getSupertokensUserId(), passwordHash); return null; }); return new ImportUserResponse(true, userInfoToBeUpdated); @@ -618,7 +618,7 @@ public static void updateUsersEmailOrPassword(AppIdentifierWithStorage appIdenti email); for (AuthRecipeUserInfo userWithSameEmail : existingUsersWithNewEmail) { - if (userWithSameEmail.isPrimaryUser && !userWithSameEmail.id.equals(user.id)) { + if (userWithSameEmail.isPrimaryUser && !userWithSameEmail.getSupertokensUserId().equals(user.getSupertokensUserId())) { throw new StorageTransactionLogicException( new EmailChangeNotAllowedException()); } @@ -682,8 +682,8 @@ public static UserInfo getUserUsingId(AppIdentifierWithStorage appIdentifierWith return null; } for (LoginMethod lM : result.loginMethods) { - if (lM.recipeUserId.equals(userId)) { - return new UserInfo(lM.recipeUserId, result.isPrimaryUser, lM); + if (lM.getSupertokensUserId().equals(userId)) { + return new UserInfo(lM.getSupertokensUserId(), result.isPrimaryUser, lM); } } return null; diff --git a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java index 45ab3baaa..933618843 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java @@ -854,7 +854,7 @@ public static AuthRecipeUserInfo[] getUsers(Start start, TenantIdentifier tenant // usersFromQuery Map userIdToInfoMap = new HashMap<>(); for (AuthRecipeUserInfo user : users) { - userIdToInfoMap.put(user.id, user); + userIdToInfoMap.put(user.getSupertokensUserId(), user); } for (int i = 0; i < usersFromQuery.size(); i++) { if (finalResult[i] == null) { @@ -1155,7 +1155,7 @@ private static List getPrimaryUserInfoForUserIds(Start start Map recipeUserIdToLoginMethodMap = new HashMap<>(); for (LoginMethod loginMethod : loginMethods) { - recipeUserIdToLoginMethodMap.put(loginMethod.recipeUserId, loginMethod); + recipeUserIdToLoginMethodMap.put(loginMethod.getSupertokensUserId(), loginMethod); } Map userIdToAuthRecipeUserInfo = new HashMap<>(); diff --git a/src/main/java/io/supertokens/passwordless/Passwordless.java b/src/main/java/io/supertokens/passwordless/Passwordless.java index 9dd392c18..610d52d7d 100644 --- a/src/main/java/io/supertokens/passwordless/Passwordless.java +++ b/src/main/java/io/supertokens/passwordless/Passwordless.java @@ -418,7 +418,7 @@ public static ConsumeCodeResponse consumeCode(TenantIdentifierWithStorage tenant long timeJoined = System.currentTimeMillis(); user = passwordlessStorage.createUser(tenantIdentifierWithStorage, userId, consumedDevice.email, consumedDevice.phoneNumber, timeJoined); - return new ConsumeCodeResponse(true, user); + return new ConsumeCodeResponse(true, user, consumedDevice.email, consumedDevice.phoneNumber); } catch (DuplicateEmailException | DuplicatePhoneNumberException e) { // Getting these would mean that between getting the user and trying creating it: // 1. the user managed to do a full create+consume flow @@ -441,7 +441,7 @@ public static ConsumeCodeResponse consumeCode(TenantIdentifierWithStorage tenant removeCodesByPhoneNumber(tenantIdentifierWithStorage, loginMethod.phoneNumber); } } - return new ConsumeCodeResponse(false, user); + return new ConsumeCodeResponse(false, user, consumedDevice.email, consumedDevice.phoneNumber); } @TestOnly @@ -545,8 +545,8 @@ public static UserInfo getUserById(AppIdentifierWithStorage appIdentifierWithSto return null; } for (LoginMethod lM : result.loginMethods) { - if (lM.recipeUserId.equals(userId)) { - return new io.supertokens.pluginInterface.passwordless.UserInfo(lM.recipeUserId, result.isPrimaryUser, + if (lM.getSupertokensUserId().equals(userId)) { + return new io.supertokens.pluginInterface.passwordless.UserInfo(lM.getSupertokensUserId(), result.isPrimaryUser, lM); } } @@ -626,7 +626,7 @@ public static void updateUser(AppIdentifierWithStorage appIdentifierWithStorage, } LoginMethod lM = Arrays.stream(user.loginMethods) - .filter(currlM -> currlM.recipeUserId.equals(recipeUserId) && currlM.recipeId == RECIPE_ID.PASSWORDLESS) + .filter(currlM -> currlM.getSupertokensUserId().equals(recipeUserId) && currlM.recipeId == RECIPE_ID.PASSWORDLESS) .findFirst().orElse(null); if (lM == null) { @@ -660,7 +660,7 @@ public static void updateUser(AppIdentifierWithStorage appIdentifierWithStorage, emailUpdate.newValue); for (AuthRecipeUserInfo userWithSameEmail : existingUsersWithNewEmail) { - if (userWithSameEmail.isPrimaryUser && !userWithSameEmail.id.equals(user.id)) { + if (userWithSameEmail.isPrimaryUser && !userWithSameEmail.getSupertokensUserId().equals(user.getSupertokensUserId())) { throw new StorageTransactionLogicException( new EmailChangeNotAllowedException()); } @@ -764,10 +764,14 @@ public CreateCodeResponse(String deviceIdHash, String codeId, String deviceId, S public static class ConsumeCodeResponse { public boolean createdNewUser; public AuthRecipeUserInfo user; + public String email; + public String phoneNumber; - public ConsumeCodeResponse(boolean createdNewUser, AuthRecipeUserInfo user) { + public ConsumeCodeResponse(boolean createdNewUser, AuthRecipeUserInfo user, String email, String phoneNumber) { this.createdNewUser = createdNewUser; this.user = user; + this.email = email; + this.phoneNumber = phoneNumber; } } diff --git a/src/main/java/io/supertokens/session/Session.java b/src/main/java/io/supertokens/session/Session.java index a34c68ea7..c5ca0b979 100644 --- a/src/main/java/io/supertokens/session/Session.java +++ b/src/main/java/io/supertokens/session/Session.java @@ -848,7 +848,7 @@ public static String[] getAllNonExpiredSessionHandlesForUser( .getPrimaryUserById(appIdentifierWithStorage, userId); if (primaryUser != null) { for (LoginMethod lM : primaryUser.loginMethods) { - userIds.add(lM.recipeUserId); + userIds.add(lM.getSupertokensUserId()); } } } @@ -883,7 +883,7 @@ public static String[] getAllNonExpiredSessionHandlesForUser( .getPrimaryUserById(tenantIdentifierWithStorage.toAppIdentifier(), userId); if (primaryUser != null) { for (LoginMethod lM : primaryUser.loginMethods) { - userIds.add(lM.recipeUserId); + userIds.add(lM.getSupertokensUserId()); } } } diff --git a/src/main/java/io/supertokens/thirdparty/ThirdParty.java b/src/main/java/io/supertokens/thirdparty/ThirdParty.java index 28a55f8cc..d4e0fa718 100644 --- a/src/main/java/io/supertokens/thirdparty/ThirdParty.java +++ b/src/main/java/io/supertokens/thirdparty/ThirdParty.java @@ -81,7 +81,7 @@ public static SignInUpResponse signInUp2_7(TenantIdentifierWithStorage tenantIde assert (finalResponse.user.loginMethods.length == 1); tenantIdentifierWithStorage.getEmailVerificationStorage() .updateIsEmailVerified_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), con, - finalResponse.user.id, finalResponse.user.loginMethods[0].email, true); + finalResponse.user.getSupertokensUserId(), finalResponse.user.loginMethods[0].email, true); tenantIdentifierWithStorage.getEmailVerificationStorage() .commitTransaction(con); return null; @@ -220,7 +220,7 @@ private static SignInUpResponse signInUpHelper(TenantIdentifierWithStorage tenan ); for (AuthRecipeUserInfo userWithSameEmail : userBasedOnEmail) { if (userWithSameEmail.isPrimaryUser && - !userWithSameEmail.id.equals(userFromDb.id)) { + !userWithSameEmail.getSupertokensUserId().equals(userFromDb.getSupertokensUserId())) { throw new StorageTransactionLogicException( new EmailChangeNotAllowedException()); } @@ -257,8 +257,8 @@ public static UserInfo getUser(AppIdentifierWithStorage appIdentifierWithStorage return null; } for (LoginMethod lM : result.loginMethods) { - if (lM.recipeUserId.equals(userId)) { - return new io.supertokens.pluginInterface.thirdparty.UserInfo(lM.recipeUserId, result.isPrimaryUser, + if (lM.getSupertokensUserId().equals(userId)) { + return new io.supertokens.pluginInterface.thirdparty.UserInfo(lM.getSupertokensUserId(), result.isPrimaryUser, lM); } } diff --git a/src/main/java/io/supertokens/webserver/api/core/GetUserByIdAPI.java b/src/main/java/io/supertokens/webserver/api/core/GetUserByIdAPI.java index debaca684..5f9f67837 100644 --- a/src/main/java/io/supertokens/webserver/api/core/GetUserByIdAPI.java +++ b/src/main/java/io/supertokens/webserver/api/core/GetUserByIdAPI.java @@ -68,7 +68,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO if (user != null) { io.supertokens.pluginInterface.useridmapping.UserIdMapping userIdMapping = UserIdMapping.getUserIdMapping( - getAppIdentifierWithStorage(req), user.id, UserIdType.SUPERTOKENS); + getAppIdentifierWithStorage(req), user.getSupertokensUserId(), UserIdType.SUPERTOKENS); if (userIdMapping != null) { user.setExternalUserId(userIdMapping.externalUserId); } else { diff --git a/src/main/java/io/supertokens/webserver/api/core/ListUsersByAccountInfoAPI.java b/src/main/java/io/supertokens/webserver/api/core/ListUsersByAccountInfoAPI.java index 834738f28..7bfa7b3a6 100644 --- a/src/main/java/io/supertokens/webserver/api/core/ListUsersByAccountInfoAPI.java +++ b/src/main/java/io/supertokens/webserver/api/core/ListUsersByAccountInfoAPI.java @@ -81,7 +81,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO // we intentionally do not use the function that accepts an array of user IDs to get the mapping cause // this is simpler to use, and cause there shouldn't be that many userIds per email anyway io.supertokens.pluginInterface.useridmapping.UserIdMapping userIdMapping = UserIdMapping - .getUserIdMapping(appIdentifierWithStorage, users[i].id, UserIdType.SUPERTOKENS); + .getUserIdMapping(appIdentifierWithStorage, users[i].getSupertokensUserId(), UserIdType.SUPERTOKENS); if (userIdMapping != null) { users[i].setExternalUserId(userIdMapping.externalUserId); } else { diff --git a/src/main/java/io/supertokens/webserver/api/core/UsersAPI.java b/src/main/java/io/supertokens/webserver/api/core/UsersAPI.java index 72b0ff878..cce18e222 100644 --- a/src/main/java/io/supertokens/webserver/api/core/UsersAPI.java +++ b/src/main/java/io/supertokens/webserver/api/core/UsersAPI.java @@ -170,7 +170,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO ArrayList userIds = new ArrayList<>(); for (int i = 0; i < users.users.length; i++) { - userIds.add(users.users[i].id); + userIds.add(users.users[i].getSupertokensUserId()); } HashMap userIdMapping = UserIdMapping.getUserIdMappingForSuperTokensUserIds( tenantIdentifierWithStorage, userIds); diff --git a/src/main/java/io/supertokens/webserver/api/emailpassword/SignInAPI.java b/src/main/java/io/supertokens/webserver/api/emailpassword/SignInAPI.java index c1aadefa2..7570ebc1f 100644 --- a/src/main/java/io/supertokens/webserver/api/emailpassword/SignInAPI.java +++ b/src/main/java/io/supertokens/webserver/api/emailpassword/SignInAPI.java @@ -25,6 +25,7 @@ import io.supertokens.output.Logging; import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; +import io.supertokens.pluginInterface.authRecipe.LoginMethod; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; @@ -78,11 +79,11 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I password); ActiveUsers.updateLastActive(tenantIdentifierWithStorage.toAppIdentifierWithStorage(), main, - user.id); // use the internal user id + user.getSupertokensUserId()); // use the internal user id // if a userIdMapping exists, pass the externalUserId to the response UserIdMapping userIdMapping = io.supertokens.useridmapping.UserIdMapping.getUserIdMapping( - tenantIdentifierWithStorage.toAppIdentifierWithStorage(), user.id, UserIdType.SUPERTOKENS); + tenantIdentifierWithStorage.toAppIdentifierWithStorage(), user.getSupertokensUserId(), UserIdType.SUPERTOKENS); if (userIdMapping != null) { user.setExternalUserId(userIdMapping.externalUserId); @@ -98,6 +99,15 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I userJson.remove("tenantIds"); } result.add("user", userJson); + if (getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v4_0)) { + for (LoginMethod loginMethod : user.loginMethods) { + if (loginMethod.recipeId.equals(RECIPE_ID.EMAIL_PASSWORD) && normalisedEmail.equals(loginMethod.email)) { + result.addProperty("recipeUserId", loginMethod.getSupertokensOrExternalUserId()); + break; + } + } + } + super.sendJsonResponse(200, result, resp); } catch (WrongCredentialsException e) { diff --git a/src/main/java/io/supertokens/webserver/api/emailpassword/SignUpAPI.java b/src/main/java/io/supertokens/webserver/api/emailpassword/SignUpAPI.java index 304468079..e7fc4556b 100644 --- a/src/main/java/io/supertokens/webserver/api/emailpassword/SignUpAPI.java +++ b/src/main/java/io/supertokens/webserver/api/emailpassword/SignUpAPI.java @@ -80,7 +80,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I TenantIdentifierWithStorage tenant = this.getTenantIdentifierWithStorageFromRequest(req); UserInfo user = EmailPassword.signUp(tenant, super.main, normalisedEmail, password); - ActiveUsers.updateLastActive(this.getAppIdentifierWithStorage(req), main, user.id); + ActiveUsers.updateLastActive(this.getAppIdentifierWithStorage(req), main, user.getSupertokensUserId()); JsonObject result = new JsonObject(); result.addProperty("status", "OK"); @@ -94,8 +94,10 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I } result.add("user", userJson); + if (getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v4_0)) { + result.addProperty("recipeUserId", user.getSupertokensOrExternalUserId()); + } super.sendJsonResponse(200, result, resp); - } catch (DuplicateEmailException e) { Logging.debug(main, tenantIdentifier, Utils.exceptionStacktraceToString(e)); JsonObject result = new JsonObject(); diff --git a/src/main/java/io/supertokens/webserver/api/emailpassword/UserAPI.java b/src/main/java/io/supertokens/webserver/api/emailpassword/UserAPI.java index 430b8d993..5a08b01a5 100644 --- a/src/main/java/io/supertokens/webserver/api/emailpassword/UserAPI.java +++ b/src/main/java/io/supertokens/webserver/api/emailpassword/UserAPI.java @@ -113,7 +113,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO if (user != null) { io.supertokens.pluginInterface.useridmapping.UserIdMapping userIdMapping = UserIdMapping.getUserIdMapping( - getAppIdentifierWithStorage(req), user.id, UserIdType.SUPERTOKENS); + getAppIdentifierWithStorage(req), user.getSupertokensUserId(), UserIdType.SUPERTOKENS); if (userIdMapping != null) { user.setExternalUserId(userIdMapping.externalUserId); } else { diff --git a/src/main/java/io/supertokens/webserver/api/passwordless/ConsumeCodeAPI.java b/src/main/java/io/supertokens/webserver/api/passwordless/ConsumeCodeAPI.java index 9be86ec51..91551f863 100644 --- a/src/main/java/io/supertokens/webserver/api/passwordless/ConsumeCodeAPI.java +++ b/src/main/java/io/supertokens/webserver/api/passwordless/ConsumeCodeAPI.java @@ -24,6 +24,7 @@ import io.supertokens.passwordless.Passwordless.ConsumeCodeResponse; import io.supertokens.passwordless.exceptions.*; import io.supertokens.pluginInterface.RECIPE_ID; +import io.supertokens.pluginInterface.authRecipe.LoginMethod; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; @@ -39,6 +40,7 @@ import java.io.IOException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; +import java.util.Objects; public class ConsumeCodeAPI extends WebserverAPI { @@ -85,11 +87,11 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I deviceId, deviceIdHash, userInputCode, linkCode); - ActiveUsers.updateLastActive(this.getAppIdentifierWithStorage(req), main, consumeCodeResponse.user.id); + ActiveUsers.updateLastActive(this.getAppIdentifierWithStorage(req), main, consumeCodeResponse.user.getSupertokensUserId()); UserIdMapping userIdMapping = io.supertokens.useridmapping.UserIdMapping.getUserIdMapping( this.getAppIdentifierWithStorage(req), - consumeCodeResponse.user.id, UserIdType.ANY); + consumeCodeResponse.user.getSupertokensUserId(), UserIdType.ANY); if (userIdMapping != null) { consumeCodeResponse.user.setExternalUserId(userIdMapping.externalUserId); } else { @@ -108,6 +110,16 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I result.addProperty("createdNewUser", consumeCodeResponse.createdNewUser); result.add("user", userJson); + if (getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v4_0)) { + for (LoginMethod loginMethod : consumeCodeResponse.user.loginMethods) { + if (loginMethod.recipeId.equals(RECIPE_ID.PASSWORDLESS) + && (consumeCodeResponse.email == null || Objects.equals(loginMethod.email, consumeCodeResponse.email)) + && (consumeCodeResponse.phoneNumber == null || Objects.equals(loginMethod.phoneNumber, consumeCodeResponse.phoneNumber))) { + result.addProperty("recipeUserId", loginMethod.getSupertokensOrExternalUserId()); + break; + } + } + } super.sendJsonResponse(200, result, resp); } catch (RestartFlowException ex) { diff --git a/src/main/java/io/supertokens/webserver/api/passwordless/UserAPI.java b/src/main/java/io/supertokens/webserver/api/passwordless/UserAPI.java index 7ed5e003e..9b3aba6af 100644 --- a/src/main/java/io/supertokens/webserver/api/passwordless/UserAPI.java +++ b/src/main/java/io/supertokens/webserver/api/passwordless/UserAPI.java @@ -102,7 +102,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO if (user != null) { UserIdMapping userIdMapping = io.supertokens.useridmapping.UserIdMapping.getUserIdMapping( this.getAppIdentifierWithStorage(req), - user.id, UserIdType.SUPERTOKENS); + user.getSupertokensUserId(), UserIdType.SUPERTOKENS); if (userIdMapping != null) { user.setExternalUserId(userIdMapping.externalUserId); } else { @@ -115,7 +115,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO if (user != null) { UserIdMapping userIdMapping = io.supertokens.useridmapping.UserIdMapping.getUserIdMapping( this.getAppIdentifierWithStorage(req), - user.id, UserIdType.SUPERTOKENS); + user.getSupertokensUserId(), UserIdType.SUPERTOKENS); if (userIdMapping != null) { user.setExternalUserId(userIdMapping.externalUserId); } else { diff --git a/src/main/java/io/supertokens/webserver/api/thirdparty/GetUsersByEmailAPI.java b/src/main/java/io/supertokens/webserver/api/thirdparty/GetUsersByEmailAPI.java index 5c55d2aac..0d5554b30 100644 --- a/src/main/java/io/supertokens/webserver/api/thirdparty/GetUsersByEmailAPI.java +++ b/src/main/java/io/supertokens/webserver/api/thirdparty/GetUsersByEmailAPI.java @@ -69,7 +69,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO // we intentionally do not use the function that accepts an array of user IDs to get the mapping cause // this is simpler to use, and cause there shouldn't be that many userIds per email anyway io.supertokens.pluginInterface.useridmapping.UserIdMapping userIdMapping = UserIdMapping - .getUserIdMapping(appIdentifierWithStorage, users[i].id, UserIdType.SUPERTOKENS); + .getUserIdMapping(appIdentifierWithStorage, users[i].getSupertokensUserId(), UserIdType.SUPERTOKENS); if (userIdMapping != null) { users[i].setExternalUserId(userIdMapping.externalUserId); } else { diff --git a/src/main/java/io/supertokens/webserver/api/thirdparty/SignInUpAPI.java b/src/main/java/io/supertokens/webserver/api/thirdparty/SignInUpAPI.java index 1ea445584..449b63884 100644 --- a/src/main/java/io/supertokens/webserver/api/thirdparty/SignInUpAPI.java +++ b/src/main/java/io/supertokens/webserver/api/thirdparty/SignInUpAPI.java @@ -22,6 +22,7 @@ import io.supertokens.emailpassword.exceptions.EmailChangeNotAllowedException; import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.pluginInterface.RECIPE_ID; +import io.supertokens.pluginInterface.authRecipe.LoginMethod; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.thirdparty.ThirdParty; @@ -36,6 +37,7 @@ import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; +import java.util.Objects; public class SignInUpAPI extends WebserverAPI { @@ -77,7 +79,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I thirdPartyId, thirdPartyUserId, email, isEmailVerified); - ActiveUsers.updateLastActive(this.getAppIdentifierWithStorage(req), main, response.user.id); + ActiveUsers.updateLastActive(this.getAppIdentifierWithStorage(req), main, response.user.getSupertokensUserId()); JsonObject result = new JsonObject(); result.addProperty("status", "OK"); @@ -89,6 +91,16 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I } result.add("user", userJson); + if (getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v4_0)) { + for (LoginMethod loginMethod : response.user.loginMethods) { + if (loginMethod.recipeId.equals(RECIPE_ID.THIRD_PARTY) + && Objects.equals(loginMethod.thirdParty.id, thirdPartyId) + && Objects.equals(loginMethod.thirdParty.userId, thirdPartyUserId)) { + result.addProperty("recipeUserId", loginMethod.getSupertokensOrExternalUserId()); + break; + } + } + } super.sendJsonResponse(200, result, resp); } catch (StorageQueryException | TenantOrAppNotFoundException e) { @@ -117,11 +129,11 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I this.getTenantIdentifierWithStorageFromRequest(req), super.main, thirdPartyId, thirdPartyUserId, email); - ActiveUsers.updateLastActive(this.getAppIdentifierWithStorage(req), main, response.user.id); + ActiveUsers.updateLastActive(this.getAppIdentifierWithStorage(req), main, response.user.getSupertokensUserId()); // io.supertokens.pluginInterface.useridmapping.UserIdMapping userIdMapping = UserIdMapping - .getUserIdMapping(this.getAppIdentifierWithStorage(req), response.user.id, + .getUserIdMapping(this.getAppIdentifierWithStorage(req), response.user.getSupertokensUserId(), UserIdType.SUPERTOKENS); if (userIdMapping != null) { response.user.setExternalUserId(userIdMapping.externalUserId); @@ -141,6 +153,16 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I } result.add("user", userJson); + if (getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v4_0)) { + for (LoginMethod loginMethod : response.user.loginMethods) { + if (loginMethod.recipeId.equals(RECIPE_ID.THIRD_PARTY) + && Objects.equals(loginMethod.thirdParty.id, thirdPartyId) + && Objects.equals(loginMethod.thirdParty.userId, thirdPartyUserId)) { + result.addProperty("recipeUserId", loginMethod.getSupertokensOrExternalUserId()); + break; + } + } + } super.sendJsonResponse(200, result, resp); } catch (StorageQueryException | TenantOrAppNotFoundException | BadPermissionException e) { diff --git a/src/main/java/io/supertokens/webserver/api/thirdparty/UserAPI.java b/src/main/java/io/supertokens/webserver/api/thirdparty/UserAPI.java index 5a79a8c5c..689c36433 100644 --- a/src/main/java/io/supertokens/webserver/api/thirdparty/UserAPI.java +++ b/src/main/java/io/supertokens/webserver/api/thirdparty/UserAPI.java @@ -100,7 +100,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO thirdPartyUserId); if (user != null) { io.supertokens.pluginInterface.useridmapping.UserIdMapping userIdMapping = UserIdMapping - .getUserIdMapping(this.getAppIdentifierWithStorage(req), user.id, UserIdType.SUPERTOKENS); + .getUserIdMapping(this.getAppIdentifierWithStorage(req), user.getSupertokensUserId(), UserIdType.SUPERTOKENS); if (userIdMapping != null) { user.setExternalUserId(userIdMapping.externalUserId); } else { diff --git a/src/test/java/io/supertokens/test/AuthRecipeAPITest2_10.java b/src/test/java/io/supertokens/test/AuthRecipeAPITest2_10.java index acb2f0407..28940cd85 100644 --- a/src/test/java/io/supertokens/test/AuthRecipeAPITest2_10.java +++ b/src/test/java/io/supertokens/test/AuthRecipeAPITest2_10.java @@ -71,7 +71,7 @@ public void deleteUser() throws Exception { { JsonObject requestBody = new JsonObject(); - requestBody.addProperty("userId", user.id); + requestBody.addProperty("userId", user.getSupertokensUserId()); JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/user/remove", requestBody, 1000, 1000, null, @@ -80,7 +80,7 @@ public void deleteUser() throws Exception { assertEquals(response.entrySet().size(), 1); } - assertNull(EmailPassword.getUserUsingId(process.getProcess(), user.id)); + assertNull(EmailPassword.getUserUsingId(process.getProcess(), user.getSupertokensUserId())); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); diff --git a/src/test/java/io/supertokens/test/AuthRecipeTest.java b/src/test/java/io/supertokens/test/AuthRecipeTest.java index ca6ca95f5..f51640f3c 100644 --- a/src/test/java/io/supertokens/test/AuthRecipeTest.java +++ b/src/test/java/io/supertokens/test/AuthRecipeTest.java @@ -497,7 +497,7 @@ public void randomPaginationTest() throws Exception { if (o1.timeJoined != o2.timeJoined) { return (int) (o1.timeJoined - o2.timeJoined); } - return o2.id.compareTo(o1.id); + return o2.getSupertokensUserId().compareTo(o1.getSupertokensUserId()); }); // we make sure it's sorted properly.. @@ -537,7 +537,7 @@ public void randomPaginationTest() throws Exception { if (o1.timeJoined != o2.timeJoined) { return (int) (o1.timeJoined - o2.timeJoined); } - return o1.id.compareTo(o2.id); + return o1.getSupertokensUserId().compareTo(o2.getSupertokensUserId()); }); // we make sure it's sorted properly.. @@ -599,29 +599,29 @@ public void deleteUserTest() throws Exception { AuthRecipeUserInfo user1 = signUpMap.get(userType).apply(null); JsonObject testMetadata = new JsonObject(); testMetadata.addProperty("test", "test"); - UserMetadata.updateUserMetadata(process.getProcess(), user1.id, testMetadata); - Session.createNewSession(process.getProcess(), user1.id, new JsonObject(), new JsonObject()); + UserMetadata.updateUserMetadata(process.getProcess(), user1.getSupertokensUserId(), testMetadata); + Session.createNewSession(process.getProcess(), user1.getSupertokensUserId(), new JsonObject(), new JsonObject()); String emailVerificationToken = EmailVerification.generateEmailVerificationToken(process.getProcess(), - user1.id, "email"); + user1.getSupertokensUserId(), "email"); EmailVerification.verifyEmail(process.getProcess(), emailVerificationToken); AuthRecipeUserInfo user2 = signUpMap.get(userType).apply(null); - Session.createNewSession(process.getProcess(), user2.id, new JsonObject(), new JsonObject()); + Session.createNewSession(process.getProcess(), user2.getSupertokensUserId(), new JsonObject(), new JsonObject()); String emailVerificationToken2 = EmailVerification.generateEmailVerificationToken(process.getProcess(), - user2.id, "email"); + user2.getSupertokensUserId(), "email"); assertEquals(2, AuthRecipe.getUsersCount(process.getProcess(), new RECIPE_ID[]{user1.getRecipeId()})); - AuthRecipe.deleteUser(process.getProcess(), user1.id); + AuthRecipe.deleteUser(process.getProcess(), user1.getSupertokensUserId()); assertEquals(1, AuthRecipe.getUsersCount(process.getProcess(), new RECIPE_ID[]{user1.getRecipeId()})); - assertEquals(0, Session.getAllNonExpiredSessionHandlesForUser(process.getProcess(), user1.id).length); - assertEquals(1, Session.getAllNonExpiredSessionHandlesForUser(process.getProcess(), user2.id).length); - assertFalse(EmailVerification.isEmailVerified(process.getProcess(), user1.id, "email")); - assertEquals(0, UserMetadata.getUserMetadata(process.getProcess(), user1.id).entrySet().size()); + assertEquals(0, Session.getAllNonExpiredSessionHandlesForUser(process.getProcess(), user1.getSupertokensUserId()).length); + assertEquals(1, Session.getAllNonExpiredSessionHandlesForUser(process.getProcess(), user2.getSupertokensUserId()).length); + assertFalse(EmailVerification.isEmailVerified(process.getProcess(), user1.getSupertokensUserId(), "email")); + assertEquals(0, UserMetadata.getUserMetadata(process.getProcess(), user1.getSupertokensUserId()).entrySet().size()); - AuthRecipe.deleteUser(process.getProcess(), user2.id); + AuthRecipe.deleteUser(process.getProcess(), user2.getSupertokensUserId()); assertEquals(0, AuthRecipe.getUsersCount(process.getProcess(), new RECIPE_ID[]{user1.getRecipeId()})); - assertEquals(0, Session.getAllNonExpiredSessionHandlesForUser(process.getProcess(), user2.id).length); - assertEquals(0, UserMetadata.getUserMetadata(process.getProcess(), user2.id).entrySet().size()); + assertEquals(0, Session.getAllNonExpiredSessionHandlesForUser(process.getProcess(), user2.getSupertokensUserId()).length); + assertEquals(0, UserMetadata.getUserMetadata(process.getProcess(), user2.getSupertokensUserId()).entrySet().size()); Exception error = null; try { diff --git a/src/test/java/io/supertokens/test/SuperTokensSaaSSecretTest.java b/src/test/java/io/supertokens/test/SuperTokensSaaSSecretTest.java index 4da18541d..0f20483d3 100644 --- a/src/test/java/io/supertokens/test/SuperTokensSaaSSecretTest.java +++ b/src/test/java/io/supertokens/test/SuperTokensSaaSSecretTest.java @@ -209,7 +209,7 @@ public void testCreatingSessionWithAndWithoutAPIKey() throws Exception { JsonObject sessionInfo = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/recipe/session", request, 1000, 1000, null, - Utils.getCdiVersionStringLatestForTests(), + SemVer.v3_0.get(), apiKey, ""); assertEquals(sessionInfo.get("status").getAsString(), "OK"); checkSessionResponse(sessionInfo, process, userId, userDataInJWT); @@ -296,7 +296,7 @@ public void testCreatingSessionWithAndWithoutAPIKeyWhenSuperTokensSaaSSecretIsAl { JsonObject sessionInfo = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/recipe/session", request, 1000, 1000, null, - Utils.getCdiVersionStringLatestForTests(), + SemVer.v3_0.get(), apiKey, ""); assertEquals(sessionInfo.get("status").getAsString(), "OK"); checkSessionResponse(sessionInfo, process, userId, userDataInJWT); @@ -305,7 +305,7 @@ public void testCreatingSessionWithAndWithoutAPIKeyWhenSuperTokensSaaSSecretIsAl { JsonObject sessionInfo = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/recipe/session", request, 1000, 1000, null, - Utils.getCdiVersionStringLatestForTests(), + SemVer.v3_0.get(), saasSecret, ""); assertEquals(sessionInfo.get("status").getAsString(), "OK"); checkSessionResponse(sessionInfo, process, userId, userDataInJWT); diff --git a/src/test/java/io/supertokens/test/accountlinking/CreatePrimaryUserTest.java b/src/test/java/io/supertokens/test/accountlinking/CreatePrimaryUserTest.java index d728792b3..f46cd3d3e 100644 --- a/src/test/java/io/supertokens/test/accountlinking/CreatePrimaryUserTest.java +++ b/src/test/java/io/supertokens/test/accountlinking/CreatePrimaryUserTest.java @@ -76,7 +76,7 @@ public void testThatOnSignUpUserIsNotAPrimaryUser() throws Exception { assert (user.loginMethods[0].email.equals("test@example.com")); assert (user.loginMethods[0].passwordHash != null); assert (user.loginMethods[0].thirdParty == null); - assert (user.id.equals(user.loginMethods[0].recipeUserId)); + assert (user.getSupertokensUserId().equals(user.loginMethods[0].getSupertokensUserId())); assert (user.loginMethods[0].phoneNumber == null); ThirdParty.SignInUpResponse resp = ThirdParty.signInUp(process.getProcess(), "google", "user-google", @@ -89,7 +89,7 @@ public void testThatOnSignUpUserIsNotAPrimaryUser() throws Exception { assert (resp.user.loginMethods[0].thirdParty.id.equals("google")); assert (resp.user.loginMethods[0].phoneNumber == null); assert (resp.user.loginMethods[0].passwordHash == null); - assert (resp.user.id.equals(resp.user.loginMethods[0].recipeUserId)); + assert (resp.user.getSupertokensUserId().equals(resp.user.loginMethods[0].getSupertokensUserId())); { Passwordless.CreateCodeResponse code = Passwordless.createCode(process.getProcess(), "u@e.com", null, null, @@ -103,7 +103,7 @@ public void testThatOnSignUpUserIsNotAPrimaryUser() throws Exception { assert (pResp.user.loginMethods[0].passwordHash == null); assert (pResp.user.loginMethods[0].thirdParty == null); assert (pResp.user.loginMethods[0].phoneNumber == null); - assert (pResp.user.id.equals(pResp.user.loginMethods[0].recipeUserId)); + assert (pResp.user.getSupertokensUserId().equals(pResp.user.loginMethods[0].getSupertokensUserId())); } { @@ -118,7 +118,7 @@ public void testThatOnSignUpUserIsNotAPrimaryUser() throws Exception { assert (pResp.user.loginMethods[0].passwordHash == null); assert (pResp.user.loginMethods[0].thirdParty == null); assert (pResp.user.loginMethods[0].phoneNumber.equals("12345")); - assert (pResp.user.id.equals(pResp.user.loginMethods[0].recipeUserId)); + assert (pResp.user.getSupertokensUserId().equals(pResp.user.loginMethods[0].getSupertokensUserId())); } process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -157,7 +157,7 @@ public void makeEmailPasswordPrimaryUserSuccess() throws Exception { AuthRecipeUserInfo emailPasswordUser = EmailPassword.signUp(process.getProcess(), "test@example.com", "pass1234"); - AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.createPrimaryUser(process.main, emailPasswordUser.id); + AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.createPrimaryUser(process.main, emailPasswordUser.getSupertokensUserId()); assert (!result.wasAlreadyAPrimaryUser); assert (result.user.isPrimaryUser); assert (result.user.loginMethods.length == 1); @@ -165,10 +165,10 @@ public void makeEmailPasswordPrimaryUserSuccess() throws Exception { assert (result.user.loginMethods[0].email.equals("test@example.com")); assert (result.user.loginMethods[0].passwordHash != null); assert (result.user.loginMethods[0].thirdParty == null); - assert (result.user.id.equals(result.user.loginMethods[0].recipeUserId)); + assert (result.user.getSupertokensUserId().equals(result.user.loginMethods[0].getSupertokensUserId())); assert (result.user.loginMethods[0].phoneNumber == null); - AuthRecipeUserInfo refetchedUser = AuthRecipe.getUserById(process.main, result.user.id); + AuthRecipeUserInfo refetchedUser = AuthRecipe.getUserById(process.main, result.user.getSupertokensUserId()); assert (refetchedUser.equals(result.user)); @@ -191,7 +191,7 @@ public void makeThirdPartyPrimaryUserSuccess() throws Exception { "test@example.com"); AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.createPrimaryUser(process.main, - signInUp.user.id); + signInUp.user.getSupertokensUserId()); assert (!result.wasAlreadyAPrimaryUser); assert (result.user.isPrimaryUser); assert (result.user.loginMethods.length == 1); @@ -201,9 +201,9 @@ public void makeThirdPartyPrimaryUserSuccess() throws Exception { assert (result.user.loginMethods[0].thirdParty.id.equals("google")); assert (result.user.loginMethods[0].phoneNumber == null); assert (result.user.loginMethods[0].passwordHash == null); - assert (result.user.id.equals(result.user.loginMethods[0].recipeUserId)); + assert (result.user.getSupertokensUserId().equals(result.user.loginMethods[0].getSupertokensUserId())); - AuthRecipeUserInfo refetchedUser = AuthRecipe.getUserById(process.main, result.user.id); + AuthRecipeUserInfo refetchedUser = AuthRecipe.getUserById(process.main, result.user.getSupertokensUserId()); assert (refetchedUser.equals(result.user)); @@ -227,7 +227,7 @@ public void makePasswordlessEmailPrimaryUserSuccess() throws Exception { code.deviceIdHash, code.userInputCode, null); AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.createPrimaryUser(process.main, - pResp.user.id); + pResp.user.getSupertokensUserId()); assert (!result.wasAlreadyAPrimaryUser); assert (result.user.isPrimaryUser); assert (result.user.loginMethods.length == 1); @@ -236,9 +236,9 @@ public void makePasswordlessEmailPrimaryUserSuccess() throws Exception { assert (result.user.loginMethods[0].passwordHash == null); assert (result.user.loginMethods[0].thirdParty == null); assert (result.user.loginMethods[0].phoneNumber == null); - assert (result.user.id.equals(result.user.loginMethods[0].recipeUserId)); + assert (result.user.getSupertokensUserId().equals(result.user.loginMethods[0].getSupertokensUserId())); - AuthRecipeUserInfo refetchedUser = AuthRecipe.getUserById(process.main, result.user.id); + AuthRecipeUserInfo refetchedUser = AuthRecipe.getUserById(process.main, result.user.getSupertokensUserId()); assert (refetchedUser.equals(result.user)); @@ -262,7 +262,7 @@ public void makePasswordlessPhonePrimaryUserSuccess() throws Exception { code.deviceIdHash, code.userInputCode, null); AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.createPrimaryUser(process.main, - pResp.user.id); + pResp.user.getSupertokensUserId()); assert (!result.wasAlreadyAPrimaryUser); assert (result.user.isPrimaryUser); assert (result.user.loginMethods.length == 1); @@ -271,9 +271,9 @@ public void makePasswordlessPhonePrimaryUserSuccess() throws Exception { assert (result.user.loginMethods[0].passwordHash == null); assert (result.user.loginMethods[0].thirdParty == null); assert (result.user.loginMethods[0].phoneNumber.equals("1234")); - assert (result.user.id.equals(result.user.loginMethods[0].recipeUserId)); + assert (result.user.getSupertokensUserId().equals(result.user.loginMethods[0].getSupertokensUserId())); - AuthRecipeUserInfo refetchedUser = AuthRecipe.getUserById(process.main, result.user.id); + AuthRecipeUserInfo refetchedUser = AuthRecipe.getUserById(process.main, result.user.getSupertokensUserId()); assert (refetchedUser.equals(result.user)); @@ -294,22 +294,22 @@ public void alreadyPrimaryUsertest() throws Exception { AuthRecipeUserInfo emailPasswordUser = EmailPassword.signUp(process.getProcess(), "test@example.com", "pass1234"); - AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.createPrimaryUser(process.main, emailPasswordUser.id); + AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.createPrimaryUser(process.main, emailPasswordUser.getSupertokensUserId()); assert (!result.wasAlreadyAPrimaryUser); - result = AuthRecipe.createPrimaryUser(process.main, emailPasswordUser.id); + result = AuthRecipe.createPrimaryUser(process.main, emailPasswordUser.getSupertokensUserId()); assert (result.wasAlreadyAPrimaryUser); - assert (result.user.id.equals(emailPasswordUser.id)); + assert (result.user.getSupertokensUserId().equals(emailPasswordUser.getSupertokensUserId())); assert (result.user.isPrimaryUser); assert (result.user.loginMethods.length == 1); assert (result.user.loginMethods[0].recipeId == RECIPE_ID.EMAIL_PASSWORD); assert (result.user.loginMethods[0].email.equals("test@example.com")); assert (result.user.loginMethods[0].passwordHash != null); assert (result.user.loginMethods[0].thirdParty == null); - assert (result.user.id.equals(result.user.loginMethods[0].recipeUserId)); + assert (result.user.getSupertokensUserId().equals(result.user.loginMethods[0].getSupertokensUserId())); assert (result.user.loginMethods[0].phoneNumber == null); - AuthRecipeUserInfo refetchedUser = AuthRecipe.getUserById(process.main, result.user.id); + AuthRecipeUserInfo refetchedUser = AuthRecipe.getUserById(process.main, result.user.getSupertokensUserId()); assert (refetchedUser.equals(result.user)); @@ -330,17 +330,17 @@ public void makePrimaryUserFailsCauseAnotherAccountWithSameEmailAlreadyAPrimaryU AuthRecipeUserInfo emailPasswordUser = EmailPassword.signUp(process.getProcess(), "test@example.com", "pass1234"); - AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.createPrimaryUser(process.main, emailPasswordUser.id); + AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.createPrimaryUser(process.main, emailPasswordUser.getSupertokensUserId()); assert (!result.wasAlreadyAPrimaryUser); ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(process.main, "google", "user-google", "test@example.com"); try { - AuthRecipe.createPrimaryUser(process.main, signInUpResponse.user.id); + AuthRecipe.createPrimaryUser(process.main, signInUpResponse.user.getSupertokensUserId()); assert (false); } catch (AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException e) { - assert (e.primaryUserId.equals(emailPasswordUser.id)); + assert (e.primaryUserId.equals(emailPasswordUser.getSupertokensUserId())); assert (e.getMessage().equals("This user's email is already associated with another user ID")); } @@ -364,7 +364,7 @@ public void makePrimarySucceedsEvenIfAnotherAccountWithSameEmailButIsNotAPrimary ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(process.main, "google", "user-google", "test@example.com"); - AuthRecipe.CreatePrimaryUserResult r = AuthRecipe.createPrimaryUser(process.main, signInUpResponse.user.id); + AuthRecipe.CreatePrimaryUserResult r = AuthRecipe.createPrimaryUser(process.main, signInUpResponse.user.getSupertokensUserId()); assert (!r.wasAlreadyAPrimaryUser); process.kill(); @@ -393,19 +393,19 @@ public void makePrimaryUserFailsCauseAnotherAccountWithSameEmailAlreadyAPrimaryU "test@example.com", "pass1234"); - AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.createPrimaryUser(process.main, emailPasswordUser.id); + AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.createPrimaryUser(process.main, emailPasswordUser.getSupertokensUserId()); assert (!result.wasAlreadyAPrimaryUser); ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(process.main, "google", "user-google", "test@example.com"); - Multitenancy.addUserIdToTenant(process.main, tenantIdentifierWithStorage, signInUpResponse.user.id); + Multitenancy.addUserIdToTenant(process.main, tenantIdentifierWithStorage, signInUpResponse.user.getSupertokensUserId()); try { - AuthRecipe.createPrimaryUser(process.main, signInUpResponse.user.id); + AuthRecipe.createPrimaryUser(process.main, signInUpResponse.user.getSupertokensUserId()); assert (false); } catch (AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException e) { - assert (e.primaryUserId.equals(emailPasswordUser.id)); + assert (e.primaryUserId.equals(emailPasswordUser.getSupertokensUserId())); assert (e.getMessage().equals("This user's email is already associated with another user ID")); } @@ -435,13 +435,13 @@ public void makePrimarySucceedsEvenIfAnotherAccountWithSameEmailButInADifferentT "test@example.com", "pass1234"); - AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.createPrimaryUser(process.main, emailPasswordUser.id); + AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.createPrimaryUser(process.main, emailPasswordUser.getSupertokensUserId()); assert (!result.wasAlreadyAPrimaryUser); ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(process.main, "google", "user-google", "test@example.com"); - AuthRecipe.CreatePrimaryUserResult r = AuthRecipe.createPrimaryUser(process.main, signInUpResponse.user.id); + AuthRecipe.CreatePrimaryUserResult r = AuthRecipe.createPrimaryUser(process.main, signInUpResponse.user.getSupertokensUserId()); assert !r.wasAlreadyAPrimaryUser; process.kill(); @@ -483,14 +483,14 @@ public void makingPrimaryUserFailsCauseAlreadyLinkedToAnotherAccount() throws Ex AuthRecipeUserInfo emailPasswordUser2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "pass1234"); - AuthRecipe.createPrimaryUser(process.main, emailPasswordUser1.id); - AuthRecipe.linkAccounts(process.main, emailPasswordUser2.id, emailPasswordUser1.id); + AuthRecipe.createPrimaryUser(process.main, emailPasswordUser1.getSupertokensUserId()); + AuthRecipe.linkAccounts(process.main, emailPasswordUser2.getSupertokensUserId(), emailPasswordUser1.getSupertokensUserId()); try { - AuthRecipe.createPrimaryUser(process.main, emailPasswordUser2.id); + AuthRecipe.createPrimaryUser(process.main, emailPasswordUser2.getSupertokensUserId()); assert (false); } catch (RecipeUserIdAlreadyLinkedWithPrimaryUserIdException e) { - assert (e.primaryUserId.equals(emailPasswordUser1.id)); + assert (e.primaryUserId.equals(emailPasswordUser1.getSupertokensUserId())); assert (e.getMessage().equals("This user ID is already linked to another user ID")); } diff --git a/src/test/java/io/supertokens/test/accountlinking/DeleteUserTest.java b/src/test/java/io/supertokens/test/accountlinking/DeleteUserTest.java index ae67c3db7..40593201b 100644 --- a/src/test/java/io/supertokens/test/accountlinking/DeleteUserTest.java +++ b/src/test/java/io/supertokens/test/accountlinking/DeleteUserTest.java @@ -71,20 +71,20 @@ public void deleteLinkedUserWithoutRemovingAllUsers() throws Exception { AuthRecipeUserInfo r2 = EmailPassword.signUp(process.main, "test2@example.com", "pass123"); - AuthRecipe.createPrimaryUser(process.main, r2.id); + AuthRecipe.createPrimaryUser(process.main, r2.getSupertokensUserId()); - assert (!AuthRecipe.linkAccounts(process.main, r1.id, r2.id)); + assert (!AuthRecipe.linkAccounts(process.main, r1.getSupertokensUserId(), r2.getSupertokensUserId())); - AuthRecipe.deleteUser(process.main, r1.id, false); + AuthRecipe.deleteUser(process.main, r1.getSupertokensUserId(), false); - assertNull(AuthRecipe.getUserById(process.main, r1.id)); + assertNull(AuthRecipe.getUserById(process.main, r1.getSupertokensUserId())); - AuthRecipeUserInfo user = AuthRecipe.getUserById(process.main, r2.id); + AuthRecipeUserInfo user = AuthRecipe.getUserById(process.main, r2.getSupertokensUserId()); assert (user.loginMethods.length == 1); assert (user.isPrimaryUser); - assert (user.id.equals(r2.id)); - assert (user.loginMethods[0].recipeUserId.equals(r2.id)); + assert (user.getSupertokensUserId().equals(r2.getSupertokensUserId())); + assert (user.loginMethods[0].getSupertokensUserId().equals(r2.getSupertokensUserId())); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -108,20 +108,20 @@ public void deleteLinkedPrimaryUserWithoutRemovingAllUsers() throws Exception { AuthRecipeUserInfo r2 = EmailPassword.signUp(process.main, "test2@example.com", "pass123"); - AuthRecipe.createPrimaryUser(process.main, r2.id); + AuthRecipe.createPrimaryUser(process.main, r2.getSupertokensUserId()); - assert (!AuthRecipe.linkAccounts(process.main, r1.id, r2.id)); + assert (!AuthRecipe.linkAccounts(process.main, r1.getSupertokensUserId(), r2.getSupertokensUserId())); - AuthRecipe.deleteUser(process.main, r2.id, false); + AuthRecipe.deleteUser(process.main, r2.getSupertokensUserId(), false); - AuthRecipeUserInfo userP = AuthRecipe.getUserById(process.main, r2.id); + AuthRecipeUserInfo userP = AuthRecipe.getUserById(process.main, r2.getSupertokensUserId()); - AuthRecipeUserInfo user = AuthRecipe.getUserById(process.main, r1.id); + AuthRecipeUserInfo user = AuthRecipe.getUserById(process.main, r1.getSupertokensUserId()); assert (user.loginMethods.length == 1); assert (user.isPrimaryUser); - assert (user.id.equals(r2.id)); - assert (user.loginMethods[0].recipeUserId.equals(r1.id)); + assert (user.getSupertokensUserId().equals(r2.getSupertokensUserId())); + assert (user.loginMethods[0].getSupertokensUserId().equals(r1.getSupertokensUserId())); assert (userP.equals(user)); process.kill(); @@ -146,15 +146,15 @@ public void deleteLinkedPrimaryUserRemovingAllUsers() throws Exception { AuthRecipeUserInfo r2 = EmailPassword.signUp(process.main, "test2@example.com", "pass123"); - AuthRecipe.createPrimaryUser(process.main, r2.id); + AuthRecipe.createPrimaryUser(process.main, r2.getSupertokensUserId()); - assert (!AuthRecipe.linkAccounts(process.main, r1.id, r2.id)); + assert (!AuthRecipe.linkAccounts(process.main, r1.getSupertokensUserId(), r2.getSupertokensUserId())); - AuthRecipe.deleteUser(process.main, r2.id); + AuthRecipe.deleteUser(process.main, r2.getSupertokensUserId()); - AuthRecipeUserInfo userP = AuthRecipe.getUserById(process.main, r2.id); + AuthRecipeUserInfo userP = AuthRecipe.getUserById(process.main, r2.getSupertokensUserId()); - AuthRecipeUserInfo user = AuthRecipe.getUserById(process.main, r1.id); + AuthRecipeUserInfo user = AuthRecipe.getUserById(process.main, r1.getSupertokensUserId()); assert (user == null && userP == null); @@ -181,15 +181,15 @@ public void deleteLinkedPrimaryUserRemovingAllUsers2() throws Exception { AuthRecipeUserInfo r2 = EmailPassword.signUp(process.main, "test2@example.com", "pass123"); - AuthRecipe.createPrimaryUser(process.main, r2.id); + AuthRecipe.createPrimaryUser(process.main, r2.getSupertokensUserId()); - assert (!AuthRecipe.linkAccounts(process.main, r1.id, r2.id)); + assert (!AuthRecipe.linkAccounts(process.main, r1.getSupertokensUserId(), r2.getSupertokensUserId())); - AuthRecipe.deleteUser(process.main, r1.id); + AuthRecipe.deleteUser(process.main, r1.getSupertokensUserId()); - AuthRecipeUserInfo userP = AuthRecipe.getUserById(process.main, r2.id); + AuthRecipeUserInfo userP = AuthRecipe.getUserById(process.main, r2.getSupertokensUserId()); - AuthRecipeUserInfo user = AuthRecipe.getUserById(process.main, r1.id); + AuthRecipeUserInfo user = AuthRecipe.getUserById(process.main, r1.getSupertokensUserId()); assert (user == null && userP == null); @@ -217,33 +217,33 @@ public void deleteUserTestWithUserIdMapping1() throws Exception { } AuthRecipeUserInfo r1 = EmailPassword.signUp(process.main, "test@example.com", "pass123"); - UserIdMapping.createUserIdMapping(process.main, r1.id, "e1", null, false); + UserIdMapping.createUserIdMapping(process.main, r1.getSupertokensUserId(), "e1", null, false); JsonObject metadata = new JsonObject(); metadata.addProperty("k1", "v1"); UserMetadata.updateUserMetadata(process.main, "e1", metadata); AuthRecipeUserInfo r2 = EmailPassword.signUp(process.main, "test2@example.com", "pass123"); - UserIdMapping.createUserIdMapping(process.main, r2.id, "e2", null, false); + UserIdMapping.createUserIdMapping(process.main, r2.getSupertokensUserId(), "e2", null, false); UserMetadata.updateUserMetadata(process.main, "e2", metadata); - AuthRecipe.createPrimaryUser(process.main, r2.id); + AuthRecipe.createPrimaryUser(process.main, r2.getSupertokensUserId()); - assert (!AuthRecipe.linkAccounts(process.main, r1.id, r2.id)); + assert (!AuthRecipe.linkAccounts(process.main, r1.getSupertokensUserId(), r2.getSupertokensUserId())); - AuthRecipe.deleteUser(process.main, r1.id, false); + AuthRecipe.deleteUser(process.main, r1.getSupertokensUserId(), false); - assertNull(AuthRecipe.getUserById(process.main, r1.id)); + assertNull(AuthRecipe.getUserById(process.main, r1.getSupertokensUserId())); assertNull(AuthRecipe.getUserById(process.main, "e2")); - assertNotNull(AuthRecipe.getUserById(process.main, r2.id)); + assertNotNull(AuthRecipe.getUserById(process.main, r2.getSupertokensUserId())); assertEquals(UserMetadata.getUserMetadata(process.main, "e1"), new JsonObject()); - assertEquals(UserMetadata.getUserMetadata(process.main, r1.id), new JsonObject()); + assertEquals(UserMetadata.getUserMetadata(process.main, r1.getSupertokensUserId()), new JsonObject()); assertEquals(UserMetadata.getUserMetadata(process.main, "e2"), metadata); - assertEquals(UserMetadata.getUserMetadata(process.main, r2.id), new JsonObject()); - assert (UserIdMapping.getUserIdMapping(process.main, r2.id, UserIdType.SUPERTOKENS) != null); - assert (UserIdMapping.getUserIdMapping(process.main, r1.id, UserIdType.SUPERTOKENS) == null); + assertEquals(UserMetadata.getUserMetadata(process.main, r2.getSupertokensUserId()), new JsonObject()); + assert (UserIdMapping.getUserIdMapping(process.main, r2.getSupertokensUserId(), UserIdType.SUPERTOKENS) != null); + assert (UserIdMapping.getUserIdMapping(process.main, r1.getSupertokensUserId(), UserIdType.SUPERTOKENS) == null); process.kill(); @@ -269,33 +269,33 @@ public void deleteUserTestWithUserIdMapping2() throws Exception { } AuthRecipeUserInfo r1 = EmailPassword.signUp(process.main, "test@example.com", "pass123"); - UserIdMapping.createUserIdMapping(process.main, r1.id, "e1", null, false); + UserIdMapping.createUserIdMapping(process.main, r1.getSupertokensUserId(), "e1", null, false); JsonObject metadata = new JsonObject(); metadata.addProperty("k1", "v1"); UserMetadata.updateUserMetadata(process.main, "e1", metadata); AuthRecipeUserInfo r2 = EmailPassword.signUp(process.main, "test2@example.com", "pass123"); - UserIdMapping.createUserIdMapping(process.main, r2.id, "e2", null, false); + UserIdMapping.createUserIdMapping(process.main, r2.getSupertokensUserId(), "e2", null, false); UserMetadata.updateUserMetadata(process.main, "e2", metadata); - AuthRecipe.createPrimaryUser(process.main, r2.id); + AuthRecipe.createPrimaryUser(process.main, r2.getSupertokensUserId()); - assert (!AuthRecipe.linkAccounts(process.main, r1.id, r2.id)); + assert (!AuthRecipe.linkAccounts(process.main, r1.getSupertokensUserId(), r2.getSupertokensUserId())); - AuthRecipe.deleteUser(process.main, r1.id); + AuthRecipe.deleteUser(process.main, r1.getSupertokensUserId()); - assertNull(AuthRecipe.getUserById(process.main, r1.id)); + assertNull(AuthRecipe.getUserById(process.main, r1.getSupertokensUserId())); assertNull(AuthRecipe.getUserById(process.main, "e2")); - assertNull(AuthRecipe.getUserById(process.main, r2.id)); + assertNull(AuthRecipe.getUserById(process.main, r2.getSupertokensUserId())); assertEquals(UserMetadata.getUserMetadata(process.main, "e1"), new JsonObject()); - assertEquals(UserMetadata.getUserMetadata(process.main, r1.id), new JsonObject()); + assertEquals(UserMetadata.getUserMetadata(process.main, r1.getSupertokensUserId()), new JsonObject()); assertEquals(UserMetadata.getUserMetadata(process.main, "e2"), new JsonObject()); - assertEquals(UserMetadata.getUserMetadata(process.main, r2.id), new JsonObject()); - assert (UserIdMapping.getUserIdMapping(process.main, r2.id, UserIdType.SUPERTOKENS) == null); - assert (UserIdMapping.getUserIdMapping(process.main, r1.id, UserIdType.SUPERTOKENS) == null); + assertEquals(UserMetadata.getUserMetadata(process.main, r2.getSupertokensUserId()), new JsonObject()); + assert (UserIdMapping.getUserIdMapping(process.main, r2.getSupertokensUserId(), UserIdType.SUPERTOKENS) == null); + assert (UserIdMapping.getUserIdMapping(process.main, r1.getSupertokensUserId(), UserIdType.SUPERTOKENS) == null); process.kill(); @@ -323,68 +323,68 @@ public void deleteUserTestWithUserIdMapping3() throws Exception { } AuthRecipeUserInfo r1 = EmailPassword.signUp(process.main, "test@example.com", "pass123"); - UserIdMapping.createUserIdMapping(process.main, r1.id, "e1", null, false); + UserIdMapping.createUserIdMapping(process.main, r1.getSupertokensUserId(), "e1", null, false); JsonObject metadata = new JsonObject(); metadata.addProperty("k1", "v1"); UserMetadata.updateUserMetadata(process.main, "e1", metadata); AuthRecipeUserInfo r2 = EmailPassword.signUp(process.main, "test2@example.com", "pass123"); - UserIdMapping.createUserIdMapping(process.main, r2.id, "e2", null, false); + UserIdMapping.createUserIdMapping(process.main, r2.getSupertokensUserId(), "e2", null, false); UserMetadata.updateUserMetadata(process.main, "e2", metadata); AuthRecipeUserInfo r3 = EmailPassword.signUp(process.main, "test3@example.com", "pass123"); - UserIdMapping.createUserIdMapping(process.main, r3.id, "e3", null, false); + UserIdMapping.createUserIdMapping(process.main, r3.getSupertokensUserId(), "e3", null, false); UserMetadata.updateUserMetadata(process.main, "e3", metadata); - AuthRecipe.createPrimaryUser(process.main, r2.id); + AuthRecipe.createPrimaryUser(process.main, r2.getSupertokensUserId()); - assert (!AuthRecipe.linkAccounts(process.main, r1.id, r2.id)); - assert (!AuthRecipe.linkAccounts(process.main, r3.id, r1.id)); + assert (!AuthRecipe.linkAccounts(process.main, r1.getSupertokensUserId(), r2.getSupertokensUserId())); + assert (!AuthRecipe.linkAccounts(process.main, r3.getSupertokensUserId(), r1.getSupertokensUserId())); - AuthRecipe.deleteUser(process.main, r1.id, false); + AuthRecipe.deleteUser(process.main, r1.getSupertokensUserId(), false); - assertNull(AuthRecipe.getUserById(process.main, r1.id)); + assertNull(AuthRecipe.getUserById(process.main, r1.getSupertokensUserId())); assertEquals(UserMetadata.getUserMetadata(process.main, "e1"), new JsonObject()); - assertEquals(UserMetadata.getUserMetadata(process.main, r1.id), new JsonObject()); + assertEquals(UserMetadata.getUserMetadata(process.main, r1.getSupertokensUserId()), new JsonObject()); { - AuthRecipeUserInfo userR2 = AuthRecipe.getUserById(process.main, r2.id); - AuthRecipeUserInfo userR3 = AuthRecipe.getUserById(process.main, r3.id); + AuthRecipeUserInfo userR2 = AuthRecipe.getUserById(process.main, r2.getSupertokensUserId()); + AuthRecipeUserInfo userR3 = AuthRecipe.getUserById(process.main, r3.getSupertokensUserId()); assert (userR2.equals(userR3)); assert (userR2.loginMethods.length == 2); assertEquals(UserMetadata.getUserMetadata(process.main, "e2"), metadata); assertEquals(UserMetadata.getUserMetadata(process.main, "e3"), metadata); - assert (UserIdMapping.getUserIdMapping(process.main, r2.id, UserIdType.SUPERTOKENS) != null); - assert (UserIdMapping.getUserIdMapping(process.main, r3.id, UserIdType.SUPERTOKENS) != null); - assert (UserIdMapping.getUserIdMapping(process.main, r1.id, UserIdType.SUPERTOKENS) == null); + assert (UserIdMapping.getUserIdMapping(process.main, r2.getSupertokensUserId(), UserIdType.SUPERTOKENS) != null); + assert (UserIdMapping.getUserIdMapping(process.main, r3.getSupertokensUserId(), UserIdType.SUPERTOKENS) != null); + assert (UserIdMapping.getUserIdMapping(process.main, r1.getSupertokensUserId(), UserIdType.SUPERTOKENS) == null); } - AuthRecipe.deleteUser(process.main, r2.id, false); + AuthRecipe.deleteUser(process.main, r2.getSupertokensUserId(), false); { - AuthRecipeUserInfo userR2 = AuthRecipe.getUserById(process.main, r2.id); - AuthRecipeUserInfo userR3 = AuthRecipe.getUserById(process.main, r3.id); + AuthRecipeUserInfo userR2 = AuthRecipe.getUserById(process.main, r2.getSupertokensUserId()); + AuthRecipeUserInfo userR3 = AuthRecipe.getUserById(process.main, r3.getSupertokensUserId()); assert (userR2.equals(userR3)); assert (userR2.loginMethods.length == 1); assertEquals(UserMetadata.getUserMetadata(process.main, "e2"), metadata); assertEquals(UserMetadata.getUserMetadata(process.main, "e3"), metadata); - assert (UserIdMapping.getUserIdMapping(process.main, r2.id, UserIdType.SUPERTOKENS) != null); - assert (UserIdMapping.getUserIdMapping(process.main, r3.id, UserIdType.SUPERTOKENS) != null); - assert (UserIdMapping.getUserIdMapping(process.main, r1.id, UserIdType.SUPERTOKENS) == null); + assert (UserIdMapping.getUserIdMapping(process.main, r2.getSupertokensUserId(), UserIdType.SUPERTOKENS) != null); + assert (UserIdMapping.getUserIdMapping(process.main, r3.getSupertokensUserId(), UserIdType.SUPERTOKENS) != null); + assert (UserIdMapping.getUserIdMapping(process.main, r1.getSupertokensUserId(), UserIdType.SUPERTOKENS) == null); } - AuthRecipe.deleteUser(process.main, r3.id, false); + AuthRecipe.deleteUser(process.main, r3.getSupertokensUserId(), false); { - AuthRecipeUserInfo userR2 = AuthRecipe.getUserById(process.main, r2.id); - AuthRecipeUserInfo userR3 = AuthRecipe.getUserById(process.main, r3.id); + AuthRecipeUserInfo userR2 = AuthRecipe.getUserById(process.main, r2.getSupertokensUserId()); + AuthRecipeUserInfo userR3 = AuthRecipe.getUserById(process.main, r3.getSupertokensUserId()); assert (userR2 == null && userR3 == null); assertEquals(UserMetadata.getUserMetadata(process.main, "e2"), new JsonObject()); assertEquals(UserMetadata.getUserMetadata(process.main, "e3"), new JsonObject()); - assert (UserIdMapping.getUserIdMapping(process.main, r2.id, UserIdType.SUPERTOKENS) == null); - assert (UserIdMapping.getUserIdMapping(process.main, r3.id, UserIdType.SUPERTOKENS) == null); - assert (UserIdMapping.getUserIdMapping(process.main, r1.id, UserIdType.SUPERTOKENS) == null); + assert (UserIdMapping.getUserIdMapping(process.main, r2.getSupertokensUserId(), UserIdType.SUPERTOKENS) == null); + assert (UserIdMapping.getUserIdMapping(process.main, r3.getSupertokensUserId(), UserIdType.SUPERTOKENS) == null); + assert (UserIdMapping.getUserIdMapping(process.main, r1.getSupertokensUserId(), UserIdType.SUPERTOKENS) == null); } process.kill(); diff --git a/src/test/java/io/supertokens/test/accountlinking/GetUserByAccountInfoTest.java b/src/test/java/io/supertokens/test/accountlinking/GetUserByAccountInfoTest.java index 8563b920d..0168677e5 100644 --- a/src/test/java/io/supertokens/test/accountlinking/GetUserByAccountInfoTest.java +++ b/src/test/java/io/supertokens/test/accountlinking/GetUserByAccountInfoTest.java @@ -113,12 +113,12 @@ public void testListUsersByAccountInfoForUnlinkedAccounts() throws Exception { AuthRecipeUserInfo userToTest = AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, "test1@example.com", null, null, null)[0]; - assertNotNull(userToTest.id); + assertNotNull(userToTest.getSupertokensUserId()); assertFalse(userToTest.isPrimaryUser); assertEquals(1, userToTest.loginMethods.length); assertEquals("test1@example.com", userToTest.loginMethods[0].email); assertEquals(RECIPE_ID.EMAIL_PASSWORD, userToTest.loginMethods[0].recipeId); - assertEquals(user1.id, userToTest.loginMethods[0].recipeUserId); + assertEquals(user1.getSupertokensUserId(), userToTest.loginMethods[0].getSupertokensUserId()); assertFalse(userToTest.loginMethods[0].verified); assert(userToTest.loginMethods[0].timeJoined > 0); @@ -221,13 +221,13 @@ public void testListUserByAccountInfoWhenAccountsAreLinked1() throws Exception { Thread.sleep(50); AuthRecipeUserInfo user2 = ThirdParty.signInUp(process.getProcess(), "google", "userid1", "test2@example.com").user; - AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.id).user; - AuthRecipe.linkAccounts(process.getProcess(), user2.id, primaryUser.id); + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.getSupertokensUserId()).user; + AuthRecipe.linkAccounts(process.getProcess(), user2.getSupertokensUserId(), primaryUser.getSupertokensUserId()); TenantIdentifierWithStorage tenantIdentifierWithStorage = TenantIdentifier.BASE_TENANT.withStorage( StorageLayer.getBaseStorage(process.getProcess())); - primaryUser = AuthRecipe.getUserById(process.getProcess(), user1.id); + primaryUser = AuthRecipe.getUserById(process.getProcess(), user1.getSupertokensUserId()); assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, "test1@example.com", null, null, null)[0]); @@ -258,13 +258,13 @@ public void testListUserByAccountInfoWhenAccountsAreLinked2() throws Exception { Thread.sleep(50); AuthRecipeUserInfo user2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "password2"); - AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.id).user; - AuthRecipe.linkAccounts(process.getProcess(), user2.id, primaryUser.id); + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.getSupertokensUserId()).user; + AuthRecipe.linkAccounts(process.getProcess(), user2.getSupertokensUserId(), primaryUser.getSupertokensUserId()); TenantIdentifierWithStorage tenantIdentifierWithStorage = TenantIdentifier.BASE_TENANT.withStorage( StorageLayer.getBaseStorage(process.getProcess())); - primaryUser = AuthRecipe.getUserById(process.getProcess(), user1.id); + primaryUser = AuthRecipe.getUserById(process.getProcess(), user1.getSupertokensUserId()); assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, "test1@example.com", null, null, null)[0]); @@ -289,13 +289,13 @@ public void testListUserByAccountInfoWhenAccountsAreLinked3() throws Exception { Thread.sleep(50); AuthRecipeUserInfo user2 = createPasswordlessUserWithEmail(process.getProcess(), "test2@example.com"); - AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.id).user; - AuthRecipe.linkAccounts(process.getProcess(), user2.id, primaryUser.id); + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.getSupertokensUserId()).user; + AuthRecipe.linkAccounts(process.getProcess(), user2.getSupertokensUserId(), primaryUser.getSupertokensUserId()); TenantIdentifierWithStorage tenantIdentifierWithStorage = TenantIdentifier.BASE_TENANT.withStorage( StorageLayer.getBaseStorage(process.getProcess())); - primaryUser = AuthRecipe.getUserById(process.getProcess(), user1.id); + primaryUser = AuthRecipe.getUserById(process.getProcess(), user1.getSupertokensUserId()); assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, "test1@example.com", null, null, null)[0]); @@ -320,13 +320,13 @@ public void testListUserByAccountInfoWhenAccountsAreLinked4() throws Exception { Thread.sleep(50); AuthRecipeUserInfo user2 = createPasswordlessUserWithPhone(process.getProcess(), "+919876543210"); - AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.id).user; - AuthRecipe.linkAccounts(process.getProcess(), user2.id, primaryUser.id); + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.getSupertokensUserId()).user; + AuthRecipe.linkAccounts(process.getProcess(), user2.getSupertokensUserId(), primaryUser.getSupertokensUserId()); TenantIdentifierWithStorage tenantIdentifierWithStorage = TenantIdentifier.BASE_TENANT.withStorage( StorageLayer.getBaseStorage(process.getProcess())); - primaryUser = AuthRecipe.getUserById(process.getProcess(), user1.id); + primaryUser = AuthRecipe.getUserById(process.getProcess(), user1.getSupertokensUserId()); assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, "test1@example.com", null, null, null)[0]); @@ -353,13 +353,13 @@ public void testListUserByAccountInfoWhenAccountsAreLinked5() throws Exception { Thread.sleep(50); AuthRecipeUserInfo user2 = createThirdPartyUser(process.getProcess(), "google", "userid1", "test2@example.com"); - AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.id).user; - AuthRecipe.linkAccounts(process.getProcess(), user2.id, primaryUser.id); + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.getSupertokensUserId()).user; + AuthRecipe.linkAccounts(process.getProcess(), user2.getSupertokensUserId(), primaryUser.getSupertokensUserId()); TenantIdentifierWithStorage tenantIdentifierWithStorage = TenantIdentifier.BASE_TENANT.withStorage( StorageLayer.getBaseStorage(process.getProcess())); - primaryUser = AuthRecipe.getUserById(process.getProcess(), user1.id); + primaryUser = AuthRecipe.getUserById(process.getProcess(), user1.getSupertokensUserId()); assertEquals(primaryUser, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, "test1@example.com", null, null, null)[0]); diff --git a/src/test/java/io/supertokens/test/accountlinking/GetUserByIdTest.java b/src/test/java/io/supertokens/test/accountlinking/GetUserByIdTest.java index ff142cafb..306beec64 100644 --- a/src/test/java/io/supertokens/test/accountlinking/GetUserByIdTest.java +++ b/src/test/java/io/supertokens/test/accountlinking/GetUserByIdTest.java @@ -115,27 +115,27 @@ public void testAllLoginMethods() throws Exception { assertFalse(user3.isPrimaryUser); assertFalse(user4.isPrimaryUser); - AuthRecipeUserInfo userToTest = AuthRecipe.getUserById(process.getProcess(), user1.id); - assertNotNull(userToTest.id); + AuthRecipeUserInfo userToTest = AuthRecipe.getUserById(process.getProcess(), user1.getSupertokensUserId()); + assertNotNull(userToTest.getSupertokensUserId()); assertFalse(userToTest.isPrimaryUser); assertEquals(1, userToTest.loginMethods.length); assertEquals("test@example.com", userToTest.loginMethods[0].email); assertEquals(RECIPE_ID.EMAIL_PASSWORD, userToTest.loginMethods[0].recipeId); - assertEquals(user1.id, userToTest.loginMethods[0].recipeUserId); + assertEquals(user1.getSupertokensUserId(), userToTest.loginMethods[0].getSupertokensUserId()); assertFalse(userToTest.loginMethods[0].verified); assert(userToTest.loginMethods[0].timeJoined > 0); - assertEquals(user1, AuthRecipe.getUserById(process.getProcess(), user1.id)); - assertEquals(user2, AuthRecipe.getUserById(process.getProcess(), user2.id)); - assertEquals(user3, AuthRecipe.getUserById(process.getProcess(), user3.id)); - assertEquals(user4, AuthRecipe.getUserById(process.getProcess(), user4.id)); + assertEquals(user1, AuthRecipe.getUserById(process.getProcess(), user1.getSupertokensUserId())); + assertEquals(user2, AuthRecipe.getUserById(process.getProcess(), user2.getSupertokensUserId())); + assertEquals(user3, AuthRecipe.getUserById(process.getProcess(), user3.getSupertokensUserId())); + assertEquals(user4, AuthRecipe.getUserById(process.getProcess(), user4.getSupertokensUserId())); - AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user4.id).user; - AuthRecipe.linkAccounts(process.getProcess(), user3.id, primaryUser.id); - AuthRecipe.linkAccounts(process.getProcess(), user2.id, primaryUser.id); - AuthRecipe.linkAccounts(process.getProcess(), user1.id, primaryUser.id); + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user4.getSupertokensUserId()).user; + AuthRecipe.linkAccounts(process.getProcess(), user3.getSupertokensUserId(), primaryUser.getSupertokensUserId()); + AuthRecipe.linkAccounts(process.getProcess(), user2.getSupertokensUserId(), primaryUser.getSupertokensUserId()); + AuthRecipe.linkAccounts(process.getProcess(), user1.getSupertokensUserId(), primaryUser.getSupertokensUserId()); - for (String userId : new String[]{user1.id, user2.id, user3.id, user4.id}) { + for (String userId : new String[]{user1.getSupertokensUserId(), user2.getSupertokensUserId(), user3.getSupertokensUserId(), user4.getSupertokensUserId()}) { AuthRecipeUserInfo result = AuthRecipe.getUserById(process.getProcess(), userId); assertTrue(result.isPrimaryUser); @@ -189,12 +189,12 @@ public void testLoginMethodsAreSortedByTime() throws Exception { AuthRecipeUserInfo user1 = createEmailPasswordUser(process.getProcess(), "test@example.com", "password1"); // Link accounts randomly - String[] userIds = new String[]{user1.id, user2.id, user3.id, user4.id}; + String[] userIds = new String[]{user1.getSupertokensUserId(), user2.getSupertokensUserId(), user3.getSupertokensUserId(), user4.getSupertokensUserId()}; Collections.shuffle(Arrays.asList(userIds)); AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), userIds[0]).user; - AuthRecipe.linkAccounts(process.getProcess(), userIds[1], primaryUser.id); - AuthRecipe.linkAccounts(process.getProcess(), userIds[2], primaryUser.id); - AuthRecipe.linkAccounts(process.getProcess(), userIds[3], primaryUser.id); + AuthRecipe.linkAccounts(process.getProcess(), userIds[1], primaryUser.getSupertokensUserId()); + AuthRecipe.linkAccounts(process.getProcess(), userIds[2], primaryUser.getSupertokensUserId()); + AuthRecipe.linkAccounts(process.getProcess(), userIds[3], primaryUser.getSupertokensUserId()); for (String userId : userIds) { AuthRecipeUserInfo result = AuthRecipe.getUserById(process.getProcess(), userId); diff --git a/src/test/java/io/supertokens/test/accountlinking/LinkAccountsTest.java b/src/test/java/io/supertokens/test/accountlinking/LinkAccountsTest.java index 7052e123e..532b3e90d 100644 --- a/src/test/java/io/supertokens/test/accountlinking/LinkAccountsTest.java +++ b/src/test/java/io/supertokens/test/accountlinking/LinkAccountsTest.java @@ -82,27 +82,27 @@ public void linkAccountSuccess() throws Exception { AuthRecipeUserInfo user2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "password"); assert (!user2.isPrimaryUser); - AuthRecipe.createPrimaryUser(process.main, user.id); + AuthRecipe.createPrimaryUser(process.main, user.getSupertokensUserId()); - Session.createNewSession(process.main, user2.id, new JsonObject(), new JsonObject()); - String[] sessions = Session.getAllNonExpiredSessionHandlesForUser(process.main, user2.id); + Session.createNewSession(process.main, user2.getSupertokensUserId(), new JsonObject(), new JsonObject()); + String[] sessions = Session.getAllNonExpiredSessionHandlesForUser(process.main, user2.getSupertokensUserId()); assert (sessions.length == 1); - boolean wasAlreadyLinked = AuthRecipe.linkAccounts(process.main, user2.id, user.id); + boolean wasAlreadyLinked = AuthRecipe.linkAccounts(process.main, user2.getSupertokensUserId(), user.getSupertokensUserId()); assert (!wasAlreadyLinked); - AuthRecipeUserInfo refetchUser2 = AuthRecipe.getUserById(process.main, user2.id); - AuthRecipeUserInfo refetchUser = AuthRecipe.getUserById(process.main, user.id); + AuthRecipeUserInfo refetchUser2 = AuthRecipe.getUserById(process.main, user2.getSupertokensUserId()); + AuthRecipeUserInfo refetchUser = AuthRecipe.getUserById(process.main, user.getSupertokensUserId()); assert (refetchUser2.equals(refetchUser)); assert (refetchUser2.loginMethods.length == 2); assert (refetchUser.loginMethods[0].equals(user.loginMethods[0])); assert (refetchUser.loginMethods[1].equals(user2.loginMethods[0])); assert (refetchUser.tenantIds.size() == 1); assert (refetchUser.isPrimaryUser); - assert (refetchUser.id.equals(user.id)); + assert (refetchUser.getSupertokensUserId().equals(user.getSupertokensUserId())); // cause linkAccounts revokes sessions for the recipe user ID - sessions = Session.getAllNonExpiredSessionHandlesForUser(process.main, user2.id); + sessions = Session.getAllNonExpiredSessionHandlesForUser(process.main, user2.getSupertokensUserId()); assert (sessions.length == 0); process.kill(); @@ -134,27 +134,27 @@ public void linkAccountSuccessWithSameEmail() throws Exception { AuthRecipeUserInfo user2 = signInUpResponse.user; assert (!user2.isPrimaryUser); - AuthRecipe.createPrimaryUser(process.main, user.id); + AuthRecipe.createPrimaryUser(process.main, user.getSupertokensUserId()); - Session.createNewSession(process.main, user2.id, new JsonObject(), new JsonObject()); - String[] sessions = Session.getAllNonExpiredSessionHandlesForUser(process.main, user2.id); + Session.createNewSession(process.main, user2.getSupertokensUserId(), new JsonObject(), new JsonObject()); + String[] sessions = Session.getAllNonExpiredSessionHandlesForUser(process.main, user2.getSupertokensUserId()); assert (sessions.length == 1); - boolean wasAlreadyLinked = AuthRecipe.linkAccounts(process.main, user2.id, user.id); + boolean wasAlreadyLinked = AuthRecipe.linkAccounts(process.main, user2.getSupertokensUserId(), user.getSupertokensUserId()); assert (!wasAlreadyLinked); - AuthRecipeUserInfo refetchUser2 = AuthRecipe.getUserById(process.main, user2.id); - AuthRecipeUserInfo refetchUser = AuthRecipe.getUserById(process.main, user.id); + AuthRecipeUserInfo refetchUser2 = AuthRecipe.getUserById(process.main, user2.getSupertokensUserId()); + AuthRecipeUserInfo refetchUser = AuthRecipe.getUserById(process.main, user.getSupertokensUserId()); assert (refetchUser2.equals(refetchUser)); assert (refetchUser2.loginMethods.length == 2); assert (refetchUser.loginMethods[0].equals(user.loginMethods[0])); assert (refetchUser.loginMethods[1].equals(user2.loginMethods[0])); assert (refetchUser.tenantIds.size() == 1); assert (refetchUser.isPrimaryUser); - assert (refetchUser.id.equals(user.id)); + assert (refetchUser.getSupertokensUserId().equals(user.getSupertokensUserId())); // cause linkAccounts revokes sessions for the recipe user ID - sessions = Session.getAllNonExpiredSessionHandlesForUser(process.main, user2.id); + sessions = Session.getAllNonExpiredSessionHandlesForUser(process.main, user2.getSupertokensUserId()); assert (sessions.length == 0); process.kill(); @@ -202,27 +202,27 @@ public void linkAccountSuccessEvenIfUsingRecipeUserIdThatIsLinkedToPrimaryUser() AuthRecipeUserInfo user2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "password"); assert (!user2.isPrimaryUser); - AuthRecipe.createPrimaryUser(process.main, user.id); + AuthRecipe.createPrimaryUser(process.main, user.getSupertokensUserId()); - boolean wasAlreadyLinked = AuthRecipe.linkAccounts(process.main, user2.id, user.id); + boolean wasAlreadyLinked = AuthRecipe.linkAccounts(process.main, user2.getSupertokensUserId(), user.getSupertokensUserId()); assert (!wasAlreadyLinked); AuthRecipeUserInfo user3 = EmailPassword.signUp(process.getProcess(), "test3@example.com", "password"); assert (!user3.isPrimaryUser); - wasAlreadyLinked = AuthRecipe.linkAccounts(process.main, user3.id, user2.id); + wasAlreadyLinked = AuthRecipe.linkAccounts(process.main, user3.getSupertokensUserId(), user2.getSupertokensUserId()); assert (!wasAlreadyLinked); - AuthRecipeUserInfo refetchUser = AuthRecipe.getUserById(process.main, user.id); + AuthRecipeUserInfo refetchUser = AuthRecipe.getUserById(process.main, user.getSupertokensUserId()); assert (refetchUser.loginMethods.length == 3); assert (refetchUser.loginMethods[0].equals(user.loginMethods[0])); assert (refetchUser.loginMethods[1].equals(user2.loginMethods[0])); assert (refetchUser.loginMethods[2].equals(user3.loginMethods[0])); assert (refetchUser.tenantIds.size() == 1); assert (refetchUser.isPrimaryUser); - assert (refetchUser.id.equals(user.id)); + assert (refetchUser.getSupertokensUserId().equals(user.getSupertokensUserId())); - AuthRecipeUserInfo refetchUser3 = AuthRecipe.getUserById(process.main, user3.id); + AuthRecipeUserInfo refetchUser3 = AuthRecipe.getUserById(process.main, user3.getSupertokensUserId()); assert (refetchUser3.equals(refetchUser)); process.kill(); @@ -254,20 +254,20 @@ public void alreadyLinkAccountLinkAgain() throws Exception { AuthRecipeUserInfo user2 = signInUpResponse.user; assert (!user2.isPrimaryUser); - AuthRecipe.createPrimaryUser(process.main, user.id); + AuthRecipe.createPrimaryUser(process.main, user.getSupertokensUserId()); - boolean wasAlreadyLinked = AuthRecipe.linkAccounts(process.main, user2.id, user.id); + boolean wasAlreadyLinked = AuthRecipe.linkAccounts(process.main, user2.getSupertokensUserId(), user.getSupertokensUserId()); assert (!wasAlreadyLinked); - Session.createNewSession(process.main, user2.id, new JsonObject(), new JsonObject()); - String[] sessions = Session.getAllNonExpiredSessionHandlesForUser(process.main, user2.id); + Session.createNewSession(process.main, user2.getSupertokensUserId(), new JsonObject(), new JsonObject()); + String[] sessions = Session.getAllNonExpiredSessionHandlesForUser(process.main, user2.getSupertokensUserId()); assert (sessions.length == 1); - wasAlreadyLinked = AuthRecipe.linkAccounts(process.main, user2.id, user.id); + wasAlreadyLinked = AuthRecipe.linkAccounts(process.main, user2.getSupertokensUserId(), user.getSupertokensUserId()); assert (wasAlreadyLinked); // cause linkAccounts revokes sessions for the recipe user ID - sessions = Session.getAllNonExpiredSessionHandlesForUser(process.main, user2.id); + sessions = Session.getAllNonExpiredSessionHandlesForUser(process.main, user2.getSupertokensUserId()); assert (sessions.length == 1); process.kill(); @@ -299,20 +299,20 @@ public void linkAccountFailureCauseRecipeUserIdLinkedWithAnotherPrimaryUser() th AuthRecipeUserInfo user2 = signInUpResponse.user; assert (!user2.isPrimaryUser); - AuthRecipe.createPrimaryUser(process.main, user.id); + AuthRecipe.createPrimaryUser(process.main, user.getSupertokensUserId()); - boolean wasAlreadyLinked = AuthRecipe.linkAccounts(process.main, user2.id, user.id); + boolean wasAlreadyLinked = AuthRecipe.linkAccounts(process.main, user2.getSupertokensUserId(), user.getSupertokensUserId()); assert (!wasAlreadyLinked); AuthRecipeUserInfo user3 = EmailPassword.signUp(process.getProcess(), "test3@example.com", "password"); assert (!user.isPrimaryUser); - AuthRecipe.createPrimaryUser(process.main, user3.id); + AuthRecipe.createPrimaryUser(process.main, user3.getSupertokensUserId()); try { - AuthRecipe.linkAccounts(process.main, user2.id, user3.id); + AuthRecipe.linkAccounts(process.main, user2.getSupertokensUserId(), user3.getSupertokensUserId()); assert (false); } catch (RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException e) { - assert (e.primaryUserId.equals(user.id)); + assert (e.primaryUserId.equals(user.getSupertokensUserId())); assert (e.getMessage().equals("The input recipe user ID is already linked to another user ID")); } @@ -344,10 +344,10 @@ public void linkAccountFailureInputUserIsNotAPrimaryUser() throws Exception { assert (!user2.isPrimaryUser); try { - AuthRecipe.linkAccounts(process.main, user2.id, user.id); + AuthRecipe.linkAccounts(process.main, user2.getSupertokensUserId(), user.getSupertokensUserId()); assert (false); } catch (InputUserIdIsNotAPrimaryUserException e) { - assert (e.userId.equals(user.id)); + assert (e.userId.equals(user.getSupertokensUserId())); } process.kill(); @@ -371,7 +371,7 @@ public void linkAccountFailureUserDoesNotExist() throws Exception { AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "password"); assert (!user.isPrimaryUser); - AuthRecipe.createPrimaryUser(process.main, user.id); + AuthRecipe.createPrimaryUser(process.main, user.getSupertokensUserId()); ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(process.getProcess(), "google", "user-google", @@ -380,13 +380,13 @@ public void linkAccountFailureUserDoesNotExist() throws Exception { assert (!user2.isPrimaryUser); try { - AuthRecipe.linkAccounts(process.main, user2.id, "random"); + AuthRecipe.linkAccounts(process.main, user2.getSupertokensUserId(), "random"); assert (false); } catch (UnknownUserIdException e) { } try { - AuthRecipe.linkAccounts(process.main, "random2", user.id); + AuthRecipe.linkAccounts(process.main, "random2", user.getSupertokensUserId()); assert (false); } catch (UnknownUserIdException e) { } @@ -417,7 +417,7 @@ public void linkAccountFailureCauseAccountInfoAssociatedWithAPrimaryUser() throw AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "password"); assert (!user.isPrimaryUser); - AuthRecipe.createPrimaryUser(process.main, user.id); + AuthRecipe.createPrimaryUser(process.main, user.getSupertokensUserId()); Thread.sleep(50); @@ -430,13 +430,13 @@ public void linkAccountFailureCauseAccountInfoAssociatedWithAPrimaryUser() throw AuthRecipeUserInfo otherPrimaryUser = EmailPassword.signUp(process.getProcess(), "test3@example.com", "password"); - AuthRecipe.createPrimaryUser(process.main, otherPrimaryUser.id); + AuthRecipe.createPrimaryUser(process.main, otherPrimaryUser.getSupertokensUserId()); try { - AuthRecipe.linkAccounts(process.main, user2.id, otherPrimaryUser.id); + AuthRecipe.linkAccounts(process.main, user2.getSupertokensUserId(), otherPrimaryUser.getSupertokensUserId()); assert (false); } catch (AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException e) { - assert (e.primaryUserId.equals(user.id)); + assert (e.primaryUserId.equals(user.getSupertokensUserId())); assert (e.getMessage().equals("This user's email is already associated with another user ID")); } @@ -469,7 +469,7 @@ public void linkAccountFailureCauseAccountInfoAssociatedWithAPrimaryUserEvenIfIn AuthRecipeUserInfo user = EmailPassword.signUp(tenantIdentifierWithStorage, process.getProcess(), "test@example.com", "password"); assert (!user.isPrimaryUser); - AuthRecipe.createPrimaryUser(process.main, user.id); + AuthRecipe.createPrimaryUser(process.main, user.getSupertokensUserId()); Thread.sleep(50); @@ -483,13 +483,13 @@ public void linkAccountFailureCauseAccountInfoAssociatedWithAPrimaryUserEvenIfIn AuthRecipeUserInfo otherPrimaryUser = EmailPassword.signUp(process.getProcess(), "test3@example.com", "password"); - AuthRecipe.createPrimaryUser(process.main, otherPrimaryUser.id); + AuthRecipe.createPrimaryUser(process.main, otherPrimaryUser.getSupertokensUserId()); try { - AuthRecipe.linkAccounts(process.main, user2.id, otherPrimaryUser.id); + AuthRecipe.linkAccounts(process.main, user2.getSupertokensUserId(), otherPrimaryUser.getSupertokensUserId()); assert (false); } catch (AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException e) { - assert (e.primaryUserId.equals(user.id)); + assert (e.primaryUserId.equals(user.getSupertokensUserId())); assert (e.getMessage().equals("This user's email is already associated with another user ID")); } @@ -522,7 +522,7 @@ public void linkAccountSuccessAcrossTenants() throws Exception { AuthRecipeUserInfo user = EmailPassword.signUp(tenantIdentifierWithStorage, process.getProcess(), "test@example.com", "password"); assert (!user.isPrimaryUser); - AuthRecipe.createPrimaryUser(process.main, user.id); + AuthRecipe.createPrimaryUser(process.main, user.getSupertokensUserId()); Thread.sleep(50); @@ -533,20 +533,20 @@ public void linkAccountSuccessAcrossTenants() throws Exception { AuthRecipeUserInfo user2 = signInUpResponse.user; assert (!user2.isPrimaryUser); - boolean wasAlreadyLinked = AuthRecipe.linkAccounts(process.main, user2.id, user.id); + boolean wasAlreadyLinked = AuthRecipe.linkAccounts(process.main, user2.getSupertokensUserId(), user.getSupertokensUserId()); assert (!wasAlreadyLinked); - AuthRecipeUserInfo refetchedUser1 = AuthRecipe.getUserById(process.main, user.id); - AuthRecipeUserInfo refetchedUser2 = AuthRecipe.getUserById(process.main, user2.id); - assert (refetchedUser1.id.equals(refetchedUser2.id)); + AuthRecipeUserInfo refetchedUser1 = AuthRecipe.getUserById(process.main, user.getSupertokensUserId()); + AuthRecipeUserInfo refetchedUser2 = AuthRecipe.getUserById(process.main, user2.getSupertokensUserId()); + assert (refetchedUser1.getSupertokensUserId().equals(refetchedUser2.getSupertokensUserId())); assert refetchedUser1.loginMethods.length == 2; assert refetchedUser1.tenantIds.size() == 2; assert refetchedUser1.tenantIds.contains("t1"); assert refetchedUser1.tenantIds.contains("public"); - assert refetchedUser1.id.equals(user.id); + assert refetchedUser1.getSupertokensUserId().equals(user.getSupertokensUserId()); assert refetchedUser1.isPrimaryUser; - assert refetchedUser1.loginMethods[0].recipeUserId.equals(user.loginMethods[0].recipeUserId); - assert refetchedUser1.loginMethods[1].recipeUserId.equals(user2.loginMethods[0].recipeUserId); + assert refetchedUser1.loginMethods[0].getSupertokensUserId().equals(user.loginMethods[0].getSupertokensUserId()); + assert refetchedUser1.loginMethods[1].getSupertokensUserId().equals(user2.loginMethods[0].getSupertokensUserId()); process.kill(); @@ -579,23 +579,23 @@ public void linkAccountSuccessWithPasswordlessEmailAndPhoneNumber() throws Excep AuthRecipeUserInfo user2 = pResp.user; assert (!user2.isPrimaryUser); - AuthRecipe.createPrimaryUser(process.main, user.id); + AuthRecipe.createPrimaryUser(process.main, user.getSupertokensUserId()); - Passwordless.updateUser(process.main, user2.id, null, new Passwordless.FieldUpdate("1234")); - user2 = AuthRecipe.getUserById(process.main, user2.id); + Passwordless.updateUser(process.main, user2.getSupertokensUserId(), null, new Passwordless.FieldUpdate("1234")); + user2 = AuthRecipe.getUserById(process.main, user2.getSupertokensUserId()); - boolean wasAlreadyLinked = AuthRecipe.linkAccounts(process.main, user2.id, user.id); + boolean wasAlreadyLinked = AuthRecipe.linkAccounts(process.main, user2.getSupertokensUserId(), user.getSupertokensUserId()); assert (!wasAlreadyLinked); - AuthRecipeUserInfo refetchUser2 = AuthRecipe.getUserById(process.main, user2.id); - AuthRecipeUserInfo refetchUser = AuthRecipe.getUserById(process.main, user.id); + AuthRecipeUserInfo refetchUser2 = AuthRecipe.getUserById(process.main, user2.getSupertokensUserId()); + AuthRecipeUserInfo refetchUser = AuthRecipe.getUserById(process.main, user.getSupertokensUserId()); assert (refetchUser2.equals(refetchUser)); assert (refetchUser2.loginMethods.length == 2); assert (refetchUser.loginMethods[0].equals(user.loginMethods[0])); assert (refetchUser.loginMethods[1].equals(user2.loginMethods[0])); assert (refetchUser.tenantIds.size() == 1); assert (refetchUser.isPrimaryUser); - assert (refetchUser.id.equals(user.id)); + assert (refetchUser.getSupertokensUserId().equals(user.getSupertokensUserId())); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); diff --git a/src/test/java/io/supertokens/test/accountlinking/UnlinkAccountsTest.java b/src/test/java/io/supertokens/test/accountlinking/UnlinkAccountsTest.java index 47d750ae3..78a2f4a8e 100644 --- a/src/test/java/io/supertokens/test/accountlinking/UnlinkAccountsTest.java +++ b/src/test/java/io/supertokens/test/accountlinking/UnlinkAccountsTest.java @@ -75,31 +75,31 @@ public void unlinkAccountSuccess() throws Exception { AuthRecipeUserInfo user2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "password"); assert (!user2.isPrimaryUser); - AuthRecipe.createPrimaryUser(process.main, user.id); + AuthRecipe.createPrimaryUser(process.main, user.getSupertokensUserId()); - AuthRecipe.linkAccounts(process.main, user2.id, user.id); + AuthRecipe.linkAccounts(process.main, user2.getSupertokensUserId(), user.getSupertokensUserId()); - Session.createNewSession(process.main, user2.id, new JsonObject(), new JsonObject()); - String[] sessions = Session.getAllNonExpiredSessionHandlesForUser(process.main, user2.id); + Session.createNewSession(process.main, user2.getSupertokensUserId(), new JsonObject(), new JsonObject()); + String[] sessions = Session.getAllNonExpiredSessionHandlesForUser(process.main, user2.getSupertokensUserId()); assert (sessions.length == 1); - boolean didDelete = AuthRecipe.unlinkAccounts(process.main, user2.id); + boolean didDelete = AuthRecipe.unlinkAccounts(process.main, user2.getSupertokensUserId()); assert (!didDelete); - AuthRecipeUserInfo refetchUser2 = AuthRecipe.getUserById(process.main, user2.id); + AuthRecipeUserInfo refetchUser2 = AuthRecipe.getUserById(process.main, user2.getSupertokensUserId()); assert (!refetchUser2.isPrimaryUser); - assert (refetchUser2.id.equals(user2.id)); + assert (refetchUser2.getSupertokensUserId().equals(user2.getSupertokensUserId())); assert (refetchUser2.loginMethods.length == 1); - assert (refetchUser2.loginMethods[0].recipeUserId.equals(user2.id)); + assert (refetchUser2.loginMethods[0].getSupertokensUserId().equals(user2.getSupertokensUserId())); - AuthRecipeUserInfo refetchUser = AuthRecipe.getUserById(process.main, user.id); + AuthRecipeUserInfo refetchUser = AuthRecipe.getUserById(process.main, user.getSupertokensUserId()); assert (!refetchUser2.equals(refetchUser)); assert (refetchUser.isPrimaryUser); assert (refetchUser.loginMethods.length == 1); - assert (refetchUser.loginMethods[0].recipeUserId.equals(user.id)); + assert (refetchUser.loginMethods[0].getSupertokensUserId().equals(user.getSupertokensUserId())); // cause linkAccounts revokes sessions for the recipe user ID - sessions = Session.getAllNonExpiredSessionHandlesForUser(process.main, user2.id); + sessions = Session.getAllNonExpiredSessionHandlesForUser(process.main, user2.getSupertokensUserId()); assert (sessions.length == 0); process.kill(); @@ -124,10 +124,10 @@ public void unlinkAccountWithoutPrimaryUserId() throws Exception { assert (!user.isPrimaryUser); try { - AuthRecipe.unlinkAccounts(process.main, user.id); + AuthRecipe.unlinkAccounts(process.main, user.getSupertokensUserId()); assert (false); } catch (InputUserIdIsNotAPrimaryUserException e) { - assert (e.userId.equals(user.id)); + assert (e.userId.equals(user.getSupertokensUserId())); } @@ -177,22 +177,22 @@ public void unlinkAccountWithPrimaryUserBecomesRecipeUser() throws Exception { AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "password"); assert (!user.isPrimaryUser); - AuthRecipe.createPrimaryUser(process.main, user.id); + AuthRecipe.createPrimaryUser(process.main, user.getSupertokensUserId()); - Session.createNewSession(process.main, user.id, new JsonObject(), new JsonObject()); - String[] sessions = Session.getAllNonExpiredSessionHandlesForUser(process.main, user.id); + Session.createNewSession(process.main, user.getSupertokensUserId(), new JsonObject(), new JsonObject()); + String[] sessions = Session.getAllNonExpiredSessionHandlesForUser(process.main, user.getSupertokensUserId()); assert (sessions.length == 1); - boolean didDelete = AuthRecipe.unlinkAccounts(process.main, user.id); + boolean didDelete = AuthRecipe.unlinkAccounts(process.main, user.getSupertokensUserId()); assert (!didDelete); - AuthRecipeUserInfo refetchUser = AuthRecipe.getUserById(process.main, user.id); + AuthRecipeUserInfo refetchUser = AuthRecipe.getUserById(process.main, user.getSupertokensUserId()); assert (!refetchUser.isPrimaryUser); assert (refetchUser.loginMethods.length == 1); - assert (refetchUser.loginMethods[0].recipeUserId.equals(user.id)); + assert (refetchUser.loginMethods[0].getSupertokensUserId().equals(user.getSupertokensUserId())); // cause linkAccounts revokes sessions for the recipe user ID - sessions = Session.getAllNonExpiredSessionHandlesForUser(process.main, user.id); + sessions = Session.getAllNonExpiredSessionHandlesForUser(process.main, user.getSupertokensUserId()); assert (sessions.length == 0); process.kill(); @@ -221,28 +221,28 @@ public void unlinkAccountSuccessButDeletesUser() throws Exception { AuthRecipeUserInfo user2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "password"); assert (!user2.isPrimaryUser); - AuthRecipe.createPrimaryUser(process.main, user.id); + AuthRecipe.createPrimaryUser(process.main, user.getSupertokensUserId()); - AuthRecipe.linkAccounts(process.main, user2.id, user.id); + AuthRecipe.linkAccounts(process.main, user2.getSupertokensUserId(), user.getSupertokensUserId()); - Session.createNewSession(process.main, user.id, new JsonObject(), new JsonObject()); - String[] sessions = Session.getAllNonExpiredSessionHandlesForUser(process.main, user.id); + Session.createNewSession(process.main, user.getSupertokensUserId(), new JsonObject(), new JsonObject()); + String[] sessions = Session.getAllNonExpiredSessionHandlesForUser(process.main, user.getSupertokensUserId()); assert (sessions.length == 1); - boolean didDelete = AuthRecipe.unlinkAccounts(process.main, user.id); + boolean didDelete = AuthRecipe.unlinkAccounts(process.main, user.getSupertokensUserId()); assert (didDelete); - AuthRecipeUserInfo refetchUser2 = AuthRecipe.getUserById(process.main, user2.id); + AuthRecipeUserInfo refetchUser2 = AuthRecipe.getUserById(process.main, user2.getSupertokensUserId()); assert (refetchUser2.isPrimaryUser); - assert (refetchUser2.id.equals(user.id)); + assert (refetchUser2.getSupertokensUserId().equals(user.getSupertokensUserId())); assert (refetchUser2.loginMethods.length == 1); - assert (refetchUser2.loginMethods[0].recipeUserId.equals(user2.id)); + assert (refetchUser2.loginMethods[0].getSupertokensUserId().equals(user2.getSupertokensUserId())); - AuthRecipeUserInfo refetchUser = AuthRecipe.getUserById(process.main, user.id); + AuthRecipeUserInfo refetchUser = AuthRecipe.getUserById(process.main, user.getSupertokensUserId()); assert (refetchUser2.equals(refetchUser)); // cause linkAccounts revokes sessions for the recipe user ID - sessions = Session.getAllNonExpiredSessionHandlesForUser(process.main, user.id); + sessions = Session.getAllNonExpiredSessionHandlesForUser(process.main, user.getSupertokensUserId()); assert (sessions.length == 0); process.kill(); diff --git a/src/test/java/io/supertokens/test/accountlinking/api/CanCreatePrimaryUserAPITest.java b/src/test/java/io/supertokens/test/accountlinking/api/CanCreatePrimaryUserAPITest.java index e7492d88b..608a313a2 100644 --- a/src/test/java/io/supertokens/test/accountlinking/api/CanCreatePrimaryUserAPITest.java +++ b/src/test/java/io/supertokens/test/accountlinking/api/CanCreatePrimaryUserAPITest.java @@ -75,7 +75,7 @@ public void canCreateReturnsTrue() throws Exception { { Map params = new HashMap<>(); - params.put("recipeUserId", user.id); + params.put("recipeUserId", user.getSupertokensUserId()); JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", "http://localhost:3567/recipe/accountlinking/user/primary/check", params, 1000, 1000, null, @@ -85,11 +85,11 @@ public void canCreateReturnsTrue() throws Exception { assertFalse(response.get("wasAlreadyAPrimaryUser").getAsBoolean()); } - AuthRecipe.createPrimaryUser(process.main, user.id); + AuthRecipe.createPrimaryUser(process.main, user.getSupertokensUserId()); { Map params = new HashMap<>(); - params.put("recipeUserId", user.id); + params.put("recipeUserId", user.getSupertokensUserId()); JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", "http://localhost:3567/recipe/accountlinking/user/primary/check", params, 1000, 1000, null, @@ -118,7 +118,7 @@ public void canCreateReturnsTrueWithUserIdMapping() throws Exception { } AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "abcd1234"); - UserIdMapping.createUserIdMapping(process.main, user.id, "r1", null, false); + UserIdMapping.createUserIdMapping(process.main, user.getSupertokensUserId(), "r1", null, false); { Map params = new HashMap<>(); @@ -132,7 +132,7 @@ public void canCreateReturnsTrueWithUserIdMapping() throws Exception { assertFalse(response.get("wasAlreadyAPrimaryUser").getAsBoolean()); } - AuthRecipe.createPrimaryUser(process.main, user.id); + AuthRecipe.createPrimaryUser(process.main, user.getSupertokensUserId()); { Map params = new HashMap<>(); @@ -213,7 +213,7 @@ public void makePrimaryUserFailsCauseAnotherAccountWithSameEmailAlreadyAPrimaryU AuthRecipeUserInfo emailPasswordUser = EmailPassword.signUp(process.getProcess(), "test@example.com", "pass1234"); - AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.createPrimaryUser(process.main, emailPasswordUser.id); + AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.createPrimaryUser(process.main, emailPasswordUser.getSupertokensUserId()); assert (!result.wasAlreadyAPrimaryUser); ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(process.main, "google", "user-google", @@ -221,7 +221,7 @@ public void makePrimaryUserFailsCauseAnotherAccountWithSameEmailAlreadyAPrimaryU { Map params = new HashMap<>(); - params.put("recipeUserId", signInUpResponse.user.id); + params.put("recipeUserId", signInUpResponse.user.getSupertokensUserId()); JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", "http://localhost:3567/recipe/accountlinking/user/primary/check", params, 1000, 1000, null, @@ -229,7 +229,7 @@ public void makePrimaryUserFailsCauseAnotherAccountWithSameEmailAlreadyAPrimaryU assertEquals(3, response.entrySet().size()); assertEquals("ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR", response.get("status").getAsString()); - assertEquals(emailPasswordUser.id, response.get("primaryUserId").getAsString()); + assertEquals(emailPasswordUser.getSupertokensUserId(), response.get("primaryUserId").getAsString()); assertEquals("This user's email is already associated with another user ID", response.get("description").getAsString()); } @@ -253,12 +253,12 @@ public void makingPrimaryUserFailsCauseAlreadyLinkedToAnotherAccount() throws Ex AuthRecipeUserInfo emailPasswordUser2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "pass1234"); - AuthRecipe.createPrimaryUser(process.main, emailPasswordUser1.id); - AuthRecipe.linkAccounts(process.main, emailPasswordUser2.id, emailPasswordUser1.id); + AuthRecipe.createPrimaryUser(process.main, emailPasswordUser1.getSupertokensUserId()); + AuthRecipe.linkAccounts(process.main, emailPasswordUser2.getSupertokensUserId(), emailPasswordUser1.getSupertokensUserId()); { Map params = new HashMap<>(); - params.put("recipeUserId", emailPasswordUser2.id); + params.put("recipeUserId", emailPasswordUser2.getSupertokensUserId()); JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", "http://localhost:3567/recipe/accountlinking/user/primary/check", params, 1000, 1000, null, @@ -266,7 +266,7 @@ public void makingPrimaryUserFailsCauseAlreadyLinkedToAnotherAccount() throws Ex assertEquals(3, response.entrySet().size()); assertEquals("RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR", response.get("status").getAsString()); - assertEquals(emailPasswordUser1.id, response.get("primaryUserId").getAsString()); + assertEquals(emailPasswordUser1.getSupertokensUserId(), response.get("primaryUserId").getAsString()); assertEquals("This user ID is already linked to another user ID", response.get("description").getAsString()); } @@ -288,9 +288,9 @@ public void makePrimaryUserFailsCauseAnotherAccountWithSameEmailAlreadyAPrimaryU AuthRecipeUserInfo emailPasswordUser = EmailPassword.signUp(process.getProcess(), "test@example.com", "pass1234"); - UserIdMapping.createUserIdMapping(process.main, emailPasswordUser.id, "r1", null, false); + UserIdMapping.createUserIdMapping(process.main, emailPasswordUser.getSupertokensUserId(), "r1", null, false); - AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.createPrimaryUser(process.main, emailPasswordUser.id); + AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.createPrimaryUser(process.main, emailPasswordUser.getSupertokensUserId()); assert (!result.wasAlreadyAPrimaryUser); ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(process.main, "google", "user-google", @@ -298,7 +298,7 @@ public void makePrimaryUserFailsCauseAnotherAccountWithSameEmailAlreadyAPrimaryU { Map params = new HashMap<>(); - params.put("recipeUserId", signInUpResponse.user.id); + params.put("recipeUserId", signInUpResponse.user.getSupertokensUserId()); JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", "http://localhost:3567/recipe/accountlinking/user/primary/check", params, 1000, 1000, null, @@ -327,16 +327,16 @@ public void makingPrimaryUserFailsCauseAlreadyLinkedToAnotherAccountWithUserIdMa AuthRecipeUserInfo emailPasswordUser1 = EmailPassword.signUp(process.getProcess(), "test@example.com", "pass1234"); - UserIdMapping.createUserIdMapping(process.main, emailPasswordUser1.id, "r1", null, false); + UserIdMapping.createUserIdMapping(process.main, emailPasswordUser1.getSupertokensUserId(), "r1", null, false); AuthRecipeUserInfo emailPasswordUser2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "pass1234"); - AuthRecipe.createPrimaryUser(process.main, emailPasswordUser1.id); - AuthRecipe.linkAccounts(process.main, emailPasswordUser2.id, emailPasswordUser1.id); + AuthRecipe.createPrimaryUser(process.main, emailPasswordUser1.getSupertokensUserId()); + AuthRecipe.linkAccounts(process.main, emailPasswordUser2.getSupertokensUserId(), emailPasswordUser1.getSupertokensUserId()); { Map params = new HashMap<>(); - params.put("recipeUserId", emailPasswordUser2.id); + params.put("recipeUserId", emailPasswordUser2.getSupertokensUserId()); JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", "http://localhost:3567/recipe/accountlinking/user/primary/check", params, 1000, 1000, null, diff --git a/src/test/java/io/supertokens/test/accountlinking/api/CanLinkAccountsAPITest.java b/src/test/java/io/supertokens/test/accountlinking/api/CanLinkAccountsAPITest.java index 42c1a6e27..e8872f1db 100644 --- a/src/test/java/io/supertokens/test/accountlinking/api/CanLinkAccountsAPITest.java +++ b/src/test/java/io/supertokens/test/accountlinking/api/CanLinkAccountsAPITest.java @@ -75,12 +75,12 @@ public void canLinkReturnsTrue() throws Exception { AuthRecipeUserInfo user2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "abcd1234"); - AuthRecipe.createPrimaryUser(process.main, user2.id); + AuthRecipe.createPrimaryUser(process.main, user2.getSupertokensUserId()); { Map params = new HashMap<>(); - params.put("recipeUserId", user.id); - params.put("primaryUserId", user2.id); + params.put("recipeUserId", user.getSupertokensUserId()); + params.put("primaryUserId", user2.getSupertokensUserId()); JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", "http://localhost:3567/recipe/accountlinking/user/link/check", params, 1000, 1000, null, @@ -90,12 +90,12 @@ public void canLinkReturnsTrue() throws Exception { assertFalse(response.get("accountsAlreadyLinked").getAsBoolean()); } - AuthRecipe.linkAccounts(process.main, user.id, user2.id); + AuthRecipe.linkAccounts(process.main, user.getSupertokensUserId(), user2.getSupertokensUserId()); { Map params = new HashMap<>(); - params.put("recipeUserId", user.id); - params.put("primaryUserId", user2.id); + params.put("recipeUserId", user.getSupertokensUserId()); + params.put("primaryUserId", user2.getSupertokensUserId()); JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", "http://localhost:3567/recipe/accountlinking/user/link/check", params, 1000, 1000, null, @@ -124,12 +124,12 @@ public void canLinkReturnsTrueWithUserIdMapping() throws Exception { } AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "abcd1234"); - UserIdMapping.createUserIdMapping(process.main, user.id, "r1", null, false); + UserIdMapping.createUserIdMapping(process.main, user.getSupertokensUserId(), "r1", null, false); AuthRecipeUserInfo user2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "abcd1234"); - UserIdMapping.createUserIdMapping(process.main, user2.id, "r2", null, false); + UserIdMapping.createUserIdMapping(process.main, user2.getSupertokensUserId(), "r2", null, false); - AuthRecipe.createPrimaryUser(process.main, user2.id); + AuthRecipe.createPrimaryUser(process.main, user2.getSupertokensUserId()); { Map params = new HashMap<>(); @@ -144,7 +144,7 @@ public void canLinkReturnsTrueWithUserIdMapping() throws Exception { assertFalse(response.get("accountsAlreadyLinked").getAsBoolean()); } - AuthRecipe.linkAccounts(process.main, user.id, user2.id); + AuthRecipe.linkAccounts(process.main, user.getSupertokensUserId(), user2.getSupertokensUserId()); { Map params = new HashMap<>(); @@ -211,13 +211,13 @@ public void canLinkUserBadInput() throws Exception { } AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "abcd1234"); - AuthRecipe.createPrimaryUser(process.main, user.id); + AuthRecipe.createPrimaryUser(process.main, user.getSupertokensUserId()); AuthRecipeUserInfo user2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "abcd1234"); { Map params = new HashMap<>(); - params.put("recipeUserId", user2.id); + params.put("recipeUserId", user2.getSupertokensUserId()); params.put("primaryUserId", "random"); try { @@ -235,7 +235,7 @@ public void canLinkUserBadInput() throws Exception { { Map params = new HashMap<>(); params.put("recipeUserId", "random"); - params.put("primaryUserId", user.id); + params.put("primaryUserId", user.getSupertokensUserId()); try { HttpRequestForTesting.sendGETRequest(process.getProcess(), "", @@ -266,13 +266,13 @@ public void linkingUsersFailsCauseAnotherAccountWithSameEmailAlreadyAPrimaryUser AuthRecipeUserInfo emailPasswordUser = EmailPassword.signUp(process.getProcess(), "test@example.com", "pass1234"); - AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.createPrimaryUser(process.main, emailPasswordUser.id); + AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.createPrimaryUser(process.main, emailPasswordUser.getSupertokensUserId()); assert (!result.wasAlreadyAPrimaryUser); ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(process.main, "google", "user-google", "test2@example.com"); - AuthRecipe.createPrimaryUser(process.main, signInUpResponse.user.id); + AuthRecipe.createPrimaryUser(process.main, signInUpResponse.user.getSupertokensUserId()); ThirdParty.SignInUpResponse signInUpResponse2 = ThirdParty.signInUp(process.main, "fb", "user-fb", "test@example.com"); @@ -280,8 +280,8 @@ public void linkingUsersFailsCauseAnotherAccountWithSameEmailAlreadyAPrimaryUser { Map params = new HashMap<>(); - params.put("primaryUserId", signInUpResponse.user.id); - params.put("recipeUserId", signInUpResponse2.user.id); + params.put("primaryUserId", signInUpResponse.user.getSupertokensUserId()); + params.put("recipeUserId", signInUpResponse2.user.getSupertokensUserId()); JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", "http://localhost:3567/recipe/accountlinking/user/link/check", params, 1000, 1000, null, @@ -289,7 +289,7 @@ public void linkingUsersFailsCauseAnotherAccountWithSameEmailAlreadyAPrimaryUser assertEquals(3, response.entrySet().size()); assertEquals("ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR", response.get("status").getAsString()); - assertEquals(emailPasswordUser.id, response.get("primaryUserId").getAsString()); + assertEquals(emailPasswordUser.getSupertokensUserId(), response.get("primaryUserId").getAsString()); assertEquals("This user's email is already associated with another user ID", response.get("description").getAsString()); } @@ -311,20 +311,20 @@ public void linkingUsersFailsCauseAnotherAccountWithSameEmailAlreadyAPrimaryUser AuthRecipeUserInfo emailPasswordUser = EmailPassword.signUp(process.getProcess(), "test@example.com", "pass1234"); - UserIdMapping.createUserIdMapping(process.main, emailPasswordUser.id, "e1", null, false); + UserIdMapping.createUserIdMapping(process.main, emailPasswordUser.getSupertokensUserId(), "e1", null, false); - AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.createPrimaryUser(process.main, emailPasswordUser.id); + AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.createPrimaryUser(process.main, emailPasswordUser.getSupertokensUserId()); assert (!result.wasAlreadyAPrimaryUser); ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(process.main, "google", "user-google", "test2@example.com"); - UserIdMapping.createUserIdMapping(process.main, signInUpResponse.user.id, "e2", null, false); + UserIdMapping.createUserIdMapping(process.main, signInUpResponse.user.getSupertokensUserId(), "e2", null, false); - AuthRecipe.createPrimaryUser(process.main, signInUpResponse.user.id); + AuthRecipe.createPrimaryUser(process.main, signInUpResponse.user.getSupertokensUserId()); ThirdParty.SignInUpResponse signInUpResponse2 = ThirdParty.signInUp(process.main, "fb", "user-fb", "test@example.com"); - UserIdMapping.createUserIdMapping(process.main, signInUpResponse2.user.id, "e3", null, false); + UserIdMapping.createUserIdMapping(process.main, signInUpResponse2.user.getSupertokensUserId(), "e3", null, false); { @@ -362,35 +362,35 @@ public void linkingUserFailsCauseAlreadyLinkedToAnotherAccount() throws Exceptio AuthRecipeUserInfo emailPasswordUser2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "pass1234"); - AuthRecipe.createPrimaryUser(process.main, emailPasswordUser1.id); - AuthRecipe.linkAccounts(process.main, emailPasswordUser2.id, emailPasswordUser1.id); + AuthRecipe.createPrimaryUser(process.main, emailPasswordUser1.getSupertokensUserId()); + AuthRecipe.linkAccounts(process.main, emailPasswordUser2.getSupertokensUserId(), emailPasswordUser1.getSupertokensUserId()); AuthRecipeUserInfo emailPasswordUser3 = EmailPassword.signUp(process.getProcess(), "test3@example.com", "pass1234"); - AuthRecipe.createPrimaryUser(process.main, emailPasswordUser3.id); + AuthRecipe.createPrimaryUser(process.main, emailPasswordUser3.getSupertokensUserId()); { Map params = new HashMap<>(); - params.put("recipeUserId", emailPasswordUser2.id); - params.put("primaryUserId", emailPasswordUser3.id); + params.put("recipeUserId", emailPasswordUser2.getSupertokensUserId()); + params.put("primaryUserId", emailPasswordUser3.getSupertokensUserId()); JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", "http://localhost:3567/recipe/accountlinking/user/link/check", params, 1000, 1000, null, WebserverAPI.getLatestCDIVersion().get(), ""); - assertEquals(3, response.entrySet().size()); + assertEquals(4, response.entrySet().size()); assertEquals("RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR", response.get("status").getAsString()); - assertEquals(emailPasswordUser1.id, response.get("primaryUserId").getAsString()); + assertEquals(emailPasswordUser1.getSupertokensUserId(), response.get("primaryUserId").getAsString()); assertEquals("The input recipe user ID is already linked to another user ID", response.get("description").getAsString()); + assertTrue(response.has("user")); } process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); } - @Test public void makingPrimaryUserFailsCauseAlreadyLinkedToAnotherAccountWithUserIdMapping() throws Exception { String[] args = {"../"}; @@ -403,19 +403,19 @@ public void makingPrimaryUserFailsCauseAlreadyLinkedToAnotherAccountWithUserIdMa AuthRecipeUserInfo emailPasswordUser1 = EmailPassword.signUp(process.getProcess(), "test@example.com", "pass1234"); - UserIdMapping.createUserIdMapping(process.main, emailPasswordUser1.id, "r1", null, false); + UserIdMapping.createUserIdMapping(process.main, emailPasswordUser1.getSupertokensUserId(), "r1", null, false); AuthRecipeUserInfo emailPasswordUser2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "pass1234"); - UserIdMapping.createUserIdMapping(process.main, emailPasswordUser2.id, "r2", null, false); + UserIdMapping.createUserIdMapping(process.main, emailPasswordUser2.getSupertokensUserId(), "r2", null, false); - AuthRecipe.createPrimaryUser(process.main, emailPasswordUser1.id); - AuthRecipe.linkAccounts(process.main, emailPasswordUser2.id, emailPasswordUser1.id); + AuthRecipe.createPrimaryUser(process.main, emailPasswordUser1.getSupertokensUserId()); + AuthRecipe.linkAccounts(process.main, emailPasswordUser2.getSupertokensUserId(), emailPasswordUser1.getSupertokensUserId()); AuthRecipeUserInfo emailPasswordUser3 = EmailPassword.signUp(process.getProcess(), "test3@example.com", "pass1234"); - UserIdMapping.createUserIdMapping(process.main, emailPasswordUser3.id, "r3", null, false); + UserIdMapping.createUserIdMapping(process.main, emailPasswordUser3.getSupertokensUserId(), "r3", null, false); - AuthRecipe.createPrimaryUser(process.main, emailPasswordUser3.id); + AuthRecipe.createPrimaryUser(process.main, emailPasswordUser3.getSupertokensUserId()); { Map params = new HashMap<>(); @@ -425,12 +425,13 @@ public void makingPrimaryUserFailsCauseAlreadyLinkedToAnotherAccountWithUserIdMa JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", "http://localhost:3567/recipe/accountlinking/user/link/check", params, 1000, 1000, null, WebserverAPI.getLatestCDIVersion().get(), ""); - assertEquals(3, response.entrySet().size()); + assertEquals(4, response.entrySet().size()); assertEquals("RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR", response.get("status").getAsString()); assertEquals("r1", response.get("primaryUserId").getAsString()); assertEquals("The input recipe user ID is already linked to another user ID", response.get("description").getAsString()); + assertTrue(response.has("user")); } process.kill(); @@ -457,8 +458,8 @@ public void inputUserIsNotAPrimaryUserTest() throws Exception { { Map params = new HashMap<>(); - params.put("recipeUserId", user.id); - params.put("primaryUserId", user2.id); + params.put("recipeUserId", user.getSupertokensUserId()); + params.put("primaryUserId", user2.getSupertokensUserId()); JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", "http://localhost:3567/recipe/accountlinking/user/link/check", params, 1000, 1000, null, diff --git a/src/test/java/io/supertokens/test/accountlinking/api/CreatePrimaryUserAPITest.java b/src/test/java/io/supertokens/test/accountlinking/api/CreatePrimaryUserAPITest.java index 5b6643302..eada034c6 100644 --- a/src/test/java/io/supertokens/test/accountlinking/api/CreatePrimaryUserAPITest.java +++ b/src/test/java/io/supertokens/test/accountlinking/api/CreatePrimaryUserAPITest.java @@ -78,7 +78,7 @@ public void createReturnsSucceeds() throws Exception { JsonObject userObj; { JsonObject params = new JsonObject(); - params.addProperty("recipeUserId", user.id); + params.addProperty("recipeUserId", user.getSupertokensUserId()); JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/recipe/accountlinking/user/primary", params, 1000, 1000, null, @@ -89,7 +89,7 @@ public void createReturnsSucceeds() throws Exception { // check user object JsonObject jsonUser = response.get("user").getAsJsonObject(); - assert (jsonUser.get("id").getAsString().equals(user.id)); + assert (jsonUser.get("id").getAsString().equals(user.getSupertokensUserId())); assert (jsonUser.get("timeJoined").getAsLong() == user.timeJoined); assert (jsonUser.get("isPrimaryUser").getAsBoolean()); assert (jsonUser.get("emails").getAsJsonArray().size() == 1); @@ -100,18 +100,18 @@ public void createReturnsSucceeds() throws Exception { JsonObject lM = jsonUser.get("loginMethods").getAsJsonArray().get(0).getAsJsonObject(); assertFalse(lM.get("verified").getAsBoolean()); assertEquals(lM.get("timeJoined").getAsLong(), user.timeJoined); - assertEquals(lM.get("recipeUserId").getAsString(), user.id); + assertEquals(lM.get("recipeUserId").getAsString(), user.getSupertokensUserId()); assertEquals(lM.get("recipeId").getAsString(), "emailpassword"); assertEquals(lM.get("email").getAsString(), "test@example.com"); assert (lM.entrySet().size() == 6); userObj = jsonUser; } - AuthRecipe.createPrimaryUser(process.main, user.id); + AuthRecipe.createPrimaryUser(process.main, user.getSupertokensUserId()); { JsonObject params = new JsonObject(); - params.addProperty("recipeUserId", user.id); + params.addProperty("recipeUserId", user.getSupertokensUserId()); JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/recipe/accountlinking/user/primary", params, 1000, 1000, null, @@ -141,7 +141,7 @@ public void createReturnsTrueWithUserIdMapping() throws Exception { } AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "abcd1234"); - UserIdMapping.createUserIdMapping(process.main, user.id, "r1", null, false); + UserIdMapping.createUserIdMapping(process.main, user.getSupertokensUserId(), "r1", null, false); JsonObject userObj; { @@ -174,7 +174,7 @@ public void createReturnsTrueWithUserIdMapping() throws Exception { userObj = jsonUser; } - AuthRecipe.createPrimaryUser(process.main, user.id); + AuthRecipe.createPrimaryUser(process.main, user.getSupertokensUserId()); { JsonObject params = new JsonObject(); @@ -191,7 +191,7 @@ public void createReturnsTrueWithUserIdMapping() throws Exception { { JsonObject params = new JsonObject(); - params.addProperty("recipeUserId", user.id); + params.addProperty("recipeUserId", user.getSupertokensUserId()); JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/recipe/accountlinking/user/primary", params, 1000, 1000, null, @@ -269,7 +269,7 @@ public void makePrimaryUserFailsCauseAnotherAccountWithSameEmailAlreadyAPrimaryU AuthRecipeUserInfo emailPasswordUser = EmailPassword.signUp(process.getProcess(), "test@example.com", "pass1234"); - AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.createPrimaryUser(process.main, emailPasswordUser.id); + AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.createPrimaryUser(process.main, emailPasswordUser.getSupertokensUserId()); assert (!result.wasAlreadyAPrimaryUser); ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(process.main, "google", "user-google", @@ -277,7 +277,7 @@ public void makePrimaryUserFailsCauseAnotherAccountWithSameEmailAlreadyAPrimaryU { JsonObject params = new JsonObject(); - params.addProperty("recipeUserId", signInUpResponse.user.id); + params.addProperty("recipeUserId", signInUpResponse.user.getSupertokensUserId()); JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/recipe/accountlinking/user/primary", params, 1000, 1000, null, @@ -285,7 +285,7 @@ public void makePrimaryUserFailsCauseAnotherAccountWithSameEmailAlreadyAPrimaryU assertEquals(3, response.entrySet().size()); assertEquals("ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR", response.get("status").getAsString()); - assertEquals(emailPasswordUser.id, response.get("primaryUserId").getAsString()); + assertEquals(emailPasswordUser.getSupertokensUserId(), response.get("primaryUserId").getAsString()); assertEquals("This user's email is already associated with another user ID", response.get("description").getAsString()); } @@ -309,12 +309,12 @@ public void makingPrimaryUserFailsCauseAlreadyLinkedToAnotherAccount() throws Ex AuthRecipeUserInfo emailPasswordUser2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "pass1234"); - AuthRecipe.createPrimaryUser(process.main, emailPasswordUser1.id); - AuthRecipe.linkAccounts(process.main, emailPasswordUser2.id, emailPasswordUser1.id); + AuthRecipe.createPrimaryUser(process.main, emailPasswordUser1.getSupertokensUserId()); + AuthRecipe.linkAccounts(process.main, emailPasswordUser2.getSupertokensUserId(), emailPasswordUser1.getSupertokensUserId()); { JsonObject params = new JsonObject(); - params.addProperty("recipeUserId", emailPasswordUser2.id); + params.addProperty("recipeUserId", emailPasswordUser2.getSupertokensUserId()); JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/recipe/accountlinking/user/primary", params, 1000, 1000, null, @@ -322,7 +322,7 @@ public void makingPrimaryUserFailsCauseAlreadyLinkedToAnotherAccount() throws Ex assertEquals(3, response.entrySet().size()); assertEquals("RECIPE_USER_ID_ALREADY_LINKED_WITH_PRIMARY_USER_ID_ERROR", response.get("status").getAsString()); - assertEquals(emailPasswordUser1.id, response.get("primaryUserId").getAsString()); + assertEquals(emailPasswordUser1.getSupertokensUserId(), response.get("primaryUserId").getAsString()); assertEquals("This user ID is already linked to another user ID", response.get("description").getAsString()); } @@ -344,9 +344,9 @@ public void makePrimaryUserFailsCauseAnotherAccountWithSameEmailAlreadyAPrimaryU AuthRecipeUserInfo emailPasswordUser = EmailPassword.signUp(process.getProcess(), "test@example.com", "pass1234"); - UserIdMapping.createUserIdMapping(process.main, emailPasswordUser.id, "r1", null, false); + UserIdMapping.createUserIdMapping(process.main, emailPasswordUser.getSupertokensUserId(), "r1", null, false); - AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.createPrimaryUser(process.main, emailPasswordUser.id); + AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.createPrimaryUser(process.main, emailPasswordUser.getSupertokensUserId()); assert (!result.wasAlreadyAPrimaryUser); ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(process.main, "google", "user-google", @@ -354,7 +354,7 @@ public void makePrimaryUserFailsCauseAnotherAccountWithSameEmailAlreadyAPrimaryU { JsonObject params = new JsonObject(); - params.addProperty("recipeUserId", signInUpResponse.user.id); + params.addProperty("recipeUserId", signInUpResponse.user.getSupertokensUserId()); JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/recipe/accountlinking/user/primary", params, 1000, 1000, null, @@ -383,16 +383,16 @@ public void makingPrimaryUserFailsCauseAlreadyLinkedToAnotherAccountWithUserIdMa AuthRecipeUserInfo emailPasswordUser1 = EmailPassword.signUp(process.getProcess(), "test@example.com", "pass1234"); - UserIdMapping.createUserIdMapping(process.main, emailPasswordUser1.id, "r1", null, false); + UserIdMapping.createUserIdMapping(process.main, emailPasswordUser1.getSupertokensUserId(), "r1", null, false); AuthRecipeUserInfo emailPasswordUser2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "pass1234"); - AuthRecipe.createPrimaryUser(process.main, emailPasswordUser1.id); - AuthRecipe.linkAccounts(process.main, emailPasswordUser2.id, emailPasswordUser1.id); + AuthRecipe.createPrimaryUser(process.main, emailPasswordUser1.getSupertokensUserId()); + AuthRecipe.linkAccounts(process.main, emailPasswordUser2.getSupertokensUserId(), emailPasswordUser1.getSupertokensUserId()); { JsonObject params = new JsonObject(); - params.addProperty("recipeUserId", emailPasswordUser2.id); + params.addProperty("recipeUserId", emailPasswordUser2.getSupertokensUserId()); JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/recipe/accountlinking/user/primary", params, 1000, 1000, null, @@ -447,7 +447,7 @@ public void createPrimaryUserInTenantWithAnotherStorage() throws Exception { JsonObject userObj; { JsonObject params = new JsonObject(); - params.addProperty("recipeUserId", user.id); + params.addProperty("recipeUserId", user.getSupertokensUserId()); JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/recipe/accountlinking/user/primary", params, 1000, 1000, null, @@ -458,7 +458,7 @@ public void createPrimaryUserInTenantWithAnotherStorage() throws Exception { // check user object JsonObject jsonUser = response.get("user").getAsJsonObject(); - assert (jsonUser.get("id").getAsString().equals(user.id)); + assert (jsonUser.get("id").getAsString().equals(user.getSupertokensUserId())); assert (jsonUser.get("tenantIds").getAsJsonArray().size() == 1); assert (jsonUser.get("tenantIds").getAsJsonArray().get(0).getAsString().equals("t1")); assert (jsonUser.get("timeJoined").getAsLong() == user.timeJoined); @@ -473,7 +473,7 @@ public void createPrimaryUserInTenantWithAnotherStorage() throws Exception { assert (lM.get("tenantIds").getAsJsonArray().size() == 1); assert (lM.get("tenantIds").getAsJsonArray().get(0).getAsString().equals("t1")); assertEquals(lM.get("timeJoined").getAsLong(), user.timeJoined); - assertEquals(lM.get("recipeUserId").getAsString(), user.id); + assertEquals(lM.get("recipeUserId").getAsString(), user.getSupertokensUserId()); assertEquals(lM.get("recipeId").getAsString(), "emailpassword"); assertEquals(lM.get("email").getAsString(), "test@example.com"); assert (lM.entrySet().size() == 6); @@ -482,11 +482,11 @@ public void createPrimaryUserInTenantWithAnotherStorage() throws Exception { AuthRecipe.createPrimaryUser(process.main, tenantIdentifier.toAppIdentifier().withStorage(StorageLayer.getStorage(tenantIdentifier, process.main)), - user.id); + user.getSupertokensUserId()); { JsonObject params = new JsonObject(); - params.addProperty("recipeUserId", user.id); + params.addProperty("recipeUserId", user.getSupertokensUserId()); JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/recipe/accountlinking/user/primary", params, 1000, 1000, null, @@ -517,7 +517,7 @@ public void createReturnsFailsWithoutFeatureEnabled() throws Exception { JsonObject userObj; { JsonObject params = new JsonObject(); - params.addProperty("recipeUserId", user.id); + params.addProperty("recipeUserId", user.getSupertokensUserId()); try { HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", diff --git a/src/test/java/io/supertokens/test/accountlinking/api/GetUserByAccountInfoTest.java b/src/test/java/io/supertokens/test/accountlinking/api/GetUserByAccountInfoTest.java index 50d2e1320..8d3d803d7 100644 --- a/src/test/java/io/supertokens/test/accountlinking/api/GetUserByAccountInfoTest.java +++ b/src/test/java/io/supertokens/test/accountlinking/api/GetUserByAccountInfoTest.java @@ -50,9 +50,7 @@ import java.io.IOException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; -import java.util.Arrays; import java.util.HashMap; -import java.util.List; import java.util.Map; import static org.junit.Assert.*; @@ -146,10 +144,10 @@ public void testListUsersByAccountInfoForUnlinkedAccounts() throws Exception { AuthRecipeUserInfo user3 = createPasswordlessUserWithEmail(process.getProcess(), "test3@example.com"); AuthRecipeUserInfo user4 = createPasswordlessUserWithPhone(process.getProcess(), "+919876543210"); - JsonObject user1json = getUserById(process.getProcess(), user1.id); - JsonObject user2json = getUserById(process.getProcess(), user2.id); - JsonObject user3json = getUserById(process.getProcess(), user3.id); - JsonObject user4json = getUserById(process.getProcess(), user4.id); + JsonObject user1json = getUserById(process.getProcess(), user1.getSupertokensUserId()); + JsonObject user2json = getUserById(process.getProcess(), user2.getSupertokensUserId()); + JsonObject user3json = getUserById(process.getProcess(), user3.getSupertokensUserId()); + JsonObject user4json = getUserById(process.getProcess(), user4.getSupertokensUserId()); // test for result assertEquals(user1json, getUsersByAccountInfo(process.getProcess(), false, "test1@example.com", null, null, null).get(0)); @@ -185,10 +183,10 @@ public void testListUsersByAccountInfoForUnlinkedAccountsWithUnionOption() throw AuthRecipeUserInfo user3 = createPasswordlessUserWithEmail(process.getProcess(), "test3@example.com"); AuthRecipeUserInfo user4 = createPasswordlessUserWithPhone(process.getProcess(), "+919876543210"); - JsonObject user1json = getUserById(process.getProcess(), user1.id); - JsonObject user2json = getUserById(process.getProcess(), user2.id); - JsonObject user3json = getUserById(process.getProcess(), user3.id); - JsonObject user4json = getUserById(process.getProcess(), user4.id); + JsonObject user1json = getUserById(process.getProcess(), user1.getSupertokensUserId()); + JsonObject user2json = getUserById(process.getProcess(), user2.getSupertokensUserId()); + JsonObject user3json = getUserById(process.getProcess(), user3.getSupertokensUserId()); + JsonObject user4json = getUserById(process.getProcess(), user4.getSupertokensUserId()); TenantIdentifierWithStorage tenantIdentifierWithStorage = TenantIdentifier.BASE_TENANT.withStorage(StorageLayer.getBaseStorage(process.getProcess())); { @@ -255,13 +253,13 @@ public void testListUserByAccountInfoWhenAccountsAreLinked1() throws Exception { Thread.sleep(50); AuthRecipeUserInfo user2 = ThirdParty.signInUp(process.getProcess(), "google", "userid1", "test2@example.com").user; - AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.id).user; - AuthRecipe.linkAccounts(process.getProcess(), user2.id, primaryUser.id); + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.getSupertokensUserId()).user; + AuthRecipe.linkAccounts(process.getProcess(), user2.getSupertokensUserId(), primaryUser.getSupertokensUserId()); TenantIdentifierWithStorage tenantIdentifierWithStorage = TenantIdentifier.BASE_TENANT.withStorage( StorageLayer.getBaseStorage(process.getProcess())); - JsonObject primaryUserJson = getUserById(process.getProcess(), user1.id); + JsonObject primaryUserJson = getUserById(process.getProcess(), user1.getSupertokensUserId()); assertEquals(primaryUserJson, getUsersByAccountInfo(process.getProcess(), false, "test1@example.com", null, null, null).get(0)); @@ -292,10 +290,10 @@ public void testListUserByAccountInfoWhenAccountsAreLinked2() throws Exception { Thread.sleep(50); AuthRecipeUserInfo user2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "password2"); - AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.id).user; - AuthRecipe.linkAccounts(process.getProcess(), user2.id, primaryUser.id); + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.getSupertokensUserId()).user; + AuthRecipe.linkAccounts(process.getProcess(), user2.getSupertokensUserId(), primaryUser.getSupertokensUserId()); - JsonObject primaryUserJson = getUserById(process.getProcess(), user1.id); + JsonObject primaryUserJson = getUserById(process.getProcess(), user1.getSupertokensUserId()); assertEquals(primaryUserJson, getUsersByAccountInfo(process.getProcess(), false, "test1@example.com", null, null, null).get(0)); @@ -320,13 +318,13 @@ public void testListUserByAccountInfoWhenAccountsAreLinked3() throws Exception { Thread.sleep(50); AuthRecipeUserInfo user2 = createPasswordlessUserWithEmail(process.getProcess(), "test2@example.com"); - AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.id).user; - AuthRecipe.linkAccounts(process.getProcess(), user2.id, primaryUser.id); + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.getSupertokensUserId()).user; + AuthRecipe.linkAccounts(process.getProcess(), user2.getSupertokensUserId(), primaryUser.getSupertokensUserId()); TenantIdentifierWithStorage tenantIdentifierWithStorage = TenantIdentifier.BASE_TENANT.withStorage( StorageLayer.getBaseStorage(process.getProcess())); - JsonObject primaryUserJson = getUserById(process.getProcess(), user1.id); + JsonObject primaryUserJson = getUserById(process.getProcess(), user1.getSupertokensUserId()); assertEquals(primaryUserJson, getUsersByAccountInfo(process.getProcess(), false, "test1@example.com", null, null, null).get(0)); @@ -351,13 +349,13 @@ public void testListUserByAccountInfoWhenAccountsAreLinked4() throws Exception { Thread.sleep(50); AuthRecipeUserInfo user2 = createPasswordlessUserWithPhone(process.getProcess(), "+919876543210"); - AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.id).user; - AuthRecipe.linkAccounts(process.getProcess(), user2.id, primaryUser.id); + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.getSupertokensUserId()).user; + AuthRecipe.linkAccounts(process.getProcess(), user2.getSupertokensUserId(), primaryUser.getSupertokensUserId()); TenantIdentifierWithStorage tenantIdentifierWithStorage = TenantIdentifier.BASE_TENANT.withStorage( StorageLayer.getBaseStorage(process.getProcess())); - JsonObject primaryUserJson = getUserById(process.getProcess(), user1.id); + JsonObject primaryUserJson = getUserById(process.getProcess(), user1.getSupertokensUserId()); assertEquals(primaryUserJson, getUsersByAccountInfo(process.getProcess(), false, "test1@example.com", null, null, null).get(0)); @@ -384,13 +382,13 @@ public void testListUserByAccountInfoWhenAccountsAreLinked5() throws Exception { Thread.sleep(50); AuthRecipeUserInfo user2 = createThirdPartyUser(process.getProcess(), "google", "userid1", "test2@example.com"); - AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.id).user; - AuthRecipe.linkAccounts(process.getProcess(), user2.id, primaryUser.id); + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.getSupertokensUserId()).user; + AuthRecipe.linkAccounts(process.getProcess(), user2.getSupertokensUserId(), primaryUser.getSupertokensUserId()); TenantIdentifierWithStorage tenantIdentifierWithStorage = TenantIdentifier.BASE_TENANT.withStorage( StorageLayer.getBaseStorage(process.getProcess())); - JsonObject primaryUserJson = getUserById(process.getProcess(), user1.id); + JsonObject primaryUserJson = getUserById(process.getProcess(), user1.getSupertokensUserId()); assertEquals(primaryUserJson, getUsersByAccountInfo(process.getProcess(), false, "test1@example.com", null, null, null).get(0)); @@ -425,14 +423,14 @@ public void testWithUserIdMapping() throws Exception { Thread.sleep(50); AuthRecipeUserInfo user4 = createPasswordlessUserWithPhone(process.getProcess(), "+919876543210"); - UserIdMapping.createUserIdMapping(process.getProcess(), user1.id, "ext1", "", false); - UserIdMapping.createUserIdMapping(process.getProcess(), user2.id, "ext2", "", false); - UserIdMapping.createUserIdMapping(process.getProcess(), user3.id, "ext3", "", false); + UserIdMapping.createUserIdMapping(process.getProcess(), user1.getSupertokensUserId(), "ext1", "", false); + UserIdMapping.createUserIdMapping(process.getProcess(), user2.getSupertokensUserId(), "ext2", "", false); + UserIdMapping.createUserIdMapping(process.getProcess(), user3.getSupertokensUserId(), "ext3", "", false); - AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.id).user; - AuthRecipe.linkAccounts(process.getProcess(), user2.id, primaryUser.id); - AuthRecipe.linkAccounts(process.getProcess(), user3.id, primaryUser.id); - AuthRecipe.linkAccounts(process.getProcess(), user4.id, primaryUser.id); + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.getSupertokensUserId()).user; + AuthRecipe.linkAccounts(process.getProcess(), user2.getSupertokensUserId(), primaryUser.getSupertokensUserId()); + AuthRecipe.linkAccounts(process.getProcess(), user3.getSupertokensUserId(), primaryUser.getSupertokensUserId()); + AuthRecipe.linkAccounts(process.getProcess(), user4.getSupertokensUserId(), primaryUser.getSupertokensUserId()); JsonObject primaryUserInfo = getUsersByAccountInfo(process.getProcess(), false, "test@example.com", null, null, null).get(0).getAsJsonObject(); diff --git a/src/test/java/io/supertokens/test/accountlinking/api/GetUserByIdTest.java b/src/test/java/io/supertokens/test/accountlinking/api/GetUserByIdTest.java index f63106c5d..8b8246bae 100644 --- a/src/test/java/io/supertokens/test/accountlinking/api/GetUserByIdTest.java +++ b/src/test/java/io/supertokens/test/accountlinking/api/GetUserByIdTest.java @@ -116,12 +116,12 @@ public void testJsonStructure() throws Exception { Thread.sleep(50); AuthRecipeUserInfo user4 = createPasswordlessUserWithPhone(process.getProcess(), "+919876543210"); - AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.id).user; - AuthRecipe.linkAccounts(process.getProcess(), user2.id, primaryUser.id); - AuthRecipe.linkAccounts(process.getProcess(), user3.id, primaryUser.id); - AuthRecipe.linkAccounts(process.getProcess(), user4.id, primaryUser.id); + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.getSupertokensUserId()).user; + AuthRecipe.linkAccounts(process.getProcess(), user2.getSupertokensUserId(), primaryUser.getSupertokensUserId()); + AuthRecipe.linkAccounts(process.getProcess(), user3.getSupertokensUserId(), primaryUser.getSupertokensUserId()); + AuthRecipe.linkAccounts(process.getProcess(), user4.getSupertokensUserId(), primaryUser.getSupertokensUserId()); - for (String userId : new String[]{user1.id, user2.id, user3.id, user4.id}) { + for (String userId : new String[]{user1.getSupertokensUserId(), user2.getSupertokensUserId(), user3.getSupertokensUserId(), user4.getSupertokensUserId()}) { Map params = new HashMap<>(); params.put("userId", userId); JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", @@ -174,11 +174,11 @@ public void testThatEmailIsAUnionOfLinkedAccounts1() throws Exception { Thread.sleep(50); AuthRecipeUserInfo user3 = createPasswordlessUserWithEmail(process.getProcess(), "test3@example.com"); - AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.id).user; - AuthRecipe.linkAccounts(process.getProcess(), user2.id, primaryUser.id); - AuthRecipe.linkAccounts(process.getProcess(), user3.id, primaryUser.id); + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.getSupertokensUserId()).user; + AuthRecipe.linkAccounts(process.getProcess(), user2.getSupertokensUserId(), primaryUser.getSupertokensUserId()); + AuthRecipe.linkAccounts(process.getProcess(), user3.getSupertokensUserId(), primaryUser.getSupertokensUserId()); - for (String userId : new String[]{user1.id, user2.id, user3.id}) { + for (String userId : new String[]{user1.getSupertokensUserId(), user2.getSupertokensUserId(), user3.getSupertokensUserId()}) { Map params = new HashMap<>(); params.put("userId", userId); JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", @@ -214,10 +214,10 @@ public void testThatEmailIsAUnionOfLinkedAccounts2() throws Exception { Thread.sleep(50); AuthRecipeUserInfo user2 = createThirdPartyUser(process.getProcess(), "google", "userid1", "test2@example.com"); - AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.id).user; - AuthRecipe.linkAccounts(process.getProcess(), user2.id, primaryUser.id); + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.getSupertokensUserId()).user; + AuthRecipe.linkAccounts(process.getProcess(), user2.getSupertokensUserId(), primaryUser.getSupertokensUserId()); - for (String userId : new String[]{user1.id, user2.id}) { + for (String userId : new String[]{user1.getSupertokensUserId(), user2.getSupertokensUserId()}) { Map params = new HashMap<>(); params.put("userId", userId); JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", @@ -252,10 +252,10 @@ public void testThatEmailIsAUnionOfLinkedAccounts3() throws Exception { Thread.sleep(50); AuthRecipeUserInfo user2 = createPasswordlessUserWithEmail(process.getProcess(), "test2@example.com"); - AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.id).user; - AuthRecipe.linkAccounts(process.getProcess(), user2.id, primaryUser.id); + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.getSupertokensUserId()).user; + AuthRecipe.linkAccounts(process.getProcess(), user2.getSupertokensUserId(), primaryUser.getSupertokensUserId()); - for (String userId : new String[]{user1.id, user2.id}) { + for (String userId : new String[]{user1.getSupertokensUserId(), user2.getSupertokensUserId()}) { Map params = new HashMap<>(); params.put("userId", userId); JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", @@ -290,10 +290,10 @@ public void testThatEmailIsAUnionOfLinkedAccounts4() throws Exception { Thread.sleep(50); AuthRecipeUserInfo user2 = createPasswordlessUserWithEmail(process.getProcess(), "test2@example.com"); - AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.id).user; - AuthRecipe.linkAccounts(process.getProcess(), user2.id, primaryUser.id); + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.getSupertokensUserId()).user; + AuthRecipe.linkAccounts(process.getProcess(), user2.getSupertokensUserId(), primaryUser.getSupertokensUserId()); - for (String userId : new String[]{user1.id, user2.id}) { + for (String userId : new String[]{user1.getSupertokensUserId(), user2.getSupertokensUserId()}) { Map params = new HashMap<>(); params.put("userId", userId); JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", @@ -328,10 +328,10 @@ public void testThatPhoneNumberIsUnionOfLinkedAccounts1() throws Exception { Thread.sleep(50); AuthRecipeUserInfo user2 = createPasswordlessUserWithPhone(process.getProcess(), "+911234567890"); - AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.id).user; - AuthRecipe.linkAccounts(process.getProcess(), user2.id, primaryUser.id); + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.getSupertokensUserId()).user; + AuthRecipe.linkAccounts(process.getProcess(), user2.getSupertokensUserId(), primaryUser.getSupertokensUserId()); - for (String userId : new String[]{user1.id, user2.id}) { + for (String userId : new String[]{user1.getSupertokensUserId(), user2.getSupertokensUserId()}) { Map params = new HashMap<>(); params.put("userId", userId); JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", @@ -368,11 +368,11 @@ public void testThatPhoneNumberIsUnionOfLinkedAccounts2() throws Exception { Thread.sleep(50); AuthRecipeUserInfo user3 = createThirdPartyUser(process.getProcess(), "google", "googleid", "test@example.com"); - AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.id).user; - AuthRecipe.linkAccounts(process.getProcess(), user2.id, primaryUser.id); - AuthRecipe.linkAccounts(process.getProcess(), user3.id, primaryUser.id); + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.getSupertokensUserId()).user; + AuthRecipe.linkAccounts(process.getProcess(), user2.getSupertokensUserId(), primaryUser.getSupertokensUserId()); + AuthRecipe.linkAccounts(process.getProcess(), user3.getSupertokensUserId(), primaryUser.getSupertokensUserId()); - for (String userId : new String[]{user1.id, user2.id}) { + for (String userId : new String[]{user1.getSupertokensUserId(), user2.getSupertokensUserId()}) { Map params = new HashMap<>(); params.put("userId", userId); JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", @@ -409,11 +409,11 @@ public void testThatPhoneNumberIsUnionOfLinkedAccounts3() throws Exception { Thread.sleep(50); AuthRecipeUserInfo user3 = createEmailPasswordUser(process.getProcess(), "test@example.com", "password"); - AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.id).user; - AuthRecipe.linkAccounts(process.getProcess(), user2.id, primaryUser.id); - AuthRecipe.linkAccounts(process.getProcess(), user3.id, primaryUser.id); + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.getSupertokensUserId()).user; + AuthRecipe.linkAccounts(process.getProcess(), user2.getSupertokensUserId(), primaryUser.getSupertokensUserId()); + AuthRecipe.linkAccounts(process.getProcess(), user3.getSupertokensUserId(), primaryUser.getSupertokensUserId()); - for (String userId : new String[]{user1.id, user2.id}) { + for (String userId : new String[]{user1.getSupertokensUserId(), user2.getSupertokensUserId()}) { Map params = new HashMap<>(); params.put("userId", userId); JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", @@ -452,16 +452,16 @@ public void testWithUserIdMapping() throws Exception { Thread.sleep(50); AuthRecipeUserInfo user4 = createPasswordlessUserWithPhone(process.getProcess(), "+919876543210"); - UserIdMapping.createUserIdMapping(process.getProcess(), user1.id, "ext1", "", false); - UserIdMapping.createUserIdMapping(process.getProcess(), user2.id, "ext2", "", false); - UserIdMapping.createUserIdMapping(process.getProcess(), user3.id, "ext3", "", false); + UserIdMapping.createUserIdMapping(process.getProcess(), user1.getSupertokensUserId(), "ext1", "", false); + UserIdMapping.createUserIdMapping(process.getProcess(), user2.getSupertokensUserId(), "ext2", "", false); + UserIdMapping.createUserIdMapping(process.getProcess(), user3.getSupertokensUserId(), "ext3", "", false); - AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.id).user; - AuthRecipe.linkAccounts(process.getProcess(), user2.id, primaryUser.id); - AuthRecipe.linkAccounts(process.getProcess(), user3.id, primaryUser.id); - AuthRecipe.linkAccounts(process.getProcess(), user4.id, primaryUser.id); + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.getSupertokensUserId()).user; + AuthRecipe.linkAccounts(process.getProcess(), user2.getSupertokensUserId(), primaryUser.getSupertokensUserId()); + AuthRecipe.linkAccounts(process.getProcess(), user3.getSupertokensUserId(), primaryUser.getSupertokensUserId()); + AuthRecipe.linkAccounts(process.getProcess(), user4.getSupertokensUserId(), primaryUser.getSupertokensUserId()); - for (String userId : new String[]{user1.id, user2.id, user3.id, user4.id, "ext1", "ext2", "ext3"}) { + for (String userId : new String[]{user1.getSupertokensUserId(), user2.getSupertokensUserId(), user3.getSupertokensUserId(), user4.getSupertokensUserId(), "ext1", "ext2", "ext3"}) { Map params = new HashMap<>(); params.put("userId", userId); JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", @@ -470,8 +470,8 @@ public void testWithUserIdMapping() throws Exception { JsonObject user = response.get("user").getAsJsonObject(); assertEquals("ext1", user.get("id").getAsString()); assertEquals("ext1", user.get("loginMethods").getAsJsonArray().get(0).getAsJsonObject().get("recipeUserId").getAsString()); - assertNotEquals("ext2", user.get("loginMethods").getAsJsonArray().get(1).getAsJsonObject().get("recipeUserId").getAsString()); // TODO should be equal once userIdMapping is fixed - assertNotEquals("ext3", user.get("loginMethods").getAsJsonArray().get(2).getAsJsonObject().get("recipeUserId").getAsString()); // TODO should be equal once userIdMapping is fixed + assertEquals("ext2", user.get("loginMethods").getAsJsonArray().get(1).getAsJsonObject().get("recipeUserId").getAsString()); + assertEquals("ext3", user.get("loginMethods").getAsJsonArray().get(2).getAsJsonObject().get("recipeUserId").getAsString()); } process.kill(); diff --git a/src/test/java/io/supertokens/test/accountlinking/api/LinkAccountsAPITest.java b/src/test/java/io/supertokens/test/accountlinking/api/LinkAccountsAPITest.java index b28e2aaf6..a75e3ef9b 100644 --- a/src/test/java/io/supertokens/test/accountlinking/api/LinkAccountsAPITest.java +++ b/src/test/java/io/supertokens/test/accountlinking/api/LinkAccountsAPITest.java @@ -72,34 +72,36 @@ public void linkReturnsTrue() throws Exception { AuthRecipeUserInfo user2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "abcd1234"); - AuthRecipe.createPrimaryUser(process.main, user2.id); + AuthRecipe.createPrimaryUser(process.main, user2.getSupertokensUserId()); { JsonObject params = new JsonObject(); - params.addProperty("recipeUserId", user.id); - params.addProperty("primaryUserId", user2.id); + params.addProperty("recipeUserId", user.getSupertokensUserId()); + params.addProperty("primaryUserId", user2.getSupertokensUserId()); JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/recipe/accountlinking/user/link", params, 1000, 1000, null, WebserverAPI.getLatestCDIVersion().get(), ""); - assertEquals(2, response.entrySet().size()); + assertEquals(3, response.entrySet().size()); assertEquals("OK", response.get("status").getAsString()); assertFalse(response.get("accountsAlreadyLinked").getAsBoolean()); + assertTrue(response.has("user")); } - AuthRecipe.linkAccounts(process.main, user.id, user2.id); + AuthRecipe.linkAccounts(process.main, user.getSupertokensUserId(), user2.getSupertokensUserId()); { JsonObject params = new JsonObject(); - params.addProperty("recipeUserId", user.id); - params.addProperty("primaryUserId", user2.id); + params.addProperty("recipeUserId", user.getSupertokensUserId()); + params.addProperty("primaryUserId", user2.getSupertokensUserId()); JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/recipe/accountlinking/user/link", params, 1000, 1000, null, WebserverAPI.getLatestCDIVersion().get(), ""); - assertEquals(2, response.entrySet().size()); + assertEquals(3, response.entrySet().size()); assertEquals("OK", response.get("status").getAsString()); assertTrue(response.get("accountsAlreadyLinked").getAsBoolean()); + assertTrue(response.has("user")); } process.kill(); @@ -121,12 +123,12 @@ public void canLinkReturnsTrueWithUserIdMapping() throws Exception { } AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "abcd1234"); - UserIdMapping.createUserIdMapping(process.main, user.id, "r1", null, false); + UserIdMapping.createUserIdMapping(process.main, user.getSupertokensUserId(), "r1", null, false); AuthRecipeUserInfo user2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "abcd1234"); - UserIdMapping.createUserIdMapping(process.main, user2.id, "r2", null, false); + UserIdMapping.createUserIdMapping(process.main, user2.getSupertokensUserId(), "r2", null, false); - AuthRecipe.createPrimaryUser(process.main, user2.id); + AuthRecipe.createPrimaryUser(process.main, user2.getSupertokensUserId()); { JsonObject params = new JsonObject(); @@ -136,12 +138,13 @@ public void canLinkReturnsTrueWithUserIdMapping() throws Exception { JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/recipe/accountlinking/user/link", params, 1000, 1000, null, WebserverAPI.getLatestCDIVersion().get(), ""); - assertEquals(2, response.entrySet().size()); + assertEquals(3, response.entrySet().size()); assertEquals("OK", response.get("status").getAsString()); assertFalse(response.get("accountsAlreadyLinked").getAsBoolean()); + assertTrue(response.has("user")); } - AuthRecipe.linkAccounts(process.main, user.id, user2.id); + AuthRecipe.linkAccounts(process.main, user.getSupertokensUserId(), user2.getSupertokensUserId()); { JsonObject params = new JsonObject(); @@ -151,9 +154,10 @@ public void canLinkReturnsTrueWithUserIdMapping() throws Exception { JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/recipe/accountlinking/user/link", params, 1000, 1000, null, WebserverAPI.getLatestCDIVersion().get(), ""); - assertEquals(2, response.entrySet().size()); + assertEquals(3, response.entrySet().size()); assertEquals("OK", response.get("status").getAsString()); assertTrue(response.get("accountsAlreadyLinked").getAsBoolean()); + assertTrue(response.has("user")); } process.kill(); @@ -208,13 +212,13 @@ public void canLinkUserBadInput() throws Exception { } AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "abcd1234"); - AuthRecipe.createPrimaryUser(process.main, user.id); + AuthRecipe.createPrimaryUser(process.main, user.getSupertokensUserId()); AuthRecipeUserInfo user2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "abcd1234"); { JsonObject params = new JsonObject(); - params.addProperty("recipeUserId", user2.id); + params.addProperty("recipeUserId", user2.getSupertokensUserId()); params.addProperty("primaryUserId", "random"); try { @@ -232,7 +236,7 @@ public void canLinkUserBadInput() throws Exception { { JsonObject params = new JsonObject(); params.addProperty("recipeUserId", "random"); - params.addProperty("primaryUserId", user.id); + params.addProperty("primaryUserId", user.getSupertokensUserId()); try { HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", @@ -263,13 +267,13 @@ public void linkingUsersFailsCauseAnotherAccountWithSameEmailAlreadyAPrimaryUser AuthRecipeUserInfo emailPasswordUser = EmailPassword.signUp(process.getProcess(), "test@example.com", "pass1234"); - AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.createPrimaryUser(process.main, emailPasswordUser.id); + AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.createPrimaryUser(process.main, emailPasswordUser.getSupertokensUserId()); assert (!result.wasAlreadyAPrimaryUser); ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(process.main, "google", "user-google", "test2@example.com"); - AuthRecipe.createPrimaryUser(process.main, signInUpResponse.user.id); + AuthRecipe.createPrimaryUser(process.main, signInUpResponse.user.getSupertokensUserId()); ThirdParty.SignInUpResponse signInUpResponse2 = ThirdParty.signInUp(process.main, "fb", "user-fb", "test@example.com"); @@ -277,8 +281,8 @@ public void linkingUsersFailsCauseAnotherAccountWithSameEmailAlreadyAPrimaryUser { JsonObject params = new JsonObject(); - params.addProperty("primaryUserId", signInUpResponse.user.id); - params.addProperty("recipeUserId", signInUpResponse2.user.id); + params.addProperty("primaryUserId", signInUpResponse.user.getSupertokensUserId()); + params.addProperty("recipeUserId", signInUpResponse2.user.getSupertokensUserId()); JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/recipe/accountlinking/user/link", params, 1000, 1000, null, @@ -286,7 +290,7 @@ public void linkingUsersFailsCauseAnotherAccountWithSameEmailAlreadyAPrimaryUser assertEquals(3, response.entrySet().size()); assertEquals("ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR", response.get("status").getAsString()); - assertEquals(emailPasswordUser.id, response.get("primaryUserId").getAsString()); + assertEquals(emailPasswordUser.getSupertokensUserId(), response.get("primaryUserId").getAsString()); assertEquals("This user's email is already associated with another user ID", response.get("description").getAsString()); } @@ -308,20 +312,20 @@ public void linkingUsersFailsCauseAnotherAccountWithSameEmailAlreadyAPrimaryUser AuthRecipeUserInfo emailPasswordUser = EmailPassword.signUp(process.getProcess(), "test@example.com", "pass1234"); - UserIdMapping.createUserIdMapping(process.main, emailPasswordUser.id, "e1", null, false); + UserIdMapping.createUserIdMapping(process.main, emailPasswordUser.getSupertokensUserId(), "e1", null, false); - AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.createPrimaryUser(process.main, emailPasswordUser.id); + AuthRecipe.CreatePrimaryUserResult result = AuthRecipe.createPrimaryUser(process.main, emailPasswordUser.getSupertokensUserId()); assert (!result.wasAlreadyAPrimaryUser); ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(process.main, "google", "user-google", "test2@example.com"); - UserIdMapping.createUserIdMapping(process.main, signInUpResponse.user.id, "e2", null, false); + UserIdMapping.createUserIdMapping(process.main, signInUpResponse.user.getSupertokensUserId(), "e2", null, false); - AuthRecipe.createPrimaryUser(process.main, signInUpResponse.user.id); + AuthRecipe.createPrimaryUser(process.main, signInUpResponse.user.getSupertokensUserId()); ThirdParty.SignInUpResponse signInUpResponse2 = ThirdParty.signInUp(process.main, "fb", "user-fb", "test@example.com"); - UserIdMapping.createUserIdMapping(process.main, signInUpResponse2.user.id, "e3", null, false); + UserIdMapping.createUserIdMapping(process.main, signInUpResponse2.user.getSupertokensUserId(), "e3", null, false); { @@ -359,28 +363,29 @@ public void linkingUserFailsCauseAlreadyLinkedToAnotherAccount() throws Exceptio AuthRecipeUserInfo emailPasswordUser2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "pass1234"); - AuthRecipe.createPrimaryUser(process.main, emailPasswordUser1.id); - AuthRecipe.linkAccounts(process.main, emailPasswordUser2.id, emailPasswordUser1.id); + AuthRecipe.createPrimaryUser(process.main, emailPasswordUser1.getSupertokensUserId()); + AuthRecipe.linkAccounts(process.main, emailPasswordUser2.getSupertokensUserId(), emailPasswordUser1.getSupertokensUserId()); AuthRecipeUserInfo emailPasswordUser3 = EmailPassword.signUp(process.getProcess(), "test3@example.com", "pass1234"); - AuthRecipe.createPrimaryUser(process.main, emailPasswordUser3.id); + AuthRecipe.createPrimaryUser(process.main, emailPasswordUser3.getSupertokensUserId()); { JsonObject params = new JsonObject(); - params.addProperty("recipeUserId", emailPasswordUser2.id); - params.addProperty("primaryUserId", emailPasswordUser3.id); + params.addProperty("recipeUserId", emailPasswordUser2.getSupertokensUserId()); + params.addProperty("primaryUserId", emailPasswordUser3.getSupertokensUserId()); JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/recipe/accountlinking/user/link", params, 1000, 1000, null, WebserverAPI.getLatestCDIVersion().get(), ""); - assertEquals(3, response.entrySet().size()); + assertEquals(4, response.entrySet().size()); assertEquals("RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR", response.get("status").getAsString()); - assertEquals(emailPasswordUser1.id, response.get("primaryUserId").getAsString()); + assertEquals(emailPasswordUser1.getSupertokensUserId(), response.get("primaryUserId").getAsString()); assertEquals("The input recipe user ID is already linked to another user ID", response.get("description").getAsString()); + assertTrue(response.has("user")); } process.kill(); @@ -400,19 +405,19 @@ public void makingPrimaryUserFailsCauseAlreadyLinkedToAnotherAccountWithUserIdMa AuthRecipeUserInfo emailPasswordUser1 = EmailPassword.signUp(process.getProcess(), "test@example.com", "pass1234"); - UserIdMapping.createUserIdMapping(process.main, emailPasswordUser1.id, "r1", null, false); + UserIdMapping.createUserIdMapping(process.main, emailPasswordUser1.getSupertokensUserId(), "r1", null, false); AuthRecipeUserInfo emailPasswordUser2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "pass1234"); - UserIdMapping.createUserIdMapping(process.main, emailPasswordUser2.id, "r2", null, false); + UserIdMapping.createUserIdMapping(process.main, emailPasswordUser2.getSupertokensUserId(), "r2", null, false); - AuthRecipe.createPrimaryUser(process.main, emailPasswordUser1.id); - AuthRecipe.linkAccounts(process.main, emailPasswordUser2.id, emailPasswordUser1.id); + AuthRecipe.createPrimaryUser(process.main, emailPasswordUser1.getSupertokensUserId()); + AuthRecipe.linkAccounts(process.main, emailPasswordUser2.getSupertokensUserId(), emailPasswordUser1.getSupertokensUserId()); AuthRecipeUserInfo emailPasswordUser3 = EmailPassword.signUp(process.getProcess(), "test3@example.com", "pass1234"); - UserIdMapping.createUserIdMapping(process.main, emailPasswordUser3.id, "r3", null, false); + UserIdMapping.createUserIdMapping(process.main, emailPasswordUser3.getSupertokensUserId(), "r3", null, false); - AuthRecipe.createPrimaryUser(process.main, emailPasswordUser3.id); + AuthRecipe.createPrimaryUser(process.main, emailPasswordUser3.getSupertokensUserId()); { JsonObject params = new JsonObject(); @@ -422,12 +427,13 @@ public void makingPrimaryUserFailsCauseAlreadyLinkedToAnotherAccountWithUserIdMa JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/recipe/accountlinking/user/link", params, 1000, 1000, null, WebserverAPI.getLatestCDIVersion().get(), ""); - assertEquals(3, response.entrySet().size()); + assertEquals(4, response.entrySet().size()); assertEquals("RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR", response.get("status").getAsString()); assertEquals("r1", response.get("primaryUserId").getAsString()); assertEquals("The input recipe user ID is already linked to another user ID", response.get("description").getAsString()); + assertTrue(response.has("user")); } process.kill(); @@ -454,8 +460,8 @@ public void inputUserIsNotAPrimaryUserTest() throws Exception { { JsonObject params = new JsonObject(); - params.addProperty("recipeUserId", user.id); - params.addProperty("primaryUserId", user2.id); + params.addProperty("recipeUserId", user.getSupertokensUserId()); + params.addProperty("primaryUserId", user2.getSupertokensUserId()); JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/recipe/accountlinking/user/link", params, 1000, 1000, null, @@ -486,8 +492,8 @@ public void linkReturnsFailsWithoutFeatureEnabled() throws Exception { JsonObject userObj; { JsonObject params = new JsonObject(); - params.addProperty("recipeUserId", user.id); - params.addProperty("primaryUserId", user2.id); + params.addProperty("recipeUserId", user.getSupertokensUserId()); + params.addProperty("primaryUserId", user2.getSupertokensUserId()); try { HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", diff --git a/src/test/java/io/supertokens/test/accountlinking/api/TestRecipeUserIdInSignInUpAPIs.java b/src/test/java/io/supertokens/test/accountlinking/api/TestRecipeUserIdInSignInUpAPIs.java new file mode 100644 index 000000000..acbadcf76 --- /dev/null +++ b/src/test/java/io/supertokens/test/accountlinking/api/TestRecipeUserIdInSignInUpAPIs.java @@ -0,0 +1,765 @@ +/* + * Copyright (c) 2023, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.test.accountlinking.api; + +import com.google.gson.JsonObject; +import io.supertokens.Main; +import io.supertokens.ProcessState; +import io.supertokens.authRecipe.AuthRecipe; +import io.supertokens.emailpassword.EmailPassword; +import io.supertokens.emailpassword.exceptions.EmailChangeNotAllowedException; +import io.supertokens.featureflag.EE_FEATURES; +import io.supertokens.featureflag.FeatureFlagTestContent; +import io.supertokens.passwordless.Passwordless; +import io.supertokens.passwordless.exceptions.*; +import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; +import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; +import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; +import io.supertokens.pluginInterface.passwordless.exception.DuplicateLinkCodeHashException; +import io.supertokens.storageLayer.StorageLayer; +import io.supertokens.test.TestingProcessManager; +import io.supertokens.test.Utils; +import io.supertokens.test.httpRequest.HttpRequestForTesting; +import io.supertokens.thirdparty.ThirdParty; +import io.supertokens.useridmapping.UserIdMapping; +import io.supertokens.utils.SemVer; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; + +import java.io.IOException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +public class TestRecipeUserIdInSignInUpAPIs { + @Rule + public TestRule watchman = Utils.getOnFailure(); + + @AfterClass + public static void afterTesting() { + Utils.afterTesting(); + } + + @Before + public void beforeEach() { + Utils.reset(); + } + + + AuthRecipeUserInfo createEmailPasswordUser(Main main, String email, String password) + throws DuplicateEmailException, StorageQueryException { + return EmailPassword.signUp(main, email, password); + } + + AuthRecipeUserInfo createThirdPartyUser(Main main, String thirdPartyId, String thirdPartyUserId, String email) + throws EmailChangeNotAllowedException, StorageQueryException { + return ThirdParty.signInUp(main, thirdPartyId, thirdPartyUserId, email).user; + } + + AuthRecipeUserInfo createPasswordlessUserWithEmail(Main main, String email) + throws DuplicateLinkCodeHashException, StorageQueryException, NoSuchAlgorithmException, IOException, + RestartFlowException, InvalidKeyException, Base64EncodingException, DeviceIdHashMismatchException, + StorageTransactionLogicException, IncorrectUserInputCodeException, ExpiredUserInputCodeException { + Passwordless.CreateCodeResponse code = Passwordless.createCode(main, email, null, + null, "123456"); + return Passwordless.consumeCode(main, code.deviceId, code.deviceIdHash, + code.userInputCode, null).user; + } + + AuthRecipeUserInfo createPasswordlessUserWithPhone(Main main, String phone) + throws DuplicateLinkCodeHashException, StorageQueryException, NoSuchAlgorithmException, IOException, + RestartFlowException, InvalidKeyException, Base64EncodingException, DeviceIdHashMismatchException, + StorageTransactionLogicException, IncorrectUserInputCodeException, ExpiredUserInputCodeException { + Passwordless.CreateCodeResponse code = Passwordless.createCode(main, null, phone, + null, "123456"); + return Passwordless.consumeCode(main, code.deviceId, code.deviceIdHash, + code.userInputCode, null).user; + } + + @Test + public void testEmailPasswordSignUp() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + JsonObject responseBody = new JsonObject(); + responseBody.addProperty("email", "random@gmail.com"); + responseBody.addProperty("password", "validPass123"); + + JsonObject signUpResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signup", responseBody, 1000, 1000, null, SemVer.v4_0.get(), + "emailpassword"); + + assertEquals(signUpResponse.get("status").getAsString(), "OK"); + assertEquals(signUpResponse.entrySet().size(), 3); + assertEquals(signUpResponse.get("recipeUserId"), signUpResponse.get("user").getAsJsonObject().get("id")); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testEmailPasswordSignIn() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo user = createEmailPasswordUser(process.getProcess(), + "test@example.com", "password"); + + { + // Before account linking + JsonObject responseBody = new JsonObject(); + responseBody.addProperty("email", "test@example.com"); + responseBody.addProperty("password", "password"); + + JsonObject signInResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signin", responseBody, 1000, 1000, null, SemVer.v4_0.get(), + "emailpassword"); + + assertEquals(signInResponse.get("status").getAsString(), "OK"); + assertEquals(signInResponse.entrySet().size(), 3); + assertEquals(signInResponse.get("recipeUserId").getAsString(), user.getSupertokensUserId()); + } + + AuthRecipeUserInfo user2 = createPasswordlessUserWithEmail(process.getProcess(), + "test@example.com"); + + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user2.getSupertokensUserId()).user; + AuthRecipe.linkAccounts(process.getProcess(), user.getSupertokensUserId(), primaryUser.getSupertokensUserId()); + + { + // After account linking + JsonObject responseBody = new JsonObject(); + responseBody.addProperty("email", "test@example.com"); + responseBody.addProperty("password", "password"); + + JsonObject signInResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signin", responseBody, 1000, 1000, null, SemVer.v4_0.get(), + "emailpassword"); + + assertEquals(signInResponse.get("status").getAsString(), "OK"); + assertEquals(signInResponse.entrySet().size(), 3); + assertEquals(signInResponse.get("recipeUserId").getAsString(), user.getSupertokensUserId()); + } + + // With another email password user + AuthRecipeUserInfo user3 = createEmailPasswordUser(process.getProcess(), + "test2@example.com", "password"); + AuthRecipe.linkAccounts(process.getProcess(), user3.getSupertokensUserId(), primaryUser.getSupertokensUserId()); + + { + // After account linking + JsonObject responseBody = new JsonObject(); + responseBody.addProperty("email", "test@example.com"); + responseBody.addProperty("password", "password"); + + JsonObject signInResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signin", responseBody, 1000, 1000, null, SemVer.v4_0.get(), + "emailpassword"); + + assertEquals(signInResponse.get("status").getAsString(), "OK"); + assertEquals(signInResponse.entrySet().size(), 3); + assertEquals(signInResponse.get("recipeUserId").getAsString(), user.getSupertokensUserId()); + } + { + // After account linking + JsonObject responseBody = new JsonObject(); + responseBody.addProperty("email", "test2@example.com"); + responseBody.addProperty("password", "password"); + + JsonObject signInResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signin", responseBody, 1000, 1000, null, SemVer.v4_0.get(), + "emailpassword"); + + assertEquals(signInResponse.get("status").getAsString(), "OK"); + assertEquals(signInResponse.entrySet().size(), 3); + assertEquals(signInResponse.get("recipeUserId").getAsString(), user3.getSupertokensUserId()); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testThirdPartySignInUp() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + String userId = null; + { + JsonObject emailObject = new JsonObject(); + emailObject.addProperty("id", "test@example.com"); + + JsonObject signUpRequestBody = new JsonObject(); + signUpRequestBody.addProperty("thirdPartyId", "google"); + signUpRequestBody.addProperty("thirdPartyUserId", "google-user"); + signUpRequestBody.add("email", emailObject); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signinup", signUpRequestBody, 1000, 1000, null, + SemVer.v4_0.get(), "thirdparty"); + + assertEquals(4, response.entrySet().size()); + assertEquals(response.get("recipeUserId"), response.get("user").getAsJsonObject().get("id")); + userId = response.get("recipeUserId").getAsString(); + } + + { + // Without account linking + JsonObject emailObject = new JsonObject(); + emailObject.addProperty("id", "test@example.com"); + + JsonObject signUpRequestBody = new JsonObject(); + signUpRequestBody.addProperty("thirdPartyId", "google"); + signUpRequestBody.addProperty("thirdPartyUserId", "google-user"); + signUpRequestBody.add("email", emailObject); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signinup", signUpRequestBody, 1000, 1000, null, + SemVer.v4_0.get(), "thirdparty"); + + assertEquals(4, response.entrySet().size()); + assertEquals(userId, response.get("recipeUserId").getAsString()); + } + + AuthRecipeUserInfo user2 = createEmailPasswordUser(process.getProcess(), + "test@example.com", "password"); + + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user2.getSupertokensUserId()).user; + AuthRecipe.linkAccounts(process.getProcess(), userId, primaryUser.getSupertokensUserId()); + + { + // After account linking + JsonObject emailObject = new JsonObject(); + emailObject.addProperty("id", "test@example.com"); + + JsonObject signUpRequestBody = new JsonObject(); + signUpRequestBody.addProperty("thirdPartyId", "google"); + signUpRequestBody.addProperty("thirdPartyUserId", "google-user"); + signUpRequestBody.add("email", emailObject); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signinup", signUpRequestBody, 1000, 1000, null, + SemVer.v4_0.get(), "thirdparty"); + + assertEquals(4, response.entrySet().size()); + assertEquals(userId, response.get("recipeUserId").getAsString()); + } + + AuthRecipeUserInfo user3 = createThirdPartyUser(process.getProcess(), "facebook", "fb-user", "test@example.com"); + AuthRecipe.linkAccounts(process.getProcess(), user3.getSupertokensUserId(), primaryUser.getSupertokensUserId()); + + { + // After account linking + JsonObject emailObject = new JsonObject(); + emailObject.addProperty("id", "test@example.com"); + + JsonObject signUpRequestBody = new JsonObject(); + signUpRequestBody.addProperty("thirdPartyId", "google"); + signUpRequestBody.addProperty("thirdPartyUserId", "google-user"); + signUpRequestBody.add("email", emailObject); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signinup", signUpRequestBody, 1000, 1000, null, + SemVer.v4_0.get(), "thirdparty"); + + assertEquals(4, response.entrySet().size()); + assertEquals(userId, response.get("recipeUserId").getAsString()); + } + { + // After account linking + JsonObject emailObject = new JsonObject(); + emailObject.addProperty("id", "test@example.com"); + + JsonObject signUpRequestBody = new JsonObject(); + signUpRequestBody.addProperty("thirdPartyId", "facebook"); + signUpRequestBody.addProperty("thirdPartyUserId", "fb-user"); + signUpRequestBody.add("email", emailObject); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signinup", signUpRequestBody, 1000, 1000, null, + SemVer.v4_0.get(), "thirdparty"); + + assertEquals(4, response.entrySet().size()); + assertEquals(user3.getSupertokensUserId(), response.get("recipeUserId").getAsString()); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testPasswordlessConsumeCode() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + String userId = null; + { + Passwordless.CreateCodeResponse createResp = Passwordless.createCode(process.getProcess(), "test@example.com", null, null, null); + + JsonObject consumeCodeRequestBody = new JsonObject(); + consumeCodeRequestBody.addProperty("preAuthSessionId", createResp.deviceIdHash); + consumeCodeRequestBody.addProperty("linkCode", createResp.linkCode); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, + SemVer.v4_0.get(), "passwordless"); + + assertEquals(4, response.entrySet().size()); + assertEquals(response.get("recipeUserId"), response.get("user").getAsJsonObject().get("id")); + userId = response.get("recipeUserId").getAsString(); + } + + { // Without account linking + Passwordless.CreateCodeResponse createResp = Passwordless.createCode(process.getProcess(), "test@example.com", null, null, null); + + JsonObject consumeCodeRequestBody = new JsonObject(); + consumeCodeRequestBody.addProperty("preAuthSessionId", createResp.deviceIdHash); + consumeCodeRequestBody.addProperty("linkCode", createResp.linkCode); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, + SemVer.v4_0.get(), "passwordless"); + + assertEquals(4, response.entrySet().size()); + assertEquals(userId, response.get("recipeUserId").getAsString()); + } + + AuthRecipeUserInfo user2 = createThirdPartyUser(process.getProcess(), "google", "google-user", "test@example.com"); + + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user2.getSupertokensUserId()).user; + AuthRecipe.linkAccounts(process.getProcess(), userId, primaryUser.getSupertokensUserId()); + + { // after account linking + Passwordless.CreateCodeResponse createResp = Passwordless.createCode(process.getProcess(), "test@example.com", null, null, null); + + JsonObject consumeCodeRequestBody = new JsonObject(); + consumeCodeRequestBody.addProperty("preAuthSessionId", createResp.deviceIdHash); + consumeCodeRequestBody.addProperty("linkCode", createResp.linkCode); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, + SemVer.v4_0.get(), "passwordless"); + + assertEquals(4, response.entrySet().size()); + assertEquals(userId, response.get("recipeUserId").getAsString()); + } + + AuthRecipeUserInfo user3 = createPasswordlessUserWithEmail(process.getProcess(), "test2@example.com"); + AuthRecipe.linkAccounts(process.getProcess(), user3.getSupertokensUserId(), primaryUser.getSupertokensUserId()); + + { // after account linking + Passwordless.CreateCodeResponse createResp = Passwordless.createCode(process.getProcess(), "test@example.com", null, null, null); + + JsonObject consumeCodeRequestBody = new JsonObject(); + consumeCodeRequestBody.addProperty("preAuthSessionId", createResp.deviceIdHash); + consumeCodeRequestBody.addProperty("linkCode", createResp.linkCode); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, + SemVer.v4_0.get(), "passwordless"); + + assertEquals(4, response.entrySet().size()); + assertEquals(userId, response.get("recipeUserId").getAsString()); + } + { // after account linking + Passwordless.CreateCodeResponse createResp = Passwordless.createCode(process.getProcess(), "test2@example.com", null, null, null); + + JsonObject consumeCodeRequestBody = new JsonObject(); + consumeCodeRequestBody.addProperty("preAuthSessionId", createResp.deviceIdHash); + consumeCodeRequestBody.addProperty("linkCode", createResp.linkCode); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, + SemVer.v4_0.get(), "passwordless"); + + assertEquals(4, response.entrySet().size()); + assertEquals(user3.getSupertokensUserId(), response.get("recipeUserId").getAsString()); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testPasswordlessConsumeCodeForPhone() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + String userId = null; + { + Passwordless.CreateCodeResponse createResp = Passwordless.createCode(process.getProcess(), null, "+919876543210", null, null); + + JsonObject consumeCodeRequestBody = new JsonObject(); + consumeCodeRequestBody.addProperty("preAuthSessionId", createResp.deviceIdHash); + consumeCodeRequestBody.addProperty("linkCode", createResp.linkCode); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, + SemVer.v4_0.get(), "passwordless"); + + assertEquals(4, response.entrySet().size()); + assertEquals(response.get("recipeUserId"), response.get("user").getAsJsonObject().get("id")); + userId = response.get("recipeUserId").getAsString(); + } + + { // Without account linking + Passwordless.CreateCodeResponse createResp = Passwordless.createCode(process.getProcess(), null, "+919876543210", null, null); + + JsonObject consumeCodeRequestBody = new JsonObject(); + consumeCodeRequestBody.addProperty("preAuthSessionId", createResp.deviceIdHash); + consumeCodeRequestBody.addProperty("linkCode", createResp.linkCode); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, + SemVer.v4_0.get(), "passwordless"); + + assertEquals(4, response.entrySet().size()); + assertEquals(userId, response.get("recipeUserId").getAsString()); + } + + AuthRecipeUserInfo user2 = createThirdPartyUser(process.getProcess(), "google", "google-user", "test@example.com"); + + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user2.getSupertokensUserId()).user; + AuthRecipe.linkAccounts(process.getProcess(), userId, primaryUser.getSupertokensUserId()); + + { // after account linking + Passwordless.CreateCodeResponse createResp = Passwordless.createCode(process.getProcess(), null, "+919876543210", null, null); + + JsonObject consumeCodeRequestBody = new JsonObject(); + consumeCodeRequestBody.addProperty("preAuthSessionId", createResp.deviceIdHash); + consumeCodeRequestBody.addProperty("linkCode", createResp.linkCode); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, + SemVer.v4_0.get(), "passwordless"); + + assertEquals(4, response.entrySet().size()); + assertEquals(userId, response.get("recipeUserId").getAsString()); + } + + AuthRecipeUserInfo user3 = createPasswordlessUserWithPhone(process.getProcess(), "+919876543211"); + AuthRecipe.linkAccounts(process.getProcess(), user3.getSupertokensUserId(), primaryUser.getSupertokensUserId()); + + { // after account linking + Passwordless.CreateCodeResponse createResp = Passwordless.createCode(process.getProcess(), null, "+919876543210", null, null); + + JsonObject consumeCodeRequestBody = new JsonObject(); + consumeCodeRequestBody.addProperty("preAuthSessionId", createResp.deviceIdHash); + consumeCodeRequestBody.addProperty("linkCode", createResp.linkCode); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, + SemVer.v4_0.get(), "passwordless"); + + assertEquals(4, response.entrySet().size()); + assertEquals(userId, response.get("recipeUserId").getAsString()); + } + { // after account linking + Passwordless.CreateCodeResponse createResp = Passwordless.createCode(process.getProcess(), null, "+919876543211", null, null); + + JsonObject consumeCodeRequestBody = new JsonObject(); + consumeCodeRequestBody.addProperty("preAuthSessionId", createResp.deviceIdHash); + consumeCodeRequestBody.addProperty("linkCode", createResp.linkCode); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, + SemVer.v4_0.get(), "passwordless"); + + assertEquals(4, response.entrySet().size()); + assertEquals(user3.getSupertokensUserId(), response.get("recipeUserId").getAsString()); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testPasswordlessConsumeCodeForPhoneAndEmail() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + String userId = null; + { + Passwordless.CreateCodeResponse createResp = Passwordless.createCode(process.getProcess(), null, "+919876543210", null, null); + + JsonObject consumeCodeRequestBody = new JsonObject(); + consumeCodeRequestBody.addProperty("preAuthSessionId", createResp.deviceIdHash); + consumeCodeRequestBody.addProperty("linkCode", createResp.linkCode); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, + SemVer.v4_0.get(), "passwordless"); + + assertEquals(4, response.entrySet().size()); + assertEquals(response.get("recipeUserId"), response.get("user").getAsJsonObject().get("id")); + userId = response.get("recipeUserId").getAsString(); + } + + Passwordless.updateUser(process.getProcess(), userId, new Passwordless.FieldUpdate("test@example.com"), null); + + { // Without account linking - phone + Passwordless.CreateCodeResponse createResp = Passwordless.createCode(process.getProcess(), null, "+919876543210", null, null); + + JsonObject consumeCodeRequestBody = new JsonObject(); + consumeCodeRequestBody.addProperty("preAuthSessionId", createResp.deviceIdHash); + consumeCodeRequestBody.addProperty("linkCode", createResp.linkCode); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, + SemVer.v4_0.get(), "passwordless"); + + assertEquals(4, response.entrySet().size()); + assertEquals(userId, response.get("recipeUserId").getAsString()); + } + { // Without account linking - email + Passwordless.CreateCodeResponse createResp = Passwordless.createCode(process.getProcess(), "test@example.com", null, null, null); + + JsonObject consumeCodeRequestBody = new JsonObject(); + consumeCodeRequestBody.addProperty("preAuthSessionId", createResp.deviceIdHash); + consumeCodeRequestBody.addProperty("linkCode", createResp.linkCode); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, + SemVer.v4_0.get(), "passwordless"); + + assertEquals(4, response.entrySet().size()); + assertEquals(userId, response.get("recipeUserId").getAsString()); + } + + AuthRecipeUserInfo user2 = createThirdPartyUser(process.getProcess(), "google", "google-user", "test@example.com"); + + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user2.getSupertokensUserId()).user; + AuthRecipe.linkAccounts(process.getProcess(), userId, primaryUser.getSupertokensUserId()); + + { // after account linking - phone + Passwordless.CreateCodeResponse createResp = Passwordless.createCode(process.getProcess(), null, "+919876543210", null, null); + + JsonObject consumeCodeRequestBody = new JsonObject(); + consumeCodeRequestBody.addProperty("preAuthSessionId", createResp.deviceIdHash); + consumeCodeRequestBody.addProperty("linkCode", createResp.linkCode); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, + SemVer.v4_0.get(), "passwordless"); + + assertEquals(4, response.entrySet().size()); + assertEquals(userId, response.get("recipeUserId").getAsString()); + } + { // after account linking - email + Passwordless.CreateCodeResponse createResp = Passwordless.createCode(process.getProcess(), "test@example.com", null, null, null); + + JsonObject consumeCodeRequestBody = new JsonObject(); + consumeCodeRequestBody.addProperty("preAuthSessionId", createResp.deviceIdHash); + consumeCodeRequestBody.addProperty("linkCode", createResp.linkCode); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, + SemVer.v4_0.get(), "passwordless"); + + assertEquals(4, response.entrySet().size()); + assertEquals(userId, response.get("recipeUserId").getAsString()); + } + + AuthRecipeUserInfo user3 = createPasswordlessUserWithPhone(process.getProcess(), "+919876543211"); + AuthRecipe.linkAccounts(process.getProcess(), user3.getSupertokensUserId(), primaryUser.getSupertokensUserId()); + + { // after account linking - phone + Passwordless.CreateCodeResponse createResp = Passwordless.createCode(process.getProcess(), null, "+919876543210", null, null); + + JsonObject consumeCodeRequestBody = new JsonObject(); + consumeCodeRequestBody.addProperty("preAuthSessionId", createResp.deviceIdHash); + consumeCodeRequestBody.addProperty("linkCode", createResp.linkCode); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, + SemVer.v4_0.get(), "passwordless"); + + assertEquals(4, response.entrySet().size()); + assertEquals(userId, response.get("recipeUserId").getAsString()); + } + { // after account linking - email + Passwordless.CreateCodeResponse createResp = Passwordless.createCode(process.getProcess(), "test@example.com", null, null, null); + + JsonObject consumeCodeRequestBody = new JsonObject(); + consumeCodeRequestBody.addProperty("preAuthSessionId", createResp.deviceIdHash); + consumeCodeRequestBody.addProperty("linkCode", createResp.linkCode); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, + SemVer.v4_0.get(), "passwordless"); + + assertEquals(4, response.entrySet().size()); + assertEquals(userId, response.get("recipeUserId").getAsString()); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testWithEmailPasswordUserWithUserIdMapping() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo user = createEmailPasswordUser(process.getProcess(), + "test@example.com", "password"); + UserIdMapping.createUserIdMapping(process.getProcess(), user.getSupertokensUserId(), "extuserid", "", false); + + { + // Before account linking + JsonObject responseBody = new JsonObject(); + responseBody.addProperty("email", "test@example.com"); + responseBody.addProperty("password", "password"); + + JsonObject signInResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signin", responseBody, 1000, 1000, null, SemVer.v4_0.get(), + "emailpassword"); + + assertEquals(signInResponse.get("status").getAsString(), "OK"); + assertEquals(signInResponse.entrySet().size(), 3); + assertEquals(signInResponse.get("recipeUserId").getAsString(), "extuserid"); + } + + AuthRecipeUserInfo user2 = createPasswordlessUserWithEmail(process.getProcess(), + "test@example.com"); + + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user2.getSupertokensUserId()).user; + AuthRecipe.linkAccounts(process.getProcess(), user.getSupertokensUserId(), primaryUser.getSupertokensUserId()); + + { + // After account linking + JsonObject responseBody = new JsonObject(); + responseBody.addProperty("email", "test@example.com"); + responseBody.addProperty("password", "password"); + + JsonObject signInResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signin", responseBody, 1000, 1000, null, SemVer.v4_0.get(), + "emailpassword"); + + assertEquals(signInResponse.get("status").getAsString(), "OK"); + assertEquals(signInResponse.entrySet().size(), 3); + assertEquals(signInResponse.get("recipeUserId").getAsString(), "extuserid"); + } + + // With another email password user + AuthRecipeUserInfo user3 = createEmailPasswordUser(process.getProcess(), + "test2@example.com", "password"); + AuthRecipe.linkAccounts(process.getProcess(), user3.getSupertokensUserId(), primaryUser.getSupertokensUserId()); + + { + // After account linking + JsonObject responseBody = new JsonObject(); + responseBody.addProperty("email", "test@example.com"); + responseBody.addProperty("password", "password"); + + JsonObject signInResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signin", responseBody, 1000, 1000, null, SemVer.v4_0.get(), + "emailpassword"); + + assertEquals(signInResponse.get("status").getAsString(), "OK"); + assertEquals(signInResponse.entrySet().size(), 3); + assertEquals(signInResponse.get("recipeUserId").getAsString(), "extuserid"); + } + { + // After account linking + JsonObject responseBody = new JsonObject(); + responseBody.addProperty("email", "test2@example.com"); + responseBody.addProperty("password", "password"); + + JsonObject signInResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signin", responseBody, 1000, 1000, null, SemVer.v4_0.get(), + "emailpassword"); + + assertEquals(signInResponse.get("status").getAsString(), "OK"); + assertEquals(signInResponse.entrySet().size(), 3); + assertEquals(signInResponse.get("recipeUserId").getAsString(), user3.getSupertokensUserId()); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } +} diff --git a/src/test/java/io/supertokens/test/accountlinking/api/UnlinkAccountsAPITest.java b/src/test/java/io/supertokens/test/accountlinking/api/UnlinkAccountsAPITest.java index 647f48a24..b0ae61018 100644 --- a/src/test/java/io/supertokens/test/accountlinking/api/UnlinkAccountsAPITest.java +++ b/src/test/java/io/supertokens/test/accountlinking/api/UnlinkAccountsAPITest.java @@ -77,17 +77,17 @@ public void unlinkAccountSuccess() throws Exception { AuthRecipeUserInfo user2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "password"); assert (!user2.isPrimaryUser); - AuthRecipe.createPrimaryUser(process.main, user.id); + AuthRecipe.createPrimaryUser(process.main, user.getSupertokensUserId()); - AuthRecipe.linkAccounts(process.main, user2.id, user.id); + AuthRecipe.linkAccounts(process.main, user2.getSupertokensUserId(), user.getSupertokensUserId()); - Session.createNewSession(process.main, user2.id, new JsonObject(), new JsonObject()); - String[] sessions = Session.getAllNonExpiredSessionHandlesForUser(process.main, user2.id); + Session.createNewSession(process.main, user2.getSupertokensUserId(), new JsonObject(), new JsonObject()); + String[] sessions = Session.getAllNonExpiredSessionHandlesForUser(process.main, user2.getSupertokensUserId()); assert (sessions.length == 1); { JsonObject params = new JsonObject(); - params.addProperty("recipeUserId", user2.id); + params.addProperty("recipeUserId", user2.getSupertokensUserId()); JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/recipe/accountlinking/user/unlink", params, 1000, 1000, null, WebserverAPI.getLatestCDIVersion().get(), ""); @@ -98,20 +98,20 @@ public void unlinkAccountSuccess() throws Exception { } - AuthRecipeUserInfo refetchUser2 = AuthRecipe.getUserById(process.main, user2.id); + AuthRecipeUserInfo refetchUser2 = AuthRecipe.getUserById(process.main, user2.getSupertokensUserId()); assert (!refetchUser2.isPrimaryUser); - assert (refetchUser2.id.equals(user2.id)); + assert (refetchUser2.getSupertokensUserId().equals(user2.getSupertokensUserId())); assert (refetchUser2.loginMethods.length == 1); - assert (refetchUser2.loginMethods[0].recipeUserId.equals(user2.id)); + assert (refetchUser2.loginMethods[0].getSupertokensUserId().equals(user2.getSupertokensUserId())); - AuthRecipeUserInfo refetchUser = AuthRecipe.getUserById(process.main, user.id); + AuthRecipeUserInfo refetchUser = AuthRecipe.getUserById(process.main, user.getSupertokensUserId()); assert (!refetchUser2.equals(refetchUser)); assert (refetchUser.isPrimaryUser); assert (refetchUser.loginMethods.length == 1); - assert (refetchUser.loginMethods[0].recipeUserId.equals(user.id)); + assert (refetchUser.loginMethods[0].getSupertokensUserId().equals(user.getSupertokensUserId())); // cause linkAccounts revokes sessions for the recipe user ID - sessions = Session.getAllNonExpiredSessionHandlesForUser(process.main, user2.id); + sessions = Session.getAllNonExpiredSessionHandlesForUser(process.main, user2.getSupertokensUserId()); assert (sessions.length == 0); process.kill(); @@ -171,11 +171,11 @@ public void unlinkAccountSuccessWithUserIdMapping() throws Exception { AuthRecipeUserInfo user2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "password"); assert (!user2.isPrimaryUser); - UserIdMapping.createUserIdMapping(process.main, user2.id, "e2", null, false); + UserIdMapping.createUserIdMapping(process.main, user2.getSupertokensUserId(), "e2", null, false); - AuthRecipe.createPrimaryUser(process.main, user.id); + AuthRecipe.createPrimaryUser(process.main, user.getSupertokensUserId()); - AuthRecipe.linkAccounts(process.main, user2.id, user.id); + AuthRecipe.linkAccounts(process.main, user2.getSupertokensUserId(), user.getSupertokensUserId()); { JsonObject params = new JsonObject(); @@ -190,17 +190,17 @@ public void unlinkAccountSuccessWithUserIdMapping() throws Exception { } - AuthRecipeUserInfo refetchUser2 = AuthRecipe.getUserById(process.main, user2.id); + AuthRecipeUserInfo refetchUser2 = AuthRecipe.getUserById(process.main, user2.getSupertokensUserId()); assert (!refetchUser2.isPrimaryUser); - assert (refetchUser2.id.equals(user2.id)); + assert (refetchUser2.getSupertokensUserId().equals(user2.getSupertokensUserId())); assert (refetchUser2.loginMethods.length == 1); - assert (refetchUser2.loginMethods[0].recipeUserId.equals(user2.id)); + assert (refetchUser2.loginMethods[0].getSupertokensUserId().equals(user2.getSupertokensUserId())); - AuthRecipeUserInfo refetchUser = AuthRecipe.getUserById(process.main, user.id); + AuthRecipeUserInfo refetchUser = AuthRecipe.getUserById(process.main, user.getSupertokensUserId()); assert (!refetchUser2.equals(refetchUser)); assert (refetchUser.isPrimaryUser); assert (refetchUser.loginMethods.length == 1); - assert (refetchUser.loginMethods[0].recipeUserId.equals(user.id)); + assert (refetchUser.loginMethods[0].getSupertokensUserId().equals(user.getSupertokensUserId())); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -225,7 +225,7 @@ public void unlinkAccountWithoutPrimaryUserId() throws Exception { { JsonObject params = new JsonObject(); - params.addProperty("recipeUserId", user.id); + params.addProperty("recipeUserId", user.getSupertokensUserId()); JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/recipe/accountlinking/user/unlink", params, 1000, 1000, null, WebserverAPI.getLatestCDIVersion().get(), ""); @@ -293,13 +293,13 @@ public void unlinkAccountSuccessButDeletesUser() throws Exception { AuthRecipeUserInfo user2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "password"); assert (!user2.isPrimaryUser); - AuthRecipe.createPrimaryUser(process.main, user.id); + AuthRecipe.createPrimaryUser(process.main, user.getSupertokensUserId()); - AuthRecipe.linkAccounts(process.main, user2.id, user.id); + AuthRecipe.linkAccounts(process.main, user2.getSupertokensUserId(), user.getSupertokensUserId()); { JsonObject params = new JsonObject(); - params.addProperty("recipeUserId", user.id); + params.addProperty("recipeUserId", user.getSupertokensUserId()); JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/recipe/accountlinking/user/unlink", params, 1000, 1000, null, WebserverAPI.getLatestCDIVersion().get(), ""); diff --git a/src/test/java/io/supertokens/test/authRecipe/AuthRecipeStorageTest.java b/src/test/java/io/supertokens/test/authRecipe/AuthRecipeStorageTest.java index 90a0ddc6c..36921f56b 100644 --- a/src/test/java/io/supertokens/test/authRecipe/AuthRecipeStorageTest.java +++ b/src/test/java/io/supertokens/test/authRecipe/AuthRecipeStorageTest.java @@ -65,7 +65,7 @@ public void testIfAUserIdIsASuperTokensUserId() throws Exception { // create a user and check that the userId exists UserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); - assertTrue(storage.doesUserIdExist(new TenantIdentifier(null, null, null), userInfo.id)); + assertTrue(storage.doesUserIdExist(new TenantIdentifier(null, null, null), userInfo.getSupertokensUserId())); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); diff --git a/src/test/java/io/supertokens/test/authRecipe/DeleteUserAPIWithUserIdMappingTest.java b/src/test/java/io/supertokens/test/authRecipe/DeleteUserAPIWithUserIdMappingTest.java index 99bf07581..ba6c763fd 100644 --- a/src/test/java/io/supertokens/test/authRecipe/DeleteUserAPIWithUserIdMappingTest.java +++ b/src/test/java/io/supertokens/test/authRecipe/DeleteUserAPIWithUserIdMappingTest.java @@ -16,13 +16,11 @@ package io.supertokens.test.authRecipe; -import com.google.gson.JsonArray; import com.google.gson.JsonObject; import io.supertokens.ProcessState; import io.supertokens.emailpassword.EmailPassword; import io.supertokens.pluginInterface.STORAGE_TYPE; import io.supertokens.pluginInterface.emailpassword.UserInfo; -import io.supertokens.pluginInterface.useridmapping.UserIdMappingStorage; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.TestingProcessManager; import io.supertokens.test.Utils; @@ -32,15 +30,12 @@ import io.supertokens.useridmapping.UserIdType; import io.supertokens.usermetadata.UserMetadata; import io.supertokens.utils.SemVer; -import junit.framework.TestCase; import org.junit.AfterClass; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestRule; -import java.util.ArrayList; - import static org.junit.Assert.*; public class DeleteUserAPIWithUserIdMappingTest { @@ -71,7 +66,7 @@ public void createAUserMapTheirIdCreateMetadataWithExternalIdAndDelete() throws { // create User UserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); - String superTokensUserId = userInfo.id; + String superTokensUserId = userInfo.getSupertokensUserId(); String externalId = "externalId"; // map their id @@ -132,19 +127,19 @@ public void testDeleteUserBehaviorInIntermediateStateWithUser_1sUserId() throws // associate some data with user JsonObject data = new JsonObject(); data.addProperty("test", "testData"); - UserMetadata.updateUserMetadata(process.main, userInfo_1.id, data); + UserMetadata.updateUserMetadata(process.main, userInfo_1.getSupertokensUserId(), data); // create a new User who we would like to migrate the EmailPassword user to ThirdParty.SignInUpResponse userInfo_2 = ThirdParty.signInUp(process.main, "google", "test-google", "test123@example.com"); // force create a mapping between the thirdParty user and EmailPassword user - UserIdMapping.createUserIdMapping(process.main, userInfo_2.user.id, userInfo_1.id, null, true); + UserIdMapping.createUserIdMapping(process.main, userInfo_2.user.getSupertokensUserId(), userInfo_1.getSupertokensUserId(), null, true); // delete User with EmailPassword userId { JsonObject requestBody = new JsonObject(); - requestBody.addProperty("userId", userInfo_1.id); + requestBody.addProperty("userId", userInfo_1.getSupertokensUserId()); JsonObject deleteResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/user/remove", requestBody, 1000, 1000, null, @@ -155,20 +150,20 @@ public void testDeleteUserBehaviorInIntermediateStateWithUser_1sUserId() throws // check that only auth tables for EmailPassword user have been deleted and the userMetadata table entries still // exist { - UserInfo epUser = EmailPassword.getUserUsingId(process.main, userInfo_1.id); + UserInfo epUser = EmailPassword.getUserUsingId(process.main, userInfo_1.getSupertokensUserId()); assertNull(epUser); - JsonObject epUserMetadata = UserMetadata.getUserMetadata(process.main, userInfo_1.id); + JsonObject epUserMetadata = UserMetadata.getUserMetadata(process.main, userInfo_1.getSupertokensUserId()); assertNotNull(epUserMetadata); assertEquals(epUserMetadata.get("test").getAsString(), "testData"); } // check that the mapping still exists { io.supertokens.pluginInterface.useridmapping.UserIdMapping mapping = UserIdMapping - .getUserIdMapping(process.main, userInfo_2.user.id, UserIdType.ANY); + .getUserIdMapping(process.main, userInfo_2.user.getSupertokensUserId(), UserIdType.ANY); assertNotNull(mapping); - assertEquals(mapping.superTokensUserId, userInfo_2.user.id); - assertEquals(mapping.externalUserId, userInfo_1.id); + assertEquals(mapping.superTokensUserId, userInfo_2.user.getSupertokensUserId()); + assertEquals(mapping.externalUserId, userInfo_1.getSupertokensUserId()); } process.kill(); @@ -192,19 +187,19 @@ public void testDeleteUserBehaviorInIntermediateStateWithUser_2sUserId() throws // associate some data with user JsonObject data = new JsonObject(); data.addProperty("test", "testData"); - UserMetadata.updateUserMetadata(process.main, userInfo_1.id, data); + UserMetadata.updateUserMetadata(process.main, userInfo_1.getSupertokensUserId(), data); // create a new User who we would like to migrate the EmailPassword user to ThirdParty.SignInUpResponse userInfo_2 = ThirdParty.signInUp(process.main, "google", "test-google", "test123@example.com"); // force create a mapping between the thirdParty user and EmailPassword user - UserIdMapping.createUserIdMapping(process.main, userInfo_2.user.id, userInfo_1.id, null, true); + UserIdMapping.createUserIdMapping(process.main, userInfo_2.user.getSupertokensUserId(), userInfo_1.getSupertokensUserId(), null, true); // delete User with ThirdParty users id { JsonObject requestBody = new JsonObject(); - requestBody.addProperty("userId", userInfo_2.user.id); + requestBody.addProperty("userId", userInfo_2.user.getSupertokensUserId()); JsonObject deleteResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/user/remove", requestBody, 1000, 1000, null, @@ -216,17 +211,17 @@ public void testDeleteUserBehaviorInIntermediateStateWithUser_2sUserId() throws // exist { io.supertokens.pluginInterface.thirdparty.UserInfo tpUserInfo = ThirdParty.getUser(process.main, - userInfo_2.user.id); + userInfo_2.user.getSupertokensUserId()); assertNull(tpUserInfo); - JsonObject epUserMetadata = UserMetadata.getUserMetadata(process.main, userInfo_1.id); + JsonObject epUserMetadata = UserMetadata.getUserMetadata(process.main, userInfo_1.getSupertokensUserId()); assertNotNull(epUserMetadata); assertEquals(epUserMetadata.get("test").getAsString(), "testData"); } // check that the mapping is also deleted { io.supertokens.pluginInterface.useridmapping.UserIdMapping mapping = UserIdMapping - .getUserIdMapping(process.main, userInfo_2.user.id, UserIdType.ANY); + .getUserIdMapping(process.main, userInfo_2.user.getSupertokensUserId(), UserIdType.ANY); assertNull(mapping); } diff --git a/src/test/java/io/supertokens/test/authRecipe/GetUserByIdAPITest.java b/src/test/java/io/supertokens/test/authRecipe/GetUserByIdAPITest.java index 2d9ad5b23..8165c74f4 100644 --- a/src/test/java/io/supertokens/test/authRecipe/GetUserByIdAPITest.java +++ b/src/test/java/io/supertokens/test/authRecipe/GetUserByIdAPITest.java @@ -78,20 +78,20 @@ public void getUserSuccess() throws Exception { AuthRecipeUserInfo user2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "password"); assert (!user2.isPrimaryUser); - AuthRecipe.createPrimaryUser(process.main, user.id); + AuthRecipe.createPrimaryUser(process.main, user.getSupertokensUserId()); - AuthRecipe.linkAccounts(process.main, user2.id, user.id); + AuthRecipe.linkAccounts(process.main, user2.getSupertokensUserId(), user.getSupertokensUserId()); { Map params = new HashMap<>(); - params.put("userId", user2.id); + params.put("userId", user2.getSupertokensUserId()); JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", "http://localhost:3567/user/id", params, 1000, 1000, null, WebserverAPI.getLatestCDIVersion().get(), ""); assertEquals(2, response.entrySet().size()); assertEquals("OK", response.get("status").getAsString()); JsonObject jsonUser = response.get("user").getAsJsonObject(); - assert (jsonUser.get("id").getAsString().equals(user.id)); + assert (jsonUser.get("id").getAsString().equals(user.getSupertokensUserId())); assert (jsonUser.get("timeJoined").getAsLong() == user.timeJoined); assert (jsonUser.get("isPrimaryUser").getAsBoolean()); assert (jsonUser.get("emails").getAsJsonArray().size() == 2); @@ -108,7 +108,7 @@ public void getUserSuccess() throws Exception { JsonObject lM = jsonUser.get("loginMethods").getAsJsonArray().get(0).getAsJsonObject(); assertFalse(lM.get("verified").getAsBoolean()); assertEquals(lM.get("timeJoined").getAsLong(), user.timeJoined); - assertEquals(lM.get("recipeUserId").getAsString(), user.id); + assertEquals(lM.get("recipeUserId").getAsString(), user.getSupertokensUserId()); assertEquals(lM.get("recipeId").getAsString(), "emailpassword"); assertEquals(lM.get("email").getAsString(), "test@example.com"); assert (lM.entrySet().size() == 6); @@ -117,7 +117,7 @@ public void getUserSuccess() throws Exception { JsonObject lM = jsonUser.get("loginMethods").getAsJsonArray().get(1).getAsJsonObject(); assertFalse(lM.get("verified").getAsBoolean()); assertEquals(lM.get("timeJoined").getAsLong(), user2.timeJoined); - assertEquals(lM.get("recipeUserId").getAsString(), user2.id); + assertEquals(lM.get("recipeUserId").getAsString(), user2.getSupertokensUserId()); assertEquals(lM.get("recipeId").getAsString(), "emailpassword"); assertEquals(lM.get("email").getAsString(), "test2@example.com"); assert (lM.entrySet().size() == 6); @@ -144,17 +144,17 @@ public void getUserSuccessWithUserIdMapping() throws Exception { AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "password"); assert (!user.isPrimaryUser); - UserIdMapping.createUserIdMapping(process.main, user.id, "e1", null, false); + UserIdMapping.createUserIdMapping(process.main, user.getSupertokensUserId(), "e1", null, false); Thread.sleep(50); AuthRecipeUserInfo user2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "password"); assert (!user2.isPrimaryUser); - UserIdMapping.createUserIdMapping(process.main, user2.id, "e2", null, false); + UserIdMapping.createUserIdMapping(process.main, user2.getSupertokensUserId(), "e2", null, false); - AuthRecipe.createPrimaryUser(process.main, user.id); + AuthRecipe.createPrimaryUser(process.main, user.getSupertokensUserId()); - AuthRecipe.linkAccounts(process.main, user2.id, user.id); + AuthRecipe.linkAccounts(process.main, user2.getSupertokensUserId(), user.getSupertokensUserId()); { Map params = new HashMap<>(); @@ -191,7 +191,7 @@ public void getUserSuccessWithUserIdMapping() throws Exception { JsonObject lM = jsonUser.get("loginMethods").getAsJsonArray().get(1).getAsJsonObject(); assertFalse(lM.get("verified").getAsBoolean()); assertEquals(lM.get("timeJoined").getAsLong(), user2.timeJoined); - assertEquals(lM.get("recipeUserId").getAsString(), user2.id); + assertEquals(lM.get("recipeUserId").getAsString(), user2.getSupertokensUserId()); assertEquals(lM.get("recipeId").getAsString(), "emailpassword"); assertEquals(lM.get("email").getAsString(), "test2@example.com"); assert (lM.entrySet().size() == 6); @@ -227,20 +227,20 @@ public void getUserSuccess2() throws Exception { AuthRecipeUserInfo user2 = signInUpRespone.user; assert (!user2.isPrimaryUser); - AuthRecipe.createPrimaryUser(process.main, user2.id); + AuthRecipe.createPrimaryUser(process.main, user2.getSupertokensUserId()); - AuthRecipe.linkAccounts(process.main, user.id, user2.id); + AuthRecipe.linkAccounts(process.main, user.getSupertokensUserId(), user2.getSupertokensUserId()); { Map params = new HashMap<>(); - params.put("userId", user2.id); + params.put("userId", user2.getSupertokensUserId()); JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", "http://localhost:3567/user/id", params, 1000, 1000, null, WebserverAPI.getLatestCDIVersion().get(), ""); assertEquals(2, response.entrySet().size()); assertEquals("OK", response.get("status").getAsString()); JsonObject jsonUser = response.get("user").getAsJsonObject(); - assert (jsonUser.get("id").getAsString().equals(user2.id)); + assert (jsonUser.get("id").getAsString().equals(user2.getSupertokensUserId())); assert (jsonUser.get("timeJoined").getAsLong() == user.timeJoined); assert (jsonUser.get("isPrimaryUser").getAsBoolean()); assert (jsonUser.get("emails").getAsJsonArray().size() == 1); @@ -252,7 +252,7 @@ public void getUserSuccess2() throws Exception { JsonObject lM = jsonUser.get("loginMethods").getAsJsonArray().get(0).getAsJsonObject(); assertFalse(lM.get("verified").getAsBoolean()); assertEquals(lM.get("timeJoined").getAsLong(), user.timeJoined); - assertEquals(lM.get("recipeUserId").getAsString(), user.id); + assertEquals(lM.get("recipeUserId").getAsString(), user.getSupertokensUserId()); assertEquals(lM.get("recipeId").getAsString(), "emailpassword"); assertEquals(lM.get("email").getAsString(), "test@example.com"); assert (lM.entrySet().size() == 6); @@ -261,7 +261,7 @@ public void getUserSuccess2() throws Exception { JsonObject lM = jsonUser.get("loginMethods").getAsJsonArray().get(1).getAsJsonObject(); assertFalse(lM.get("verified").getAsBoolean()); assertEquals(lM.get("timeJoined").getAsLong(), user2.timeJoined); - assertEquals(lM.get("recipeUserId").getAsString(), user2.id); + assertEquals(lM.get("recipeUserId").getAsString(), user2.getSupertokensUserId()); assertEquals(lM.get("recipeId").getAsString(), "thirdparty"); assertEquals(lM.get("email").getAsString(), "test@example.com"); assert (lM.entrySet().size() == 7); diff --git a/src/test/java/io/supertokens/test/authRecipe/GetUsersAPIWithUserIdMappingTest.java b/src/test/java/io/supertokens/test/authRecipe/GetUsersAPIWithUserIdMappingTest.java index 0de6afa4e..a03dedb3c 100644 --- a/src/test/java/io/supertokens/test/authRecipe/GetUsersAPIWithUserIdMappingTest.java +++ b/src/test/java/io/supertokens/test/authRecipe/GetUsersAPIWithUserIdMappingTest.java @@ -71,7 +71,7 @@ public void createMultipleUsersAndMapTheirIdsRetrieveAllUsersAndCheckThatExterna for (int i = 1; i <= 10; i++) { // create User UserInfo userInfo = EmailPassword.signUp(process.main, "test" + i + "@example.com", "testPass123"); - String superTokensUserId = userInfo.id; + String superTokensUserId = userInfo.getSupertokensUserId(); String externalUserId = "externalId" + i; externalUserIdList.add(externalUserId); @@ -111,7 +111,7 @@ public void createMultipleUsersAndMapTheirIdsRetrieveUsersUsingPaginationTokenAn for (int i = 1; i <= 20; i++) { // create User UserInfo userInfo = EmailPassword.signUp(process.main, "test" + i + "@example.com", "testPass123"); - String superTokensUserId = userInfo.id; + String superTokensUserId = userInfo.getSupertokensUserId(); String externalUserId = "externalId" + i; externalUserIdList.add(externalUserId); diff --git a/src/test/java/io/supertokens/test/authRecipe/GetUsersWithSearchTagsAPITest.java b/src/test/java/io/supertokens/test/authRecipe/GetUsersWithSearchTagsAPITest.java index 069fc8dec..9dabb17d4 100644 --- a/src/test/java/io/supertokens/test/authRecipe/GetUsersWithSearchTagsAPITest.java +++ b/src/test/java/io/supertokens/test/authRecipe/GetUsersWithSearchTagsAPITest.java @@ -21,7 +21,6 @@ import static org.junit.Assert.assertTrue; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; import io.supertokens.utils.SemVer; @@ -32,16 +31,13 @@ import org.junit.rules.TestRule; import com.google.gson.JsonArray; -import com.google.gson.JsonElement; import com.google.gson.JsonObject; import io.supertokens.ProcessState.PROCESS_STATE; -import io.supertokens.dashboard.Dashboard; import io.supertokens.emailpassword.EmailPassword; import io.supertokens.passwordless.Passwordless; import io.supertokens.passwordless.Passwordless.CreateCodeResponse; import io.supertokens.pluginInterface.STORAGE_TYPE; -import io.supertokens.pluginInterface.dashboard.DashboardSearchTags; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.TestingProcessManager; import io.supertokens.test.Utils; @@ -74,17 +70,17 @@ public void testSearchingWhenFieldsHaveEmptyInputsWillBehaveLikeRegularPaginatio // create emailpassword user ArrayList userIds = new ArrayList<>(); - userIds.add(EmailPassword.signUp(process.getProcess(), "test@example.com", "testPass123").id); + userIds.add(EmailPassword.signUp(process.getProcess(), "test@example.com", "testPass123").getSupertokensUserId()); // create thirdparty user - userIds.add(ThirdParty.signInUp(process.getProcess(), "testTPID", "test", "test2@example.com").user.id); + userIds.add(ThirdParty.signInUp(process.getProcess(), "testTPID", "test", "test2@example.com").user.getSupertokensUserId()); // create passwordless user CreateCodeResponse createCodeResponse = Passwordless.createCode(process.getProcess(), "test@example.com", null, null, null); userIds.add(Passwordless.consumeCode(process.getProcess(), createCodeResponse.deviceId, createCodeResponse.deviceIdHash, - createCodeResponse.userInputCode, null).user.id); + createCodeResponse.userInputCode, null).user.getSupertokensUserId()); // search with empty input for email field { @@ -150,18 +146,18 @@ public void testSearchingForUsers() throws Exception { // create emailpassword user ArrayList userIds = new ArrayList<>(); - userIds.add(EmailPassword.signUp(process.getProcess(), "test@example.com", "testPass123").id); - userIds.add(EmailPassword.signUp(process.getProcess(), "test2@example.com", "testPass123").id); + userIds.add(EmailPassword.signUp(process.getProcess(), "test@example.com", "testPass123").getSupertokensUserId()); + userIds.add(EmailPassword.signUp(process.getProcess(), "test2@example.com", "testPass123").getSupertokensUserId()); // create thirdparty user - userIds.add(ThirdParty.signInUp(process.getProcess(), "testTPID", "test", "test2@example.com").user.id); + userIds.add(ThirdParty.signInUp(process.getProcess(), "testTPID", "test", "test2@example.com").user.getSupertokensUserId()); // create passwordless user CreateCodeResponse createCodeResponse = Passwordless.createCode(process.getProcess(), "test@example.com", null, null, null); userIds.add(Passwordless.consumeCode(process.getProcess(), createCodeResponse.deviceId, createCodeResponse.deviceIdHash, - createCodeResponse.userInputCode, null).user.id); + createCodeResponse.userInputCode, null).user.getSupertokensUserId()); // search with partial input for email field HashMap params = new HashMap<>(); @@ -194,9 +190,9 @@ public void testSearchingForUsersWithMultipleInputsForEachField() throws Excepti // create emailpassword user ArrayList userIds = new ArrayList<>(); - userIds.add(EmailPassword.signUp(process.getProcess(), "test@example.com", "testPass123").id); + userIds.add(EmailPassword.signUp(process.getProcess(), "test@example.com", "testPass123").getSupertokensUserId()); Thread.sleep(50); - userIds.add(EmailPassword.signUp(process.getProcess(), "abc@example.com", "testPass123").id); + userIds.add(EmailPassword.signUp(process.getProcess(), "abc@example.com", "testPass123").getSupertokensUserId()); Thread.sleep(50); // search with multiple inputs to email @@ -217,9 +213,9 @@ public void testSearchingForUsersWithMultipleInputsForEachField() throws Excepti } // create thirdparty user - userIds.add(ThirdParty.signInUp(process.getProcess(), "testpid", "test", "test@example.com").user.id); + userIds.add(ThirdParty.signInUp(process.getProcess(), "testpid", "test", "test@example.com").user.getSupertokensUserId()); Thread.sleep(50); - userIds.add(ThirdParty.signInUp(process.getProcess(), "newtestpid", "test123", "test@example.com").user.id); + userIds.add(ThirdParty.signInUp(process.getProcess(), "newtestpid", "test123", "test@example.com").user.getSupertokensUserId()); Thread.sleep(50); // search with multiple inputs to provider { @@ -245,7 +241,7 @@ public void testSearchingForUsersWithMultipleInputsForEachField() throws Excepti null, null); userIds.add(Passwordless.consumeCode(process.getProcess(), createCodeResponse.deviceId, createCodeResponse.deviceIdHash, - createCodeResponse.userInputCode, null).user.id); + createCodeResponse.userInputCode, null).user.getSupertokensUserId()); } Thread.sleep(50); { @@ -254,7 +250,7 @@ public void testSearchingForUsersWithMultipleInputsForEachField() throws Excepti null, null); userIds.add(Passwordless.consumeCode(process.getProcess(), createCodeResponse.deviceId, createCodeResponse.deviceIdHash, - createCodeResponse.userInputCode, null).user.id); + createCodeResponse.userInputCode, null).user.getSupertokensUserId()); } Thread.sleep(50); @@ -289,10 +285,10 @@ public void testRetrievingUsersWithConflictingTagsReturnsEmptyList() throws Exce // create emailpassword user ArrayList userIds = new ArrayList<>(); - userIds.add(EmailPassword.signUp(process.getProcess(), "test@example.com", "testPass123").id); + userIds.add(EmailPassword.signUp(process.getProcess(), "test@example.com", "testPass123").getSupertokensUserId()); // create thirdparty user - userIds.add(ThirdParty.signInUp(process.getProcess(), "testTPID", "test", "test@example.com").user.id); + userIds.add(ThirdParty.signInUp(process.getProcess(), "testTPID", "test", "test@example.com").user.getSupertokensUserId()); // create passwordless user CreateCodeResponse createCodeResponse = Passwordless.createCode(process.getProcess(), "test@example.com", @@ -300,7 +296,7 @@ public void testRetrievingUsersWithConflictingTagsReturnsEmptyList() throws Exce null, null); userIds.add(Passwordless.consumeCode(process.getProcess(), createCodeResponse.deviceId, createCodeResponse.deviceIdHash, - createCodeResponse.userInputCode, null).user.id); + createCodeResponse.userInputCode, null).user.getSupertokensUserId()); HashMap params = new HashMap<>(); params.put("email", "test@example.com"); @@ -330,10 +326,10 @@ public void testNormalizingSearchInputsWorksCorrectly() throws Exception { // create emailpassword user ArrayList userIds = new ArrayList<>(); - userIds.add(EmailPassword.signUp(process.getProcess(), "test@example.com", "testPass123").id); + userIds.add(EmailPassword.signUp(process.getProcess(), "test@example.com", "testPass123").getSupertokensUserId()); // create thirdparty user - userIds.add(ThirdParty.signInUp(process.getProcess(), "testpid", "test", "test@example.com").user.id); + userIds.add(ThirdParty.signInUp(process.getProcess(), "testpid", "test", "test@example.com").user.getSupertokensUserId()); { // searching for email with upper and lower case combination diff --git a/src/test/java/io/supertokens/test/authRecipe/GetUsersWithSearchTagsTest.java b/src/test/java/io/supertokens/test/authRecipe/GetUsersWithSearchTagsTest.java index bb335b9b1..4f0246f73 100644 --- a/src/test/java/io/supertokens/test/authRecipe/GetUsersWithSearchTagsTest.java +++ b/src/test/java/io/supertokens/test/authRecipe/GetUsersWithSearchTagsTest.java @@ -33,12 +33,9 @@ import io.supertokens.authRecipe.UserPaginationContainer; import io.supertokens.emailpassword.EmailPassword; import io.supertokens.passwordless.Passwordless; -import io.supertokens.passwordless.Passwordless.ConsumeCodeResponse; import io.supertokens.passwordless.Passwordless.CreateCodeResponse; import io.supertokens.pluginInterface.STORAGE_TYPE; -import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.dashboard.DashboardSearchTags; -import io.supertokens.pluginInterface.passwordless.UserInfo; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.TestingProcessManager; import io.supertokens.test.Utils; @@ -70,11 +67,11 @@ public void retriveUsersUsingSearchTags() throws Exception { // create emailpassword user ArrayList userIds = new ArrayList<>(); - userIds.add(EmailPassword.signUp(process.getProcess(), "test@example.com", "testPass123").id); - userIds.add(EmailPassword.signUp(process.getProcess(), "test2@example.com", "testPass123").id); + userIds.add(EmailPassword.signUp(process.getProcess(), "test@example.com", "testPass123").getSupertokensUserId()); + userIds.add(EmailPassword.signUp(process.getProcess(), "test2@example.com", "testPass123").getSupertokensUserId()); // create thirdparty user - userIds.add(ThirdParty.signInUp(process.getProcess(), "testTPID", "test", "test2@example.com").user.id); + userIds.add(ThirdParty.signInUp(process.getProcess(), "testTPID", "test", "test2@example.com").user.getSupertokensUserId()); // create passwordless user CreateCodeResponse createCodeResponse = Passwordless.createCode(process.getProcess(), "test@example.com", @@ -82,7 +79,7 @@ public void retriveUsersUsingSearchTags() throws Exception { null, null); userIds.add(Passwordless.consumeCode(process.getProcess(), createCodeResponse.deviceId, createCodeResponse.deviceIdHash, - createCodeResponse.userInputCode, null).user.id); + createCodeResponse.userInputCode, null).user.getSupertokensUserId()); // partial search with input emails as "test" { @@ -94,7 +91,7 @@ public void retriveUsersUsingSearchTags() throws Exception { UserPaginationContainer info = AuthRecipe.getUsers(process.getProcess(), 10, "ASC", null, null, tags); assertEquals(userIds.size(), info.users.length); for (int i = 0; i < info.users.length; i++) { - assertTrue(userIds.contains(info.users[i].id)); + assertTrue(userIds.contains(info.users[i].getSupertokensUserId())); } } @@ -108,7 +105,7 @@ public void retriveUsersUsingSearchTags() throws Exception { DashboardSearchTags tags = new DashboardSearchTags(arrayList, null, arrayList); UserPaginationContainer info = AuthRecipe.getUsers(process.getProcess(), 10, "ASC", null, null, tags); assertEquals(1, info.users.length); - assertEquals(userIds.get(2), info.users[0].id); + assertEquals(userIds.get(2), info.users[0].getSupertokensUserId()); assertEquals("thirdparty", info.users[0].loginMethods[0].recipeId.toString()); } @@ -123,7 +120,7 @@ public void retriveUsersUsingSearchTags() throws Exception { DashboardSearchTags tags = new DashboardSearchTags(null, arrayList, null); UserPaginationContainer info = AuthRecipe.getUsers(process.getProcess(), 10, "ASC", null, null, tags); assertEquals(1, info.users.length); - assertEquals(userIds.get(3), info.users[0].id); + assertEquals(userIds.get(3), info.users[0].getSupertokensUserId()); assertEquals("passwordless", info.users[0].loginMethods[0].recipeId.toString()); } @@ -143,11 +140,11 @@ public void testRetrievingUsersWithConflictingTagsReturnsEmptyList() throws Exce // create emailpassword user ArrayList userIds = new ArrayList<>(); - userIds.add(EmailPassword.signUp(process.getProcess(), "test@example.com", "testPass123").id); - userIds.add(EmailPassword.signUp(process.getProcess(), "test2@example.com", "testPass123").id); + userIds.add(EmailPassword.signUp(process.getProcess(), "test@example.com", "testPass123").getSupertokensUserId()); + userIds.add(EmailPassword.signUp(process.getProcess(), "test2@example.com", "testPass123").getSupertokensUserId()); // create thirdparty user - userIds.add(ThirdParty.signInUp(process.getProcess(), "testTPID", "test", "test2@example.com").user.id); + userIds.add(ThirdParty.signInUp(process.getProcess(), "testTPID", "test", "test2@example.com").user.getSupertokensUserId()); // create passwordless user CreateCodeResponse createCodeResponse = Passwordless.createCode(process.getProcess(), "test@example.com", @@ -155,7 +152,7 @@ public void testRetrievingUsersWithConflictingTagsReturnsEmptyList() throws Exce null, null); userIds.add(Passwordless.consumeCode(process.getProcess(), createCodeResponse.deviceId, createCodeResponse.deviceIdHash, - createCodeResponse.userInputCode, null).user.id); + createCodeResponse.userInputCode, null).user.getSupertokensUserId()); // test retrieving a user with a phoneNumber and provider { @@ -186,12 +183,12 @@ public void testSearchParamRegex() throws Exception { // create emailpassword user ArrayList userIds = new ArrayList<>(); - userIds.add(EmailPassword.signUp(process.getProcess(), "test@example.com", "testPass123").id); - userIds.add(EmailPassword.signUp(process.getProcess(), "abc@example.com", "testPass123").id); - userIds.add(EmailPassword.signUp(process.getProcess(), "user@abc.com", "testPass123").id); + userIds.add(EmailPassword.signUp(process.getProcess(), "test@example.com", "testPass123").getSupertokensUserId()); + userIds.add(EmailPassword.signUp(process.getProcess(), "abc@example.com", "testPass123").getSupertokensUserId()); + userIds.add(EmailPassword.signUp(process.getProcess(), "user@abc.com", "testPass123").getSupertokensUserId()); // create thirdparty user - userIds.add(ThirdParty.signInUp(process.getProcess(), "testTPID", "test", "test2@example.com").user.id); + userIds.add(ThirdParty.signInUp(process.getProcess(), "testTPID", "test", "test2@example.com").user.getSupertokensUserId()); // create passwordless user CreateCodeResponse createCodeResponse = Passwordless.createCode(process.getProcess(), "test@example.com", @@ -199,7 +196,7 @@ public void testSearchParamRegex() throws Exception { null, null); userIds.add(Passwordless.consumeCode(process.getProcess(), createCodeResponse.deviceId, createCodeResponse.deviceIdHash, - createCodeResponse.userInputCode, null).user.id); + createCodeResponse.userInputCode, null).user.getSupertokensUserId()); // regex for emails: email* and *@email* { @@ -213,9 +210,9 @@ public void testSearchParamRegex() throws Exception { DashboardSearchTags tags = new DashboardSearchTags(emailList, null, null); UserPaginationContainer info = AuthRecipe.getUsers(process.getProcess(), 10, "ASC", null, null, tags); assertEquals(3, info.users.length); - assertEquals(userIds.get(0), info.users[0].id); - assertEquals(userIds.get(3), info.users[1].id); - assertEquals(userIds.get(4), info.users[2].id); + assertEquals(userIds.get(0), info.users[0].getSupertokensUserId()); + assertEquals(userIds.get(3), info.users[1].getSupertokensUserId()); + assertEquals(userIds.get(4), info.users[2].getSupertokensUserId()); } // retrieve emails for users whose email starts with abc or have domain abc @@ -228,8 +225,8 @@ public void testSearchParamRegex() throws Exception { DashboardSearchTags tags = new DashboardSearchTags(emailList, null, null); UserPaginationContainer info = AuthRecipe.getUsers(process.getProcess(), 10, "ASC", null, null, tags); assertEquals(2, info.users.length); - assertEquals(userIds.get(1), info.users[0].id); - assertEquals(userIds.get(2), info.users[1].id); + assertEquals(userIds.get(1), info.users[0].getSupertokensUserId()); + assertEquals(userIds.get(2), info.users[1].getSupertokensUserId()); } @@ -243,7 +240,7 @@ public void testSearchParamRegex() throws Exception { UserPaginationContainer info = AuthRecipe.getUsers(process.getProcess(), 10, "ASC", null, null, tags); assertEquals(1, info.users.length); - assertEquals(userIds.get(4), info.users[0].id); + assertEquals(userIds.get(4), info.users[0].getSupertokensUserId()); } } @@ -267,7 +264,7 @@ public void testThatQueryLimitIsCappedAt1000PerTable() throws Exception { ArrayList userIds = new ArrayList<>(); for (int i = 0; i < 1005; i++) { - userIds.add(EmailPassword.signUp(process.getProcess(), "test" + i + "@example.com", "testPass123").id); + userIds.add(EmailPassword.signUp(process.getProcess(), "test" + i + "@example.com", "testPass123").getSupertokensUserId()); Thread.sleep(10); } @@ -279,7 +276,7 @@ public void testThatQueryLimitIsCappedAt1000PerTable() throws Exception { UserPaginationContainer info = AuthRecipe.getUsers(process.getProcess(), 10, "ASC", null, null, tags); assertEquals(1000, info.users.length); for (int i = 0; i < info.users.length; i++) { - assertTrue(userIds.contains(info.users[i].id)); + assertTrue(userIds.contains(info.users[i].getSupertokensUserId())); } diff --git a/src/test/java/io/supertokens/test/authRecipe/MultitenantAPITest.java b/src/test/java/io/supertokens/test/authRecipe/MultitenantAPITest.java index ddc0d1cff..1e0318a9b 100644 --- a/src/test/java/io/supertokens/test/authRecipe/MultitenantAPITest.java +++ b/src/test/java/io/supertokens/test/authRecipe/MultitenantAPITest.java @@ -195,16 +195,16 @@ private void createUsers() "user@example.com", "password" + (pcount++) ); - tenantToUsers.get(tenant).add(user1.id); - recipeToUsers.get("emailpassword").add(user1.id); + tenantToUsers.get(tenant).add(user1.getSupertokensUserId()); + recipeToUsers.get("emailpassword").add(user1.getSupertokensUserId()); UserInfo user2 = EmailPassword.signUp( tenant.withStorage(StorageLayer.getStorage(tenant, process.getProcess())), process.getProcess(), "user@gmail.com", "password2" + (pcount++) ); - tenantToUsers.get(tenant).add(user2.id); - recipeToUsers.get("emailpassword").add(user2.id); + tenantToUsers.get(tenant).add(user2.getSupertokensUserId()); + recipeToUsers.get("emailpassword").add(user2.getSupertokensUserId()); } } { // passwordless users @@ -227,8 +227,8 @@ private void createUsers() Passwordless.ConsumeCodeResponse response = Passwordless.consumeCode(tenantIdentifierWithStorage, process.getProcess(), codeResponse.deviceId, codeResponse.deviceIdHash, "abcd", null); - tenantToUsers.get(tenant).add(response.user.id); - recipeToUsers.get("passwordless").add(response.user.id); + tenantToUsers.get(tenant).add(response.user.getSupertokensUserId()); + recipeToUsers.get("passwordless").add(response.user.getSupertokensUserId()); } { Passwordless.CreateCodeResponse codeResponse = Passwordless.createCode( @@ -241,8 +241,8 @@ private void createUsers() Passwordless.ConsumeCodeResponse response = Passwordless.consumeCode(tenantIdentifierWithStorage, process.getProcess(), codeResponse.deviceId, codeResponse.deviceIdHash, "abcd", null); - tenantToUsers.get(tenant).add(response.user.id); - recipeToUsers.get("passwordless").add(response.user.id); + tenantToUsers.get(tenant).add(response.user.getSupertokensUserId()); + recipeToUsers.get("passwordless").add(response.user.getSupertokensUserId()); } { Passwordless.CreateCodeResponse codeResponse = Passwordless.createCode( @@ -255,8 +255,8 @@ private void createUsers() Passwordless.ConsumeCodeResponse response = Passwordless.consumeCode(tenantIdentifierWithStorage, process.getProcess(), codeResponse.deviceId, codeResponse.deviceIdHash, "abcd", null); - tenantToUsers.get(tenant).add(response.user.id); - recipeToUsers.get("passwordless").add(response.user.id); + tenantToUsers.get(tenant).add(response.user.getSupertokensUserId()); + recipeToUsers.get("passwordless").add(response.user.getSupertokensUserId()); } { Passwordless.CreateCodeResponse codeResponse = Passwordless.createCode( @@ -269,8 +269,8 @@ private void createUsers() Passwordless.ConsumeCodeResponse response = Passwordless.consumeCode(tenantIdentifierWithStorage, process.getProcess(), codeResponse.deviceId, codeResponse.deviceIdHash, "abcd", null); - tenantToUsers.get(tenant).add(response.user.id); - recipeToUsers.get("passwordless").add(response.user.id); + tenantToUsers.get(tenant).add(response.user.getSupertokensUserId()); + recipeToUsers.get("passwordless").add(response.user.getSupertokensUserId()); } } } @@ -287,23 +287,23 @@ private void createUsers() ThirdParty.SignInUpResponse user1 = ThirdParty.signInUp(tenantIdentifierWithStorage, process.getProcess(), "google", "googleid1", "user@example.com"); - tenantToUsers.get(tenant).add(user1.user.id); - recipeToUsers.get("thirdparty").add(user1.user.id); + tenantToUsers.get(tenant).add(user1.user.getSupertokensUserId()); + recipeToUsers.get("thirdparty").add(user1.user.getSupertokensUserId()); ThirdParty.SignInUpResponse user2 = ThirdParty.signInUp(tenantIdentifierWithStorage, process.getProcess(), "google", "googleid2", "user@gmail.com"); - tenantToUsers.get(tenant).add(user2.user.id); - recipeToUsers.get("thirdparty").add(user2.user.id); + tenantToUsers.get(tenant).add(user2.user.getSupertokensUserId()); + recipeToUsers.get("thirdparty").add(user2.user.getSupertokensUserId()); ThirdParty.SignInUpResponse user3 = ThirdParty.signInUp(tenantIdentifierWithStorage, process.getProcess(), "facebook", "facebookid1", "user@example.com"); - tenantToUsers.get(tenant).add(user3.user.id); - recipeToUsers.get("thirdparty").add(user3.user.id); + tenantToUsers.get(tenant).add(user3.user.getSupertokensUserId()); + recipeToUsers.get("thirdparty").add(user3.user.getSupertokensUserId()); ThirdParty.SignInUpResponse user4 = ThirdParty.signInUp(tenantIdentifierWithStorage, process.getProcess(), "facebook", "facebookid2", "user@gmail.com"); - tenantToUsers.get(tenant).add(user4.user.id); - recipeToUsers.get("thirdparty").add(user4.user.id); + tenantToUsers.get(tenant).add(user4.user.getSupertokensUserId()); + recipeToUsers.get("thirdparty").add(user4.user.getSupertokensUserId()); } } } diff --git a/src/test/java/io/supertokens/test/authRecipe/UserPaginationTest.java b/src/test/java/io/supertokens/test/authRecipe/UserPaginationTest.java index 8f875cd2c..5340213cd 100644 --- a/src/test/java/io/supertokens/test/authRecipe/UserPaginationTest.java +++ b/src/test/java/io/supertokens/test/authRecipe/UserPaginationTest.java @@ -188,11 +188,11 @@ private void createUsers(TenantIdentifier tenantIdentifier, int numUsers, String UserInfo user = EmailPassword.signUp( tenantIdentifierWithStorage, process.getProcess(), prefix + "epuser" + i + "@example.com", "password" + i); - tenantToUsers.get(tenantIdentifier).add(user.id); + tenantToUsers.get(tenantIdentifier).add(user.getSupertokensUserId()); if (!recipeToUsers.containsKey("emailpassword")) { recipeToUsers.put("emailpassword", new ArrayList<>()); } - recipeToUsers.get("emailpassword").add(user.id); + recipeToUsers.get("emailpassword").add(user.getSupertokensUserId()); } { Passwordless.CreateCodeResponse codeResponse = Passwordless.createCode( @@ -205,27 +205,27 @@ private void createUsers(TenantIdentifier tenantIdentifier, int numUsers, String Passwordless.ConsumeCodeResponse response = Passwordless.consumeCode(tenantIdentifierWithStorage, process.getProcess(), codeResponse.deviceId, codeResponse.deviceIdHash, "abcd", null); - tenantToUsers.get(tenantIdentifier).add(response.user.id); + tenantToUsers.get(tenantIdentifier).add(response.user.getSupertokensUserId()); if (!recipeToUsers.containsKey("passwordless")) { recipeToUsers.put("passwordless", new ArrayList<>()); } - recipeToUsers.get("passwordless").add(response.user.id); + recipeToUsers.get("passwordless").add(response.user.getSupertokensUserId()); } { ThirdParty.SignInUpResponse user1 = ThirdParty.signInUp(tenantIdentifierWithStorage, process.getProcess(), "google", "googleid" + i, prefix + "tpuser" + i + "@example.com"); - tenantToUsers.get(tenantIdentifier).add(user1.user.id); + tenantToUsers.get(tenantIdentifier).add(user1.user.getSupertokensUserId()); ThirdParty.SignInUpResponse user2 = ThirdParty.signInUp(tenantIdentifierWithStorage, process.getProcess(), "facebook", "fbid" + i, prefix + "tpuser" + i + "@example.com"); - tenantToUsers.get(tenantIdentifier).add(user2.user.id); + tenantToUsers.get(tenantIdentifier).add(user2.user.getSupertokensUserId()); if (!recipeToUsers.containsKey("thirdparty")) { recipeToUsers.put("thirdparty", new ArrayList<>()); } - recipeToUsers.get("thirdparty").add(user1.user.id); - recipeToUsers.get("thirdparty").add(user2.user.id); + recipeToUsers.get("thirdparty").add(user1.user.getSupertokensUserId()); + recipeToUsers.get("thirdparty").add(user2.user.getSupertokensUserId()); } } } diff --git a/src/test/java/io/supertokens/test/emailpassword/DeleteExpiredPasswordResetTokensCronjobTest.java b/src/test/java/io/supertokens/test/emailpassword/DeleteExpiredPasswordResetTokensCronjobTest.java index 838b37993..6ddd7af34 100644 --- a/src/test/java/io/supertokens/test/emailpassword/DeleteExpiredPasswordResetTokensCronjobTest.java +++ b/src/test/java/io/supertokens/test/emailpassword/DeleteExpiredPasswordResetTokensCronjobTest.java @@ -66,21 +66,21 @@ public void checkingCronJob() throws Exception { UserInfo user = EmailPassword.signUp(process.getProcess(), "test1@example.com", "password"); - String tok = EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.id); - String tok2 = EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.id); + String tok = EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.getSupertokensUserId()); + String tok2 = EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.getSupertokensUserId()); Thread.sleep(2000); - String tok3 = EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.id); - String tok4 = EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.id); + String tok3 = EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.getSupertokensUserId()); + String tok4 = EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.getSupertokensUserId()); assert (((EmailPasswordSQLStorage) StorageLayer.getStorage(process.getProcess())) - .getAllPasswordResetTokenInfoForUser(new AppIdentifier(null, null), user.id).length == 4); + .getAllPasswordResetTokenInfoForUser(new AppIdentifier(null, null), user.getSupertokensUserId()).length == 4); Thread.sleep(3500); PasswordResetTokenInfo[] tokens = ((EmailPasswordSQLStorage) StorageLayer.getStorage(process.getProcess())) - .getAllPasswordResetTokenInfoForUser(new AppIdentifier(null, null), user.id); + .getAllPasswordResetTokenInfoForUser(new AppIdentifier(null, null), user.getSupertokensUserId()); assert (tokens.length == 2); diff --git a/src/test/java/io/supertokens/test/emailpassword/EmailPasswordTest.java b/src/test/java/io/supertokens/test/emailpassword/EmailPasswordTest.java index 2e3a7e8a7..0ff40b125 100644 --- a/src/test/java/io/supertokens/test/emailpassword/EmailPasswordTest.java +++ b/src/test/java/io/supertokens/test/emailpassword/EmailPasswordTest.java @@ -150,11 +150,11 @@ public void testResetPasswordToken() throws Exception { UserInfo userInfo = EmailPassword.signUp(process.getProcess(), "random@gmail.com", "validPass123"); assertEquals(userInfo.email, "random@gmail.com"); - assertNotNull(userInfo.id); + assertNotNull(userInfo.getSupertokensUserId()); for (int i = 0; i < 100; i++) { String generatedResetToken = EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), - userInfo.id); + userInfo.getSupertokensUserId()); assertEquals(generatedResetToken.length(), 128); assertFalse(generatedResetToken.contains("+")); @@ -207,7 +207,7 @@ public void testThatAfterResetPasswordGenerateTokenTheTokenIsHashedInTheDatabase UserInfo user = EmailPassword.signUp(process.getProcess(), "random@gmail.com", "validPass123"); - String resetToken = EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.id); + String resetToken = EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.getSupertokensUserId()); PasswordResetTokenInfo resetTokenInfo = ((EmailPasswordSQLStorage) StorageLayer.getStorage( process.getProcess())) .getPasswordResetTokenInfo(new AppIdentifier(null, null), @@ -236,7 +236,7 @@ public void testThatAfterResetPasswordIsCompletedThePasswordIsHashedInTheDatabas UserInfo user = EmailPassword.signUp(process.getProcess(), "random@gmail.com", "validPass123"); - String resetToken = EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.id); + String resetToken = EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.getSupertokensUserId()); EmailPassword.resetPassword(process.getProcess(), resetToken, "newValidPass123"); @@ -266,10 +266,10 @@ public void passwordResetTokenExpiredCheck() throws Exception { UserInfo user = EmailPassword.signUp(process.getProcess(), "test1@example.com", "password"); - String tok = EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.id); + String tok = EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.getSupertokensUserId()); assert (((EmailPasswordSQLStorage) StorageLayer.getStorage(process.getProcess())) - .getAllPasswordResetTokenInfoForUser(new AppIdentifier(null, null), user.id).length == 1); + .getAllPasswordResetTokenInfoForUser(new AppIdentifier(null, null), user.getSupertokensUserId()).length == 1); Thread.sleep(20); @@ -281,7 +281,7 @@ public void passwordResetTokenExpiredCheck() throws Exception { } assert (((EmailPasswordSQLStorage) StorageLayer.getStorage(process.getProcess())) - .getAllPasswordResetTokenInfoForUser(new AppIdentifier(null, null), user.id).length == 0); + .getAllPasswordResetTokenInfoForUser(new AppIdentifier(null, null), user.getSupertokensUserId()).length == 0); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -300,19 +300,19 @@ public void multiplePasswordResetTokensPerUserAndThenVerifyWithSignin() throws E UserInfo user = EmailPassword.signUp(process.getProcess(), "test1@example.com", "password"); - EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.id); - String tok = EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.id); - EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.id); + EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.getSupertokensUserId()); + String tok = EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.getSupertokensUserId()); + EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.getSupertokensUserId()); PasswordResetTokenInfo[] tokens = ((EmailPasswordSQLStorage) StorageLayer.getStorage(process.getProcess())) - .getAllPasswordResetTokenInfoForUser(new AppIdentifier(null, null), user.id); + .getAllPasswordResetTokenInfoForUser(new AppIdentifier(null, null), user.getSupertokensUserId()); assert (tokens.length == 3); EmailPassword.resetPassword(process.getProcess(), tok, "newPassword"); tokens = ((EmailPasswordSQLStorage) StorageLayer.getStorage(process.getProcess())) - .getAllPasswordResetTokenInfoForUser(new AppIdentifier(null, null), user.id); + .getAllPasswordResetTokenInfoForUser(new AppIdentifier(null, null), user.getSupertokensUserId()); assert (tokens.length == 0); try { @@ -388,14 +388,14 @@ public void clashingPassowordResetToken() throws Exception { ((EmailPasswordSQLStorage) StorageLayer.getStorage(process.getProcess())) .addPasswordResetToken(new AppIdentifier(null, null), new PasswordResetTokenInfo( - user.id, "token", + user.getSupertokensUserId(), "token", System.currentTimeMillis() + Config.getConfig(process.getProcess()).getPasswordResetTokenLifetime(), "email")); try { ((EmailPasswordSQLStorage) StorageLayer.getStorage(process.getProcess())) .addPasswordResetToken(new AppIdentifier(null, null), - new PasswordResetTokenInfo(user.id, "token", System.currentTimeMillis() + new PasswordResetTokenInfo(user.getSupertokensUserId(), "token", System.currentTimeMillis() + Config.getConfig(process.getProcess()).getPasswordResetTokenLifetime(), "email")); assert (false); } catch (DuplicatePasswordResetTokenException ignored) { @@ -529,7 +529,7 @@ public void signUpAndThenSignIn() throws Exception { assert (user.loginMethods[0].email.equals("test@example.com")); - assert (userSignUp.id.equals(user.id)); + assert (userSignUp.getSupertokensUserId().equals(user.getSupertokensUserId())); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -637,19 +637,19 @@ public void testGeneratingResetPasswordTokenForNonEPUserNonPrimary() throws Exce "test@example.com"); try { - EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.main, signInUpResponse.user.id); + EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.main, signInUpResponse.user.getSupertokensUserId()); assert false; } catch (UnknownUserIdException ignored) { } - String token = EmailPassword.generatePasswordResetToken(process.main, signInUpResponse.user.id, + String token = EmailPassword.generatePasswordResetToken(process.main, signInUpResponse.user.getSupertokensUserId(), "test@example.com"); EmailPassword.ConsumeResetPasswordTokenResult res = EmailPassword.consumeResetPasswordToken(process.main, token); assert (res.email.equals("test@example.com")); - assert (res.userId.equals(signInUpResponse.user.id)); + assert (res.userId.equals(signInUpResponse.user.getSupertokensUserId())); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -674,15 +674,15 @@ public void testGeneratingResetPasswordTokenForNonEPUserPrimary() throws Excepti "user-google", "test@example.com"); - AuthRecipe.createPrimaryUser(process.main, signInUpResponse.user.id); + AuthRecipe.createPrimaryUser(process.main, signInUpResponse.user.getSupertokensUserId()); - String token = EmailPassword.generatePasswordResetToken(process.main, signInUpResponse.user.id, + String token = EmailPassword.generatePasswordResetToken(process.main, signInUpResponse.user.getSupertokensUserId(), "test@example.com"); EmailPassword.ConsumeResetPasswordTokenResult res = EmailPassword.consumeResetPasswordToken(process.main, token); assert (res.email.equals("test@example.com")); - assert (res.userId.equals(signInUpResponse.user.id)); + assert (res.userId.equals(signInUpResponse.user.getSupertokensUserId())); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -711,17 +711,17 @@ public void testGeneratingResetPasswordTokenForNonEPUserPrimaryButDeletedWithOth "user-fb", "test2@example.com"); - AuthRecipe.createPrimaryUser(process.main, signInUpResponse.user.id); - AuthRecipe.linkAccounts(process.main, signInUpResponse2.user.id, signInUpResponse.user.id); - assert (AuthRecipe.unlinkAccounts(process.main, signInUpResponse.user.id)); + AuthRecipe.createPrimaryUser(process.main, signInUpResponse.user.getSupertokensUserId()); + AuthRecipe.linkAccounts(process.main, signInUpResponse2.user.getSupertokensUserId(), signInUpResponse.user.getSupertokensUserId()); + assert (AuthRecipe.unlinkAccounts(process.main, signInUpResponse.user.getSupertokensUserId())); - String token = EmailPassword.generatePasswordResetToken(process.main, signInUpResponse.user.id, + String token = EmailPassword.generatePasswordResetToken(process.main, signInUpResponse.user.getSupertokensUserId(), "test@example.com"); EmailPassword.ConsumeResetPasswordTokenResult res = EmailPassword.consumeResetPasswordToken(process.main, token); assert (res.email.equals("test@example.com")); - assert (res.userId.equals(signInUpResponse.user.id)); + assert (res.userId.equals(signInUpResponse.user.getSupertokensUserId())); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -747,14 +747,14 @@ public void deletionOfTpUserDeletesPasswordResetToken() throws Exception { "test@example.com"); - String token = EmailPassword.generatePasswordResetToken(process.main, signInUpResponse.user.id, + String token = EmailPassword.generatePasswordResetToken(process.main, signInUpResponse.user.getSupertokensUserId(), "test@example.com"); token = io.supertokens.utils.Utils.hashSHA256(token); assertNotNull(((EmailPasswordSQLStorage) StorageLayer.getStorage(process.main)).getPasswordResetTokenInfo( new AppIdentifier(null, null), token)); - AuthRecipe.deleteUser(process.main, signInUpResponse.user.id); + AuthRecipe.deleteUser(process.main, signInUpResponse.user.getSupertokensUserId()); assertNull(((EmailPasswordSQLStorage) StorageLayer.getStorage(process.main)).getPasswordResetTokenInfo( new AppIdentifier(null, null), token)); @@ -778,10 +778,10 @@ public void passwordResetTokenExpiredCheckWithConsumeCode() throws Exception { UserInfo user = EmailPassword.signUp(process.getProcess(), "test1@example.com", "password"); - String tok = EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.id); + String tok = EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.getSupertokensUserId()); assert (((EmailPasswordSQLStorage) StorageLayer.getStorage(process.getProcess())) - .getAllPasswordResetTokenInfoForUser(new AppIdentifier(null, null), user.id).length == 1); + .getAllPasswordResetTokenInfoForUser(new AppIdentifier(null, null), user.getSupertokensUserId()).length == 1); Thread.sleep(20); @@ -793,7 +793,7 @@ public void passwordResetTokenExpiredCheckWithConsumeCode() throws Exception { } assert (((EmailPasswordSQLStorage) StorageLayer.getStorage(process.getProcess())) - .getAllPasswordResetTokenInfoForUser(new AppIdentifier(null, null), user.id).length == 0); + .getAllPasswordResetTokenInfoForUser(new AppIdentifier(null, null), user.getSupertokensUserId()).length == 0); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -812,19 +812,19 @@ public void multiplePasswordResetTokensPerUserAndThenVerifyWithSigninWithConsume UserInfo user = EmailPassword.signUp(process.getProcess(), "test1@example.com", "password"); - EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.id); - String tok = EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.id); - EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.id); + EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.getSupertokensUserId()); + String tok = EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.getSupertokensUserId()); + EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.getSupertokensUserId()); PasswordResetTokenInfo[] tokens = ((EmailPasswordSQLStorage) StorageLayer.getStorage(process.getProcess())) - .getAllPasswordResetTokenInfoForUser(new AppIdentifier(null, null), user.id); + .getAllPasswordResetTokenInfoForUser(new AppIdentifier(null, null), user.getSupertokensUserId()); assert (tokens.length == 3); EmailPassword.consumeResetPasswordToken(process.getProcess(), tok); tokens = ((EmailPasswordSQLStorage) StorageLayer.getStorage(process.getProcess())) - .getAllPasswordResetTokenInfoForUser(new AppIdentifier(null, null), user.id); + .getAllPasswordResetTokenInfoForUser(new AppIdentifier(null, null), user.getSupertokensUserId()); assert (tokens.length == 0); process.kill(); @@ -868,20 +868,20 @@ public void consumeCodeCorrectlySetsTheUserEmailForOlderTokens() throws Exceptio UserInfo user = EmailPassword.signUp(process.getProcess(), "test1@example.com", "password"); String tok = EmailPassword.generatePasswordResetTokenBeforeCdi4_0WithoutAddingEmail(process.getProcess(), - user.id); + user.getSupertokensUserId()); assert (((EmailPasswordSQLStorage) StorageLayer.getStorage(process.getProcess())) - .getAllPasswordResetTokenInfoForUser(new AppIdentifier(null, null), user.id).length == 1); + .getAllPasswordResetTokenInfoForUser(new AppIdentifier(null, null), user.getSupertokensUserId()).length == 1); assert (((EmailPasswordSQLStorage) StorageLayer.getStorage(process.getProcess())) - .getAllPasswordResetTokenInfoForUser(new AppIdentifier(null, null), user.id)[0].email == null); + .getAllPasswordResetTokenInfoForUser(new AppIdentifier(null, null), user.getSupertokensUserId())[0].email == null); EmailPassword.ConsumeResetPasswordTokenResult result = EmailPassword.consumeResetPasswordToken( process.getProcess(), tok); assert (result.email.equals("test1@example.com")); - assert (result.userId.equals(user.id)); + assert (result.userId.equals(user.getSupertokensUserId())); assert (((EmailPasswordSQLStorage) StorageLayer.getStorage(process.getProcess())) - .getAllPasswordResetTokenInfoForUser(new AppIdentifier(null, null), user.id).length == 0); + .getAllPasswordResetTokenInfoForUser(new AppIdentifier(null, null), user.getSupertokensUserId()).length == 0); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -903,13 +903,13 @@ public void updateEmailFailsIfEmailUsedByOtherPrimaryUserInSameTenant() throws E } UserInfo user0 = EmailPassword.signUp(process.getProcess(), "someemail1@gmail.com", "somePass"); - AuthRecipe.createPrimaryUser(process.main, user0.id); + AuthRecipe.createPrimaryUser(process.main, user0.getSupertokensUserId()); UserInfo user = EmailPassword.signUp(process.getProcess(), "someemail@gmail.com", "somePass"); - AuthRecipe.createPrimaryUser(process.main, user.id); + AuthRecipe.createPrimaryUser(process.main, user.getSupertokensUserId()); try { - EmailPassword.updateUsersEmailOrPassword(process.main, user.id, "someemail1@gmail.com", null); + EmailPassword.updateUsersEmailOrPassword(process.main, user.getSupertokensUserId(), "someemail1@gmail.com", null); assert (false); } catch (EmailChangeNotAllowedException ignored) { @@ -945,12 +945,12 @@ public void updateEmailSucceedsIfEmailUsedByOtherPrimaryUserInDifferentTenantWhi "someemail1@gmail.com", "pass1234"); - AuthRecipe.createPrimaryUser(process.main, user0.id); + AuthRecipe.createPrimaryUser(process.main, user0.getSupertokensUserId()); UserInfo user = EmailPassword.signUp(process.getProcess(), "someemail@gmail.com", "somePass"); - AuthRecipe.createPrimaryUser(process.main, user.id); + AuthRecipe.createPrimaryUser(process.main, user.getSupertokensUserId()); - EmailPassword.updateUsersEmailOrPassword(process.main, user.id, "someemail1@gmail.com", null); + EmailPassword.updateUsersEmailOrPassword(process.main, user.getSupertokensUserId(), "someemail1@gmail.com", null); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -982,15 +982,15 @@ public void updateEmailFailsIfEmailUsedByOtherPrimaryUserInDifferentTenant() "someemail1@gmail.com", "pass1234"); - AuthRecipe.createPrimaryUser(process.main, user0.id); + AuthRecipe.createPrimaryUser(process.main, user0.getSupertokensUserId()); UserInfo user = EmailPassword.signUp(process.getProcess(), "someemail@gmail.com", "somePass"); - AuthRecipe.createPrimaryUser(process.main, user.id); + AuthRecipe.createPrimaryUser(process.main, user.getSupertokensUserId()); - Multitenancy.addUserIdToTenant(process.main, tenantIdentifierWithStorage, user.id); + Multitenancy.addUserIdToTenant(process.main, tenantIdentifierWithStorage, user.getSupertokensUserId()); try { - EmailPassword.updateUsersEmailOrPassword(process.main, user.id, "someemail1@gmail.com", null); + EmailPassword.updateUsersEmailOrPassword(process.main, user.getSupertokensUserId(), "someemail1@gmail.com", null); assert (false); } catch (EmailChangeNotAllowedException ignored) { diff --git a/src/test/java/io/supertokens/test/emailpassword/MultitenantEmailPasswordTest.java b/src/test/java/io/supertokens/test/emailpassword/MultitenantEmailPasswordTest.java index 5414e404e..01e53b16e 100644 --- a/src/test/java/io/supertokens/test/emailpassword/MultitenantEmailPasswordTest.java +++ b/src/test/java/io/supertokens/test/emailpassword/MultitenantEmailPasswordTest.java @@ -273,24 +273,24 @@ public void testGetUserUsingIdReturnsCorrectUser() { UserInfo userInfo = EmailPassword.getUserUsingId( StorageLayer.getAppIdentifierWithStorageAndUserIdMappingForUserWithPriorityForTenantStorage( - process.getProcess(), new AppIdentifier(null, "a1"), storage, user1.id, - UserIdType.SUPERTOKENS).appIdentifierWithStorage, user1.id); + process.getProcess(), new AppIdentifier(null, "a1"), storage, user1.getSupertokensUserId(), + UserIdType.SUPERTOKENS).appIdentifierWithStorage, user1.getSupertokensUserId()); assertEquals(user1, userInfo); } { UserInfo userInfo = EmailPassword.getUserUsingId( StorageLayer.getAppIdentifierWithStorageAndUserIdMappingForUserWithPriorityForTenantStorage( - process.getProcess(), new AppIdentifier(null, "a1"), storage, user2.id, - UserIdType.SUPERTOKENS).appIdentifierWithStorage, user2.id); + process.getProcess(), new AppIdentifier(null, "a1"), storage, user2.getSupertokensUserId(), + UserIdType.SUPERTOKENS).appIdentifierWithStorage, user2.getSupertokensUserId()); assertEquals(user2, userInfo); } { UserInfo userInfo = EmailPassword.getUserUsingId( StorageLayer.getAppIdentifierWithStorageAndUserIdMappingForUserWithPriorityForTenantStorage( - process.getProcess(), new AppIdentifier(null, "a1"), storage, user3.id, - UserIdType.SUPERTOKENS).appIdentifierWithStorage, user3.id); + process.getProcess(), new AppIdentifier(null, "a1"), storage, user3.getSupertokensUserId(), + UserIdType.SUPERTOKENS).appIdentifierWithStorage, user3.getSupertokensUserId()); assertEquals(user3, userInfo); } @@ -383,42 +383,42 @@ public void testUpdatePasswordWorksCorrectlyAcrossAllTenants() EmailPassword.updateUsersEmailOrPassword( StorageLayer.getAppIdentifierWithStorageAndUserIdMappingForUserWithPriorityForTenantStorage( - process.getProcess(), new AppIdentifier(null, "a1"), storage, user1.id, + process.getProcess(), new AppIdentifier(null, "a1"), storage, user1.getSupertokensUserId(), UserIdType.SUPERTOKENS).appIdentifierWithStorage, - process.getProcess(), user1.id, null, "newpassword1"); + process.getProcess(), user1.getSupertokensUserId(), null, "newpassword1"); EmailPassword.updateUsersEmailOrPassword( StorageLayer.getAppIdentifierWithStorageAndUserIdMappingForUserWithPriorityForTenantStorage( - process.getProcess(), new AppIdentifier(null, "a1"), storage, user2.id, + process.getProcess(), new AppIdentifier(null, "a1"), storage, user2.getSupertokensUserId(), UserIdType.SUPERTOKENS).appIdentifierWithStorage, - process.getProcess(), user2.id, null, "newpassword2"); + process.getProcess(), user2.getSupertokensUserId(), null, "newpassword2"); EmailPassword.updateUsersEmailOrPassword( StorageLayer.getAppIdentifierWithStorageAndUserIdMappingForUserWithPriorityForTenantStorage( - process.getProcess(), new AppIdentifier(null, "a1"), storage, user3.id, + process.getProcess(), new AppIdentifier(null, "a1"), storage, user3.getSupertokensUserId(), UserIdType.SUPERTOKENS).appIdentifierWithStorage, - process.getProcess(), user3.id, null, "newpassword3"); + process.getProcess(), user3.getSupertokensUserId(), null, "newpassword3"); { - t1 = StorageLayer.getTenantIdentifierWithStorageAndUserIdMappingForUser(process.getProcess(), t1, user1.id, + t1 = StorageLayer.getTenantIdentifierWithStorageAndUserIdMappingForUser(process.getProcess(), t1, user1.getSupertokensUserId(), UserIdType.SUPERTOKENS).tenantIdentifierWithStorage; AuthRecipeUserInfo userInfo = EmailPassword.signIn(t1storage, process.getProcess(), "user@example.com", "newpassword1"); - assertEquals(user1.id, userInfo.id); + assertEquals(user1.getSupertokensUserId(), userInfo.getSupertokensUserId()); } { - t2 = StorageLayer.getTenantIdentifierWithStorageAndUserIdMappingForUser(process.getProcess(), t2, user2.id, + t2 = StorageLayer.getTenantIdentifierWithStorageAndUserIdMappingForUser(process.getProcess(), t2, user2.getSupertokensUserId(), UserIdType.SUPERTOKENS).tenantIdentifierWithStorage; AuthRecipeUserInfo userInfo = EmailPassword.signIn(t2storage, process.getProcess(), "user@example.com", "newpassword2"); - assertEquals(user2.id, userInfo.id); + assertEquals(user2.getSupertokensUserId(), userInfo.getSupertokensUserId()); } { - t3 = StorageLayer.getTenantIdentifierWithStorageAndUserIdMappingForUser(process.getProcess(), t3, user3.id, + t3 = StorageLayer.getTenantIdentifierWithStorageAndUserIdMappingForUser(process.getProcess(), t3, user3.getSupertokensUserId(), UserIdType.SUPERTOKENS).tenantIdentifierWithStorage; AuthRecipeUserInfo userInfo = EmailPassword.signIn(t3storage, process.getProcess(), "user@example.com", "newpassword3"); - assertEquals(user3.id, userInfo.id); + assertEquals(user3.getSupertokensUserId(), userInfo.getSupertokensUserId()); } process.kill(); diff --git a/src/test/java/io/supertokens/test/emailpassword/PasswordHashingTest.java b/src/test/java/io/supertokens/test/emailpassword/PasswordHashingTest.java index d37e8ad54..ebafbef56 100644 --- a/src/test/java/io/supertokens/test/emailpassword/PasswordHashingTest.java +++ b/src/test/java/io/supertokens/test/emailpassword/PasswordHashingTest.java @@ -530,7 +530,7 @@ public void hashAndVerifyWithBcryptChangeToArgonPasswordWithResetFlow() throws E Config.getConfig(process.getProcess()).setPasswordHashingAlg(CoreConfig.PASSWORD_HASHING_ALG.ARGON2); - String token = EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.id); + String token = EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.getSupertokensUserId()); EmailPassword.resetPassword(process.getProcess(), token, "somePass2"); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.PASSWORD_HASH_ARGON)); @@ -561,7 +561,7 @@ public void hashAndVerifyWithArgonChangeToBcryptPasswordWithResetFlow() throws E Config.getConfig(process.getProcess()).setPasswordHashingAlg(CoreConfig.PASSWORD_HASHING_ALG.BCRYPT); - String token = EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.id); + String token = EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.getSupertokensUserId()); EmailPassword.resetPassword(process.getProcess(), token, "somePass2"); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.PASSWORD_HASH_BCRYPT)); @@ -591,7 +591,7 @@ public void hashAndVerifyWithBcryptChangeToArgonChangePassword() throws Exceptio Config.getConfig(process.getProcess()).setPasswordHashingAlg(CoreConfig.PASSWORD_HASHING_ALG.ARGON2); - EmailPassword.updateUsersEmailOrPassword(process.getProcess(), user.id, null, "somePass2"); + EmailPassword.updateUsersEmailOrPassword(process.getProcess(), user.getSupertokensUserId(), null, "somePass2"); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.PASSWORD_HASH_ARGON)); @@ -621,7 +621,7 @@ public void hashAndVerifyWithArgonChangeToBcryptChangePassword() throws Exceptio Config.getConfig(process.getProcess()).setPasswordHashingAlg(CoreConfig.PASSWORD_HASHING_ALG.BCRYPT); - EmailPassword.updateUsersEmailOrPassword(process.getProcess(), user.id, null, "somePass2"); + EmailPassword.updateUsersEmailOrPassword(process.getProcess(), user.getSupertokensUserId(), null, "somePass2"); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.PASSWORD_HASH_BCRYPT)); diff --git a/src/test/java/io/supertokens/test/emailpassword/UpdateUsersEmailAndPasswordTest.java b/src/test/java/io/supertokens/test/emailpassword/UpdateUsersEmailAndPasswordTest.java index 208a266f3..63393f08d 100644 --- a/src/test/java/io/supertokens/test/emailpassword/UpdateUsersEmailAndPasswordTest.java +++ b/src/test/java/io/supertokens/test/emailpassword/UpdateUsersEmailAndPasswordTest.java @@ -75,12 +75,12 @@ public void testUpdateEmailOnly() throws Exception { UserInfo userInfo = EmailPassword.signUp(main, "john.doe@example.com", "password"); // when - EmailPassword.updateUsersEmailOrPassword(main, userInfo.id, "dave.doe@example.com", null); + EmailPassword.updateUsersEmailOrPassword(main, userInfo.getSupertokensUserId(), "dave.doe@example.com", null); // then AuthRecipeUserInfo changedEmailUserInfo = EmailPassword.signIn(main, "dave.doe@example.com", "password"); - Assert.assertEquals(userInfo.id, changedEmailUserInfo.id); + Assert.assertEquals(userInfo.getSupertokensUserId(), changedEmailUserInfo.getSupertokensUserId()); Assert.assertEquals("dave.doe@example.com", changedEmailUserInfo.loginMethods[0].email); }); } @@ -100,7 +100,7 @@ public void testUpdateEmailToAnotherThatAlreadyExists() throws Exception { // when try { - EmailPassword.updateUsersEmailOrPassword(main, userInfo.id, userInfo2.email, null); + EmailPassword.updateUsersEmailOrPassword(main, userInfo.getSupertokensUserId(), userInfo2.email, null); Assert.fail(); } catch (DuplicateEmailException ignored) { } @@ -121,12 +121,12 @@ public void testUpdatePasswordOnly() throws Exception { UserInfo userInfo = EmailPassword.signUp(main, "john.doe@example.com", "password"); // when - EmailPassword.updateUsersEmailOrPassword(main, userInfo.id, null, "newPassword"); + EmailPassword.updateUsersEmailOrPassword(main, userInfo.getSupertokensUserId(), null, "newPassword"); // then AuthRecipeUserInfo changedEmailUserInfo = EmailPassword.signIn(main, "john.doe@example.com", "newPassword"); - Assert.assertEquals(userInfo.id, changedEmailUserInfo.id); + Assert.assertEquals(userInfo.getSupertokensUserId(), changedEmailUserInfo.getSupertokensUserId()); }); } @@ -143,13 +143,13 @@ public void testUpdateEmailAndPassword() throws Exception { UserInfo userInfo = EmailPassword.signUp(main, "john.doe@example.com", "password"); // when - EmailPassword.updateUsersEmailOrPassword(main, userInfo.id, "dave.doe@example.com", "newPassword"); + EmailPassword.updateUsersEmailOrPassword(main, userInfo.getSupertokensUserId(), "dave.doe@example.com", "newPassword"); // then AuthRecipeUserInfo changedCredentialsUserInfo = EmailPassword.signIn(main, "dave.doe@example.com", "newPassword"); - Assert.assertEquals(userInfo.id, changedCredentialsUserInfo.id); + Assert.assertEquals(userInfo.getSupertokensUserId(), changedCredentialsUserInfo.getSupertokensUserId()); Assert.assertEquals("dave.doe@example.com", changedCredentialsUserInfo.loginMethods[0].email); }); } diff --git a/src/test/java/io/supertokens/test/emailpassword/UserMigrationTest.java b/src/test/java/io/supertokens/test/emailpassword/UserMigrationTest.java index 4882074aa..805696057 100644 --- a/src/test/java/io/supertokens/test/emailpassword/UserMigrationTest.java +++ b/src/test/java/io/supertokens/test/emailpassword/UserMigrationTest.java @@ -137,7 +137,7 @@ public void testBasicUserMigration() throws Exception { // try and sign in with plainTextPassword AuthRecipeUserInfo userInfo = EmailPassword.signIn(process.main, email, plainTextPassword); - assertEquals(userInfo.id, importUserResponse.user.id); + assertEquals(userInfo.getSupertokensUserId(), importUserResponse.user.getSupertokensUserId()); assertEquals(userInfo.loginMethods[0].passwordHash, passwordHash); assertEquals(userInfo.loginMethods[0].email, email); } @@ -156,7 +156,7 @@ public void testBasicUserMigration() throws Exception { // try and sign in with plainTextPassword AuthRecipeUserInfo userInfo = EmailPassword.signIn(process.main, email, plainTextPassword); - assertEquals(userInfo.id, importUserResponse.user.id); + assertEquals(userInfo.getSupertokensUserId(), importUserResponse.user.getSupertokensUserId()); assertEquals(userInfo.loginMethods[0].passwordHash, passwordHash); assertEquals(userInfo.loginMethods[0].email, email); } @@ -202,7 +202,7 @@ public void testUpdatingAUsersPasswordHash() throws Exception { // sign in with the newPassword and check that it works AuthRecipeUserInfo userInfo = EmailPassword.signIn(process.main, email, newPassword); assertEquals(userInfo.loginMethods[0].email, signUpUserInfo.email); - assertEquals(userInfo.id, signUpUserInfo.id); + assertEquals(userInfo.getSupertokensUserId(), signUpUserInfo.getSupertokensUserId()); assertEquals(userInfo.timeJoined, signUpUserInfo.timeJoined); assertEquals(userInfo.loginMethods[0].passwordHash, newPasswordHash); diff --git a/src/test/java/io/supertokens/test/emailpassword/api/ConsumeResetPasswordAPITest4_0.java b/src/test/java/io/supertokens/test/emailpassword/api/ConsumeResetPasswordAPITest4_0.java index cb7385d8b..bb973959d 100644 --- a/src/test/java/io/supertokens/test/emailpassword/api/ConsumeResetPasswordAPITest4_0.java +++ b/src/test/java/io/supertokens/test/emailpassword/api/ConsumeResetPasswordAPITest4_0.java @@ -105,7 +105,7 @@ public void testGoodInput() throws Exception { AuthRecipeUserInfo user = EmailPassword.signUp(process.main, "random@gmail.com", "validPass123"); - String userId = user.id; + String userId = user.getSupertokensUserId(); String token = EmailPassword.generatePasswordResetToken(process.main, userId, "random@gmail.com"); @@ -117,7 +117,7 @@ public void testGoodInput() throws Exception { SemVer.v4_0.get(), "emailpassword"); assertEquals(passwordResetResponse.get("status").getAsString(), "OK"); assertEquals(passwordResetResponse.get("email").getAsString(), "random@gmail.com"); - assertEquals(passwordResetResponse.get("userId").getAsString(), user.id); + assertEquals(passwordResetResponse.get("userId").getAsString(), user.getSupertokensUserId()); assertEquals(passwordResetResponse.entrySet().size(), 3); process.kill(); @@ -136,9 +136,9 @@ public void testGoodInputWithUserIdMapping() throws Exception { } AuthRecipeUserInfo user = EmailPassword.signUp(process.main, "random@gmail.com", "validPass123"); - UserIdMapping.createUserIdMapping(process.main, user.id, "e1", null, false); + UserIdMapping.createUserIdMapping(process.main, user.getSupertokensUserId(), "e1", null, false); - String userId = user.id; + String userId = user.getSupertokensUserId(); String token = EmailPassword.generatePasswordResetToken(process.main, userId, "random@gmail.com"); diff --git a/src/test/java/io/supertokens/test/emailpassword/api/GeneratePasswordResetTokenAPITest2_7.java b/src/test/java/io/supertokens/test/emailpassword/api/GeneratePasswordResetTokenAPITest2_7.java index 85cf266e4..44f6c0de9 100644 --- a/src/test/java/io/supertokens/test/emailpassword/api/GeneratePasswordResetTokenAPITest2_7.java +++ b/src/test/java/io/supertokens/test/emailpassword/api/GeneratePasswordResetTokenAPITest2_7.java @@ -177,7 +177,7 @@ public void testUnknownUserWithUserIdFromNonEp() throws Exception { ThirdParty.SignInUpResponse res = ThirdParty.signInUp(process.main, "google", "ug", "t@example.com"); JsonObject requestBody = new JsonObject(); - requestBody.addProperty("userId", res.user.id); + requestBody.addProperty("userId", res.user.getSupertokensUserId()); JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/recipe/user/password/reset/token", requestBody, 1000, 1000, null, diff --git a/src/test/java/io/supertokens/test/emailpassword/api/GeneratePasswordResetTokenAPITest4_0.java b/src/test/java/io/supertokens/test/emailpassword/api/GeneratePasswordResetTokenAPITest4_0.java index 54f28dbfc..5e5a96814 100644 --- a/src/test/java/io/supertokens/test/emailpassword/api/GeneratePasswordResetTokenAPITest4_0.java +++ b/src/test/java/io/supertokens/test/emailpassword/api/GeneratePasswordResetTokenAPITest4_0.java @@ -106,7 +106,7 @@ public void testBadInput() throws Exception { { AuthRecipeUserInfo user = EmailPassword.signUp(process.main, "a@a.com", "p1234"); JsonObject requestBody = new JsonObject(); - requestBody.addProperty("userId", user.id); + requestBody.addProperty("userId", user.getSupertokensUserId()); try { HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", "http://localhost:3567/recipe/user/password/reset/token", requestBody, 1000, 1000, null, @@ -167,7 +167,7 @@ public void testGoodInputWithUserIdMapping() throws Exception { } AuthRecipeUserInfo user = EmailPassword.signUp(process.main, "a@a.com", "p1234"); - UserIdMapping.createUserIdMapping(process.main, user.id, "e1", null, false); + UserIdMapping.createUserIdMapping(process.main, user.getSupertokensUserId(), "e1", null, false); JsonObject requestBody = new JsonObject(); requestBody.addProperty("userId", "e1"); @@ -226,7 +226,7 @@ public void testUnknownUserWithUserIdFromNonEp() throws Exception { ThirdParty.SignInUpResponse res = ThirdParty.signInUp(process.main, "google", "ug", "t@example.com"); JsonObject requestBody = new JsonObject(); - requestBody.addProperty("userId", res.user.id); + requestBody.addProperty("userId", res.user.getSupertokensUserId()); requestBody.addProperty("email", res.user.loginMethods[0].email); JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", diff --git a/src/test/java/io/supertokens/test/emailpassword/api/ImportUserWithPasswordHashAPITest.java b/src/test/java/io/supertokens/test/emailpassword/api/ImportUserWithPasswordHashAPITest.java index 90609ce21..c79af7716 100644 --- a/src/test/java/io/supertokens/test/emailpassword/api/ImportUserWithPasswordHashAPITest.java +++ b/src/test/java/io/supertokens/test/emailpassword/api/ImportUserWithPasswordHashAPITest.java @@ -537,7 +537,7 @@ public void testUpdatingAUsersPasswordHash() throws Exception { assertTrue(response.get("didUserAlreadyExist").getAsBoolean()); // check that a new user was not created by comparing userIds - assertEquals(initialUserInfo.id, response.get("user").getAsJsonObject().get("id").getAsString()); + assertEquals(initialUserInfo.getSupertokensUserId(), response.get("user").getAsJsonObject().get("id").getAsString()); // sign in with the new password to check if the password hash got updated AuthRecipeUserInfo updatedUserInfo = EmailPassword.signIn(process.main, email, newPassword); diff --git a/src/test/java/io/supertokens/test/emailpassword/api/SignInAPITest4_0.java b/src/test/java/io/supertokens/test/emailpassword/api/SignInAPITest4_0.java index dfb2dc334..8d81f0038 100644 --- a/src/test/java/io/supertokens/test/emailpassword/api/SignInAPITest4_0.java +++ b/src/test/java/io/supertokens/test/emailpassword/api/SignInAPITest4_0.java @@ -84,7 +84,7 @@ public void testGoodInput() throws Exception { assertEquals(signInResponse.entrySet().size(), 2); JsonObject jsonUser = signInResponse.get("user").getAsJsonObject(); - assert (jsonUser.get("id").getAsString().equals(user.id)); + assert (jsonUser.get("id").getAsString().equals(user.getSupertokensUserId())); assert (jsonUser.get("timeJoined").getAsLong() == user.timeJoined); assert (!jsonUser.get("isPrimaryUser").getAsBoolean()); assert (jsonUser.get("emails").getAsJsonArray().size() == 1); @@ -95,7 +95,7 @@ public void testGoodInput() throws Exception { JsonObject lM = jsonUser.get("loginMethods").getAsJsonArray().get(0).getAsJsonObject(); assertFalse(lM.get("verified").getAsBoolean()); assertEquals(lM.get("timeJoined").getAsLong(), user.timeJoined); - assertEquals(lM.get("recipeUserId").getAsString(), user.id); + assertEquals(lM.get("recipeUserId").getAsString(), user.getSupertokensUserId()); assertEquals(lM.get("recipeId").getAsString(), "emailpassword"); assertEquals(lM.get("email").getAsString(), "random@gmail.com"); assert (lM.entrySet().size() == 6); @@ -119,7 +119,7 @@ public void testGoodInputWithUserIdMapping() throws Exception { } AuthRecipeUserInfo user = EmailPassword.signUp(process.main, "random@gmail.com", "validPass123"); - UserIdMapping.createUserIdMapping(process.main, user.id, "e1", null, false); + UserIdMapping.createUserIdMapping(process.main, user.getSupertokensUserId(), "e1", null, false); JsonObject responseBody = new JsonObject(); responseBody.addProperty("email", "random@gmail.com"); @@ -175,14 +175,14 @@ public void testGoodInputWithUserIdMappingAndMultipleLinkedAccounts() throws Exc } AuthRecipeUserInfo user0 = EmailPassword.signUp(process.main, "random1@gmail.com", "validPass123"); - UserIdMapping.createUserIdMapping(process.main, user0.id, "e0", null, false); + UserIdMapping.createUserIdMapping(process.main, user0.getSupertokensUserId(), "e0", null, false); Thread.sleep(1); // add a small delay to ensure a unique timestamp AuthRecipeUserInfo user = EmailPassword.signUp(process.main, "random@gmail.com", "validPass123"); - UserIdMapping.createUserIdMapping(process.main, user.id, "e1", null, false); - AuthRecipe.createPrimaryUser(process.main, user.id); - AuthRecipe.linkAccounts(process.main, user0.id, user.id); + UserIdMapping.createUserIdMapping(process.main, user.getSupertokensUserId(), "e1", null, false); + AuthRecipe.createPrimaryUser(process.main, user.getSupertokensUserId()); + AuthRecipe.linkAccounts(process.main, user0.getSupertokensUserId(), user.getSupertokensUserId()); JsonObject responseBody = new JsonObject(); responseBody.addProperty("email", "random@gmail.com"); @@ -225,7 +225,7 @@ public void testGoodInputWithUserIdMappingAndMultipleLinkedAccounts() throws Exc JsonObject lM = jsonUser.get("loginMethods").getAsJsonArray().get(0).getAsJsonObject(); assertFalse(lM.get("verified").getAsBoolean()); assertEquals(lM.get("timeJoined").getAsLong(), user0.timeJoined); - assertEquals(lM.get("recipeUserId").getAsString(), user0.id); + assertEquals(lM.get("recipeUserId").getAsString(), user0.getSupertokensUserId()); assertEquals(lM.get("recipeId").getAsString(), "emailpassword"); assertEquals(lM.get("email").getAsString(), "random1@gmail.com"); assert (lM.entrySet().size() == 6); diff --git a/src/test/java/io/supertokens/test/emailpassword/api/SignUpAPITest2_7.java b/src/test/java/io/supertokens/test/emailpassword/api/SignUpAPITest2_7.java index cbca37a5b..7705ee413 100644 --- a/src/test/java/io/supertokens/test/emailpassword/api/SignUpAPITest2_7.java +++ b/src/test/java/io/supertokens/test/emailpassword/api/SignUpAPITest2_7.java @@ -148,7 +148,7 @@ public void testGoodInput() throws Exception { AuthRecipeUserInfo user = ((AuthRecipeStorage) StorageLayer.getStorage(process.getProcess())) .listPrimaryUsersByEmail(new TenantIdentifier(null, null, null), "random@gmail.com")[0]; assertEquals(user.loginMethods[0].email, signUpUser.get("email").getAsString()); - assertEquals(user.id, signUpUser.get("id").getAsString()); + assertEquals(user.getSupertokensUserId(), signUpUser.get("id").getAsString()); JsonObject responseBody = new JsonObject(); responseBody.addProperty("email", "random@gmail.com"); diff --git a/src/test/java/io/supertokens/test/emailpassword/api/UserPutAPITest2_8.java b/src/test/java/io/supertokens/test/emailpassword/api/UserPutAPITest2_8.java index c4f62681c..6cfb9d331 100644 --- a/src/test/java/io/supertokens/test/emailpassword/api/UserPutAPITest2_8.java +++ b/src/test/java/io/supertokens/test/emailpassword/api/UserPutAPITest2_8.java @@ -79,7 +79,7 @@ public void testQueryingWithEmailThatAlreadyExists() throws Exception { UserInfo user2 = EmailPassword.signUp(process.getProcess(), "someemail2@gmail.com", "somePass"); JsonObject body = new JsonObject(); - body.addProperty("userId", user.id); + body.addProperty("userId", user.getSupertokensUserId()); body.addProperty("email", user2.email); JsonObject response = HttpRequestForTesting.sendJsonPUTRequest(process.getProcess(), "", @@ -101,7 +101,7 @@ public void testUpdatingEmailNormalisesIt() throws Exception { UserInfo user = EmailPassword.signUp(process.getProcess(), "someemail@gmail.com", "somePass"); JsonObject body = new JsonObject(); - body.addProperty("userId", user.id); + body.addProperty("userId", user.getSupertokensUserId()); body.addProperty("email", "someemail+TEST@gmail.com"); JsonObject response = HttpRequestForTesting.sendJsonPUTRequest(process.getProcess(), "", @@ -147,7 +147,7 @@ public void testSuccessfulUpdate() throws Exception { UserInfo user = EmailPassword.signUp(process.getProcess(), "someemail@gmail.com", "somePass"); JsonObject body = new JsonObject(); - body.addProperty("userId", user.id); + body.addProperty("userId", user.getSupertokensUserId()); body.addProperty("email", "someOtherEmail@gmail.com"); JsonObject response = HttpRequestForTesting.sendJsonPUTRequest(process.getProcess(), "", @@ -171,7 +171,7 @@ public void testSuccessfulUpdateWithOnlyPassword() throws Exception { UserInfo user = EmailPassword.signUp(process.getProcess(), "someemail@gmail.com", "somePass"); JsonObject body = new JsonObject(); - body.addProperty("userId", user.id); + body.addProperty("userId", user.getSupertokensUserId()); body.addProperty("password", "somePass123"); JsonObject response = HttpRequestForTesting.sendJsonPUTRequest(process.getProcess(), "", diff --git a/src/test/java/io/supertokens/test/emailpassword/api/UserPutAPITest4_0.java b/src/test/java/io/supertokens/test/emailpassword/api/UserPutAPITest4_0.java index cce87f29a..07cd1b100 100644 --- a/src/test/java/io/supertokens/test/emailpassword/api/UserPutAPITest4_0.java +++ b/src/test/java/io/supertokens/test/emailpassword/api/UserPutAPITest4_0.java @@ -68,13 +68,13 @@ public void testThatAPIReturnsEmailUpdateNotPossibleWithSingleTenant() throws Ex } UserInfo user0 = EmailPassword.signUp(process.getProcess(), "someemail1@gmail.com", "somePass"); - AuthRecipe.createPrimaryUser(process.main, user0.id); + AuthRecipe.createPrimaryUser(process.main, user0.getSupertokensUserId()); UserInfo user = EmailPassword.signUp(process.getProcess(), "someemail@gmail.com", "somePass"); - AuthRecipe.createPrimaryUser(process.main, user.id); + AuthRecipe.createPrimaryUser(process.main, user.getSupertokensUserId()); JsonObject body = new JsonObject(); - body.addProperty("recipeUserId", user.id); + body.addProperty("recipeUserId", user.getSupertokensUserId()); body.addProperty("email", "someemail1@gmail.com"); JsonObject response = HttpRequestForTesting.sendJsonPUTRequest(process.getProcess(), "", diff --git a/src/test/java/io/supertokens/test/emailverification/DeleteExpiredEmailVerificationTokensCronjobTest.java b/src/test/java/io/supertokens/test/emailverification/DeleteExpiredEmailVerificationTokensCronjobTest.java index 78beca854..11e074f94 100644 --- a/src/test/java/io/supertokens/test/emailverification/DeleteExpiredEmailVerificationTokensCronjobTest.java +++ b/src/test/java/io/supertokens/test/emailverification/DeleteExpiredEmailVerificationTokensCronjobTest.java @@ -25,7 +25,6 @@ import io.supertokens.pluginInterface.emailpassword.UserInfo; import io.supertokens.pluginInterface.emailverification.EmailVerificationTokenInfo; import io.supertokens.pluginInterface.emailverification.sqlStorage.EmailVerificationSQLStorage; -import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.TestingProcessManager; @@ -68,22 +67,22 @@ public void checkingCronJob() throws Exception { UserInfo user = EmailPassword.signUp(process.getProcess(), "test1@example.com", "password"); - String tok = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.id, user.email); - String tok2 = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.id, user.email); + String tok = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.getSupertokensUserId(), user.email); + String tok2 = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.getSupertokensUserId(), user.email); Thread.sleep(2000); - String tok3 = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.id, user.email); - String tok4 = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.id, user.email); + String tok3 = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.getSupertokensUserId(), user.email); + String tok4 = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.getSupertokensUserId(), user.email); assert (((EmailVerificationSQLStorage) StorageLayer.getStorage(process.getProcess())) - .getAllEmailVerificationTokenInfoForUser(new TenantIdentifier(null, null, null), user.id, user.email).length == + .getAllEmailVerificationTokenInfoForUser(new TenantIdentifier(null, null, null), user.getSupertokensUserId(), user.email).length == 4); Thread.sleep(3500); EmailVerificationTokenInfo[] tokens = ((EmailVerificationSQLStorage) StorageLayer.getStorage(process.getProcess())) - .getAllEmailVerificationTokenInfoForUser(new TenantIdentifier(null, null, null), user.id, user.email); + .getAllEmailVerificationTokenInfoForUser(new TenantIdentifier(null, null, null), user.getSupertokensUserId(), user.email); assert (tokens.length == 2); diff --git a/src/test/java/io/supertokens/test/emailverification/EmailVerificationTest.java b/src/test/java/io/supertokens/test/emailverification/EmailVerificationTest.java index de78a89cb..94b210777 100644 --- a/src/test/java/io/supertokens/test/emailverification/EmailVerificationTest.java +++ b/src/test/java/io/supertokens/test/emailverification/EmailVerificationTest.java @@ -80,13 +80,13 @@ public void testGeneratingEmailVerificationTokenTwoTimes() throws Exception { } UserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "testPass123"); - String token1 = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.id, user.email); - String token2 = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.id, user.email); + String token1 = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.getSupertokensUserId(), user.email); + String token2 = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.getSupertokensUserId(), user.email); assertNotEquals(token1, token2); EmailVerificationTokenInfo[] tokenInfo = ((EmailVerificationSQLStorage) StorageLayer.getStorage(process.getProcess())) - .getAllEmailVerificationTokenInfoForUser(new TenantIdentifier(null, null, null), user.id, user.email); + .getAllEmailVerificationTokenInfoForUser(new TenantIdentifier(null, null, null), user.getSupertokensUserId(), user.email); assertEquals(tokenInfo.length, 2); assertTrue((tokenInfo[0].token.equals(io.supertokens.utils.Utils.hashSHA256(token1))) @@ -111,14 +111,14 @@ public void testVerifyingEmailAndGeneratingToken() throws Exception { } UserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "testPass123"); - String token = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.id, user.email); + String token = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.getSupertokensUserId(), user.email); EmailVerification.verifyEmail(process.getProcess(), token); - assertTrue(EmailVerification.isEmailVerified(process.getProcess(), user.id, user.email)); + assertTrue(EmailVerification.isEmailVerified(process.getProcess(), user.getSupertokensUserId(), user.email)); try { - EmailVerification.generateEmailVerificationToken(process.getProcess(), user.id, user.email); + EmailVerification.generateEmailVerificationToken(process.getProcess(), user.getSupertokensUserId(), user.email); throw new Exception("should not come here"); } catch (EmailAlreadyVerifiedException ignored) { } @@ -162,11 +162,11 @@ public void testGeneratingTwoTokenVerifyOtherTokenShouldThrowAnError() throws Ex } UserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "testPass123"); - String token1 = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.id, user.email); - String token2 = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.id, user.email); + String token1 = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.getSupertokensUserId(), user.email); + String token2 = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.getSupertokensUserId(), user.email); EmailVerification.verifyEmail(process.getProcess(), token1); - assertTrue(EmailVerification.isEmailVerified(process.getProcess(), user.id, user.email)); + assertTrue(EmailVerification.isEmailVerified(process.getProcess(), user.getSupertokensUserId(), user.email)); try { EmailVerification.verifyEmail(process.getProcess(), token2); @@ -195,7 +195,7 @@ public void useAnExpiredTokenItShouldThrowAnError() throws Exception { } UserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "testPass123"); - String token = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.id, user.email); + String token = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.getSupertokensUserId(), user.email); Thread.sleep(20); @@ -224,7 +224,7 @@ public void testFormatOfEmailVerificationToken() throws Exception { UserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "testPass123"); for (int i = 0; i < 100; i++) { - String verifyToken = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.id, + String verifyToken = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.getSupertokensUserId(), user.email); assertEquals(verifyToken.length(), 128); assertFalse(verifyToken.contains("+")); @@ -252,7 +252,7 @@ public void clashingEmailVerificationToken() throws Exception { ((EmailVerificationSQLStorage) StorageLayer.getStorage(process.getProcess())) .addEmailVerificationToken(new TenantIdentifier(null, null, null), - new EmailVerificationTokenInfo(user.id, "token", + new EmailVerificationTokenInfo(user.getSupertokensUserId(), "token", System.currentTimeMillis() + Config.getConfig(process.getProcess()).getEmailVerificationTokenLifetime(), "test1@example.com")); @@ -260,7 +260,7 @@ public void clashingEmailVerificationToken() throws Exception { try { ((EmailVerificationSQLStorage) StorageLayer.getStorage(process.getProcess())) .addEmailVerificationToken(new TenantIdentifier(null, null, null), - new EmailVerificationTokenInfo(user.id, "token", + new EmailVerificationTokenInfo(user.getSupertokensUserId(), "token", System.currentTimeMillis() + Config.getConfig(process.getProcess()).getEmailVerificationTokenLifetime(), @@ -287,15 +287,15 @@ public void verifyEmail() throws Exception { UserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "password"); - assert (!EmailVerification.isEmailVerified(process.getProcess(), user.id, user.email)); + assert (!EmailVerification.isEmailVerified(process.getProcess(), user.getSupertokensUserId(), user.email)); - String token = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.id, user.email); + String token = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.getSupertokensUserId(), user.email); assert (token != null); EmailVerification.verifyEmail(process.getProcess(), token); - assert (EmailVerification.isEmailVerified(process.getProcess(), user.id, user.email)); + assert (EmailVerification.isEmailVerified(process.getProcess(), user.getSupertokensUserId(), user.email)); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -314,23 +314,23 @@ public void testVerifyingEmailAndThenUnverify() throws Exception { } UserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "testPass123"); - String token = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.id, user.email); + String token = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.getSupertokensUserId(), user.email); EmailVerification.verifyEmail(process.getProcess(), token); - assertTrue(EmailVerification.isEmailVerified(process.getProcess(), user.id, user.email)); + assertTrue(EmailVerification.isEmailVerified(process.getProcess(), user.getSupertokensUserId(), user.email)); ((EmailVerificationSQLStorage) StorageLayer.getStorage(process.getProcess())).startTransaction(con -> { try { ((EmailVerificationSQLStorage) StorageLayer.getStorage(process.getProcess())) .updateIsEmailVerified_Transaction(new AppIdentifier(null, null), con, - user.id, user.email, false); + user.getSupertokensUserId(), user.email, false); } catch (TenantOrAppNotFoundException e) { throw new RuntimeException(e); } return null; }); - assertFalse(EmailVerification.isEmailVerified(process.getProcess(), user.id, user.email)); + assertFalse(EmailVerification.isEmailVerified(process.getProcess(), user.getSupertokensUserId(), user.email)); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -349,23 +349,23 @@ public void testVerifyingSameEmailTwice() throws Exception { } UserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "testPass123"); - String token = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.id, user.email); + String token = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.getSupertokensUserId(), user.email); EmailVerification.verifyEmail(process.getProcess(), token); - assertTrue(EmailVerification.isEmailVerified(process.getProcess(), user.id, user.email)); + assertTrue(EmailVerification.isEmailVerified(process.getProcess(), user.getSupertokensUserId(), user.email)); ((EmailVerificationSQLStorage) StorageLayer.getStorage(process.getProcess())).startTransaction(con -> { try { ((EmailVerificationSQLStorage) StorageLayer.getStorage(process.getProcess())) .updateIsEmailVerified_Transaction(new AppIdentifier(null, null), con, - user.id, user.email, true); + user.getSupertokensUserId(), user.email, true); } catch (TenantOrAppNotFoundException e) { throw new RuntimeException(e); } return null; }); - assertTrue(EmailVerification.isEmailVerified(process.getProcess(), user.id, user.email)); + assertTrue(EmailVerification.isEmailVerified(process.getProcess(), user.getSupertokensUserId(), user.email)); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); diff --git a/src/test/java/io/supertokens/test/multitenant/AppTenantUserTest.java b/src/test/java/io/supertokens/test/multitenant/AppTenantUserTest.java index d7c13d4fd..4b84b2f26 100644 --- a/src/test/java/io/supertokens/test/multitenant/AppTenantUserTest.java +++ b/src/test/java/io/supertokens/test/multitenant/AppTenantUserTest.java @@ -122,7 +122,7 @@ public void testDeletingAppDeleteNonAuthRecipeData() throws Exception { UserInfo user = EmailPassword.signUp(tWithStorage, process.getProcess(), "test@example.com", "password"); - String userId = user.id; + String userId = user.getSupertokensUserId(); // create entry in nonAuth table StorageLayer.getStorage(process.main).addInfoToNonAuthRecipesBasedOnUserId(app, className, userId); @@ -224,7 +224,7 @@ public void testDisassociationOfUserDeletesNonAuthRecipeData() throws Exception } UserInfo user = EmailPassword.signUp(appWithStorage, process.getProcess(), "test@example.com", "password"); - String userId = user.id; + String userId = user.getSupertokensUserId(); Multitenancy.addUserIdToTenant(process.getProcess(), tenantWithStorage, userId); @@ -292,7 +292,7 @@ public void deletingTenantKeepsTheUserInTheApp() throws Exception { StorageLayer.getStorage(tenant, process.getProcess())); UserInfo user = EmailPassword.signUp(tenantWithStorage, process.getProcess(), "test@example.com", "password"); - String userId = user.id; + String userId = user.getSupertokensUserId(); Multitenancy.deleteTenant(tenant, process.getProcess()); @@ -302,7 +302,7 @@ public void deletingTenantKeepsTheUserInTheApp() throws Exception { UserInfo appUser = EmailPassword.getUserUsingId(appWithStorage.toAppIdentifierWithStorage(), userId); assertNotNull(appUser); - assertEquals(userId, appUser.id); + assertEquals(userId, appUser.getSupertokensUserId()); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); diff --git a/src/test/java/io/supertokens/test/multitenant/TestAppData.java b/src/test/java/io/supertokens/test/multitenant/TestAppData.java index 61a9ad9ab..7af3ee787 100644 --- a/src/test/java/io/supertokens/test/multitenant/TestAppData.java +++ b/src/test/java/io/supertokens/test/multitenant/TestAppData.java @@ -124,7 +124,7 @@ public void testThatDeletingAppDeleteDataFromAllTables() throws Exception { // Add all recipe data UserInfo epUser = EmailPassword.signUp(appWithStorage, process.getProcess(), "test@example.com", "password"); - EmailPassword.generatePasswordResetTokenBeforeCdi4_0(appWithStorage, process.getProcess(), epUser.id); + EmailPassword.generatePasswordResetTokenBeforeCdi4_0(appWithStorage, process.getProcess(), epUser.getSupertokensUserId()); ThirdParty.SignInUpResponse tpUser = ThirdParty.signInUp(appWithStorage, process.getProcess(), "google", "googleid", "test@example.com"); @@ -141,28 +141,28 @@ public void testThatDeletingAppDeleteDataFromAllTables() throws Exception { "user@example.com", "password"); String evToken = EmailVerification.generateEmailVerificationToken(appWithStorage, process.getProcess(), - epUser.id, epUser.email); + epUser.getSupertokensUserId(), epUser.email); EmailVerification.verifyEmail(appWithStorage, evToken); - EmailVerification.generateEmailVerificationToken(appWithStorage, process.getProcess(), tpUser.user.id, + EmailVerification.generateEmailVerificationToken(appWithStorage, process.getProcess(), tpUser.user.getSupertokensUserId(), tpUser.user.loginMethods[0].email); - Session.createNewSession(appWithStorage, process.getProcess(), epUser.id, new JsonObject(), new JsonObject()); + Session.createNewSession(appWithStorage, process.getProcess(), epUser.getSupertokensUserId(), new JsonObject(), new JsonObject()); UserRoles.createNewRoleOrModifyItsPermissions(appWithStorage.toAppIdentifierWithStorage(), "role", new String[]{"permission1", "permission2"}); - UserRoles.addRoleToUser(appWithStorage, epUser.id, "role"); + UserRoles.addRoleToUser(appWithStorage, epUser.getSupertokensUserId(), "role"); TOTPDevice totpDevice = Totp.registerDevice(appWithStorage.toAppIdentifierWithStorage(), process.getProcess(), - epUser.id, "test", 1, 3); - Totp.verifyCode(appWithStorage, process.getProcess(), epUser.id, + epUser.getSupertokensUserId(), "test", 1, 3); + Totp.verifyCode(appWithStorage, process.getProcess(), epUser.getSupertokensUserId(), generateTotpCode(process.getProcess(), totpDevice, 0), true); - ActiveUsers.updateLastActive(appWithStorage.toAppIdentifierWithStorage(), process.getProcess(), epUser.id); + ActiveUsers.updateLastActive(appWithStorage.toAppIdentifierWithStorage(), process.getProcess(), epUser.getSupertokensUserId()); - UserMetadata.updateUserMetadata(appWithStorage.toAppIdentifierWithStorage(), epUser.id, new JsonObject()); + UserMetadata.updateUserMetadata(appWithStorage.toAppIdentifierWithStorage(), epUser.getSupertokensUserId(), new JsonObject()); UserIdMapping.createUserIdMapping(process.getProcess(), appWithStorage.toAppIdentifierWithStorage(), - plUser.user.id, "externalid", null, false); + plUser.user.getSupertokensUserId(), "externalid", null, false); String[] tablesThatHaveData = appWithStorage.getStorage() .getAllTablesInTheDatabaseThatHasDataForAppId(app.getAppId()); diff --git a/src/test/java/io/supertokens/test/multitenant/api/TestTenantIdIsNotPresentForOlderCDI.java b/src/test/java/io/supertokens/test/multitenant/api/TestTenantIdIsNotPresentForOlderCDI.java index 98bccfaa4..3a91931e8 100644 --- a/src/test/java/io/supertokens/test/multitenant/api/TestTenantIdIsNotPresentForOlderCDI.java +++ b/src/test/java/io/supertokens/test/multitenant/api/TestTenantIdIsNotPresentForOlderCDI.java @@ -288,11 +288,11 @@ private void createUsers(TenantIdentifier tenantIdentifier, int numUsers, String UserInfo user = EmailPassword.signUp( tenantIdentifierWithStorage, process.getProcess(), prefix + "epuser" + i + "@example.com", "password" + i); - tenantToUsers.get(tenantIdentifier).add(user.id); + tenantToUsers.get(tenantIdentifier).add(user.getSupertokensUserId()); if (!recipeToUsers.containsKey("emailpassword")) { recipeToUsers.put("emailpassword", new ArrayList<>()); } - recipeToUsers.get("emailpassword").add(user.id); + recipeToUsers.get("emailpassword").add(user.getSupertokensUserId()); } { Passwordless.CreateCodeResponse codeResponse = Passwordless.createCode( @@ -305,27 +305,27 @@ private void createUsers(TenantIdentifier tenantIdentifier, int numUsers, String Passwordless.ConsumeCodeResponse response = Passwordless.consumeCode(tenantIdentifierWithStorage, process.getProcess(), codeResponse.deviceId, codeResponse.deviceIdHash, "abcd", null); - tenantToUsers.get(tenantIdentifier).add(response.user.id); + tenantToUsers.get(tenantIdentifier).add(response.user.getSupertokensUserId()); if (!recipeToUsers.containsKey("passwordless")) { recipeToUsers.put("passwordless", new ArrayList<>()); } - recipeToUsers.get("passwordless").add(response.user.id); + recipeToUsers.get("passwordless").add(response.user.getSupertokensUserId()); } { ThirdParty.SignInUpResponse user1 = ThirdParty.signInUp(tenantIdentifierWithStorage, process.getProcess(), "google", "googleid" + i, prefix + "tpuser" + i + "@example.com"); - tenantToUsers.get(tenantIdentifier).add(user1.user.id); + tenantToUsers.get(tenantIdentifier).add(user1.user.getSupertokensUserId()); ThirdParty.SignInUpResponse user2 = ThirdParty.signInUp(tenantIdentifierWithStorage, process.getProcess(), "facebook", "fbid" + i, prefix + "tpuser" + i + "@example.com"); - tenantToUsers.get(tenantIdentifier).add(user2.user.id); + tenantToUsers.get(tenantIdentifier).add(user2.user.getSupertokensUserId()); if (!recipeToUsers.containsKey("thirdparty")) { recipeToUsers.put("thirdparty", new ArrayList<>()); } - recipeToUsers.get("thirdparty").add(user1.user.id); - recipeToUsers.get("thirdparty").add(user2.user.id); + recipeToUsers.get("thirdparty").add(user1.user.getSupertokensUserId()); + recipeToUsers.get("thirdparty").add(user2.user.getSupertokensUserId()); } } } diff --git a/src/test/java/io/supertokens/test/multitenant/api/TestTenantUserAssociation.java b/src/test/java/io/supertokens/test/multitenant/api/TestTenantUserAssociation.java index 45fdb5904..c95a5d84d 100644 --- a/src/test/java/io/supertokens/test/multitenant/api/TestTenantUserAssociation.java +++ b/src/test/java/io/supertokens/test/multitenant/api/TestTenantUserAssociation.java @@ -282,16 +282,16 @@ public void testEmailPasswordUsersHaveTenantIds() throws Exception { process.getProcess(), "user@example.com", "password"); assertArrayEquals(new String[]{"t1"}, user.tenantIds.toArray()); - Multitenancy.addUserIdToTenant(process.getProcess(), t2WithStorage, user.id); - user = EmailPassword.getUserUsingId(t1WithStorage.toAppIdentifierWithStorage(), user.id); + Multitenancy.addUserIdToTenant(process.getProcess(), t2WithStorage, user.getSupertokensUserId()); + user = EmailPassword.getUserUsingId(t1WithStorage.toAppIdentifierWithStorage(), user.getSupertokensUserId()); Utils.assertArrayEqualsIgnoreOrder(new String[]{"t1", "t2"}, user.tenantIds.toArray()); user = EmailPassword.getUserUsingEmail(t1WithStorage, user.loginMethods[0].email); Utils.assertArrayEqualsIgnoreOrder(new String[]{"t1", "t2"}, user.tenantIds.toArray()); - Multitenancy.removeUserIdFromTenant(process.getProcess(), t1WithStorage, user.id, null); - user = EmailPassword.getUserUsingId(t1WithStorage.toAppIdentifierWithStorage(), user.id); + Multitenancy.removeUserIdFromTenant(process.getProcess(), t1WithStorage, user.getSupertokensUserId(), null); + user = EmailPassword.getUserUsingId(t1WithStorage.toAppIdentifierWithStorage(), user.getSupertokensUserId()); assertArrayEquals(new String[]{"t2"}, user.tenantIds.toArray()); } @@ -317,15 +317,15 @@ public void testPasswordlessUsersHaveTenantIds1() throws Exception { assertArrayEquals(new String[]{"t1"}, consumeCodeResponse.user.tenantIds.toArray()); AuthRecipeUserInfo user; - Multitenancy.addUserIdToTenant(process.getProcess(), t2WithStorage, consumeCodeResponse.user.id); - user = Passwordless.getUserById(t1WithStorage.toAppIdentifierWithStorage(), consumeCodeResponse.user.id); + Multitenancy.addUserIdToTenant(process.getProcess(), t2WithStorage, consumeCodeResponse.user.getSupertokensUserId()); + user = Passwordless.getUserById(t1WithStorage.toAppIdentifierWithStorage(), consumeCodeResponse.user.getSupertokensUserId()); Utils.assertArrayEqualsIgnoreOrder(new String[]{"t1", "t2"}, user.tenantIds.toArray()); user = Passwordless.getUserByEmail(t1WithStorage, consumeCodeResponse.user.loginMethods[0].email); Utils.assertArrayEqualsIgnoreOrder(new String[]{"t1", "t2"}, user.tenantIds.toArray()); - Multitenancy.removeUserIdFromTenant(process.getProcess(), t1WithStorage, consumeCodeResponse.user.id, null); - user = Passwordless.getUserById(t1WithStorage.toAppIdentifierWithStorage(), consumeCodeResponse.user.id); + Multitenancy.removeUserIdFromTenant(process.getProcess(), t1WithStorage, consumeCodeResponse.user.getSupertokensUserId(), null); + user = Passwordless.getUserById(t1WithStorage.toAppIdentifierWithStorage(), consumeCodeResponse.user.getSupertokensUserId()); assertArrayEquals(new String[]{"t2"}, user.tenantIds.toArray()); } @@ -351,15 +351,15 @@ public void testPasswordlessUsersHaveTenantIds2() throws Exception { assertArrayEquals(new String[]{"t1"}, consumeCodeResponse.user.tenantIds.toArray()); AuthRecipeUserInfo user; - Multitenancy.addUserIdToTenant(process.getProcess(), t2WithStorage, consumeCodeResponse.user.id); - user = Passwordless.getUserById(t1WithStorage.toAppIdentifierWithStorage(), consumeCodeResponse.user.id); + Multitenancy.addUserIdToTenant(process.getProcess(), t2WithStorage, consumeCodeResponse.user.getSupertokensUserId()); + user = Passwordless.getUserById(t1WithStorage.toAppIdentifierWithStorage(), consumeCodeResponse.user.getSupertokensUserId()); Utils.assertArrayEqualsIgnoreOrder(new String[]{"t1", "t2"}, user.tenantIds.toArray()); user = Passwordless.getUserByPhoneNumber(t1WithStorage, consumeCodeResponse.user.loginMethods[0].phoneNumber); Utils.assertArrayEqualsIgnoreOrder(new String[]{"t1", "t2"}, user.tenantIds.toArray()); - Multitenancy.removeUserIdFromTenant(process.getProcess(), t1WithStorage, consumeCodeResponse.user.id, null); - user = Passwordless.getUserById(t1WithStorage.toAppIdentifierWithStorage(), consumeCodeResponse.user.id); + Multitenancy.removeUserIdFromTenant(process.getProcess(), t1WithStorage, consumeCodeResponse.user.getSupertokensUserId(), null); + user = Passwordless.getUserById(t1WithStorage.toAppIdentifierWithStorage(), consumeCodeResponse.user.getSupertokensUserId()); assertArrayEquals(new String[]{"t2"}, user.tenantIds.toArray()); } @@ -382,9 +382,9 @@ public void testThirdPartyUsersHaveTenantIds() throws Exception { "googleid", "user@example.com"); assertArrayEquals(new String[]{"t1"}, signInUpResponse.user.tenantIds.toArray()); - Multitenancy.addUserIdToTenant(process.getProcess(), t2WithStorage, signInUpResponse.user.id); + Multitenancy.addUserIdToTenant(process.getProcess(), t2WithStorage, signInUpResponse.user.getSupertokensUserId()); AuthRecipeUserInfo user = ThirdParty.getUser( - t1WithStorage.toAppIdentifierWithStorage(), signInUpResponse.user.id); + t1WithStorage.toAppIdentifierWithStorage(), signInUpResponse.user.getSupertokensUserId()); Utils.assertArrayEqualsIgnoreOrder(new String[]{"t1", "t2"}, user.tenantIds.toArray()); user = ThirdParty.getUsersByEmail(t1WithStorage, signInUpResponse.user.loginMethods[0].email)[0]; @@ -396,8 +396,8 @@ public void testThirdPartyUsersHaveTenantIds() throws Exception { user = ThirdParty.getUser(t2WithStorage, "google", "googleid"); Utils.assertArrayEqualsIgnoreOrder(new String[]{"t1", "t2"}, user.tenantIds.toArray()); - Multitenancy.removeUserIdFromTenant(process.getProcess(), t1WithStorage, signInUpResponse.user.id, null); - user = ThirdParty.getUser(t1WithStorage.toAppIdentifierWithStorage(), signInUpResponse.user.id); + Multitenancy.removeUserIdFromTenant(process.getProcess(), t1WithStorage, signInUpResponse.user.getSupertokensUserId(), null); + user = ThirdParty.getUser(t1WithStorage.toAppIdentifierWithStorage(), signInUpResponse.user.getSupertokensUserId()); assertArrayEquals(new String[]{"t2"}, user.tenantIds.toArray()); } diff --git a/src/test/java/io/supertokens/test/passwordless/PasswordlessConsumeCodeTest.java b/src/test/java/io/supertokens/test/passwordless/PasswordlessConsumeCodeTest.java index b4f349b28..3e9471552 100644 --- a/src/test/java/io/supertokens/test/passwordless/PasswordlessConsumeCodeTest.java +++ b/src/test/java/io/supertokens/test/passwordless/PasswordlessConsumeCodeTest.java @@ -267,9 +267,9 @@ public void testConsumeCodeCleanupUserInputCodeWithEmailAndPhoneNumber() throws assertNotNull(consumeCodeResponse); AuthRecipeUserInfo authUser = storage.getPrimaryUserById(new AppIdentifier(null, null), - consumeCodeResponse.user.id); - Passwordless.updateUser(process.getProcess(), authUser.id, null, new Passwordless.FieldUpdate(PHONE_NUMBER)); - authUser = storage.getPrimaryUserById(new AppIdentifier(null, null), consumeCodeResponse.user.id); + consumeCodeResponse.user.getSupertokensUserId()); + Passwordless.updateUser(process.getProcess(), authUser.getSupertokensUserId(), null, new Passwordless.FieldUpdate(PHONE_NUMBER)); + authUser = storage.getPrimaryUserById(new AppIdentifier(null, null), consumeCodeResponse.user.getSupertokensUserId()); assertEquals(authUser.loginMethods[0].phoneNumber, PHONE_NUMBER); // create code with email twice @@ -342,9 +342,9 @@ public void testConsumeCodeCleanupLinkCodeWithEmailAndPhoneNumber() throws Excep assertNotNull(consumeCodeResponse); AuthRecipeUserInfo authUser = storage.getPrimaryUserById(new AppIdentifier(null, null), - consumeCodeResponse.user.id); - Passwordless.updateUser(process.getProcess(), authUser.id, null, new Passwordless.FieldUpdate(PHONE_NUMBER)); - authUser = storage.getPrimaryUserById(new AppIdentifier(null, null), consumeCodeResponse.user.id); + consumeCodeResponse.user.getSupertokensUserId()); + Passwordless.updateUser(process.getProcess(), authUser.getSupertokensUserId(), null, new Passwordless.FieldUpdate(PHONE_NUMBER)); + authUser = storage.getPrimaryUserById(new AppIdentifier(null, null), consumeCodeResponse.user.getSupertokensUserId()); assertEquals(authUser.loginMethods[0].phoneNumber, PHONE_NUMBER); // create code with email twice @@ -823,7 +823,7 @@ private AuthRecipeUserInfo checkUserWithConsumeResponse(PasswordlessStorage stor Passwordless.ConsumeCodeResponse resp, String email, String phoneNumber, long joinedAfter) throws StorageQueryException { - AuthRecipeUserInfo user = storage.getPrimaryUserById(new AppIdentifier(null, null), resp.user.id); + AuthRecipeUserInfo user = storage.getPrimaryUserById(new AppIdentifier(null, null), resp.user.getSupertokensUserId()); assertNotNull(user); assertEquals(email, resp.user.loginMethods[0].email); diff --git a/src/test/java/io/supertokens/test/passwordless/PasswordlessGetUserTest.java b/src/test/java/io/supertokens/test/passwordless/PasswordlessGetUserTest.java index 34c53ba04..a91ccf1d2 100644 --- a/src/test/java/io/supertokens/test/passwordless/PasswordlessGetUserTest.java +++ b/src/test/java/io/supertokens/test/passwordless/PasswordlessGetUserTest.java @@ -71,7 +71,7 @@ public void getUserByIdWithEmail() throws Exception { Passwordless.ConsumeCodeResponse consumeCodeResponse = createUserWith(process, EMAIL, null); - UserInfo user = Passwordless.getUserById(process.getProcess(), consumeCodeResponse.user.id); + UserInfo user = Passwordless.getUserById(process.getProcess(), consumeCodeResponse.user.getSupertokensUserId()); assertNotNull(user); assertEquals(user.email, EMAIL); @@ -98,7 +98,7 @@ public void getUserByIdWithPhoneNumber() throws Exception { Passwordless.ConsumeCodeResponse consumeCodeResponse = createUserWith(process, null, PHONE_NUMBER); - UserInfo user = Passwordless.getUserById(process.getProcess(), consumeCodeResponse.user.id); + UserInfo user = Passwordless.getUserById(process.getProcess(), consumeCodeResponse.user.getSupertokensUserId()); assertNotNull(user); assertEquals(user.phoneNumber, PHONE_NUMBER); @@ -125,7 +125,7 @@ public void getUserByIdWithEmailAndPhoneNumber() throws Exception { Passwordless.ConsumeCodeResponse consumeCodeResponse = createUserWith(process, EMAIL, PHONE_NUMBER); - UserInfo user = Passwordless.getUserById(process.getProcess(), consumeCodeResponse.user.id); + UserInfo user = Passwordless.getUserById(process.getProcess(), consumeCodeResponse.user.getSupertokensUserId()); assertNotNull(user); assertEquals(user.email, EMAIL); assertEquals(user.phoneNumber, PHONE_NUMBER); @@ -153,7 +153,7 @@ public void getUserByInvalidId() throws Exception { Passwordless.ConsumeCodeResponse consumeCodeResponse = createUserWith(process, EMAIL, null); - UserInfo user = Passwordless.getUserById(process.getProcess(), consumeCodeResponse.user.id + "1"); + UserInfo user = Passwordless.getUserById(process.getProcess(), consumeCodeResponse.user.getSupertokensUserId() + "1"); assertNull(user); process.kill(); diff --git a/src/test/java/io/supertokens/test/passwordless/PasswordlessUpdateUserTest.java b/src/test/java/io/supertokens/test/passwordless/PasswordlessUpdateUserTest.java index 4d46b753a..841b82f2e 100644 --- a/src/test/java/io/supertokens/test/passwordless/PasswordlessUpdateUserTest.java +++ b/src/test/java/io/supertokens/test/passwordless/PasswordlessUpdateUserTest.java @@ -87,7 +87,7 @@ public void updateEmailToAnExistingOne() throws Exception { Exception ex = null; try { - Passwordless.updateUser(process.getProcess(), user[0].id, new Passwordless.FieldUpdate(alternate_email), + Passwordless.updateUser(process.getProcess(), user[0].getSupertokensUserId(), new Passwordless.FieldUpdate(alternate_email), null); } catch (Exception e) { ex = e; @@ -135,7 +135,7 @@ public void updatePhoneNumberToAnExistingOne() throws Exception { Exception ex = null; try { - Passwordless.updateUser(process.getProcess(), user[0].id, null, + Passwordless.updateUser(process.getProcess(), user[0].getSupertokensUserId(), null, new Passwordless.FieldUpdate(alternate_phoneNumber)); } catch (Exception e) { ex = e; @@ -177,10 +177,10 @@ public void updateEmail() throws Exception { AuthRecipeUserInfo[] user = storage.listPrimaryUsersByEmail(new TenantIdentifier(null, null, null), EMAIL); assert (user.length == 1); - Passwordless.updateUser(process.getProcess(), user[0].id, new Passwordless.FieldUpdate(alternate_email), null); + Passwordless.updateUser(process.getProcess(), user[0].getSupertokensUserId(), new Passwordless.FieldUpdate(alternate_email), null); assertEquals(alternate_email, - storage.getPrimaryUserById(new AppIdentifier(null, null), user[0].id).loginMethods[0].email); + storage.getPrimaryUserById(new AppIdentifier(null, null), user[0].getSupertokensUserId()).loginMethods[0].email); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -212,11 +212,11 @@ public void updatePhoneNumber() throws Exception { PHONE_NUMBER); assert (user.length == 1); - Passwordless.updateUser(process.getProcess(), user[0].id, null, + Passwordless.updateUser(process.getProcess(), user[0].getSupertokensUserId(), null, new Passwordless.FieldUpdate(alternate_phoneNumber)); assertEquals(alternate_phoneNumber, - storage.getPrimaryUserById(new AppIdentifier(null, null), user[0].id).loginMethods[0].phoneNumber); + storage.getPrimaryUserById(new AppIdentifier(null, null), user[0].getSupertokensUserId()).loginMethods[0].phoneNumber); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -246,12 +246,12 @@ public void clearEmailSetPhoneNumber() throws Exception { AuthRecipeUserInfo[] user = storage.listPrimaryUsersByEmail(new TenantIdentifier(null, null, null), EMAIL); assert (user.length == 1); - Passwordless.updateUser(process.getProcess(), user[0].id, new Passwordless.FieldUpdate(null), + Passwordless.updateUser(process.getProcess(), user[0].getSupertokensUserId(), new Passwordless.FieldUpdate(null), new Passwordless.FieldUpdate(PHONE_NUMBER)); assertEquals(PHONE_NUMBER, - storage.getPrimaryUserById(new AppIdentifier(null, null), user[0].id).loginMethods[0].phoneNumber); - assertNull(storage.getPrimaryUserById(new AppIdentifier(null, null), user[0].id).loginMethods[0].email); + storage.getPrimaryUserById(new AppIdentifier(null, null), user[0].getSupertokensUserId()).loginMethods[0].phoneNumber); + assertNull(storage.getPrimaryUserById(new AppIdentifier(null, null), user[0].getSupertokensUserId()).loginMethods[0].email); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -282,12 +282,12 @@ public void clearPhoneNumberSetEmail() throws Exception { PHONE_NUMBER); assert (user.length == 1); - Passwordless.updateUser(process.getProcess(), user[0].id, new Passwordless.FieldUpdate(EMAIL), + Passwordless.updateUser(process.getProcess(), user[0].getSupertokensUserId(), new Passwordless.FieldUpdate(EMAIL), new Passwordless.FieldUpdate(null)); assertEquals(EMAIL, - storage.getPrimaryUserById(new AppIdentifier(null, null), user[0].id).loginMethods[0].email); - assertNull(storage.getPrimaryUserById(new AppIdentifier(null, null), user[0].id).loginMethods[0].phoneNumber); + storage.getPrimaryUserById(new AppIdentifier(null, null), user[0].getSupertokensUserId()).loginMethods[0].email); + assertNull(storage.getPrimaryUserById(new AppIdentifier(null, null), user[0].getSupertokensUserId()).loginMethods[0].phoneNumber); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -320,7 +320,7 @@ public void clearPhoneNumberAndEmail() throws Exception { Exception ex = null; try { - Passwordless.updateUser(process.getProcess(), user[0].id, new Passwordless.FieldUpdate(null), + Passwordless.updateUser(process.getProcess(), user[0].getSupertokensUserId(), new Passwordless.FieldUpdate(null), new Passwordless.FieldUpdate(null)); } catch (Exception e) { ex = e; @@ -360,7 +360,7 @@ public void clearEmailOfEmailOnlyUser() throws Exception { Exception ex = null; try { - Passwordless.updateUser(process.getProcess(), user[0].id, new Passwordless.FieldUpdate(null), null); + Passwordless.updateUser(process.getProcess(), user[0].getSupertokensUserId(), new Passwordless.FieldUpdate(null), null); } catch (Exception e) { ex = e; } @@ -400,7 +400,7 @@ public void clearPhoneOfPhoneOnlyUser() throws Exception { Exception ex = null; try { - Passwordless.updateUser(process.getProcess(), user[0].id, null, new Passwordless.FieldUpdate(null)); + Passwordless.updateUser(process.getProcess(), user[0].getSupertokensUserId(), null, new Passwordless.FieldUpdate(null)); } catch (Exception e) { ex = e; } @@ -438,13 +438,13 @@ public void setPhoneNumberSetEmail() throws Exception { PHONE_NUMBER); assert (user.length == 1); - Passwordless.updateUser(process.getProcess(), user[0].id, new Passwordless.FieldUpdate(EMAIL), + Passwordless.updateUser(process.getProcess(), user[0].getSupertokensUserId(), new Passwordless.FieldUpdate(EMAIL), new Passwordless.FieldUpdate(alternate_phoneNumber)); assertEquals(EMAIL, - storage.getPrimaryUserById(new AppIdentifier(null, null), user[0].id).loginMethods[0].email); + storage.getPrimaryUserById(new AppIdentifier(null, null), user[0].getSupertokensUserId()).loginMethods[0].email); assertEquals(alternate_phoneNumber, - storage.getPrimaryUserById(new AppIdentifier(null, null), user[0].id).loginMethods[0].phoneNumber); + storage.getPrimaryUserById(new AppIdentifier(null, null), user[0].getSupertokensUserId()).loginMethods[0].phoneNumber); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); diff --git a/src/test/java/io/supertokens/test/thirdparty/ThirdPartyTest.java b/src/test/java/io/supertokens/test/thirdparty/ThirdPartyTest.java index b2946c0c3..dd0863086 100644 --- a/src/test/java/io/supertokens/test/thirdparty/ThirdPartyTest.java +++ b/src/test/java/io/supertokens/test/thirdparty/ThirdPartyTest.java @@ -104,7 +104,7 @@ public void testSignUpWithEmailNotVerifiedAndCheckEmailIsNotVerified() throws Ex thirdPartyUserId, email); checkSignInUpResponse(signUpResponse, thirdPartyUserId, thirdPartyId, email, true); - assertFalse(EmailVerification.isEmailVerified(process.getProcess(), signUpResponse.user.id, + assertFalse(EmailVerification.isEmailVerified(process.getProcess(), signUpResponse.user.getSupertokensUserId(), signUpResponse.user.loginMethods[0].email)); process.kill(); @@ -156,7 +156,7 @@ public void testSignUpWithFalseVerifiedEmailAndSignInWithVerifiedEmail() throws thirdPartyUserId, email); checkSignInUpResponse(signUpResponse, thirdPartyUserId, thirdPartyId, email, true); - assertFalse(EmailVerification.isEmailVerified(process.getProcess(), signUpResponse.user.id, + assertFalse(EmailVerification.isEmailVerified(process.getProcess(), signUpResponse.user.getSupertokensUserId(), signUpResponse.user.loginMethods[0].email)); ThirdParty.SignInUpResponse signInResponse = ThirdParty.signInUp(process.getProcess(), thirdPartyId, @@ -194,7 +194,7 @@ public void testUpdatingEmailAndCheckVerification() throws Exception { thirdPartyUserId, email_2); checkSignInUpResponse(signInResponse, thirdPartyUserId, thirdPartyId, email_2, false); - assertFalse(EmailVerification.isEmailVerified(process.getProcess(), signInResponse.user.id, + assertFalse(EmailVerification.isEmailVerified(process.getProcess(), signInResponse.user.getSupertokensUserId(), signInResponse.user.loginMethods[0].email)); AuthRecipeUserInfo updatedUserInfo = ThirdParty.getUser(process.getProcess(), thirdPartyId, thirdPartyUserId); @@ -325,7 +325,7 @@ public void testSignUpWithSameUserIdAndCheckDuplicateUserIdException() throws Ex checkSignInUpResponse(signUpResponse, thirdPartyUserId, thirdPartyId, email, true); try { ((ThirdPartySQLStorage) StorageLayer.getStorage(process.getProcess())) - .signUp(new TenantIdentifier(null, null, null), signUpResponse.user.id, email, + .signUp(new TenantIdentifier(null, null, null), signUpResponse.user.getSupertokensUserId(), email, new LoginMethod.ThirdParty("newThirdParty", "newThirdPartyUserId"), System.currentTimeMillis()); throw new Exception("Should not come here"); @@ -389,8 +389,8 @@ public void testGetUserFunctions() throws Exception { checkSignInUpResponse(signUpResponse, thirdPartyUserId, thirdPartyId, email, true); - UserInfo getUserInfoFromId = ThirdParty.getUser(process.getProcess(), signUpResponse.user.id); - assertEquals(getUserInfoFromId.id, signUpResponse.user.id); + UserInfo getUserInfoFromId = ThirdParty.getUser(process.getProcess(), signUpResponse.user.getSupertokensUserId()); + assertEquals(getUserInfoFromId.getSupertokensUserId(), signUpResponse.user.getSupertokensUserId()); assertEquals(getUserInfoFromId.timeJoined, signUpResponse.user.timeJoined); assertEquals(getUserInfoFromId.email, signUpResponse.user.loginMethods[0].email); assertEquals(getUserInfoFromId.thirdParty.userId, signUpResponse.user.loginMethods[0].thirdParty.userId); @@ -399,7 +399,7 @@ public void testGetUserFunctions() throws Exception { AuthRecipeUserInfo getUserInfoFromThirdParty = ThirdParty.getUser(process.getProcess(), signUpResponse.user.loginMethods[0].thirdParty.id, signUpResponse.user.loginMethods[0].thirdParty.userId); - assertEquals(getUserInfoFromThirdParty.id, signUpResponse.user.id); + assertEquals(getUserInfoFromThirdParty.getSupertokensUserId(), signUpResponse.user.getSupertokensUserId()); assertEquals(getUserInfoFromThirdParty.timeJoined, signUpResponse.user.timeJoined); assertEquals(getUserInfoFromThirdParty.loginMethods[0].email, signUpResponse.user.loginMethods[0].email); assertEquals(getUserInfoFromThirdParty.loginMethods[0].thirdParty.userId, @@ -414,7 +414,7 @@ public void testGetUserFunctions() throws Exception { public static void checkSignInUpResponse(ThirdParty.SignInUpResponse response, String thirdPartyUserId, String thirdPartyId, String email, boolean createNewUser) { assertEquals(response.createdNewUser, createNewUser); - assertNotNull(response.user.id); + assertNotNull(response.user.getSupertokensUserId()); assertEquals(response.user.loginMethods[0].thirdParty.userId, thirdPartyUserId); assertEquals(response.user.loginMethods[0].thirdParty.id, thirdPartyId); assertEquals(response.user.loginMethods[0].email, email); diff --git a/src/test/java/io/supertokens/test/thirdparty/ThirdPartyTest2_7.java b/src/test/java/io/supertokens/test/thirdparty/ThirdPartyTest2_7.java index e9c619eeb..16f63fbd0 100644 --- a/src/test/java/io/supertokens/test/thirdparty/ThirdPartyTest2_7.java +++ b/src/test/java/io/supertokens/test/thirdparty/ThirdPartyTest2_7.java @@ -104,7 +104,7 @@ public void testSignUpWithEmailNotVerifiedAndCheckEmailIsNotVerified() throws Ex thirdPartyUserId, email, false); checkSignInUpResponse(signUpResponse, thirdPartyUserId, thirdPartyId, email, true); - assertFalse(EmailVerification.isEmailVerified(process.getProcess(), signUpResponse.user.id, + assertFalse(EmailVerification.isEmailVerified(process.getProcess(), signUpResponse.user.getSupertokensUserId(), signUpResponse.user.loginMethods[0].email)); process.kill(); @@ -130,7 +130,7 @@ public void testSignUpWithVerifiedEmailTrueAndCheckSignUp() throws Exception { ThirdParty.SignInUpResponse signUpResponse = ThirdParty.signInUp2_7(process.getProcess(), thirdPartyId, thirdPartyUserId, email, true); checkSignInUpResponse(signUpResponse, thirdPartyUserId, thirdPartyId, email, true); - assertTrue(EmailVerification.isEmailVerified(process.getProcess(), signUpResponse.user.id, + assertTrue(EmailVerification.isEmailVerified(process.getProcess(), signUpResponse.user.getSupertokensUserId(), signUpResponse.user.loginMethods[0].email)); process.kill(); @@ -158,14 +158,14 @@ public void testSignUpWithFalseVerifiedEmailAndSignInWithVerifiedEmail() throws thirdPartyUserId, email, false); checkSignInUpResponse(signUpResponse, thirdPartyUserId, thirdPartyId, email, true); - assertFalse(EmailVerification.isEmailVerified(process.getProcess(), signUpResponse.user.id, + assertFalse(EmailVerification.isEmailVerified(process.getProcess(), signUpResponse.user.getSupertokensUserId(), signUpResponse.user.loginMethods[0].email)); ThirdParty.SignInUpResponse signInResponse = ThirdParty.signInUp2_7(process.getProcess(), thirdPartyId, thirdPartyUserId, email, true); checkSignInUpResponse(signInResponse, thirdPartyUserId, thirdPartyId, email, false); - assertTrue(EmailVerification.isEmailVerified(process.getProcess(), signInResponse.user.id, + assertTrue(EmailVerification.isEmailVerified(process.getProcess(), signInResponse.user.getSupertokensUserId(), signInResponse.user.loginMethods[0].email)); process.kill(); @@ -195,14 +195,14 @@ public void testUpdatingEmailAndCheckVerification() throws Exception { thirdPartyUserId, email_1, true); checkSignInUpResponse(signUpResponse, thirdPartyUserId, thirdPartyId, email_1, true); - assertTrue(EmailVerification.isEmailVerified(process.getProcess(), signUpResponse.user.id, + assertTrue(EmailVerification.isEmailVerified(process.getProcess(), signUpResponse.user.getSupertokensUserId(), signUpResponse.user.loginMethods[0].email)); ThirdParty.SignInUpResponse signInResponse = ThirdParty.signInUp2_7(process.getProcess(), thirdPartyId, thirdPartyUserId, email_2, false); checkSignInUpResponse(signInResponse, thirdPartyUserId, thirdPartyId, email_2, false); - assertFalse(EmailVerification.isEmailVerified(process.getProcess(), signInResponse.user.id, + assertFalse(EmailVerification.isEmailVerified(process.getProcess(), signInResponse.user.getSupertokensUserId(), signInResponse.user.loginMethods[0].email)); AuthRecipeUserInfo updatedUserInfo = ThirdParty.getUser(process.getProcess(), thirdPartyId, thirdPartyUserId); @@ -333,7 +333,7 @@ public void testSignUpWithSameUserIdAndCheckDuplicateUserIdException() throws Ex checkSignInUpResponse(signUpResponse, thirdPartyUserId, thirdPartyId, email, true); try { ((ThirdPartySQLStorage) StorageLayer.getStorage(process.getProcess())) - .signUp(new TenantIdentifier(null, null, null), signUpResponse.user.id, email, + .signUp(new TenantIdentifier(null, null, null), signUpResponse.user.getSupertokensUserId(), email, new LoginMethod.ThirdParty("newThirdParty", "newThirdPartyUserId"), System.currentTimeMillis()); throw new Exception("Should not come here"); @@ -365,14 +365,14 @@ public void testSignUpWithVerifiedEmailSignInWithUnVerifiedEmail() throws Except thirdPartyUserId, email, true); checkSignInUpResponse(signUpResponse, thirdPartyUserId, thirdPartyId, email, true); - assertTrue(EmailVerification.isEmailVerified(process.getProcess(), signUpResponse.user.id, + assertTrue(EmailVerification.isEmailVerified(process.getProcess(), signUpResponse.user.getSupertokensUserId(), signUpResponse.user.loginMethods[0].email)); ThirdParty.SignInUpResponse signInResponse = ThirdParty.signInUp2_7(process.getProcess(), thirdPartyId, thirdPartyUserId, email, false); checkSignInUpResponse(signInResponse, thirdPartyUserId, thirdPartyId, email, false); - assertTrue(EmailVerification.isEmailVerified(process.getProcess(), signInResponse.user.id, + assertTrue(EmailVerification.isEmailVerified(process.getProcess(), signInResponse.user.getSupertokensUserId(), signInResponse.user.loginMethods[0].email)); process.kill(); @@ -401,8 +401,8 @@ public void testGetUserFunctions() throws Exception { checkSignInUpResponse(signUpResponse, thirdPartyUserId, thirdPartyId, email, true); - UserInfo getUserInfoFromId = ThirdParty.getUser(process.getProcess(), signUpResponse.user.id); - assertEquals(getUserInfoFromId.id, signUpResponse.user.id); + UserInfo getUserInfoFromId = ThirdParty.getUser(process.getProcess(), signUpResponse.user.getSupertokensUserId()); + assertEquals(getUserInfoFromId.getSupertokensUserId(), signUpResponse.user.getSupertokensUserId()); assertEquals(getUserInfoFromId.timeJoined, signUpResponse.user.timeJoined); assertEquals(getUserInfoFromId.email, signUpResponse.user.loginMethods[0].email); assertEquals(getUserInfoFromId.thirdParty.userId, signUpResponse.user.loginMethods[0].thirdParty.userId); @@ -411,7 +411,7 @@ public void testGetUserFunctions() throws Exception { AuthRecipeUserInfo getUserInfoFromThirdParty = ThirdParty.getUser(process.getProcess(), signUpResponse.user.loginMethods[0].thirdParty.id, signUpResponse.user.loginMethods[0].thirdParty.userId); - assertEquals(getUserInfoFromThirdParty.id, signUpResponse.user.id); + assertEquals(getUserInfoFromThirdParty.getSupertokensUserId(), signUpResponse.user.getSupertokensUserId()); assertEquals(getUserInfoFromThirdParty.timeJoined, signUpResponse.user.timeJoined); assertEquals(getUserInfoFromThirdParty.loginMethods[0].email, signUpResponse.user.loginMethods[0].email); assertEquals(getUserInfoFromThirdParty.loginMethods[0].thirdParty.userId, @@ -426,7 +426,7 @@ public void testGetUserFunctions() throws Exception { public static void checkSignInUpResponse(ThirdParty.SignInUpResponse response, String thirdPartyUserId, String thirdPartyId, String email, boolean createNewUser) { assertEquals(response.createdNewUser, createNewUser); - assertNotNull(response.user.id); + assertNotNull(response.user.getSupertokensUserId()); assertEquals(response.user.loginMethods[0].thirdParty.userId, thirdPartyUserId); assertEquals(response.user.loginMethods[0].thirdParty.id, thirdPartyId); assertEquals(response.user.loginMethods[0].email, email); diff --git a/src/test/java/io/supertokens/test/thirdparty/api/ThirdPartyGetUserAPITest2_7.java b/src/test/java/io/supertokens/test/thirdparty/api/ThirdPartyGetUserAPITest2_7.java index 241bb326c..7ab18068b 100644 --- a/src/test/java/io/supertokens/test/thirdparty/api/ThirdPartyGetUserAPITest2_7.java +++ b/src/test/java/io/supertokens/test/thirdparty/api/ThirdPartyGetUserAPITest2_7.java @@ -139,7 +139,7 @@ public void testGoodInput() throws Exception { // query with userId { HashMap QueryParams = new HashMap<>(); - QueryParams.put("userId", signUpResponse.user.id); + QueryParams.put("userId", signUpResponse.user.getSupertokensUserId()); JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", "http://localhost:3567/recipe/user", QueryParams, 1000, 1000, null, diff --git a/src/test/java/io/supertokens/test/thirdparty/api/ThirdPartySignInUpAPITest4_0.java b/src/test/java/io/supertokens/test/thirdparty/api/ThirdPartySignInUpAPITest4_0.java index 1fe296cd2..f83a5db3b 100644 --- a/src/test/java/io/supertokens/test/thirdparty/api/ThirdPartySignInUpAPITest4_0.java +++ b/src/test/java/io/supertokens/test/thirdparty/api/ThirdPartySignInUpAPITest4_0.java @@ -134,11 +134,11 @@ public void testNotAllowedUpdateOfEmail() throws Exception { } UserInfo user0 = EmailPassword.signUp(process.getProcess(), "someemail1@gmail.com", "somePass"); - AuthRecipe.createPrimaryUser(process.main, user0.id); + AuthRecipe.createPrimaryUser(process.main, user0.getSupertokensUserId()); ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(process.getProcess(), "google", "user", "someemail@gmail.com"); - AuthRecipe.createPrimaryUser(process.main, signInUpResponse.user.id); + AuthRecipe.createPrimaryUser(process.main, signInUpResponse.user.getSupertokensUserId()); JsonObject emailObject = new JsonObject(); emailObject.addProperty("id", "someemail1@gmail.com"); diff --git a/src/test/java/io/supertokens/test/totp/api/TotpUserIdMappingTest.java b/src/test/java/io/supertokens/test/totp/api/TotpUserIdMappingTest.java index bc15b9ef4..a263439bd 100644 --- a/src/test/java/io/supertokens/test/totp/api/TotpUserIdMappingTest.java +++ b/src/test/java/io/supertokens/test/totp/api/TotpUserIdMappingTest.java @@ -4,7 +4,6 @@ import io.supertokens.ProcessState; import io.supertokens.emailpassword.EmailPassword; import io.supertokens.featureflag.EE_FEATURES; -import io.supertokens.featureflag.FeatureFlag; import io.supertokens.featureflag.FeatureFlagTestContent; import io.supertokens.pluginInterface.emailpassword.UserInfo; import io.supertokens.pluginInterface.totp.TOTPDevice; @@ -14,7 +13,6 @@ import io.supertokens.test.Utils; import io.supertokens.test.httpRequest.HttpRequestForTesting; import io.supertokens.test.totp.TOTPRecipeTest; -import io.supertokens.test.totp.TotpLicenseTest; import io.supertokens.useridmapping.UserIdMapping; import static org.junit.Assert.assertNotNull; @@ -55,7 +53,7 @@ public void testExternalUserIdTranslation() throws Exception { JsonObject body = new JsonObject(); UserInfo user = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); - String superTokensUserId = user.id; + String superTokensUserId = user.getSupertokensUserId(); String externalUserId = "external-user-id"; // Create user id mapping first: diff --git a/src/test/java/io/supertokens/test/userIdMapping/UserIdMappingStorageTest.java b/src/test/java/io/supertokens/test/userIdMapping/UserIdMappingStorageTest.java index dee0ff75f..0e63a27b9 100644 --- a/src/test/java/io/supertokens/test/userIdMapping/UserIdMappingStorageTest.java +++ b/src/test/java/io/supertokens/test/userIdMapping/UserIdMappingStorageTest.java @@ -102,13 +102,13 @@ public void testCreatingUserIdMapping() throws Exception { String externalUserIdInfo = "external-info"; // create a userId mapping - storage.createUserIdMapping(new AppIdentifier(null, null), userInfo.id, externalUserId, + storage.createUserIdMapping(new AppIdentifier(null, null), userInfo.getSupertokensUserId(), externalUserId, externalUserIdInfo); // check that the mapping exists - UserIdMapping userIdMapping = storage.getUserIdMapping(new AppIdentifier(null, null), userInfo.id, + UserIdMapping userIdMapping = storage.getUserIdMapping(new AppIdentifier(null, null), userInfo.getSupertokensUserId(), true); - assertEquals(userInfo.id, userIdMapping.superTokensUserId); + assertEquals(userInfo.getSupertokensUserId(), userIdMapping.superTokensUserId); assertEquals(externalUserId, userIdMapping.externalUserId); assertEquals(externalUserIdInfo, userIdMapping.externalUserIdInfo); @@ -132,13 +132,13 @@ public void testDuplicateUserIdMapping() throws Exception { UserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPassword"); String externalUserId = "external-test"; - storage.createUserIdMapping(new AppIdentifier(null, null), userInfo.id, externalUserId, null); + storage.createUserIdMapping(new AppIdentifier(null, null), userInfo.getSupertokensUserId(), externalUserId, null); { // duplicate exception with both supertokensUserId and externalUserId Exception error = null; try { - storage.createUserIdMapping(new AppIdentifier(null, null), userInfo.id, externalUserId, null); + storage.createUserIdMapping(new AppIdentifier(null, null), userInfo.getSupertokensUserId(), externalUserId, null); } catch (Exception e) { error = e; } @@ -155,7 +155,7 @@ public void testDuplicateUserIdMapping() throws Exception { // duplicate exception with superTokensUserId Exception error = null; try { - storage.createUserIdMapping(new AppIdentifier(null, null), userInfo.id, "newExternalId", null); + storage.createUserIdMapping(new AppIdentifier(null, null), userInfo.getSupertokensUserId(), "newExternalId", null); } catch (Exception e) { error = e; } @@ -175,7 +175,7 @@ public void testDuplicateUserIdMapping() throws Exception { UserInfo newUser = EmailPassword.signUp(process.main, "test2@example.com", "testPass123"); Exception error = null; try { - storage.createUserIdMapping(new AppIdentifier(null, null), newUser.id, externalUserId, null); + storage.createUserIdMapping(new AppIdentifier(null, null), newUser.getSupertokensUserId(), externalUserId, null); } catch (Exception e) { error = e; } @@ -210,7 +210,7 @@ public void testCreatingAMappingWithAnUnknownStUserIdAndAPreexistingExternalUser String externalUserId = "externalUserId"; // create a userId mapping - storage.createUserIdMapping(new AppIdentifier(null, null), userInfo.id, externalUserId, null); + storage.createUserIdMapping(new AppIdentifier(null, null), userInfo.getSupertokensUserId(), externalUserId, null); // create a new mapping with unknown superTokensUserId and existing externalUserId Exception error = null; @@ -285,16 +285,16 @@ public void testRetrievingUserIdMapping() throws Exception { String externalUserIdInfo = "externalUserIdInfo"; // create the mapping - storage.createUserIdMapping(new AppIdentifier(null, null), userInfo.id, externalUserId, + storage.createUserIdMapping(new AppIdentifier(null, null), userInfo.getSupertokensUserId(), externalUserId, externalUserIdInfo); // check that the mapping exists with supertokensUserId { - UserIdMapping userIdMapping = storage.getUserIdMapping(new AppIdentifier(null, null), userInfo.id, + UserIdMapping userIdMapping = storage.getUserIdMapping(new AppIdentifier(null, null), userInfo.getSupertokensUserId(), true); assertNotNull(userIdMapping); - assertEquals(userInfo.id, userIdMapping.superTokensUserId); + assertEquals(userInfo.getSupertokensUserId(), userIdMapping.superTokensUserId); assertEquals(externalUserId, userIdMapping.externalUserId); assertEquals(externalUserIdInfo, userIdMapping.externalUserIdInfo); } @@ -305,7 +305,7 @@ public void testRetrievingUserIdMapping() throws Exception { externalUserId, false); assertNotNull(userIdMapping); - assertEquals(userInfo.id, userIdMapping.superTokensUserId); + assertEquals(userInfo.getSupertokensUserId(), userIdMapping.superTokensUserId); assertEquals(externalUserId, userIdMapping.externalUserId); assertEquals(externalUserIdInfo, userIdMapping.externalUserIdInfo); } @@ -313,9 +313,9 @@ public void testRetrievingUserIdMapping() throws Exception { // check that the mapping exists with either { UserIdMapping[] userIdMappings = storage.getUserIdMapping(new AppIdentifier(null, null), - userInfo.id); + userInfo.getSupertokensUserId()); assertEquals(1, userIdMappings.length); - assertEquals(userInfo.id, userIdMappings[0].superTokensUserId); + assertEquals(userInfo.getSupertokensUserId(), userIdMappings[0].superTokensUserId); assertEquals(externalUserId, userIdMappings[0].externalUserId); assertEquals(externalUserIdInfo, userIdMappings[0].externalUserIdInfo); } @@ -323,7 +323,7 @@ public void testRetrievingUserIdMapping() throws Exception { UserIdMapping[] userIdMappings = storage.getUserIdMapping(new AppIdentifier(null, null), externalUserId); assertEquals(1, userIdMappings.length); - assertEquals(userInfo.id, userIdMappings[0].superTokensUserId); + assertEquals(userInfo.getSupertokensUserId(), userIdMappings[0].superTokensUserId); assertEquals(externalUserId, userIdMappings[0].externalUserId); assertEquals(externalUserIdInfo, userIdMappings[0].externalUserIdInfo); @@ -334,9 +334,9 @@ public void testRetrievingUserIdMapping() throws Exception { { UserInfo newUserInfo = EmailPassword.signUp(process.main, "test2@example.com", "testPass123"); - String externalUserId2 = userInfo.id; + String externalUserId2 = userInfo.getSupertokensUserId(); - storage.createUserIdMapping(new AppIdentifier(null, null), newUserInfo.id, externalUserId2, null); + storage.createUserIdMapping(new AppIdentifier(null, null), newUserInfo.getSupertokensUserId(), externalUserId2, null); UserIdMapping[] userIdMappings = storage.getUserIdMapping(new AppIdentifier(null, null), externalUserId2); @@ -346,13 +346,13 @@ public void testRetrievingUserIdMapping() throws Exception { boolean checkThatUser2MappingIsReturned = false; for (UserIdMapping userIdMapping : userIdMappings) { - if (userIdMapping.superTokensUserId.equals(userInfo.id)) { - assertEquals(userInfo.id, userIdMapping.superTokensUserId); + if (userIdMapping.superTokensUserId.equals(userInfo.getSupertokensUserId())) { + assertEquals(userInfo.getSupertokensUserId(), userIdMapping.superTokensUserId); assertEquals(externalUserId, userIdMapping.externalUserId); assertEquals(externalUserIdInfo, userIdMapping.externalUserIdInfo); checkThatUser1MappingIsReturned = true; } else { - assertEquals(newUserInfo.id, userIdMapping.superTokensUserId); + assertEquals(newUserInfo.getSupertokensUserId(), userIdMapping.superTokensUserId); assertEquals(externalUserId2, userIdMapping.externalUserId); assertNull(userIdMapping.externalUserIdInfo); checkThatUser2MappingIsReturned = true; @@ -399,7 +399,7 @@ public void testDeletingAUserIdMapping() throws Exception { // create a user UserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); - String superTokensUserId = userInfo.id; + String superTokensUserId = userInfo.getSupertokensUserId(); String externalUserId = "externalUserId"; { // create a new userId mapping @@ -494,7 +494,7 @@ public void testUpdatingExternalUserIdInfo() throws Exception { // create User UserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); - String superTokensUserId = userInfo.id; + String superTokensUserId = userInfo.getSupertokensUserId(); String externalUserId = "externalId"; String externalUserIdInfo = "externalUserIdInfo"; @@ -574,8 +574,8 @@ public void createUsersMapTheirIdsCheckRetrieveUseIdMappingsWithListOfUserIds() // create users equal to the User Pagination limit for (int i = 1; i <= AuthRecipe.USER_PAGINATION_LIMIT; i++) { UserInfo userInfo = EmailPassword.signUp(process.main, "test" + i + "@example.com", "testPass123"); - superTokensUserIdList.add(userInfo.id); - String superTokensUserId = userInfo.id; + superTokensUserIdList.add(userInfo.getSupertokensUserId()); + String superTokensUserId = userInfo.getSupertokensUserId(); String externalUserId = "externalId" + i; externalUserIdList.add(externalUserId); @@ -628,7 +628,7 @@ public void testCallingGetUserIdMappingForSuperTokensIdsWhenNoMappingExists() th for (int i = 1; i <= 10; i++) { UserInfo userInfo = EmailPassword.signUp(process.main, "test" + i + "@example.com", "testPass123"); - superTokensUserIdList.add(userInfo.id); + superTokensUserIdList.add(userInfo.getSupertokensUserId()); } HashMap userIdMapping = storage.getUserIdMappingForSuperTokensIds(superTokensUserIdList); @@ -655,15 +655,15 @@ public void create10UsersAndMap5UsersIds() throws Exception { // create users equal to the User Pagination limit for (int i = 1; i <= 10; i++) { UserInfo userInfo = EmailPassword.signUp(process.main, "test" + i + "@example.com", "testPass123"); - superTokensUserIdList.add(userInfo.id); + superTokensUserIdList.add(userInfo.getSupertokensUserId()); if (i <= 5) { - userIdList.add(userInfo.id); + userIdList.add(userInfo.getSupertokensUserId()); } else { // create userIdMapping for the last 5 users String externalUserId = "externalId" + i; userIdList.add(externalUserId); - storage.createUserIdMapping(new AppIdentifier(null, null), userInfo.id, externalUserId, null); + storage.createUserIdMapping(new AppIdentifier(null, null), userInfo.getSupertokensUserId(), externalUserId, null); } } diff --git a/src/test/java/io/supertokens/test/userIdMapping/UserIdMappingTest.java b/src/test/java/io/supertokens/test/userIdMapping/UserIdMappingTest.java index 1c44f621d..d61aa3954 100644 --- a/src/test/java/io/supertokens/test/userIdMapping/UserIdMappingTest.java +++ b/src/test/java/io/supertokens/test/userIdMapping/UserIdMappingTest.java @@ -107,13 +107,13 @@ public void testDuplicateUserIdMapping() throws Exception { String externalUserId = "external-test"; - UserIdMapping.createUserIdMapping(process.main, userInfo.id, externalUserId, null, false); + UserIdMapping.createUserIdMapping(process.main, userInfo.getSupertokensUserId(), externalUserId, null, false); { // duplicate exception with both supertokensUserId and externalUserId Exception error = null; try { - UserIdMapping.createUserIdMapping(process.main, userInfo.id, externalUserId, null, false); + UserIdMapping.createUserIdMapping(process.main, userInfo.getSupertokensUserId(), externalUserId, null, false); } catch (Exception e) { error = e; } @@ -130,7 +130,7 @@ public void testDuplicateUserIdMapping() throws Exception { // duplicate exception with superTokensUserId Exception error = null; try { - UserIdMapping.createUserIdMapping(process.main, userInfo.id, "newExternalId", null, false); + UserIdMapping.createUserIdMapping(process.main, userInfo.getSupertokensUserId(), "newExternalId", null, false); } catch (Exception e) { error = e; } @@ -150,7 +150,7 @@ public void testDuplicateUserIdMapping() throws Exception { UserInfo newUser = EmailPassword.signUp(process.main, "test2@example.com", "testPass123"); Exception error = null; try { - UserIdMapping.createUserIdMapping(process.main, newUser.id, externalUserId, null, false); + UserIdMapping.createUserIdMapping(process.main, newUser.getSupertokensUserId(), externalUserId, null, false); } catch (Exception e) { error = e; } @@ -187,13 +187,13 @@ public void testCreatingUserIdMapping() throws Exception { String externalUserIdInfo = "external-info"; // create a userId mapping - UserIdMapping.createUserIdMapping(process.getProcess(), userInfo.id, externalUserId, externalUserIdInfo, false); + UserIdMapping.createUserIdMapping(process.getProcess(), userInfo.getSupertokensUserId(), externalUserId, externalUserIdInfo, false); // check that the mapping exists io.supertokens.pluginInterface.useridmapping.UserIdMapping userIdMapping = storage.getUserIdMapping( - new AppIdentifier(null, null), userInfo.id, + new AppIdentifier(null, null), userInfo.getSupertokensUserId(), true); - assertEquals(userInfo.id, userIdMapping.superTokensUserId); + assertEquals(userInfo.getSupertokensUserId(), userIdMapping.superTokensUserId); assertEquals(externalUserId, userIdMapping.externalUserId); assertEquals(externalUserIdInfo, userIdMapping.externalUserIdInfo); @@ -236,7 +236,7 @@ public void testRetrievingUserIdMapping() throws Exception { // create a User and then a UserId mapping UserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); - String superTokensUserId = userInfo.id; + String superTokensUserId = userInfo.getSupertokensUserId(); String externalUserId = "externalId"; String externalUserIdInfo = "externalIdInfo"; @@ -293,8 +293,8 @@ public void testRetrievingUserIdMapping() throws Exception { // create a new mapping where the superTokensUserId of Mapping1 = externalUserId of Mapping2 UserInfo userInfo2 = EmailPassword.signUp(process.main, "test2@example.com", "testPass123"); - String newSuperTokensUserId = userInfo2.id; - String newExternalUserId = userInfo.id; + String newSuperTokensUserId = userInfo2.getSupertokensUserId(); + String newExternalUserId = userInfo.getSupertokensUserId(); String newExternalUserIdInfo = "newExternalUserIdInfo"; UserIdMapping.createUserIdMapping(process.main, newSuperTokensUserId, newExternalUserId, newExternalUserIdInfo, @@ -370,7 +370,7 @@ public void testDeletingUserIdMapping() throws Exception { // create mapping and check that it exists UserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); - String superTokensUserId = userInfo.id; + String superTokensUserId = userInfo.getSupertokensUserId(); String externalUserId = "externalId"; String externalUserIdInfo = "externalIdInfo"; @@ -478,7 +478,7 @@ public void testDeletingUserIdMappingWithSharedId() throws Exception { UserInfo userInfo_1 = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); io.supertokens.pluginInterface.useridmapping.UserIdMapping userIdMapping_1 = new io.supertokens.pluginInterface.useridmapping.UserIdMapping( - userInfo_1.id, "externalUserId", "externalUserIdInfo"); + userInfo_1.getSupertokensUserId(), "externalUserId", "externalUserIdInfo"); // create the mapping and check that it exists { @@ -494,7 +494,7 @@ public void testDeletingUserIdMappingWithSharedId() throws Exception { UserInfo userInfo_2 = EmailPassword.signUp(process.main, "test2@example.com", "testPass123"); io.supertokens.pluginInterface.useridmapping.UserIdMapping userIdMapping_2 = new io.supertokens.pluginInterface.useridmapping.UserIdMapping( - userInfo_2.id, userIdMapping_1.superTokensUserId, "externalUserIdInfo2"); + userInfo_2.getSupertokensUserId(), userIdMapping_1.superTokensUserId, "externalUserIdInfo2"); // create the mapping and check that it exists { @@ -572,7 +572,7 @@ public void testUpdatingExternalUserIdInfo() throws Exception { // create User UserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); - String superTokensUserId = userInfo.id; + String superTokensUserId = userInfo.getSupertokensUserId(); String externalUserId = "externalId"; // create a userId mapping @@ -667,7 +667,7 @@ public void testUpdatingExternalUserIdInfoWithSharedUserIds() throws Exception { // Create mapping 1 UserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); - String superTokensUserId = userInfo.id; + String superTokensUserId = userInfo.getSupertokensUserId(); String externalUserId = "externalId"; String externalUserIdInfo = "externalUserIdInfo"; @@ -685,8 +685,8 @@ public void testUpdatingExternalUserIdInfoWithSharedUserIds() throws Exception { // Create mapping 2 UserInfo userInfo2 = EmailPassword.signUp(process.main, "test2@example.com", "testPass123"); - String superTokensUserId2 = userInfo2.id; - String externalUserId2 = userInfo.id; + String superTokensUserId2 = userInfo2.getSupertokensUserId(); + String externalUserId2 = userInfo.getSupertokensUserId(); String externalUserIdInfo2 = "newExternalUserIdInfo"; UserIdMapping.createUserIdMapping(process.main, superTokensUserId2, externalUserId2, externalUserIdInfo2, true); @@ -751,7 +751,7 @@ public void testUpdatingTheExternalUserIdInfoOfAMappingWithTheSameValue() throws // create a userIdMapping with externalUserIdInfo as null and update it to null { UserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); - String superTokensUserId = userInfo.id; + String superTokensUserId = userInfo.getSupertokensUserId(); String externalUserId = "externalUserId"; // create mapping @@ -764,7 +764,7 @@ public void testUpdatingTheExternalUserIdInfoOfAMappingWithTheSameValue() throws { UserInfo userInfo = EmailPassword.signUp(process.main, "test2@example.com", "testPass123"); - String superTokensUserId = userInfo.id; + String superTokensUserId = userInfo.getSupertokensUserId(); String externalUserId = "newExternalUserIdInfo"; String externalUserIdInfo = "externalUserIdInfo"; @@ -809,7 +809,7 @@ public void checkThatCreateUserIdMappingHasAllNonAuthRecipeChecks() throws Excep for (String className : classNames) { UserInfo user = EmailPassword.signUp(process.main, "test@example.com", "password"); - String userId = user.id; + String userId = user.getSupertokensUserId(); // create entry in nonAuth table StorageLayer.getStorage(process.main).addInfoToNonAuthRecipesBasedOnUserId(TenantIdentifier.BASE_TENANT, className, userId); @@ -890,7 +890,7 @@ public void checkThatDeleteUserIdMappingHasAllNonAuthRecipeChecks() throws Excep UserInfo user = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); // create a mapping with the user - UserIdMapping.createUserIdMapping(process.main, user.id, externalId, null, false); + UserIdMapping.createUserIdMapping(process.main, user.getSupertokensUserId(), externalId, null, false); // create entry in nonAuth table with externalId StorageLayer.getStorage(process.main).addInfoToNonAuthRecipesBasedOnUserId(TenantIdentifier.BASE_TENANT, className, externalId); @@ -898,7 +898,7 @@ public void checkThatDeleteUserIdMappingHasAllNonAuthRecipeChecks() throws Excep // try to delete UserIdMapping String errorMessage = null; try { - UserIdMapping.deleteUserIdMapping(process.main, user.id, UserIdType.SUPERTOKENS, false); + UserIdMapping.deleteUserIdMapping(process.main, user.getSupertokensUserId(), UserIdType.SUPERTOKENS, false); } catch (ServletException e) { errorMessage = e.getRootCause().getMessage(); } @@ -907,7 +907,7 @@ public void checkThatDeleteUserIdMappingHasAllNonAuthRecipeChecks() throws Excep assertTrue(errorMessage.contains("UserId is already in use")); } // delete user data - AuthRecipe.deleteUser(process.main, user.id); + AuthRecipe.deleteUser(process.main, user.getSupertokensUserId()); } process.kill(); @@ -929,7 +929,7 @@ public void checkThatWeDontAllowDBStateA5FromBeingCreatedWhenForceIsFalse() thro // create an EmailPassword User UserInfo user_1 = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); // create a mapping for the EmailPassword User - UserIdMapping.createUserIdMapping(process.main, user_1.id, "externalId", null, false); + UserIdMapping.createUserIdMapping(process.main, user_1.getSupertokensUserId(), "externalId", null, false); // create some metadata for the user JsonObject data = new JsonObject(); @@ -942,7 +942,7 @@ public void checkThatWeDontAllowDBStateA5FromBeingCreatedWhenForceIsFalse() thro // try and map user_2 to user_1s superTokensUserId String errorMessage = null; try { - UserIdMapping.createUserIdMapping(process.main, user_2.id, user_1.id, null, false); + UserIdMapping.createUserIdMapping(process.main, user_2.getSupertokensUserId(), user_1.getSupertokensUserId(), null, false); } catch (ServletException e) { errorMessage = e.getRootCause().getMessage(); } @@ -970,12 +970,12 @@ public void testThatWeDontAllowDBStateA6WithoutForce() throws Exception { UserInfo user_2 = EmailPassword.signUp(process.main, "test123@example.com", "testPass123"); // create a mapping between User_1 and User_2 with force - UserIdMapping.createUserIdMapping(process.main, user_1.id, user_2.id, null, true); + UserIdMapping.createUserIdMapping(process.main, user_1.getSupertokensUserId(), user_2.getSupertokensUserId(), null, true); // try and create a mapping between User_2 and User_1 without force String errorMessage = null; try { - UserIdMapping.createUserIdMapping(process.main, user_2.id, user_1.id, null, false); + UserIdMapping.createUserIdMapping(process.main, user_2.getSupertokensUserId(), user_1.getSupertokensUserId(), null, false); } catch (ServletException e) { errorMessage = e.getRootCause().getMessage(); } @@ -1006,24 +1006,24 @@ public void testDeleteMappingWithUser_1AndUserIdTypeAsAny() throws Exception { UserInfo user_2 = EmailPassword.signUp(process.main, "test123@exmaple.com", "testPass123"); // create a mapping between User_2 and User_1 with force - UserIdMapping.createUserIdMapping(process.main, user_2.id, user_1.id, null, true); + UserIdMapping.createUserIdMapping(process.main, user_2.getSupertokensUserId(), user_1.getSupertokensUserId(), null, true); // check that mapping exists { io.supertokens.pluginInterface.useridmapping.UserIdMapping mapping = UserIdMapping - .getUserIdMapping(process.main, user_2.id, UserIdType.SUPERTOKENS); + .getUserIdMapping(process.main, user_2.getSupertokensUserId(), UserIdType.SUPERTOKENS); assertNotNull(mapping); - assertEquals(mapping.superTokensUserId, user_2.id); - assertEquals(mapping.externalUserId, user_1.id); + assertEquals(mapping.superTokensUserId, user_2.getSupertokensUserId()); + assertEquals(mapping.externalUserId, user_1.getSupertokensUserId()); } // delete mapping with User_1s Id and UserIdType set to ANY, it should delete the mapping - assertTrue(UserIdMapping.deleteUserIdMapping(process.main, user_1.id, UserIdType.ANY, false)); + assertTrue(UserIdMapping.deleteUserIdMapping(process.main, user_1.getSupertokensUserId(), UserIdType.ANY, false)); // check that mapping is deleted { io.supertokens.pluginInterface.useridmapping.UserIdMapping mapping = UserIdMapping - .getUserIdMapping(process.main, user_2.id, UserIdType.SUPERTOKENS); + .getUserIdMapping(process.main, user_2.getSupertokensUserId(), UserIdType.SUPERTOKENS); assertNull(mapping); } @@ -1050,24 +1050,24 @@ public void testDeleteMappingWithUser_1AndUserIdTypeAsSUPERTOKENS() throws Excep UserInfo user_2 = EmailPassword.signUp(process.main, "test123@exmaple.com", "testPass123"); // create a mapping between User_2 and User_1 with force - UserIdMapping.createUserIdMapping(process.main, user_2.id, user_1.id, null, true); + UserIdMapping.createUserIdMapping(process.main, user_2.getSupertokensUserId(), user_1.getSupertokensUserId(), null, true); // check that mapping exists { io.supertokens.pluginInterface.useridmapping.UserIdMapping mapping = UserIdMapping - .getUserIdMapping(process.main, user_2.id, UserIdType.SUPERTOKENS); + .getUserIdMapping(process.main, user_2.getSupertokensUserId(), UserIdType.SUPERTOKENS); assertNotNull(mapping); - assertEquals(mapping.superTokensUserId, user_2.id); - assertEquals(mapping.externalUserId, user_1.id); + assertEquals(mapping.superTokensUserId, user_2.getSupertokensUserId()); + assertEquals(mapping.externalUserId, user_1.getSupertokensUserId()); } // delete mapping with User_1s Id and UserIdType set to ANY, it should delete the mapping - assertTrue(UserIdMapping.deleteUserIdMapping(process.main, user_1.id, UserIdType.SUPERTOKENS, false)); + assertTrue(UserIdMapping.deleteUserIdMapping(process.main, user_1.getSupertokensUserId(), UserIdType.SUPERTOKENS, false)); // check that mapping is deleted { io.supertokens.pluginInterface.useridmapping.UserIdMapping mapping = UserIdMapping - .getUserIdMapping(process.main, user_2.id, UserIdType.SUPERTOKENS); + .getUserIdMapping(process.main, user_2.getSupertokensUserId(), UserIdType.SUPERTOKENS); assertNull(mapping); } diff --git a/src/test/java/io/supertokens/test/userIdMapping/api/CreateUserIdMappingAPITest.java b/src/test/java/io/supertokens/test/userIdMapping/api/CreateUserIdMappingAPITest.java index 2c1a8c61e..b2219b3c9 100644 --- a/src/test/java/io/supertokens/test/userIdMapping/api/CreateUserIdMappingAPITest.java +++ b/src/test/java/io/supertokens/test/userIdMapping/api/CreateUserIdMappingAPITest.java @@ -32,9 +32,6 @@ import io.supertokens.test.httpRequest.HttpResponseException; import io.supertokens.usermetadata.UserMetadata; import io.supertokens.utils.SemVer; -import io.supertokens.userroles.UserRoles; -import io.supertokens.utils.SemVer; -import io.supertokens.webserver.WebserverAPI; import org.junit.AfterClass; import org.junit.Before; import org.junit.Rule; @@ -232,8 +229,8 @@ public void testCreatingAUserIdMappingWithAndWithoutForce() throws Exception { // add some metadata to the user JsonObject userMetadata = new JsonObject(); userMetadata.addProperty("test", "testExample"); - UserMetadata.updateUserMetadata(process.main, userInfo.id, userMetadata); - String superTokensUserId = userInfo.id; + UserMetadata.updateUserMetadata(process.main, userInfo.getSupertokensUserId(), userMetadata); + String superTokensUserId = userInfo.getSupertokensUserId(); String externalUserId = "externalId"; // try and create mapping without force @@ -285,7 +282,7 @@ public void testCreatingAUserIdMapping() throws Exception { // create a User UserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); - String superTokensUserId = userInfo.id; + String superTokensUserId = userInfo.getSupertokensUserId(); String externalUserId = "userId"; String externalUserIdInfo = "externUserIdInfo"; @@ -359,7 +356,7 @@ public void testCreatingUserIdMappingWithExternalUserIdInfoAsNull() throws Excep UserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); String externalUserId = "externalUserId"; JsonObject requestBody = new JsonObject(); - requestBody.addProperty("superTokensUserId", userInfo.id); + requestBody.addProperty("superTokensUserId", userInfo.getSupertokensUserId()); requestBody.addProperty("externalUserId", externalUserId); requestBody.add("externalUserIdInfo", null); @@ -369,11 +366,11 @@ public void testCreatingUserIdMappingWithExternalUserIdInfoAsNull() throws Excep UserIdMappingStorage storage = (UserIdMappingStorage) StorageLayer.getStorage(process.main); - UserIdMapping userIdMapping = storage.getUserIdMapping(new AppIdentifier(null, null), userInfo.id, + UserIdMapping userIdMapping = storage.getUserIdMapping(new AppIdentifier(null, null), userInfo.getSupertokensUserId(), true); assertNotNull(userIdMapping); - assertEquals(userInfo.id, userIdMapping.superTokensUserId); + assertEquals(userInfo.getSupertokensUserId(), userIdMapping.superTokensUserId); assertEquals(externalUserId, userIdMapping.externalUserId); assertNull(userIdMapping.externalUserIdInfo); @@ -395,7 +392,7 @@ public void testCreatingDuplicateUserIdMapping() throws Exception { UserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); - String superTokensUserId = userInfo.id; + String superTokensUserId = userInfo.getSupertokensUserId(); String externalUserId = "externalUserId"; // create UserId mapping @@ -443,7 +440,7 @@ public void testCreatingDuplicateUserIdMapping() throws Exception { JsonObject requestBody = new JsonObject(); - requestBody.addProperty("superTokensUserId", newUserInfo.id); + requestBody.addProperty("superTokensUserId", newUserInfo.getSupertokensUserId()); requestBody.addProperty("externalUserId", externalUserId); JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", diff --git a/src/test/java/io/supertokens/test/userIdMapping/api/GetUserIdMappingAPITest.java b/src/test/java/io/supertokens/test/userIdMapping/api/GetUserIdMappingAPITest.java index a9d437ede..c06075e56 100644 --- a/src/test/java/io/supertokens/test/userIdMapping/api/GetUserIdMappingAPITest.java +++ b/src/test/java/io/supertokens/test/userIdMapping/api/GetUserIdMappingAPITest.java @@ -197,7 +197,7 @@ public void testRetrieveUserIdMapping() throws Exception { // create a user and map their userId to an external userId UserInfo user = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); - String superTokensUserId = user.id; + String superTokensUserId = user.getSupertokensUserId(); String externalUserId = "externalUserId"; String externalUserIdInfo = "externalUserIdInfo"; @@ -291,7 +291,7 @@ public void testRetrievingUserIdMappingWithoutSendingUserIdType() throws Excepti // create a user and map their userId to an external userId UserInfo user = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); - String superTokensUserId = user.id; + String superTokensUserId = user.getSupertokensUserId(); String externalUserId = "externalUserId"; String externalUserIdInfo = "externalUserIdInfo"; @@ -346,7 +346,7 @@ public void testRetrieveUserIdMappingWithExternalUserIdInfoAsNull() throws Excep // create a user and map their userId to an external userId UserInfo user = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); - String superTokensUserId = user.id; + String superTokensUserId = user.getSupertokensUserId(); String externalUserId = "externalUserId"; UserIdMapping.createUserIdMapping(process.main, superTokensUserId, externalUserId, null, false); diff --git a/src/test/java/io/supertokens/test/userIdMapping/api/RemoveUserIdMappingAPITest.java b/src/test/java/io/supertokens/test/userIdMapping/api/RemoveUserIdMappingAPITest.java index 0ec895d3e..5a7eeb43c 100644 --- a/src/test/java/io/supertokens/test/userIdMapping/api/RemoveUserIdMappingAPITest.java +++ b/src/test/java/io/supertokens/test/userIdMapping/api/RemoveUserIdMappingAPITest.java @@ -17,10 +17,8 @@ package io.supertokens.test.userIdMapping.api; import com.google.gson.JsonObject; -import io.supertokens.Main; import io.supertokens.ProcessState; import io.supertokens.emailpassword.EmailPassword; -import io.supertokens.emailverification.User; import io.supertokens.pluginInterface.STORAGE_TYPE; import io.supertokens.pluginInterface.emailpassword.UserInfo; import io.supertokens.pluginInterface.useridmapping.UserIdMapping; @@ -38,8 +36,6 @@ import org.junit.Test; import org.junit.rules.TestRule; -import jakarta.servlet.ServletException; - import static io.supertokens.test.Utils.createUserIdMappingAndCheckThatItExists; import static org.junit.Assert.*; @@ -235,7 +231,7 @@ public void testDeletingUserIdMapping() throws Exception { // create a userId mapping UserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); - UserIdMapping userIdMapping = new UserIdMapping(userInfo.id, "externalUserId", "externalUserIdInfo"); + UserIdMapping userIdMapping = new UserIdMapping(userInfo.getSupertokensUserId(), "externalUserId", "externalUserIdInfo"); createUserIdMappingAndCheckThatItExists(process.main, userIdMapping); // delete userId mapping with userIdType as SUPERTOKENS @@ -337,7 +333,7 @@ public void testDeletingAUserIdMappingWithoutSendingUserIdType() throws Exceptio // create a userId mapping UserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); - UserIdMapping userIdMapping = new UserIdMapping(userInfo.id, "externalUserId", "externalUserIdInfo"); + UserIdMapping userIdMapping = new UserIdMapping(userInfo.getSupertokensUserId(), "externalUserId", "externalUserIdInfo"); createUserIdMappingAndCheckThatItExists(process.main, userIdMapping); { @@ -394,7 +390,7 @@ public void deleteUserIdMappingWithAndWithoutForce() throws Exception { } UserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); - String superTokensUserId = userInfo.id; + String superTokensUserId = userInfo.getSupertokensUserId(); String externalId = "externalId"; io.supertokens.useridmapping.UserIdMapping.createUserIdMapping(process.main, superTokensUserId, externalId, null, false); diff --git a/src/test/java/io/supertokens/test/userIdMapping/api/UpdateExternalUserIdInfoTest.java b/src/test/java/io/supertokens/test/userIdMapping/api/UpdateExternalUserIdInfoTest.java index b9472bbb6..e1e213e65 100644 --- a/src/test/java/io/supertokens/test/userIdMapping/api/UpdateExternalUserIdInfoTest.java +++ b/src/test/java/io/supertokens/test/userIdMapping/api/UpdateExternalUserIdInfoTest.java @@ -270,7 +270,7 @@ public void testUpdatingExternalUserIdInfoWithSuperTokensUserId() throws Excepti // create userId mapping with externalUserIdInfo String externalUserIdInfo = "externalUserIdInfo"; UserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); - UserIdMapping userIdMapping = new io.supertokens.pluginInterface.useridmapping.UserIdMapping(userInfo.id, + UserIdMapping userIdMapping = new io.supertokens.pluginInterface.useridmapping.UserIdMapping(userInfo.getSupertokensUserId(), "externalUserIdInfo", externalUserIdInfo); Utils.createUserIdMappingAndCheckThatItExists(process.main, userIdMapping); @@ -341,7 +341,7 @@ public void testUpdatingExternalUserIdInfoWithExternalUserId() throws Exception // create userId mapping with externalUserIdInfo String externalUserIdInfo = "externalUserIdInfo"; UserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); - UserIdMapping userIdMapping = new io.supertokens.pluginInterface.useridmapping.UserIdMapping(userInfo.id, + UserIdMapping userIdMapping = new io.supertokens.pluginInterface.useridmapping.UserIdMapping(userInfo.getSupertokensUserId(), "externalUserIdInfo", externalUserIdInfo); Utils.createUserIdMappingAndCheckThatItExists(process.main, userIdMapping); @@ -412,7 +412,7 @@ public void testDeletingExternalUserIdInfo() throws Exception { // create userId mapping with externalUserIdInfo String externalUserIdInfo = "externalUserIdInfo"; UserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); - UserIdMapping userIdMapping = new io.supertokens.pluginInterface.useridmapping.UserIdMapping(userInfo.id, + UserIdMapping userIdMapping = new io.supertokens.pluginInterface.useridmapping.UserIdMapping(userInfo.getSupertokensUserId(), "externalUserIdInfo", externalUserIdInfo); Utils.createUserIdMappingAndCheckThatItExists(process.main, userIdMapping); diff --git a/src/test/java/io/supertokens/test/userIdMapping/recipe/EmailPasswordAPITest.java b/src/test/java/io/supertokens/test/userIdMapping/recipe/EmailPasswordAPITest.java index 0237eb447..077d3b26e 100644 --- a/src/test/java/io/supertokens/test/userIdMapping/recipe/EmailPasswordAPITest.java +++ b/src/test/java/io/supertokens/test/userIdMapping/recipe/EmailPasswordAPITest.java @@ -69,7 +69,7 @@ public void testSignInAPI() throws Exception { String email = "test@example.com"; String password = "testPass123"; UserInfo userInfo = EmailPassword.signUp(process.main, email, password); - String superTokensUserId = userInfo.id; + String superTokensUserId = userInfo.getSupertokensUserId(); String externalUserId = "externalId"; // create the mapping @@ -124,7 +124,7 @@ public void testResetPasswordFlowWithUserIdMapping() throws Exception { String email = "test@example.com"; String password = "testPass123"; UserInfo userInfo = EmailPassword.signUp(process.main, email, password); - String superTokensUserId = userInfo.id; + String superTokensUserId = userInfo.getSupertokensUserId(); String externalUserId = "externalId"; // create the mapping @@ -183,7 +183,7 @@ public void testRetrievingUser() throws Exception { String email = "test@example.com"; String password = "testPass123"; UserInfo userInfo = EmailPassword.signUp(process.main, email, password); - String superTokensUserId = userInfo.id; + String superTokensUserId = userInfo.getSupertokensUserId(); String externalUserId = "externalId"; // create the mapping @@ -229,7 +229,7 @@ public void testUpdatingUsersEmail() throws Exception { String email = "test@example.com"; String password = "testPass123"; UserInfo userInfo = EmailPassword.signUp(process.main, email, password); - String superTokensUserId = userInfo.id; + String superTokensUserId = userInfo.getSupertokensUserId(); String externalUserId = "externalId"; // create the mapping diff --git a/src/test/java/io/supertokens/test/userIdMapping/recipe/PasswordlessAPITest.java b/src/test/java/io/supertokens/test/userIdMapping/recipe/PasswordlessAPITest.java index 0bbffcf2a..96f70ec97 100644 --- a/src/test/java/io/supertokens/test/userIdMapping/recipe/PasswordlessAPITest.java +++ b/src/test/java/io/supertokens/test/userIdMapping/recipe/PasswordlessAPITest.java @@ -74,7 +74,7 @@ public void testCreatingAPasswordlessUserMapTheirUserIdAndRetrieveUserId() throw createCodeResponse.deviceId, createCodeResponse.deviceIdHash, createCodeResponse.userInputCode, null); assertTrue(consumeCodeResponse.createdNewUser); - superTokensUserId = consumeCodeResponse.user.id; + superTokensUserId = consumeCodeResponse.user.getSupertokensUserId(); // create mapping UserIdMapping.createUserIdMapping(process.main, superTokensUserId, externalId, null, false); @@ -141,7 +141,7 @@ public void testCreatingAPasswordlessUserAndRetrieveInfo() throws Exception { createCodeResponse.deviceId, createCodeResponse.deviceIdHash, createCodeResponse.userInputCode, null); assertTrue(consumeCodeResponse.createdNewUser); - superTokensUserId = consumeCodeResponse.user.id; + superTokensUserId = consumeCodeResponse.user.getSupertokensUserId(); // create mapping UserIdMapping.createUserIdMapping(process.main, superTokensUserId, externalId, null, false); @@ -219,7 +219,7 @@ public void testCreatingPasswordlessUserWithPhoneNumberAndRetrieveInfo() throws createCodeResponse.deviceId, createCodeResponse.deviceIdHash, createCodeResponse.userInputCode, null); assertTrue(consumeCodeResponse.createdNewUser); - superTokensUserId = consumeCodeResponse.user.id; + superTokensUserId = consumeCodeResponse.user.getSupertokensUserId(); // create mapping UserIdMapping.createUserIdMapping(process.main, superTokensUserId, externalId, null, false); @@ -267,7 +267,7 @@ public void testUpdatingPasswordlessUserWithTheirExternalId() throws Exception { null); Passwordless.ConsumeCodeResponse consumeCodeResponse = Passwordless.consumeCode(process.main, response.deviceId, response.deviceIdHash, response.userInputCode, null); - superTokensUserId = consumeCodeResponse.user.id; + superTokensUserId = consumeCodeResponse.user.getSupertokensUserId(); // map their userId UserIdMapping.createUserIdMapping(process.main, superTokensUserId, externalId, null, false); @@ -287,7 +287,7 @@ public void testUpdatingPasswordlessUserWithTheirExternalId() throws Exception { // check that user got updated AuthRecipeUserInfo userInfo = Passwordless.getUserByEmail(process.main, newEmail); assertNotNull(userInfo); - assertEquals(userInfo.id, superTokensUserId); + assertEquals(userInfo.getSupertokensUserId(), superTokensUserId); } process.kill(); diff --git a/src/test/java/io/supertokens/test/userIdMapping/recipe/ThirdPartyAPITest.java b/src/test/java/io/supertokens/test/userIdMapping/recipe/ThirdPartyAPITest.java index 71f7e6c43..0f3d7d5c6 100644 --- a/src/test/java/io/supertokens/test/userIdMapping/recipe/ThirdPartyAPITest.java +++ b/src/test/java/io/supertokens/test/userIdMapping/recipe/ThirdPartyAPITest.java @@ -20,9 +20,7 @@ import com.google.gson.JsonObject; import io.supertokens.ProcessState; import io.supertokens.authRecipe.AuthRecipe; -import io.supertokens.emailpassword.EmailPassword; import io.supertokens.pluginInterface.STORAGE_TYPE; -import io.supertokens.pluginInterface.thirdparty.UserInfo; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.TestingProcessManager; import io.supertokens.test.Utils; @@ -72,7 +70,7 @@ public void testSignInAPI() throws Exception { String email = "test@example.com"; ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(process.main, thirdPartyId, thirdPartyUserId, email); - String superTokensUserId = signInUpResponse.user.id; + String superTokensUserId = signInUpResponse.user.getSupertokensUserId(); String externalUserId = "externalId"; // create the mapping @@ -132,7 +130,7 @@ public void testGetUsersByEmailAPI() throws Exception { String email = "test@example.com"; ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(process.main, thirdPartyId, thirdPartyUserId, email); - String superTokensUserId = signInUpResponse.user.id; + String superTokensUserId = signInUpResponse.user.getSupertokensUserId(); String externalUserId = "externalId"; // create the mapping @@ -174,7 +172,7 @@ public void testGetUserById() throws Exception { String email = "test@example.com"; ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(process.main, thirdPartyId, thirdPartyUserId, email); - String superTokensUserId = signInUpResponse.user.id; + String superTokensUserId = signInUpResponse.user.getSupertokensUserId(); String externalUserId = "externalId"; // create the mapping @@ -215,7 +213,7 @@ public void testGetUserByThirdPartyId() throws Exception { String email = "test@example.com"; ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(process.main, thirdPartyId, thirdPartyUserId, email); - String superTokensUserId = signInUpResponse.user.id; + String superTokensUserId = signInUpResponse.user.getSupertokensUserId(); String externalUserId = "externalId"; // create the mapping diff --git a/src/test/java/io/supertokens/test/userRoles/UserRolesTest.java b/src/test/java/io/supertokens/test/userRoles/UserRolesTest.java index bcc3587f8..21c07fcd2 100644 --- a/src/test/java/io/supertokens/test/userRoles/UserRolesTest.java +++ b/src/test/java/io/supertokens/test/userRoles/UserRolesTest.java @@ -1008,25 +1008,25 @@ public void createAnAuthUserAssignRolesAndDeleteUser() throws Exception { UserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPassword"); // assign role to user - UserRoles.addRoleToUser(process.main, userInfo.id, role); + UserRoles.addRoleToUser(process.main, userInfo.getSupertokensUserId(), role); { // check that user has role - String[] retrievedRoles = UserRoles.getRolesForUser(process.main, userInfo.id); + String[] retrievedRoles = UserRoles.getRolesForUser(process.main, userInfo.getSupertokensUserId()); assertEquals(1, retrievedRoles.length); assertEquals(role, retrievedRoles[0]); } // delete User - AuthRecipe.deleteUser(process.main, userInfo.id); + AuthRecipe.deleteUser(process.main, userInfo.getSupertokensUserId()); { // check that user has no roles - String[] retrievedRoles = UserRoles.getRolesForUser(process.main, userInfo.id); + String[] retrievedRoles = UserRoles.getRolesForUser(process.main, userInfo.getSupertokensUserId()); assertEquals(0, retrievedRoles.length); // check that the mapping for user role doesnt exist - String[] roleUserMapping = storage.getRolesForUser(new TenantIdentifier(null, null, null), userInfo.id); + String[] roleUserMapping = storage.getRolesForUser(new TenantIdentifier(null, null, null), userInfo.getSupertokensUserId()); assertEquals(0, roleUserMapping.length); } From bd0062bbd1040fd0a77bc1b1b09de3b321291703 Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Fri, 25 Aug 2023 13:05:23 +0530 Subject: [PATCH 094/131] fix: External userid (#770) * fix: recipeUserId in sign in/up related APIs * fix: phone and email case * fix: pr comments * fix: test with external user id * fix: pr comments * fix: pr comments * fix: pr comments * fix: pr comments * fix: external userid * fix: tests --- coreDriverInterfaceSupported.json | 3 +- .../useridmapping/UserIdMapping.java | 62 +++++++++++++++++++ .../webserver/api/core/GetUserByIdAPI.java | 9 +-- .../webserver/api/core/UsersAPI.java | 16 +---- .../ImportUserWithPasswordHashAPI.java | 3 + .../api/emailpassword/SignInAPI.java | 11 +--- .../webserver/api/emailpassword/UserAPI.java | 22 ++----- .../api/passwordless/ConsumeCodeAPI.java | 11 +--- .../webserver/api/passwordless/UserAPI.java | 25 +------- .../api/thirdparty/GetUsersByEmailAPI.java | 14 +---- .../webserver/api/thirdparty/SignInUpAPI.java | 13 +--- .../webserver/api/thirdparty/UserAPI.java | 15 +---- .../accountlinking/api/GetUserByIdTest.java | 3 +- .../api/UserPaginationTest.java | 50 +++++++-------- .../emailpassword/api/SignInAPITest4_0.java | 8 +-- .../emailpassword/api/SignUpAPITest4_0.java | 2 +- 16 files changed, 117 insertions(+), 150 deletions(-) diff --git a/coreDriverInterfaceSupported.json b/coreDriverInterfaceSupported.json index 1b1bc74f9..0c9d09fe0 100644 --- a/coreDriverInterfaceSupported.json +++ b/coreDriverInterfaceSupported.json @@ -16,6 +16,7 @@ "2.19", "2.20", "2.21", - "3.0" + "3.0", + "4.0" ] } \ No newline at end of file diff --git a/src/main/java/io/supertokens/useridmapping/UserIdMapping.java b/src/main/java/io/supertokens/useridmapping/UserIdMapping.java index f613f73fb..c5c5ddbf7 100644 --- a/src/main/java/io/supertokens/useridmapping/UserIdMapping.java +++ b/src/main/java/io/supertokens/useridmapping/UserIdMapping.java @@ -20,6 +20,8 @@ import io.supertokens.Main; import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.authRecipe.AuthRecipeStorage; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; +import io.supertokens.pluginInterface.authRecipe.LoginMethod; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.emailverification.EmailVerificationStorage; import io.supertokens.pluginInterface.exceptions.StorageQueryException; @@ -42,6 +44,8 @@ import javax.annotation.Nullable; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; public class UserIdMapping { @@ -261,6 +265,14 @@ public static HashMap getUserIdMappingForSuperTokensUserIds( return tenantIdentifierWithStorage.getUserIdMappingStorage().getUserIdMappingForSuperTokensIds(userIds); } + public static HashMap getUserIdMappingForSuperTokensUserIds( + AppIdentifierWithStorage appIdentifierWithStorage, + ArrayList userIds) + throws StorageQueryException { + // userIds are already filtered for a tenant, so this becomes a tenant specific operation. + return appIdentifierWithStorage.getUserIdMappingStorage().getUserIdMappingForSuperTokensIds(userIds); + } + @TestOnly public static HashMap getUserIdMappingForSuperTokensUserIds(Main main, ArrayList userIds) @@ -321,4 +333,54 @@ public static void assertThatUserIdIsNotBeingUsedInNonAuthRecipes( } } } + + public static void populateExternalUserIdForUsers(AppIdentifierWithStorage appIdentifierWithStorage, AuthRecipeUserInfo[] users) + throws StorageQueryException { + Set userIds = new HashSet<>(); + + for (AuthRecipeUserInfo user : users) { + userIds.add(user.getSupertokensUserId()); + + for (LoginMethod lm : user.loginMethods) { + userIds.add(lm.getSupertokensUserId()); + } + } + ArrayList userIdsList = new ArrayList<>(userIds); + userIdsList.addAll(userIds); + HashMap userIdMappings = getUserIdMappingForSuperTokensUserIds(appIdentifierWithStorage, + userIdsList); + + for (AuthRecipeUserInfo user : users) { + user.setExternalUserId(userIdMappings.get(user.getSupertokensUserId())); + + for (LoginMethod lm : user.loginMethods) { + lm.setExternalUserId(userIdMappings.get(lm.getSupertokensUserId())); + } + } + } + + public static void populateExternalUserIdForUsers(TenantIdentifierWithStorage tenantIdentifierWithStorage, AuthRecipeUserInfo[] users) + throws StorageQueryException { + Set userIds = new HashSet<>(); + + for (AuthRecipeUserInfo user : users) { + userIds.add(user.getSupertokensUserId()); + + for (LoginMethod lm : user.loginMethods) { + userIds.add(lm.getSupertokensUserId()); + } + } + ArrayList userIdsList = new ArrayList<>(userIds); + userIdsList.addAll(userIds); + HashMap userIdMappings = getUserIdMappingForSuperTokensUserIds(tenantIdentifierWithStorage, + userIdsList); + + for (AuthRecipeUserInfo user : users) { + user.setExternalUserId(userIdMappings.get(user.getSupertokensUserId())); + + for (LoginMethod lm : user.loginMethods) { + lm.setExternalUserId(userIdMappings.get(lm.getSupertokensUserId())); + } + } + } } diff --git a/src/main/java/io/supertokens/webserver/api/core/GetUserByIdAPI.java b/src/main/java/io/supertokens/webserver/api/core/GetUserByIdAPI.java index 5f9f67837..e2562c8fa 100644 --- a/src/main/java/io/supertokens/webserver/api/core/GetUserByIdAPI.java +++ b/src/main/java/io/supertokens/webserver/api/core/GetUserByIdAPI.java @@ -66,14 +66,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO // if a userIdMapping exists, set the userId in the response to the externalUserId if (user != null) { - io.supertokens.pluginInterface.useridmapping.UserIdMapping userIdMapping = - UserIdMapping.getUserIdMapping( - getAppIdentifierWithStorage(req), user.getSupertokensUserId(), UserIdType.SUPERTOKENS); - if (userIdMapping != null) { - user.setExternalUserId(userIdMapping.externalUserId); - } else { - user.setExternalUserId(null); - } + UserIdMapping.populateExternalUserIdForUsers(appIdentifierWithStorageAndUserIdMapping.appIdentifierWithStorage, new AuthRecipeUserInfo[]{user}); } } catch (UnknownUserIdException e) { diff --git a/src/main/java/io/supertokens/webserver/api/core/UsersAPI.java b/src/main/java/io/supertokens/webserver/api/core/UsersAPI.java index cce18e222..4e7e2120c 100644 --- a/src/main/java/io/supertokens/webserver/api/core/UsersAPI.java +++ b/src/main/java/io/supertokens/webserver/api/core/UsersAPI.java @@ -168,21 +168,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO limit, timeJoinedOrder, paginationToken, recipeIdsEnumBuilder.build().toArray(RECIPE_ID[]::new), searchTags); - ArrayList userIds = new ArrayList<>(); - for (int i = 0; i < users.users.length; i++) { - userIds.add(users.users[i].getSupertokensUserId()); - } - HashMap userIdMapping = UserIdMapping.getUserIdMappingForSuperTokensUserIds( - tenantIdentifierWithStorage, userIds); - - for (int i = 0; i < users.users.length; i++) { - String externalId = userIdMapping.get(userIds.get(i)); - if (externalId != null) { - users.users[i].setExternalUserId(externalId); - } else { - users.users[i].setExternalUserId(null); - } - } + UserIdMapping.populateExternalUserIdForUsers(tenantIdentifierWithStorage, users.users); JsonObject result = new JsonObject(); result.addProperty("status", "OK"); diff --git a/src/main/java/io/supertokens/webserver/api/emailpassword/ImportUserWithPasswordHashAPI.java b/src/main/java/io/supertokens/webserver/api/emailpassword/ImportUserWithPasswordHashAPI.java index 1af432641..f1d9a7a9a 100644 --- a/src/main/java/io/supertokens/webserver/api/emailpassword/ImportUserWithPasswordHashAPI.java +++ b/src/main/java/io/supertokens/webserver/api/emailpassword/ImportUserWithPasswordHashAPI.java @@ -23,10 +23,12 @@ import io.supertokens.emailpassword.exceptions.UnsupportedPasswordHashingFormatException; import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.pluginInterface.RECIPE_ID; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; +import io.supertokens.useridmapping.UserIdMapping; import io.supertokens.utils.SemVer; import io.supertokens.utils.Utils; import io.supertokens.webserver.InputParser; @@ -96,6 +98,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I EmailPassword.ImportUserResponse importUserResponse = EmailPassword.importUserWithPasswordHash( tenant, main, email, passwordHash, passwordHashingAlgorithm); + UserIdMapping.populateExternalUserIdForUsers(getTenantIdentifierWithStorageFromRequest(req), new AuthRecipeUserInfo[]{importUserResponse.user}); JsonObject response = new JsonObject(); response.addProperty("status", "OK"); JsonObject userJson = diff --git a/src/main/java/io/supertokens/webserver/api/emailpassword/SignInAPI.java b/src/main/java/io/supertokens/webserver/api/emailpassword/SignInAPI.java index 7570ebc1f..cf57898cd 100644 --- a/src/main/java/io/supertokens/webserver/api/emailpassword/SignInAPI.java +++ b/src/main/java/io/supertokens/webserver/api/emailpassword/SignInAPI.java @@ -77,20 +77,11 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I try { AuthRecipeUserInfo user = EmailPassword.signIn(tenantIdentifierWithStorage, super.main, normalisedEmail, password); + io.supertokens.useridmapping.UserIdMapping.populateExternalUserIdForUsers(tenantIdentifierWithStorage, new AuthRecipeUserInfo[]{user}); ActiveUsers.updateLastActive(tenantIdentifierWithStorage.toAppIdentifierWithStorage(), main, user.getSupertokensUserId()); // use the internal user id - // if a userIdMapping exists, pass the externalUserId to the response - UserIdMapping userIdMapping = io.supertokens.useridmapping.UserIdMapping.getUserIdMapping( - tenantIdentifierWithStorage.toAppIdentifierWithStorage(), user.getSupertokensUserId(), UserIdType.SUPERTOKENS); - - if (userIdMapping != null) { - user.setExternalUserId(userIdMapping.externalUserId); - } else { - user.setExternalUserId(null); - } - JsonObject result = new JsonObject(); result.addProperty("status", "OK"); JsonObject userJson = getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v4_0) ? user.toJson() : diff --git a/src/main/java/io/supertokens/webserver/api/emailpassword/UserAPI.java b/src/main/java/io/supertokens/webserver/api/emailpassword/UserAPI.java index 5a08b01a5..7f992f6a5 100644 --- a/src/main/java/io/supertokens/webserver/api/emailpassword/UserAPI.java +++ b/src/main/java/io/supertokens/webserver/api/emailpassword/UserAPI.java @@ -89,16 +89,9 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO user = EmailPassword.getUserUsingId( appIdentifierWithStorageAndUserIdMapping.appIdentifierWithStorage, userId); - - // if the userIdMapping exists set the userId in the response to the externalUserId - if (user != null) { - if (appIdentifierWithStorageAndUserIdMapping.userIdMapping != null) { - user.setExternalUserId( - appIdentifierWithStorageAndUserIdMapping.userIdMapping.externalUserId); - } else { - user.setExternalUserId(null); - } - } + if (user != null) { + UserIdMapping.populateExternalUserIdForUsers(appIdentifierWithStorageAndUserIdMapping.appIdentifierWithStorage, new AuthRecipeUserInfo[]{user}); + } } else { // API is tenant specific for get by Email @@ -111,14 +104,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO // if a userIdMapping exists, set the userId in the response to the externalUserId if (user != null) { - io.supertokens.pluginInterface.useridmapping.UserIdMapping userIdMapping = - UserIdMapping.getUserIdMapping( - getAppIdentifierWithStorage(req), user.getSupertokensUserId(), UserIdType.SUPERTOKENS); - if (userIdMapping != null) { - user.setExternalUserId(userIdMapping.externalUserId); - } else { - user.setExternalUserId(null); - } + UserIdMapping.populateExternalUserIdForUsers(tenantIdentifierWithStorage, new AuthRecipeUserInfo[]{user}); } } } catch (UnknownUserIdException e) { diff --git a/src/main/java/io/supertokens/webserver/api/passwordless/ConsumeCodeAPI.java b/src/main/java/io/supertokens/webserver/api/passwordless/ConsumeCodeAPI.java index 91551f863..31a3420af 100644 --- a/src/main/java/io/supertokens/webserver/api/passwordless/ConsumeCodeAPI.java +++ b/src/main/java/io/supertokens/webserver/api/passwordless/ConsumeCodeAPI.java @@ -24,6 +24,7 @@ import io.supertokens.passwordless.Passwordless.ConsumeCodeResponse; import io.supertokens.passwordless.exceptions.*; import io.supertokens.pluginInterface.RECIPE_ID; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.authRecipe.LoginMethod; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; @@ -86,18 +87,10 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I this.getTenantIdentifierWithStorageFromRequest(req), main, deviceId, deviceIdHash, userInputCode, linkCode); + io.supertokens.useridmapping.UserIdMapping.populateExternalUserIdForUsers(this.getTenantIdentifierWithStorageFromRequest(req), new AuthRecipeUserInfo[]{consumeCodeResponse.user}); ActiveUsers.updateLastActive(this.getAppIdentifierWithStorage(req), main, consumeCodeResponse.user.getSupertokensUserId()); - UserIdMapping userIdMapping = io.supertokens.useridmapping.UserIdMapping.getUserIdMapping( - this.getAppIdentifierWithStorage(req), - consumeCodeResponse.user.getSupertokensUserId(), UserIdType.ANY); - if (userIdMapping != null) { - consumeCodeResponse.user.setExternalUserId(userIdMapping.externalUserId); - } else { - consumeCodeResponse.user.setExternalUserId(null); - } - JsonObject result = new JsonObject(); result.addProperty("status", "OK"); JsonObject userJson = diff --git a/src/main/java/io/supertokens/webserver/api/passwordless/UserAPI.java b/src/main/java/io/supertokens/webserver/api/passwordless/UserAPI.java index 9b3aba6af..76d352bb2 100644 --- a/src/main/java/io/supertokens/webserver/api/passwordless/UserAPI.java +++ b/src/main/java/io/supertokens/webserver/api/passwordless/UserAPI.java @@ -86,12 +86,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO // if the userIdMapping exists set the userId in the response to the externalUserId if (user != null) { - if (appIdentifierWithStorageAndUserIdMapping.userIdMapping != null) { - user.setExternalUserId( - appIdentifierWithStorageAndUserIdMapping.userIdMapping.externalUserId); - } else { - user.setExternalUserId(null); - } + io.supertokens.useridmapping.UserIdMapping.populateExternalUserIdForUsers(appIdentifierWithStorageAndUserIdMapping.appIdentifierWithStorage, new AuthRecipeUserInfo[]{user}); } } catch (UnknownUserIdException e) { user = null; @@ -100,27 +95,13 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO email = Utils.normaliseEmail(email); user = Passwordless.getUserByEmail(this.getTenantIdentifierWithStorageFromRequest(req), email); if (user != null) { - UserIdMapping userIdMapping = io.supertokens.useridmapping.UserIdMapping.getUserIdMapping( - this.getAppIdentifierWithStorage(req), - user.getSupertokensUserId(), UserIdType.SUPERTOKENS); - if (userIdMapping != null) { - user.setExternalUserId(userIdMapping.externalUserId); - } else { - user.setExternalUserId(null); - } + io.supertokens.useridmapping.UserIdMapping.populateExternalUserIdForUsers(this.getTenantIdentifierWithStorageFromRequest(req), new AuthRecipeUserInfo[]{user}); } } else { user = Passwordless.getUserByPhoneNumber(this.getTenantIdentifierWithStorageFromRequest(req), phoneNumber); if (user != null) { - UserIdMapping userIdMapping = io.supertokens.useridmapping.UserIdMapping.getUserIdMapping( - this.getAppIdentifierWithStorage(req), - user.getSupertokensUserId(), UserIdType.SUPERTOKENS); - if (userIdMapping != null) { - user.setExternalUserId(userIdMapping.externalUserId); - } else { - user.setExternalUserId(null); - } + io.supertokens.useridmapping.UserIdMapping.populateExternalUserIdForUsers(this.getTenantIdentifierWithStorageFromRequest(req), new AuthRecipeUserInfo[]{user}); } } diff --git a/src/main/java/io/supertokens/webserver/api/thirdparty/GetUsersByEmailAPI.java b/src/main/java/io/supertokens/webserver/api/thirdparty/GetUsersByEmailAPI.java index 0d5554b30..391db2c70 100644 --- a/src/main/java/io/supertokens/webserver/api/thirdparty/GetUsersByEmailAPI.java +++ b/src/main/java/io/supertokens/webserver/api/thirdparty/GetUsersByEmailAPI.java @@ -63,19 +63,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO String email = InputParser.getQueryParamOrThrowError(req, "email", false); email = Utils.normaliseEmail(email); AuthRecipeUserInfo[] users = ThirdParty.getUsersByEmail(tenantIdentifierWithStorage, email); - - // return the externalUserId if a mapping exists for a user - for (int i = 0; i < users.length; i++) { - // we intentionally do not use the function that accepts an array of user IDs to get the mapping cause - // this is simpler to use, and cause there shouldn't be that many userIds per email anyway - io.supertokens.pluginInterface.useridmapping.UserIdMapping userIdMapping = UserIdMapping - .getUserIdMapping(appIdentifierWithStorage, users[i].getSupertokensUserId(), UserIdType.SUPERTOKENS); - if (userIdMapping != null) { - users[i].setExternalUserId(userIdMapping.externalUserId); - } else { - users[i].setExternalUserId(null); - } - } + UserIdMapping.populateExternalUserIdForUsers(tenantIdentifierWithStorage, users); JsonObject result = new JsonObject(); result.addProperty("status", "OK"); diff --git a/src/main/java/io/supertokens/webserver/api/thirdparty/SignInUpAPI.java b/src/main/java/io/supertokens/webserver/api/thirdparty/SignInUpAPI.java index 449b63884..18e0a40c1 100644 --- a/src/main/java/io/supertokens/webserver/api/thirdparty/SignInUpAPI.java +++ b/src/main/java/io/supertokens/webserver/api/thirdparty/SignInUpAPI.java @@ -22,6 +22,7 @@ import io.supertokens.emailpassword.exceptions.EmailChangeNotAllowedException; import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.pluginInterface.RECIPE_ID; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.authRecipe.LoginMethod; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; @@ -78,6 +79,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I this.getTenantIdentifierWithStorageFromRequest(req), super.main, thirdPartyId, thirdPartyUserId, email, isEmailVerified); + UserIdMapping.populateExternalUserIdForUsers(this.getTenantIdentifierWithStorageFromRequest(req), new AuthRecipeUserInfo[]{response.user}); ActiveUsers.updateLastActive(this.getAppIdentifierWithStorage(req), main, response.user.getSupertokensUserId()); @@ -128,19 +130,10 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I ThirdParty.SignInUpResponse response = ThirdParty.signInUp( this.getTenantIdentifierWithStorageFromRequest(req), super.main, thirdPartyId, thirdPartyUserId, email); + UserIdMapping.populateExternalUserIdForUsers(this.getTenantIdentifierWithStorageFromRequest(req), new AuthRecipeUserInfo[]{response.user}); ActiveUsers.updateLastActive(this.getAppIdentifierWithStorage(req), main, response.user.getSupertokensUserId()); - // - io.supertokens.pluginInterface.useridmapping.UserIdMapping userIdMapping = UserIdMapping - .getUserIdMapping(this.getAppIdentifierWithStorage(req), response.user.getSupertokensUserId(), - UserIdType.SUPERTOKENS); - if (userIdMapping != null) { - response.user.setExternalUserId(userIdMapping.externalUserId); - } else { - response.user.setExternalUserId(null); - } - JsonObject result = new JsonObject(); result.addProperty("status", "OK"); result.addProperty("createdNewUser", response.createdNewUser); diff --git a/src/main/java/io/supertokens/webserver/api/thirdparty/UserAPI.java b/src/main/java/io/supertokens/webserver/api/thirdparty/UserAPI.java index 689c36433..2ce566281 100644 --- a/src/main/java/io/supertokens/webserver/api/thirdparty/UserAPI.java +++ b/src/main/java/io/supertokens/webserver/api/thirdparty/UserAPI.java @@ -85,12 +85,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO user = ThirdParty.getUser(appIdentifierWithStorageAndUserIdMapping.appIdentifierWithStorage, userId); if (user != null) { - if (appIdentifierWithStorageAndUserIdMapping.userIdMapping != null) { - user.setExternalUserId( - appIdentifierWithStorageAndUserIdMapping.userIdMapping.externalUserId); - } else { - user.setExternalUserId(null); - } + UserIdMapping.populateExternalUserIdForUsers(appIdentifierWithStorageAndUserIdMapping.appIdentifierWithStorage, new AuthRecipeUserInfo[]{user}); } } catch (UnknownUserIdException e) { // let the user be null @@ -99,13 +94,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO user = ThirdParty.getUser(this.getTenantIdentifierWithStorageFromRequest(req), thirdPartyId, thirdPartyUserId); if (user != null) { - io.supertokens.pluginInterface.useridmapping.UserIdMapping userIdMapping = UserIdMapping - .getUserIdMapping(this.getAppIdentifierWithStorage(req), user.getSupertokensUserId(), UserIdType.SUPERTOKENS); - if (userIdMapping != null) { - user.setExternalUserId(userIdMapping.externalUserId); - } else { - user.setExternalUserId(null); - } + UserIdMapping.populateExternalUserIdForUsers(getTenantIdentifierWithStorageFromRequest(req), new AuthRecipeUserInfo[]{user}); } } diff --git a/src/test/java/io/supertokens/test/accountlinking/api/GetUserByIdTest.java b/src/test/java/io/supertokens/test/accountlinking/api/GetUserByIdTest.java index 8b8246bae..a949f7afb 100644 --- a/src/test/java/io/supertokens/test/accountlinking/api/GetUserByIdTest.java +++ b/src/test/java/io/supertokens/test/accountlinking/api/GetUserByIdTest.java @@ -37,6 +37,7 @@ import io.supertokens.test.httpRequest.HttpRequestForTesting; import io.supertokens.thirdparty.ThirdParty; import io.supertokens.useridmapping.UserIdMapping; +import io.supertokens.utils.SemVer; import io.supertokens.webserver.WebserverAPI; import org.junit.AfterClass; import org.junit.Before; @@ -466,7 +467,7 @@ public void testWithUserIdMapping() throws Exception { params.put("userId", userId); JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", "http://localhost:3567/user/id", params, 1000, 1000, null, - WebserverAPI.getLatestCDIVersion().get(), ""); + SemVer.v4_0.get(), ""); JsonObject user = response.get("user").getAsJsonObject(); assertEquals("ext1", user.get("id").getAsString()); assertEquals("ext1", user.get("loginMethods").getAsJsonArray().get(0).getAsJsonObject().get("recipeUserId").getAsString()); diff --git a/src/test/java/io/supertokens/test/accountlinking/api/UserPaginationTest.java b/src/test/java/io/supertokens/test/accountlinking/api/UserPaginationTest.java index 6b6c90d6c..b5d619959 100644 --- a/src/test/java/io/supertokens/test/accountlinking/api/UserPaginationTest.java +++ b/src/test/java/io/supertokens/test/accountlinking/api/UserPaginationTest.java @@ -152,19 +152,19 @@ public void testUserPaginationResultJson() throws Exception { AuthRecipeUserInfo user8 = createThirdPartyUser(process.getProcess(), "google", "test8", "test8@example.com"); { - AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.id).user; - AuthRecipe.linkAccounts(process.getProcess(), user3.id, primaryUser.id); + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user1.getSupertokensUserId()).user; + AuthRecipe.linkAccounts(process.getProcess(), user3.getSupertokensUserId(), primaryUser.getSupertokensUserId()); } { - AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user2.id).user; - AuthRecipe.linkAccounts(process.getProcess(), user5.id, primaryUser.id); - AuthRecipe.linkAccounts(process.getProcess(), user7.id, primaryUser.id); + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user2.getSupertokensUserId()).user; + AuthRecipe.linkAccounts(process.getProcess(), user5.getSupertokensUserId(), primaryUser.getSupertokensUserId()); + AuthRecipe.linkAccounts(process.getProcess(), user7.getSupertokensUserId(), primaryUser.getSupertokensUserId()); } { - AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user6.id).user; - AuthRecipe.linkAccounts(process.getProcess(), user8.id, primaryUser.id); + AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user6.getSupertokensUserId()).user; + AuthRecipe.linkAccounts(process.getProcess(), user8.getSupertokensUserId(), primaryUser.getSupertokensUserId()); } @@ -178,7 +178,7 @@ public void testUserPaginationResultJson() throws Exception { { params = new HashMap<>(); - params.put("userId", user1.id); + params.put("userId", user1.getSupertokensUserId()); JsonObject userResponse = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", "http://localhost:3567/user/id", params, 1000, 1000, null, SemVer.v4_0.get(), ""); @@ -188,7 +188,7 @@ public void testUserPaginationResultJson() throws Exception { { params = new HashMap<>(); - params.put("userId", user2.id); + params.put("userId", user2.getSupertokensUserId()); JsonObject userResponse = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", "http://localhost:3567/user/id", params, 1000, 1000, null, SemVer.v4_0.get(), ""); @@ -198,7 +198,7 @@ public void testUserPaginationResultJson() throws Exception { { params = new HashMap<>(); - params.put("userId", user4.id); + params.put("userId", user4.getSupertokensUserId()); JsonObject userResponse = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", "http://localhost:3567/user/id", params, 1000, 1000, null, SemVer.v4_0.get(), ""); @@ -208,7 +208,7 @@ public void testUserPaginationResultJson() throws Exception { { params = new HashMap<>(); - params.put("userId", user6.id); + params.put("userId", user6.getSupertokensUserId()); JsonObject userResponse = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", "http://localhost:3567/user/id", params, 1000, 1000, null, SemVer.v4_0.get(), ""); @@ -239,36 +239,36 @@ public void testUserPaginationWithManyUsers() throws Exception { // emailpassword users for (int i=0; i < 200; i++) { AuthRecipeUserInfo user = createEmailPasswordUser(process.getProcess(), "epuser" + i + "@gmail.com", "password" + i); - userInfoMap.put(user.id, user); - userIds.add(user.id); - emailPasswordUsers.add(user.id); + userInfoMap.put(user.getSupertokensUserId(), user); + userIds.add(user.getSupertokensUserId()); + emailPasswordUsers.add(user.getSupertokensUserId()); Thread.sleep(10); } // passwordless users with email for (int i=0; i < 200; i++) { AuthRecipeUserInfo user = createPasswordlessUserWithEmail(process.getProcess(), "pluser" + i + "@gmail.com"); - userInfoMap.put(user.id, user); - userIds.add(user.id); - passwordlessUsers.add(user.id); + userInfoMap.put(user.getSupertokensUserId(), user); + userIds.add(user.getSupertokensUserId()); + passwordlessUsers.add(user.getSupertokensUserId()); Thread.sleep(10); } // passwordless users with phone for (int i=0; i < 200; i++) { AuthRecipeUserInfo user = createPasswordlessUserWithPhone(process.getProcess(), "+1234567890" + i); - userInfoMap.put(user.id, user); - userIds.add(user.id); - passwordlessUsers.add(user.id); + userInfoMap.put(user.getSupertokensUserId(), user); + userIds.add(user.getSupertokensUserId()); + passwordlessUsers.add(user.getSupertokensUserId()); Thread.sleep(10); } // thirdparty users for (int i=0; i < 200; i++) { AuthRecipeUserInfo user = createThirdPartyUser(process.getProcess(), "google", "tpuser" + i, "tpuser" + i + "@gmail.com"); - userInfoMap.put(user.id, user); - userIds.add(user.id); - thirdPartyUsers.add(user.id); + userInfoMap.put(user.getSupertokensUserId(), user); + userIds.add(user.getSupertokensUserId()); + thirdPartyUsers.add(user.getSupertokensUserId()); Thread.sleep(10); } @@ -294,10 +294,10 @@ public void testUserPaginationWithManyUsers() throws Exception { if (userIdsToLink.size() > 1) { AuthRecipeUserInfo primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), userIdsToLink.get(0)).user; - primaryUserIds.add(primaryUser.id); + primaryUserIds.add(primaryUser.getSupertokensUserId()); for (int i=1; i < userIdsToLink.size(); i++) { - AuthRecipe.linkAccounts(process.getProcess(), userIdsToLink.get(i), primaryUser.id); + AuthRecipe.linkAccounts(process.getProcess(), userIdsToLink.get(i), primaryUser.getSupertokensUserId()); } } else { primaryUserIds.add(userIdsToLink.get(0)); diff --git a/src/test/java/io/supertokens/test/emailpassword/api/SignInAPITest4_0.java b/src/test/java/io/supertokens/test/emailpassword/api/SignInAPITest4_0.java index 8d81f0038..c2c081f61 100644 --- a/src/test/java/io/supertokens/test/emailpassword/api/SignInAPITest4_0.java +++ b/src/test/java/io/supertokens/test/emailpassword/api/SignInAPITest4_0.java @@ -81,7 +81,7 @@ public void testGoodInput() throws Exception { "emailpassword"); assertEquals(signInResponse.get("status").getAsString(), "OK"); - assertEquals(signInResponse.entrySet().size(), 2); + assertEquals(signInResponse.entrySet().size(), 3); JsonObject jsonUser = signInResponse.get("user").getAsJsonObject(); assert (jsonUser.get("id").getAsString().equals(user.getSupertokensUserId())); @@ -133,7 +133,7 @@ public void testGoodInputWithUserIdMapping() throws Exception { "emailpassword"); assertEquals(signInResponse.get("status").getAsString(), "OK"); - assertEquals(signInResponse.entrySet().size(), 2); + assertEquals(signInResponse.entrySet().size(), 3); JsonObject jsonUser = signInResponse.get("user").getAsJsonObject(); assert (jsonUser.get("id").getAsString().equals("e1")); @@ -196,7 +196,7 @@ public void testGoodInputWithUserIdMappingAndMultipleLinkedAccounts() throws Exc "emailpassword"); assertEquals(signInResponse.get("status").getAsString(), "OK"); - assertEquals(signInResponse.entrySet().size(), 2); + assertEquals(signInResponse.entrySet().size(), 3); JsonObject jsonUser = signInResponse.get("user").getAsJsonObject(); assert (jsonUser.get("id").getAsString().equals("e1")); @@ -225,7 +225,7 @@ public void testGoodInputWithUserIdMappingAndMultipleLinkedAccounts() throws Exc JsonObject lM = jsonUser.get("loginMethods").getAsJsonArray().get(0).getAsJsonObject(); assertFalse(lM.get("verified").getAsBoolean()); assertEquals(lM.get("timeJoined").getAsLong(), user0.timeJoined); - assertEquals(lM.get("recipeUserId").getAsString(), user0.getSupertokensUserId()); + assertEquals(lM.get("recipeUserId").getAsString(), "e0"); assertEquals(lM.get("recipeId").getAsString(), "emailpassword"); assertEquals(lM.get("email").getAsString(), "random1@gmail.com"); assert (lM.entrySet().size() == 6); diff --git a/src/test/java/io/supertokens/test/emailpassword/api/SignUpAPITest4_0.java b/src/test/java/io/supertokens/test/emailpassword/api/SignUpAPITest4_0.java index 223b37267..1dc7cc2ef 100644 --- a/src/test/java/io/supertokens/test/emailpassword/api/SignUpAPITest4_0.java +++ b/src/test/java/io/supertokens/test/emailpassword/api/SignUpAPITest4_0.java @@ -73,7 +73,7 @@ public void testGoodInput() throws Exception { "emailpassword"); assertEquals(signInResponse.get("status").getAsString(), "OK"); - assertEquals(signInResponse.entrySet().size(), 2); + assertEquals(signInResponse.entrySet().size(), 3); JsonObject jsonUser = signInResponse.get("user").getAsJsonObject(); assertNotNull(jsonUser.get("id")); From 0a79d5a124e98d840b7a8b45407320a39c22f158 Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Fri, 25 Aug 2023 16:05:13 +0530 Subject: [PATCH 095/131] fix: user object in link accounts api (#771) * fix: link accounts api * fix: minor fix * fix: tests --- .../io/supertokens/authRecipe/AuthRecipe.java | 26 +++-- ...nkedWithAnotherPrimaryUserIdException.java | 8 +- .../accountlinking/CanLinkAccountsAPI.java | 11 +- .../api/accountlinking/LinkAccountsAPI.java | 18 ++- .../api/core/ListUsersByAccountInfoAPI.java | 13 +-- .../test/accountlinking/DeleteUserTest.java | 16 +-- .../test/accountlinking/LinkAccountsTest.java | 20 ++-- .../api/CanLinkAccountsAPITest.java | 6 +- .../api/GetUserByAccountInfoTest.java | 4 +- .../api/LinkAccountsAPITest.java | 103 ++++++++++++++++++ .../test/authRecipe/GetUserByIdAPITest.java | 2 +- 11 files changed, 161 insertions(+), 66 deletions(-) diff --git a/src/main/java/io/supertokens/authRecipe/AuthRecipe.java b/src/main/java/io/supertokens/authRecipe/AuthRecipe.java index d1cea2a54..4c07bdadf 100644 --- a/src/main/java/io/supertokens/authRecipe/AuthRecipe.java +++ b/src/main/java/io/supertokens/authRecipe/AuthRecipe.java @@ -234,7 +234,7 @@ private static CanLinkAccountsResult canLinkAccountsHelper(TransactionConnection if (recipeUser.getSupertokensUserId().equals(primaryUser.getSupertokensUserId())) { return new CanLinkAccountsResult(recipeUser.getSupertokensUserId(), primaryUser.getSupertokensUserId(), true); } else { - throw new RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException(recipeUser.getSupertokensUserId(), + throw new RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException(recipeUser, "The input recipe user ID is already linked to another user ID"); } } @@ -308,7 +308,7 @@ private static CanLinkAccountsResult canLinkAccountsHelper(TransactionConnection } @TestOnly - public static boolean linkAccounts(Main main, String recipeUserId, String primaryUserId) + public static LinkAccountsResult linkAccounts(Main main, String recipeUserId, String primaryUserId) throws StorageQueryException, AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException, UnknownUserIdException, FeatureNotEnabledException, InputUserIdIsNotAPrimaryUserException, @@ -322,7 +322,7 @@ public static boolean linkAccounts(Main main, String recipeUserId, String primar } } - public static boolean linkAccounts(Main main, AppIdentifierWithStorage appIdentifierWithStorage, + public static LinkAccountsResult linkAccounts(Main main, AppIdentifierWithStorage appIdentifierWithStorage, String _recipeUserId, String _primaryUserId) throws StorageQueryException, AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException, @@ -337,14 +337,14 @@ public static boolean linkAccounts(Main main, AppIdentifierWithStorage appIdenti AuthRecipeSQLStorage storage = (AuthRecipeSQLStorage) appIdentifierWithStorage.getAuthRecipeStorage(); try { - boolean wasAlreadyLinked = storage.startTransaction(con -> { + LinkAccountsResult result = storage.startTransaction(con -> { try { CanLinkAccountsResult canLinkAccounts = canLinkAccountsHelper(con, appIdentifierWithStorage, _recipeUserId, _primaryUserId); if (canLinkAccounts.alreadyLinked) { - return true; + return new LinkAccountsResult(getUserById(appIdentifierWithStorage, canLinkAccounts.primaryUserId), true); } // now we can link accounts in the db. storage.linkAccounts_Transaction(appIdentifierWithStorage, con, canLinkAccounts.recipeUserId, @@ -352,7 +352,7 @@ public static boolean linkAccounts(Main main, AppIdentifierWithStorage appIdenti storage.commitTransaction(con); - return false; + return new LinkAccountsResult(getUserById(appIdentifierWithStorage, canLinkAccounts.primaryUserId), false); } catch (UnknownUserIdException | InputUserIdIsNotAPrimaryUserException | RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException | AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException e) { @@ -360,7 +360,7 @@ public static boolean linkAccounts(Main main, AppIdentifierWithStorage appIdenti } }); - if (!wasAlreadyLinked) { + if (!result.wasAlreadyLinked) { io.supertokens.pluginInterface.useridmapping.UserIdMapping mappingResult = io.supertokens.useridmapping.UserIdMapping.getUserIdMapping( appIdentifierWithStorage, @@ -370,7 +370,7 @@ public static boolean linkAccounts(Main main, AppIdentifierWithStorage appIdenti mappingResult == null ? _recipeUserId : mappingResult.externalUserId, false); } - return wasAlreadyLinked; + return result; } catch (StorageTransactionLogicException e) { if (e.actualException instanceof UnknownUserIdException) { throw (UnknownUserIdException) e.actualException; @@ -385,6 +385,16 @@ public static boolean linkAccounts(Main main, AppIdentifierWithStorage appIdenti } } + public static class LinkAccountsResult { + public final AuthRecipeUserInfo user; + public final boolean wasAlreadyLinked; + + public LinkAccountsResult(AuthRecipeUserInfo user, boolean wasAlreadyLinked) { + this.user = user; + this.wasAlreadyLinked = wasAlreadyLinked; + } + } + @TestOnly public static CreatePrimaryUserResult canCreatePrimaryUser(Main main, String recipeUserId) diff --git a/src/main/java/io/supertokens/authRecipe/exception/RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException.java b/src/main/java/io/supertokens/authRecipe/exception/RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException.java index 901f021d8..737929a62 100644 --- a/src/main/java/io/supertokens/authRecipe/exception/RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException.java +++ b/src/main/java/io/supertokens/authRecipe/exception/RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException.java @@ -16,11 +16,13 @@ package io.supertokens.authRecipe.exception; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; + public class RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException extends Exception { - public final String primaryUserId; + public final AuthRecipeUserInfo recipeUser; - public RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException(String primaryUserId, String description) { + public RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException(AuthRecipeUserInfo recipeUser, String description) { super(description); - this.primaryUserId = primaryUserId; + this.recipeUser = recipeUser; } } diff --git a/src/main/java/io/supertokens/webserver/api/accountlinking/CanLinkAccountsAPI.java b/src/main/java/io/supertokens/webserver/api/accountlinking/CanLinkAccountsAPI.java index 93d1714ba..97c10f2c6 100644 --- a/src/main/java/io/supertokens/webserver/api/accountlinking/CanLinkAccountsAPI.java +++ b/src/main/java/io/supertokens/webserver/api/accountlinking/CanLinkAccountsAPI.java @@ -24,6 +24,7 @@ import io.supertokens.authRecipe.exception.InputUserIdIsNotAPrimaryUserException; import io.supertokens.authRecipe.exception.RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException; import io.supertokens.pluginInterface.RECIPE_ID; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; @@ -121,14 +122,8 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO try { JsonObject response = new JsonObject(); response.addProperty("status", "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"); - io.supertokens.pluginInterface.useridmapping.UserIdMapping result = UserIdMapping.getUserIdMapping( - recipeUserIdAppIdentifierWithStorage, e.primaryUserId, - UserIdType.SUPERTOKENS); - if (result != null) { - response.addProperty("primaryUserId", result.externalUserId); - } else { - response.addProperty("primaryUserId", e.primaryUserId); - } + UserIdMapping.populateExternalUserIdForUsers(recipeUserIdAppIdentifierWithStorage, new AuthRecipeUserInfo[]{e.recipeUser}); + response.addProperty("primaryUserId", e.recipeUser.getSupertokensOrExternalUserId()); response.addProperty("description", e.getMessage()); super.sendJsonResponse(200, response, resp); } catch (StorageQueryException ex) { diff --git a/src/main/java/io/supertokens/webserver/api/accountlinking/LinkAccountsAPI.java b/src/main/java/io/supertokens/webserver/api/accountlinking/LinkAccountsAPI.java index 279b5d189..b48009cfa 100644 --- a/src/main/java/io/supertokens/webserver/api/accountlinking/LinkAccountsAPI.java +++ b/src/main/java/io/supertokens/webserver/api/accountlinking/LinkAccountsAPI.java @@ -25,6 +25,7 @@ import io.supertokens.authRecipe.exception.RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException; import io.supertokens.featureflag.exceptions.FeatureNotEnabledException; import io.supertokens.pluginInterface.RECIPE_ID; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; @@ -92,12 +93,14 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I recipeUserIdAppIdentifierWithStorage.getStorage().getUserPoolId())); } - boolean alreadyLinked = AuthRecipe.linkAccounts(main, + AuthRecipe.LinkAccountsResult linkAccountsResult = AuthRecipe.linkAccounts(main, primaryUserIdAppIdentifierWithStorage, recipeUserId, primaryUserId); + UserIdMapping.populateExternalUserIdForUsers(primaryUserIdAppIdentifierWithStorage, new AuthRecipeUserInfo[]{linkAccountsResult.user}); JsonObject response = new JsonObject(); response.addProperty("status", "OK"); - response.addProperty("accountsAlreadyLinked", alreadyLinked); + response.addProperty("accountsAlreadyLinked", linkAccountsResult.wasAlreadyLinked); + response.add("user", linkAccountsResult.user.toJson()); super.sendJsonResponse(200, response, resp); } catch (StorageQueryException | TenantOrAppNotFoundException | FeatureNotEnabledException e) { throw new ServletException(e); @@ -124,15 +127,10 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I try { JsonObject response = new JsonObject(); response.addProperty("status", "RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"); - io.supertokens.pluginInterface.useridmapping.UserIdMapping result = UserIdMapping.getUserIdMapping( - recipeUserIdAppIdentifierWithStorage, e.primaryUserId, - UserIdType.SUPERTOKENS); - if (result != null) { - response.addProperty("primaryUserId", result.externalUserId); - } else { - response.addProperty("primaryUserId", e.primaryUserId); - } + UserIdMapping.populateExternalUserIdForUsers(recipeUserIdAppIdentifierWithStorage, new AuthRecipeUserInfo[]{e.recipeUser}); + response.addProperty("primaryUserId", e.recipeUser.getSupertokensOrExternalUserId()); response.addProperty("description", e.getMessage()); + response.add("user", e.recipeUser.toJson()); super.sendJsonResponse(200, response, resp); } catch (StorageQueryException ex) { throw new ServletException(ex); diff --git a/src/main/java/io/supertokens/webserver/api/core/ListUsersByAccountInfoAPI.java b/src/main/java/io/supertokens/webserver/api/core/ListUsersByAccountInfoAPI.java index 7bfa7b3a6..2cc2a7eb5 100644 --- a/src/main/java/io/supertokens/webserver/api/core/ListUsersByAccountInfoAPI.java +++ b/src/main/java/io/supertokens/webserver/api/core/ListUsersByAccountInfoAPI.java @@ -76,18 +76,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO AuthRecipeUserInfo[] users = AuthRecipe.getUsersByAccountInfo( this.getTenantIdentifierWithStorageFromRequest( req), doUnionOfAccountInfo, email, phoneNumber, thirdPartyId, thirdPartyUserId); - - for (int i = 0; i < users.length; i++) { - // we intentionally do not use the function that accepts an array of user IDs to get the mapping cause - // this is simpler to use, and cause there shouldn't be that many userIds per email anyway - io.supertokens.pluginInterface.useridmapping.UserIdMapping userIdMapping = UserIdMapping - .getUserIdMapping(appIdentifierWithStorage, users[i].getSupertokensUserId(), UserIdType.SUPERTOKENS); - if (userIdMapping != null) { - users[i].setExternalUserId(userIdMapping.externalUserId); - } else { - users[i].setExternalUserId(null); - } - } + UserIdMapping.populateExternalUserIdForUsers(appIdentifierWithStorage, users); JsonObject result = new JsonObject(); result.addProperty("status", "OK"); diff --git a/src/test/java/io/supertokens/test/accountlinking/DeleteUserTest.java b/src/test/java/io/supertokens/test/accountlinking/DeleteUserTest.java index 40593201b..edc07511d 100644 --- a/src/test/java/io/supertokens/test/accountlinking/DeleteUserTest.java +++ b/src/test/java/io/supertokens/test/accountlinking/DeleteUserTest.java @@ -73,7 +73,7 @@ public void deleteLinkedUserWithoutRemovingAllUsers() throws Exception { AuthRecipe.createPrimaryUser(process.main, r2.getSupertokensUserId()); - assert (!AuthRecipe.linkAccounts(process.main, r1.getSupertokensUserId(), r2.getSupertokensUserId())); + assert (!AuthRecipe.linkAccounts(process.main, r1.getSupertokensUserId(), r2.getSupertokensUserId()).wasAlreadyLinked); AuthRecipe.deleteUser(process.main, r1.getSupertokensUserId(), false); @@ -110,7 +110,7 @@ public void deleteLinkedPrimaryUserWithoutRemovingAllUsers() throws Exception { AuthRecipe.createPrimaryUser(process.main, r2.getSupertokensUserId()); - assert (!AuthRecipe.linkAccounts(process.main, r1.getSupertokensUserId(), r2.getSupertokensUserId())); + assert (!AuthRecipe.linkAccounts(process.main, r1.getSupertokensUserId(), r2.getSupertokensUserId()).wasAlreadyLinked); AuthRecipe.deleteUser(process.main, r2.getSupertokensUserId(), false); @@ -148,7 +148,7 @@ public void deleteLinkedPrimaryUserRemovingAllUsers() throws Exception { AuthRecipe.createPrimaryUser(process.main, r2.getSupertokensUserId()); - assert (!AuthRecipe.linkAccounts(process.main, r1.getSupertokensUserId(), r2.getSupertokensUserId())); + assert (!AuthRecipe.linkAccounts(process.main, r1.getSupertokensUserId(), r2.getSupertokensUserId()).wasAlreadyLinked); AuthRecipe.deleteUser(process.main, r2.getSupertokensUserId()); @@ -183,7 +183,7 @@ public void deleteLinkedPrimaryUserRemovingAllUsers2() throws Exception { AuthRecipe.createPrimaryUser(process.main, r2.getSupertokensUserId()); - assert (!AuthRecipe.linkAccounts(process.main, r1.getSupertokensUserId(), r2.getSupertokensUserId())); + assert (!AuthRecipe.linkAccounts(process.main, r1.getSupertokensUserId(), r2.getSupertokensUserId()).wasAlreadyLinked); AuthRecipe.deleteUser(process.main, r1.getSupertokensUserId()); @@ -228,7 +228,7 @@ public void deleteUserTestWithUserIdMapping1() throws Exception { AuthRecipe.createPrimaryUser(process.main, r2.getSupertokensUserId()); - assert (!AuthRecipe.linkAccounts(process.main, r1.getSupertokensUserId(), r2.getSupertokensUserId())); + assert (!AuthRecipe.linkAccounts(process.main, r1.getSupertokensUserId(), r2.getSupertokensUserId()).wasAlreadyLinked); AuthRecipe.deleteUser(process.main, r1.getSupertokensUserId(), false); @@ -280,7 +280,7 @@ public void deleteUserTestWithUserIdMapping2() throws Exception { AuthRecipe.createPrimaryUser(process.main, r2.getSupertokensUserId()); - assert (!AuthRecipe.linkAccounts(process.main, r1.getSupertokensUserId(), r2.getSupertokensUserId())); + assert (!AuthRecipe.linkAccounts(process.main, r1.getSupertokensUserId(), r2.getSupertokensUserId()).wasAlreadyLinked); AuthRecipe.deleteUser(process.main, r1.getSupertokensUserId()); @@ -338,8 +338,8 @@ public void deleteUserTestWithUserIdMapping3() throws Exception { AuthRecipe.createPrimaryUser(process.main, r2.getSupertokensUserId()); - assert (!AuthRecipe.linkAccounts(process.main, r1.getSupertokensUserId(), r2.getSupertokensUserId())); - assert (!AuthRecipe.linkAccounts(process.main, r3.getSupertokensUserId(), r1.getSupertokensUserId())); + assert (!AuthRecipe.linkAccounts(process.main, r1.getSupertokensUserId(), r2.getSupertokensUserId()).wasAlreadyLinked); + assert (!AuthRecipe.linkAccounts(process.main, r3.getSupertokensUserId(), r1.getSupertokensUserId()).wasAlreadyLinked); AuthRecipe.deleteUser(process.main, r1.getSupertokensUserId(), false); diff --git a/src/test/java/io/supertokens/test/accountlinking/LinkAccountsTest.java b/src/test/java/io/supertokens/test/accountlinking/LinkAccountsTest.java index 532b3e90d..5298adf7f 100644 --- a/src/test/java/io/supertokens/test/accountlinking/LinkAccountsTest.java +++ b/src/test/java/io/supertokens/test/accountlinking/LinkAccountsTest.java @@ -88,7 +88,7 @@ public void linkAccountSuccess() throws Exception { String[] sessions = Session.getAllNonExpiredSessionHandlesForUser(process.main, user2.getSupertokensUserId()); assert (sessions.length == 1); - boolean wasAlreadyLinked = AuthRecipe.linkAccounts(process.main, user2.getSupertokensUserId(), user.getSupertokensUserId()); + boolean wasAlreadyLinked = AuthRecipe.linkAccounts(process.main, user2.getSupertokensUserId(), user.getSupertokensUserId()).wasAlreadyLinked; assert (!wasAlreadyLinked); AuthRecipeUserInfo refetchUser2 = AuthRecipe.getUserById(process.main, user2.getSupertokensUserId()); @@ -140,7 +140,7 @@ public void linkAccountSuccessWithSameEmail() throws Exception { String[] sessions = Session.getAllNonExpiredSessionHandlesForUser(process.main, user2.getSupertokensUserId()); assert (sessions.length == 1); - boolean wasAlreadyLinked = AuthRecipe.linkAccounts(process.main, user2.getSupertokensUserId(), user.getSupertokensUserId()); + boolean wasAlreadyLinked = AuthRecipe.linkAccounts(process.main, user2.getSupertokensUserId(), user.getSupertokensUserId()).wasAlreadyLinked; assert (!wasAlreadyLinked); AuthRecipeUserInfo refetchUser2 = AuthRecipe.getUserById(process.main, user2.getSupertokensUserId()); @@ -204,13 +204,13 @@ public void linkAccountSuccessEvenIfUsingRecipeUserIdThatIsLinkedToPrimaryUser() AuthRecipe.createPrimaryUser(process.main, user.getSupertokensUserId()); - boolean wasAlreadyLinked = AuthRecipe.linkAccounts(process.main, user2.getSupertokensUserId(), user.getSupertokensUserId()); + boolean wasAlreadyLinked = AuthRecipe.linkAccounts(process.main, user2.getSupertokensUserId(), user.getSupertokensUserId()).wasAlreadyLinked; assert (!wasAlreadyLinked); AuthRecipeUserInfo user3 = EmailPassword.signUp(process.getProcess(), "test3@example.com", "password"); assert (!user3.isPrimaryUser); - wasAlreadyLinked = AuthRecipe.linkAccounts(process.main, user3.getSupertokensUserId(), user2.getSupertokensUserId()); + wasAlreadyLinked = AuthRecipe.linkAccounts(process.main, user3.getSupertokensUserId(), user2.getSupertokensUserId()).wasAlreadyLinked; assert (!wasAlreadyLinked); AuthRecipeUserInfo refetchUser = AuthRecipe.getUserById(process.main, user.getSupertokensUserId()); @@ -256,14 +256,14 @@ public void alreadyLinkAccountLinkAgain() throws Exception { AuthRecipe.createPrimaryUser(process.main, user.getSupertokensUserId()); - boolean wasAlreadyLinked = AuthRecipe.linkAccounts(process.main, user2.getSupertokensUserId(), user.getSupertokensUserId()); + boolean wasAlreadyLinked = AuthRecipe.linkAccounts(process.main, user2.getSupertokensUserId(), user.getSupertokensUserId()).wasAlreadyLinked; assert (!wasAlreadyLinked); Session.createNewSession(process.main, user2.getSupertokensUserId(), new JsonObject(), new JsonObject()); String[] sessions = Session.getAllNonExpiredSessionHandlesForUser(process.main, user2.getSupertokensUserId()); assert (sessions.length == 1); - wasAlreadyLinked = AuthRecipe.linkAccounts(process.main, user2.getSupertokensUserId(), user.getSupertokensUserId()); + wasAlreadyLinked = AuthRecipe.linkAccounts(process.main, user2.getSupertokensUserId(), user.getSupertokensUserId()).wasAlreadyLinked; assert (wasAlreadyLinked); // cause linkAccounts revokes sessions for the recipe user ID @@ -301,7 +301,7 @@ public void linkAccountFailureCauseRecipeUserIdLinkedWithAnotherPrimaryUser() th AuthRecipe.createPrimaryUser(process.main, user.getSupertokensUserId()); - boolean wasAlreadyLinked = AuthRecipe.linkAccounts(process.main, user2.getSupertokensUserId(), user.getSupertokensUserId()); + boolean wasAlreadyLinked = AuthRecipe.linkAccounts(process.main, user2.getSupertokensUserId(), user.getSupertokensUserId()).wasAlreadyLinked; assert (!wasAlreadyLinked); AuthRecipeUserInfo user3 = EmailPassword.signUp(process.getProcess(), "test3@example.com", "password"); @@ -312,7 +312,7 @@ public void linkAccountFailureCauseRecipeUserIdLinkedWithAnotherPrimaryUser() th AuthRecipe.linkAccounts(process.main, user2.getSupertokensUserId(), user3.getSupertokensUserId()); assert (false); } catch (RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException e) { - assert (e.primaryUserId.equals(user.getSupertokensUserId())); + assert (e.recipeUser.getSupertokensUserId().equals(user.getSupertokensUserId())); assert (e.getMessage().equals("The input recipe user ID is already linked to another user ID")); } @@ -533,7 +533,7 @@ public void linkAccountSuccessAcrossTenants() throws Exception { AuthRecipeUserInfo user2 = signInUpResponse.user; assert (!user2.isPrimaryUser); - boolean wasAlreadyLinked = AuthRecipe.linkAccounts(process.main, user2.getSupertokensUserId(), user.getSupertokensUserId()); + boolean wasAlreadyLinked = AuthRecipe.linkAccounts(process.main, user2.getSupertokensUserId(), user.getSupertokensUserId()).wasAlreadyLinked; assert (!wasAlreadyLinked); AuthRecipeUserInfo refetchedUser1 = AuthRecipe.getUserById(process.main, user.getSupertokensUserId()); @@ -584,7 +584,7 @@ public void linkAccountSuccessWithPasswordlessEmailAndPhoneNumber() throws Excep Passwordless.updateUser(process.main, user2.getSupertokensUserId(), null, new Passwordless.FieldUpdate("1234")); user2 = AuthRecipe.getUserById(process.main, user2.getSupertokensUserId()); - boolean wasAlreadyLinked = AuthRecipe.linkAccounts(process.main, user2.getSupertokensUserId(), user.getSupertokensUserId()); + boolean wasAlreadyLinked = AuthRecipe.linkAccounts(process.main, user2.getSupertokensUserId(), user.getSupertokensUserId()).wasAlreadyLinked; assert (!wasAlreadyLinked); AuthRecipeUserInfo refetchUser2 = AuthRecipe.getUserById(process.main, user2.getSupertokensUserId()); diff --git a/src/test/java/io/supertokens/test/accountlinking/api/CanLinkAccountsAPITest.java b/src/test/java/io/supertokens/test/accountlinking/api/CanLinkAccountsAPITest.java index e8872f1db..1613ae749 100644 --- a/src/test/java/io/supertokens/test/accountlinking/api/CanLinkAccountsAPITest.java +++ b/src/test/java/io/supertokens/test/accountlinking/api/CanLinkAccountsAPITest.java @@ -378,13 +378,12 @@ public void linkingUserFailsCauseAlreadyLinkedToAnotherAccount() throws Exceptio JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", "http://localhost:3567/recipe/accountlinking/user/link/check", params, 1000, 1000, null, WebserverAPI.getLatestCDIVersion().get(), ""); - assertEquals(4, response.entrySet().size()); + assertEquals(3, response.entrySet().size()); assertEquals("RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR", response.get("status").getAsString()); assertEquals(emailPasswordUser1.getSupertokensUserId(), response.get("primaryUserId").getAsString()); assertEquals("The input recipe user ID is already linked to another user ID", response.get("description").getAsString()); - assertTrue(response.has("user")); } process.kill(); @@ -425,13 +424,12 @@ public void makingPrimaryUserFailsCauseAlreadyLinkedToAnotherAccountWithUserIdMa JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", "http://localhost:3567/recipe/accountlinking/user/link/check", params, 1000, 1000, null, WebserverAPI.getLatestCDIVersion().get(), ""); - assertEquals(4, response.entrySet().size()); + assertEquals(3, response.entrySet().size()); assertEquals("RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR", response.get("status").getAsString()); assertEquals("r1", response.get("primaryUserId").getAsString()); assertEquals("The input recipe user ID is already linked to another user ID", response.get("description").getAsString()); - assertTrue(response.has("user")); } process.kill(); diff --git a/src/test/java/io/supertokens/test/accountlinking/api/GetUserByAccountInfoTest.java b/src/test/java/io/supertokens/test/accountlinking/api/GetUserByAccountInfoTest.java index 8d3d803d7..90f14ca2f 100644 --- a/src/test/java/io/supertokens/test/accountlinking/api/GetUserByAccountInfoTest.java +++ b/src/test/java/io/supertokens/test/accountlinking/api/GetUserByAccountInfoTest.java @@ -435,8 +435,8 @@ public void testWithUserIdMapping() throws Exception { JsonObject primaryUserInfo = getUsersByAccountInfo(process.getProcess(), false, "test@example.com", null, null, null).get(0).getAsJsonObject(); assertEquals("ext1", primaryUserInfo.get("loginMethods").getAsJsonArray().get(0).getAsJsonObject().get("recipeUserId").getAsString()); - assertNotEquals("ext2", primaryUserInfo.get("loginMethods").getAsJsonArray().get(1).getAsJsonObject().get("recipeUserId").getAsString()); // TODO should be equal once userIdMapping is fixed - assertNotEquals("ext3", primaryUserInfo.get("loginMethods").getAsJsonArray().get(2).getAsJsonObject().get("recipeUserId").getAsString()); // TODO should be equal once userIdMapping is fixed + assertEquals("ext2", primaryUserInfo.get("loginMethods").getAsJsonArray().get(1).getAsJsonObject().get("recipeUserId").getAsString()); + assertEquals("ext3", primaryUserInfo.get("loginMethods").getAsJsonArray().get(2).getAsJsonObject().get("recipeUserId").getAsString()); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); diff --git a/src/test/java/io/supertokens/test/accountlinking/api/LinkAccountsAPITest.java b/src/test/java/io/supertokens/test/accountlinking/api/LinkAccountsAPITest.java index a75e3ef9b..39a252ed7 100644 --- a/src/test/java/io/supertokens/test/accountlinking/api/LinkAccountsAPITest.java +++ b/src/test/java/io/supertokens/test/accountlinking/api/LinkAccountsAPITest.java @@ -31,6 +31,7 @@ import io.supertokens.test.httpRequest.HttpResponseException; import io.supertokens.thirdparty.ThirdParty; import io.supertokens.useridmapping.UserIdMapping; +import io.supertokens.utils.SemVer; import io.supertokens.webserver.WebserverAPI; import org.junit.AfterClass; import org.junit.Before; @@ -38,6 +39,9 @@ import org.junit.Test; import org.junit.rules.TestRule; +import java.util.HashMap; +import java.util.Map; + import static org.junit.Assert.*; public class LinkAccountsAPITest { @@ -509,4 +513,103 @@ public void linkReturnsFailsWithoutFeatureEnabled() throws Exception { } } + @Test + public void testUserObjectInLinkAccountsResponse() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "abcd1234"); + + AuthRecipeUserInfo user2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "abcd1234"); + + AuthRecipe.createPrimaryUser(process.main, user2.getSupertokensUserId()); + + { + JsonObject params = new JsonObject(); + params.addProperty("recipeUserId", user.getSupertokensUserId()); + params.addProperty("primaryUserId", user2.getSupertokensUserId()); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/link", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assertEquals(3, response.entrySet().size()); + assertEquals("OK", response.get("status").getAsString()); + assertFalse(response.get("accountsAlreadyLinked").getAsBoolean()); + JsonObject userObj = response.get("user").getAsJsonObject(); + + Map getUserParams = new HashMap<>(); + getUserParams.put("userId", user.getSupertokensUserId()); + JsonObject getUserResponse = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/user/id", getUserParams, 1000, 1000, null, + SemVer.v4_0.get(), ""); + JsonObject userObj2 = response.get("user").getAsJsonObject(); + assertEquals(userObj, userObj2); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void linkingUserFailsCauseAlreadyLinkedToAnotherAccountReturnsUserObject() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + AuthRecipeUserInfo emailPasswordUser1 = EmailPassword.signUp(process.getProcess(), "test@example.com", + "pass1234"); + AuthRecipeUserInfo emailPasswordUser2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", + "pass1234"); + + AuthRecipe.createPrimaryUser(process.main, emailPasswordUser1.getSupertokensUserId()); + AuthRecipe.linkAccounts(process.main, emailPasswordUser2.getSupertokensUserId(), emailPasswordUser1.getSupertokensUserId()); + + AuthRecipeUserInfo emailPasswordUser3 = EmailPassword.signUp(process.getProcess(), "test3@example.com", + "pass1234"); + + AuthRecipe.createPrimaryUser(process.main, emailPasswordUser3.getSupertokensUserId()); + + { + JsonObject params = new JsonObject(); + params.addProperty("recipeUserId", emailPasswordUser2.getSupertokensUserId()); + params.addProperty("primaryUserId", emailPasswordUser3.getSupertokensUserId()); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/link", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + assertEquals(4, response.entrySet().size()); + assertEquals("RECIPE_USER_ID_ALREADY_LINKED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR", + response.get("status").getAsString()); + assertEquals(emailPasswordUser1.getSupertokensUserId(), response.get("primaryUserId").getAsString()); + assertEquals("The input recipe user ID is already linked to another user ID", + response.get("description").getAsString()); + + JsonObject userObj = response.get("user").getAsJsonObject(); + + Map getUserParams = new HashMap<>(); + getUserParams.put("userId", emailPasswordUser1.getSupertokensUserId()); + JsonObject getUserResponse = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/user/id", getUserParams, 1000, 1000, null, + SemVer.v4_0.get(), ""); + JsonObject userObj2 = response.get("user").getAsJsonObject(); + assertEquals(userObj, userObj2); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + } diff --git a/src/test/java/io/supertokens/test/authRecipe/GetUserByIdAPITest.java b/src/test/java/io/supertokens/test/authRecipe/GetUserByIdAPITest.java index 8165c74f4..8d898c1f3 100644 --- a/src/test/java/io/supertokens/test/authRecipe/GetUserByIdAPITest.java +++ b/src/test/java/io/supertokens/test/authRecipe/GetUserByIdAPITest.java @@ -191,7 +191,7 @@ public void getUserSuccessWithUserIdMapping() throws Exception { JsonObject lM = jsonUser.get("loginMethods").getAsJsonArray().get(1).getAsJsonObject(); assertFalse(lM.get("verified").getAsBoolean()); assertEquals(lM.get("timeJoined").getAsLong(), user2.timeJoined); - assertEquals(lM.get("recipeUserId").getAsString(), user2.getSupertokensUserId()); + assertEquals(lM.get("recipeUserId").getAsString(), "e2"); assertEquals(lM.get("recipeId").getAsString(), "emailpassword"); assertEquals(lM.get("email").getAsString(), "test2@example.com"); assert (lM.entrySet().size() == 6); From 3ce68da509bc7b679073f365ec02b60cba7defa1 Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Sat, 26 Aug 2023 18:12:33 +0530 Subject: [PATCH 096/131] fix: remove UserInfo class (#772) * fix: remove UserInfo class * fix: remove getRecipeId * fix: uncomment test --- .../emailpassword/EmailPassword.java | 13 ++-- .../UserPaginationContainer.java | 32 ---------- .../java/io/supertokens/inmemorydb/Start.java | 7 +- .../queries/EmailPasswordQueries.java | 10 +-- .../queries/PasswordlessQueries.java | 10 +-- .../inmemorydb/queries/ThirdPartyQueries.java | 8 +-- .../passwordless/Passwordless.java | 7 +- .../io/supertokens/thirdparty/ThirdParty.java | 9 ++- .../thirdparty/UserPaginationContainer.java | 32 ---------- .../api/emailpassword/SignUpAPI.java | 4 +- .../test/AuthRecipeAPITest2_10.java | 4 +- .../io/supertokens/test/AuthRecipeTest.java | 25 ++++---- .../authRecipe/AuthRecipeStorageTest.java | 4 +- .../DeleteUserAPIWithUserIdMappingTest.java | 14 ++-- .../GetUsersAPIWithUserIdMappingTest.java | 6 +- .../test/authRecipe/MultitenantAPITest.java | 6 +- .../test/authRecipe/UserPaginationTest.java | 4 +- ...ExpiredPasswordResetTokensCronjobTest.java | 4 +- .../test/emailpassword/EmailPasswordTest.java | 39 ++++++----- .../MultitenantEmailPasswordTest.java | 25 ++++---- .../emailpassword/PasswordHashingTest.java | 9 ++- .../UpdateUsersEmailAndPasswordTest.java | 13 ++-- .../test/emailpassword/UserMigrationTest.java | 5 +- .../ImportUserWithPasswordHashAPITest.java | 3 +- .../emailpassword/api/UserPutAPITest2_8.java | 14 ++-- .../emailpassword/api/UserPutAPITest4_0.java | 6 +- ...redEmailVerificationTokensCronjobTest.java | 16 ++--- .../EmailVerificationTest.java | 64 +++++++++---------- .../test/multitenant/AppTenantUserTest.java | 10 +-- .../test/multitenant/TestAppData.java | 6 +- .../TestTenantIdIsNotPresentForOlderCDI.java | 4 +- .../PasswordlessConsumeCodeTest.java | 5 +- .../passwordless/PasswordlessGetUserTest.java | 17 +++-- .../test/thirdparty/ThirdPartyTest.java | 9 ++- .../test/thirdparty/ThirdPartyTest2_7.java | 9 ++- .../api/ThirdPartySignInUpAPITest4_0.java | 4 +- .../test/totp/api/TotpUserIdMappingTest.java | 4 +- .../UserIdMappingStorageTest.java | 24 +++---- .../test/userIdMapping/UserIdMappingTest.java | 48 +++++++------- .../api/CreateUserIdMappingAPITest.java | 12 ++-- .../api/GetUserIdMappingAPITest.java | 8 +-- .../api/RemoveUserIdMappingAPITest.java | 8 +-- .../api/UpdateExternalUserIdInfoTest.java | 8 +-- .../recipe/EmailPasswordAPITest.java | 9 ++- .../test/userRoles/UserRolesTest.java | 4 +- 45 files changed, 251 insertions(+), 331 deletions(-) delete mode 100644 src/main/java/io/supertokens/emailpassword/UserPaginationContainer.java delete mode 100644 src/main/java/io/supertokens/thirdparty/UserPaginationContainer.java diff --git a/src/main/java/io/supertokens/emailpassword/EmailPassword.java b/src/main/java/io/supertokens/emailpassword/EmailPassword.java index 708d448fc..60141104f 100644 --- a/src/main/java/io/supertokens/emailpassword/EmailPassword.java +++ b/src/main/java/io/supertokens/emailpassword/EmailPassword.java @@ -32,7 +32,6 @@ import io.supertokens.pluginInterface.authRecipe.LoginMethod; import io.supertokens.pluginInterface.authRecipe.sqlStorage.AuthRecipeSQLStorage; import io.supertokens.pluginInterface.emailpassword.PasswordResetTokenInfo; -import io.supertokens.pluginInterface.emailpassword.UserInfo; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicatePasswordResetTokenException; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateUserIdException; @@ -83,7 +82,7 @@ private static long getPasswordResetTokenLifetime(TenantIdentifier tenantIdentif } @TestOnly - public static UserInfo signUp(Main main, @Nonnull String email, @Nonnull String password) + public static AuthRecipeUserInfo signUp(Main main, @Nonnull String email, @Nonnull String password) throws DuplicateEmailException, StorageQueryException { try { Storage storage = StorageLayer.getStorage(main); @@ -94,7 +93,7 @@ public static UserInfo signUp(Main main, @Nonnull String email, @Nonnull String } } - public static UserInfo signUp(TenantIdentifierWithStorage tenantIdentifierWithStorage, Main main, + public static AuthRecipeUserInfo signUp(TenantIdentifierWithStorage tenantIdentifierWithStorage, Main main, @Nonnull String email, @Nonnull String password) throws DuplicateEmailException, StorageQueryException, TenantOrAppNotFoundException, BadPermissionException { @@ -167,7 +166,7 @@ public static ImportUserResponse importUserWithPasswordHash(TenantIdentifierWith EmailPasswordSQLStorage storage = tenantIdentifierWithStorage.getEmailPasswordStorage(); try { - UserInfo userInfo = storage.signUp(tenantIdentifierWithStorage, userId, email, passwordHash, + AuthRecipeUserInfo userInfo = storage.signUp(tenantIdentifierWithStorage, userId, email, passwordHash, timeJoined); return new ImportUserResponse(false, userInfo); } catch (DuplicateUserIdException e) { @@ -663,7 +662,7 @@ public static void updateUsersEmailOrPassword(AppIdentifierWithStorage appIdenti @Deprecated @TestOnly - public static UserInfo getUserUsingId(Main main, String userId) + public static AuthRecipeUserInfo getUserUsingId(Main main, String userId) throws StorageQueryException { try { Storage storage = StorageLayer.getStorage(main); @@ -674,7 +673,7 @@ public static UserInfo getUserUsingId(Main main, String userId) } @Deprecated - public static UserInfo getUserUsingId(AppIdentifierWithStorage appIdentifierWithStorage, String userId) + public static AuthRecipeUserInfo getUserUsingId(AppIdentifierWithStorage appIdentifierWithStorage, String userId) throws StorageQueryException, TenantOrAppNotFoundException { AuthRecipeUserInfo result = appIdentifierWithStorage.getAuthRecipeStorage() .getPrimaryUserById(appIdentifierWithStorage, userId); @@ -683,7 +682,7 @@ public static UserInfo getUserUsingId(AppIdentifierWithStorage appIdentifierWith } for (LoginMethod lM : result.loginMethods) { if (lM.getSupertokensUserId().equals(userId)) { - return new UserInfo(lM.getSupertokensUserId(), result.isPrimaryUser, lM); + return AuthRecipeUserInfo.create(lM.getSupertokensUserId(), result.isPrimaryUser, lM); } } return null; diff --git a/src/main/java/io/supertokens/emailpassword/UserPaginationContainer.java b/src/main/java/io/supertokens/emailpassword/UserPaginationContainer.java deleted file mode 100644 index fa112e2dc..000000000 --- a/src/main/java/io/supertokens/emailpassword/UserPaginationContainer.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. - * - * This software is licensed under the Apache License, Version 2.0 (the - * "License") as published by the Apache Software Foundation. - * - * You may not use this file except in compliance with the License. You may - * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - */ - -package io.supertokens.emailpassword; - -import io.supertokens.pluginInterface.emailpassword.UserInfo; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -public class UserPaginationContainer { - public final UserInfo[] users; - public final String nextPaginationToken; - - public UserPaginationContainer(@Nonnull UserInfo[] users, @Nullable String nextPaginationToken) { - this.users = users; - this.nextPaginationToken = nextPaginationToken; - } -} diff --git a/src/main/java/io/supertokens/inmemorydb/Start.java b/src/main/java/io/supertokens/inmemorydb/Start.java index 3a8f95536..0241be936 100644 --- a/src/main/java/io/supertokens/inmemorydb/Start.java +++ b/src/main/java/io/supertokens/inmemorydb/Start.java @@ -32,7 +32,6 @@ import io.supertokens.pluginInterface.dashboard.exceptions.UserIdNotFoundException; import io.supertokens.pluginInterface.dashboard.sqlStorage.DashboardSQLStorage; import io.supertokens.pluginInterface.emailpassword.PasswordResetTokenInfo; -import io.supertokens.pluginInterface.emailpassword.UserInfo; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicatePasswordResetTokenException; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateUserIdException; @@ -725,7 +724,7 @@ public String[] getProtectedConfigsFromSuperTokensSaaSUsers() { } @Override - public UserInfo signUp(TenantIdentifier tenantIdentifier, String id, String email, String passwordHash, + public AuthRecipeUserInfo signUp(TenantIdentifier tenantIdentifier, String id, String email, String passwordHash, long timeJoined) throws StorageQueryException, DuplicateUserIdException, DuplicateEmailException, TenantOrAppNotFoundException { @@ -1074,7 +1073,7 @@ public void deleteThirdPartyUser_Transaction(TransactionConnection con, AppIdent } @Override - public io.supertokens.pluginInterface.thirdparty.UserInfo signUp( + public AuthRecipeUserInfo signUp( TenantIdentifier tenantIdentifier, String id, String email, LoginMethod.ThirdParty thirdParty, long timeJoined) throws StorageQueryException, io.supertokens.pluginInterface.thirdparty.exception.DuplicateUserIdException, @@ -1618,7 +1617,7 @@ public void createCode(TenantIdentifier tenantIdentifier, PasswordlessCode code) } @Override - public io.supertokens.pluginInterface.passwordless.UserInfo createUser(TenantIdentifier tenantIdentifier, + public AuthRecipeUserInfo createUser(TenantIdentifier tenantIdentifier, String id, @javax.annotation.Nullable String email, @javax.annotation.Nullable diff --git a/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java index 3069a60fb..ee8db83fc 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java @@ -21,9 +21,9 @@ import io.supertokens.inmemorydb.Utils; import io.supertokens.inmemorydb.config.Config; import io.supertokens.pluginInterface.RowMapper; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.authRecipe.LoginMethod; import io.supertokens.pluginInterface.emailpassword.PasswordResetTokenInfo; -import io.supertokens.pluginInterface.emailpassword.UserInfo; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; @@ -228,8 +228,8 @@ public static void addPasswordResetToken(Start start, AppIdentifier appIdentifie }); } - public static UserInfo signUp(Start start, TenantIdentifier tenantIdentifier, String userId, String email, - String passwordHash, long timeJoined) + public static AuthRecipeUserInfo signUp(Start start, TenantIdentifier tenantIdentifier, String userId, String email, + String passwordHash, long timeJoined) throws StorageQueryException, StorageTransactionLogicException { return start.startTransaction(con -> { Connection sqlCon = (Connection) con.getConnection(); @@ -287,7 +287,7 @@ public static UserInfo signUp(Start start, TenantIdentifier tenantIdentifier, St fillUserInfoWithTenantIds_transaction(start, sqlCon, tenantIdentifier.toAppIdentifier(), userInfo); fillUserInfoWithVerified_transaction(start, sqlCon, tenantIdentifier.toAppIdentifier(), userInfo); sqlCon.commit(); - return new UserInfo(userId, false, userInfo.toLoginMethod()); + return AuthRecipeUserInfo.create(userId, false, userInfo.toLoginMethod()); } catch (SQLException throwables) { throw new StorageTransactionLogicException(throwables); } @@ -511,7 +511,7 @@ private static List fillUserInfoWithTenantIds_transaction(Start Map> tenantIdsForUserIds = GeneralQueries.getTenantIdsForUserIds_transaction(start, sqlCon, appIdentifier, userIds); - List result = new ArrayList<>(); + List result = new ArrayList<>(); for (UserInfoPartial userInfo : userInfos) { userInfo.tenantIds = tenantIdsForUserIds.get(userInfo.id).toArray(new String[0]); } diff --git a/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java index 0e51af72b..31736e30e 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java @@ -22,6 +22,7 @@ import io.supertokens.inmemorydb.Utils; import io.supertokens.inmemorydb.config.Config; import io.supertokens.pluginInterface.RowMapper; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.authRecipe.LoginMethod; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; @@ -29,7 +30,6 @@ import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.passwordless.PasswordlessCode; import io.supertokens.pluginInterface.passwordless.PasswordlessDevice; -import io.supertokens.pluginInterface.passwordless.UserInfo; import io.supertokens.pluginInterface.sqlStorage.SQLStorage.TransactionIsolationLevel; import javax.annotation.Nonnull; @@ -365,8 +365,8 @@ public static void deleteCode_Transaction(Start start, Connection con, TenantIde }); } - public static UserInfo createUser(Start start, TenantIdentifier tenantIdentifier, String id, @Nullable String email, - @Nullable String phoneNumber, long timeJoined) + public static AuthRecipeUserInfo createUser(Start start, TenantIdentifier tenantIdentifier, String id, @Nullable String email, + @Nullable String phoneNumber, long timeJoined) throws StorageTransactionLogicException, StorageQueryException { return start.startTransaction(con -> { Connection sqlCon = (Connection) con.getConnection(); @@ -424,7 +424,7 @@ public static UserInfo createUser(Start start, TenantIdentifier tenantIdentifier fillUserInfoWithTenantIds_transaction(start, sqlCon, tenantIdentifier.toAppIdentifier(), userInfo); fillUserInfoWithVerified_transaction(start, sqlCon, tenantIdentifier.toAppIdentifier(), userInfo); sqlCon.commit(); - return new UserInfo(id, false, + return AuthRecipeUserInfo.create(id, false, userInfo.toLoginMethod()); } catch (SQLException throwables) { throw new StorageTransactionLogicException(throwables); @@ -931,7 +931,7 @@ private static List fillUserInfoWithTenantIds_transaction(Start Map> tenantIdsForUserIds = GeneralQueries.getTenantIdsForUserIds_transaction(start, sqlCon, appIdentifier, userIds); - List result = new ArrayList<>(); + List result = new ArrayList<>(); for (UserInfoPartial userInfo : userInfos) { userInfo.tenantIds = tenantIdsForUserIds.get(userInfo.id).toArray(new String[0]); } diff --git a/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java index 9e94b4814..27a691ce9 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java @@ -21,12 +21,12 @@ import io.supertokens.inmemorydb.Utils; import io.supertokens.inmemorydb.config.Config; import io.supertokens.pluginInterface.RowMapper; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.authRecipe.LoginMethod; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; -import io.supertokens.pluginInterface.thirdparty.UserInfo; import java.sql.Connection; import java.sql.ResultSet; @@ -83,8 +83,8 @@ static String getQueryToCreateThirdPartyUserToTenantTable(Start start) { // @formatter:on } - public static UserInfo signUp(Start start, TenantIdentifier tenantIdentifier, String id, String email, - LoginMethod.ThirdParty thirdParty, long timeJoined) + public static AuthRecipeUserInfo signUp(Start start, TenantIdentifier tenantIdentifier, String id, String email, + LoginMethod.ThirdParty thirdParty, long timeJoined) throws StorageQueryException, StorageTransactionLogicException { return start.startTransaction(con -> { Connection sqlCon = (Connection) con.getConnection(); @@ -144,7 +144,7 @@ public static UserInfo signUp(Start start, TenantIdentifier tenantIdentifier, St fillUserInfoWithTenantIds_transaction(start, sqlCon, tenantIdentifier.toAppIdentifier(), userInfo); fillUserInfoWithVerified_transaction(start, sqlCon, tenantIdentifier.toAppIdentifier(), userInfo); sqlCon.commit(); - return new UserInfo(id, false, userInfo.toLoginMethod()); + return AuthRecipeUserInfo.create(id, false, userInfo.toLoginMethod()); } catch (SQLException throwables) { throw new StorageTransactionLogicException(throwables); diff --git a/src/main/java/io/supertokens/passwordless/Passwordless.java b/src/main/java/io/supertokens/passwordless/Passwordless.java index 610d52d7d..f0c1da86a 100644 --- a/src/main/java/io/supertokens/passwordless/Passwordless.java +++ b/src/main/java/io/supertokens/passwordless/Passwordless.java @@ -40,7 +40,6 @@ import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.passwordless.PasswordlessCode; import io.supertokens.pluginInterface.passwordless.PasswordlessDevice; -import io.supertokens.pluginInterface.passwordless.UserInfo; import io.supertokens.pluginInterface.passwordless.exception.*; import io.supertokens.pluginInterface.passwordless.sqlStorage.PasswordlessSQLStorage; import io.supertokens.storageLayer.StorageLayer; @@ -529,7 +528,7 @@ public static void removeCodesByPhoneNumber(TenantIdentifierWithStorage tenantId @TestOnly @Deprecated - public static UserInfo getUserById(Main main, String userId) + public static AuthRecipeUserInfo getUserById(Main main, String userId) throws StorageQueryException { Storage storage = StorageLayer.getStorage(main); return getUserById( @@ -537,7 +536,7 @@ public static UserInfo getUserById(Main main, String userId) } @Deprecated - public static UserInfo getUserById(AppIdentifierWithStorage appIdentifierWithStorage, String userId) + public static AuthRecipeUserInfo getUserById(AppIdentifierWithStorage appIdentifierWithStorage, String userId) throws StorageQueryException { AuthRecipeUserInfo result = appIdentifierWithStorage.getAuthRecipeStorage() .getPrimaryUserById(appIdentifierWithStorage, userId); @@ -546,7 +545,7 @@ public static UserInfo getUserById(AppIdentifierWithStorage appIdentifierWithSto } for (LoginMethod lM : result.loginMethods) { if (lM.getSupertokensUserId().equals(userId)) { - return new io.supertokens.pluginInterface.passwordless.UserInfo(lM.getSupertokensUserId(), result.isPrimaryUser, + return AuthRecipeUserInfo.create(lM.getSupertokensUserId(), result.isPrimaryUser, lM); } } diff --git a/src/main/java/io/supertokens/thirdparty/ThirdParty.java b/src/main/java/io/supertokens/thirdparty/ThirdParty.java index d4e0fa718..24d3826c7 100644 --- a/src/main/java/io/supertokens/thirdparty/ThirdParty.java +++ b/src/main/java/io/supertokens/thirdparty/ThirdParty.java @@ -29,7 +29,6 @@ import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; import io.supertokens.pluginInterface.multitenancy.*; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; -import io.supertokens.pluginInterface.thirdparty.UserInfo; import io.supertokens.pluginInterface.thirdparty.exception.DuplicateThirdPartyUserException; import io.supertokens.pluginInterface.thirdparty.exception.DuplicateUserIdException; import io.supertokens.pluginInterface.thirdparty.sqlStorage.ThirdPartySQLStorage; @@ -158,7 +157,7 @@ private static SignInUpResponse signInUpHelper(TenantIdentifierWithStorage tenan long timeJoined = System.currentTimeMillis(); try { - UserInfo createdUser = storage.signUp(tenantIdentifierWithStorage, userId, email, + AuthRecipeUserInfo createdUser = storage.signUp(tenantIdentifierWithStorage, userId, email, new LoginMethod.ThirdParty(thirdPartyId, thirdPartyUserId), timeJoined); return new SignInUpResponse(true, createdUser); @@ -249,7 +248,7 @@ private static SignInUpResponse signInUpHelper(TenantIdentifierWithStorage tenan } @Deprecated - public static UserInfo getUser(AppIdentifierWithStorage appIdentifierWithStorage, String userId) + public static AuthRecipeUserInfo getUser(AppIdentifierWithStorage appIdentifierWithStorage, String userId) throws StorageQueryException { AuthRecipeUserInfo result = appIdentifierWithStorage.getAuthRecipeStorage() .getPrimaryUserById(appIdentifierWithStorage, userId); @@ -258,7 +257,7 @@ public static UserInfo getUser(AppIdentifierWithStorage appIdentifierWithStorage } for (LoginMethod lM : result.loginMethods) { if (lM.getSupertokensUserId().equals(userId)) { - return new io.supertokens.pluginInterface.thirdparty.UserInfo(lM.getSupertokensUserId(), result.isPrimaryUser, + return AuthRecipeUserInfo.create(lM.getSupertokensUserId(), result.isPrimaryUser, lM); } } @@ -267,7 +266,7 @@ public static UserInfo getUser(AppIdentifierWithStorage appIdentifierWithStorage @Deprecated @TestOnly - public static UserInfo getUser(Main main, String userId) throws StorageQueryException { + public static AuthRecipeUserInfo getUser(Main main, String userId) throws StorageQueryException { Storage storage = StorageLayer.getStorage(main); return getUser(new AppIdentifierWithStorage(null, null, storage), userId); } diff --git a/src/main/java/io/supertokens/thirdparty/UserPaginationContainer.java b/src/main/java/io/supertokens/thirdparty/UserPaginationContainer.java deleted file mode 100644 index cf361ea96..000000000 --- a/src/main/java/io/supertokens/thirdparty/UserPaginationContainer.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. - * - * This software is licensed under the Apache License, Version 2.0 (the - * "License") as published by the Apache Software Foundation. - * - * You may not use this file except in compliance with the License. You may - * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - */ - -package io.supertokens.thirdparty; - -import io.supertokens.pluginInterface.thirdparty.UserInfo; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -public class UserPaginationContainer { - public final UserInfo[] users; - public final String nextPaginationToken; - - public UserPaginationContainer(@Nonnull UserInfo[] users, @Nullable String nextPaginationToken) { - this.users = users; - this.nextPaginationToken = nextPaginationToken; - } -} diff --git a/src/main/java/io/supertokens/webserver/api/emailpassword/SignUpAPI.java b/src/main/java/io/supertokens/webserver/api/emailpassword/SignUpAPI.java index e7fc4556b..bb915fadb 100644 --- a/src/main/java/io/supertokens/webserver/api/emailpassword/SignUpAPI.java +++ b/src/main/java/io/supertokens/webserver/api/emailpassword/SignUpAPI.java @@ -23,7 +23,7 @@ import io.supertokens.multitenancy.exception.BadPermissionException; import io.supertokens.output.Logging; import io.supertokens.pluginInterface.RECIPE_ID; -import io.supertokens.pluginInterface.emailpassword.UserInfo; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; @@ -78,7 +78,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I try { TenantIdentifierWithStorage tenant = this.getTenantIdentifierWithStorageFromRequest(req); - UserInfo user = EmailPassword.signUp(tenant, super.main, normalisedEmail, password); + AuthRecipeUserInfo user = EmailPassword.signUp(tenant, super.main, normalisedEmail, password); ActiveUsers.updateLastActive(this.getAppIdentifierWithStorage(req), main, user.getSupertokensUserId()); diff --git a/src/test/java/io/supertokens/test/AuthRecipeAPITest2_10.java b/src/test/java/io/supertokens/test/AuthRecipeAPITest2_10.java index 28940cd85..e8209e53d 100644 --- a/src/test/java/io/supertokens/test/AuthRecipeAPITest2_10.java +++ b/src/test/java/io/supertokens/test/AuthRecipeAPITest2_10.java @@ -20,7 +20,7 @@ import io.supertokens.ProcessState; import io.supertokens.emailpassword.EmailPassword; import io.supertokens.pluginInterface.STORAGE_TYPE; -import io.supertokens.pluginInterface.emailpassword.UserInfo; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.httpRequest.HttpRequestForTesting; import io.supertokens.test.httpRequest.HttpResponseException; @@ -67,7 +67,7 @@ public void deleteUser() throws Exception { assertEquals(response.entrySet().size(), 1); } - UserInfo user = EmailPassword.signUp(process.getProcess(), "test0@example.com", "password0"); + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test0@example.com", "password0"); { JsonObject requestBody = new JsonObject(); diff --git a/src/test/java/io/supertokens/test/AuthRecipeTest.java b/src/test/java/io/supertokens/test/AuthRecipeTest.java index f51640f3c..def36c276 100644 --- a/src/test/java/io/supertokens/test/AuthRecipeTest.java +++ b/src/test/java/io/supertokens/test/AuthRecipeTest.java @@ -28,7 +28,6 @@ import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.STORAGE_TYPE; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; -import io.supertokens.pluginInterface.emailpassword.UserInfo; import io.supertokens.session.Session; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.thirdparty.ThirdParty; @@ -199,10 +198,10 @@ public void paginationTest() throws Exception { assert (users.users.length == 0); } - UserInfo user1 = EmailPassword.signUp(process.getProcess(), "test0@example.com", "password0"); - UserInfo user2 = EmailPassword.signUp(process.getProcess(), "test1@example.com", "password1"); - UserInfo user3 = EmailPassword.signUp(process.getProcess(), "test20@example.com", "password2"); - UserInfo user4 = EmailPassword.signUp(process.getProcess(), "test3@example.com", "password3"); + AuthRecipeUserInfo user1 = EmailPassword.signUp(process.getProcess(), "test0@example.com", "password0"); + AuthRecipeUserInfo user2 = EmailPassword.signUp(process.getProcess(), "test1@example.com", "password1"); + AuthRecipeUserInfo user3 = EmailPassword.signUp(process.getProcess(), "test20@example.com", "password2"); + AuthRecipeUserInfo user4 = EmailPassword.signUp(process.getProcess(), "test3@example.com", "password3"); { UserPaginationContainer users = AuthRecipe.getUsers(process.getProcess(), 100, "ASC", null, @@ -520,7 +519,7 @@ public void randomPaginationTest() throws Exception { AuthRecipeUserInfo expected = usersCreated.get(indexIntoUsers); AuthRecipeUserInfo actualUser = uc; - assert (actualUser.equals(expected) && uc.loginMethods[0].recipeId.toString().equals(expected.getRecipeId().toString())); + assert (actualUser.equals(expected) && uc.loginMethods[0].recipeId.toString().equals(expected.loginMethods[0].recipeId.toString())); indexIntoUsers++; } @@ -559,7 +558,7 @@ public void randomPaginationTest() throws Exception { AuthRecipeUserInfo expected = usersCreated.get(indexIntoUsers); AuthRecipeUserInfo actualUser = uc; - assert (actualUser.equals(expected) && uc.loginMethods[0].recipeId.toString().equals(expected.getRecipeId().toString())); + assert (actualUser.equals(expected) && uc.loginMethods[0].recipeId.toString().equals(expected.loginMethods[0].recipeId.toString())); indexIntoUsers--; } @@ -610,16 +609,16 @@ public void deleteUserTest() throws Exception { String emailVerificationToken2 = EmailVerification.generateEmailVerificationToken(process.getProcess(), user2.getSupertokensUserId(), "email"); - assertEquals(2, AuthRecipe.getUsersCount(process.getProcess(), new RECIPE_ID[]{user1.getRecipeId()})); + assertEquals(2, AuthRecipe.getUsersCount(process.getProcess(), new RECIPE_ID[]{user1.loginMethods[0].recipeId})); AuthRecipe.deleteUser(process.getProcess(), user1.getSupertokensUserId()); - assertEquals(1, AuthRecipe.getUsersCount(process.getProcess(), new RECIPE_ID[]{user1.getRecipeId()})); + assertEquals(1, AuthRecipe.getUsersCount(process.getProcess(), new RECIPE_ID[]{user1.loginMethods[0].recipeId})); assertEquals(0, Session.getAllNonExpiredSessionHandlesForUser(process.getProcess(), user1.getSupertokensUserId()).length); assertEquals(1, Session.getAllNonExpiredSessionHandlesForUser(process.getProcess(), user2.getSupertokensUserId()).length); assertFalse(EmailVerification.isEmailVerified(process.getProcess(), user1.getSupertokensUserId(), "email")); assertEquals(0, UserMetadata.getUserMetadata(process.getProcess(), user1.getSupertokensUserId()).entrySet().size()); AuthRecipe.deleteUser(process.getProcess(), user2.getSupertokensUserId()); - assertEquals(0, AuthRecipe.getUsersCount(process.getProcess(), new RECIPE_ID[]{user1.getRecipeId()})); + assertEquals(0, AuthRecipe.getUsersCount(process.getProcess(), new RECIPE_ID[]{user1.loginMethods[0].recipeId})); assertEquals(0, Session.getAllNonExpiredSessionHandlesForUser(process.getProcess(), user2.getSupertokensUserId()).length); assertEquals(0, UserMetadata.getUserMetadata(process.getProcess(), user2.getSupertokensUserId()).entrySet().size()); @@ -648,7 +647,7 @@ private static List getUserInfoClassNameList() { AtomicInteger count = new AtomicInteger(); Map> signUpMap = new HashMap<>(); - signUpMap.put("io.supertokens.pluginInterface.emailpassword.UserInfo", o -> { + signUpMap.put("io.supertokens.pluginInterface.emailpassword.AuthRecipeUserInfo", o -> { try { return EmailPassword.signUp(process.getProcess(), "test" + count.getAndIncrement() + "@example.com", "password0"); @@ -656,7 +655,7 @@ private static List getUserInfoClassNameList() { } return null; }); - signUpMap.put("io.supertokens.pluginInterface.thirdparty.UserInfo", o -> { + signUpMap.put("io.supertokens.pluginInterface.thirdparty.AuthRecipeUserInfo", o -> { try { String thirdPartyId = "testThirdParty"; String thirdPartyUserId = "thirdPartyUserId" + count.getAndIncrement(); @@ -667,7 +666,7 @@ private static List getUserInfoClassNameList() { } return null; }); - signUpMap.put("io.supertokens.pluginInterface.passwordless.UserInfo", o -> { + signUpMap.put("io.supertokens.pluginInterface.passwordless.AuthRecipeUserInfo", o -> { try { String email = "test" + count.getAndIncrement() + "@example.com"; CreateCodeResponse createCode = Passwordless.createCode(process.getProcess(), email, null, null, null); diff --git a/src/test/java/io/supertokens/test/authRecipe/AuthRecipeStorageTest.java b/src/test/java/io/supertokens/test/authRecipe/AuthRecipeStorageTest.java index 36921f56b..0443dcbbb 100644 --- a/src/test/java/io/supertokens/test/authRecipe/AuthRecipeStorageTest.java +++ b/src/test/java/io/supertokens/test/authRecipe/AuthRecipeStorageTest.java @@ -20,7 +20,7 @@ import io.supertokens.emailpassword.EmailPassword; import io.supertokens.pluginInterface.STORAGE_TYPE; import io.supertokens.pluginInterface.authRecipe.AuthRecipeStorage; -import io.supertokens.pluginInterface.emailpassword.UserInfo; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.TestingProcessManager; @@ -64,7 +64,7 @@ public void testIfAUserIdIsASuperTokensUserId() throws Exception { assertFalse(storage.doesUserIdExist(new TenantIdentifier(null, null, null), "unknownUser")); // create a user and check that the userId exists - UserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); + AuthRecipeUserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); assertTrue(storage.doesUserIdExist(new TenantIdentifier(null, null, null), userInfo.getSupertokensUserId())); process.kill(); diff --git a/src/test/java/io/supertokens/test/authRecipe/DeleteUserAPIWithUserIdMappingTest.java b/src/test/java/io/supertokens/test/authRecipe/DeleteUserAPIWithUserIdMappingTest.java index ba6c763fd..aa5d0e538 100644 --- a/src/test/java/io/supertokens/test/authRecipe/DeleteUserAPIWithUserIdMappingTest.java +++ b/src/test/java/io/supertokens/test/authRecipe/DeleteUserAPIWithUserIdMappingTest.java @@ -20,7 +20,7 @@ import io.supertokens.ProcessState; import io.supertokens.emailpassword.EmailPassword; import io.supertokens.pluginInterface.STORAGE_TYPE; -import io.supertokens.pluginInterface.emailpassword.UserInfo; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.TestingProcessManager; import io.supertokens.test.Utils; @@ -65,7 +65,7 @@ public void createAUserMapTheirIdCreateMetadataWithExternalIdAndDelete() throws // deleting with superTokensUserId { // create User - UserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); + AuthRecipeUserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); String superTokensUserId = userInfo.getSupertokensUserId(); String externalId = "externalId"; @@ -87,7 +87,7 @@ public void createAUserMapTheirIdCreateMetadataWithExternalIdAndDelete() throws // check that user doesnt exist { - UserInfo response = EmailPassword.getUserUsingId(process.main, superTokensUserId); + AuthRecipeUserInfo response = EmailPassword.getUserUsingId(process.main, superTokensUserId); assertNull(response); } @@ -122,7 +122,7 @@ public void testDeleteUserBehaviorInIntermediateStateWithUser_1sUserId() throws } // create an EmailPassword User - UserInfo userInfo_1 = EmailPassword.signUp(process.main, "test@example.com", "testPassword123"); + AuthRecipeUserInfo userInfo_1 = EmailPassword.signUp(process.main, "test@example.com", "testPassword123"); // associate some data with user JsonObject data = new JsonObject(); @@ -150,7 +150,7 @@ public void testDeleteUserBehaviorInIntermediateStateWithUser_1sUserId() throws // check that only auth tables for EmailPassword user have been deleted and the userMetadata table entries still // exist { - UserInfo epUser = EmailPassword.getUserUsingId(process.main, userInfo_1.getSupertokensUserId()); + AuthRecipeUserInfo epUser = EmailPassword.getUserUsingId(process.main, userInfo_1.getSupertokensUserId()); assertNull(epUser); JsonObject epUserMetadata = UserMetadata.getUserMetadata(process.main, userInfo_1.getSupertokensUserId()); @@ -182,7 +182,7 @@ public void testDeleteUserBehaviorInIntermediateStateWithUser_2sUserId() throws } // create an EmailPassword User - UserInfo userInfo_1 = EmailPassword.signUp(process.main, "test@example.com", "testPassword123"); + AuthRecipeUserInfo userInfo_1 = EmailPassword.signUp(process.main, "test@example.com", "testPassword123"); // associate some data with user JsonObject data = new JsonObject(); @@ -210,7 +210,7 @@ public void testDeleteUserBehaviorInIntermediateStateWithUser_2sUserId() throws // check that only auth tables for thirdParty user have been deleted and the userMetadata table entries still // exist { - io.supertokens.pluginInterface.thirdparty.UserInfo tpUserInfo = ThirdParty.getUser(process.main, + AuthRecipeUserInfo tpUserInfo = ThirdParty.getUser(process.main, userInfo_2.user.getSupertokensUserId()); assertNull(tpUserInfo); diff --git a/src/test/java/io/supertokens/test/authRecipe/GetUsersAPIWithUserIdMappingTest.java b/src/test/java/io/supertokens/test/authRecipe/GetUsersAPIWithUserIdMappingTest.java index a03dedb3c..eca8e44a5 100644 --- a/src/test/java/io/supertokens/test/authRecipe/GetUsersAPIWithUserIdMappingTest.java +++ b/src/test/java/io/supertokens/test/authRecipe/GetUsersAPIWithUserIdMappingTest.java @@ -21,7 +21,7 @@ import io.supertokens.ProcessState; import io.supertokens.emailpassword.EmailPassword; import io.supertokens.pluginInterface.STORAGE_TYPE; -import io.supertokens.pluginInterface.emailpassword.UserInfo; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.useridmapping.UserIdMappingStorage; import io.supertokens.storageLayer.StorageLayer; @@ -70,7 +70,7 @@ public void createMultipleUsersAndMapTheirIdsRetrieveAllUsersAndCheckThatExterna for (int i = 1; i <= 10; i++) { // create User - UserInfo userInfo = EmailPassword.signUp(process.main, "test" + i + "@example.com", "testPass123"); + AuthRecipeUserInfo userInfo = EmailPassword.signUp(process.main, "test" + i + "@example.com", "testPass123"); String superTokensUserId = userInfo.getSupertokensUserId(); String externalUserId = "externalId" + i; externalUserIdList.add(externalUserId); @@ -110,7 +110,7 @@ public void createMultipleUsersAndMapTheirIdsRetrieveUsersUsingPaginationTokenAn for (int i = 1; i <= 20; i++) { // create User - UserInfo userInfo = EmailPassword.signUp(process.main, "test" + i + "@example.com", "testPass123"); + AuthRecipeUserInfo userInfo = EmailPassword.signUp(process.main, "test" + i + "@example.com", "testPass123"); String superTokensUserId = userInfo.getSupertokensUserId(); String externalUserId = "externalId" + i; externalUserIdList.add(externalUserId); diff --git a/src/test/java/io/supertokens/test/authRecipe/MultitenantAPITest.java b/src/test/java/io/supertokens/test/authRecipe/MultitenantAPITest.java index 1e0318a9b..c2e58abfd 100644 --- a/src/test/java/io/supertokens/test/authRecipe/MultitenantAPITest.java +++ b/src/test/java/io/supertokens/test/authRecipe/MultitenantAPITest.java @@ -31,7 +31,7 @@ import io.supertokens.passwordless.Passwordless; import io.supertokens.passwordless.exceptions.*; import io.supertokens.pluginInterface.STORAGE_TYPE; -import io.supertokens.pluginInterface.emailpassword.UserInfo; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; import io.supertokens.pluginInterface.exceptions.InvalidConfigException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; @@ -189,7 +189,7 @@ private void createUsers() tenantToUsers.put(tenant, new ArrayList<>()); } - UserInfo user1 = EmailPassword.signUp( + AuthRecipeUserInfo user1 = EmailPassword.signUp( tenant.withStorage(StorageLayer.getStorage(tenant, process.getProcess())), process.getProcess(), "user@example.com", @@ -197,7 +197,7 @@ private void createUsers() ); tenantToUsers.get(tenant).add(user1.getSupertokensUserId()); recipeToUsers.get("emailpassword").add(user1.getSupertokensUserId()); - UserInfo user2 = EmailPassword.signUp( + AuthRecipeUserInfo user2 = EmailPassword.signUp( tenant.withStorage(StorageLayer.getStorage(tenant, process.getProcess())), process.getProcess(), "user@gmail.com", diff --git a/src/test/java/io/supertokens/test/authRecipe/UserPaginationTest.java b/src/test/java/io/supertokens/test/authRecipe/UserPaginationTest.java index 5340213cd..0e7f5e8d5 100644 --- a/src/test/java/io/supertokens/test/authRecipe/UserPaginationTest.java +++ b/src/test/java/io/supertokens/test/authRecipe/UserPaginationTest.java @@ -31,7 +31,7 @@ import io.supertokens.passwordless.Passwordless; import io.supertokens.passwordless.exceptions.*; import io.supertokens.pluginInterface.STORAGE_TYPE; -import io.supertokens.pluginInterface.emailpassword.UserInfo; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; import io.supertokens.pluginInterface.exceptions.InvalidConfigException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; @@ -185,7 +185,7 @@ private void createUsers(TenantIdentifier tenantIdentifier, int numUsers, String StorageLayer.getStorage(tenantIdentifier, process.getProcess())); for (int i = 0; i < numUsers; i++) { { - UserInfo user = EmailPassword.signUp( + AuthRecipeUserInfo user = EmailPassword.signUp( tenantIdentifierWithStorage, process.getProcess(), prefix + "epuser" + i + "@example.com", "password" + i); tenantToUsers.get(tenantIdentifier).add(user.getSupertokensUserId()); diff --git a/src/test/java/io/supertokens/test/emailpassword/DeleteExpiredPasswordResetTokensCronjobTest.java b/src/test/java/io/supertokens/test/emailpassword/DeleteExpiredPasswordResetTokensCronjobTest.java index 6ddd7af34..63959abb4 100644 --- a/src/test/java/io/supertokens/test/emailpassword/DeleteExpiredPasswordResetTokensCronjobTest.java +++ b/src/test/java/io/supertokens/test/emailpassword/DeleteExpiredPasswordResetTokensCronjobTest.java @@ -21,8 +21,8 @@ import io.supertokens.cronjobs.deleteExpiredPasswordResetTokens.DeleteExpiredPasswordResetTokens; import io.supertokens.emailpassword.EmailPassword; import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.emailpassword.PasswordResetTokenInfo; -import io.supertokens.pluginInterface.emailpassword.UserInfo; import io.supertokens.pluginInterface.emailpassword.sqlStorage.EmailPasswordSQLStorage; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.storageLayer.StorageLayer; @@ -64,7 +64,7 @@ public void checkingCronJob() throws Exception { return; } - UserInfo user = EmailPassword.signUp(process.getProcess(), "test1@example.com", "password"); + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test1@example.com", "password"); String tok = EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.getSupertokensUserId()); String tok2 = EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.getSupertokensUserId()); diff --git a/src/test/java/io/supertokens/test/emailpassword/EmailPasswordTest.java b/src/test/java/io/supertokens/test/emailpassword/EmailPasswordTest.java index 0ff40b125..b9f9387bc 100644 --- a/src/test/java/io/supertokens/test/emailpassword/EmailPasswordTest.java +++ b/src/test/java/io/supertokens/test/emailpassword/EmailPasswordTest.java @@ -32,7 +32,6 @@ import io.supertokens.pluginInterface.authRecipe.AuthRecipeStorage; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.emailpassword.PasswordResetTokenInfo; -import io.supertokens.pluginInterface.emailpassword.UserInfo; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicatePasswordResetTokenException; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateUserIdException; @@ -148,8 +147,8 @@ public void testResetPasswordToken() throws Exception { return; } - UserInfo userInfo = EmailPassword.signUp(process.getProcess(), "random@gmail.com", "validPass123"); - assertEquals(userInfo.email, "random@gmail.com"); + AuthRecipeUserInfo userInfo = EmailPassword.signUp(process.getProcess(), "random@gmail.com", "validPass123"); + assertEquals(userInfo.loginMethods[0].email, "random@gmail.com"); assertNotNull(userInfo.getSupertokensUserId()); for (int i = 0; i < 100; i++) { @@ -179,10 +178,10 @@ public void testThatAfterSignUpThePasswordIsHashedAndStoredInTheDatabase() throw return; } - UserInfo user = EmailPassword.signUp(process.getProcess(), "random@gmail.com", "validPass123"); + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "random@gmail.com", "validPass123"); AuthRecipeUserInfo userInfo = ((AuthRecipeStorage) StorageLayer.getStorage(process.getProcess())) - .listPrimaryUsersByEmail(new TenantIdentifier(null, null, null), user.email)[0]; + .listPrimaryUsersByEmail(new TenantIdentifier(null, null, null), user.loginMethods[0].email)[0]; assertNotEquals(userInfo.loginMethods[0].passwordHash, "validPass123"); assertTrue(PasswordHashing.getInstance(process.getProcess()).verifyPasswordWithHash("validPass123", userInfo.loginMethods[0].passwordHash)); @@ -205,7 +204,7 @@ public void testThatAfterResetPasswordGenerateTokenTheTokenIsHashedInTheDatabase return; } - UserInfo user = EmailPassword.signUp(process.getProcess(), "random@gmail.com", "validPass123"); + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "random@gmail.com", "validPass123"); String resetToken = EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.getSupertokensUserId()); PasswordResetTokenInfo resetTokenInfo = ((EmailPasswordSQLStorage) StorageLayer.getStorage( @@ -234,14 +233,14 @@ public void testThatAfterResetPasswordIsCompletedThePasswordIsHashedInTheDatabas return; } - UserInfo user = EmailPassword.signUp(process.getProcess(), "random@gmail.com", "validPass123"); + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "random@gmail.com", "validPass123"); String resetToken = EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.getSupertokensUserId()); EmailPassword.resetPassword(process.getProcess(), resetToken, "newValidPass123"); AuthRecipeUserInfo userInfo = ((AuthRecipeStorage) StorageLayer.getStorage(process.getProcess())) - .listPrimaryUsersByEmail(new TenantIdentifier(null, null, null), user.email)[0]; + .listPrimaryUsersByEmail(new TenantIdentifier(null, null, null), user.loginMethods[0].email)[0]; assertNotEquals(userInfo.loginMethods[0].passwordHash, "newValidPass123"); assertTrue(PasswordHashing.getInstance(process.getProcess()).verifyPasswordWithHash("newValidPass123", @@ -264,7 +263,7 @@ public void passwordResetTokenExpiredCheck() throws Exception { return; } - UserInfo user = EmailPassword.signUp(process.getProcess(), "test1@example.com", "password"); + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test1@example.com", "password"); String tok = EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.getSupertokensUserId()); @@ -298,7 +297,7 @@ public void multiplePasswordResetTokensPerUserAndThenVerifyWithSignin() throws E return; } - UserInfo user = EmailPassword.signUp(process.getProcess(), "test1@example.com", "password"); + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test1@example.com", "password"); EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.getSupertokensUserId()); String tok = EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.getSupertokensUserId()); @@ -324,7 +323,7 @@ public void multiplePasswordResetTokensPerUserAndThenVerifyWithSignin() throws E AuthRecipeUserInfo user1 = EmailPassword.signIn(process.getProcess(), "test1@example.com", "newPassword"); - assertEquals(user1.loginMethods[0].email, user.email); + assertEquals(user1.loginMethods[0].email, user.loginMethods[0].email); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -384,7 +383,7 @@ public void clashingPassowordResetToken() throws Exception { } // we add a user first. - UserInfo user = EmailPassword.signUp(process.getProcess(), "test1@example.com", "password"); + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test1@example.com", "password"); ((EmailPasswordSQLStorage) StorageLayer.getStorage(process.getProcess())) .addPasswordResetToken(new AppIdentifier(null, null), new PasswordResetTokenInfo( @@ -523,7 +522,7 @@ public void signUpAndThenSignIn() throws Exception { return; } - UserInfo userSignUp = EmailPassword.signUp(process.getProcess(), "test@example.com", "password"); + AuthRecipeUserInfo userSignUp = EmailPassword.signUp(process.getProcess(), "test@example.com", "password"); AuthRecipeUserInfo user = EmailPassword.signIn(process.getProcess(), "test@example.com", "password"); @@ -776,7 +775,7 @@ public void passwordResetTokenExpiredCheckWithConsumeCode() throws Exception { return; } - UserInfo user = EmailPassword.signUp(process.getProcess(), "test1@example.com", "password"); + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test1@example.com", "password"); String tok = EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.getSupertokensUserId()); @@ -810,7 +809,7 @@ public void multiplePasswordResetTokensPerUserAndThenVerifyWithSigninWithConsume return; } - UserInfo user = EmailPassword.signUp(process.getProcess(), "test1@example.com", "password"); + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test1@example.com", "password"); EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.getSupertokensUserId()); String tok = EmailPassword.generatePasswordResetTokenBeforeCdi4_0(process.getProcess(), user.getSupertokensUserId()); @@ -865,7 +864,7 @@ public void consumeCodeCorrectlySetsTheUserEmailForOlderTokens() throws Exceptio return; } - UserInfo user = EmailPassword.signUp(process.getProcess(), "test1@example.com", "password"); + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test1@example.com", "password"); String tok = EmailPassword.generatePasswordResetTokenBeforeCdi4_0WithoutAddingEmail(process.getProcess(), user.getSupertokensUserId()); @@ -902,10 +901,10 @@ public void updateEmailFailsIfEmailUsedByOtherPrimaryUserInSameTenant() throws E return; } - UserInfo user0 = EmailPassword.signUp(process.getProcess(), "someemail1@gmail.com", "somePass"); + AuthRecipeUserInfo user0 = EmailPassword.signUp(process.getProcess(), "someemail1@gmail.com", "somePass"); AuthRecipe.createPrimaryUser(process.main, user0.getSupertokensUserId()); - UserInfo user = EmailPassword.signUp(process.getProcess(), "someemail@gmail.com", "somePass"); + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "someemail@gmail.com", "somePass"); AuthRecipe.createPrimaryUser(process.main, user.getSupertokensUserId()); try { @@ -947,7 +946,7 @@ public void updateEmailSucceedsIfEmailUsedByOtherPrimaryUserInDifferentTenantWhi AuthRecipe.createPrimaryUser(process.main, user0.getSupertokensUserId()); - UserInfo user = EmailPassword.signUp(process.getProcess(), "someemail@gmail.com", "somePass"); + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "someemail@gmail.com", "somePass"); AuthRecipe.createPrimaryUser(process.main, user.getSupertokensUserId()); EmailPassword.updateUsersEmailOrPassword(process.main, user.getSupertokensUserId(), "someemail1@gmail.com", null); @@ -984,7 +983,7 @@ public void updateEmailFailsIfEmailUsedByOtherPrimaryUserInDifferentTenant() AuthRecipe.createPrimaryUser(process.main, user0.getSupertokensUserId()); - UserInfo user = EmailPassword.signUp(process.getProcess(), "someemail@gmail.com", "somePass"); + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "someemail@gmail.com", "somePass"); AuthRecipe.createPrimaryUser(process.main, user.getSupertokensUserId()); Multitenancy.addUserIdToTenant(process.main, tenantIdentifierWithStorage, user.getSupertokensUserId()); diff --git a/src/test/java/io/supertokens/test/emailpassword/MultitenantEmailPasswordTest.java b/src/test/java/io/supertokens/test/emailpassword/MultitenantEmailPasswordTest.java index 01e53b16e..80a6fbfe1 100644 --- a/src/test/java/io/supertokens/test/emailpassword/MultitenantEmailPasswordTest.java +++ b/src/test/java/io/supertokens/test/emailpassword/MultitenantEmailPasswordTest.java @@ -30,7 +30,6 @@ import io.supertokens.pluginInterface.STORAGE_TYPE; import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; -import io.supertokens.pluginInterface.emailpassword.UserInfo; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.exceptions.InvalidConfigException; @@ -264,14 +263,14 @@ public void testGetUserUsingIdReturnsCorrectUser() TenantIdentifier t3 = new TenantIdentifier(null, "a1", "t2"); TenantIdentifierWithStorage t3storage = t3.withStorage(StorageLayer.getStorage(t3, process.getProcess())); - UserInfo user1 = EmailPassword.signUp(t1storage, process.getProcess(), "user1@example.com", "password1"); - UserInfo user2 = EmailPassword.signUp(t2storage, process.getProcess(), "user2@example.com", "password2"); - UserInfo user3 = EmailPassword.signUp(t3storage, process.getProcess(), "user3@example.com", "password3"); + AuthRecipeUserInfo user1 = EmailPassword.signUp(t1storage, process.getProcess(), "user1@example.com", "password1"); + AuthRecipeUserInfo user2 = EmailPassword.signUp(t2storage, process.getProcess(), "user2@example.com", "password2"); + AuthRecipeUserInfo user3 = EmailPassword.signUp(t3storage, process.getProcess(), "user3@example.com", "password3"); Storage storage = StorageLayer.getStorage(process.getProcess()); { - UserInfo userInfo = EmailPassword.getUserUsingId( + AuthRecipeUserInfo userInfo = EmailPassword.getUserUsingId( StorageLayer.getAppIdentifierWithStorageAndUserIdMappingForUserWithPriorityForTenantStorage( process.getProcess(), new AppIdentifier(null, "a1"), storage, user1.getSupertokensUserId(), UserIdType.SUPERTOKENS).appIdentifierWithStorage, user1.getSupertokensUserId()); @@ -279,7 +278,7 @@ public void testGetUserUsingIdReturnsCorrectUser() } { - UserInfo userInfo = EmailPassword.getUserUsingId( + AuthRecipeUserInfo userInfo = EmailPassword.getUserUsingId( StorageLayer.getAppIdentifierWithStorageAndUserIdMappingForUserWithPriorityForTenantStorage( process.getProcess(), new AppIdentifier(null, "a1"), storage, user2.getSupertokensUserId(), UserIdType.SUPERTOKENS).appIdentifierWithStorage, user2.getSupertokensUserId()); @@ -287,7 +286,7 @@ public void testGetUserUsingIdReturnsCorrectUser() } { - UserInfo userInfo = EmailPassword.getUserUsingId( + AuthRecipeUserInfo userInfo = EmailPassword.getUserUsingId( StorageLayer.getAppIdentifierWithStorageAndUserIdMappingForUserWithPriorityForTenantStorage( process.getProcess(), new AppIdentifier(null, "a1"), storage, user3.getSupertokensUserId(), UserIdType.SUPERTOKENS).appIdentifierWithStorage, user3.getSupertokensUserId()); @@ -324,9 +323,9 @@ public void testGetUserUsingEmailReturnsTheUserFromTheSpecificTenant() TenantIdentifier t3 = new TenantIdentifier(null, "a1", "t2"); TenantIdentifierWithStorage t3storage = t3.withStorage(StorageLayer.getStorage(t3, process.getProcess())); - UserInfo user1 = EmailPassword.signUp(t1storage, process.getProcess(), "user@example.com", "password1"); - UserInfo user2 = EmailPassword.signUp(t2storage, process.getProcess(), "user@example.com", "password2"); - UserInfo user3 = EmailPassword.signUp(t3storage, process.getProcess(), "user@example.com", "password3"); + AuthRecipeUserInfo user1 = EmailPassword.signUp(t1storage, process.getProcess(), "user@example.com", "password1"); + AuthRecipeUserInfo user2 = EmailPassword.signUp(t2storage, process.getProcess(), "user@example.com", "password2"); + AuthRecipeUserInfo user3 = EmailPassword.signUp(t3storage, process.getProcess(), "user@example.com", "password3"); { AuthRecipeUserInfo userInfo = EmailPassword.getUserUsingEmail(t1storage, user1.loginMethods[0].email); @@ -375,9 +374,9 @@ public void testUpdatePasswordWorksCorrectlyAcrossAllTenants() TenantIdentifier t3 = new TenantIdentifier(null, "a1", "t2"); TenantIdentifierWithStorage t3storage = t3.withStorage(StorageLayer.getStorage(t3, process.getProcess())); - UserInfo user1 = EmailPassword.signUp(t1storage, process.getProcess(), "user@example.com", "password1"); - UserInfo user2 = EmailPassword.signUp(t2storage, process.getProcess(), "user@example.com", "password2"); - UserInfo user3 = EmailPassword.signUp(t3storage, process.getProcess(), "user@example.com", "password3"); + AuthRecipeUserInfo user1 = EmailPassword.signUp(t1storage, process.getProcess(), "user@example.com", "password1"); + AuthRecipeUserInfo user2 = EmailPassword.signUp(t2storage, process.getProcess(), "user@example.com", "password2"); + AuthRecipeUserInfo user3 = EmailPassword.signUp(t3storage, process.getProcess(), "user@example.com", "password3"); Storage storage = StorageLayer.getStorage(process.getProcess()); diff --git a/src/test/java/io/supertokens/test/emailpassword/PasswordHashingTest.java b/src/test/java/io/supertokens/test/emailpassword/PasswordHashingTest.java index ebafbef56..683d99001 100644 --- a/src/test/java/io/supertokens/test/emailpassword/PasswordHashingTest.java +++ b/src/test/java/io/supertokens/test/emailpassword/PasswordHashingTest.java @@ -26,7 +26,6 @@ import io.supertokens.inmemorydb.Start; import io.supertokens.pluginInterface.STORAGE_TYPE; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; -import io.supertokens.pluginInterface.emailpassword.UserInfo; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.TestingProcessManager; @@ -523,7 +522,7 @@ public void hashAndVerifyWithBcryptChangeToArgonPasswordWithResetFlow() throws E return; } - UserInfo user = EmailPassword.signUp(process.getProcess(), "t@example.com", "somePass"); + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "t@example.com", "somePass"); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.PASSWORD_HASH_BCRYPT)); ProcessState.getInstance(process.getProcess()).clear(); @@ -554,7 +553,7 @@ public void hashAndVerifyWithArgonChangeToBcryptPasswordWithResetFlow() throws E return; } - UserInfo user = EmailPassword.signUp(process.getProcess(), "t@example.com", "somePass"); + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "t@example.com", "somePass"); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.PASSWORD_HASH_ARGON)); ProcessState.getInstance(process.getProcess()).clear(); @@ -584,7 +583,7 @@ public void hashAndVerifyWithBcryptChangeToArgonChangePassword() throws Exceptio return; } - UserInfo user = EmailPassword.signUp(process.getProcess(), "t@example.com", "somePass"); + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "t@example.com", "somePass"); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.PASSWORD_HASH_BCRYPT)); ProcessState.getInstance(process.getProcess()).clear(); @@ -614,7 +613,7 @@ public void hashAndVerifyWithArgonChangeToBcryptChangePassword() throws Exceptio return; } - UserInfo user = EmailPassword.signUp(process.getProcess(), "t@example.com", "somePass"); + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "t@example.com", "somePass"); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.PASSWORD_HASH_ARGON)); ProcessState.getInstance(process.getProcess()).clear(); diff --git a/src/test/java/io/supertokens/test/emailpassword/UpdateUsersEmailAndPasswordTest.java b/src/test/java/io/supertokens/test/emailpassword/UpdateUsersEmailAndPasswordTest.java index 63393f08d..aea6505ee 100644 --- a/src/test/java/io/supertokens/test/emailpassword/UpdateUsersEmailAndPasswordTest.java +++ b/src/test/java/io/supertokens/test/emailpassword/UpdateUsersEmailAndPasswordTest.java @@ -20,7 +20,6 @@ import io.supertokens.emailpassword.EmailPassword; import io.supertokens.pluginInterface.STORAGE_TYPE; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; -import io.supertokens.pluginInterface.emailpassword.UserInfo; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.storageLayer.StorageLayer; @@ -72,7 +71,7 @@ public void testUpdateEmailOnly() throws Exception { } // given - UserInfo userInfo = EmailPassword.signUp(main, "john.doe@example.com", "password"); + AuthRecipeUserInfo userInfo = EmailPassword.signUp(main, "john.doe@example.com", "password"); // when EmailPassword.updateUsersEmailOrPassword(main, userInfo.getSupertokensUserId(), "dave.doe@example.com", null); @@ -95,12 +94,12 @@ public void testUpdateEmailToAnotherThatAlreadyExists() throws Exception { } // given - UserInfo userInfo = EmailPassword.signUp(main, "john.doe@example.com", "password"); - UserInfo userInfo2 = EmailPassword.signUp(main, "john.doe1@example.com", "password"); + AuthRecipeUserInfo userInfo = EmailPassword.signUp(main, "john.doe@example.com", "password"); + AuthRecipeUserInfo userInfo2 = EmailPassword.signUp(main, "john.doe1@example.com", "password"); // when try { - EmailPassword.updateUsersEmailOrPassword(main, userInfo.getSupertokensUserId(), userInfo2.email, null); + EmailPassword.updateUsersEmailOrPassword(main, userInfo.getSupertokensUserId(), userInfo2.loginMethods[0].email, null); Assert.fail(); } catch (DuplicateEmailException ignored) { } @@ -118,7 +117,7 @@ public void testUpdatePasswordOnly() throws Exception { } // given - UserInfo userInfo = EmailPassword.signUp(main, "john.doe@example.com", "password"); + AuthRecipeUserInfo userInfo = EmailPassword.signUp(main, "john.doe@example.com", "password"); // when EmailPassword.updateUsersEmailOrPassword(main, userInfo.getSupertokensUserId(), null, "newPassword"); @@ -140,7 +139,7 @@ public void testUpdateEmailAndPassword() throws Exception { } // given - UserInfo userInfo = EmailPassword.signUp(main, "john.doe@example.com", "password"); + AuthRecipeUserInfo userInfo = EmailPassword.signUp(main, "john.doe@example.com", "password"); // when EmailPassword.updateUsersEmailOrPassword(main, userInfo.getSupertokensUserId(), "dave.doe@example.com", "newPassword"); diff --git a/src/test/java/io/supertokens/test/emailpassword/UserMigrationTest.java b/src/test/java/io/supertokens/test/emailpassword/UserMigrationTest.java index 805696057..dea91f847 100644 --- a/src/test/java/io/supertokens/test/emailpassword/UserMigrationTest.java +++ b/src/test/java/io/supertokens/test/emailpassword/UserMigrationTest.java @@ -23,7 +23,6 @@ import io.supertokens.emailpassword.exceptions.WrongCredentialsException; import io.supertokens.pluginInterface.STORAGE_TYPE; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; -import io.supertokens.pluginInterface.emailpassword.UserInfo; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.TestingProcessManager; import io.supertokens.test.Utils; @@ -179,7 +178,7 @@ public void testUpdatingAUsersPasswordHash() throws Exception { String email = "test@example.com"; String originalPassword = "testPass123"; - UserInfo signUpUserInfo = EmailPassword.signUp(process.main, email, originalPassword); + AuthRecipeUserInfo signUpUserInfo = EmailPassword.signUp(process.main, email, originalPassword); // update passwordHash with new passwordHash String newPassword = "newTestPass123"; @@ -201,7 +200,7 @@ public void testUpdatingAUsersPasswordHash() throws Exception { // sign in with the newPassword and check that it works AuthRecipeUserInfo userInfo = EmailPassword.signIn(process.main, email, newPassword); - assertEquals(userInfo.loginMethods[0].email, signUpUserInfo.email); + assertEquals(userInfo.loginMethods[0].email, signUpUserInfo.loginMethods[0].email); assertEquals(userInfo.getSupertokensUserId(), signUpUserInfo.getSupertokensUserId()); assertEquals(userInfo.timeJoined, signUpUserInfo.timeJoined); assertEquals(userInfo.loginMethods[0].passwordHash, newPasswordHash); diff --git a/src/test/java/io/supertokens/test/emailpassword/api/ImportUserWithPasswordHashAPITest.java b/src/test/java/io/supertokens/test/emailpassword/api/ImportUserWithPasswordHashAPITest.java index c79af7716..2422599b5 100644 --- a/src/test/java/io/supertokens/test/emailpassword/api/ImportUserWithPasswordHashAPITest.java +++ b/src/test/java/io/supertokens/test/emailpassword/api/ImportUserWithPasswordHashAPITest.java @@ -23,7 +23,6 @@ import io.supertokens.emailpassword.ParsedFirebaseSCryptResponse; import io.supertokens.pluginInterface.STORAGE_TYPE; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; -import io.supertokens.pluginInterface.emailpassword.UserInfo; import io.supertokens.pluginInterface.emailpassword.sqlStorage.EmailPasswordSQLStorage; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.storageLayer.StorageLayer; @@ -519,7 +518,7 @@ public void testUpdatingAUsersPasswordHash() throws Exception { String email = "test@example.com"; String password = "testPass123"; - UserInfo initialUserInfo = EmailPassword.signUp(process.main, email, password); + AuthRecipeUserInfo initialUserInfo = EmailPassword.signUp(process.main, email, password); // update a user's passwordHash diff --git a/src/test/java/io/supertokens/test/emailpassword/api/UserPutAPITest2_8.java b/src/test/java/io/supertokens/test/emailpassword/api/UserPutAPITest2_8.java index 6cfb9d331..a04258162 100644 --- a/src/test/java/io/supertokens/test/emailpassword/api/UserPutAPITest2_8.java +++ b/src/test/java/io/supertokens/test/emailpassword/api/UserPutAPITest2_8.java @@ -20,7 +20,7 @@ import io.supertokens.emailpassword.EmailPassword; import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.STORAGE_TYPE; -import io.supertokens.pluginInterface.emailpassword.UserInfo; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.TestingProcessManager; import io.supertokens.test.Utils; @@ -75,12 +75,12 @@ public void testQueryingWithEmailThatAlreadyExists() throws Exception { return; } - UserInfo user = EmailPassword.signUp(process.getProcess(), "someemail@gmail.com", "somePass"); - UserInfo user2 = EmailPassword.signUp(process.getProcess(), "someemail2@gmail.com", "somePass"); + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "someemail@gmail.com", "somePass"); + AuthRecipeUserInfo user2 = EmailPassword.signUp(process.getProcess(), "someemail2@gmail.com", "somePass"); JsonObject body = new JsonObject(); body.addProperty("userId", user.getSupertokensUserId()); - body.addProperty("email", user2.email); + body.addProperty("email", user2.loginMethods[0].email); JsonObject response = HttpRequestForTesting.sendJsonPUTRequest(process.getProcess(), "", "http://localhost:3567/recipe/user", body, 1000, 1000, null, SemVer.v2_8.get(), @@ -98,7 +98,7 @@ public void testUpdatingEmailNormalisesIt() throws Exception { return; } - UserInfo user = EmailPassword.signUp(process.getProcess(), "someemail@gmail.com", "somePass"); + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "someemail@gmail.com", "somePass"); JsonObject body = new JsonObject(); body.addProperty("userId", user.getSupertokensUserId()); @@ -144,7 +144,7 @@ public void testSuccessfulUpdate() throws Exception { return; } - UserInfo user = EmailPassword.signUp(process.getProcess(), "someemail@gmail.com", "somePass"); + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "someemail@gmail.com", "somePass"); JsonObject body = new JsonObject(); body.addProperty("userId", user.getSupertokensUserId()); @@ -168,7 +168,7 @@ public void testSuccessfulUpdateWithOnlyPassword() throws Exception { return; } - UserInfo user = EmailPassword.signUp(process.getProcess(), "someemail@gmail.com", "somePass"); + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "someemail@gmail.com", "somePass"); JsonObject body = new JsonObject(); body.addProperty("userId", user.getSupertokensUserId()); diff --git a/src/test/java/io/supertokens/test/emailpassword/api/UserPutAPITest4_0.java b/src/test/java/io/supertokens/test/emailpassword/api/UserPutAPITest4_0.java index 07cd1b100..4754a04b1 100644 --- a/src/test/java/io/supertokens/test/emailpassword/api/UserPutAPITest4_0.java +++ b/src/test/java/io/supertokens/test/emailpassword/api/UserPutAPITest4_0.java @@ -24,7 +24,7 @@ import io.supertokens.featureflag.FeatureFlagTestContent; import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.STORAGE_TYPE; -import io.supertokens.pluginInterface.emailpassword.UserInfo; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.TestingProcessManager; import io.supertokens.test.Utils; @@ -67,10 +67,10 @@ public void testThatAPIReturnsEmailUpdateNotPossibleWithSingleTenant() throws Ex return; } - UserInfo user0 = EmailPassword.signUp(process.getProcess(), "someemail1@gmail.com", "somePass"); + AuthRecipeUserInfo user0 = EmailPassword.signUp(process.getProcess(), "someemail1@gmail.com", "somePass"); AuthRecipe.createPrimaryUser(process.main, user0.getSupertokensUserId()); - UserInfo user = EmailPassword.signUp(process.getProcess(), "someemail@gmail.com", "somePass"); + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "someemail@gmail.com", "somePass"); AuthRecipe.createPrimaryUser(process.main, user.getSupertokensUserId()); JsonObject body = new JsonObject(); diff --git a/src/test/java/io/supertokens/test/emailverification/DeleteExpiredEmailVerificationTokensCronjobTest.java b/src/test/java/io/supertokens/test/emailverification/DeleteExpiredEmailVerificationTokensCronjobTest.java index 11e074f94..a036ebfae 100644 --- a/src/test/java/io/supertokens/test/emailverification/DeleteExpiredEmailVerificationTokensCronjobTest.java +++ b/src/test/java/io/supertokens/test/emailverification/DeleteExpiredEmailVerificationTokensCronjobTest.java @@ -22,7 +22,7 @@ import io.supertokens.emailpassword.EmailPassword; import io.supertokens.emailverification.EmailVerification; import io.supertokens.pluginInterface.STORAGE_TYPE; -import io.supertokens.pluginInterface.emailpassword.UserInfo; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.emailverification.EmailVerificationTokenInfo; import io.supertokens.pluginInterface.emailverification.sqlStorage.EmailVerificationSQLStorage; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; @@ -65,24 +65,24 @@ public void checkingCronJob() throws Exception { return; } - UserInfo user = EmailPassword.signUp(process.getProcess(), "test1@example.com", "password"); + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test1@example.com", "password"); - String tok = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.getSupertokensUserId(), user.email); - String tok2 = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.getSupertokensUserId(), user.email); + String tok = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.getSupertokensUserId(), user.loginMethods[0].email); + String tok2 = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.getSupertokensUserId(), user.loginMethods[0].email); Thread.sleep(2000); - String tok3 = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.getSupertokensUserId(), user.email); - String tok4 = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.getSupertokensUserId(), user.email); + String tok3 = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.getSupertokensUserId(), user.loginMethods[0].email); + String tok4 = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.getSupertokensUserId(), user.loginMethods[0].email); assert (((EmailVerificationSQLStorage) StorageLayer.getStorage(process.getProcess())) - .getAllEmailVerificationTokenInfoForUser(new TenantIdentifier(null, null, null), user.getSupertokensUserId(), user.email).length == + .getAllEmailVerificationTokenInfoForUser(new TenantIdentifier(null, null, null), user.getSupertokensUserId(), user.loginMethods[0].email).length == 4); Thread.sleep(3500); EmailVerificationTokenInfo[] tokens = ((EmailVerificationSQLStorage) StorageLayer.getStorage(process.getProcess())) - .getAllEmailVerificationTokenInfoForUser(new TenantIdentifier(null, null, null), user.getSupertokensUserId(), user.email); + .getAllEmailVerificationTokenInfoForUser(new TenantIdentifier(null, null, null), user.getSupertokensUserId(), user.loginMethods[0].email); assert (tokens.length == 2); diff --git a/src/test/java/io/supertokens/test/emailverification/EmailVerificationTest.java b/src/test/java/io/supertokens/test/emailverification/EmailVerificationTest.java index 94b210777..94c9a4238 100644 --- a/src/test/java/io/supertokens/test/emailverification/EmailVerificationTest.java +++ b/src/test/java/io/supertokens/test/emailverification/EmailVerificationTest.java @@ -23,7 +23,7 @@ import io.supertokens.emailverification.exception.EmailAlreadyVerifiedException; import io.supertokens.emailverification.exception.EmailVerificationInvalidTokenException; import io.supertokens.pluginInterface.STORAGE_TYPE; -import io.supertokens.pluginInterface.emailpassword.UserInfo; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.emailverification.EmailVerificationTokenInfo; import io.supertokens.pluginInterface.emailverification.exception.DuplicateEmailVerificationTokenException; import io.supertokens.pluginInterface.emailverification.sqlStorage.EmailVerificationSQLStorage; @@ -79,14 +79,14 @@ public void testGeneratingEmailVerificationTokenTwoTimes() throws Exception { return; } - UserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "testPass123"); - String token1 = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.getSupertokensUserId(), user.email); - String token2 = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.getSupertokensUserId(), user.email); + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "testPass123"); + String token1 = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.getSupertokensUserId(), user.loginMethods[0].email); + String token2 = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.getSupertokensUserId(), user.loginMethods[0].email); assertNotEquals(token1, token2); EmailVerificationTokenInfo[] tokenInfo = ((EmailVerificationSQLStorage) StorageLayer.getStorage(process.getProcess())) - .getAllEmailVerificationTokenInfoForUser(new TenantIdentifier(null, null, null), user.getSupertokensUserId(), user.email); + .getAllEmailVerificationTokenInfoForUser(new TenantIdentifier(null, null, null), user.getSupertokensUserId(), user.loginMethods[0].email); assertEquals(tokenInfo.length, 2); assertTrue((tokenInfo[0].token.equals(io.supertokens.utils.Utils.hashSHA256(token1))) @@ -110,15 +110,15 @@ public void testVerifyingEmailAndGeneratingToken() throws Exception { return; } - UserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "testPass123"); - String token = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.getSupertokensUserId(), user.email); + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "testPass123"); + String token = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.getSupertokensUserId(), user.loginMethods[0].email); EmailVerification.verifyEmail(process.getProcess(), token); - assertTrue(EmailVerification.isEmailVerified(process.getProcess(), user.getSupertokensUserId(), user.email)); + assertTrue(EmailVerification.isEmailVerified(process.getProcess(), user.getSupertokensUserId(), user.loginMethods[0].email)); try { - EmailVerification.generateEmailVerificationToken(process.getProcess(), user.getSupertokensUserId(), user.email); + EmailVerification.generateEmailVerificationToken(process.getProcess(), user.getSupertokensUserId(), user.loginMethods[0].email); throw new Exception("should not come here"); } catch (EmailAlreadyVerifiedException ignored) { } @@ -160,13 +160,13 @@ public void testGeneratingTwoTokenVerifyOtherTokenShouldThrowAnError() throws Ex if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { return; } - UserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "testPass123"); + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "testPass123"); - String token1 = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.getSupertokensUserId(), user.email); - String token2 = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.getSupertokensUserId(), user.email); + String token1 = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.getSupertokensUserId(), user.loginMethods[0].email); + String token2 = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.getSupertokensUserId(), user.loginMethods[0].email); EmailVerification.verifyEmail(process.getProcess(), token1); - assertTrue(EmailVerification.isEmailVerified(process.getProcess(), user.getSupertokensUserId(), user.email)); + assertTrue(EmailVerification.isEmailVerified(process.getProcess(), user.getSupertokensUserId(), user.loginMethods[0].email)); try { EmailVerification.verifyEmail(process.getProcess(), token2); @@ -193,9 +193,9 @@ public void useAnExpiredTokenItShouldThrowAnError() throws Exception { if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { return; } - UserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "testPass123"); + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "testPass123"); - String token = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.getSupertokensUserId(), user.email); + String token = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.getSupertokensUserId(), user.loginMethods[0].email); Thread.sleep(20); @@ -221,11 +221,11 @@ public void testFormatOfEmailVerificationToken() throws Exception { if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { return; } - UserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "testPass123"); + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "testPass123"); for (int i = 0; i < 100; i++) { String verifyToken = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.getSupertokensUserId(), - user.email); + user.loginMethods[0].email); assertEquals(verifyToken.length(), 128); assertFalse(verifyToken.contains("+")); assertFalse(verifyToken.contains("=")); @@ -248,7 +248,7 @@ public void clashingEmailVerificationToken() throws Exception { } // we add a user first. - UserInfo user = EmailPassword.signUp(process.getProcess(), "test1@example.com", "password"); + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test1@example.com", "password"); ((EmailVerificationSQLStorage) StorageLayer.getStorage(process.getProcess())) .addEmailVerificationToken(new TenantIdentifier(null, null, null), @@ -285,17 +285,17 @@ public void verifyEmail() throws Exception { return; } - UserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "password"); + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "password"); - assert (!EmailVerification.isEmailVerified(process.getProcess(), user.getSupertokensUserId(), user.email)); + assert (!EmailVerification.isEmailVerified(process.getProcess(), user.getSupertokensUserId(), user.loginMethods[0].email)); - String token = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.getSupertokensUserId(), user.email); + String token = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.getSupertokensUserId(), user.loginMethods[0].email); assert (token != null); EmailVerification.verifyEmail(process.getProcess(), token); - assert (EmailVerification.isEmailVerified(process.getProcess(), user.getSupertokensUserId(), user.email)); + assert (EmailVerification.isEmailVerified(process.getProcess(), user.getSupertokensUserId(), user.loginMethods[0].email)); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -313,24 +313,24 @@ public void testVerifyingEmailAndThenUnverify() throws Exception { return; } - UserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "testPass123"); - String token = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.getSupertokensUserId(), user.email); + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "testPass123"); + String token = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.getSupertokensUserId(), user.loginMethods[0].email); EmailVerification.verifyEmail(process.getProcess(), token); - assertTrue(EmailVerification.isEmailVerified(process.getProcess(), user.getSupertokensUserId(), user.email)); + assertTrue(EmailVerification.isEmailVerified(process.getProcess(), user.getSupertokensUserId(), user.loginMethods[0].email)); ((EmailVerificationSQLStorage) StorageLayer.getStorage(process.getProcess())).startTransaction(con -> { try { ((EmailVerificationSQLStorage) StorageLayer.getStorage(process.getProcess())) .updateIsEmailVerified_Transaction(new AppIdentifier(null, null), con, - user.getSupertokensUserId(), user.email, false); + user.getSupertokensUserId(), user.loginMethods[0].email, false); } catch (TenantOrAppNotFoundException e) { throw new RuntimeException(e); } return null; }); - assertFalse(EmailVerification.isEmailVerified(process.getProcess(), user.getSupertokensUserId(), user.email)); + assertFalse(EmailVerification.isEmailVerified(process.getProcess(), user.getSupertokensUserId(), user.loginMethods[0].email)); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -348,24 +348,24 @@ public void testVerifyingSameEmailTwice() throws Exception { return; } - UserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "testPass123"); - String token = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.getSupertokensUserId(), user.email); + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "testPass123"); + String token = EmailVerification.generateEmailVerificationToken(process.getProcess(), user.getSupertokensUserId(), user.loginMethods[0].email); EmailVerification.verifyEmail(process.getProcess(), token); - assertTrue(EmailVerification.isEmailVerified(process.getProcess(), user.getSupertokensUserId(), user.email)); + assertTrue(EmailVerification.isEmailVerified(process.getProcess(), user.getSupertokensUserId(), user.loginMethods[0].email)); ((EmailVerificationSQLStorage) StorageLayer.getStorage(process.getProcess())).startTransaction(con -> { try { ((EmailVerificationSQLStorage) StorageLayer.getStorage(process.getProcess())) .updateIsEmailVerified_Transaction(new AppIdentifier(null, null), con, - user.getSupertokensUserId(), user.email, true); + user.getSupertokensUserId(), user.loginMethods[0].email, true); } catch (TenantOrAppNotFoundException e) { throw new RuntimeException(e); } return null; }); - assertTrue(EmailVerification.isEmailVerified(process.getProcess(), user.getSupertokensUserId(), user.email)); + assertTrue(EmailVerification.isEmailVerified(process.getProcess(), user.getSupertokensUserId(), user.loginMethods[0].email)); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); diff --git a/src/test/java/io/supertokens/test/multitenant/AppTenantUserTest.java b/src/test/java/io/supertokens/test/multitenant/AppTenantUserTest.java index 4b84b2f26..e1c7f9e0c 100644 --- a/src/test/java/io/supertokens/test/multitenant/AppTenantUserTest.java +++ b/src/test/java/io/supertokens/test/multitenant/AppTenantUserTest.java @@ -25,7 +25,7 @@ import io.supertokens.multitenancy.Multitenancy; import io.supertokens.pluginInterface.ActiveUsersStorage; import io.supertokens.pluginInterface.STORAGE_TYPE; -import io.supertokens.pluginInterface.emailpassword.UserInfo; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.multitenancy.*; import io.supertokens.pluginInterface.nonAuthRecipe.NonAuthRecipeStorage; import io.supertokens.storageLayer.StorageLayer; @@ -120,7 +120,7 @@ public void testDeletingAppDeleteNonAuthRecipeData() throws Exception { StorageLayer.getStorage(t, process.getProcess())); - UserInfo user = EmailPassword.signUp(tWithStorage, process.getProcess(), "test@example.com", + AuthRecipeUserInfo user = EmailPassword.signUp(tWithStorage, process.getProcess(), "test@example.com", "password"); String userId = user.getSupertokensUserId(); @@ -223,7 +223,7 @@ public void testDisassociationOfUserDeletesNonAuthRecipeData() throws Exception continue; } - UserInfo user = EmailPassword.signUp(appWithStorage, process.getProcess(), "test@example.com", "password"); + AuthRecipeUserInfo user = EmailPassword.signUp(appWithStorage, process.getProcess(), "test@example.com", "password"); String userId = user.getSupertokensUserId(); Multitenancy.addUserIdToTenant(process.getProcess(), tenantWithStorage, userId); @@ -291,7 +291,7 @@ public void deletingTenantKeepsTheUserInTheApp() throws Exception { TenantIdentifierWithStorage tenantWithStorage = tenant.withStorage( StorageLayer.getStorage(tenant, process.getProcess())); - UserInfo user = EmailPassword.signUp(tenantWithStorage, process.getProcess(), "test@example.com", "password"); + AuthRecipeUserInfo user = EmailPassword.signUp(tenantWithStorage, process.getProcess(), "test@example.com", "password"); String userId = user.getSupertokensUserId(); Multitenancy.deleteTenant(tenant, process.getProcess()); @@ -299,7 +299,7 @@ public void deletingTenantKeepsTheUserInTheApp() throws Exception { Multitenancy.addUserIdToTenant(process.getProcess(), appWithStorage, userId); // user id must be intact to do this - UserInfo appUser = EmailPassword.getUserUsingId(appWithStorage.toAppIdentifierWithStorage(), userId); + AuthRecipeUserInfo appUser = EmailPassword.getUserUsingId(appWithStorage.toAppIdentifierWithStorage(), userId); assertNotNull(appUser); assertEquals(userId, appUser.getSupertokensUserId()); diff --git a/src/test/java/io/supertokens/test/multitenant/TestAppData.java b/src/test/java/io/supertokens/test/multitenant/TestAppData.java index 7af3ee787..f6c4c679e 100644 --- a/src/test/java/io/supertokens/test/multitenant/TestAppData.java +++ b/src/test/java/io/supertokens/test/multitenant/TestAppData.java @@ -29,7 +29,7 @@ import io.supertokens.multitenancy.Multitenancy; import io.supertokens.passwordless.Passwordless; import io.supertokens.pluginInterface.STORAGE_TYPE; -import io.supertokens.pluginInterface.emailpassword.UserInfo; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.*; import io.supertokens.pluginInterface.totp.TOTPDevice; @@ -123,7 +123,7 @@ public void testThatDeletingAppDeleteDataFromAllTables() throws Exception { Arrays.sort(allTableNames); // Add all recipe data - UserInfo epUser = EmailPassword.signUp(appWithStorage, process.getProcess(), "test@example.com", "password"); + AuthRecipeUserInfo epUser = EmailPassword.signUp(appWithStorage, process.getProcess(), "test@example.com", "password"); EmailPassword.generatePasswordResetTokenBeforeCdi4_0(appWithStorage, process.getProcess(), epUser.getSupertokensUserId()); ThirdParty.SignInUpResponse tpUser = ThirdParty.signInUp(appWithStorage, process.getProcess(), "google", @@ -141,7 +141,7 @@ public void testThatDeletingAppDeleteDataFromAllTables() throws Exception { "user@example.com", "password"); String evToken = EmailVerification.generateEmailVerificationToken(appWithStorage, process.getProcess(), - epUser.getSupertokensUserId(), epUser.email); + epUser.getSupertokensUserId(), epUser.loginMethods[0].email); EmailVerification.verifyEmail(appWithStorage, evToken); EmailVerification.generateEmailVerificationToken(appWithStorage, process.getProcess(), tpUser.user.getSupertokensUserId(), tpUser.user.loginMethods[0].email); diff --git a/src/test/java/io/supertokens/test/multitenant/api/TestTenantIdIsNotPresentForOlderCDI.java b/src/test/java/io/supertokens/test/multitenant/api/TestTenantIdIsNotPresentForOlderCDI.java index 3a91931e8..1b12646d4 100644 --- a/src/test/java/io/supertokens/test/multitenant/api/TestTenantIdIsNotPresentForOlderCDI.java +++ b/src/test/java/io/supertokens/test/multitenant/api/TestTenantIdIsNotPresentForOlderCDI.java @@ -32,7 +32,7 @@ import io.supertokens.passwordless.Passwordless; import io.supertokens.passwordless.exceptions.*; import io.supertokens.pluginInterface.STORAGE_TYPE; -import io.supertokens.pluginInterface.emailpassword.UserInfo; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; import io.supertokens.pluginInterface.exceptions.InvalidConfigException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; @@ -285,7 +285,7 @@ private void createUsers(TenantIdentifier tenantIdentifier, int numUsers, String StorageLayer.getStorage(tenantIdentifier, process.getProcess())); for (int i = 0; i < numUsers; i++) { { - UserInfo user = EmailPassword.signUp( + AuthRecipeUserInfo user = EmailPassword.signUp( tenantIdentifierWithStorage, process.getProcess(), prefix + "epuser" + i + "@example.com", "password" + i); tenantToUsers.get(tenantIdentifier).add(user.getSupertokensUserId()); diff --git a/src/test/java/io/supertokens/test/passwordless/PasswordlessConsumeCodeTest.java b/src/test/java/io/supertokens/test/passwordless/PasswordlessConsumeCodeTest.java index 3e9471552..884799871 100644 --- a/src/test/java/io/supertokens/test/passwordless/PasswordlessConsumeCodeTest.java +++ b/src/test/java/io/supertokens/test/passwordless/PasswordlessConsumeCodeTest.java @@ -30,7 +30,6 @@ import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.passwordless.PasswordlessDevice; import io.supertokens.pluginInterface.passwordless.PasswordlessStorage; -import io.supertokens.pluginInterface.passwordless.UserInfo; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.TestingProcessManager; import io.supertokens.test.Utils; @@ -256,7 +255,7 @@ public void testConsumeCodeCleanupUserInputCodeWithEmailAndPhoneNumber() throws PasswordlessStorage storage = (PasswordlessStorage) StorageLayer.getStorage(process.getProcess()); - UserInfo user; + AuthRecipeUserInfo user; Passwordless.CreateCodeResponse createCodeResponse = Passwordless.createCode(process.getProcess(), EMAIL, null, null, null); @@ -331,7 +330,7 @@ public void testConsumeCodeCleanupLinkCodeWithEmailAndPhoneNumber() throws Excep PasswordlessStorage storage = (PasswordlessStorage) StorageLayer.getStorage(process.getProcess()); - UserInfo user; + AuthRecipeUserInfo user; Passwordless.CreateCodeResponse createCodeResponse = Passwordless.createCode(process.getProcess(), EMAIL, null, null, null); diff --git a/src/test/java/io/supertokens/test/passwordless/PasswordlessGetUserTest.java b/src/test/java/io/supertokens/test/passwordless/PasswordlessGetUserTest.java index a91ccf1d2..59825b0d9 100644 --- a/src/test/java/io/supertokens/test/passwordless/PasswordlessGetUserTest.java +++ b/src/test/java/io/supertokens/test/passwordless/PasswordlessGetUserTest.java @@ -20,7 +20,6 @@ import io.supertokens.passwordless.Passwordless; import io.supertokens.pluginInterface.STORAGE_TYPE; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; -import io.supertokens.pluginInterface.passwordless.UserInfo; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.TestingProcessManager; import io.supertokens.test.Utils; @@ -71,9 +70,9 @@ public void getUserByIdWithEmail() throws Exception { Passwordless.ConsumeCodeResponse consumeCodeResponse = createUserWith(process, EMAIL, null); - UserInfo user = Passwordless.getUserById(process.getProcess(), consumeCodeResponse.user.getSupertokensUserId()); + AuthRecipeUserInfo user = Passwordless.getUserById(process.getProcess(), consumeCodeResponse.user.getSupertokensUserId()); assertNotNull(user); - assertEquals(user.email, EMAIL); + assertEquals(user.loginMethods[0].email, EMAIL); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -98,9 +97,9 @@ public void getUserByIdWithPhoneNumber() throws Exception { Passwordless.ConsumeCodeResponse consumeCodeResponse = createUserWith(process, null, PHONE_NUMBER); - UserInfo user = Passwordless.getUserById(process.getProcess(), consumeCodeResponse.user.getSupertokensUserId()); + AuthRecipeUserInfo user = Passwordless.getUserById(process.getProcess(), consumeCodeResponse.user.getSupertokensUserId()); assertNotNull(user); - assertEquals(user.phoneNumber, PHONE_NUMBER); + assertEquals(user.loginMethods[0].phoneNumber, PHONE_NUMBER); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -125,10 +124,10 @@ public void getUserByIdWithEmailAndPhoneNumber() throws Exception { Passwordless.ConsumeCodeResponse consumeCodeResponse = createUserWith(process, EMAIL, PHONE_NUMBER); - UserInfo user = Passwordless.getUserById(process.getProcess(), consumeCodeResponse.user.getSupertokensUserId()); + AuthRecipeUserInfo user = Passwordless.getUserById(process.getProcess(), consumeCodeResponse.user.getSupertokensUserId()); assertNotNull(user); - assertEquals(user.email, EMAIL); - assertEquals(user.phoneNumber, PHONE_NUMBER); + assertEquals(user.loginMethods[0].email, EMAIL); + assertEquals(user.loginMethods[0].phoneNumber, PHONE_NUMBER); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); @@ -153,7 +152,7 @@ public void getUserByInvalidId() throws Exception { Passwordless.ConsumeCodeResponse consumeCodeResponse = createUserWith(process, EMAIL, null); - UserInfo user = Passwordless.getUserById(process.getProcess(), consumeCodeResponse.user.getSupertokensUserId() + "1"); + AuthRecipeUserInfo user = Passwordless.getUserById(process.getProcess(), consumeCodeResponse.user.getSupertokensUserId() + "1"); assertNull(user); process.kill(); diff --git a/src/test/java/io/supertokens/test/thirdparty/ThirdPartyTest.java b/src/test/java/io/supertokens/test/thirdparty/ThirdPartyTest.java index dd0863086..bf1da6392 100644 --- a/src/test/java/io/supertokens/test/thirdparty/ThirdPartyTest.java +++ b/src/test/java/io/supertokens/test/thirdparty/ThirdPartyTest.java @@ -22,7 +22,6 @@ import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.authRecipe.LoginMethod; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; -import io.supertokens.pluginInterface.thirdparty.UserInfo; import io.supertokens.pluginInterface.thirdparty.exception.DuplicateThirdPartyUserException; import io.supertokens.pluginInterface.thirdparty.exception.DuplicateUserIdException; import io.supertokens.pluginInterface.thirdparty.sqlStorage.ThirdPartySQLStorage; @@ -389,12 +388,12 @@ public void testGetUserFunctions() throws Exception { checkSignInUpResponse(signUpResponse, thirdPartyUserId, thirdPartyId, email, true); - UserInfo getUserInfoFromId = ThirdParty.getUser(process.getProcess(), signUpResponse.user.getSupertokensUserId()); + AuthRecipeUserInfo getUserInfoFromId = ThirdParty.getUser(process.getProcess(), signUpResponse.user.getSupertokensUserId()); assertEquals(getUserInfoFromId.getSupertokensUserId(), signUpResponse.user.getSupertokensUserId()); assertEquals(getUserInfoFromId.timeJoined, signUpResponse.user.timeJoined); - assertEquals(getUserInfoFromId.email, signUpResponse.user.loginMethods[0].email); - assertEquals(getUserInfoFromId.thirdParty.userId, signUpResponse.user.loginMethods[0].thirdParty.userId); - assertEquals(getUserInfoFromId.thirdParty.id, signUpResponse.user.loginMethods[0].thirdParty.id); + assertEquals(getUserInfoFromId.loginMethods[0].email, signUpResponse.user.loginMethods[0].email); + assertEquals(getUserInfoFromId.loginMethods[0].thirdParty.userId, signUpResponse.user.loginMethods[0].thirdParty.userId); + assertEquals(getUserInfoFromId.loginMethods[0].thirdParty.id, signUpResponse.user.loginMethods[0].thirdParty.id); AuthRecipeUserInfo getUserInfoFromThirdParty = ThirdParty.getUser(process.getProcess(), signUpResponse.user.loginMethods[0].thirdParty.id, diff --git a/src/test/java/io/supertokens/test/thirdparty/ThirdPartyTest2_7.java b/src/test/java/io/supertokens/test/thirdparty/ThirdPartyTest2_7.java index 16f63fbd0..d7b7818cc 100644 --- a/src/test/java/io/supertokens/test/thirdparty/ThirdPartyTest2_7.java +++ b/src/test/java/io/supertokens/test/thirdparty/ThirdPartyTest2_7.java @@ -22,7 +22,6 @@ import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.authRecipe.LoginMethod; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; -import io.supertokens.pluginInterface.thirdparty.UserInfo; import io.supertokens.pluginInterface.thirdparty.exception.DuplicateThirdPartyUserException; import io.supertokens.pluginInterface.thirdparty.exception.DuplicateUserIdException; import io.supertokens.pluginInterface.thirdparty.sqlStorage.ThirdPartySQLStorage; @@ -401,12 +400,12 @@ public void testGetUserFunctions() throws Exception { checkSignInUpResponse(signUpResponse, thirdPartyUserId, thirdPartyId, email, true); - UserInfo getUserInfoFromId = ThirdParty.getUser(process.getProcess(), signUpResponse.user.getSupertokensUserId()); + AuthRecipeUserInfo getUserInfoFromId = ThirdParty.getUser(process.getProcess(), signUpResponse.user.getSupertokensUserId()); assertEquals(getUserInfoFromId.getSupertokensUserId(), signUpResponse.user.getSupertokensUserId()); assertEquals(getUserInfoFromId.timeJoined, signUpResponse.user.timeJoined); - assertEquals(getUserInfoFromId.email, signUpResponse.user.loginMethods[0].email); - assertEquals(getUserInfoFromId.thirdParty.userId, signUpResponse.user.loginMethods[0].thirdParty.userId); - assertEquals(getUserInfoFromId.thirdParty.id, signUpResponse.user.loginMethods[0].thirdParty.id); + assertEquals(getUserInfoFromId.loginMethods[0].email, signUpResponse.user.loginMethods[0].email); + assertEquals(getUserInfoFromId.loginMethods[0].thirdParty.userId, signUpResponse.user.loginMethods[0].thirdParty.userId); + assertEquals(getUserInfoFromId.loginMethods[0].thirdParty.id, signUpResponse.user.loginMethods[0].thirdParty.id); AuthRecipeUserInfo getUserInfoFromThirdParty = ThirdParty.getUser(process.getProcess(), signUpResponse.user.loginMethods[0].thirdParty.id, diff --git a/src/test/java/io/supertokens/test/thirdparty/api/ThirdPartySignInUpAPITest4_0.java b/src/test/java/io/supertokens/test/thirdparty/api/ThirdPartySignInUpAPITest4_0.java index f83a5db3b..09cc93136 100644 --- a/src/test/java/io/supertokens/test/thirdparty/api/ThirdPartySignInUpAPITest4_0.java +++ b/src/test/java/io/supertokens/test/thirdparty/api/ThirdPartySignInUpAPITest4_0.java @@ -24,7 +24,7 @@ import io.supertokens.featureflag.EE_FEATURES; import io.supertokens.featureflag.FeatureFlagTestContent; import io.supertokens.pluginInterface.STORAGE_TYPE; -import io.supertokens.pluginInterface.emailpassword.UserInfo; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.TestingProcessManager; import io.supertokens.test.Utils; @@ -133,7 +133,7 @@ public void testNotAllowedUpdateOfEmail() throws Exception { return; } - UserInfo user0 = EmailPassword.signUp(process.getProcess(), "someemail1@gmail.com", "somePass"); + AuthRecipeUserInfo user0 = EmailPassword.signUp(process.getProcess(), "someemail1@gmail.com", "somePass"); AuthRecipe.createPrimaryUser(process.main, user0.getSupertokensUserId()); ThirdParty.SignInUpResponse signInUpResponse = ThirdParty.signInUp(process.getProcess(), "google", "user", diff --git a/src/test/java/io/supertokens/test/totp/api/TotpUserIdMappingTest.java b/src/test/java/io/supertokens/test/totp/api/TotpUserIdMappingTest.java index a263439bd..7e135958c 100644 --- a/src/test/java/io/supertokens/test/totp/api/TotpUserIdMappingTest.java +++ b/src/test/java/io/supertokens/test/totp/api/TotpUserIdMappingTest.java @@ -5,7 +5,7 @@ import io.supertokens.emailpassword.EmailPassword; import io.supertokens.featureflag.EE_FEATURES; import io.supertokens.featureflag.FeatureFlagTestContent; -import io.supertokens.pluginInterface.emailpassword.UserInfo; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.totp.TOTPDevice; import io.supertokens.pluginInterface.STORAGE_TYPE; import io.supertokens.storageLayer.StorageLayer; @@ -52,7 +52,7 @@ public void testExternalUserIdTranslation() throws Exception { JsonObject body = new JsonObject(); - UserInfo user = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); + AuthRecipeUserInfo user = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); String superTokensUserId = user.getSupertokensUserId(); String externalUserId = "external-user-id"; diff --git a/src/test/java/io/supertokens/test/userIdMapping/UserIdMappingStorageTest.java b/src/test/java/io/supertokens/test/userIdMapping/UserIdMappingStorageTest.java index 0e63a27b9..26e5bf129 100644 --- a/src/test/java/io/supertokens/test/userIdMapping/UserIdMappingStorageTest.java +++ b/src/test/java/io/supertokens/test/userIdMapping/UserIdMappingStorageTest.java @@ -20,7 +20,7 @@ import io.supertokens.authRecipe.AuthRecipe; import io.supertokens.emailpassword.EmailPassword; import io.supertokens.pluginInterface.STORAGE_TYPE; -import io.supertokens.pluginInterface.emailpassword.UserInfo; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.useridmapping.UserIdMapping; import io.supertokens.pluginInterface.useridmapping.UserIdMappingStorage; @@ -96,7 +96,7 @@ public void testCreatingUserIdMapping() throws Exception { UserIdMappingStorage storage = (UserIdMappingStorage) StorageLayer.getStorage(process.main); // create a user - UserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPassword"); + AuthRecipeUserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPassword"); String externalUserId = "external-test"; String externalUserIdInfo = "external-info"; @@ -129,7 +129,7 @@ public void testDuplicateUserIdMapping() throws Exception { UserIdMappingStorage storage = (UserIdMappingStorage) StorageLayer.getStorage(process.main); // create a user - UserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPassword"); + AuthRecipeUserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPassword"); String externalUserId = "external-test"; storage.createUserIdMapping(new AppIdentifier(null, null), userInfo.getSupertokensUserId(), externalUserId, null); @@ -172,7 +172,7 @@ public void testDuplicateUserIdMapping() throws Exception { { // duplicate exception with externalUserId - UserInfo newUser = EmailPassword.signUp(process.main, "test2@example.com", "testPass123"); + AuthRecipeUserInfo newUser = EmailPassword.signUp(process.main, "test2@example.com", "testPass123"); Exception error = null; try { storage.createUserIdMapping(new AppIdentifier(null, null), newUser.getSupertokensUserId(), externalUserId, null); @@ -206,7 +206,7 @@ public void testCreatingAMappingWithAnUnknownStUserIdAndAPreexistingExternalUser UserIdMappingStorage storage = (UserIdMappingStorage) StorageLayer.getStorage(process.main); // create a User - UserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); + AuthRecipeUserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); String externalUserId = "externalUserId"; // create a userId mapping @@ -279,7 +279,7 @@ public void testRetrievingUserIdMapping() throws Exception { UserIdMappingStorage storage = (UserIdMappingStorage) StorageLayer.getStorage(process.main); // create a user - UserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); + AuthRecipeUserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); String externalUserId = "externalUserId"; String externalUserIdInfo = "externalUserIdInfo"; @@ -333,7 +333,7 @@ public void testRetrievingUserIdMapping() throws Exception { // superTokensUserId { - UserInfo newUserInfo = EmailPassword.signUp(process.main, "test2@example.com", "testPass123"); + AuthRecipeUserInfo newUserInfo = EmailPassword.signUp(process.main, "test2@example.com", "testPass123"); String externalUserId2 = userInfo.getSupertokensUserId(); storage.createUserIdMapping(new AppIdentifier(null, null), newUserInfo.getSupertokensUserId(), externalUserId2, null); @@ -398,7 +398,7 @@ public void testDeletingAUserIdMapping() throws Exception { UserIdMappingStorage storage = (UserIdMappingStorage) StorageLayer.getStorage(process.main); // create a user - UserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); + AuthRecipeUserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); String superTokensUserId = userInfo.getSupertokensUserId(); String externalUserId = "externalUserId"; { @@ -492,7 +492,7 @@ public void testUpdatingExternalUserIdInfo() throws Exception { UserIdMappingStorage storage = (UserIdMappingStorage) StorageLayer.getStorage(process.main); // create User - UserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); + AuthRecipeUserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); String superTokensUserId = userInfo.getSupertokensUserId(); String externalUserId = "externalId"; @@ -573,7 +573,7 @@ public void createUsersMapTheirIdsCheckRetrieveUseIdMappingsWithListOfUserIds() // create users equal to the User Pagination limit for (int i = 1; i <= AuthRecipe.USER_PAGINATION_LIMIT; i++) { - UserInfo userInfo = EmailPassword.signUp(process.main, "test" + i + "@example.com", "testPass123"); + AuthRecipeUserInfo userInfo = EmailPassword.signUp(process.main, "test" + i + "@example.com", "testPass123"); superTokensUserIdList.add(userInfo.getSupertokensUserId()); String superTokensUserId = userInfo.getSupertokensUserId(); String externalUserId = "externalId" + i; @@ -627,7 +627,7 @@ public void testCallingGetUserIdMappingForSuperTokensIdsWhenNoMappingExists() th ArrayList superTokensUserIdList = new ArrayList<>(); for (int i = 1; i <= 10; i++) { - UserInfo userInfo = EmailPassword.signUp(process.main, "test" + i + "@example.com", "testPass123"); + AuthRecipeUserInfo userInfo = EmailPassword.signUp(process.main, "test" + i + "@example.com", "testPass123"); superTokensUserIdList.add(userInfo.getSupertokensUserId()); } @@ -654,7 +654,7 @@ public void create10UsersAndMap5UsersIds() throws Exception { // create users equal to the User Pagination limit for (int i = 1; i <= 10; i++) { - UserInfo userInfo = EmailPassword.signUp(process.main, "test" + i + "@example.com", "testPass123"); + AuthRecipeUserInfo userInfo = EmailPassword.signUp(process.main, "test" + i + "@example.com", "testPass123"); superTokensUserIdList.add(userInfo.getSupertokensUserId()); if (i <= 5) { diff --git a/src/test/java/io/supertokens/test/userIdMapping/UserIdMappingTest.java b/src/test/java/io/supertokens/test/userIdMapping/UserIdMappingTest.java index d61aa3954..9a6cfb33a 100644 --- a/src/test/java/io/supertokens/test/userIdMapping/UserIdMappingTest.java +++ b/src/test/java/io/supertokens/test/userIdMapping/UserIdMappingTest.java @@ -24,7 +24,7 @@ import io.supertokens.featureflag.FeatureFlagTestContent; import io.supertokens.pluginInterface.ActiveUsersStorage; import io.supertokens.pluginInterface.STORAGE_TYPE; -import io.supertokens.pluginInterface.emailpassword.UserInfo; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.nonAuthRecipe.NonAuthRecipeStorage; @@ -103,7 +103,7 @@ public void testDuplicateUserIdMapping() throws Exception { } // create a user - UserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPassword"); + AuthRecipeUserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPassword"); String externalUserId = "external-test"; @@ -147,7 +147,7 @@ public void testDuplicateUserIdMapping() throws Exception { { // duplicate exception with externalUserId - UserInfo newUser = EmailPassword.signUp(process.main, "test2@example.com", "testPass123"); + AuthRecipeUserInfo newUser = EmailPassword.signUp(process.main, "test2@example.com", "testPass123"); Exception error = null; try { UserIdMapping.createUserIdMapping(process.main, newUser.getSupertokensUserId(), externalUserId, null, false); @@ -181,7 +181,7 @@ public void testCreatingUserIdMapping() throws Exception { UserIdMappingStorage storage = (UserIdMappingStorage) StorageLayer.getStorage(process.main); // create a user - UserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPassword"); + AuthRecipeUserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPassword"); String externalUserId = "external-test"; String externalUserIdInfo = "external-info"; @@ -235,7 +235,7 @@ public void testRetrievingUserIdMapping() throws Exception { } // create a User and then a UserId mapping - UserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); + AuthRecipeUserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); String superTokensUserId = userInfo.getSupertokensUserId(); String externalUserId = "externalId"; String externalUserIdInfo = "externalIdInfo"; @@ -292,7 +292,7 @@ public void testRetrievingUserIdMapping() throws Exception { } // create a new mapping where the superTokensUserId of Mapping1 = externalUserId of Mapping2 - UserInfo userInfo2 = EmailPassword.signUp(process.main, "test2@example.com", "testPass123"); + AuthRecipeUserInfo userInfo2 = EmailPassword.signUp(process.main, "test2@example.com", "testPass123"); String newSuperTokensUserId = userInfo2.getSupertokensUserId(); String newExternalUserId = userInfo.getSupertokensUserId(); String newExternalUserIdInfo = "newExternalUserIdInfo"; @@ -369,7 +369,7 @@ public void testDeletingUserIdMapping() throws Exception { } // create mapping and check that it exists - UserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); + AuthRecipeUserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); String superTokensUserId = userInfo.getSupertokensUserId(); String externalUserId = "externalId"; String externalUserIdInfo = "externalIdInfo"; @@ -475,7 +475,7 @@ public void testDeletingUserIdMappingWithSharedId() throws Exception { // Create UserId mapping 1 - UserInfo userInfo_1 = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); + AuthRecipeUserInfo userInfo_1 = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); io.supertokens.pluginInterface.useridmapping.UserIdMapping userIdMapping_1 = new io.supertokens.pluginInterface.useridmapping.UserIdMapping( userInfo_1.getSupertokensUserId(), "externalUserId", "externalUserIdInfo"); @@ -491,7 +491,7 @@ public void testDeletingUserIdMappingWithSharedId() throws Exception { // Create UserId mapping 2 - UserInfo userInfo_2 = EmailPassword.signUp(process.main, "test2@example.com", "testPass123"); + AuthRecipeUserInfo userInfo_2 = EmailPassword.signUp(process.main, "test2@example.com", "testPass123"); io.supertokens.pluginInterface.useridmapping.UserIdMapping userIdMapping_2 = new io.supertokens.pluginInterface.useridmapping.UserIdMapping( userInfo_2.getSupertokensUserId(), userIdMapping_1.superTokensUserId, "externalUserIdInfo2"); @@ -570,7 +570,7 @@ public void testUpdatingExternalUserIdInfo() throws Exception { } // create User - UserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); + AuthRecipeUserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); String superTokensUserId = userInfo.getSupertokensUserId(); String externalUserId = "externalId"; @@ -665,7 +665,7 @@ public void testUpdatingExternalUserIdInfoWithSharedUserIds() throws Exception { // create two UserMappings where superTokensUserId in Mapping 1 = externalUserId in Mapping 2 // Create mapping 1 - UserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); + AuthRecipeUserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); String superTokensUserId = userInfo.getSupertokensUserId(); String externalUserId = "externalId"; @@ -684,7 +684,7 @@ public void testUpdatingExternalUserIdInfoWithSharedUserIds() throws Exception { } // Create mapping 2 - UserInfo userInfo2 = EmailPassword.signUp(process.main, "test2@example.com", "testPass123"); + AuthRecipeUserInfo userInfo2 = EmailPassword.signUp(process.main, "test2@example.com", "testPass123"); String superTokensUserId2 = userInfo2.getSupertokensUserId(); String externalUserId2 = userInfo.getSupertokensUserId(); String externalUserIdInfo2 = "newExternalUserIdInfo"; @@ -750,7 +750,7 @@ public void testUpdatingTheExternalUserIdInfoOfAMappingWithTheSameValue() throws // create a userIdMapping with externalUserIdInfo as null and update it to null { - UserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); + AuthRecipeUserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); String superTokensUserId = userInfo.getSupertokensUserId(); String externalUserId = "externalUserId"; @@ -763,7 +763,7 @@ public void testUpdatingTheExternalUserIdInfoOfAMappingWithTheSameValue() throws } { - UserInfo userInfo = EmailPassword.signUp(process.main, "test2@example.com", "testPass123"); + AuthRecipeUserInfo userInfo = EmailPassword.signUp(process.main, "test2@example.com", "testPass123"); String superTokensUserId = userInfo.getSupertokensUserId(); String externalUserId = "newExternalUserIdInfo"; String externalUserIdInfo = "externalUserIdInfo"; @@ -808,7 +808,7 @@ public void checkThatCreateUserIdMappingHasAllNonAuthRecipeChecks() throws Excep } for (String className : classNames) { - UserInfo user = EmailPassword.signUp(process.main, "test@example.com", "password"); + AuthRecipeUserInfo user = EmailPassword.signUp(process.main, "test@example.com", "password"); String userId = user.getSupertokensUserId(); // create entry in nonAuth table @@ -887,7 +887,7 @@ public void checkThatDeleteUserIdMappingHasAllNonAuthRecipeChecks() throws Excep String externalId = "externalId"; for (String className : classNames) { // Create a User - UserInfo user = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); + AuthRecipeUserInfo user = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); // create a mapping with the user UserIdMapping.createUserIdMapping(process.main, user.getSupertokensUserId(), externalId, null, false); @@ -927,7 +927,7 @@ public void checkThatWeDontAllowDBStateA5FromBeingCreatedWhenForceIsFalse() thro } // create an EmailPassword User - UserInfo user_1 = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); + AuthRecipeUserInfo user_1 = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); // create a mapping for the EmailPassword User UserIdMapping.createUserIdMapping(process.main, user_1.getSupertokensUserId(), "externalId", null, false); @@ -937,7 +937,7 @@ public void checkThatWeDontAllowDBStateA5FromBeingCreatedWhenForceIsFalse() thro UserMetadata.updateUserMetadata(process.main, "externalId", data); // Create another User - UserInfo user_2 = EmailPassword.signUp(process.main, "test123@example.com", "testPass123"); + AuthRecipeUserInfo user_2 = EmailPassword.signUp(process.main, "test123@example.com", "testPass123"); // try and map user_2 to user_1s superTokensUserId String errorMessage = null; @@ -964,10 +964,10 @@ public void testThatWeDontAllowDBStateA6WithoutForce() throws Exception { } // create user 1 - UserInfo user_1 = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); + AuthRecipeUserInfo user_1 = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); // create user 2 - UserInfo user_2 = EmailPassword.signUp(process.main, "test123@example.com", "testPass123"); + AuthRecipeUserInfo user_2 = EmailPassword.signUp(process.main, "test123@example.com", "testPass123"); // create a mapping between User_1 and User_2 with force UserIdMapping.createUserIdMapping(process.main, user_1.getSupertokensUserId(), user_2.getSupertokensUserId(), null, true); @@ -1002,8 +1002,8 @@ public void testDeleteMappingWithUser_1AndUserIdTypeAsAny() throws Exception { } // create User_1 and User_2 - UserInfo user_1 = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); - UserInfo user_2 = EmailPassword.signUp(process.main, "test123@exmaple.com", "testPass123"); + AuthRecipeUserInfo user_1 = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); + AuthRecipeUserInfo user_2 = EmailPassword.signUp(process.main, "test123@exmaple.com", "testPass123"); // create a mapping between User_2 and User_1 with force UserIdMapping.createUserIdMapping(process.main, user_2.getSupertokensUserId(), user_1.getSupertokensUserId(), null, true); @@ -1046,8 +1046,8 @@ public void testDeleteMappingWithUser_1AndUserIdTypeAsSUPERTOKENS() throws Excep } // create User_1 and User_2 - UserInfo user_1 = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); - UserInfo user_2 = EmailPassword.signUp(process.main, "test123@exmaple.com", "testPass123"); + AuthRecipeUserInfo user_1 = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); + AuthRecipeUserInfo user_2 = EmailPassword.signUp(process.main, "test123@exmaple.com", "testPass123"); // create a mapping between User_2 and User_1 with force UserIdMapping.createUserIdMapping(process.main, user_2.getSupertokensUserId(), user_1.getSupertokensUserId(), null, true); diff --git a/src/test/java/io/supertokens/test/userIdMapping/api/CreateUserIdMappingAPITest.java b/src/test/java/io/supertokens/test/userIdMapping/api/CreateUserIdMappingAPITest.java index b2219b3c9..8cc8d4e16 100644 --- a/src/test/java/io/supertokens/test/userIdMapping/api/CreateUserIdMappingAPITest.java +++ b/src/test/java/io/supertokens/test/userIdMapping/api/CreateUserIdMappingAPITest.java @@ -21,7 +21,7 @@ import io.supertokens.ProcessState; import io.supertokens.emailpassword.EmailPassword; import io.supertokens.pluginInterface.STORAGE_TYPE; -import io.supertokens.pluginInterface.emailpassword.UserInfo; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.useridmapping.UserIdMapping; import io.supertokens.pluginInterface.useridmapping.UserIdMappingStorage; @@ -224,7 +224,7 @@ public void testCreatingAUserIdMappingWithAndWithoutForce() throws Exception { } // create a User and add some non auth recipe info - UserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); + AuthRecipeUserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); // add some metadata to the user JsonObject userMetadata = new JsonObject(); @@ -281,7 +281,7 @@ public void testCreatingAUserIdMapping() throws Exception { UserIdMappingStorage storage = (UserIdMappingStorage) StorageLayer.getStorage(process.main); // create a User - UserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); + AuthRecipeUserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); String superTokensUserId = userInfo.getSupertokensUserId(); String externalUserId = "userId"; String externalUserIdInfo = "externUserIdInfo"; @@ -353,7 +353,7 @@ public void testCreatingUserIdMappingWithExternalUserIdInfoAsNull() throws Excep return; } - UserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); + AuthRecipeUserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); String externalUserId = "externalUserId"; JsonObject requestBody = new JsonObject(); requestBody.addProperty("superTokensUserId", userInfo.getSupertokensUserId()); @@ -390,7 +390,7 @@ public void testCreatingDuplicateUserIdMapping() throws Exception { return; } - UserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); + AuthRecipeUserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); String superTokensUserId = userInfo.getSupertokensUserId(); String externalUserId = "externalUserId"; @@ -436,7 +436,7 @@ public void testCreatingDuplicateUserIdMapping() throws Exception { { // create a duplicate mapping with externalUserId - UserInfo newUserInfo = EmailPassword.signUp(process.main, "test2@example.com", "testPass123"); + AuthRecipeUserInfo newUserInfo = EmailPassword.signUp(process.main, "test2@example.com", "testPass123"); JsonObject requestBody = new JsonObject(); diff --git a/src/test/java/io/supertokens/test/userIdMapping/api/GetUserIdMappingAPITest.java b/src/test/java/io/supertokens/test/userIdMapping/api/GetUserIdMappingAPITest.java index c06075e56..4544013c8 100644 --- a/src/test/java/io/supertokens/test/userIdMapping/api/GetUserIdMappingAPITest.java +++ b/src/test/java/io/supertokens/test/userIdMapping/api/GetUserIdMappingAPITest.java @@ -20,7 +20,7 @@ import io.supertokens.ProcessState; import io.supertokens.emailpassword.EmailPassword; import io.supertokens.pluginInterface.STORAGE_TYPE; -import io.supertokens.pluginInterface.emailpassword.UserInfo; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.TestingProcessManager; import io.supertokens.test.Utils; @@ -196,7 +196,7 @@ public void testRetrieveUserIdMapping() throws Exception { } // create a user and map their userId to an external userId - UserInfo user = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); + AuthRecipeUserInfo user = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); String superTokensUserId = user.getSupertokensUserId(); String externalUserId = "externalUserId"; String externalUserIdInfo = "externalUserIdInfo"; @@ -290,7 +290,7 @@ public void testRetrievingUserIdMappingWithoutSendingUserIdType() throws Excepti } // create a user and map their userId to an external userId - UserInfo user = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); + AuthRecipeUserInfo user = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); String superTokensUserId = user.getSupertokensUserId(); String externalUserId = "externalUserId"; String externalUserIdInfo = "externalUserIdInfo"; @@ -345,7 +345,7 @@ public void testRetrieveUserIdMappingWithExternalUserIdInfoAsNull() throws Excep } // create a user and map their userId to an external userId - UserInfo user = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); + AuthRecipeUserInfo user = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); String superTokensUserId = user.getSupertokensUserId(); String externalUserId = "externalUserId"; diff --git a/src/test/java/io/supertokens/test/userIdMapping/api/RemoveUserIdMappingAPITest.java b/src/test/java/io/supertokens/test/userIdMapping/api/RemoveUserIdMappingAPITest.java index 5a7eeb43c..225ba319f 100644 --- a/src/test/java/io/supertokens/test/userIdMapping/api/RemoveUserIdMappingAPITest.java +++ b/src/test/java/io/supertokens/test/userIdMapping/api/RemoveUserIdMappingAPITest.java @@ -20,7 +20,7 @@ import io.supertokens.ProcessState; import io.supertokens.emailpassword.EmailPassword; import io.supertokens.pluginInterface.STORAGE_TYPE; -import io.supertokens.pluginInterface.emailpassword.UserInfo; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.useridmapping.UserIdMapping; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.TestingProcessManager; @@ -230,7 +230,7 @@ public void testDeletingUserIdMapping() throws Exception { } // create a userId mapping - UserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); + AuthRecipeUserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); UserIdMapping userIdMapping = new UserIdMapping(userInfo.getSupertokensUserId(), "externalUserId", "externalUserIdInfo"); createUserIdMappingAndCheckThatItExists(process.main, userIdMapping); @@ -332,7 +332,7 @@ public void testDeletingAUserIdMappingWithoutSendingUserIdType() throws Exceptio } // create a userId mapping - UserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); + AuthRecipeUserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); UserIdMapping userIdMapping = new UserIdMapping(userInfo.getSupertokensUserId(), "externalUserId", "externalUserIdInfo"); createUserIdMappingAndCheckThatItExists(process.main, userIdMapping); @@ -389,7 +389,7 @@ public void deleteUserIdMappingWithAndWithoutForce() throws Exception { return; } - UserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); + AuthRecipeUserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); String superTokensUserId = userInfo.getSupertokensUserId(); String externalId = "externalId"; io.supertokens.useridmapping.UserIdMapping.createUserIdMapping(process.main, superTokensUserId, externalId, diff --git a/src/test/java/io/supertokens/test/userIdMapping/api/UpdateExternalUserIdInfoTest.java b/src/test/java/io/supertokens/test/userIdMapping/api/UpdateExternalUserIdInfoTest.java index e1e213e65..fe3f930cd 100644 --- a/src/test/java/io/supertokens/test/userIdMapping/api/UpdateExternalUserIdInfoTest.java +++ b/src/test/java/io/supertokens/test/userIdMapping/api/UpdateExternalUserIdInfoTest.java @@ -20,7 +20,7 @@ import io.supertokens.ProcessState; import io.supertokens.emailpassword.EmailPassword; import io.supertokens.pluginInterface.STORAGE_TYPE; -import io.supertokens.pluginInterface.emailpassword.UserInfo; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.useridmapping.UserIdMapping; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.TestingProcessManager; @@ -269,7 +269,7 @@ public void testUpdatingExternalUserIdInfoWithSuperTokensUserId() throws Excepti // create userId mapping with externalUserIdInfo String externalUserIdInfo = "externalUserIdInfo"; - UserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); + AuthRecipeUserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); UserIdMapping userIdMapping = new io.supertokens.pluginInterface.useridmapping.UserIdMapping(userInfo.getSupertokensUserId(), "externalUserIdInfo", externalUserIdInfo); @@ -340,7 +340,7 @@ public void testUpdatingExternalUserIdInfoWithExternalUserId() throws Exception // create userId mapping with externalUserIdInfo String externalUserIdInfo = "externalUserIdInfo"; - UserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); + AuthRecipeUserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); UserIdMapping userIdMapping = new io.supertokens.pluginInterface.useridmapping.UserIdMapping(userInfo.getSupertokensUserId(), "externalUserIdInfo", externalUserIdInfo); @@ -411,7 +411,7 @@ public void testDeletingExternalUserIdInfo() throws Exception { // create userId mapping with externalUserIdInfo String externalUserIdInfo = "externalUserIdInfo"; - UserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); + AuthRecipeUserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPass123"); UserIdMapping userIdMapping = new io.supertokens.pluginInterface.useridmapping.UserIdMapping(userInfo.getSupertokensUserId(), "externalUserIdInfo", externalUserIdInfo); diff --git a/src/test/java/io/supertokens/test/userIdMapping/recipe/EmailPasswordAPITest.java b/src/test/java/io/supertokens/test/userIdMapping/recipe/EmailPasswordAPITest.java index 077d3b26e..801d7bd6d 100644 --- a/src/test/java/io/supertokens/test/userIdMapping/recipe/EmailPasswordAPITest.java +++ b/src/test/java/io/supertokens/test/userIdMapping/recipe/EmailPasswordAPITest.java @@ -22,7 +22,6 @@ import io.supertokens.emailpassword.EmailPassword; import io.supertokens.pluginInterface.STORAGE_TYPE; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; -import io.supertokens.pluginInterface.emailpassword.UserInfo; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.TestingProcessManager; import io.supertokens.test.Utils; @@ -68,7 +67,7 @@ public void testSignInAPI() throws Exception { // create a User String email = "test@example.com"; String password = "testPass123"; - UserInfo userInfo = EmailPassword.signUp(process.main, email, password); + AuthRecipeUserInfo userInfo = EmailPassword.signUp(process.main, email, password); String superTokensUserId = userInfo.getSupertokensUserId(); String externalUserId = "externalId"; @@ -123,7 +122,7 @@ public void testResetPasswordFlowWithUserIdMapping() throws Exception { // create a User String email = "test@example.com"; String password = "testPass123"; - UserInfo userInfo = EmailPassword.signUp(process.main, email, password); + AuthRecipeUserInfo userInfo = EmailPassword.signUp(process.main, email, password); String superTokensUserId = userInfo.getSupertokensUserId(); String externalUserId = "externalId"; @@ -182,7 +181,7 @@ public void testRetrievingUser() throws Exception { // create a User String email = "test@example.com"; String password = "testPass123"; - UserInfo userInfo = EmailPassword.signUp(process.main, email, password); + AuthRecipeUserInfo userInfo = EmailPassword.signUp(process.main, email, password); String superTokensUserId = userInfo.getSupertokensUserId(); String externalUserId = "externalId"; @@ -228,7 +227,7 @@ public void testUpdatingUsersEmail() throws Exception { // create a User String email = "test@example.com"; String password = "testPass123"; - UserInfo userInfo = EmailPassword.signUp(process.main, email, password); + AuthRecipeUserInfo userInfo = EmailPassword.signUp(process.main, email, password); String superTokensUserId = userInfo.getSupertokensUserId(); String externalUserId = "externalId"; diff --git a/src/test/java/io/supertokens/test/userRoles/UserRolesTest.java b/src/test/java/io/supertokens/test/userRoles/UserRolesTest.java index 21c07fcd2..726001688 100644 --- a/src/test/java/io/supertokens/test/userRoles/UserRolesTest.java +++ b/src/test/java/io/supertokens/test/userRoles/UserRolesTest.java @@ -20,7 +20,7 @@ import io.supertokens.authRecipe.AuthRecipe; import io.supertokens.emailpassword.EmailPassword; import io.supertokens.pluginInterface.STORAGE_TYPE; -import io.supertokens.pluginInterface.emailpassword.UserInfo; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.userroles.exception.UnknownRoleException; @@ -1005,7 +1005,7 @@ public void createAnAuthUserAssignRolesAndDeleteUser() throws Exception { UserRoles.createNewRoleOrModifyItsPermissions(process.main, role, null); // Create an Auth User - UserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPassword"); + AuthRecipeUserInfo userInfo = EmailPassword.signUp(process.main, "test@example.com", "testPassword"); // assign role to user UserRoles.addRoleToUser(process.main, userInfo.getSupertokensUserId(), role); From 446d2820a4c6b81b2efacca1e1a85f06e27b7c74 Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Mon, 28 Aug 2023 17:01:08 +0530 Subject: [PATCH 097/131] fix: emailverified in tp & pless (#774) * fix: email verification in thirdparty and pless * fix: email verification * fix: more test for passwordless * fix: thirdparty and tests --- .../passwordless/Passwordless.java | 67 +++++- .../io/supertokens/thirdparty/ThirdParty.java | 45 +++- .../api/passwordless/ConsumeCodeAPI.java | 4 +- .../webserver/api/thirdparty/SignInUpAPI.java | 9 +- .../api/EmailVerificationTest.java | 200 ++++++++++++++++++ .../thirdparty/api/EmailVerificationTest.java | 192 +++++++++++++++++ 6 files changed, 512 insertions(+), 5 deletions(-) create mode 100644 src/test/java/io/supertokens/test/passwordless/api/EmailVerificationTest.java create mode 100644 src/test/java/io/supertokens/test/thirdparty/api/EmailVerificationTest.java diff --git a/src/main/java/io/supertokens/passwordless/Passwordless.java b/src/main/java/io/supertokens/passwordless/Passwordless.java index f0c1da86a..e6acfb4b4 100644 --- a/src/main/java/io/supertokens/passwordless/Passwordless.java +++ b/src/main/java/io/supertokens/passwordless/Passwordless.java @@ -251,12 +251,13 @@ public static ConsumeCodeResponse consumeCode(Main main, Storage storage = StorageLayer.getStorage(main); return consumeCode( new TenantIdentifierWithStorage(null, null, null, storage), - main, deviceId, deviceIdHashFromUser, userInputCode, linkCode); + main, deviceId, deviceIdHashFromUser, userInputCode, linkCode, false); } catch (TenantOrAppNotFoundException | BadPermissionException e) { throw new IllegalStateException(e); } } + @TestOnly public static ConsumeCodeResponse consumeCode(TenantIdentifierWithStorage tenantIdentifierWithStorage, Main main, String deviceId, String deviceIdHashFromUser, String userInputCode, String linkCode) @@ -264,6 +265,17 @@ public static ConsumeCodeResponse consumeCode(TenantIdentifierWithStorage tenant IncorrectUserInputCodeException, DeviceIdHashMismatchException, StorageTransactionLogicException, StorageQueryException, NoSuchAlgorithmException, InvalidKeyException, IOException, Base64EncodingException, TenantOrAppNotFoundException, BadPermissionException { + return consumeCode(tenantIdentifierWithStorage, main, deviceId, deviceIdHashFromUser, userInputCode, linkCode, + false); + } + + public static ConsumeCodeResponse consumeCode(TenantIdentifierWithStorage tenantIdentifierWithStorage, Main main, + String deviceId, String deviceIdHashFromUser, + String userInputCode, String linkCode, boolean setEmailVerified) + throws RestartFlowException, ExpiredUserInputCodeException, + IncorrectUserInputCodeException, DeviceIdHashMismatchException, StorageTransactionLogicException, + StorageQueryException, NoSuchAlgorithmException, InvalidKeyException, IOException, Base64EncodingException, + TenantOrAppNotFoundException, BadPermissionException { TenantConfig config = Multitenancy.getTenantInfo(main, tenantIdentifierWithStorage); if (config == null) { @@ -417,6 +429,33 @@ public static ConsumeCodeResponse consumeCode(TenantIdentifierWithStorage tenant long timeJoined = System.currentTimeMillis(); user = passwordlessStorage.createUser(tenantIdentifierWithStorage, userId, consumedDevice.email, consumedDevice.phoneNumber, timeJoined); + + // Set email as verified, if using email + if (setEmailVerified && consumedDevice.email != null) { + try { + AuthRecipeUserInfo finalUser = user; + tenantIdentifierWithStorage.getEmailVerificationStorage().startTransaction(con -> { + try { + tenantIdentifierWithStorage.getEmailVerificationStorage() + .updateIsEmailVerified_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), con, + finalUser.getSupertokensUserId(), consumedDevice.email, true); + tenantIdentifierWithStorage.getEmailVerificationStorage() + .commitTransaction(con); + + return null; + } catch (TenantOrAppNotFoundException e) { + throw new StorageTransactionLogicException(e); + } + }); + user.loginMethods[0].setVerified(); // newly created user has only one loginMethod + } catch (StorageTransactionLogicException e) { + if (e.actualException instanceof TenantOrAppNotFoundException) { + throw (TenantOrAppNotFoundException) e.actualException; + } + throw new StorageQueryException(e); + } + } + return new ConsumeCodeResponse(true, user, consumedDevice.email, consumedDevice.phoneNumber); } catch (DuplicateEmailException | DuplicatePhoneNumberException e) { // Getting these would mean that between getting the user and trying creating it: @@ -433,6 +472,32 @@ public static ConsumeCodeResponse consumeCode(TenantIdentifierWithStorage tenant } else { // We do not need this cleanup if we are creating the user, since it uses the email/phoneNumber of the // device, which has already been cleaned up + if (setEmailVerified && consumedDevice.email != null) { + // Set email verification + try { + LoginMethod finalLoginMethod = loginMethod; + tenantIdentifierWithStorage.getEmailVerificationStorage().startTransaction(con -> { + try { + tenantIdentifierWithStorage.getEmailVerificationStorage() + .updateIsEmailVerified_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), con, + finalLoginMethod.getSupertokensUserId(), consumedDevice.email, true); + tenantIdentifierWithStorage.getEmailVerificationStorage() + .commitTransaction(con); + + return null; + } catch (TenantOrAppNotFoundException e) { + throw new StorageTransactionLogicException(e); + } + }); + loginMethod.setVerified(); + } catch (StorageTransactionLogicException e) { + if (e.actualException instanceof TenantOrAppNotFoundException) { + throw (TenantOrAppNotFoundException) e.actualException; + } + throw new StorageQueryException(e); + } + } + if (loginMethod.email != null && !loginMethod.email.equals(consumedDevice.email)) { removeCodesByEmail(tenantIdentifierWithStorage, loginMethod.email); } diff --git a/src/main/java/io/supertokens/thirdparty/ThirdParty.java b/src/main/java/io/supertokens/thirdparty/ThirdParty.java index 24d3826c7..da6629b4a 100644 --- a/src/main/java/io/supertokens/thirdparty/ThirdParty.java +++ b/src/main/java/io/supertokens/thirdparty/ThirdParty.java @@ -120,17 +120,26 @@ public static SignInUpResponse signInUp(Main main, String thirdPartyId, String t Storage storage = StorageLayer.getStorage(main); return signInUp( new TenantIdentifierWithStorage(null, null, null, storage), main, - thirdPartyId, thirdPartyUserId, email); + thirdPartyId, thirdPartyUserId, email, false); } catch (TenantOrAppNotFoundException | BadPermissionException e) { throw new IllegalStateException(e); } } + @TestOnly public static SignInUpResponse signInUp(TenantIdentifierWithStorage tenantIdentifierWithStorage, Main main, String thirdPartyId, String thirdPartyUserId, String email) throws StorageQueryException, TenantOrAppNotFoundException, BadPermissionException, EmailChangeNotAllowedException { + return signInUp(tenantIdentifierWithStorage, main, thirdPartyId, thirdPartyUserId, email, false); + } + + public static SignInUpResponse signInUp(TenantIdentifierWithStorage tenantIdentifierWithStorage, Main main, + String thirdPartyId, + String thirdPartyUserId, String email, boolean isEmailVerified) + throws StorageQueryException, TenantOrAppNotFoundException, BadPermissionException, + EmailChangeNotAllowedException { TenantConfig config = Multitenancy.getTenantInfo(main, tenantIdentifierWithStorage); if (config == null) { @@ -140,7 +149,39 @@ public static SignInUpResponse signInUp(TenantIdentifierWithStorage tenantIdenti throw new BadPermissionException("Third Party login not enabled for tenant"); } - return signInUpHelper(tenantIdentifierWithStorage, main, thirdPartyId, thirdPartyUserId, email); + SignInUpResponse response = signInUpHelper(tenantIdentifierWithStorage, main, thirdPartyId, thirdPartyUserId, + email); + + if (isEmailVerified) { + for (LoginMethod lM : response.user.loginMethods) { + if (lM.thirdParty != null && lM.thirdParty.id.equals(thirdPartyId) && lM.thirdParty.userId.equals(thirdPartyUserId)) { + try { + tenantIdentifierWithStorage.getEmailVerificationStorage().startTransaction(con -> { + try { + tenantIdentifierWithStorage.getEmailVerificationStorage() + .updateIsEmailVerified_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), con, + lM.getSupertokensUserId(), lM.email, true); + tenantIdentifierWithStorage.getEmailVerificationStorage() + .commitTransaction(con); + + return null; + } catch (TenantOrAppNotFoundException e) { + throw new StorageTransactionLogicException(e); + } + }); + lM.setVerified(); + } catch (StorageTransactionLogicException e) { + if (e.actualException instanceof TenantOrAppNotFoundException) { + throw (TenantOrAppNotFoundException) e.actualException; + } + throw new StorageQueryException(e); + } + break; + } + } + } + + return response; } private static SignInUpResponse signInUpHelper(TenantIdentifierWithStorage tenantIdentifierWithStorage, diff --git a/src/main/java/io/supertokens/webserver/api/passwordless/ConsumeCodeAPI.java b/src/main/java/io/supertokens/webserver/api/passwordless/ConsumeCodeAPI.java index 31a3420af..6329a81d2 100644 --- a/src/main/java/io/supertokens/webserver/api/passwordless/ConsumeCodeAPI.java +++ b/src/main/java/io/supertokens/webserver/api/passwordless/ConsumeCodeAPI.java @@ -86,7 +86,9 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I ConsumeCodeResponse consumeCodeResponse = Passwordless.consumeCode( this.getTenantIdentifierWithStorageFromRequest(req), main, deviceId, deviceIdHash, - userInputCode, linkCode); + userInputCode, linkCode, + // From CDI version 4.0 onwards, the email verification will be set + getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v4_0)); io.supertokens.useridmapping.UserIdMapping.populateExternalUserIdForUsers(this.getTenantIdentifierWithStorageFromRequest(req), new AuthRecipeUserInfo[]{consumeCodeResponse.user}); ActiveUsers.updateLastActive(this.getAppIdentifierWithStorage(req), main, consumeCodeResponse.user.getSupertokensUserId()); diff --git a/src/main/java/io/supertokens/webserver/api/thirdparty/SignInUpAPI.java b/src/main/java/io/supertokens/webserver/api/thirdparty/SignInUpAPI.java index 18e0a40c1..06042eb23 100644 --- a/src/main/java/io/supertokens/webserver/api/thirdparty/SignInUpAPI.java +++ b/src/main/java/io/supertokens/webserver/api/thirdparty/SignInUpAPI.java @@ -115,6 +115,13 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I JsonObject emailObject = InputParser.parseJsonObjectOrThrowError(input, "email", false); String email = InputParser.parseStringOrThrowError(emailObject, "id", false); + // setting email verified behaviour is to be done only for CDI 4.0 onwards. version 3.0 and earlier + // do not have this field + Boolean isEmailVerified = false; + if (getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v4_0)) { + isEmailVerified = InputParser.parseBooleanOrThrowError(emailObject, "isVerified", false); + } + assert thirdPartyId != null; assert thirdPartyUserId != null; assert email != null; @@ -129,7 +136,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I try { ThirdParty.SignInUpResponse response = ThirdParty.signInUp( this.getTenantIdentifierWithStorageFromRequest(req), super.main, thirdPartyId, thirdPartyUserId, - email); + email, isEmailVerified); UserIdMapping.populateExternalUserIdForUsers(this.getTenantIdentifierWithStorageFromRequest(req), new AuthRecipeUserInfo[]{response.user}); ActiveUsers.updateLastActive(this.getAppIdentifierWithStorage(req), main, response.user.getSupertokensUserId()); diff --git a/src/test/java/io/supertokens/test/passwordless/api/EmailVerificationTest.java b/src/test/java/io/supertokens/test/passwordless/api/EmailVerificationTest.java new file mode 100644 index 000000000..0596536cf --- /dev/null +++ b/src/test/java/io/supertokens/test/passwordless/api/EmailVerificationTest.java @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2023, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.test.passwordless.api; + +import com.google.gson.JsonObject; +import io.supertokens.Main; +import io.supertokens.ProcessState; +import io.supertokens.authRecipe.AuthRecipe; +import io.supertokens.emailpassword.EmailPassword; +import io.supertokens.emailpassword.exceptions.EmailChangeNotAllowedException; +import io.supertokens.emailverification.EmailVerification; +import io.supertokens.featureflag.EE_FEATURES; +import io.supertokens.featureflag.FeatureFlagTestContent; +import io.supertokens.passwordless.Passwordless; +import io.supertokens.passwordless.exceptions.*; +import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; +import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; +import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; +import io.supertokens.pluginInterface.passwordless.exception.DuplicateLinkCodeHashException; +import io.supertokens.storageLayer.StorageLayer; +import io.supertokens.test.TestingProcessManager; +import io.supertokens.test.Utils; +import io.supertokens.test.httpRequest.HttpRequestForTesting; +import io.supertokens.thirdparty.ThirdParty; +import io.supertokens.utils.SemVer; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; + +import java.io.IOException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; + +import static org.junit.Assert.*; + +public class EmailVerificationTest { + @Rule + public TestRule watchman = Utils.getOnFailure(); + + @AfterClass + public static void afterTesting() { + Utils.afterTesting(); + } + + @Before + public void beforeEach() { + Utils.reset(); + } + + AuthRecipeUserInfo createEmailPasswordUser(Main main, String email, String password) + throws DuplicateEmailException, StorageQueryException { + return EmailPassword.signUp(main, email, password); + } + + AuthRecipeUserInfo createPasswordlessUserWithEmail(Main main, String email) + throws DuplicateLinkCodeHashException, StorageQueryException, NoSuchAlgorithmException, IOException, + RestartFlowException, InvalidKeyException, Base64EncodingException, DeviceIdHashMismatchException, + StorageTransactionLogicException, IncorrectUserInputCodeException, ExpiredUserInputCodeException { + Passwordless.CreateCodeResponse code = Passwordless.createCode(main, email, null, + null, "123456"); + return Passwordless.consumeCode(main, code.deviceId, code.deviceIdHash, + code.userInputCode, null).user; + } + + @Test + public void testPasswordlessLoginSetsEmailVerified_v3_0() throws Exception { + String[] args = { "../" }; + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + String email = "test@example.com"; + + { + // Email verification is not set for CDI < 4.0 + Passwordless.CreateCodeResponse createResp = Passwordless.createCode(process.getProcess(), email, null, null, null); + JsonObject consumeCodeRequestBody = new JsonObject(); + consumeCodeRequestBody.addProperty("preAuthSessionId", createResp.deviceIdHash); + consumeCodeRequestBody.addProperty("linkCode", createResp.linkCode); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, + SemVer.v3_0.get(), "passwordless"); + + String userId = response.get("user").getAsJsonObject().get("id").getAsString(); + assertFalse(EmailVerification.isEmailVerified(process.getProcess(), userId, email)); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testPasswordlessLoginSetsEmailVerified_v4_0() throws Exception { + String[] args = { "../" }; + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + String email = "test@example.com"; + + { + // Email verification is set for CDI >= 4.0 + Passwordless.CreateCodeResponse createResp = Passwordless.createCode(process.getProcess(), email, null, null, null); + JsonObject consumeCodeRequestBody = new JsonObject(); + consumeCodeRequestBody.addProperty("preAuthSessionId", createResp.deviceIdHash); + consumeCodeRequestBody.addProperty("linkCode", createResp.linkCode); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, + SemVer.v4_0.get(), "passwordless"); + + String userId = response.get("user").getAsJsonObject().get("id").getAsString(); + assertTrue(EmailVerification.isEmailVerified(process.getProcess(), userId, email)); + + EmailVerification.unverifyEmail(process.getProcess(), userId, email); + } + + { + // Email verification is set for CDI >= 4.0, for returning user + Passwordless.CreateCodeResponse createResp = Passwordless.createCode(process.getProcess(), email, null, null, null); + JsonObject consumeCodeRequestBody = new JsonObject(); + consumeCodeRequestBody.addProperty("preAuthSessionId", createResp.deviceIdHash); + consumeCodeRequestBody.addProperty("linkCode", createResp.linkCode); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, + SemVer.v4_0.get(), "passwordless"); + + String userId = response.get("user").getAsJsonObject().get("id").getAsString(); + assertTrue(EmailVerification.isEmailVerified(process.getProcess(), userId, email)); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testWithAccountLinking() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + AuthRecipeUserInfo user1 = createEmailPasswordUser(process.getProcess(), "test@example.com", "password"); + Thread.sleep(50); + AuthRecipeUserInfo user2 = createPasswordlessUserWithEmail(process.getProcess(), "test@example.com"); + + AuthRecipe.createPrimaryUser(process.getProcess(), user1.getSupertokensUserId()); + AuthRecipe.linkAccounts(process.getProcess(), user2.getSupertokensUserId(), user1.getSupertokensUserId()); + + EmailVerification.unverifyEmail(process.getProcess(), user2.getSupertokensUserId(), "test@example.com"); + + { + Passwordless.CreateCodeResponse createResp = Passwordless.createCode(process.getProcess(), "test@example.com", null, null, null); + JsonObject consumeCodeRequestBody = new JsonObject(); + consumeCodeRequestBody.addProperty("preAuthSessionId", createResp.deviceIdHash); + consumeCodeRequestBody.addProperty("linkCode", createResp.linkCode); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signinup/code/consume", consumeCodeRequestBody, 1000, 1000, null, + SemVer.v4_0.get(), "passwordless"); + + assertTrue(EmailVerification.isEmailVerified(process.getProcess(), user2.getSupertokensUserId(), "test@example.com")); + assertTrue(response.get("user").getAsJsonObject().get("loginMethods").getAsJsonArray().get(1).getAsJsonObject().get("verified").getAsBoolean()); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } +} diff --git a/src/test/java/io/supertokens/test/thirdparty/api/EmailVerificationTest.java b/src/test/java/io/supertokens/test/thirdparty/api/EmailVerificationTest.java new file mode 100644 index 000000000..98bf51675 --- /dev/null +++ b/src/test/java/io/supertokens/test/thirdparty/api/EmailVerificationTest.java @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2023, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.test.thirdparty.api; + +import com.google.gson.JsonObject; +import io.supertokens.Main; +import io.supertokens.ProcessState; +import io.supertokens.authRecipe.AuthRecipe; +import io.supertokens.emailpassword.EmailPassword; +import io.supertokens.emailpassword.exceptions.EmailChangeNotAllowedException; +import io.supertokens.emailverification.EmailVerification; +import io.supertokens.featureflag.EE_FEATURES; +import io.supertokens.featureflag.FeatureFlagTestContent; +import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; +import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; +import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.storageLayer.StorageLayer; +import io.supertokens.test.TestingProcessManager; +import io.supertokens.test.Utils; +import io.supertokens.test.httpRequest.HttpRequestForTesting; +import io.supertokens.test.httpRequest.HttpResponseException; +import io.supertokens.thirdparty.ThirdParty; +import io.supertokens.utils.SemVer; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; + +import static org.junit.Assert.*; + +public class EmailVerificationTest { + @Rule + public TestRule watchman = Utils.getOnFailure(); + + @AfterClass + public static void afterTesting() { + Utils.afterTesting(); + } + + @Before + public void beforeEach() { + Utils.reset(); + } + + AuthRecipeUserInfo createEmailPasswordUser(Main main, String email, String password) + throws DuplicateEmailException, StorageQueryException { + return EmailPassword.signUp(main, email, password); + } + + AuthRecipeUserInfo createThirdPartyUser(Main main, String thirdPartyId, String thirdPartyUserId, String email) + throws EmailChangeNotAllowedException, StorageQueryException { + return ThirdParty.signInUp(main, thirdPartyId, thirdPartyUserId, email).user; + } + + @Test + public void testEmailVerificationOnSignInUp_v3_0() throws Exception { + String[] args = {"../"}; + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + { + JsonObject emailObject = new JsonObject(); + emailObject.addProperty("id", "test@example.com"); + + JsonObject signUpRequestBody = new JsonObject(); + signUpRequestBody.addProperty("thirdPartyId", "google"); + signUpRequestBody.addProperty("thirdPartyUserId", "google-user"); + signUpRequestBody.add("email", emailObject); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signinup", signUpRequestBody, 1000, 1000, null, + SemVer.v3_0.get(), "thirdparty"); + + String userId = response.get("user").getAsJsonObject().get("id").getAsString(); + assertFalse(EmailVerification.isEmailVerified(process.getProcess(), userId, "test@example.com")); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testEmailVerificationOnSignInUp_v4_0() throws Exception { + String[] args = {"../"}; + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + { // Expects emailVerified in emailObject + JsonObject emailObject = new JsonObject(); + emailObject.addProperty("id", "test@example.com"); + + JsonObject signUpRequestBody = new JsonObject(); + signUpRequestBody.addProperty("thirdPartyId", "google"); + signUpRequestBody.addProperty("thirdPartyUserId", "google-user"); + signUpRequestBody.add("email", emailObject); + + try { + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signinup", signUpRequestBody, 1000, 1000, null, + SemVer.v4_0.get(), "thirdparty"); + fail(); + } catch (HttpResponseException e) { + assertEquals(400, e.statusCode); + assertTrue(e.getMessage().contains("Http error. Status Code: 400. Message: Field name 'isVerified' is invalid in JSON input")); + } + } + + { + JsonObject emailObject = new JsonObject(); + emailObject.addProperty("id", "test@example.com"); + emailObject.addProperty("isVerified", true); + + JsonObject signUpRequestBody = new JsonObject(); + signUpRequestBody.addProperty("thirdPartyId", "google"); + signUpRequestBody.addProperty("thirdPartyUserId", "google-user"); + signUpRequestBody.add("email", emailObject); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signinup", signUpRequestBody, 1000, 1000, null, + SemVer.v4_0.get(), "thirdparty"); + + String userId = response.get("user").getAsJsonObject().get("id").getAsString(); + assertTrue(EmailVerification.isEmailVerified(process.getProcess(), userId, "test@example.com")); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testWithAccountLinking() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + AuthRecipeUserInfo user1 = createEmailPasswordUser(process.getProcess(), "test@example.com", "password"); + Thread.sleep(50); + AuthRecipeUserInfo user2 = createThirdPartyUser(process.getProcess(), "google", "google-user", "test@example.com"); + + AuthRecipe.createPrimaryUser(process.getProcess(), user1.getSupertokensUserId()); + AuthRecipe.linkAccounts(process.getProcess(), user2.getSupertokensUserId(), user1.getSupertokensUserId()); + + EmailVerification.unverifyEmail(process.getProcess(), user2.getSupertokensUserId(), "test@example.com"); + + { + JsonObject emailObject = new JsonObject(); + emailObject.addProperty("id", "test@example.com"); + emailObject.addProperty("isVerified", true); + + JsonObject signUpRequestBody = new JsonObject(); + signUpRequestBody.addProperty("thirdPartyId", "google"); + signUpRequestBody.addProperty("thirdPartyUserId", "google-user"); + signUpRequestBody.add("email", emailObject); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signinup", signUpRequestBody, 1000000, 1000000, null, + SemVer.v4_0.get(), "thirdparty"); + + assertTrue(EmailVerification.isEmailVerified(process.getProcess(), user2.getSupertokensUserId(), "test@example.com")); + } + } +} From 97eca33f72f6dcd18f70d49b7eb764b1e9234619 Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Mon, 28 Aug 2023 17:04:28 +0530 Subject: [PATCH 098/131] fix: remove active user of recipe user when linked (#773) * fix: remove active user of recipe user when linked * fix: query --- src/main/java/io/supertokens/ActiveUsers.java | 16 ++ .../api/accountlinking/LinkAccountsAPI.java | 7 + .../accountlinking/api/ActiveUserTest.java | 194 ++++++++++++++++++ 3 files changed, 217 insertions(+) create mode 100644 src/test/java/io/supertokens/test/accountlinking/api/ActiveUserTest.java diff --git a/src/main/java/io/supertokens/ActiveUsers.java b/src/main/java/io/supertokens/ActiveUsers.java index 55224cb49..7c4601958 100644 --- a/src/main/java/io/supertokens/ActiveUsers.java +++ b/src/main/java/io/supertokens/ActiveUsers.java @@ -1,6 +1,8 @@ package io.supertokens; +import io.supertokens.pluginInterface.authRecipe.sqlStorage.AuthRecipeSQLStorage; import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.storageLayer.StorageLayer; @@ -37,4 +39,18 @@ public static int countUsersActiveSince(Main main, long time) return countUsersActiveSince(new AppIdentifierWithStorage(null, null, StorageLayer.getStorage(main)), main, time); } + + public static void removeActiveUser(AppIdentifierWithStorage appIdentifierWithStorage, String userId) + throws StorageQueryException { + try { + ((AuthRecipeSQLStorage) appIdentifierWithStorage.getActiveUsersStorage()).startTransaction(con -> { + appIdentifierWithStorage.getActiveUsersStorage().deleteUserActive_Transaction(con, appIdentifierWithStorage, userId); + ((AuthRecipeSQLStorage) appIdentifierWithStorage.getActiveUsersStorage()).commitTransaction(con); + return null; + }); + + } catch (StorageTransactionLogicException e) { + throw new StorageQueryException(e.actualException); + } + } } diff --git a/src/main/java/io/supertokens/webserver/api/accountlinking/LinkAccountsAPI.java b/src/main/java/io/supertokens/webserver/api/accountlinking/LinkAccountsAPI.java index b48009cfa..10f7a7bd1 100644 --- a/src/main/java/io/supertokens/webserver/api/accountlinking/LinkAccountsAPI.java +++ b/src/main/java/io/supertokens/webserver/api/accountlinking/LinkAccountsAPI.java @@ -17,6 +17,7 @@ package io.supertokens.webserver.api.accountlinking; import com.google.gson.JsonObject; +import io.supertokens.ActiveUsers; import io.supertokens.AppIdentifierWithStorageAndUserIdMapping; import io.supertokens.Main; import io.supertokens.authRecipe.AuthRecipe; @@ -26,6 +27,7 @@ import io.supertokens.featureflag.exceptions.FeatureNotEnabledException; import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; +import io.supertokens.pluginInterface.authRecipe.LoginMethod; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; @@ -39,6 +41,8 @@ import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; public class LinkAccountsAPI extends WebserverAPI { @@ -96,6 +100,9 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I AuthRecipe.LinkAccountsResult linkAccountsResult = AuthRecipe.linkAccounts(main, primaryUserIdAppIdentifierWithStorage, recipeUserId, primaryUserId); + // Remove linked account user id from active user + ActiveUsers.removeActiveUser(recipeUserIdAppIdentifierWithStorage, recipeUserId); + UserIdMapping.populateExternalUserIdForUsers(primaryUserIdAppIdentifierWithStorage, new AuthRecipeUserInfo[]{linkAccountsResult.user}); JsonObject response = new JsonObject(); response.addProperty("status", "OK"); diff --git a/src/test/java/io/supertokens/test/accountlinking/api/ActiveUserTest.java b/src/test/java/io/supertokens/test/accountlinking/api/ActiveUserTest.java new file mode 100644 index 000000000..c07765164 --- /dev/null +++ b/src/test/java/io/supertokens/test/accountlinking/api/ActiveUserTest.java @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2023, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.test.accountlinking.api; + +import com.google.gson.JsonObject; +import io.supertokens.ActiveUsers; +import io.supertokens.Main; +import io.supertokens.ProcessState; +import io.supertokens.authRecipe.AuthRecipe; +import io.supertokens.emailpassword.EmailPassword; +import io.supertokens.emailpassword.exceptions.EmailChangeNotAllowedException; +import io.supertokens.featureflag.EE_FEATURES; +import io.supertokens.featureflag.FeatureFlagTestContent; +import io.supertokens.passwordless.Passwordless; +import io.supertokens.passwordless.exceptions.*; +import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; +import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; +import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; +import io.supertokens.pluginInterface.passwordless.exception.DuplicateLinkCodeHashException; +import io.supertokens.storageLayer.StorageLayer; +import io.supertokens.test.TestingProcessManager; +import io.supertokens.test.Utils; +import io.supertokens.test.httpRequest.HttpRequestForTesting; +import io.supertokens.thirdparty.ThirdParty; +import io.supertokens.utils.SemVer; +import io.supertokens.webserver.WebserverAPI; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; + +import java.io.IOException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +public class ActiveUserTest { + @Rule + public TestRule watchman = Utils.getOnFailure(); + + @AfterClass + public static void afterTesting() { + Utils.afterTesting(); + } + + @Before + public void beforeEach() { + Utils.reset(); + } + + + AuthRecipeUserInfo createEmailPasswordUser(Main main, String email, String password) + throws DuplicateEmailException, StorageQueryException { + return EmailPassword.signUp(main, email, password); + } + + AuthRecipeUserInfo createThirdPartyUser(Main main, String thirdPartyId, String thirdPartyUserId, String email) + throws EmailChangeNotAllowedException, StorageQueryException { + return ThirdParty.signInUp(main, thirdPartyId, thirdPartyUserId, email).user; + } + + AuthRecipeUserInfo createPasswordlessUserWithEmail(Main main, String email) + throws DuplicateLinkCodeHashException, StorageQueryException, NoSuchAlgorithmException, IOException, + RestartFlowException, InvalidKeyException, Base64EncodingException, DeviceIdHashMismatchException, + StorageTransactionLogicException, IncorrectUserInputCodeException, ExpiredUserInputCodeException { + Passwordless.CreateCodeResponse code = Passwordless.createCode(main, email, null, + null, "123456"); + return Passwordless.consumeCode(main, code.deviceId, code.deviceIdHash, + code.userInputCode, null).user; + } + + AuthRecipeUserInfo createPasswordlessUserWithPhone(Main main, String phone) + throws DuplicateLinkCodeHashException, StorageQueryException, NoSuchAlgorithmException, IOException, + RestartFlowException, InvalidKeyException, Base64EncodingException, DeviceIdHashMismatchException, + StorageTransactionLogicException, IncorrectUserInputCodeException, ExpiredUserInputCodeException { + Passwordless.CreateCodeResponse code = Passwordless.createCode(main, null, phone, + null, "123456"); + return Passwordless.consumeCode(main, code.deviceId, code.deviceIdHash, + code.userInputCode, null).user; + } + + @Test + public void testActiveUserIsRemovedAfterLinkingAccounts() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo user1 = createEmailPasswordUser(process.getProcess(), "test@example.com", "password"); + AuthRecipeUserInfo user2 = createThirdPartyUser(process.getProcess(), "google", "google-user", "test@example.com"); + + { + // Update active user + JsonObject responseBody = new JsonObject(); + responseBody.addProperty("email", "test@example.com"); + responseBody.addProperty("password", "password"); + + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signin", responseBody, 1000, 1000, null, SemVer.v4_0.get(), + "emailpassword"); + } + { + JsonObject emailObject = new JsonObject(); + emailObject.addProperty("id", "test@example.com"); + + JsonObject signUpRequestBody = new JsonObject(); + signUpRequestBody.addProperty("thirdPartyId", "google"); + signUpRequestBody.addProperty("thirdPartyUserId", "google-user"); + signUpRequestBody.add("email", emailObject); + + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signinup", signUpRequestBody, 1000, 1000, null, + SemVer.v4_0.get(), "thirdparty"); + } + + int userCount = ActiveUsers.countUsersActiveSince(process.getProcess(), System.currentTimeMillis() - 10000); + assertEquals(2, userCount); + + { + // Link accounts + AuthRecipe.createPrimaryUser(process.main, user2.getSupertokensUserId()); + + JsonObject params = new JsonObject(); + params.addProperty("recipeUserId", user1.getSupertokensUserId()); + params.addProperty("primaryUserId", user2.getSupertokensUserId()); + + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/accountlinking/user/link", params, 1000, 1000, null, + WebserverAPI.getLatestCDIVersion().get(), ""); + } + + // Now there should be only one active user + userCount = ActiveUsers.countUsersActiveSince(process.getProcess(), System.currentTimeMillis() - 10000); + assertEquals(1, userCount); + + // Sign in to the accounts once again + { + // Update active user + JsonObject responseBody = new JsonObject(); + responseBody.addProperty("email", "test@example.com"); + responseBody.addProperty("password", "password"); + + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signin", responseBody, 1000, 1000, null, SemVer.v4_0.get(), + "emailpassword"); + } + { + JsonObject emailObject = new JsonObject(); + emailObject.addProperty("id", "test@example.com"); + + JsonObject signUpRequestBody = new JsonObject(); + signUpRequestBody.addProperty("thirdPartyId", "google"); + signUpRequestBody.addProperty("thirdPartyUserId", "google-user"); + signUpRequestBody.add("email", emailObject); + + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signinup", signUpRequestBody, 1000, 1000, null, + SemVer.v4_0.get(), "thirdparty"); + } + + // there should still be only one active user + userCount = ActiveUsers.countUsersActiveSince(process.getProcess(), System.currentTimeMillis() - 10000); + assertEquals(1, userCount); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } +} From 5e0fb6f7649f024aa3cd0b95fe11446c8f95a4bf Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Thu, 31 Aug 2023 12:01:30 +0530 Subject: [PATCH 099/131] fix: multitenant user association with account linking (#777) * fix: test user association * fix: multitenancy related changes * fix: pr comments * fix: pr comments --- .../io/supertokens/authRecipe/AuthRecipe.java | 68 +-- .../emailpassword/EmailPassword.java | 13 +- .../java/io/supertokens/inmemorydb/Start.java | 72 +-- .../multitenancy/Multitenancy.java | 82 +++- .../passwordless/Passwordless.java | 13 +- .../io/supertokens/thirdparty/ThirdParty.java | 27 +- .../test/accountlinking/MultitenantTest.java | 427 ++++++++++++++++++ .../accountlinking/UnlinkAccountsTest.java | 39 +- .../api/TestRecipeUserIdInSignInUpAPIs.java | 5 + .../api/TestMultitenancyAPIHelper.java | 75 +++ .../api/TestTenantUserAssociation.java | 72 +++ 11 files changed, 800 insertions(+), 93 deletions(-) create mode 100644 src/test/java/io/supertokens/test/accountlinking/MultitenantTest.java diff --git a/src/main/java/io/supertokens/authRecipe/AuthRecipe.java b/src/main/java/io/supertokens/authRecipe/AuthRecipe.java index 4c07bdadf..75a28a173 100644 --- a/src/main/java/io/supertokens/authRecipe/AuthRecipe.java +++ b/src/main/java/io/supertokens/authRecipe/AuthRecipe.java @@ -244,7 +244,8 @@ private static CanLinkAccountsResult canLinkAccountsHelper(TransactionConnection assert (recipeUser.loginMethods.length == 1); LoginMethod recipeUserIdLM = recipeUser.loginMethods[0]; - Set tenantIds = recipeUser.tenantIds; + Set tenantIds = new HashSet<>(); + tenantIds.addAll(recipeUser.tenantIds); tenantIds.addAll(primaryUser.tenantIds); // we loop through the union of both the user's tenantIds and check that the criteria for @@ -267,9 +268,12 @@ private static CanLinkAccountsResult canLinkAccountsHelper(TransactionConnection if (recipeUserIdLM.email != null) { AuthRecipeUserInfo[] usersWithSameEmail = storage - .listPrimaryUsersByEmail_Transaction(tenantIdentifier, con, + .listPrimaryUsersByEmail_Transaction(appIdentifierWithStorage, con, recipeUserIdLM.email); for (AuthRecipeUserInfo user : usersWithSameEmail) { + if (!user.tenantIds.contains(tenantId)) { + continue; + } if (user.isPrimaryUser && !user.getSupertokensUserId().equals(primaryUser.getSupertokensUserId())) { throw new AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException(user.getSupertokensUserId(), "This user's email is already associated with another user ID"); @@ -279,9 +283,12 @@ private static CanLinkAccountsResult canLinkAccountsHelper(TransactionConnection if (recipeUserIdLM.phoneNumber != null) { AuthRecipeUserInfo[] usersWithSamePhoneNumber = storage - .listPrimaryUsersByPhoneNumber_Transaction(tenantIdentifier, con, + .listPrimaryUsersByPhoneNumber_Transaction(appIdentifierWithStorage, con, recipeUserIdLM.phoneNumber); for (AuthRecipeUserInfo user : usersWithSamePhoneNumber) { + if (!user.tenantIds.contains(tenantId)) { + continue; + } if (user.isPrimaryUser && !user.getSupertokensUserId().equals(primaryUser.getSupertokensUserId())) { throw new AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException(user.getSupertokensUserId(), "This user's phone number is already associated with another user" + @@ -291,16 +298,22 @@ private static CanLinkAccountsResult canLinkAccountsHelper(TransactionConnection } if (recipeUserIdLM.thirdParty != null) { - AuthRecipeUserInfo userWithSameThirdParty = storage - .getPrimaryUsersByThirdPartyInfo_Transaction(tenantIdentifier, con, + AuthRecipeUserInfo[] usersWithSameThirdParty = storage + .listPrimaryUsersByThirdPartyInfo_Transaction(appIdentifierWithStorage, con, recipeUserIdLM.thirdParty.id, recipeUserIdLM.thirdParty.userId); - if (userWithSameThirdParty != null && userWithSameThirdParty.isPrimaryUser && - !userWithSameThirdParty.getSupertokensUserId().equals(primaryUser.getSupertokensUserId())) { - throw new AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException( - userWithSameThirdParty.getSupertokensUserId(), - "This user's third party login is already associated with another" + - " user ID"); + for (AuthRecipeUserInfo userWithSameThirdParty : usersWithSameThirdParty) { + if (!userWithSameThirdParty.tenantIds.contains(tenantId)) { + continue; + } + if (userWithSameThirdParty.isPrimaryUser && + !userWithSameThirdParty.getSupertokensUserId().equals(primaryUser.getSupertokensUserId())) { + throw new AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException( + userWithSameThirdParty.getSupertokensUserId(), + "This user's third party login is already associated with another" + + " user ID"); + } } + } } @@ -461,18 +474,14 @@ private static CreatePrimaryUserResult canCreatePrimaryUserHelper(TransactionCon LoginMethod loginMethod = targetUser.loginMethods[0]; for (String tenantId : targetUser.tenantIds) { - TenantIdentifier tenantIdentifier = new TenantIdentifier( - appIdentifierWithStorage.getConnectionUriDomain(), appIdentifierWithStorage.getAppId(), - tenantId); - // we do not bother with getting the tenantIdentifierWithStorage 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. - if (loginMethod.email != null) { AuthRecipeUserInfo[] usersWithSameEmail = storage - .listPrimaryUsersByEmail_Transaction(tenantIdentifier, con, + .listPrimaryUsersByEmail_Transaction(appIdentifierWithStorage, con, loginMethod.email); for (AuthRecipeUserInfo user : usersWithSameEmail) { + if (!user.tenantIds.contains(tenantId)) { + continue; + } if (user.isPrimaryUser) { throw new AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException(user.getSupertokensUserId(), "This user's email is already associated with another user ID"); @@ -482,7 +491,7 @@ private static CreatePrimaryUserResult canCreatePrimaryUserHelper(TransactionCon if (loginMethod.phoneNumber != null) { AuthRecipeUserInfo[] usersWithSamePhoneNumber = storage - .listPrimaryUsersByPhoneNumber_Transaction(tenantIdentifier, con, + .listPrimaryUsersByPhoneNumber_Transaction(appIdentifierWithStorage, con, loginMethod.phoneNumber); for (AuthRecipeUserInfo user : usersWithSamePhoneNumber) { if (user.isPrimaryUser) { @@ -494,14 +503,19 @@ private static CreatePrimaryUserResult canCreatePrimaryUserHelper(TransactionCon } if (loginMethod.thirdParty != null) { - AuthRecipeUserInfo userWithSameThirdParty = storage - .getPrimaryUsersByThirdPartyInfo_Transaction(tenantIdentifier, con, + AuthRecipeUserInfo[] usersWithSameThirdParty = storage + .listPrimaryUsersByThirdPartyInfo_Transaction(appIdentifierWithStorage, con, loginMethod.thirdParty.id, loginMethod.thirdParty.userId); - if (userWithSameThirdParty != null && userWithSameThirdParty.isPrimaryUser) { - throw new AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException( - userWithSameThirdParty.getSupertokensUserId(), - "This user's third party login is already associated with another" + - " user ID"); + for (AuthRecipeUserInfo userWithSameThirdParty : usersWithSameThirdParty) { + if (!userWithSameThirdParty.tenantIds.contains(tenantId)) { + continue; + } + if (userWithSameThirdParty.isPrimaryUser) { + throw new AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException( + userWithSameThirdParty.getSupertokensUserId(), + "This user's third party login is already associated with another" + + " user ID"); + } } } } diff --git a/src/main/java/io/supertokens/emailpassword/EmailPassword.java b/src/main/java/io/supertokens/emailpassword/EmailPassword.java index 60141104f..950b9bf3e 100644 --- a/src/main/java/io/supertokens/emailpassword/EmailPassword.java +++ b/src/main/java/io/supertokens/emailpassword/EmailPassword.java @@ -603,20 +603,15 @@ public static void updateUsersEmailOrPassword(AppIdentifierWithStorage appIdenti if (email != null) { if (user.isPrimaryUser) { for (String tenantId : user.tenantIds) { - // we do not bother with getting the tenantIdentifierWithStorage 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. - TenantIdentifier tenantIdentifier = new TenantIdentifier( - appIdentifierWithStorage.getConnectionUriDomain(), - appIdentifierWithStorage.getAppId(), - tenantId); - AuthRecipeUserInfo[] existingUsersWithNewEmail = authRecipeStorage.listPrimaryUsersByEmail_Transaction( - tenantIdentifier, transaction, + appIdentifierWithStorage, transaction, email); for (AuthRecipeUserInfo userWithSameEmail : existingUsersWithNewEmail) { + if (!userWithSameEmail.tenantIds.contains(tenantId)) { + continue; + } if (userWithSameEmail.isPrimaryUser && !userWithSameEmail.getSupertokensUserId().equals(user.getSupertokensUserId())) { throw new StorageTransactionLogicException( new EmailChangeNotAllowedException()); diff --git a/src/main/java/io/supertokens/inmemorydb/Start.java b/src/main/java/io/supertokens/inmemorydb/Start.java index 0241be936..7820d2b26 100644 --- a/src/main/java/io/supertokens/inmemorydb/Start.java +++ b/src/main/java/io/supertokens/inmemorydb/Start.java @@ -57,6 +57,7 @@ import io.supertokens.pluginInterface.multitenancy.exceptions.DuplicateTenantException; 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.passwordless.PasswordlessCode; import io.supertokens.pluginInterface.passwordless.PasswordlessDevice; import io.supertokens.pluginInterface.passwordless.exception.*; @@ -102,7 +103,7 @@ public class Start implements SessionSQLStorage, EmailPasswordSQLStorage, EmailVerificationSQLStorage, ThirdPartySQLStorage, JWTRecipeSQLStorage, PasswordlessSQLStorage, UserMetadataSQLStorage, UserRolesSQLStorage, UserIdMappingStorage, - MultitenancyStorage, TOTPSQLStorage, ActiveUsersStorage, DashboardSQLStorage, AuthRecipeSQLStorage { + MultitenancyStorage, MultitenancySQLStorage, TOTPSQLStorage, ActiveUsersStorage, DashboardSQLStorage, AuthRecipeSQLStorage { private static final Object appenderLock = new Object(); private static final String APP_ID_KEY_NAME = "app_id"; @@ -2245,9 +2246,9 @@ public TenantConfig[] getAllTenants() throws StorageQueryException { } @Override - public boolean addUserIdToTenant(TenantIdentifier tenantIdentifier, String userId) - throws TenantOrAppNotFoundException, UnknownUserIdException, StorageQueryException, - DuplicateEmailException, DuplicateThirdPartyUserException, DuplicatePhoneNumberException { + public boolean addUserIdToTenant_Transaction(TenantIdentifier tenantIdentifier, TransactionConnection conn, String userId) + throws StorageQueryException, TenantOrAppNotFoundException, DuplicateEmailException, + DuplicateThirdPartyUserException, DuplicatePhoneNumberException, UnknownUserIdException { try { return this.startTransaction(con -> { Connection sqlCon = (Connection) con.getConnection(); @@ -2758,44 +2759,47 @@ public AuthRecipeUserInfo getPrimaryUserById_Transaction(AppIdentifier appIdenti } @Override - public AuthRecipeUserInfo[] listPrimaryUsersByEmail_Transaction(TenantIdentifier tenantIdentifier, + public AuthRecipeUserInfo[] listPrimaryUsersByEmail_Transaction(AppIdentifier appIdentifier, TransactionConnection con, String email) throws StorageQueryException { - try { - Connection sqlCon = (Connection) con.getConnection(); - return GeneralQueries.listPrimaryUsersByEmail_Transaction(this, sqlCon, tenantIdentifier, email); - } catch (SQLException e) { - throw new StorageQueryException(e); - } + return null; // TODO +// try { +// Connection sqlCon = (Connection) con.getConnection(); +// return GeneralQueries.listPrimaryUsersByEmail_Transaction(this, sqlCon, appIdentifier, email); +// } catch (SQLException e) { +// throw new StorageQueryException(e); +// } } @Override - public AuthRecipeUserInfo[] listPrimaryUsersByPhoneNumber_Transaction(TenantIdentifier tenantIdentifier, + public AuthRecipeUserInfo[] listPrimaryUsersByPhoneNumber_Transaction(AppIdentifier appIdentifier, TransactionConnection con, String phoneNumber) throws StorageQueryException { - try { - Connection sqlCon = (Connection) con.getConnection(); - return GeneralQueries.listPrimaryUsersByPhoneNumber_Transaction(this, sqlCon, tenantIdentifier, - phoneNumber); - } catch (SQLException e) { - throw new StorageQueryException(e); - } - } - - @Override - public AuthRecipeUserInfo getPrimaryUsersByThirdPartyInfo_Transaction(TenantIdentifier tenantIdentifier, - TransactionConnection con, - String thirdPartyId, - String thirdPartyUserId) - throws StorageQueryException { - try { - Connection sqlCon = (Connection) con.getConnection(); - return GeneralQueries.getPrimaryUsersByThirdPartyInfo_Transaction(this, sqlCon, tenantIdentifier, - thirdPartyId, thirdPartyUserId); - } catch (SQLException e) { - throw new StorageQueryException(e); - } + return null; // TODO +// try { +// Connection sqlCon = (Connection) con.getConnection(); +// return GeneralQueries.listPrimaryUsersByPhoneNumber_Transaction(this, sqlCon, tenantIdentifier, +// phoneNumber); +// } catch (SQLException e) { +// throw new StorageQueryException(e); +// } + } + + @Override + public AuthRecipeUserInfo[] listPrimaryUsersByThirdPartyInfo_Transaction(AppIdentifier appIdentifier, + TransactionConnection con, + String thirdPartyId, + String thirdPartyUserId) + throws StorageQueryException { + return null; // TODO +// try { +// Connection sqlCon = (Connection) con.getConnection(); +// return GeneralQueries.getPrimaryUsersByThirdPartyInfo_Transaction(this, sqlCon, tenantIdentifier, +// thirdPartyId, thirdPartyUserId); +// } catch (SQLException e) { +// throw new StorageQueryException(e); +// } } @Override diff --git a/src/main/java/io/supertokens/multitenancy/Multitenancy.java b/src/main/java/io/supertokens/multitenancy/Multitenancy.java index 21fe432cb..03858a5c0 100644 --- a/src/main/java/io/supertokens/multitenancy/Multitenancy.java +++ b/src/main/java/io/supertokens/multitenancy/Multitenancy.java @@ -28,15 +28,20 @@ import io.supertokens.featureflag.exceptions.FeatureNotEnabledException; import io.supertokens.multitenancy.exception.*; import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; +import io.supertokens.pluginInterface.authRecipe.LoginMethod; +import io.supertokens.pluginInterface.authRecipe.sqlStorage.AuthRecipeSQLStorage; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.exceptions.InvalidConfigException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; import io.supertokens.pluginInterface.multitenancy.*; import io.supertokens.pluginInterface.multitenancy.exceptions.DuplicateClientTypeException; import io.supertokens.pluginInterface.multitenancy.exceptions.DuplicateTenantException; 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.passwordless.exception.DuplicatePhoneNumberException; import io.supertokens.pluginInterface.thirdparty.exception.DuplicateThirdPartyUserException; import io.supertokens.storageLayer.StorageLayer; @@ -385,8 +390,81 @@ public static boolean addUserIdToTenant(Main main, TenantIdentifierWithStorage t throw new FeatureNotEnabledException(EE_FEATURES.MULTI_TENANCY); } - return tenantIdentifierWithStorage.getMultitenancyStorageWithTargetStorage() - .addUserIdToTenant(tenantIdentifierWithStorage, userId); + AuthRecipeSQLStorage storage = (AuthRecipeSQLStorage) tenantIdentifierWithStorage.getAuthRecipeStorage(); + try { + return storage.startTransaction(con -> { + String tenantId = tenantIdentifierWithStorage.getTenantId(); + AuthRecipeUserInfo userToAssociate = storage.getPrimaryUserById_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), con, userId); + + if (userToAssociate.isPrimaryUser) { + Set emails = new HashSet<>(); + Set phoneNumbers = new HashSet<>(); + Set thirdParties = new HashSet<>(); + + // Loop through all the emails, phoneNumbers and thirdPartyInfos and check for conflicts + for (LoginMethod lM : userToAssociate.loginMethods) { + if (lM.email != null) { + emails.add(lM.email); + } + if (lM.phoneNumber != null) { + phoneNumbers.add(lM.phoneNumber); + } + if (lM.thirdParty != null) { + thirdParties.add(lM.thirdParty); + } + } + + for (String email : emails) { + AuthRecipeUserInfo[] users = storage.listPrimaryUsersByEmail_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), con, email); + for (AuthRecipeUserInfo user : users) { + if (user.tenantIds.contains(tenantId) && !user.getSupertokensUserId().equals(userId)) { + throw new StorageTransactionLogicException(new DuplicateEmailException()); + } + } + } + + for (String phoneNumber : phoneNumbers) { + AuthRecipeUserInfo[] users = storage.listPrimaryUsersByPhoneNumber_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), con, phoneNumber); + for (AuthRecipeUserInfo user : users) { + if (user.tenantIds.contains(tenantId) && !user.getSupertokensUserId().equals(userId)) { + throw new StorageTransactionLogicException(new DuplicatePhoneNumberException()); + } + } + } + + for (LoginMethod.ThirdParty tp : thirdParties) { + AuthRecipeUserInfo[] users = storage.listPrimaryUsersByThirdPartyInfo_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), con, tp.id, tp.userId); + for (AuthRecipeUserInfo user : users) { + if (user.tenantIds.contains(tenantId) && !user.getSupertokensUserId().equals(userId)) { + throw new StorageTransactionLogicException(new DuplicateThirdPartyUserException()); + } + } + } + } + + try { + boolean result = ((MultitenancySQLStorage) storage).addUserIdToTenant_Transaction(tenantIdentifierWithStorage, con, userId); + storage.commitTransaction(con); + return result; + } catch (TenantOrAppNotFoundException | UnknownUserIdException | DuplicatePhoneNumberException | + DuplicateThirdPartyUserException | DuplicateEmailException e) { + throw new StorageTransactionLogicException(e); + } + }); + } catch (StorageTransactionLogicException e) { + if (e.actualException instanceof DuplicateEmailException) { + throw (DuplicateEmailException) e.actualException; + } else if (e.actualException instanceof DuplicatePhoneNumberException) { + throw (DuplicatePhoneNumberException) e.actualException; + } else if (e.actualException instanceof DuplicateThirdPartyUserException) { + throw (DuplicateThirdPartyUserException) e.actualException; + } else if (e.actualException instanceof TenantOrAppNotFoundException) { + throw (TenantOrAppNotFoundException) e.actualException; + } else if (e.actualException instanceof UnknownUserIdException) { + throw (UnknownUserIdException) e.actualException; + } + throw new StorageQueryException(e.actualException); + } } public static boolean removeUserIdFromTenant(Main main, TenantIdentifierWithStorage tenantIdentifierWithStorage, diff --git a/src/main/java/io/supertokens/passwordless/Passwordless.java b/src/main/java/io/supertokens/passwordless/Passwordless.java index e6acfb4b4..d0b06dc0b 100644 --- a/src/main/java/io/supertokens/passwordless/Passwordless.java +++ b/src/main/java/io/supertokens/passwordless/Passwordless.java @@ -710,20 +710,15 @@ public static void updateUser(AppIdentifierWithStorage appIdentifierWithStorage, if (emailUpdate != null && !Objects.equals(emailUpdate.newValue, lM.email)) { if (user.isPrimaryUser) { for (String tenantId : user.tenantIds) { - // we do not bother with getting the tenantIdentifierWithStorage 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. - TenantIdentifier tenantIdentifier = new TenantIdentifier( - appIdentifierWithStorage.getConnectionUriDomain(), - appIdentifierWithStorage.getAppId(), - tenantId); - AuthRecipeUserInfo[] existingUsersWithNewEmail = authRecipeSQLStorage.listPrimaryUsersByEmail_Transaction( - tenantIdentifier, con, + appIdentifierWithStorage, con, emailUpdate.newValue); for (AuthRecipeUserInfo userWithSameEmail : existingUsersWithNewEmail) { + if (!userWithSameEmail.tenantIds.contains(tenantId)) { + continue; + } if (userWithSameEmail.isPrimaryUser && !userWithSameEmail.getSupertokensUserId().equals(user.getSupertokensUserId())) { throw new StorageTransactionLogicException( new EmailChangeNotAllowedException()); diff --git a/src/main/java/io/supertokens/thirdparty/ThirdParty.java b/src/main/java/io/supertokens/thirdparty/ThirdParty.java index da6629b4a..366002d6e 100644 --- a/src/main/java/io/supertokens/thirdparty/ThirdParty.java +++ b/src/main/java/io/supertokens/thirdparty/ThirdParty.java @@ -217,10 +217,20 @@ private static SignInUpResponse signInUpHelper(TenantIdentifierWithStorage tenan (AuthRecipeSQLStorage) tenantIdentifierWithStorage.getAuthRecipeStorage(); storage.startTransaction(con -> { - AuthRecipeUserInfo userFromDb = authRecipeStorage.getPrimaryUsersByThirdPartyInfo_Transaction( - tenantIdentifierWithStorage, + AuthRecipeUserInfo userFromDb = null; + + AuthRecipeUserInfo[] usersFromDb = authRecipeStorage.listPrimaryUsersByThirdPartyInfo_Transaction( + appIdentifier, con, thirdPartyId, thirdPartyUserId); + for (AuthRecipeUserInfo user : usersFromDb) { + if (user.tenantIds.contains(tenantIdentifierWithStorage.getTenantId())) { + if (userFromDb != null) { + throw new IllegalStateException("Should never happen"); + } + userFromDb = user; + } + } if (userFromDb == null) { storage.commitTransaction(con); @@ -246,19 +256,14 @@ private static SignInUpResponse signInUpHelper(TenantIdentifierWithStorage tenan // email, and if they do, then we do not allow the update. if (userFromDb.isPrimaryUser) { for (String tenantId : userFromDb.tenantIds) { - // we do not bother with getting the tenantIdentifierWithStorage 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. - TenantIdentifier tenantIdentifier = new TenantIdentifier( - tenantIdentifierWithStorage.getConnectionUriDomain(), - tenantIdentifierWithStorage.getAppId(), - tenantId); - AuthRecipeUserInfo[] userBasedOnEmail = authRecipeStorage.listPrimaryUsersByEmail_Transaction( - tenantIdentifier, con, email + appIdentifier, con, email ); for (AuthRecipeUserInfo userWithSameEmail : userBasedOnEmail) { + if (!userWithSameEmail.tenantIds.contains(tenantId)) { + continue; + } if (userWithSameEmail.isPrimaryUser && !userWithSameEmail.getSupertokensUserId().equals(userFromDb.getSupertokensUserId())) { throw new StorageTransactionLogicException( diff --git a/src/test/java/io/supertokens/test/accountlinking/MultitenantTest.java b/src/test/java/io/supertokens/test/accountlinking/MultitenantTest.java new file mode 100644 index 000000000..f9cdc8e12 --- /dev/null +++ b/src/test/java/io/supertokens/test/accountlinking/MultitenantTest.java @@ -0,0 +1,427 @@ +/* + * Copyright (c) 2023, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.test.accountlinking; + +import com.google.gson.JsonObject; +import io.supertokens.Main; +import io.supertokens.ProcessState; +import io.supertokens.authRecipe.AuthRecipe; +import io.supertokens.authRecipe.exception.AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException; +import io.supertokens.emailpassword.EmailPassword; +import io.supertokens.emailpassword.exceptions.WrongCredentialsException; +import io.supertokens.featureflag.EE_FEATURES; +import io.supertokens.featureflag.FeatureFlagTestContent; +import io.supertokens.featureflag.exceptions.FeatureNotEnabledException; +import io.supertokens.multitenancy.Multitenancy; +import io.supertokens.multitenancy.exception.BadPermissionException; +import io.supertokens.multitenancy.exception.CannotModifyBaseConfigException; +import io.supertokens.passwordless.Passwordless; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; +import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; +import io.supertokens.pluginInterface.exceptions.InvalidConfigException; +import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.multitenancy.*; +import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; +import io.supertokens.storageLayer.StorageLayer; +import io.supertokens.test.TestingProcessManager; +import io.supertokens.test.Utils; +import io.supertokens.thirdparty.InvalidProviderConfigException; +import io.supertokens.thirdparty.ThirdParty; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; + +import java.io.IOException; +import java.util.function.Function; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; + +public class MultitenantTest { + @Rule + public TestRule watchman = Utils.getOnFailure(); + + @AfterClass + public static void afterTesting() { + Utils.afterTesting(); + } + + @Before + public void beforeEach() { + Utils.reset(); + } + + TenantIdentifier t1, t2, t3; + + private void createTenants(Main main) + throws StorageQueryException, TenantOrAppNotFoundException, InvalidProviderConfigException, + FeatureNotEnabledException, IOException, InvalidConfigException, + CannotModifyBaseConfigException, BadPermissionException { + // User pool 1 - (null, a1, null) + // User pool 2 - (null, a1, t1), (null, a1, t2) + + { // tenant 1 + JsonObject config = new JsonObject(); + TenantIdentifier tenantIdentifier = new TenantIdentifier(null, "a1", null); + + StorageLayer.getStorage(new TenantIdentifier(null, null, null), main) + .modifyConfigToAddANewUserPoolForTesting(config, 1); + + Multitenancy.addNewOrUpdateAppOrTenant( + main, + new TenantIdentifier(null, null, null), + new TenantConfig( + tenantIdentifier, + new EmailPasswordConfig(true), + new ThirdPartyConfig(true, null), + new PasswordlessConfig(true), + config + ) + ); + } + + { // tenant 2 + JsonObject config = new JsonObject(); + TenantIdentifier tenantIdentifier = new TenantIdentifier(null, "a1", "t1"); + + StorageLayer.getStorage(new TenantIdentifier(null, null, null), main) + .modifyConfigToAddANewUserPoolForTesting(config, 1); + + Multitenancy.addNewOrUpdateAppOrTenant( + main, + new TenantIdentifier(null, "a1", null), + new TenantConfig( + tenantIdentifier, + new EmailPasswordConfig(true), + new ThirdPartyConfig(true, null), + new PasswordlessConfig(true), + config + ) + ); + } + + { // tenant 3 + JsonObject config = new JsonObject(); + TenantIdentifier tenantIdentifier = new TenantIdentifier(null, "a1", "t2"); + + StorageLayer.getStorage(new TenantIdentifier(null, null, null), main) + .modifyConfigToAddANewUserPoolForTesting(config, 1); + + Multitenancy.addNewOrUpdateAppOrTenant( + main, + new TenantIdentifier(null, "a1", null), + new TenantConfig( + tenantIdentifier, + new EmailPasswordConfig(true), + new ThirdPartyConfig(true, null), + new PasswordlessConfig(true), + config + ) + ); + } + + t1 = new TenantIdentifier(null, "a1", null); + t2 = new TenantIdentifier(null, "a1", "t1"); + t3 = new TenantIdentifier(null, "a1", "t2"); + } + + @Test + public void testWithEmailPasswordUsers1() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + createTenants(process.getProcess()); + + TenantIdentifierWithStorage t1WithStorage = t1.withStorage(StorageLayer.getStorage(t1, process.getProcess())); + TenantIdentifierWithStorage t2WithStorage = t2.withStorage(StorageLayer.getStorage(t2, process.getProcess())); + + AuthRecipeUserInfo user1 = EmailPassword.signUp(t1WithStorage, process.getProcess(), "test1@example.com", "password"); + AuthRecipeUserInfo user2 = EmailPassword.signUp(t2WithStorage, process.getProcess(), "test2@example.com", "password"); + AuthRecipe.createPrimaryUser(process.getProcess(), t1WithStorage.toAppIdentifierWithStorage(), user1.getSupertokensUserId()); + AuthRecipe.linkAccounts(process.getProcess(), t1WithStorage.toAppIdentifierWithStorage(), user2.getSupertokensUserId(), user1.getSupertokensUserId()); + + try { + // Credentials does not exist in t2 + EmailPassword.signIn(t2WithStorage, process.getProcess(), "test1@example.com", "password"); + fail(); + } catch (WrongCredentialsException e) { + // ignore + } + + Multitenancy.addUserIdToTenant(process.getProcess(), t2WithStorage, user1.getSupertokensUserId()); + + // Sign in should now pass + EmailPassword.signIn(t2WithStorage, process.getProcess(), "test1@example.com", "password"); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testWithEmailPasswordUsers2() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + createTenants(process.getProcess()); + + TenantIdentifierWithStorage t1WithStorage = t1.withStorage(StorageLayer.getStorage(t1, process.getProcess())); + TenantIdentifierWithStorage t2WithStorage = t2.withStorage(StorageLayer.getStorage(t2, process.getProcess())); + + AuthRecipeUserInfo user1 = EmailPassword.signUp(t1WithStorage, process.getProcess(), "test1@example.com", "password"); + AuthRecipeUserInfo user2 = EmailPassword.signUp(t2WithStorage, process.getProcess(), "test2@example.com", "password"); + AuthRecipe.createPrimaryUser(process.getProcess(), t1WithStorage.toAppIdentifierWithStorage(), user1.getSupertokensUserId()); + AuthRecipe.linkAccounts(process.getProcess(), t1WithStorage.toAppIdentifierWithStorage(), user2.getSupertokensUserId(), user1.getSupertokensUserId()); + + try { + // Credentials does not exist in t2 + EmailPassword.signIn(t2WithStorage, process.getProcess(), "test1@example.com", "password"); + fail(); + } catch (WrongCredentialsException e) { + // ignore + } + + // same email is allowed to sign up + AuthRecipeUserInfo user3 = EmailPassword.signUp(t1WithStorage, process.getProcess(), "test2@example.com", "password2"); + + // Sign in should pass + EmailPassword.signIn(t1WithStorage, process.getProcess(), "test2@example.com", "password2"); + + try { + // user 3 cannot become a primary user + AuthRecipe.createPrimaryUser(process.getProcess(), t1WithStorage.toAppIdentifierWithStorage(), user3.getSupertokensUserId()); + fail(); + } catch (AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException e) { + // Ignore + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testWithEmailPasswordUsersAndPasswordlessUser1() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + createTenants(process.getProcess()); + + TenantIdentifierWithStorage t1WithStorage = t1.withStorage(StorageLayer.getStorage(t1, process.getProcess())); + TenantIdentifierWithStorage t2WithStorage = t2.withStorage(StorageLayer.getStorage(t2, process.getProcess())); + + AuthRecipeUserInfo user1 = EmailPassword.signUp(t1WithStorage, process.getProcess(), "test1@example.com", "password"); + Passwordless.CreateCodeResponse user2Code = Passwordless.createCode(t2WithStorage, process.getProcess(), + "test2@example.com", null, null, null); + AuthRecipeUserInfo user2 = Passwordless.consumeCode(t2WithStorage, process.getProcess(), user2Code.deviceId, user2Code.deviceIdHash, user2Code.userInputCode, null).user; + AuthRecipe.createPrimaryUser(process.getProcess(), t1WithStorage.toAppIdentifierWithStorage(), user1.getSupertokensUserId()); + AuthRecipe.linkAccounts(process.getProcess(), t1WithStorage.toAppIdentifierWithStorage(), user2.getSupertokensUserId(), user1.getSupertokensUserId()); + + try { + // Credentials does not exist in t2 + EmailPassword.signIn(t2WithStorage, process.getProcess(), "test1@example.com", "password"); + fail(); + } catch (WrongCredentialsException e) { + // ignore + } + + // same email is allowed to sign up + AuthRecipeUserInfo user3 = EmailPassword.signUp(t1WithStorage, process.getProcess(), "test2@example.com", "password2"); + + // Sign in should pass + EmailPassword.signIn(t1WithStorage, process.getProcess(), "test2@example.com", "password2"); + + try { + // user 3 cannot become a primary user + AuthRecipe.createPrimaryUser(process.getProcess(), t1WithStorage.toAppIdentifierWithStorage(), user3.getSupertokensUserId()); + fail(); + } catch (AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException e) { + // Ignore + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testWithEmailPasswordUsersAndPasswordlessUser2() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + createTenants(process.getProcess()); + + TenantIdentifierWithStorage t1WithStorage = t1.withStorage(StorageLayer.getStorage(t1, process.getProcess())); + TenantIdentifierWithStorage t2WithStorage = t2.withStorage(StorageLayer.getStorage(t2, process.getProcess())); + + AuthRecipeUserInfo user1 = EmailPassword.signUp(t1WithStorage, process.getProcess(), "test1@example.com", "password"); + Passwordless.CreateCodeResponse user2Code = Passwordless.createCode(t2WithStorage, process.getProcess(), + "test2@example.com", null, null, null); + AuthRecipeUserInfo user2 = Passwordless.consumeCode(t2WithStorage, process.getProcess(), user2Code.deviceId, user2Code.deviceIdHash, user2Code.userInputCode, null).user; + AuthRecipe.createPrimaryUser(process.getProcess(), t1WithStorage.toAppIdentifierWithStorage(), user1.getSupertokensUserId()); + AuthRecipe.linkAccounts(process.getProcess(), t1WithStorage.toAppIdentifierWithStorage(), user2.getSupertokensUserId(), user1.getSupertokensUserId()); + + // same email is allowed to sign in up + Passwordless.CreateCodeResponse user3code = Passwordless.createCode(t1WithStorage, process.getProcess(), + "test2@example.com", null, null, null); + + AuthRecipeUserInfo user3 = Passwordless.consumeCode(t1WithStorage, process.getProcess(), user3code.deviceId, user3code.deviceIdHash, user3code.userInputCode, null).user; + + try { + // user 3 cannot become a primary user + AuthRecipe.createPrimaryUser(process.getProcess(), t1WithStorage.toAppIdentifierWithStorage(), user3.getSupertokensUserId()); + fail(); + } catch (AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException e) { + // Ignore + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testWithEmailPasswordUserAndThirdPartyUser() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + createTenants(process.getProcess()); + + TenantIdentifierWithStorage t1WithStorage = t1.withStorage(StorageLayer.getStorage(t1, process.getProcess())); + TenantIdentifierWithStorage t2WithStorage = t2.withStorage(StorageLayer.getStorage(t2, process.getProcess())); + + AuthRecipeUserInfo user1 = EmailPassword.signUp(t1WithStorage, process.getProcess(), "test1@example.com", "password"); + AuthRecipeUserInfo user2 = ThirdParty.signInUp(t2WithStorage, process.getProcess(), "google", "google-user", "test2@example.com").user; + AuthRecipe.createPrimaryUser(process.getProcess(), t1WithStorage.toAppIdentifierWithStorage(), user1.getSupertokensUserId()); + AuthRecipe.linkAccounts(process.getProcess(), t1WithStorage.toAppIdentifierWithStorage(), user2.getSupertokensUserId(), user1.getSupertokensUserId()); + + try { + // Credentials does not exist in t2 + EmailPassword.signIn(t2WithStorage, process.getProcess(), "test1@example.com", "password"); + fail(); + } catch (WrongCredentialsException e) { + // ignore + } + + // same email is allowed to sign up + AuthRecipeUserInfo user3 = ThirdParty.signInUp(t1WithStorage, process.getProcess(), "google", "google-user", "test2@example.com").user; + + try { + // user 3 cannot become a primary user + AuthRecipe.createPrimaryUser(process.getProcess(), t1WithStorage.toAppIdentifierWithStorage(), user3.getSupertokensUserId()); + fail(); + } catch (AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException e) { + // Ignore + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testTenantAssociationWithEPUsers1() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + createTenants(process.getProcess()); + + TenantIdentifierWithStorage t1WithStorage = t1.withStorage(StorageLayer.getStorage(t1, process.getProcess())); + TenantIdentifierWithStorage t2WithStorage = t2.withStorage(StorageLayer.getStorage(t2, process.getProcess())); + TenantIdentifierWithStorage t3WithStorage = t3.withStorage(StorageLayer.getStorage(t3, process.getProcess())); + + AuthRecipeUserInfo user1 = EmailPassword.signUp(t1WithStorage, process.getProcess(), "test1@example.com", "password1"); + AuthRecipeUserInfo user2 = EmailPassword.signUp(t2WithStorage, process.getProcess(), "test2@example.com", "password2"); + AuthRecipeUserInfo user3 = EmailPassword.signUp(t3WithStorage, process.getProcess(), "test1@example.com", "password3"); + + AuthRecipe.createPrimaryUser(process.getProcess(), t1WithStorage.toAppIdentifierWithStorage(), user1.getSupertokensUserId()); + AuthRecipe.linkAccounts(process.getProcess(), t1WithStorage.toAppIdentifierWithStorage(), user2.getSupertokensUserId(), user1.getSupertokensUserId()); + + Multitenancy.addUserIdToTenant(process.getProcess(), t2WithStorage, user3.getSupertokensUserId()); + try { + AuthRecipe.createPrimaryUser(process.getProcess(), t2WithStorage.toAppIdentifierWithStorage(), user3.getSupertokensUserId()); + fail(); + } catch (AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException e) { + // ignore + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testTenantAssociationWithEPUsers2() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + createTenants(process.getProcess()); + + TenantIdentifierWithStorage t1WithStorage = t1.withStorage(StorageLayer.getStorage(t1, process.getProcess())); + TenantIdentifierWithStorage t2WithStorage = t2.withStorage(StorageLayer.getStorage(t2, process.getProcess())); + TenantIdentifierWithStorage t3WithStorage = t3.withStorage(StorageLayer.getStorage(t3, process.getProcess())); + + AuthRecipeUserInfo user1 = EmailPassword.signUp(t1WithStorage, process.getProcess(), "test1@example.com", "password1"); + AuthRecipeUserInfo user2 = EmailPassword.signUp(t2WithStorage, process.getProcess(), "test2@example.com", "password2"); + AuthRecipeUserInfo user3 = EmailPassword.signUp(t3WithStorage, process.getProcess(), "test1@example.com", "password3"); + + AuthRecipe.createPrimaryUser(process.getProcess(), t1WithStorage.toAppIdentifierWithStorage(), user1.getSupertokensUserId()); + AuthRecipe.linkAccounts(process.getProcess(), t1WithStorage.toAppIdentifierWithStorage(), user2.getSupertokensUserId(), user1.getSupertokensUserId()); + + AuthRecipe.createPrimaryUser(process.getProcess(), t2WithStorage.toAppIdentifierWithStorage(), user3.getSupertokensUserId()); + try { + Multitenancy.addUserIdToTenant(process.getProcess(), t2WithStorage, user3.getSupertokensUserId()); + fail(); + } catch (DuplicateEmailException e) { + // ignore + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } +} diff --git a/src/test/java/io/supertokens/test/accountlinking/UnlinkAccountsTest.java b/src/test/java/io/supertokens/test/accountlinking/UnlinkAccountsTest.java index 78a2f4a8e..5a9d7cb74 100644 --- a/src/test/java/io/supertokens/test/accountlinking/UnlinkAccountsTest.java +++ b/src/test/java/io/supertokens/test/accountlinking/UnlinkAccountsTest.java @@ -36,7 +36,7 @@ import org.junit.Test; import org.junit.rules.TestRule; -import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.*; public class UnlinkAccountsTest { @@ -248,4 +248,41 @@ public void unlinkAccountSuccessButDeletesUser() throws Exception { process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); } + + @Test + public void testUnlinkUserDeletesRecipeUserAndAnotherUserLinkToIt() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo user1 = EmailPassword.signUp(process.getProcess(), "test1@example.com", "password"); + AuthRecipeUserInfo user2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "password"); + AuthRecipeUserInfo user3 = EmailPassword.signUp(process.getProcess(), "test3@example.com", "password"); + + AuthRecipe.createPrimaryUser(process.getProcess(), user1.getSupertokensUserId()); + AuthRecipe.linkAccounts(process.getProcess(), user2.getSupertokensUserId(), user1.getSupertokensUserId()); + + AuthRecipe.unlinkAccounts(process.getProcess(), user1.getSupertokensUserId()); + + AuthRecipeUserInfo refetchUser2 = AuthRecipe.getUserById(process.getProcess(), user2.getSupertokensUserId()); + assertEquals(refetchUser2.getSupertokensUserId(), user1.getSupertokensUserId()); + + AuthRecipe.linkAccounts(process.getProcess(), user3.getSupertokensUserId(), user2.getSupertokensUserId()); + AuthRecipeUserInfo refetchUser3 = AuthRecipe.getUserById(process.getProcess(), user3.getSupertokensUserId()); + assertEquals(refetchUser3.getSupertokensUserId(), user1.getSupertokensUserId()); + + assertEquals(refetchUser3.loginMethods.length, 2); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + + } } diff --git a/src/test/java/io/supertokens/test/accountlinking/api/TestRecipeUserIdInSignInUpAPIs.java b/src/test/java/io/supertokens/test/accountlinking/api/TestRecipeUserIdInSignInUpAPIs.java index acbadcf76..b09b69976 100644 --- a/src/test/java/io/supertokens/test/accountlinking/api/TestRecipeUserIdInSignInUpAPIs.java +++ b/src/test/java/io/supertokens/test/accountlinking/api/TestRecipeUserIdInSignInUpAPIs.java @@ -236,6 +236,7 @@ public void testThirdPartySignInUp() throws Exception { { JsonObject emailObject = new JsonObject(); emailObject.addProperty("id", "test@example.com"); + emailObject.addProperty("isVerified", false); JsonObject signUpRequestBody = new JsonObject(); signUpRequestBody.addProperty("thirdPartyId", "google"); @@ -255,6 +256,7 @@ public void testThirdPartySignInUp() throws Exception { // Without account linking JsonObject emailObject = new JsonObject(); emailObject.addProperty("id", "test@example.com"); + emailObject.addProperty("isVerified", false); JsonObject signUpRequestBody = new JsonObject(); signUpRequestBody.addProperty("thirdPartyId", "google"); @@ -279,6 +281,7 @@ public void testThirdPartySignInUp() throws Exception { // After account linking JsonObject emailObject = new JsonObject(); emailObject.addProperty("id", "test@example.com"); + emailObject.addProperty("isVerified", false); JsonObject signUpRequestBody = new JsonObject(); signUpRequestBody.addProperty("thirdPartyId", "google"); @@ -300,6 +303,7 @@ public void testThirdPartySignInUp() throws Exception { // After account linking JsonObject emailObject = new JsonObject(); emailObject.addProperty("id", "test@example.com"); + emailObject.addProperty("isVerified", false); JsonObject signUpRequestBody = new JsonObject(); signUpRequestBody.addProperty("thirdPartyId", "google"); @@ -317,6 +321,7 @@ public void testThirdPartySignInUp() throws Exception { // After account linking JsonObject emailObject = new JsonObject(); emailObject.addProperty("id", "test@example.com"); + emailObject.addProperty("isVerified", false); JsonObject signUpRequestBody = new JsonObject(); signUpRequestBody.addProperty("thirdPartyId", "facebook"); diff --git a/src/test/java/io/supertokens/test/multitenant/api/TestMultitenancyAPIHelper.java b/src/test/java/io/supertokens/test/multitenant/api/TestMultitenancyAPIHelper.java index 8d5ff811c..2338576ed 100644 --- a/src/test/java/io/supertokens/test/multitenant/api/TestMultitenancyAPIHelper.java +++ b/src/test/java/io/supertokens/test/multitenant/api/TestMultitenancyAPIHelper.java @@ -28,6 +28,7 @@ import java.io.IOException; import java.util.HashMap; import java.util.Map; +import java.util.Random; import static org.junit.Assert.assertEquals; @@ -315,6 +316,80 @@ public static JsonObject tpSignInUp(TenantIdentifier tenantIdentifier, String th return response.get("user").getAsJsonObject(); } + private static String generateRandomString(int length) { + StringBuilder sb = new StringBuilder(length); + final String ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + final Random RANDOM = new Random(); + for (int i = 0; i < length; i++) { + int randomIndex = RANDOM.nextInt(ALPHABET.length()); + char randomChar = ALPHABET.charAt(randomIndex); + sb.append(randomChar); + } + return sb.toString(); + } + + private static JsonObject createCodeWithEmail(TenantIdentifier tenantIdentifier, String email, Main main) + throws HttpResponseException, IOException { + String exampleCode = generateRandomString(6); + JsonObject createCodeRequestBody = new JsonObject(); + createCodeRequestBody.addProperty("email", email); + createCodeRequestBody.addProperty("userInputCode", exampleCode); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(main, "", + HttpRequestForTesting.getMultitenantUrl(tenantIdentifier, "/recipe/signinup/code"), + createCodeRequestBody, 1000, 1000, null, + SemVer.v3_0.get(), "passwordless"); + + assertEquals("OK", response.get("status").getAsString()); + assertEquals(8, response.entrySet().size()); + + return response; + } + + private static JsonObject consumeCode(TenantIdentifier tenantIdentifier, String deviceId, String preAuthSessionId, + String userInputCode, Main main) + throws HttpResponseException, IOException { + JsonObject consumeCodeRequestBody = new JsonObject(); + consumeCodeRequestBody.addProperty("deviceId", deviceId); + consumeCodeRequestBody.addProperty("preAuthSessionId", preAuthSessionId); + consumeCodeRequestBody.addProperty("userInputCode", userInputCode); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(main, "", + HttpRequestForTesting.getMultitenantUrl(tenantIdentifier, "/recipe/signinup/code/consume"), + consumeCodeRequestBody, 1000, 1000, null, + SemVer.v3_0.get(), "passwordless"); + assertEquals("OK", response.get("status").getAsString()); + return response.get("user").getAsJsonObject(); + } + + public static JsonObject plSignInUpEmail(TenantIdentifier tenantIdentifier, String email, Main main) + throws HttpResponseException, IOException { + JsonObject code = createCodeWithEmail(tenantIdentifier, email, main); + return consumeCode(tenantIdentifier, code.get("deviceId").getAsString(), code.get("preAuthSessionId").getAsString(), code.get("userInputCode").getAsString(), main); + } + + private static JsonObject createCodeWithNumber(TenantIdentifier tenantIdentifier, String phoneNumber, Main main) + throws HttpResponseException, IOException { + JsonObject createCodeRequestBody = new JsonObject(); + createCodeRequestBody.addProperty("phoneNumber", phoneNumber); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(main, "", + HttpRequestForTesting.getMultitenantUrl(tenantIdentifier, "/recipe/signinup/code"), + createCodeRequestBody, 1000, 1000, null, + SemVer.v3_0.get(), "passwordless"); + + assertEquals("OK", response.get("status").getAsString()); + assertEquals(8, response.entrySet().size()); + + return response; + } + + public static JsonObject plSignInUpNumber(TenantIdentifier tenantIdentifier, String phoneNumber, Main main) + throws HttpResponseException, IOException { + JsonObject code = createCodeWithNumber(tenantIdentifier, phoneNumber, main); + return consumeCode(tenantIdentifier, code.get("deviceId").getAsString(), code.get("preAuthSessionId").getAsString(), code.get("userInputCode").getAsString(), main); + } + public static void addLicense(String licenseKey, Main main) throws HttpResponseException, IOException { JsonObject licenseKeyRequest = new JsonObject(); licenseKeyRequest.addProperty("licenseKey", licenseKey); diff --git a/src/test/java/io/supertokens/test/multitenant/api/TestTenantUserAssociation.java b/src/test/java/io/supertokens/test/multitenant/api/TestTenantUserAssociation.java index c95a5d84d..9c86c127e 100644 --- a/src/test/java/io/supertokens/test/multitenant/api/TestTenantUserAssociation.java +++ b/src/test/java/io/supertokens/test/multitenant/api/TestTenantUserAssociation.java @@ -501,4 +501,76 @@ public void testDisassociateUserWithUserIdMappingAndSession() throws Exception { // OK } } + + @Test + public void testThatUserWithSameEmailCannotBeAssociatedToATenantForEp() throws Exception { + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + createTenants(); + JsonObject user1 = TestMultitenancyAPIHelper.epSignUp(new TenantIdentifier(null, "a1", "t1"), "user@example.com", + "password", process.getProcess()); + String userId1 = user1.get("id").getAsString(); + + TestMultitenancyAPIHelper.epSignUp(new TenantIdentifier(null, "a1", "t2"), "user@example.com", + "password", process.getProcess()); + + JsonObject response = TestMultitenancyAPIHelper.associateUserToTenant(new TenantIdentifier(null, "a1", "t2"), userId1, process.getProcess()); + assertEquals("EMAIL_ALREADY_EXISTS_ERROR", response.getAsJsonPrimitive("status").getAsString()); + } + + @Test + public void testThatUserWithSameThirdPartyInfoCannotBeAssociatedToATenantForTp() throws Exception { + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + createTenants(); + JsonObject user1 = TestMultitenancyAPIHelper.tpSignInUp(new TenantIdentifier(null, "a1", "t1"), "google", "google-user", "user@example.com", + process.getProcess()); + String userId1 = user1.get("id").getAsString(); + + TestMultitenancyAPIHelper.tpSignInUp(new TenantIdentifier(null, "a1", "t2"), "google", "google-user", "user@example.com", + process.getProcess()); + + JsonObject response = TestMultitenancyAPIHelper.associateUserToTenant(new TenantIdentifier(null, "a1", "t2"), userId1, process.getProcess()); + assertEquals("THIRD_PARTY_USER_ALREADY_EXISTS_ERROR", response.getAsJsonPrimitive("status").getAsString()); + } + + @Test + public void testThatUserWithSameEmailCannotBeAssociatedToATenantForPless() throws Exception { + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + createTenants(); + JsonObject user1 = TestMultitenancyAPIHelper.plSignInUpEmail(new TenantIdentifier(null, "a1", "t1"), "user@example.com", + process.getProcess()); + String userId1 = user1.get("id").getAsString(); + + TestMultitenancyAPIHelper.plSignInUpEmail(new TenantIdentifier(null, "a1", "t2"), "user@example.com", + process.getProcess()); + + JsonObject response = TestMultitenancyAPIHelper.associateUserToTenant(new TenantIdentifier(null, "a1", "t2"), userId1, process.getProcess()); + assertEquals("EMAIL_ALREADY_EXISTS_ERROR", response.getAsJsonPrimitive("status").getAsString()); + } + + @Test + public void testThatUserWithSamePhoneCannotBeAssociatedToATenantForPless() throws Exception { + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + createTenants(); + JsonObject user1 = TestMultitenancyAPIHelper.plSignInUpNumber(new TenantIdentifier(null, "a1", "t1"), "+919876543210", + process.getProcess()); + String userId1 = user1.get("id").getAsString(); + + TestMultitenancyAPIHelper.plSignInUpNumber(new TenantIdentifier(null, "a1", "t2"), "+919876543210", + process.getProcess()); + + JsonObject response = TestMultitenancyAPIHelper.associateUserToTenant(new TenantIdentifier(null, "a1", "t2"), userId1, process.getProcess()); + assertEquals("PHONE_NUMBER_ALREADY_EXISTS_ERROR", response.getAsJsonPrimitive("status").getAsString()); + } } From de76ce8dd02ac0dffc04f3fbd1077dd05cb2ed8a Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Fri, 1 Sep 2023 11:34:58 +0530 Subject: [PATCH 100/131] fix: merge latest (#782) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: add exp and iat to JWT payloads without scientific notation (#765) * adding dev-v6.0.9 tag to this commit to ensure building * fix: fix handling of b64 and b64url encoded access tokens (#767) * adding dev-v6.0.10 tag to this commit to ensure building * Update release.md * Update release.md * fix: ee featureflag cron job (#778) * fix: ee featureflag cron job * fix: test * fix: tests * fix: tests * adding dev-v6.0.11 tag to this commit to ensure building * fix: test (#779) * adding dev-v6.0.11 tag to this commit to ensure building * fix: test (#780) * fix: test * fix: test * adding dev-v6.0.11 tag to this commit to ensure building * fix: test (#781) * adding dev-v6.0.11 tag to this commit to ensure building --------- Co-authored-by: Mihály Lengyel Co-authored-by: rishabhpoddar --- .github/ISSUE_TEMPLATE/release.md | 7 ++ CHANGELOG.md | 12 +++ build.gradle | 2 +- cli/jar/cli.jar | Bin 47546 -> 47546 bytes downloader/jar/downloader.jar | Bin 15229 -> 15229 bytes ee/jar/ee.jar | Bin 13259 -> 13262 bytes .../java/io/supertokens/ee/EEFeatureFlag.java | 4 +- jar/{core-6.0.8.jar => core-6.0.11.jar} | Bin 658492 -> 658527 bytes src/main/java/io/supertokens/Main.java | 3 +- .../supertokens/jwt/JWTSigningFunctions.java | 7 +- src/main/java/io/supertokens/utils/Utils.java | 3 +- .../java/io/supertokens/test/CronjobTest.java | 88 +++++++++++++++++- .../io/supertokens/test/FeatureFlagTest.java | 39 ++++++++ .../test/session/AccessTokenTest.java | 44 +++++++-- 14 files changed, 187 insertions(+), 22 deletions(-) rename jar/{core-6.0.8.jar => core-6.0.11.jar} (90%) diff --git a/.github/ISSUE_TEMPLATE/release.md b/.github/ISSUE_TEMPLATE/release.md index 3ef05dfdd..ad9f7269e 100644 --- a/.github/ISSUE_TEMPLATE/release.md +++ b/.github/ISSUE_TEMPLATE/release.md @@ -119,6 +119,7 @@ labels: - [ ] Change [checklist in contributing guide for which tables to pick when migrating data from dev to prod instance](https://test.supertokens.com/docs/contribute/checklists/saas/tables-to-consider-for-data-migration-dev-to-prod). - [ ] Update license key used for cores to include nea feature. - [ ] Update table schema in mysql / postgresql section for self hosted in docs + - [ ] Update API that returns the list of paid features in saas dashboard - [ ] [supertokens-node:X.Y](https://github.com/supertokens/supertokens-node/tree/X.Y) - [ ] [supertokens-golang:X.Y](https://github.com/supertokens/supertokens-golang/tree/X.Y) - [ ] [supertokens-website:X.Y](https://github.com/supertokens/supertokens-website/tree/X.Y) @@ -183,6 +184,12 @@ curl --location --request POST 'https://try.supertokens.com/recipe/dashboard/use --header 'Content-Type: application/json' \ --data-raw '{"email": "rishabh@supertokens.com","password": "abcd1234"}' +curl --location --request POST 'https://try.supertokens.com/recipe/dashboard/user' \ +--header 'rid: dashboard' \ +--header 'api-key: ' \ +--header 'Content-Type: application/json' \ +--data-raw '{"email": "demo@supertokens.com","password": "abcd1234"}' + curl --location --request PUT 'https://try.supertokens.com/recipe/multitenancy/tenant' \ --header 'Content-Type: application/json' \ --data-raw '{ diff --git a/CHANGELOG.md b/CHANGELOG.md index eb6a91080..a6cf0be20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,6 +55,18 @@ ALTER TABLE emailpassword_pswd_reset_tokens ADD CONSTRAINT emailpassword_pswd_re ALTER TABLE emailpassword_pswd_reset_tokens ADD COLUMN email VARCHAR(256); ``` +## [6.0.11] - 2023-08-16 + +- Fixed feature flag cron job + +## [6.0.10] - 2023-08-16 + +- Fixed an encoding/decoding issue for certain access token payloads + +## [6.0.9] - 2023-08-14 + +- Now using decimal notation to add numbers into the access token payload (instead of scientific notation) + ## [6.0.8] - 2023-08-01 - Fixes CUD validation starting with number. diff --git a/build.gradle b/build.gradle index 49e9735c9..83265aeea 100644 --- a/build.gradle +++ b/build.gradle @@ -19,7 +19,7 @@ compileTestJava { options.encoding = "UTF-8" } // } //} -version = "6.0.8" +version = "6.0.11" repositories { diff --git a/cli/jar/cli.jar b/cli/jar/cli.jar index 68c5606cfed5c236f84078292f980153013e94b4..e34e8ae494be2f3da80e37a1880e7c62bb89902a 100644 GIT binary patch delta 835 zcmdn>nQ7N&Cf)#VW)?061`ZB}nLFht@~Sa|C^IiLAQjF41WX_T2$+F5Ghd&X0ae2? z9k7P01|VuO2ctcR-t5IVg%vFDkj)(|pv|5p1{Rni>k1aQBiqOe7Wf!%JbALVAXsp@ z_HHh)V5(UFSYU_Qenv2(%Hn|tSSHrj6D+XOcNxqEE8PZXb zEMSIt+;*@blRw70fCVfPW<&IX><&m{oy?a8(JPx)%EpK4@yUjrl9TPXaPgygeDkFo zABgdag=-0*4Yg4>6*z-U?m?XKZnTN zp2p6F%?+Fj!2!5gY2gouZ)6u+fOSSJ=789ryy62$U~<^XzYuzr37GC(1&)--=U3^0 z_>)#!frV{X+kxr2)xKc*-0DOyZL}s7On0rx1k>Nv1cPaxwW$z#ll0{CYt6xI&UMLP zI)7apn7*)XCYa7yUk#>Ttq%v&E*o;e^p*_}vw1c`%&yp&1{Qy~F#}BdYzhL?3pYW` z^#R(Yv>9So%w~w#q0Js(HN0COdds#z?6|PS3oI_XH5yD;Z-uz`@m7fYEw_b%#V2ip w=)b!SoW>>_Y=@XLb2~U?O@6u^B5$xG39NqV4mU7;ZwJI2lbw)oE8Xb|0NYwZ!2kdN delta 835 zcmdn>nQ7N&Cf)#VW)?061`ZB}zS)cudDWOfl$no;+Dw5G=S{ zdp8$YFx4yoEU?3DKO>k?W${1+EEDVN2^LuCy9{Q7@#OQt9Fu#4dB75Lf@|2o4CyFI z7BIs+ZadhJ$sglgzycNtvmtsxb_b-fPUcI4=#@uiSjbj@TZu#%6HpF?DB zPh;o8<_6A%-~imLwD1SSH?oT@z&axqb3p7*Uhx4WFga}HUkJU*1Wfm?0!PZ^^Q-hg z{7I{=z{0kx?Z9;1YF{vYZgnD>mkqgKddmig**qH|W>;)X1B*Z0m;t7JHU)v{g_|Je z`T*@x+6=KPW-~;*+*P w^xxeEPGge|wnNOBxgDIcCO_Q{kvG_p1Xe$FhZ~r_w*z91$xcYPmF{!}021~pSpWb4 diff --git a/downloader/jar/downloader.jar b/downloader/jar/downloader.jar index 80c8ebfa64d016bd47bfa2d87374e00376e4206f..7a4eb7a87fde8d5d767d1b0cca06eea336c1d5d6 100644 GIT binary patch delta 375 zcmexc_P2~Tz?+$ci-CcIgJI52`H8%0%pl6lOASbcGXMb-hyVg+AkNI!XJ$avuuKQ6 z;i@@^+RV?mk`2U|yqnz_EWpSS%nB9Ip6qMRGr5nK2P`p{_a+yZQ7O^K1ZHqaSAiLm z+hv`=0%v5WLuAqvjzbs*%5B^rsmVL_9KlNd>ajueuo>Ee1w0I^nZYWKnV5j-zb0Ub z$@ZptV19wA6_{RY>I$a6n#O|ZP_sZVz0xcLOpBO9_!;I&VE$S2958KT5e24aS_FXU iHx^l7I?@ti&Q42zFrV8BLdRNVg83({e8IG&bqWAfzJVM7 delta 375 zcmexc_P2~Tz?+$ci-CcIgJHsK#)-UY%pl6lOASbcGXMb-hyVg+AkNI!XJ$avuuKQ6 z;i@@^+RV?mk`2U|yqnz_EWpSS%nB9Ip6qMRGr5nK2P`p{_a+yZQ7O^K1ZHqaSAiLm z+hv`=0%v5WLuAqvjzbs*%5B^rsmVL_9KlNd>ajueuo>Ee1w0I^nZYWKnV5j-zb0Ub z$@ZptV19wA6_{RY>I$a6n#O|ZP_sZVz0xcLOpBO9_!;I&VE$S2958KT5e24aS_FXU iHx^l7I?@ti&Q42zFrV8BLdRNVg83({e8IG&bqWCNUUl36 diff --git a/ee/jar/ee.jar b/ee/jar/ee.jar index 7c0f3649f87184be4c642c8f5de301441097a5dd..11e6be2a9df4f3b12d7cba66182919b5b4903a32 100644 GIT binary patch delta 10063 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

Di6B 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 diff --git a/ee/src/main/java/io/supertokens/ee/EEFeatureFlag.java b/ee/src/main/java/io/supertokens/ee/EEFeatureFlag.java index 2b0a94dba..8efe42552 100644 --- a/ee/src/main/java/io/supertokens/ee/EEFeatureFlag.java +++ b/ee/src/main/java/io/supertokens/ee/EEFeatureFlag.java @@ -52,8 +52,8 @@ import java.util.List; public class EEFeatureFlag implements io.supertokens.featureflag.EEFeatureFlagInterface { - public static final int INTERVAL_BETWEEN_SERVER_SYNC = 1000 * 3600 * 24; // 1 day. - private static final long INTERVAL_BETWEEN_DB_READS = (long) 1000 * 3600 * 4; // 4 hour. + public static final int INTERVAL_BETWEEN_SERVER_SYNC = 3600 * 24; // 1 day (in seconds). + private static final long INTERVAL_BETWEEN_DB_READS = (long) 1000 * 3600 * 4; // 4 hour (in millis). public static final String REQUEST_ID = "licensecheck"; public static final String FEATURE_FLAG_KEY_IN_DB = "FEATURE_FLAG"; diff --git a/jar/core-6.0.8.jar b/jar/core-6.0.11.jar similarity index 90% rename from jar/core-6.0.8.jar rename to jar/core-6.0.11.jar index da52cfb58f0db043e7fde5056502f1b63586b75a..2704fcaef5d5bad97612a72005f23d0cb32f877a 100644 GIT binary patch delta 34569 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 delta 34206 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 jwtPayload = new Gson().fromJson(payload, HashMap.class); - if (jwksDomain != null) { - jwtPayload.putIfAbsent("iss", jwksDomain); + if (jwksDomain != null && !payload.has("iss")){ + payload.addProperty("iss", jwksDomain); } JWTCreator.Builder builder = com.auth0.jwt.JWT.create(); @@ -141,7 +140,7 @@ public static String createJWTToken(JWTSigningKey.SupportedAlgorithms supportedA if (jwksDomain != null) { builder.withIssuer(jwksDomain); } - builder.withPayload(jwtPayload); + builder.withPayload(payload.toString()); return builder.sign(signingAlgorithm); } diff --git a/src/main/java/io/supertokens/utils/Utils.java b/src/main/java/io/supertokens/utils/Utils.java index 1a0232180..37b5a9e98 100644 --- a/src/main/java/io/supertokens/utils/Utils.java +++ b/src/main/java/io/supertokens/utils/Utils.java @@ -77,8 +77,9 @@ public static String convertToBase64(String str) { return new String(Base64.getEncoder().encode(stringToBytes(str)), StandardCharsets.UTF_8); } + // This function deserializes both B64 and B64URL encodings public static String convertFromBase64(String str) { - return new String(Base64.getDecoder().decode(stringToBytes(str)), StandardCharsets.UTF_8); + return new String(Base64.getDecoder().decode(stringToBytes(str.replace("-", "+").replace("_", "/"))), StandardCharsets.UTF_8); } public static String throwableStacktraceToString(Throwable e) { diff --git a/src/test/java/io/supertokens/test/CronjobTest.java b/src/test/java/io/supertokens/test/CronjobTest.java index a9bf933b1..606ea745b 100644 --- a/src/test/java/io/supertokens/test/CronjobTest.java +++ b/src/test/java/io/supertokens/test/CronjobTest.java @@ -31,6 +31,7 @@ import io.supertokens.multitenancy.MultitenancyHelper; import io.supertokens.pluginInterface.STORAGE_TYPE; import io.supertokens.pluginInterface.Storage; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.multitenancy.*; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.storageLayer.StorageLayer; @@ -39,11 +40,9 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestRule; +import org.reflections.Reflections; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; +import java.util.*; import static org.junit.Assert.*; @@ -780,4 +779,85 @@ public void testThatCoreAutomaticallySyncsToConfigChangesInDb() throws Exception process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); } + + @Test + public void testThatNoCronJobIntervalIsMoreThanADay() throws Exception { + String[] args = {"../"}; + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + // ensure none of the tasks have an interval more than a day + for (CronTask task : Cronjobs.getInstance(process.getProcess()).getTasks()) { + assertTrue(task.getIntervalTimeSeconds() <= 3600 * 24); + assertTrue(task.getInitialWaitTimeSeconds() <= 3600 * 24); + } + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testThatThereAreTasksOfAllCronTaskClassesAndHaveCorrectIntervals() throws Exception { + String[] args = {"../"}; + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + Reflections reflections = new Reflections("io.supertokens"); + Set> classes = reflections.getSubTypesOf(CronTask.class); + + Set classNames = new HashSet<>(); + + for (Class cls : classes) { + if (!cls.getName().contains("io.supertokens.test")) { + classNames.add(cls.getName()); + } + } + + // Note that the time is in seconds + Map intervals = new HashMap<>(); + intervals.put("io.supertokens.ee.cronjobs.EELicenseCheck", 86400); + intervals.put("io.supertokens.cronjobs.syncCoreConfigWithDb.SyncCoreConfigWithDb", 60); + intervals.put("io.supertokens.cronjobs.deleteExpiredSessions.DeleteExpiredSessions", 43200); + intervals.put("io.supertokens.cronjobs.deleteExpiredPasswordResetTokens.DeleteExpiredPasswordResetTokens", 3600); + intervals.put("io.supertokens.cronjobs.deleteExpiredEmailVerificationTokens.DeleteExpiredEmailVerificationTokens", 43200); + intervals.put("io.supertokens.cronjobs.deleteExpiredPasswordlessDevices.DeleteExpiredPasswordlessDevices", 3600); + intervals.put("io.supertokens.cronjobs.deleteExpiredTotpTokens.DeleteExpiredTotpTokens", 3600); + intervals.put("io.supertokens.cronjobs.deleteExpiredDashboardSessions.DeleteExpiredDashboardSessions", 43200); + intervals.put("io.supertokens.cronjobs.telemetry.Telemetry", 86400); + intervals.put("io.supertokens.cronjobs.deleteExpiredAccessTokenSigningKeys.DeleteExpiredAccessTokenSigningKeys", 86400); + + Map delays = new HashMap<>(); + delays.put("io.supertokens.ee.cronjobs.EELicenseCheck", 86400); + delays.put("io.supertokens.cronjobs.syncCoreConfigWithDb.SyncCoreConfigWithDb", 0); + delays.put("io.supertokens.cronjobs.deleteExpiredSessions.DeleteExpiredSessions", 0); + delays.put("io.supertokens.cronjobs.deleteExpiredPasswordResetTokens.DeleteExpiredPasswordResetTokens", 0); + delays.put("io.supertokens.cronjobs.deleteExpiredEmailVerificationTokens.DeleteExpiredEmailVerificationTokens", 0); + delays.put("io.supertokens.cronjobs.deleteExpiredPasswordlessDevices.DeleteExpiredPasswordlessDevices", 0); + delays.put("io.supertokens.cronjobs.deleteExpiredTotpTokens.DeleteExpiredTotpTokens", 0); + delays.put("io.supertokens.cronjobs.deleteExpiredDashboardSessions.DeleteExpiredDashboardSessions", 0); + delays.put("io.supertokens.cronjobs.telemetry.Telemetry", 0); + delays.put("io.supertokens.cronjobs.deleteExpiredAccessTokenSigningKeys.DeleteExpiredAccessTokenSigningKeys", 0); + + List allTasks = Cronjobs.getInstance(process.getProcess()).getTasks(); + assertEquals(10, allTasks.size()); + + for (CronTask task : allTasks) { + assertEquals(intervals.get(task.getClass().getName()).intValue(), task.getIntervalTimeSeconds()); + assertEquals(delays.get(task.getClass().getName()).intValue(), task.getInitialWaitTimeSeconds()); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } } diff --git a/src/test/java/io/supertokens/test/FeatureFlagTest.java b/src/test/java/io/supertokens/test/FeatureFlagTest.java index c52221252..15be7c36e 100644 --- a/src/test/java/io/supertokens/test/FeatureFlagTest.java +++ b/src/test/java/io/supertokens/test/FeatureFlagTest.java @@ -21,6 +21,10 @@ import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; import io.supertokens.ProcessState; +import io.supertokens.cronjobs.CronTask; +import io.supertokens.cronjobs.CronTaskTest; +import io.supertokens.cronjobs.Cronjobs; +import io.supertokens.cronjobs.syncCoreConfigWithDb.SyncCoreConfigWithDb; import io.supertokens.emailpassword.EmailPassword; import io.supertokens.featureflag.EE_FEATURES; import io.supertokens.featureflag.FeatureFlag; @@ -35,6 +39,7 @@ import io.supertokens.session.Session; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.httpRequest.HttpRequestForTesting; +import io.supertokens.test.multitenant.api.TestMultitenancyAPIHelper; import io.supertokens.webserver.WebserverAPI; import org.junit.*; import org.junit.rules.TestRule; @@ -702,4 +707,38 @@ public void testPaidFeaturesAreEnabledIfUsingInMemoryDatabase() throws Exception process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); } + + @Test + public void testNetworkCallIsMadeInCoreInit() throws Exception { + String[] args = {"../"}; + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + if (StorageLayer.isInMemDb(process.getProcess())) { + return; + } + + // While adding license + TestMultitenancyAPIHelper.addLicense(OPAQUE_KEY_WITH_MULTITENANCY_FEATURE, process.getProcess()); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.LICENSE_KEY_CHECK_NETWORK_CALL)); + ProcessState.getInstance(process.getProcess()).clear(); + + process.kill(false); + + + // Restart core and check if the call was made during init + process = TestingProcessManager.start(args); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.LICENSE_KEY_CHECK_NETWORK_CALL)); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } } diff --git a/src/test/java/io/supertokens/test/session/AccessTokenTest.java b/src/test/java/io/supertokens/test/session/AccessTokenTest.java index 968c2a460..30247227a 100644 --- a/src/test/java/io/supertokens/test/session/AccessTokenTest.java +++ b/src/test/java/io/supertokens/test/session/AccessTokenTest.java @@ -18,6 +18,7 @@ import com.google.gson.Gson; import com.google.gson.JsonObject; +import com.google.gson.JsonParser; import io.supertokens.ProcessState.EventAndException; import io.supertokens.ProcessState.PROCESS_STATE; import io.supertokens.exceptions.AccessTokenPayloadError; @@ -256,7 +257,8 @@ public void inputOutputTest() throws Exception { EventAndException e = process.checkOrWaitForEvent(PROCESS_STATE.STARTED); assertNotNull(e); JsonObject jsonObj = new JsonObject(); - jsonObj.addProperty("key", "value"); + String testValue = "asdf???123"; + jsonObj.addProperty("key", testValue); // db key long expiryTime = System.currentTimeMillis() + 1000; @@ -268,10 +270,15 @@ public void inputOutputTest() throws Exception { assertEquals("userId", info.recipeUserId); assertEquals("refreshTokenHash1", info.refreshTokenHash1); assertEquals("parentRefreshTokenHash1", info.parentRefreshTokenHash1); - assertEquals("value", info.userData.get("key").getAsString()); + assertEquals(testValue, info.userData.get("key").getAsString()); assertEquals("antiCsrfToken", info.antiCsrfToken); assertEquals(expiryTime / 1000 * 1000, info.expiryTime); + JsonObject payload = (JsonObject) new JsonParser() + .parse(io.supertokens.utils.Utils.convertFromBase64(newToken.token.split("\\.")[1])); + // This throws if the number is in scientific (E) format + assertEquals(expiryTime / 1000, Long.parseLong(payload.get("exp").toString())); + JWT.JWTPreParseInfo jwtInfo = JWT.preParseJWTInfo(newToken.token); assertNotNull(jwtInfo.kid); assertEquals(jwtInfo.version, AccessToken.getLatestVersion()); @@ -286,22 +293,29 @@ public void inputOutputTestStatic() throws Exception { EventAndException e = process.checkOrWaitForEvent(PROCESS_STATE.STARTED); assertNotNull(e); JsonObject jsonObj = new JsonObject(); - jsonObj.addProperty("key", "value"); + String testValue = "asdf???123"; + jsonObj.addProperty("key", testValue); // db key long expiryTime = System.currentTimeMillis() + 1000; TokenInfo newToken = AccessToken.createNewAccessToken(process.getProcess(), "sessionHandle", "userId", "refreshTokenHash1", "parentRefreshTokenHash1", jsonObj, "antiCsrfToken", expiryTime, AccessToken.getLatestVersion(), true); + System.out.println(newToken.token); AccessTokenInfo info = AccessToken.getInfoFromAccessToken(process.getProcess(), newToken.token, true); assertEquals("sessionHandle", info.sessionHandle); assertEquals("userId", info.recipeUserId); assertEquals("refreshTokenHash1", info.refreshTokenHash1); assertEquals("parentRefreshTokenHash1", info.parentRefreshTokenHash1); - assertEquals("value", info.userData.get("key").getAsString()); + assertEquals(testValue, info.userData.get("key").getAsString()); assertEquals("antiCsrfToken", info.antiCsrfToken); assertEquals(expiryTime / 1000 * 1000, info.expiryTime); + JsonObject payload = (JsonObject) new JsonParser() + .parse(io.supertokens.utils.Utils.convertFromBase64(newToken.token.split("\\.")[1])); + // This throws if the number is in scientific (E) format + assertEquals(expiryTime / 1000, Long.parseLong(payload.get("exp").toString())); + JWT.JWTPreParseInfo jwtInfo = JWT.preParseJWTInfo(newToken.token); assertNotNull(jwtInfo.kid); assertEquals(jwtInfo.version, AccessToken.getLatestVersion()); @@ -315,7 +329,8 @@ public void inputOutputTestV2() throws Exception { EventAndException e = process.checkOrWaitForEvent(PROCESS_STATE.STARTED); assertNotNull(e); JsonObject jsonObj = new JsonObject(); - jsonObj.addProperty("key", "value"); + String testValue = "asdf???123"; + jsonObj.addProperty("key", testValue); // db key long expiryTime = System.currentTimeMillis() + 1000; @@ -327,9 +342,15 @@ public void inputOutputTestV2() throws Exception { assertEquals("userId", info.recipeUserId); assertEquals("refreshTokenHash1", info.refreshTokenHash1); assertEquals("parentRefreshTokenHash1", info.parentRefreshTokenHash1); - assertEquals("value", info.userData.get("key").getAsString()); + assertEquals(testValue, info.userData.get("key").getAsString()); assertEquals("antiCsrfToken", info.antiCsrfToken); assertEquals(expiryTime, info.expiryTime); + + JsonObject payload = (JsonObject) new JsonParser() + .parse(io.supertokens.utils.Utils.convertFromBase64(newToken.token.split("\\.")[1])); + // This throws if the number is in scientific (E) format + assertEquals(expiryTime, Long.parseLong(payload.get("expiryTime").toString())); + process.kill(); } @@ -343,7 +364,8 @@ public void inputOutputTestv1() throws InterruptedException, InvalidKeyException EventAndException e = process.checkOrWaitForEvent(PROCESS_STATE.STARTED); assertNotNull(e); JsonObject jsonObj = new JsonObject(); - jsonObj.addProperty("key", "value"); + String testValue = "asdf???123"; + jsonObj.addProperty("key", testValue); // db key TokenInfo newToken = AccessToken.createNewAccessTokenV1(process.getProcess(), "sessionHandle", "userId", @@ -353,8 +375,14 @@ public void inputOutputTestv1() throws InterruptedException, InvalidKeyException assertEquals("userId", info.recipeUserId); assertEquals("refreshTokenHash1", info.refreshTokenHash1); assertEquals("parentRefreshTokenHash1", info.parentRefreshTokenHash1); - assertEquals("value", info.userData.get("key").getAsString()); + assertEquals(testValue, info.userData.get("key").getAsString()); assertEquals("antiCsrfToken", info.antiCsrfToken); + + JsonObject payload = (JsonObject) new JsonParser() + .parse(io.supertokens.utils.Utils.convertFromBase64(newToken.token.split("\\.")[1])); + // This throws if the number is in scientific (E) format + Long.parseLong(payload.get("expiryTime").toString()); + process.kill(); } From 3e39d0b4676b9691582bb25bc9f17c09aa0dd021 Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Wed, 6 Sep 2023 16:29:09 +0530 Subject: [PATCH 101/131] fix: tests (#776) * fix: tests * fix: removed removal of active user * fix: user id mapping deletion * fix: more fixes * fix: user delete * fix: test * fix: test * fix: session fix and thirdparty ev test * fix: pr comments * fix: pr comments * fix: tests * fix: phone number change related --- .../io/supertokens/authRecipe/AuthRecipe.java | 15 +- .../java/io/supertokens/inmemorydb/Start.java | 8 + .../multitenancy/Multitenancy.java | 74 +- ...ryUserWithEmailAlreadyExistsException.java | 23 + ...WithPhoneNumberAlreadyExistsException.java | 23 + ...hThirdPartyInfoAlreadyExistsException.java | 23 + .../DisassociationNotAllowedException.java | 23 + .../passwordless/Passwordless.java | 29 +- .../PhoneNumberChangeNotAllowedException.java | 20 + .../java/io/supertokens/session/Session.java | 3 +- .../io/supertokens/thirdparty/ThirdParty.java | 161 ++-- .../api/accountlinking/LinkAccountsAPI.java | 2 - .../AssociateUserToTenantAPI.java | 10 + .../DisassociateUserFromTenant.java | 11 +- .../webserver/api/passwordless/UserAPI.java | 7 +- .../io/supertokens/test/AuthRecipeTest.java | 35 +- .../test/AuthRecipesParallelTest.java | 144 ++++ .../test/accountlinking/MultitenantTest.java | 792 ++++++++++++------ .../accountlinking/api/ActiveUserTest.java | 8 +- .../emailpassword/api/MultitenantAPITest.java | 17 +- .../thirdparty/api/EmailVerificationTest.java | 69 ++ .../api/ThirdPartySignInUpAPITest4_0.java | 2 + 22 files changed, 1131 insertions(+), 368 deletions(-) create mode 100644 src/main/java/io/supertokens/multitenancy/exception/AnotherPrimaryUserWithEmailAlreadyExistsException.java create mode 100644 src/main/java/io/supertokens/multitenancy/exception/AnotherPrimaryUserWithPhoneNumberAlreadyExistsException.java create mode 100644 src/main/java/io/supertokens/multitenancy/exception/AnotherPrimaryUserWithThirdPartyInfoAlreadyExistsException.java create mode 100644 src/main/java/io/supertokens/multitenancy/exception/DisassociationNotAllowedException.java create mode 100644 src/main/java/io/supertokens/passwordless/exceptions/PhoneNumberChangeNotAllowedException.java create mode 100644 src/test/java/io/supertokens/test/AuthRecipesParallelTest.java diff --git a/src/main/java/io/supertokens/authRecipe/AuthRecipe.java b/src/main/java/io/supertokens/authRecipe/AuthRecipe.java index 75a28a173..5633ebf1d 100644 --- a/src/main/java/io/supertokens/authRecipe/AuthRecipe.java +++ b/src/main/java/io/supertokens/authRecipe/AuthRecipe.java @@ -494,6 +494,9 @@ private static CreatePrimaryUserResult canCreatePrimaryUserHelper(TransactionCon .listPrimaryUsersByPhoneNumber_Transaction(appIdentifierWithStorage, con, loginMethod.phoneNumber); for (AuthRecipeUserInfo user : usersWithSamePhoneNumber) { + if (!user.tenantIds.contains(tenantId)) { + continue; + } if (user.isPrimaryUser) { throw new AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException(user.getSupertokensUserId(), "This user's phone number is already associated with another user" + @@ -817,6 +820,10 @@ private static void deleteUserHelper(TransactionConnection con, AppIdentifierWit if (removeAllLinkedAccounts || userToDelete.loginMethods.length == 1) { if (userToDelete.getSupertokensUserId().equals(userIdToDeleteForAuthRecipe)) { primaryUserIdToDeleteNonAuthRecipe = userIdToDeleteForNonAuthRecipeForRecipeUserId; + if (primaryUserIdToDeleteNonAuthRecipe == null) { + deleteAuthRecipeUser(con, appIdentifierWithStorage, userToDelete.getSupertokensUserId(), + true); + } } else { // this is always type supertokens user ID cause it's from a user from the database. io.supertokens.pluginInterface.useridmapping.UserIdMapping mappingResult = @@ -918,15 +925,15 @@ private static void deleteNonAuthRecipeUser(TransactionConnection con, AppIdenti private static void deleteAuthRecipeUser(TransactionConnection con, AppIdentifierWithStorage appIdentifierWithStorage, String - userId, boolean deleteUserIdMappingToo) + userId, boolean deleteFromUserIdToAppIdTableToo) throws StorageQueryException { // auth recipe deletions here only appIdentifierWithStorage.getEmailPasswordStorage() - .deleteEmailPasswordUser_Transaction(con, appIdentifierWithStorage, userId, deleteUserIdMappingToo); + .deleteEmailPasswordUser_Transaction(con, appIdentifierWithStorage, userId, deleteFromUserIdToAppIdTableToo); appIdentifierWithStorage.getThirdPartyStorage() - .deleteThirdPartyUser_Transaction(con, appIdentifierWithStorage, userId, deleteUserIdMappingToo); + .deleteThirdPartyUser_Transaction(con, appIdentifierWithStorage, userId, deleteFromUserIdToAppIdTableToo); appIdentifierWithStorage.getPasswordlessStorage() - .deletePasswordlessUser_Transaction(con, appIdentifierWithStorage, userId, deleteUserIdMappingToo); + .deletePasswordlessUser_Transaction(con, appIdentifierWithStorage, userId, deleteFromUserIdToAppIdTableToo); } public static boolean deleteNonAuthRecipeUser(TenantIdentifierWithStorage diff --git a/src/main/java/io/supertokens/inmemorydb/Start.java b/src/main/java/io/supertokens/inmemorydb/Start.java index 7820d2b26..88bf47678 100644 --- a/src/main/java/io/supertokens/inmemorydb/Start.java +++ b/src/main/java/io/supertokens/inmemorydb/Start.java @@ -2786,6 +2786,14 @@ public AuthRecipeUserInfo[] listPrimaryUsersByPhoneNumber_Transaction(AppIdentif // } } + @Override + public AuthRecipeUserInfo[] listPrimaryUsersByThirdPartyInfo(AppIdentifier appIdentifier, + String thirdPartyId, + String thirdPartyUserId) + throws StorageQueryException { + return null; // TODO + } + @Override public AuthRecipeUserInfo[] listPrimaryUsersByThirdPartyInfo_Transaction(AppIdentifier appIdentifier, TransactionConnection con, diff --git a/src/main/java/io/supertokens/multitenancy/Multitenancy.java b/src/main/java/io/supertokens/multitenancy/Multitenancy.java index 03858a5c0..1aaa2a61e 100644 --- a/src/main/java/io/supertokens/multitenancy/Multitenancy.java +++ b/src/main/java/io/supertokens/multitenancy/Multitenancy.java @@ -384,7 +384,9 @@ public static boolean addUserIdToTenant(Main main, TenantIdentifierWithStorage t String userId) throws TenantOrAppNotFoundException, UnknownUserIdException, StorageQueryException, FeatureNotEnabledException, DuplicateEmailException, DuplicatePhoneNumberException, - DuplicateThirdPartyUserException { + DuplicateThirdPartyUserException, AnotherPrimaryUserWithPhoneNumberAlreadyExistsException, + AnotherPrimaryUserWithEmailAlreadyExistsException, + AnotherPrimaryUserWithThirdPartyInfoAlreadyExistsException { if (Arrays.stream(FeatureFlag.getInstance(main, new AppIdentifier(null, null)).getEnabledFeatures()) .noneMatch(ee_features -> ee_features == EE_FEATURES.MULTI_TENANCY)) { throw new FeatureNotEnabledException(EE_FEATURES.MULTI_TENANCY); @@ -396,7 +398,7 @@ public static boolean addUserIdToTenant(Main main, TenantIdentifierWithStorage t String tenantId = tenantIdentifierWithStorage.getTenantId(); AuthRecipeUserInfo userToAssociate = storage.getPrimaryUserById_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), con, userId); - if (userToAssociate.isPrimaryUser) { + if (userToAssociate != null && userToAssociate.isPrimaryUser) { Set emails = new HashSet<>(); Set phoneNumbers = new HashSet<>(); Set thirdParties = new HashSet<>(); @@ -418,7 +420,16 @@ public static boolean addUserIdToTenant(Main main, TenantIdentifierWithStorage t AuthRecipeUserInfo[] users = storage.listPrimaryUsersByEmail_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), con, email); for (AuthRecipeUserInfo user : users) { if (user.tenantIds.contains(tenantId) && !user.getSupertokensUserId().equals(userId)) { - throw new StorageTransactionLogicException(new DuplicateEmailException()); + for (LoginMethod lm1 : user.loginMethods) { + if (lm1.tenantIds.contains(tenantId)) { + for (LoginMethod lm2 : userToAssociate.loginMethods) { + if (lm1.recipeId.equals(lm2.recipeId) && email.equals(lm1.email) && lm1.email.equals(lm2.email)) { + throw new StorageTransactionLogicException(new DuplicateEmailException()); + } + } + } + } + throw new StorageTransactionLogicException(new AnotherPrimaryUserWithEmailAlreadyExistsException(user.getSupertokensUserId())); } } } @@ -427,7 +438,16 @@ public static boolean addUserIdToTenant(Main main, TenantIdentifierWithStorage t AuthRecipeUserInfo[] users = storage.listPrimaryUsersByPhoneNumber_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), con, phoneNumber); for (AuthRecipeUserInfo user : users) { if (user.tenantIds.contains(tenantId) && !user.getSupertokensUserId().equals(userId)) { - throw new StorageTransactionLogicException(new DuplicatePhoneNumberException()); + for (LoginMethod lm1 : user.loginMethods) { + if (lm1.tenantIds.contains(tenantId)) { + for (LoginMethod lm2 : userToAssociate.loginMethods) { + if (lm1.recipeId.equals(lm2.recipeId) && phoneNumber.equals(lm1.phoneNumber) && lm1.phoneNumber.equals(lm2.phoneNumber)) { + throw new StorageTransactionLogicException(new DuplicatePhoneNumberException()); + } + } + } + } + throw new StorageTransactionLogicException(new AnotherPrimaryUserWithPhoneNumberAlreadyExistsException(user.getSupertokensUserId())); } } } @@ -436,12 +456,25 @@ public static boolean addUserIdToTenant(Main main, TenantIdentifierWithStorage t AuthRecipeUserInfo[] users = storage.listPrimaryUsersByThirdPartyInfo_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), con, tp.id, tp.userId); for (AuthRecipeUserInfo user : users) { if (user.tenantIds.contains(tenantId) && !user.getSupertokensUserId().equals(userId)) { - throw new StorageTransactionLogicException(new DuplicateThirdPartyUserException()); + for (LoginMethod lm1 : user.loginMethods) { + if (lm1.tenantIds.contains(tenantId)) { + for (LoginMethod lm2 : userToAssociate.loginMethods) { + if (lm1.recipeId.equals(lm2.recipeId) && tp.equals(lm1.thirdParty) && lm1.thirdParty.equals(lm2.thirdParty)) { + throw new StorageTransactionLogicException(new DuplicateThirdPartyUserException()); + } + } + } + } + + throw new StorageTransactionLogicException(new AnotherPrimaryUserWithThirdPartyInfoAlreadyExistsException(user.getSupertokensUserId())); } } } } + // userToAssociate may be null if the user is not associated to any tenants, we can still try and + // associate it. This happens only in CDI 3.0 where we allow disassociation from all tenants + // This will not happen in CDI >= 4.0 because we will not allow disassociation from all tenants try { boolean result = ((MultitenancySQLStorage) storage).addUserIdToTenant_Transaction(tenantIdentifierWithStorage, con, userId); storage.commitTransaction(con); @@ -462,20 +495,51 @@ public static boolean addUserIdToTenant(Main main, TenantIdentifierWithStorage t throw (TenantOrAppNotFoundException) e.actualException; } else if (e.actualException instanceof UnknownUserIdException) { throw (UnknownUserIdException) e.actualException; + } else if (e.actualException instanceof AnotherPrimaryUserWithPhoneNumberAlreadyExistsException) { + throw (AnotherPrimaryUserWithPhoneNumberAlreadyExistsException) e.actualException; + } else if (e.actualException instanceof AnotherPrimaryUserWithEmailAlreadyExistsException) { + throw (AnotherPrimaryUserWithEmailAlreadyExistsException) e.actualException; + } else if (e.actualException instanceof AnotherPrimaryUserWithThirdPartyInfoAlreadyExistsException) { + throw (AnotherPrimaryUserWithThirdPartyInfoAlreadyExistsException) e.actualException; } throw new StorageQueryException(e.actualException); } } + @TestOnly public static boolean removeUserIdFromTenant(Main main, TenantIdentifierWithStorage tenantIdentifierWithStorage, String userId, String externalUserId) throws FeatureNotEnabledException, TenantOrAppNotFoundException, StorageQueryException, UnknownUserIdException { + try { + return removeUserIdFromTenant(main, tenantIdentifierWithStorage, userId, externalUserId, false); + } catch (DisassociationNotAllowedException e) { + throw new IllegalStateException("should never happen"); + } + } + + public static boolean removeUserIdFromTenant(Main main, TenantIdentifierWithStorage tenantIdentifierWithStorage, + String userId, String externalUserId, boolean disallowLastTenantDisassociation) + throws FeatureNotEnabledException, TenantOrAppNotFoundException, StorageQueryException, + UnknownUserIdException, DisassociationNotAllowedException { if (Arrays.stream(FeatureFlag.getInstance(main, new AppIdentifier(null, null)).getEnabledFeatures()) .noneMatch(ee_features -> ee_features == EE_FEATURES.MULTI_TENANCY)) { throw new FeatureNotEnabledException(EE_FEATURES.MULTI_TENANCY); } + if (disallowLastTenantDisassociation) { + AuthRecipeUserInfo userInfo = AuthRecipe.getUserById(tenantIdentifierWithStorage.toAppIdentifierWithStorage(), userId); + if (userInfo != null) { + for (LoginMethod lM : userInfo.loginMethods) { + if (lM.getSupertokensUserId().equals(userId)) { + if (lM.tenantIds.size() == 1 && lM.tenantIds.contains(tenantIdentifierWithStorage.getTenantId())) { + throw new DisassociationNotAllowedException(); + } + } + } + } + } + boolean finalDidExist = false; boolean didExist = AuthRecipe.deleteNonAuthRecipeUser(tenantIdentifierWithStorage, externalUserId == null ? userId : externalUserId); diff --git a/src/main/java/io/supertokens/multitenancy/exception/AnotherPrimaryUserWithEmailAlreadyExistsException.java b/src/main/java/io/supertokens/multitenancy/exception/AnotherPrimaryUserWithEmailAlreadyExistsException.java new file mode 100644 index 000000000..c95bfdcc3 --- /dev/null +++ b/src/main/java/io/supertokens/multitenancy/exception/AnotherPrimaryUserWithEmailAlreadyExistsException.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2023, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.multitenancy.exception; + +public class AnotherPrimaryUserWithEmailAlreadyExistsException extends Exception { + public AnotherPrimaryUserWithEmailAlreadyExistsException(String primaryUserId) { + super("Another primary user with email already exists: " + primaryUserId); + } +} diff --git a/src/main/java/io/supertokens/multitenancy/exception/AnotherPrimaryUserWithPhoneNumberAlreadyExistsException.java b/src/main/java/io/supertokens/multitenancy/exception/AnotherPrimaryUserWithPhoneNumberAlreadyExistsException.java new file mode 100644 index 000000000..e012f9349 --- /dev/null +++ b/src/main/java/io/supertokens/multitenancy/exception/AnotherPrimaryUserWithPhoneNumberAlreadyExistsException.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2023, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.multitenancy.exception; + +public class AnotherPrimaryUserWithPhoneNumberAlreadyExistsException extends Exception { + public AnotherPrimaryUserWithPhoneNumberAlreadyExistsException(String primaryUserId) { + super("Another primary user with phone number already exists: " + primaryUserId); + } +} diff --git a/src/main/java/io/supertokens/multitenancy/exception/AnotherPrimaryUserWithThirdPartyInfoAlreadyExistsException.java b/src/main/java/io/supertokens/multitenancy/exception/AnotherPrimaryUserWithThirdPartyInfoAlreadyExistsException.java new file mode 100644 index 000000000..d5f413cf5 --- /dev/null +++ b/src/main/java/io/supertokens/multitenancy/exception/AnotherPrimaryUserWithThirdPartyInfoAlreadyExistsException.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2023, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.multitenancy.exception; + +public class AnotherPrimaryUserWithThirdPartyInfoAlreadyExistsException extends Exception { + public AnotherPrimaryUserWithThirdPartyInfoAlreadyExistsException(String primaryUserId) { + super("Another primary user with third party info already exists: " + primaryUserId); + } +} diff --git a/src/main/java/io/supertokens/multitenancy/exception/DisassociationNotAllowedException.java b/src/main/java/io/supertokens/multitenancy/exception/DisassociationNotAllowedException.java new file mode 100644 index 000000000..aad011c8b --- /dev/null +++ b/src/main/java/io/supertokens/multitenancy/exception/DisassociationNotAllowedException.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2023, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.multitenancy.exception; + +public class DisassociationNotAllowedException extends Exception { + public DisassociationNotAllowedException() { + super("Disassociation not allowed"); + } +} diff --git a/src/main/java/io/supertokens/passwordless/Passwordless.java b/src/main/java/io/supertokens/passwordless/Passwordless.java index d0b06dc0b..930e61493 100644 --- a/src/main/java/io/supertokens/passwordless/Passwordless.java +++ b/src/main/java/io/supertokens/passwordless/Passwordless.java @@ -35,7 +35,6 @@ import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.TenantConfig; -import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.passwordless.PasswordlessCode; @@ -670,7 +669,8 @@ public static AuthRecipeUserInfo getUserByEmail(TenantIdentifierWithStorage tena public static void updateUser(Main main, String userId, FieldUpdate emailUpdate, FieldUpdate phoneNumberUpdate) throws StorageQueryException, UnknownUserIdException, DuplicateEmailException, - DuplicatePhoneNumberException, UserWithoutContactInfoException, EmailChangeNotAllowedException { + DuplicatePhoneNumberException, UserWithoutContactInfoException, EmailChangeNotAllowedException, + PhoneNumberChangeNotAllowedException { Storage storage = StorageLayer.getStorage(main); updateUser(new AppIdentifierWithStorage(null, null, storage), userId, emailUpdate, phoneNumberUpdate); @@ -679,7 +679,8 @@ public static void updateUser(Main main, String userId, public static void updateUser(AppIdentifierWithStorage appIdentifierWithStorage, String recipeUserId, FieldUpdate emailUpdate, FieldUpdate phoneNumberUpdate) throws StorageQueryException, UnknownUserIdException, DuplicateEmailException, - DuplicatePhoneNumberException, UserWithoutContactInfoException, EmailChangeNotAllowedException { + DuplicatePhoneNumberException, UserWithoutContactInfoException, EmailChangeNotAllowedException, + PhoneNumberChangeNotAllowedException { PasswordlessSQLStorage storage = appIdentifierWithStorage.getPasswordlessStorage(); // We do not lock the user here, because we decided that even if the device cleanup used outdated information @@ -742,6 +743,24 @@ public static void updateUser(AppIdentifierWithStorage appIdentifierWithStorage, } } if (phoneNumberUpdate != null && !Objects.equals(phoneNumberUpdate.newValue, lM.phoneNumber)) { + if (user.isPrimaryUser) { + for (String tenantId : user.tenantIds) { + AuthRecipeUserInfo[] existingUsersWithNewPhoneNumber = + authRecipeSQLStorage.listPrimaryUsersByPhoneNumber_Transaction( + appIdentifierWithStorage, con, + phoneNumberUpdate.newValue); + + for (AuthRecipeUserInfo userWithSamePhoneNumber : existingUsersWithNewPhoneNumber) { + if (!userWithSamePhoneNumber.tenantIds.contains(tenantId)) { + continue; + } + if (userWithSamePhoneNumber.isPrimaryUser && !userWithSamePhoneNumber.getSupertokensUserId().equals(user.getSupertokensUserId())) { + throw new StorageTransactionLogicException( + new PhoneNumberChangeNotAllowedException()); + } + } + } + } try { storage.updateUserPhoneNumber_Transaction(appIdentifierWithStorage, con, recipeUserId, phoneNumberUpdate.newValue); @@ -776,6 +795,10 @@ public static void updateUser(AppIdentifierWithStorage appIdentifierWithStorage, if (e.actualException instanceof EmailChangeNotAllowedException) { throw (EmailChangeNotAllowedException) e.actualException; } + + if (e.actualException instanceof PhoneNumberChangeNotAllowedException) { + throw (PhoneNumberChangeNotAllowedException) e.actualException; + } } } diff --git a/src/main/java/io/supertokens/passwordless/exceptions/PhoneNumberChangeNotAllowedException.java b/src/main/java/io/supertokens/passwordless/exceptions/PhoneNumberChangeNotAllowedException.java new file mode 100644 index 000000000..d0117d1cc --- /dev/null +++ b/src/main/java/io/supertokens/passwordless/exceptions/PhoneNumberChangeNotAllowedException.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2023, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.passwordless.exceptions; + +public class PhoneNumberChangeNotAllowedException extends Exception { +} diff --git a/src/main/java/io/supertokens/session/Session.java b/src/main/java/io/supertokens/session/Session.java index c5ca0b979..63b2cb6d4 100644 --- a/src/main/java/io/supertokens/session/Session.java +++ b/src/main/java/io/supertokens/session/Session.java @@ -37,6 +37,7 @@ import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.session.noSqlStorage.SessionNoSQLStorage_1; import io.supertokens.pluginInterface.session.sqlStorage.SessionSQLStorage; +import io.supertokens.pluginInterface.sqlStorage.SQLStorage; import io.supertokens.session.accessToken.AccessToken; import io.supertokens.session.accessToken.AccessToken.AccessTokenInfo; import io.supertokens.session.info.SessionInfo; @@ -416,7 +417,7 @@ public static SessionInformationHolder getSession(AppIdentifier appIdentifier, M TenantOrAppNotFoundException e) { throw new StorageTransactionLogicException(e); } - }); + }, SQLStorage.TransactionIsolationLevel.REPEATABLE_READ); } catch (StorageTransactionLogicException e) { if (e.actualException instanceof UnauthorisedException) { throw (UnauthorisedException) e.actualException; diff --git a/src/main/java/io/supertokens/thirdparty/ThirdParty.java b/src/main/java/io/supertokens/thirdparty/ThirdParty.java index 366002d6e..3100daf4a 100644 --- a/src/main/java/io/supertokens/thirdparty/ThirdParty.java +++ b/src/main/java/io/supertokens/thirdparty/ThirdParty.java @@ -211,85 +211,120 @@ private static SignInUpResponse signInUpHelper(TenantIdentifierWithStorage tenan } // we try to get user and update their email - try { - AppIdentifier appIdentifier = tenantIdentifierWithStorage.toAppIdentifier(); - AuthRecipeSQLStorage authRecipeStorage = - (AuthRecipeSQLStorage) tenantIdentifierWithStorage.getAuthRecipeStorage(); - - storage.startTransaction(con -> { - AuthRecipeUserInfo userFromDb = null; - - AuthRecipeUserInfo[] usersFromDb = authRecipeStorage.listPrimaryUsersByThirdPartyInfo_Transaction( - appIdentifier, - con, - thirdPartyId, thirdPartyUserId); - for (AuthRecipeUserInfo user : usersFromDb) { - if (user.tenantIds.contains(tenantIdentifierWithStorage.getTenantId())) { - if (userFromDb != null) { - throw new IllegalStateException("Should never happen"); - } - userFromDb = user; + AppIdentifier appIdentifier = tenantIdentifierWithStorage.toAppIdentifier(); + AuthRecipeSQLStorage authRecipeStorage = + (AuthRecipeSQLStorage) tenantIdentifierWithStorage.getAuthRecipeStorage(); + + { // Try without transaction, because in most cases we might not need to update the email + AuthRecipeUserInfo userFromDb = null; + + AuthRecipeUserInfo[] usersFromDb = authRecipeStorage.listPrimaryUsersByThirdPartyInfo( + appIdentifier, + thirdPartyId, thirdPartyUserId); + for (AuthRecipeUserInfo user : usersFromDb) { + if (user.tenantIds.contains(tenantIdentifierWithStorage.getTenantId())) { + if (userFromDb != null) { + throw new IllegalStateException("Should never happen"); } + userFromDb = user; } + } + if (userFromDb == null) { + continue; // try to create the user again + } - if (userFromDb == null) { - storage.commitTransaction(con); - return null; - } - - LoginMethod lM = null; - for (LoginMethod loginMethod : userFromDb.loginMethods) { - if (loginMethod.thirdParty != null && loginMethod.thirdParty.id.equals(thirdPartyId) && - loginMethod.thirdParty.userId.equals(thirdPartyUserId)) { - lM = loginMethod; - break; - } + LoginMethod lM = null; + for (LoginMethod loginMethod : userFromDb.loginMethods) { + if (loginMethod.thirdParty != null && loginMethod.thirdParty.id.equals(thirdPartyId) && + loginMethod.thirdParty.userId.equals(thirdPartyUserId)) { + lM = loginMethod; + break; } + } - if (lM == null) { - throw new IllegalStateException("Should never come here"); - } + if (lM == null) { + throw new IllegalStateException("Should never come here"); + } + if (email.equals(lM.email)) { + return new SignInUpResponse(false, userFromDb); + } else { + // Email needs updating, so repeat everything in a transaction + try { - if (!email.equals(lM.email)) { - // before updating the email, we must check for if another primary user has the same - // email, and if they do, then we do not allow the update. - if (userFromDb.isPrimaryUser) { - for (String tenantId : userFromDb.tenantIds) { - AuthRecipeUserInfo[] userBasedOnEmail = - authRecipeStorage.listPrimaryUsersByEmail_Transaction( - appIdentifier, con, email - ); - for (AuthRecipeUserInfo userWithSameEmail : userBasedOnEmail) { - if (!userWithSameEmail.tenantIds.contains(tenantId)) { - continue; + storage.startTransaction(con -> { + AuthRecipeUserInfo userFromDb1 = null; + + AuthRecipeUserInfo[] usersFromDb1 = authRecipeStorage.listPrimaryUsersByThirdPartyInfo_Transaction( + appIdentifier, + con, + thirdPartyId, thirdPartyUserId); + for (AuthRecipeUserInfo user : usersFromDb1) { + if (user.tenantIds.contains(tenantIdentifierWithStorage.getTenantId())) { + if (userFromDb1 != null) { + throw new IllegalStateException("Should never happen"); } - if (userWithSameEmail.isPrimaryUser && - !userWithSameEmail.getSupertokensUserId().equals(userFromDb.getSupertokensUserId())) { - throw new StorageTransactionLogicException( - new EmailChangeNotAllowedException()); + userFromDb1 = user; + } + } + + if (userFromDb1 == null) { + storage.commitTransaction(con); + return null; + } + + LoginMethod lM1 = null; + for (LoginMethod loginMethod : userFromDb1.loginMethods) { + if (loginMethod.thirdParty != null && loginMethod.thirdParty.id.equals(thirdPartyId) && + loginMethod.thirdParty.userId.equals(thirdPartyUserId)) { + lM1 = loginMethod; + break; + } + } + + if (lM1 == null) { + throw new IllegalStateException("Should never come here"); + } + + if (!email.equals(lM1.email)) { + // before updating the email, we must check for if another primary user has the same + // email, and if they do, then we do not allow the update. + if (userFromDb1.isPrimaryUser) { + for (String tenantId : userFromDb1.tenantIds) { + AuthRecipeUserInfo[] userBasedOnEmail = + authRecipeStorage.listPrimaryUsersByEmail_Transaction( + appIdentifier, con, email + ); + for (AuthRecipeUserInfo userWithSameEmail : userBasedOnEmail) { + if (!userWithSameEmail.tenantIds.contains(tenantId)) { + continue; + } + if (userWithSameEmail.isPrimaryUser && + !userWithSameEmail.getSupertokensUserId().equals(userFromDb1.getSupertokensUserId())) { + throw new StorageTransactionLogicException( + new EmailChangeNotAllowedException()); + } + } } } + storage.updateUserEmail_Transaction(appIdentifier, con, + thirdPartyId, thirdPartyUserId, email); } + + storage.commitTransaction(con); + return null; + }); + } catch (StorageTransactionLogicException e) { + if (e.actualException instanceof EmailChangeNotAllowedException) { + throw (EmailChangeNotAllowedException) e.actualException; } - storage.updateUserEmail_Transaction(appIdentifier, con, - thirdPartyId, thirdPartyUserId, email); + throw new StorageQueryException(e); } - - storage.commitTransaction(con); - return null; - }); - - AuthRecipeUserInfo user = getUser(tenantIdentifierWithStorage, thirdPartyId, thirdPartyUserId); - return new SignInUpResponse(false, user); - } catch (StorageTransactionLogicException e) { - if (e.actualException instanceof EmailChangeNotAllowedException) { - throw (EmailChangeNotAllowedException) e.actualException; } - throw new StorageQueryException(e); } - // retry.. + AuthRecipeUserInfo user = getUser(tenantIdentifierWithStorage, thirdPartyId, thirdPartyUserId); + return new SignInUpResponse(false, user); } } diff --git a/src/main/java/io/supertokens/webserver/api/accountlinking/LinkAccountsAPI.java b/src/main/java/io/supertokens/webserver/api/accountlinking/LinkAccountsAPI.java index 10f7a7bd1..76a9d4dfe 100644 --- a/src/main/java/io/supertokens/webserver/api/accountlinking/LinkAccountsAPI.java +++ b/src/main/java/io/supertokens/webserver/api/accountlinking/LinkAccountsAPI.java @@ -100,8 +100,6 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I AuthRecipe.LinkAccountsResult linkAccountsResult = AuthRecipe.linkAccounts(main, primaryUserIdAppIdentifierWithStorage, recipeUserId, primaryUserId); - // Remove linked account user id from active user - ActiveUsers.removeActiveUser(recipeUserIdAppIdentifierWithStorage, recipeUserId); UserIdMapping.populateExternalUserIdForUsers(primaryUserIdAppIdentifierWithStorage, new AuthRecipeUserInfo[]{linkAccountsResult.user}); JsonObject response = new JsonObject(); diff --git a/src/main/java/io/supertokens/webserver/api/multitenancy/AssociateUserToTenantAPI.java b/src/main/java/io/supertokens/webserver/api/multitenancy/AssociateUserToTenantAPI.java index d53c2e7e8..3eb502dc5 100644 --- a/src/main/java/io/supertokens/webserver/api/multitenancy/AssociateUserToTenantAPI.java +++ b/src/main/java/io/supertokens/webserver/api/multitenancy/AssociateUserToTenantAPI.java @@ -21,6 +21,9 @@ import io.supertokens.Main; import io.supertokens.featureflag.exceptions.FeatureNotEnabledException; import io.supertokens.multitenancy.Multitenancy; +import io.supertokens.multitenancy.exception.AnotherPrimaryUserWithEmailAlreadyExistsException; +import io.supertokens.multitenancy.exception.AnotherPrimaryUserWithPhoneNumberAlreadyExistsException; +import io.supertokens.multitenancy.exception.AnotherPrimaryUserWithThirdPartyInfoAlreadyExistsException; import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; @@ -99,6 +102,13 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I JsonObject result = new JsonObject(); result.addProperty("status", "THIRD_PARTY_USER_ALREADY_EXISTS_ERROR"); super.sendJsonResponse(200, result, resp); + + } catch (AnotherPrimaryUserWithEmailAlreadyExistsException | AnotherPrimaryUserWithPhoneNumberAlreadyExistsException | + AnotherPrimaryUserWithThirdPartyInfoAlreadyExistsException e) { + JsonObject result = new JsonObject(); + result.addProperty("status", "ASSOCIATION_NOT_ALLOWED_ERROR"); + result.addProperty("reason", e.getMessage()); + super.sendJsonResponse(200, result, resp); } } } diff --git a/src/main/java/io/supertokens/webserver/api/multitenancy/DisassociateUserFromTenant.java b/src/main/java/io/supertokens/webserver/api/multitenancy/DisassociateUserFromTenant.java index e97f94f64..023f91342 100644 --- a/src/main/java/io/supertokens/webserver/api/multitenancy/DisassociateUserFromTenant.java +++ b/src/main/java/io/supertokens/webserver/api/multitenancy/DisassociateUserFromTenant.java @@ -21,11 +21,13 @@ import io.supertokens.Main; import io.supertokens.featureflag.exceptions.FeatureNotEnabledException; import io.supertokens.multitenancy.Multitenancy; +import io.supertokens.multitenancy.exception.DisassociationNotAllowedException; import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.useridmapping.UserIdType; +import io.supertokens.utils.SemVer; import io.supertokens.webserver.InputParser; import io.supertokens.webserver.WebserverAPI; import jakarta.servlet.ServletException; @@ -68,7 +70,8 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I } boolean wasAssociated = Multitenancy.removeUserIdFromTenant(main, - getTenantIdentifierWithStorageFromRequest(req), userId, externalUserId); + getTenantIdentifierWithStorageFromRequest(req), userId, externalUserId, getVersionFromRequest(req).greaterThanOrEqualTo( + SemVer.v4_0)); JsonObject result = new JsonObject(); result.addProperty("status", "OK"); @@ -79,7 +82,11 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I JsonObject result = new JsonObject(); result.addProperty("status", "UNKNOWN_USER_ID_ERROR"); super.sendJsonResponse(200, result, resp); - + } catch (DisassociationNotAllowedException e) { + JsonObject result = new JsonObject(); + result.addProperty("status", "DISASSOCIATION_NOT_ALLOWED_ERROR"); + result.addProperty("reason", "The user belongs to only one tenant and cannot be disassociated from that"); + super.sendJsonResponse(200, result, resp); } catch (StorageQueryException | TenantOrAppNotFoundException | FeatureNotEnabledException e) { throw new ServletException(e); } diff --git a/src/main/java/io/supertokens/webserver/api/passwordless/UserAPI.java b/src/main/java/io/supertokens/webserver/api/passwordless/UserAPI.java index 76d352bb2..253a3f81a 100644 --- a/src/main/java/io/supertokens/webserver/api/passwordless/UserAPI.java +++ b/src/main/java/io/supertokens/webserver/api/passwordless/UserAPI.java @@ -22,6 +22,7 @@ import io.supertokens.emailpassword.exceptions.EmailChangeNotAllowedException; import io.supertokens.passwordless.Passwordless; import io.supertokens.passwordless.Passwordless.FieldUpdate; +import io.supertokens.passwordless.exceptions.PhoneNumberChangeNotAllowedException; import io.supertokens.passwordless.exceptions.UserWithoutContactInfoException; import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; @@ -30,7 +31,6 @@ import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.passwordless.exception.DuplicatePhoneNumberException; -import io.supertokens.pluginInterface.useridmapping.UserIdMapping; import io.supertokens.useridmapping.UserIdType; import io.supertokens.utils.SemVer; import io.supertokens.utils.Utils; @@ -187,6 +187,11 @@ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IO result.addProperty("status", "EMAIL_CHANGE_NOT_ALLOWED_ERROR"); result.addProperty("reason", "New email is associated with another primary user ID"); super.sendJsonResponse(200, result, resp); + } catch (PhoneNumberChangeNotAllowedException e) { + JsonObject result = new JsonObject(); + result.addProperty("status", "PHONE_NUMBER_CHANGE_NOT_ALLOWED_ERROR"); + result.addProperty("reason", "New phone number is associated with another primary user ID"); + super.sendJsonResponse(200, result, resp); } } } diff --git a/src/test/java/io/supertokens/test/AuthRecipeTest.java b/src/test/java/io/supertokens/test/AuthRecipeTest.java index def36c276..d941f81cc 100644 --- a/src/test/java/io/supertokens/test/AuthRecipeTest.java +++ b/src/test/java/io/supertokens/test/AuthRecipeTest.java @@ -439,11 +439,11 @@ public void randomPaginationTest() throws Exception { return; } - Map> signUpMap = getSignUpMap(process); + Map> signUpMap = getSignUpMap(process); - List classes = getUserInfoClassNameList(); + List authRecipes = getAuthRecipes(); - for (String className : classes) { + for (String className : authRecipes) { if (!signUpMap.containsKey(className)) { fail(); } @@ -456,7 +456,7 @@ public void randomPaginationTest() throws Exception { for (int i = 0; i < numberOfUsers; i++) { if (Math.random() > 0.5) { while (true) { - String currUserType = classes.get((int) (Math.random() * classes.size())); + String currUserType = authRecipes.get((int) (Math.random() * authRecipes.size())); AuthRecipeUserInfo user = signUpMap.get(currUserType).apply(null); if (user != null) { synchronized (usersCreated) { @@ -468,7 +468,7 @@ public void randomPaginationTest() throws Exception { } else { es.execute(() -> { while (true) { - String currUserType = classes.get((int) (Math.random() * classes.size())); + String currUserType = authRecipes.get((int) (Math.random() * authRecipes.size())); AuthRecipeUserInfo user = signUpMap.get(currUserType).apply(null); if (user != null) { synchronized (usersCreated) { @@ -584,17 +584,17 @@ public void deleteUserTest() throws Exception { return; } - Map> signUpMap = getSignUpMap(process); + Map> signUpMap = getSignUpMap(process); - List classes = getUserInfoClassNameList(); + List authRecipes = getAuthRecipes(); - for (String className : classes) { + for (String className : authRecipes) { if (!signUpMap.containsKey(className)) { fail(); } } - for (String userType : classes) { + for (String userType : authRecipes) { AuthRecipeUserInfo user1 = signUpMap.get(userType).apply(null); JsonObject testMetadata = new JsonObject(); testMetadata.addProperty("test", "test"); @@ -635,19 +635,16 @@ public void deleteUserTest() throws Exception { assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); } - private static List getUserInfoClassNameList() { - Reflections reflections = new Reflections("io.supertokens"); - Set> classes = reflections.getSubTypesOf(AuthRecipeUserInfo.class); - - return classes.stream().map(Class::getCanonicalName).collect(Collectors.toList()); + private static List getAuthRecipes() { + return Arrays.asList("emailpassword", "thirdparty", "passwordless"); } - private static Map> getSignUpMap( + private static Map> getSignUpMap( TestingProcessManager.TestingProcess process) { AtomicInteger count = new AtomicInteger(); - Map> signUpMap = new HashMap<>(); - signUpMap.put("io.supertokens.pluginInterface.emailpassword.AuthRecipeUserInfo", o -> { + Map> signUpMap = new HashMap<>(); + signUpMap.put("emailpassword", o -> { try { return EmailPassword.signUp(process.getProcess(), "test" + count.getAndIncrement() + "@example.com", "password0"); @@ -655,7 +652,7 @@ private static List getUserInfoClassNameList() { } return null; }); - signUpMap.put("io.supertokens.pluginInterface.thirdparty.AuthRecipeUserInfo", o -> { + signUpMap.put("thirdparty", o -> { try { String thirdPartyId = "testThirdParty"; String thirdPartyUserId = "thirdPartyUserId" + count.getAndIncrement(); @@ -666,7 +663,7 @@ private static List getUserInfoClassNameList() { } return null; }); - signUpMap.put("io.supertokens.pluginInterface.passwordless.AuthRecipeUserInfo", o -> { + signUpMap.put("passwordless", o -> { try { String email = "test" + count.getAndIncrement() + "@example.com"; CreateCodeResponse createCode = Passwordless.createCode(process.getProcess(), email, null, null, null); diff --git a/src/test/java/io/supertokens/test/AuthRecipesParallelTest.java b/src/test/java/io/supertokens/test/AuthRecipesParallelTest.java new file mode 100644 index 000000000..fb6cc9af6 --- /dev/null +++ b/src/test/java/io/supertokens/test/AuthRecipesParallelTest.java @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2023, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.test; + +import io.supertokens.ProcessState; +import io.supertokens.emailpassword.EmailPassword; +import io.supertokens.emailpassword.exceptions.EmailChangeNotAllowedException; +import io.supertokens.emailpassword.exceptions.WrongCredentialsException; +import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.test.TestingProcessManager; +import io.supertokens.test.Utils; +import io.supertokens.thirdparty.ThirdParty; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +public class AuthRecipesParallelTest { + @Rule + public TestRule watchman = Utils.getOnFailure(); + + @AfterClass + public static void afterTesting() { + Utils.afterTesting(); + } + + @Before + public void beforeEach() { + Utils.reset(); + } + + @Test + public void timeTakenFor500SignInParallel() throws Exception { + String[] args = {"../"}; + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + ExecutorService ex = Executors.newFixedThreadPool(1000); + int numberOfThreads = 500; + + EmailPassword.signUp(process.getProcess(), "test@example.com", "password"); + AtomicInteger counter = new AtomicInteger(0); + AtomicInteger retryCounter = new AtomicInteger(0); + + long st = System.currentTimeMillis(); + for (int i = 0; i < numberOfThreads; i++) { + ex.execute(() -> { + while(true) { + try { + EmailPassword.signIn(process.getProcess(), "test@example.com", "password"); + counter.incrementAndGet(); + break; + } catch (StorageQueryException e) { + retryCounter.incrementAndGet(); + // continue + } catch (WrongCredentialsException e) { + throw new RuntimeException(e); + } + } + }); + } + + ex.shutdown(); + + ex.awaitTermination(2, TimeUnit.MINUTES); + System.out.println("Time taken for " + numberOfThreads + " sign in parallel: " + (System.currentTimeMillis() - st) + "ms"); + System.out.println("Retry counter: " + retryCounter.get()); + assertEquals(counter.get(), numberOfThreads); + assertEquals(0, retryCounter.get()); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void timeTakenFor500SignInUpParallel() throws Exception { + String[] args = {"../"}; + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + ExecutorService ex = Executors.newFixedThreadPool(1000); + int numberOfThreads = 500; + + ThirdParty.signInUp(process.getProcess(), "google", "google-user", "test@example.com"); + AtomicInteger counter = new AtomicInteger(0); + AtomicInteger retryCounter = new AtomicInteger(0); + + ThirdParty.signInUp(process.getProcess(), "google", "google-user", "test@example.com"); + + long st = System.currentTimeMillis(); + for (int i = 0; i < numberOfThreads; i++) { + ex.execute(() -> { + while(true) { + try { + ThirdParty.signInUp(process.getProcess(), "google", "google-user", "test@example.com"); + counter.incrementAndGet(); + break; + } catch (StorageQueryException e) { + retryCounter.incrementAndGet(); + // continue + } catch (EmailChangeNotAllowedException e) { + throw new RuntimeException(e); + } + } + }); + } + + ex.shutdown(); + + ex.awaitTermination(2, TimeUnit.MINUTES); + System.out.println("Time taken for " + numberOfThreads + " sign in parallel: " + (System.currentTimeMillis() - st) + "ms"); + System.out.println("Retry counter: " + retryCounter.get()); + assertEquals (counter.get(), numberOfThreads); + assertEquals(0, retryCounter.get()); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } +} diff --git a/src/test/java/io/supertokens/test/accountlinking/MultitenantTest.java b/src/test/java/io/supertokens/test/accountlinking/MultitenantTest.java index f9cdc8e12..17ff75764 100644 --- a/src/test/java/io/supertokens/test/accountlinking/MultitenantTest.java +++ b/src/test/java/io/supertokens/test/accountlinking/MultitenantTest.java @@ -22,20 +22,22 @@ import io.supertokens.authRecipe.AuthRecipe; import io.supertokens.authRecipe.exception.AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException; import io.supertokens.emailpassword.EmailPassword; -import io.supertokens.emailpassword.exceptions.WrongCredentialsException; +import io.supertokens.emailpassword.exceptions.EmailChangeNotAllowedException; import io.supertokens.featureflag.EE_FEATURES; import io.supertokens.featureflag.FeatureFlagTestContent; import io.supertokens.featureflag.exceptions.FeatureNotEnabledException; import io.supertokens.multitenancy.Multitenancy; -import io.supertokens.multitenancy.exception.BadPermissionException; -import io.supertokens.multitenancy.exception.CannotModifyBaseConfigException; +import io.supertokens.multitenancy.exception.*; import io.supertokens.passwordless.Passwordless; +import io.supertokens.passwordless.exceptions.PhoneNumberChangeNotAllowedException; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; import io.supertokens.pluginInterface.exceptions.InvalidConfigException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.*; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; +import io.supertokens.pluginInterface.passwordless.exception.DuplicatePhoneNumberException; +import io.supertokens.pluginInterface.thirdparty.exception.DuplicateThirdPartyUserException; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.TestingProcessManager; import io.supertokens.test.Utils; @@ -48,10 +50,10 @@ import org.junit.rules.TestRule; import java.io.IOException; -import java.util.function.Function; +import java.util.ArrayList; +import java.util.List; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.fail; +import static org.junit.Assert.*; public class MultitenantTest { @Rule @@ -67,7 +69,7 @@ public void beforeEach() { Utils.reset(); } - TenantIdentifier t1, t2, t3; + TenantIdentifier t1, t2, t3, t4; private void createTenants(Main main) throws StorageQueryException, TenantOrAppNotFoundException, InvalidProviderConfigException, @@ -136,292 +138,564 @@ private void createTenants(Main main) ); } + { // tenant 4 + JsonObject config = new JsonObject(); + TenantIdentifier tenantIdentifier = new TenantIdentifier(null, "a1", "t3"); + + StorageLayer.getStorage(new TenantIdentifier(null, null, null), main) + .modifyConfigToAddANewUserPoolForTesting(config, 1); + + Multitenancy.addNewOrUpdateAppOrTenant( + main, + new TenantIdentifier(null, "a1", null), + new TenantConfig( + tenantIdentifier, + new EmailPasswordConfig(true), + new ThirdPartyConfig(true, null), + new PasswordlessConfig(true), + config + ) + ); + } + } + + @Test + public void testVariousCases() throws Exception { t1 = new TenantIdentifier(null, "a1", null); t2 = new TenantIdentifier(null, "a1", "t1"); t3 = new TenantIdentifier(null, "a1", "t2"); + t4 = new TenantIdentifier(null, "a1", "t3"); + + TestCase[] testCases = new TestCase[]{ + new TestCase(new TestCaseStep[]{ + new CreateEmailPasswordUser(t1, "test1@example.com"), + new CreateEmailPasswordUser(t2, "test2@example.com"), + new MakePrimaryUser(t1, 0), + new LinkAccounts(t1, 0, 1), + new CreateEmailPasswordUser(t3, "test1@example.com"), + new AssociateUserToTenant(t1, 2).expect(new DuplicateEmailException()), + new AssociateUserToTenant(t2, 2), // Allowed + }), + + new TestCase(new TestCaseStep[]{ + new CreateEmailPasswordUser(t1, "test1@example.com"), + new CreateEmailPasswordUser(t2, "test2@example.com"), + new MakePrimaryUser(t1, 0), + new LinkAccounts(t1, 0, 1), + new CreateEmailPasswordUser(t3, "test1@example.com"), + new MakePrimaryUser(t3, 2), + new AssociateUserToTenant(t2, 2).expect(new AnotherPrimaryUserWithEmailAlreadyExistsException("")), + }), + + new TestCase(new TestCaseStep[]{ + new CreateEmailPasswordUser(t1, "test1@example.com"), + new CreateEmailPasswordUser(t2, "test2@example.com"), + new MakePrimaryUser(t1, 0), + new LinkAccounts(t1, 0, 1), + new CreateEmailPasswordUser(t3, "test1@example.com"), + new AssociateUserToTenant(t2, 2), + new MakePrimaryUser(t3, 2).expect(new AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException("", "")), + }), + + new TestCase(new TestCaseStep[]{ + new CreateEmailPasswordUser(t1, "test1@example.com"), + new CreatePlessUserWithEmail(t2, "test2@example.com"), + new MakePrimaryUser(t1, 0), + new LinkAccounts(t1, 0, 1), + new CreateEmailPasswordUser(t3, "test1@example.com"), + new AssociateUserToTenant(t1, 2).expect(new DuplicateEmailException()), + new AssociateUserToTenant(t2, 2), // Allowed + }), + + new TestCase(new TestCaseStep[]{ + new CreateEmailPasswordUser(t1, "test1@example.com"), + new CreatePlessUserWithEmail(t2, "test2@example.com"), + new MakePrimaryUser(t1, 0), + new LinkAccounts(t1, 0, 1), + new CreateEmailPasswordUser(t3, "test1@example.com"), + new MakePrimaryUser(t3, 2), + new AssociateUserToTenant(t2, 2).expect(new AnotherPrimaryUserWithEmailAlreadyExistsException("")), + }), + + new TestCase(new TestCaseStep[]{ + new CreateEmailPasswordUser(t1, "test1@example.com"), + new CreatePlessUserWithEmail(t2, "test2@example.com"), + new MakePrimaryUser(t1, 0), + new LinkAccounts(t1, 0, 1), + new CreateEmailPasswordUser(t3, "test1@example.com"), + new AssociateUserToTenant(t2, 2), + new MakePrimaryUser(t3, 2).expect(new AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException("", "")), + }), + + new TestCase(new TestCaseStep[]{ + new CreatePlessUserWithEmail(t1, "test1@example.com"), + new CreateEmailPasswordUser(t2, "test2@example.com"), + new MakePrimaryUser(t1, 0), + new LinkAccounts(t1, 0, 1), + new CreateEmailPasswordUser(t3, "test1@example.com"), + new AssociateUserToTenant(t1, 2), + new AssociateUserToTenant(t2, 2), // Allowed + }), + + new TestCase(new TestCaseStep[]{ + new CreatePlessUserWithEmail(t1, "test1@example.com"), + new CreateEmailPasswordUser(t2, "test2@example.com"), + new MakePrimaryUser(t1, 0), + new LinkAccounts(t1, 0, 1), + new CreateEmailPasswordUser(t3, "test1@example.com"), + new MakePrimaryUser(t3, 2), + new AssociateUserToTenant(t2, 2).expect(new AnotherPrimaryUserWithEmailAlreadyExistsException("")), + }), + + new TestCase(new TestCaseStep[]{ + new CreatePlessUserWithEmail(t1, "test1@example.com"), + new CreateEmailPasswordUser(t2, "test2@example.com"), + new MakePrimaryUser(t1, 0), + new LinkAccounts(t1, 0, 1), + new CreateEmailPasswordUser(t3, "test1@example.com"), + new AssociateUserToTenant(t2, 2), + new MakePrimaryUser(t3, 2).expect(new AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException("", "")), + }), + + new TestCase(new TestCaseStep[]{ + new CreatePlessUserWithEmail(t1, "test1@example.com"), + new CreatePlessUserWithEmail(t2, "test2@example.com"), + new CreateEmailPasswordUser(t2, "test2@example.com"), + new MakePrimaryUser(t1, 0), + new LinkAccounts(t1, 0, 2), + new UpdatePlessUserEmail(t1, 0, "test2@example.com"), + }), + + new TestCase(new TestCaseStep[]{ + new CreatePlessUserWithEmail(t1, "test1@example.com"), + new CreatePlessUserWithEmail(t1, "test3@example.com"), + new CreateEmailPasswordUser(t2, "test2@example.com"), + new MakePrimaryUser(t1, 0), + new LinkAccounts(t1, 0, 2), + new MakePrimaryUser(t2, 1), + new UpdatePlessUserEmail(t1, 0, "test3@example.com").expect(new EmailChangeNotAllowedException()), + }), + + new TestCase(new TestCaseStep[]{ + new CreatePlessUserWithEmail(t1, "test1@example.com"), + new CreatePlessUserWithEmail(t1, "test3@example.com"), + new CreateEmailPasswordUser(t2, "test2@example.com"), + new MakePrimaryUser(t1, 0), + new LinkAccounts(t1, 0, 2), + new MakePrimaryUser(t2, 1), + new UpdatePlessUserEmail(t1, 1, "test1@example.com").expect(new EmailChangeNotAllowedException()), + }), + + new TestCase(new TestCaseStep[]{ + new CreatePlessUserWithPhone(t1, "+1000001"), + new CreatePlessUserWithPhone(t1, "+1000003"), + new CreatePlessUserWithPhone(t2, "+1000002"), + new MakePrimaryUser(t1, 0), + new LinkAccounts(t1, 0, 2), + new MakePrimaryUser(t2, 1), + new UpdatePlessUserPhone(t1, 0, "+1000003").expect(new PhoneNumberChangeNotAllowedException()), + }), + + new TestCase(new TestCaseStep[]{ + new CreatePlessUserWithPhone(t1, "+1000001"), + new CreatePlessUserWithPhone(t1, "+1000003"), + new CreatePlessUserWithPhone(t2, "+1000002"), + new MakePrimaryUser(t1, 0), + new LinkAccounts(t1, 0, 2), + new MakePrimaryUser(t2, 1), + new UpdatePlessUserPhone(t1, 1, "+1000001").expect(new PhoneNumberChangeNotAllowedException()), + }), + + new TestCase(new TestCaseStep[]{ + new CreateEmailPasswordUser(t1, "test1@example.com"), + new CreateEmailPasswordUser(t1, "test3@example.com"), + new CreateEmailPasswordUser(t2, "test2@example.com"), + new MakePrimaryUser(t1, 0), + new LinkAccounts(t1, 0, 2), + new MakePrimaryUser(t2, 1), + new UpdateEmailPasswordUserEmail(t1, 0, "test3@example.com").expect(new EmailChangeNotAllowedException()), + }), + + new TestCase(new TestCaseStep[]{ + new CreateEmailPasswordUser(t1, "test1@example.com"), + new CreateEmailPasswordUser(t1, "test3@example.com"), + new CreateEmailPasswordUser(t2, "test2@example.com"), + new MakePrimaryUser(t1, 0), + new LinkAccounts(t1, 0, 2), + new MakePrimaryUser(t2, 1), + new UpdateEmailPasswordUserEmail(t1, 1, "test1@example.com").expect(new EmailChangeNotAllowedException()), + }), + + new TestCase(new TestCaseStep[]{ + new CreateEmailPasswordUser(t1, "test1@example.com"), + new CreateThirdPartyUser(t2, "google", "googleid", "test2@example.com"), + new MakePrimaryUser(t1, 0), + new LinkAccounts(t1, 0, 1), + new CreateEmailPasswordUser(t3, "test1@example.com"), + new AssociateUserToTenant(t1, 2).expect(new DuplicateEmailException()), + new AssociateUserToTenant(t2, 2), // Allowed + }), + + new TestCase(new TestCaseStep[]{ + new CreateEmailPasswordUser(t1, "test1@example.com"), + new CreateThirdPartyUser(t2, "google", "googleid", "test2@example.com"), + new MakePrimaryUser(t1, 0), + new LinkAccounts(t1, 0, 1), + new CreateEmailPasswordUser(t3, "test1@example.com"), + new MakePrimaryUser(t3, 2), + new AssociateUserToTenant(t2, 2).expect(new AnotherPrimaryUserWithEmailAlreadyExistsException("")), + }), + + new TestCase(new TestCaseStep[]{ + new CreateEmailPasswordUser(t1, "test1@example.com"), + new CreateThirdPartyUser(t2, "google", "googleid", "test2@example.com"), + new MakePrimaryUser(t1, 0), + new LinkAccounts(t1, 0, 1), + new CreateEmailPasswordUser(t3, "test1@example.com"), + new AssociateUserToTenant(t2, 2), + new MakePrimaryUser(t3, 2).expect(new AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException("", "")), + }), + + new TestCase(new TestCaseStep[]{ + new CreateThirdPartyUser(t1, "google", "googleid", "test1@example.com"), + new CreateEmailPasswordUser(t2, "test2@example.com"), + new MakePrimaryUser(t1, 0), + new LinkAccounts(t1, 0, 1), + new CreateEmailPasswordUser(t3, "test1@example.com"), + new AssociateUserToTenant(t1, 2), + new AssociateUserToTenant(t2, 2), // Allowed + }), + + new TestCase(new TestCaseStep[]{ + new CreateThirdPartyUser(t1, "google", "googleid", "test1@example.com"), + new CreateEmailPasswordUser(t2, "test2@example.com"), + new MakePrimaryUser(t1, 0), + new LinkAccounts(t1, 0, 1), + new CreateEmailPasswordUser(t3, "test1@example.com"), + new MakePrimaryUser(t3, 2), + new AssociateUserToTenant(t2, 2).expect(new AnotherPrimaryUserWithEmailAlreadyExistsException("")), + }), + + new TestCase(new TestCaseStep[]{ + new CreateThirdPartyUser(t1, "google", "googleid", "test1@example.com"), + new CreateEmailPasswordUser(t2, "test2@example.com"), + new MakePrimaryUser(t1, 0), + new LinkAccounts(t1, 0, 1), + new CreateEmailPasswordUser(t3, "test1@example.com"), + new AssociateUserToTenant(t2, 2), + new MakePrimaryUser(t3, 2).expect(new AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException("", "")), + }), + + new TestCase(new TestCaseStep[]{ + new CreateThirdPartyUser(t1, "google", "googleid1", "test1@example.com"), + new CreateThirdPartyUser(t2, "google", "googleid2", "test2@example.com"), + new CreateEmailPasswordUser(t2, "test2@example.com"), + new MakePrimaryUser(t1, 0), + new LinkAccounts(t1, 0, 2), + new CreateThirdPartyUser(t1, "google", "googleid1", "test2@example.com"), + }), + + new TestCase(new TestCaseStep[]{ + new CreateThirdPartyUser(t1, "google", "googleid1", "test1@example.com"), + new CreateThirdPartyUser(t1, "google", "googleid3", "test3@example.com"), + new CreateEmailPasswordUser(t2, "test2@example.com"), + new MakePrimaryUser(t1, 0), + new LinkAccounts(t1, 0, 2), + new MakePrimaryUser(t2, 1), + new CreateThirdPartyUser(t1, "google", "googleid1", "test3@example.com").expect(new EmailChangeNotAllowedException()), + }), + + new TestCase(new TestCaseStep[]{ + new CreateThirdPartyUser(t1, "google", "googleid1", "test1@example.com"), + new CreateThirdPartyUser(t1, "google", "googleid3", "test3@example.com"), + new CreateEmailPasswordUser(t2, "test2@example.com"), + new MakePrimaryUser(t1, 0), + new LinkAccounts(t1, 0, 2), + new MakePrimaryUser(t2, 1), + new CreateThirdPartyUser(t1, "google", "googleid3", "test1@example.com").expect(new EmailChangeNotAllowedException()), + }), + + new TestCase(new TestCaseStep[]{ + new CreatePlessUserWithPhone(t1, "+1000001"), + new CreatePlessUserWithPhone(t2, "+1000002"), + new CreatePlessUserWithPhone(t3, "+1000001"), + new MakePrimaryUser(t1, 0), + new LinkAccounts(t1, 0, 1), + new MakePrimaryUser(t3, 2), + new AssociateUserToTenant(t1, 2).expect(new DuplicatePhoneNumberException()), + new AssociateUserToTenant(t2, 2).expect(new AnotherPrimaryUserWithPhoneNumberAlreadyExistsException("")), + }), + + new TestCase(new TestCaseStep[]{ + new CreateThirdPartyUser(t1, "google", "googleid1", "test1@example.com"), + new CreateThirdPartyUser(t2, "google", "googleid2", "test2@example.com"), + new CreateThirdPartyUser(t3, "google", "googleid1", "test3@example.com"), + new MakePrimaryUser(t1, 0), + new LinkAccounts(t1, 0, 1), + new MakePrimaryUser(t3, 2), + new AssociateUserToTenant(t1, 2).expect(new DuplicateThirdPartyUserException()), + new AssociateUserToTenant(t2, 2).expect(new AnotherPrimaryUserWithThirdPartyInfoAlreadyExistsException("")), + }), + new TestCase(new TestCaseStep[]{ + new CreateThirdPartyUser(t1, "google", "googleid1", "test1@example.com"), + new CreateThirdPartyUser(t2, "google", "googleid2", "test2@example.com"), + new CreateThirdPartyUser(t1, "google", "googleid3", "test3@example.com"), + new MakePrimaryUser(t1, 0), + new LinkAccounts(t1, 0, 1), + new MakePrimaryUser(t1, 2), + new CreateThirdPartyUser(t1, "google", "googleid1", "test3@example.com").expect(new EmailChangeNotAllowedException()), + new CreateThirdPartyUser(t1, "google", "googleid3", "test1@example.com").expect(new EmailChangeNotAllowedException()), + }), + }; + + int i = 0; + for (TestCase testCase : testCases) { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + createTenants(process.getProcess()); + + System.out.println("Executing test case : " + i); + testCase.doTest(process.getProcess()); + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + i++; + } } - @Test - public void testWithEmailPasswordUsers1() throws Exception { - String[] args = {"../"}; - TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); - FeatureFlagTestContent.getInstance(process.getProcess()) - .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ - EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); - process.startProcess(); - assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + private static class TestCase { + TestCaseStep[] steps; + public static List users; - createTenants(process.getProcess()); + public static void resetUsers() { + users = new ArrayList<>(); + } - TenantIdentifierWithStorage t1WithStorage = t1.withStorage(StorageLayer.getStorage(t1, process.getProcess())); - TenantIdentifierWithStorage t2WithStorage = t2.withStorage(StorageLayer.getStorage(t2, process.getProcess())); + public static void addUser(AuthRecipeUserInfo user) { + users.add(user); + } - AuthRecipeUserInfo user1 = EmailPassword.signUp(t1WithStorage, process.getProcess(), "test1@example.com", "password"); - AuthRecipeUserInfo user2 = EmailPassword.signUp(t2WithStorage, process.getProcess(), "test2@example.com", "password"); - AuthRecipe.createPrimaryUser(process.getProcess(), t1WithStorage.toAppIdentifierWithStorage(), user1.getSupertokensUserId()); - AuthRecipe.linkAccounts(process.getProcess(), t1WithStorage.toAppIdentifierWithStorage(), user2.getSupertokensUserId(), user1.getSupertokensUserId()); + public TestCase(TestCaseStep[] steps) { + this.steps = steps; + } + + public void doTest(Main main) throws Exception { + TestCase.resetUsers(); - try { - // Credentials does not exist in t2 - EmailPassword.signIn(t2WithStorage, process.getProcess(), "test1@example.com", "password"); - fail(); - } catch (WrongCredentialsException e) { - // ignore + for (TestCaseStep step : steps) { + step.doStep(main); + } } + } + + private static class TestCaseStep { + Exception e; - Multitenancy.addUserIdToTenant(process.getProcess(), t2WithStorage, user1.getSupertokensUserId()); + public TestCaseStep expect(Exception e) { + this.e = e; + return this; + } - // Sign in should now pass - EmailPassword.signIn(t2WithStorage, process.getProcess(), "test1@example.com", "password"); + public void doStep(Main main) throws Exception { + if (e == null) { + this.execute(main); + } else { + try { + this.execute(main); + fail(); + } catch (Exception e) { + assertEquals(this.e.getClass(), e.getClass()); + } + } + } - process.kill(); - assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + public void execute(Main main) throws Exception { + } } - @Test - public void testWithEmailPasswordUsers2() throws Exception { - String[] args = {"../"}; - TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); - FeatureFlagTestContent.getInstance(process.getProcess()) - .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ - EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); - process.startProcess(); - assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + private static class CreateEmailPasswordUser extends TestCaseStep { + private final TenantIdentifier tenantIdentifier; + private final String email; + + public CreateEmailPasswordUser(TenantIdentifier tenantIdentifier, String email) { + this.tenantIdentifier = tenantIdentifier; + this.email = email; + } + + @Override + public void execute(Main main) throws Exception { + TenantIdentifierWithStorage tenantIdentifierWithStorage = tenantIdentifier.withStorage(StorageLayer.getStorage(tenantIdentifier, main)); + AuthRecipeUserInfo user = EmailPassword.signUp(tenantIdentifierWithStorage, main, email, "password"); + TestCase.addUser(user); + } + } - createTenants(process.getProcess()); + private static class CreatePlessUserWithEmail extends TestCaseStep { + private final TenantIdentifier tenantIdentifier; + private final String email; - TenantIdentifierWithStorage t1WithStorage = t1.withStorage(StorageLayer.getStorage(t1, process.getProcess())); - TenantIdentifierWithStorage t2WithStorage = t2.withStorage(StorageLayer.getStorage(t2, process.getProcess())); + public CreatePlessUserWithEmail(TenantIdentifier tenantIdentifier, String email) { + this.tenantIdentifier = tenantIdentifier; + this.email = email; + } - AuthRecipeUserInfo user1 = EmailPassword.signUp(t1WithStorage, process.getProcess(), "test1@example.com", "password"); - AuthRecipeUserInfo user2 = EmailPassword.signUp(t2WithStorage, process.getProcess(), "test2@example.com", "password"); - AuthRecipe.createPrimaryUser(process.getProcess(), t1WithStorage.toAppIdentifierWithStorage(), user1.getSupertokensUserId()); - AuthRecipe.linkAccounts(process.getProcess(), t1WithStorage.toAppIdentifierWithStorage(), user2.getSupertokensUserId(), user1.getSupertokensUserId()); + @Override + public void execute(Main main) throws Exception { + TenantIdentifierWithStorage tenantIdentifierWithStorage = tenantIdentifier.withStorage(StorageLayer.getStorage(tenantIdentifier, main)); + Passwordless.CreateCodeResponse code = Passwordless.createCode(tenantIdentifierWithStorage, main, + email, null, null, null); + AuthRecipeUserInfo user = Passwordless.consumeCode(tenantIdentifierWithStorage, main, code.deviceId, code.deviceIdHash, code.userInputCode, null).user; + TestCase.addUser(user); + } + } - try { - // Credentials does not exist in t2 - EmailPassword.signIn(t2WithStorage, process.getProcess(), "test1@example.com", "password"); - fail(); - } catch (WrongCredentialsException e) { - // ignore + private static class CreatePlessUserWithPhone extends TestCaseStep { + private final TenantIdentifier tenantIdentifier; + private final String phoneNumber; + + public CreatePlessUserWithPhone(TenantIdentifier tenantIdentifier, String phoneNumber) { + this.tenantIdentifier = tenantIdentifier; + this.phoneNumber = phoneNumber; } - // same email is allowed to sign up - AuthRecipeUserInfo user3 = EmailPassword.signUp(t1WithStorage, process.getProcess(), "test2@example.com", "password2"); + @Override + public void execute(Main main) throws Exception { + TenantIdentifierWithStorage tenantIdentifierWithStorage = tenantIdentifier.withStorage(StorageLayer.getStorage(tenantIdentifier, main)); + Passwordless.CreateCodeResponse code = Passwordless.createCode(tenantIdentifierWithStorage, main, + null, phoneNumber, null, null); + AuthRecipeUserInfo user = Passwordless.consumeCode(tenantIdentifierWithStorage, main, code.deviceId, code.deviceIdHash, code.userInputCode, null).user; + TestCase.addUser(user); + } + } - // Sign in should pass - EmailPassword.signIn(t1WithStorage, process.getProcess(), "test2@example.com", "password2"); + private static class CreateThirdPartyUser extends TestCaseStep { + TenantIdentifier tenantIdentifier; + String thirdPartyId; + String thirdPartyUserId; + String email; + + public CreateThirdPartyUser(TenantIdentifier tenantIdentifier, String thirdPartyId, String thirdPartyUserId, String email) { + this.tenantIdentifier = tenantIdentifier; + this.thirdPartyId = thirdPartyId; + this.thirdPartyUserId = thirdPartyUserId; + this.email = email; + } - try { - // user 3 cannot become a primary user - AuthRecipe.createPrimaryUser(process.getProcess(), t1WithStorage.toAppIdentifierWithStorage(), user3.getSupertokensUserId()); - fail(); - } catch (AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException e) { - // Ignore + @Override + public void execute(Main main) throws Exception { + TenantIdentifierWithStorage tenantIdentifierWithStorage = tenantIdentifier.withStorage(StorageLayer.getStorage(tenantIdentifier, main)); + AuthRecipeUserInfo user = ThirdParty.signInUp(tenantIdentifierWithStorage, main, thirdPartyId, thirdPartyUserId, email).user; + TestCase.addUser(user); } + } - process.kill(); - assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + private static class MakePrimaryUser extends TestCaseStep { + TenantIdentifier tenantIdentifier; + int userIndex; + + public MakePrimaryUser(TenantIdentifier tenantIdentifier, int userIndex) { + this.tenantIdentifier = tenantIdentifier; + this.userIndex = userIndex; + } + + @Override + public void execute(Main main) throws Exception { + TenantIdentifierWithStorage tenantIdentifierWithStorage = tenantIdentifier.withStorage(StorageLayer.getStorage(tenantIdentifier, main)); + AuthRecipe.createPrimaryUser(main, tenantIdentifierWithStorage.toAppIdentifierWithStorage(), TestCase.users.get(userIndex).getSupertokensUserId()); + } } - @Test - public void testWithEmailPasswordUsersAndPasswordlessUser1() throws Exception { - String[] args = {"../"}; - TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); - FeatureFlagTestContent.getInstance(process.getProcess()) - .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ - EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); - process.startProcess(); - assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); - - createTenants(process.getProcess()); - - TenantIdentifierWithStorage t1WithStorage = t1.withStorage(StorageLayer.getStorage(t1, process.getProcess())); - TenantIdentifierWithStorage t2WithStorage = t2.withStorage(StorageLayer.getStorage(t2, process.getProcess())); - - AuthRecipeUserInfo user1 = EmailPassword.signUp(t1WithStorage, process.getProcess(), "test1@example.com", "password"); - Passwordless.CreateCodeResponse user2Code = Passwordless.createCode(t2WithStorage, process.getProcess(), - "test2@example.com", null, null, null); - AuthRecipeUserInfo user2 = Passwordless.consumeCode(t2WithStorage, process.getProcess(), user2Code.deviceId, user2Code.deviceIdHash, user2Code.userInputCode, null).user; - AuthRecipe.createPrimaryUser(process.getProcess(), t1WithStorage.toAppIdentifierWithStorage(), user1.getSupertokensUserId()); - AuthRecipe.linkAccounts(process.getProcess(), t1WithStorage.toAppIdentifierWithStorage(), user2.getSupertokensUserId(), user1.getSupertokensUserId()); - - try { - // Credentials does not exist in t2 - EmailPassword.signIn(t2WithStorage, process.getProcess(), "test1@example.com", "password"); - fail(); - } catch (WrongCredentialsException e) { - // ignore - } - - // same email is allowed to sign up - AuthRecipeUserInfo user3 = EmailPassword.signUp(t1WithStorage, process.getProcess(), "test2@example.com", "password2"); - - // Sign in should pass - EmailPassword.signIn(t1WithStorage, process.getProcess(), "test2@example.com", "password2"); - - try { - // user 3 cannot become a primary user - AuthRecipe.createPrimaryUser(process.getProcess(), t1WithStorage.toAppIdentifierWithStorage(), user3.getSupertokensUserId()); - fail(); - } catch (AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException e) { - // Ignore - } - - process.kill(); - assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + private static class LinkAccounts extends TestCaseStep { + TenantIdentifier tenantIdentifier; + int primaryUserIndex; + int recipeUserIndex; + + public LinkAccounts(TenantIdentifier tenantIdentifier, int primaryUserIndex, int recipeUserIndex) { + this.tenantIdentifier = tenantIdentifier; + this.primaryUserIndex = primaryUserIndex; + this.recipeUserIndex = recipeUserIndex; + } + + @Override + public void execute(Main main) throws Exception { + TenantIdentifierWithStorage tenantIdentifierWithStorage = tenantIdentifier.withStorage(StorageLayer.getStorage(tenantIdentifier, main)); + AuthRecipe.linkAccounts(main, tenantIdentifierWithStorage.toAppIdentifierWithStorage(), TestCase.users.get(recipeUserIndex).getSupertokensUserId(), TestCase.users.get(primaryUserIndex).getSupertokensUserId()); + } } - @Test - public void testWithEmailPasswordUsersAndPasswordlessUser2() throws Exception { - String[] args = {"../"}; - TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); - FeatureFlagTestContent.getInstance(process.getProcess()) - .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ - EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); - process.startProcess(); - assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); - - createTenants(process.getProcess()); - - TenantIdentifierWithStorage t1WithStorage = t1.withStorage(StorageLayer.getStorage(t1, process.getProcess())); - TenantIdentifierWithStorage t2WithStorage = t2.withStorage(StorageLayer.getStorage(t2, process.getProcess())); - - AuthRecipeUserInfo user1 = EmailPassword.signUp(t1WithStorage, process.getProcess(), "test1@example.com", "password"); - Passwordless.CreateCodeResponse user2Code = Passwordless.createCode(t2WithStorage, process.getProcess(), - "test2@example.com", null, null, null); - AuthRecipeUserInfo user2 = Passwordless.consumeCode(t2WithStorage, process.getProcess(), user2Code.deviceId, user2Code.deviceIdHash, user2Code.userInputCode, null).user; - AuthRecipe.createPrimaryUser(process.getProcess(), t1WithStorage.toAppIdentifierWithStorage(), user1.getSupertokensUserId()); - AuthRecipe.linkAccounts(process.getProcess(), t1WithStorage.toAppIdentifierWithStorage(), user2.getSupertokensUserId(), user1.getSupertokensUserId()); - - // same email is allowed to sign in up - Passwordless.CreateCodeResponse user3code = Passwordless.createCode(t1WithStorage, process.getProcess(), - "test2@example.com", null, null, null); - - AuthRecipeUserInfo user3 = Passwordless.consumeCode(t1WithStorage, process.getProcess(), user3code.deviceId, user3code.deviceIdHash, user3code.userInputCode, null).user; - - try { - // user 3 cannot become a primary user - AuthRecipe.createPrimaryUser(process.getProcess(), t1WithStorage.toAppIdentifierWithStorage(), user3.getSupertokensUserId()); - fail(); - } catch (AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException e) { - // Ignore - } - - process.kill(); - assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + private static class AssociateUserToTenant extends TestCaseStep { + TenantIdentifier tenantIdentifier; + int userIndex; + + public AssociateUserToTenant(TenantIdentifier tenantIdentifier, int userIndex) { + this.tenantIdentifier = tenantIdentifier; + this.userIndex = userIndex; + } + + @Override + public void execute(Main main) throws Exception { + TenantIdentifierWithStorage tenantIdentifierWithStorage = tenantIdentifier.withStorage(StorageLayer.getStorage(tenantIdentifier, main)); + Multitenancy.addUserIdToTenant(main, tenantIdentifierWithStorage, TestCase.users.get(userIndex).getSupertokensUserId()); + } } - @Test - public void testWithEmailPasswordUserAndThirdPartyUser() throws Exception { - String[] args = {"../"}; - TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); - FeatureFlagTestContent.getInstance(process.getProcess()) - .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ - EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); - process.startProcess(); - assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); - - createTenants(process.getProcess()); - - TenantIdentifierWithStorage t1WithStorage = t1.withStorage(StorageLayer.getStorage(t1, process.getProcess())); - TenantIdentifierWithStorage t2WithStorage = t2.withStorage(StorageLayer.getStorage(t2, process.getProcess())); - - AuthRecipeUserInfo user1 = EmailPassword.signUp(t1WithStorage, process.getProcess(), "test1@example.com", "password"); - AuthRecipeUserInfo user2 = ThirdParty.signInUp(t2WithStorage, process.getProcess(), "google", "google-user", "test2@example.com").user; - AuthRecipe.createPrimaryUser(process.getProcess(), t1WithStorage.toAppIdentifierWithStorage(), user1.getSupertokensUserId()); - AuthRecipe.linkAccounts(process.getProcess(), t1WithStorage.toAppIdentifierWithStorage(), user2.getSupertokensUserId(), user1.getSupertokensUserId()); - - try { - // Credentials does not exist in t2 - EmailPassword.signIn(t2WithStorage, process.getProcess(), "test1@example.com", "password"); - fail(); - } catch (WrongCredentialsException e) { - // ignore - } - - // same email is allowed to sign up - AuthRecipeUserInfo user3 = ThirdParty.signInUp(t1WithStorage, process.getProcess(), "google", "google-user", "test2@example.com").user; - - try { - // user 3 cannot become a primary user - AuthRecipe.createPrimaryUser(process.getProcess(), t1WithStorage.toAppIdentifierWithStorage(), user3.getSupertokensUserId()); - fail(); - } catch (AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException e) { - // Ignore - } - - process.kill(); - assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + private static class UpdateEmailPasswordUserEmail extends TestCaseStep { + TenantIdentifier tenantIdentifier; + int userIndex; + String email; + + public UpdateEmailPasswordUserEmail(TenantIdentifier tenantIdentifier, int userIndex, String email) { + this.tenantIdentifier = tenantIdentifier; + this.userIndex = userIndex; + this.email = email; + } + + @Override + public void execute(Main main) throws Exception { + TenantIdentifierWithStorage tenantIdentifierWithStorage = tenantIdentifier.withStorage(StorageLayer.getStorage(tenantIdentifier, main)); + EmailPassword.updateUsersEmailOrPassword(tenantIdentifierWithStorage.toAppIdentifierWithStorage(), main, TestCase.users.get(userIndex).getSupertokensUserId(), email, null); + } } - @Test - public void testTenantAssociationWithEPUsers1() throws Exception { - String[] args = {"../"}; - TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); - FeatureFlagTestContent.getInstance(process.getProcess()) - .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ - EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); - process.startProcess(); - assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); - - createTenants(process.getProcess()); - - TenantIdentifierWithStorage t1WithStorage = t1.withStorage(StorageLayer.getStorage(t1, process.getProcess())); - TenantIdentifierWithStorage t2WithStorage = t2.withStorage(StorageLayer.getStorage(t2, process.getProcess())); - TenantIdentifierWithStorage t3WithStorage = t3.withStorage(StorageLayer.getStorage(t3, process.getProcess())); - - AuthRecipeUserInfo user1 = EmailPassword.signUp(t1WithStorage, process.getProcess(), "test1@example.com", "password1"); - AuthRecipeUserInfo user2 = EmailPassword.signUp(t2WithStorage, process.getProcess(), "test2@example.com", "password2"); - AuthRecipeUserInfo user3 = EmailPassword.signUp(t3WithStorage, process.getProcess(), "test1@example.com", "password3"); - - AuthRecipe.createPrimaryUser(process.getProcess(), t1WithStorage.toAppIdentifierWithStorage(), user1.getSupertokensUserId()); - AuthRecipe.linkAccounts(process.getProcess(), t1WithStorage.toAppIdentifierWithStorage(), user2.getSupertokensUserId(), user1.getSupertokensUserId()); - - Multitenancy.addUserIdToTenant(process.getProcess(), t2WithStorage, user3.getSupertokensUserId()); - try { - AuthRecipe.createPrimaryUser(process.getProcess(), t2WithStorage.toAppIdentifierWithStorage(), user3.getSupertokensUserId()); - fail(); - } catch (AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException e) { - // ignore - } - - process.kill(); - assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + private static class UpdatePlessUserEmail extends TestCaseStep { + TenantIdentifier tenantIdentifier; + int userIndex; + String email; + + public UpdatePlessUserEmail(TenantIdentifier tenantIdentifier, int userIndex, String email) { + this.tenantIdentifier = tenantIdentifier; + this.userIndex = userIndex; + this.email = email; + } + + @Override + public void execute(Main main) throws Exception { + TenantIdentifierWithStorage tenantIdentifierWithStorage = tenantIdentifier.withStorage(StorageLayer.getStorage(tenantIdentifier, main)); + Passwordless.updateUser(tenantIdentifierWithStorage.toAppIdentifierWithStorage(), TestCase.users.get(userIndex).getSupertokensUserId(), new Passwordless.FieldUpdate(email), null); + } } - @Test - public void testTenantAssociationWithEPUsers2() throws Exception { - String[] args = {"../"}; - TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); - FeatureFlagTestContent.getInstance(process.getProcess()) - .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ - EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); - process.startProcess(); - assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); - - createTenants(process.getProcess()); - - TenantIdentifierWithStorage t1WithStorage = t1.withStorage(StorageLayer.getStorage(t1, process.getProcess())); - TenantIdentifierWithStorage t2WithStorage = t2.withStorage(StorageLayer.getStorage(t2, process.getProcess())); - TenantIdentifierWithStorage t3WithStorage = t3.withStorage(StorageLayer.getStorage(t3, process.getProcess())); - - AuthRecipeUserInfo user1 = EmailPassword.signUp(t1WithStorage, process.getProcess(), "test1@example.com", "password1"); - AuthRecipeUserInfo user2 = EmailPassword.signUp(t2WithStorage, process.getProcess(), "test2@example.com", "password2"); - AuthRecipeUserInfo user3 = EmailPassword.signUp(t3WithStorage, process.getProcess(), "test1@example.com", "password3"); - - AuthRecipe.createPrimaryUser(process.getProcess(), t1WithStorage.toAppIdentifierWithStorage(), user1.getSupertokensUserId()); - AuthRecipe.linkAccounts(process.getProcess(), t1WithStorage.toAppIdentifierWithStorage(), user2.getSupertokensUserId(), user1.getSupertokensUserId()); - - AuthRecipe.createPrimaryUser(process.getProcess(), t2WithStorage.toAppIdentifierWithStorage(), user3.getSupertokensUserId()); - try { - Multitenancy.addUserIdToTenant(process.getProcess(), t2WithStorage, user3.getSupertokensUserId()); - fail(); - } catch (DuplicateEmailException e) { - // ignore - } - - process.kill(); - assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + private static class UpdatePlessUserPhone extends TestCaseStep { + TenantIdentifier tenantIdentifier; + int userIndex; + String phoneNumber; + + public UpdatePlessUserPhone(TenantIdentifier tenantIdentifier, int userIndex, String phoneNumber) { + this.tenantIdentifier = tenantIdentifier; + this.userIndex = userIndex; + this.phoneNumber = phoneNumber; + } + + @Override + public void execute(Main main) throws Exception { + TenantIdentifierWithStorage tenantIdentifierWithStorage = tenantIdentifier.withStorage(StorageLayer.getStorage(tenantIdentifier, main)); + Passwordless.updateUser(tenantIdentifierWithStorage.toAppIdentifierWithStorage(), TestCase.users.get(userIndex).getSupertokensUserId(), null, new Passwordless.FieldUpdate(phoneNumber)); + } } } diff --git a/src/test/java/io/supertokens/test/accountlinking/api/ActiveUserTest.java b/src/test/java/io/supertokens/test/accountlinking/api/ActiveUserTest.java index c07765164..10c652c3c 100644 --- a/src/test/java/io/supertokens/test/accountlinking/api/ActiveUserTest.java +++ b/src/test/java/io/supertokens/test/accountlinking/api/ActiveUserTest.java @@ -128,6 +128,7 @@ public void testActiveUserIsRemovedAfterLinkingAccounts() throws Exception { { JsonObject emailObject = new JsonObject(); emailObject.addProperty("id", "test@example.com"); + emailObject.addProperty("isVerified", false); JsonObject signUpRequestBody = new JsonObject(); signUpRequestBody.addProperty("thirdPartyId", "google"); @@ -155,9 +156,9 @@ public void testActiveUserIsRemovedAfterLinkingAccounts() throws Exception { WebserverAPI.getLatestCDIVersion().get(), ""); } - // Now there should be only one active user + // we don't remove the active user for the recipe user, so it should still be 2 userCount = ActiveUsers.countUsersActiveSince(process.getProcess(), System.currentTimeMillis() - 10000); - assertEquals(1, userCount); + assertEquals(2, userCount); // Sign in to the accounts once again { @@ -173,6 +174,7 @@ public void testActiveUserIsRemovedAfterLinkingAccounts() throws Exception { { JsonObject emailObject = new JsonObject(); emailObject.addProperty("id", "test@example.com"); + emailObject.addProperty("isVerified", false); JsonObject signUpRequestBody = new JsonObject(); signUpRequestBody.addProperty("thirdPartyId", "google"); @@ -186,7 +188,7 @@ public void testActiveUserIsRemovedAfterLinkingAccounts() throws Exception { // there should still be only one active user userCount = ActiveUsers.countUsersActiveSince(process.getProcess(), System.currentTimeMillis() - 10000); - assertEquals(1, userCount); + assertEquals(2, userCount); process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); diff --git a/src/test/java/io/supertokens/test/emailpassword/api/MultitenantAPITest.java b/src/test/java/io/supertokens/test/emailpassword/api/MultitenantAPITest.java index 6a9627fee..7953013d6 100644 --- a/src/test/java/io/supertokens/test/emailpassword/api/MultitenantAPITest.java +++ b/src/test/java/io/supertokens/test/emailpassword/api/MultitenantAPITest.java @@ -720,7 +720,7 @@ public void testThatTenantIdIsNotAllowedForOlderCDIVersion() throws Exception { } @Test - public void testGetUserByIdForUserThatBelongsToNoTenant() throws Exception { + public void testThatDisassociationFromAllTenantsIsDisallowed() throws Exception { if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { return; } @@ -729,11 +729,16 @@ public void testGetUserByIdForUserThatBelongsToNoTenant() throws Exception { { JsonObject user = TestMultitenancyAPIHelper.epSignUp(t1, "test@example.com", "password", process.getProcess()); - TestMultitenancyAPIHelper.disassociateUserFromTenant(t1, user.get("id").getAsString(), process.getProcess()); - JsonObject userInfoFromId = TestMultitenancyAPIHelper.getEpUserById(t1, user.get("id").getAsString(), - process.getProcess()); - - assertEquals(0, userInfoFromId.get("tenantIds").getAsJsonArray().size()); + { + JsonObject requestBody = new JsonObject(); + requestBody.addProperty("userId", user.get("id").getAsString()); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + HttpRequestForTesting.getMultitenantUrl(t1, "/recipe/multitenancy/tenant/user/remove"), + requestBody, 1000, 1000, null, + SemVer.v4_0.get(), "multitenancy"); + assertEquals("DISASSOCIATION_NOT_ALLOWED_ERROR", response.get("status").getAsString()); + } } } } diff --git a/src/test/java/io/supertokens/test/thirdparty/api/EmailVerificationTest.java b/src/test/java/io/supertokens/test/thirdparty/api/EmailVerificationTest.java index 98bf51675..8ca4ab873 100644 --- a/src/test/java/io/supertokens/test/thirdparty/api/EmailVerificationTest.java +++ b/src/test/java/io/supertokens/test/thirdparty/api/EmailVerificationTest.java @@ -153,6 +153,75 @@ public void testEmailVerificationOnSignInUp_v4_0() throws Exception { assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); } + @Test + public void testEmailVerificationStateDoesNotChangeWhenFalseIsPassed() throws Exception { + String[] args = {"../"}; + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + { + JsonObject emailObject = new JsonObject(); + emailObject.addProperty("id", "test@example.com"); + emailObject.addProperty("isVerified", false); + + JsonObject signUpRequestBody = new JsonObject(); + signUpRequestBody.addProperty("thirdPartyId", "google"); + signUpRequestBody.addProperty("thirdPartyUserId", "google-user"); + signUpRequestBody.add("email", emailObject); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signinup", signUpRequestBody, 1000, 1000, null, + SemVer.v4_0.get(), "thirdparty"); + + String userId = response.get("user").getAsJsonObject().get("id").getAsString(); + assertFalse(EmailVerification.isEmailVerified(process.getProcess(), userId, "test@example.com")); + } + + { + JsonObject emailObject = new JsonObject(); + emailObject.addProperty("id", "test@example.com"); + emailObject.addProperty("isVerified", true); + + JsonObject signUpRequestBody = new JsonObject(); + signUpRequestBody.addProperty("thirdPartyId", "google"); + signUpRequestBody.addProperty("thirdPartyUserId", "google-user"); + signUpRequestBody.add("email", emailObject); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signinup", signUpRequestBody, 1000, 1000, null, + SemVer.v4_0.get(), "thirdparty"); + + String userId = response.get("user").getAsJsonObject().get("id").getAsString(); + assertTrue(EmailVerification.isEmailVerified(process.getProcess(), userId, "test@example.com")); + } + + { + JsonObject emailObject = new JsonObject(); + emailObject.addProperty("id", "test@example.com"); + emailObject.addProperty("isVerified", false); + + JsonObject signUpRequestBody = new JsonObject(); + signUpRequestBody.addProperty("thirdPartyId", "google"); + signUpRequestBody.addProperty("thirdPartyUserId", "google-user"); + signUpRequestBody.add("email", emailObject); + + JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signinup", signUpRequestBody, 1000, 1000, null, + SemVer.v4_0.get(), "thirdparty"); + + String userId = response.get("user").getAsJsonObject().get("id").getAsString(); + assertTrue(EmailVerification.isEmailVerified(process.getProcess(), userId, "test@example.com")); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + @Test public void testWithAccountLinking() throws Exception { String[] args = {"../"}; diff --git a/src/test/java/io/supertokens/test/thirdparty/api/ThirdPartySignInUpAPITest4_0.java b/src/test/java/io/supertokens/test/thirdparty/api/ThirdPartySignInUpAPITest4_0.java index 09cc93136..290224130 100644 --- a/src/test/java/io/supertokens/test/thirdparty/api/ThirdPartySignInUpAPITest4_0.java +++ b/src/test/java/io/supertokens/test/thirdparty/api/ThirdPartySignInUpAPITest4_0.java @@ -72,6 +72,7 @@ public void testGoodInput() throws Exception { JsonObject emailObject = new JsonObject(); emailObject.addProperty("id", "test@example.com"); + emailObject.addProperty("isVerified", false); JsonObject signUpRequestBody = new JsonObject(); signUpRequestBody.addProperty("thirdPartyId", "google"); @@ -142,6 +143,7 @@ public void testNotAllowedUpdateOfEmail() throws Exception { JsonObject emailObject = new JsonObject(); emailObject.addProperty("id", "someemail1@gmail.com"); + emailObject.addProperty("isVerified", false); JsonObject signUpRequestBody = new JsonObject(); signUpRequestBody.addProperty("thirdPartyId", "google"); From 265dbda4231d7eed2f93a610d576e39f069da05a Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Wed, 6 Sep 2023 16:57:06 +0530 Subject: [PATCH 102/131] fix: get user by id as per recipe (#788) --- .../emailpassword/EmailPassword.java | 2 +- .../passwordless/Passwordless.java | 2 +- .../io/supertokens/thirdparty/ThirdParty.java | 2 +- .../api/EmailPasswordGetUserAPITest2_7.java | 43 +++++++++++++++++++ .../api/PasswordlessUserGetAPITest2_11.java | 42 ++++++++++++++++++ .../api/ThirdPartyGetUserAPITest2_7.java | 43 +++++++++++++++++++ 6 files changed, 131 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/supertokens/emailpassword/EmailPassword.java b/src/main/java/io/supertokens/emailpassword/EmailPassword.java index 950b9bf3e..2ac0a96b9 100644 --- a/src/main/java/io/supertokens/emailpassword/EmailPassword.java +++ b/src/main/java/io/supertokens/emailpassword/EmailPassword.java @@ -676,7 +676,7 @@ public static AuthRecipeUserInfo getUserUsingId(AppIdentifierWithStorage appIden return null; } for (LoginMethod lM : result.loginMethods) { - if (lM.getSupertokensUserId().equals(userId)) { + if (lM.getSupertokensUserId().equals(userId) && lM.recipeId == RECIPE_ID.EMAIL_PASSWORD) { return AuthRecipeUserInfo.create(lM.getSupertokensUserId(), result.isPrimaryUser, lM); } } diff --git a/src/main/java/io/supertokens/passwordless/Passwordless.java b/src/main/java/io/supertokens/passwordless/Passwordless.java index 930e61493..62f92b037 100644 --- a/src/main/java/io/supertokens/passwordless/Passwordless.java +++ b/src/main/java/io/supertokens/passwordless/Passwordless.java @@ -608,7 +608,7 @@ public static AuthRecipeUserInfo getUserById(AppIdentifierWithStorage appIdentif return null; } for (LoginMethod lM : result.loginMethods) { - if (lM.getSupertokensUserId().equals(userId)) { + if (lM.getSupertokensUserId().equals(userId) && lM.recipeId == RECIPE_ID.PASSWORDLESS) { return AuthRecipeUserInfo.create(lM.getSupertokensUserId(), result.isPrimaryUser, lM); } diff --git a/src/main/java/io/supertokens/thirdparty/ThirdParty.java b/src/main/java/io/supertokens/thirdparty/ThirdParty.java index 3100daf4a..33a7f8717 100644 --- a/src/main/java/io/supertokens/thirdparty/ThirdParty.java +++ b/src/main/java/io/supertokens/thirdparty/ThirdParty.java @@ -337,7 +337,7 @@ public static AuthRecipeUserInfo getUser(AppIdentifierWithStorage appIdentifierW return null; } for (LoginMethod lM : result.loginMethods) { - if (lM.getSupertokensUserId().equals(userId)) { + if (lM.getSupertokensUserId().equals(userId) && lM.recipeId == RECIPE_ID.THIRD_PARTY) { return AuthRecipeUserInfo.create(lM.getSupertokensUserId(), result.isPrimaryUser, lM); } diff --git a/src/test/java/io/supertokens/test/emailpassword/api/EmailPasswordGetUserAPITest2_7.java b/src/test/java/io/supertokens/test/emailpassword/api/EmailPasswordGetUserAPITest2_7.java index 4cbb5f559..ee71b3b44 100644 --- a/src/test/java/io/supertokens/test/emailpassword/api/EmailPasswordGetUserAPITest2_7.java +++ b/src/test/java/io/supertokens/test/emailpassword/api/EmailPasswordGetUserAPITest2_7.java @@ -18,11 +18,14 @@ import com.google.gson.JsonObject; import io.supertokens.ProcessState; +import io.supertokens.passwordless.Passwordless; import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.TestingProcessManager; import io.supertokens.test.Utils; import io.supertokens.test.httpRequest.HttpRequestForTesting; +import io.supertokens.thirdparty.ThirdParty; import io.supertokens.utils.SemVer; import org.junit.AfterClass; import org.junit.Before; @@ -194,4 +197,44 @@ public void testForAllTypesOfOutput() throws Exception { process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); } + + @Test + public void testGetUserForUsersOfOtherRecipeIds() throws Exception { + String[] args = {"../"}; + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo user1 = ThirdParty.signInUp(process.getProcess(), "google", "googleid", "test@example.com").user; + Passwordless.CreateCodeResponse user2code = Passwordless.createCode(process.getProcess(), "test@example.com", + null, null, null); + AuthRecipeUserInfo user2 = Passwordless.consumeCode(process.getProcess(), user2code.deviceId, user2code.deviceIdHash, user2code.userInputCode, null).user; + + { + HashMap map = new HashMap<>(); + map.put("userId", user1.getSupertokensUserId()); + + JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/recipe/user", map, 1000, 1000, null, SemVer.v2_7.get(), + "emailpassword"); + assertEquals(response.get("status").getAsString(), "UNKNOWN_USER_ID_ERROR"); + } + + { + HashMap map = new HashMap<>(); + map.put("userId", user2.getSupertokensUserId()); + + JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/recipe/user", map, 1000, 1000, null, SemVer.v2_7.get(), + "emailpassword"); + assertEquals(response.get("status").getAsString(), "UNKNOWN_USER_ID_ERROR"); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } } diff --git a/src/test/java/io/supertokens/test/passwordless/api/PasswordlessUserGetAPITest2_11.java b/src/test/java/io/supertokens/test/passwordless/api/PasswordlessUserGetAPITest2_11.java index 0f2b3f24e..92d1b580d 100644 --- a/src/test/java/io/supertokens/test/passwordless/api/PasswordlessUserGetAPITest2_11.java +++ b/src/test/java/io/supertokens/test/passwordless/api/PasswordlessUserGetAPITest2_11.java @@ -18,7 +18,10 @@ import com.google.gson.JsonObject; import io.supertokens.ProcessState; +import io.supertokens.emailpassword.EmailPassword; +import io.supertokens.passwordless.Passwordless; import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.passwordless.PasswordlessStorage; import io.supertokens.storageLayer.StorageLayer; @@ -26,6 +29,7 @@ import io.supertokens.test.Utils; import io.supertokens.test.httpRequest.HttpRequestForTesting; +import io.supertokens.thirdparty.ThirdParty; import io.supertokens.utils.SemVer; import io.supertokens.test.httpRequest.HttpResponseException; import org.junit.AfterClass; @@ -233,4 +237,42 @@ private static void checkUser(JsonObject resp, String userId, String email, Stri assert (System.currentTimeMillis() - 10000 < user.get("timeJoined").getAsLong()); assertEquals(3, user.entrySet().size()); } + + @Test + public void testGetUserForUsersOfOtherRecipeIds() throws Exception { + String[] args = {"../"}; + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo user1 = EmailPassword.signUp(process.getProcess(), "test@example.com", "password"); + AuthRecipeUserInfo user2 = ThirdParty.signInUp(process.getProcess(), "google", "googleid", "test@example.com").user; + + { + HashMap map = new HashMap<>(); + map.put("userId", user1.getSupertokensUserId()); + + JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/recipe/user", map, 1000, 1000, null, SemVer.v2_7.get(), + "passwordless"); + assertEquals(response.get("status").getAsString(), "UNKNOWN_USER_ID_ERROR"); + } + + { + HashMap map = new HashMap<>(); + map.put("userId", user2.getSupertokensUserId()); + + JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/recipe/user", map, 1000, 1000, null, SemVer.v2_7.get(), + "passwordless"); + assertEquals(response.get("status").getAsString(), "UNKNOWN_USER_ID_ERROR"); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } } diff --git a/src/test/java/io/supertokens/test/thirdparty/api/ThirdPartyGetUserAPITest2_7.java b/src/test/java/io/supertokens/test/thirdparty/api/ThirdPartyGetUserAPITest2_7.java index 7ab18068b..9aab47c5a 100644 --- a/src/test/java/io/supertokens/test/thirdparty/api/ThirdPartyGetUserAPITest2_7.java +++ b/src/test/java/io/supertokens/test/thirdparty/api/ThirdPartyGetUserAPITest2_7.java @@ -18,7 +18,10 @@ import com.google.gson.JsonObject; import io.supertokens.ProcessState; +import io.supertokens.emailpassword.EmailPassword; +import io.supertokens.passwordless.Passwordless; import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.TestingProcessManager; import io.supertokens.test.Utils; @@ -205,6 +208,46 @@ public void testAllTypesOfOutput() throws Exception { } } + @Test + public void testGetUserForUsersOfOtherRecipeIds() throws Exception { + String[] args = {"../"}; + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo user1 = EmailPassword.signUp(process.getProcess(), "test@example.com", "password"); + Passwordless.CreateCodeResponse user2code = Passwordless.createCode(process.getProcess(), "test@example.com", + null, null, null); + AuthRecipeUserInfo user2 = Passwordless.consumeCode(process.getProcess(), user2code.deviceId, user2code.deviceIdHash, user2code.userInputCode, null).user; + + { + HashMap map = new HashMap<>(); + map.put("userId", user1.getSupertokensUserId()); + + JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/recipe/user", map, 1000, 1000, null, SemVer.v2_7.get(), + "thirdparty"); + assertEquals(response.get("status").getAsString(), "UNKNOWN_USER_ID_ERROR"); + } + + { + HashMap map = new HashMap<>(); + map.put("userId", user2.getSupertokensUserId()); + + JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/recipe/user", map, 1000, 1000, null, SemVer.v2_7.get(), + "thirdparty"); + assertEquals(response.get("status").getAsString(), "UNKNOWN_USER_ID_ERROR"); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + public static void checkUser(JsonObject user, String thirdPartyId, String thirdPartyUserId, String email) { assertNotNull(user.get("id")); assertNotNull(user.get("timeJoined")); From bd99d66d142fc514179e2938c43040d7e77cefef Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Thu, 7 Sep 2023 12:10:55 +0530 Subject: [PATCH 103/131] fix: updated migration (#790) --- CHANGELOG.md | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6cf0be20..1e7b4fc83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,9 +18,13 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### Db schema changes: +- Removed index `all_auth_recipe_users_pagination_index` and added these instead: + - `all_auth_recipe_users_pagination_index1` + - `all_auth_recipe_users_pagination_index2` + - `all_auth_recipe_users_pagination_index3` + - `all_auth_recipe_users_pagination_index4` +- Added new index `all_auth_recipe_users_recipe_id_index` - Added new index `all_auth_recipe_users_primary_user_id_index`. -- Added new index `all_auth_recipe_users_primary_user_id_and_tenant_id_index`. -- Modified `all_auth_recipe_users_pagination_index` index to be on `primary_or_recipe_user_id` instead of `user_id` - Added a two new columns in `all_auth_recipe_users`: - `primary_or_recipe_user_id` (default value is equal to `user_id` column) - `is_linked_or_is_a_primary_user` (default value is false) @@ -44,11 +48,16 @@ ALTER TABLE all_auth_recipe_users ALTER primary_or_recipe_user_id DROP DEFAULT; DROP INDEX all_auth_recipe_users_pagination_index; -CREATE INDEX all_auth_recipe_users_pagination_index ON all_auth_recipe_users (time_joined DESC, - primary_or_recipe_user_id DESC, tenant_id - DESC, app_id DESC); -CREATE INDEX all_auth_recipe_users_primary_user_id_index ON all_auth_recipe_users (app_id, primary_or_recipe_user_id); -CREATE INDEX all_auth_recipe_users_primary_user_id_and_tenant_id_index ON all_auth_recipe_users (app_id, tenant_id, primary_or_recipe_user_id); +CREATE INDEX all_auth_recipe_users_pagination_index1 ON all_auth_recipe_users ( + app_id, tenant_id, primary_or_recipe_user_time_joined DESC, primary_or_recipe_user_id DESC); +CREATE INDEX all_auth_recipe_users_pagination_index2 ON all_auth_recipe_users ( + app_id, tenant_id, primary_or_recipe_user_time_joined ASC, primary_or_recipe_user_id DESC); +CREATE INDEX all_auth_recipe_users_pagination_index3 ON all_auth_recipe_users ( + recipe_id, app_id, tenant_id, primary_or_recipe_user_time_joined DESC, primary_or_recipe_user_id DESC); +CREATE INDEX all_auth_recipe_users_pagination_index4 ON all_auth_recipe_users ( + recipe_id, app_id, tenant_id, primary_or_recipe_user_time_joined ASC, primary_or_recipe_user_id DESC); + +CREATE INDEX all_auth_recipe_users_primary_user_id_index ON all_auth_recipe_users (primary_or_recipe_user_id, app_id); ALTER TABLE emailpassword_pswd_reset_tokens DROP CONSTRAINT IF EXISTS emailpassword_pswd_reset_tokens_user_id_fkey; ALTER TABLE emailpassword_pswd_reset_tokens ADD CONSTRAINT emailpassword_pswd_reset_tokens_user_id_fkey FOREIGN KEY (app_id, user_id) REFERENCES app_id_to_user_id (app_id, user_id) ON DELETE CASCADE; From 82a2216ef243ba036eda55532dfa065945ac688a Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Thu, 7 Sep 2023 12:16:30 +0530 Subject: [PATCH 104/131] fix: updated migration script --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e7b4fc83..d4281f39e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,10 +40,18 @@ ALTER TABLE all_auth_recipe_users ALTER TABLE all_auth_recipe_users ADD COLUMN is_linked_or_is_a_primary_user BOOLEAN NOT NULL DEFAULT FALSE; +ALTER TABLE all_auth_recipe_users + ADD COLUMN primary_or_recipe_user_time_joined BIGINT NOT NULL DEFAULT 0; + UPDATE all_auth_recipe_users SET primary_or_recipe_user_id = user_id WHERE primary_or_recipe_user_id = '0'; +UPDATE all_auth_recipe_users +SET primary_or_recipe_user_time_joined = time_joined +WHERE primary_or_recipe_user_time_joined = 0; + + ALTER TABLE all_auth_recipe_users ALTER primary_or_recipe_user_id DROP DEFAULT; From d8569da4c77dff4d0a452d30299d4a360402c8a0 Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Thu, 7 Sep 2023 12:45:25 +0530 Subject: [PATCH 105/131] fix: fkey constraint on primary_or_recipe_user_id (#791) --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d4281f39e..ba7efbbff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,10 @@ UPDATE all_auth_recipe_users SET primary_or_recipe_user_time_joined = time_joined WHERE primary_or_recipe_user_time_joined = 0; +ALTER TABLE all_auth_recipe_users + ADD CONSTRAINT all_auth_recipe_users_primary_or_recipe_user_id_fkey + FOREIGN KEY (app_id, primary_or_recipe_user_id) + REFERENCES app_id_to_user_id (app_id, user_id) ON DELETE CASCADE; ALTER TABLE all_auth_recipe_users ALTER primary_or_recipe_user_id DROP DEFAULT; From f7e4d6bb461657aa3b5de12ca0c9f2fa1a93f8d7 Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Thu, 7 Sep 2023 16:17:02 +0530 Subject: [PATCH 106/131] fix: account linking stats (#792) * fix: account linking stats * fix: test * fix: pr comments * fix: updated test --- .../java/io/supertokens/ee/EEFeatureFlag.java | 48 ++- .../java/io/supertokens/inmemorydb/Start.java | 18 + .../io/supertokens/test/FeatureFlagTest.java | 309 ++++++++++++++++++ 3 files changed, 374 insertions(+), 1 deletion(-) diff --git a/ee/src/main/java/io/supertokens/ee/EEFeatureFlag.java b/ee/src/main/java/io/supertokens/ee/EEFeatureFlag.java index 8efe42552..40eb83bb7 100644 --- a/ee/src/main/java/io/supertokens/ee/EEFeatureFlag.java +++ b/ee/src/main/java/io/supertokens/ee/EEFeatureFlag.java @@ -29,7 +29,6 @@ import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.TenantConfig; -import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.ThirdPartyConfig; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.session.sqlStorage.SessionSQLStorage; @@ -264,6 +263,49 @@ private JsonObject getMultiTenancyStats() return stats; } + private JsonObject getAccountLinkingStats() throws StorageQueryException { + JsonObject result = new JsonObject(); + Storage[] storages = StorageLayer.getStoragesForApp(main, this.appIdentifier); + boolean usesAccountLinking = false; + + for (Storage storage : storages) { + if (((AuthRecipeStorage)storage).checkIfUsesAccountLinking(this.appIdentifier)) { + usesAccountLinking = true; + break; + } + } + + result.addProperty("usesAccountLinking", usesAccountLinking); + if (!usesAccountLinking) { + result.addProperty("totalUserCountWithMoreThanOneLoginMethod", 0); + JsonArray mauArray = new JsonArray(); + for (int i = 0; i < 30; i++) { + mauArray.add(new JsonPrimitive(0)); + } + result.add("mauWithMoreThanOneLoginMethod", mauArray); + return result; + } + + int totalUserCountWithMoreThanOneLoginMethod = 0; + int[] maus = new int[30]; + + long now = System.currentTimeMillis(); + long today = now - (now % (24 * 60 * 60 * 1000L)); + + for (Storage storage : storages) { + totalUserCountWithMoreThanOneLoginMethod += ((AuthRecipeStorage)storage).getUsersCountWithMoreThanOneLoginMethod(this.appIdentifier); + + for (int i = 0; i < 30; i++) { + long timestamp = today - (i * 24 * 60 * 60 * 1000L); + maus[i] += ((ActiveUsersStorage)storage).countUsersThatHaveMoreThanOneLoginMethodAndActiveSince(appIdentifier, timestamp); + } + } + + result.addProperty("totalUserCountWithMoreThanOneLoginMethod", totalUserCountWithMoreThanOneLoginMethod); + result.add("mauWithMoreThanOneLoginMethod", new Gson().toJsonTree(maus)); + return result; + } + private JsonArray getMAUs() throws StorageQueryException, TenantOrAppNotFoundException { JsonArray mauArr = new JsonArray(); for (int i = 0; i < 30; i++) { @@ -311,6 +353,10 @@ public JsonObject getPaidFeatureStats() throws StorageQueryException, TenantOrAp if (feature == EE_FEATURES.MULTI_TENANCY) { usageStats.add(EE_FEATURES.MULTI_TENANCY.toString(), getMultiTenancyStats()); } + + if (feature == EE_FEATURES.ACCOUNT_LINKING) { + usageStats.add(EE_FEATURES.ACCOUNT_LINKING.toString(), getAccountLinkingStats()); + } } usageStats.add("maus", getMAUs()); diff --git a/src/main/java/io/supertokens/inmemorydb/Start.java b/src/main/java/io/supertokens/inmemorydb/Start.java index 88bf47678..53e6b831a 100644 --- a/src/main/java/io/supertokens/inmemorydb/Start.java +++ b/src/main/java/io/supertokens/inmemorydb/Start.java @@ -2834,4 +2834,22 @@ public void unlinkAccounts_Transaction(AppIdentifier appIdentifier, TransactionC throws StorageQueryException { // TODO:.. } + + @Override + public boolean checkIfUsesAccountLinking(AppIdentifier appIdentifier) throws StorageQueryException { + // TODO + return false; + } + + @Override + public int countUsersThatHaveMoreThanOneLoginMethodAndActiveSince(AppIdentifier appIdentifier, long sinceTime) throws StorageQueryException { + // TODO + return 0; + } + + @Override + public int getUsersCountWithMoreThanOneLoginMethod(AppIdentifier appIdentifier) throws StorageQueryException { + // TODO + return 0; + } } diff --git a/src/test/java/io/supertokens/test/FeatureFlagTest.java b/src/test/java/io/supertokens/test/FeatureFlagTest.java index 15be7c36e..f2394b2dd 100644 --- a/src/test/java/io/supertokens/test/FeatureFlagTest.java +++ b/src/test/java/io/supertokens/test/FeatureFlagTest.java @@ -21,6 +21,7 @@ import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; import io.supertokens.ProcessState; +import io.supertokens.authRecipe.AuthRecipe; import io.supertokens.cronjobs.CronTask; import io.supertokens.cronjobs.CronTaskTest; import io.supertokens.cronjobs.Cronjobs; @@ -32,7 +33,9 @@ import io.supertokens.featureflag.exceptions.FeatureNotEnabledException; import io.supertokens.featureflag.exceptions.NoLicenseKeyFoundException; import io.supertokens.multitenancy.Multitenancy; +import io.supertokens.multitenancy.MultitenancyHelper; import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.*; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; @@ -40,6 +43,7 @@ import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.httpRequest.HttpRequestForTesting; import io.supertokens.test.multitenant.api.TestMultitenancyAPIHelper; +import io.supertokens.utils.SemVer; import io.supertokens.webserver.WebserverAPI; import org.junit.*; import org.junit.rules.TestRule; @@ -509,6 +513,7 @@ public void testThatMultitenantStatsAreAccurateForAnApp() throws Exception { null, 1000, 1000, null, WebserverAPI.getLatestCDIVersion().get(), ""); Assert.assertEquals("OK", response.get("status").getAsString()); + assertFalse(response.get("usageStats").getAsJsonObject().has("account_linking")); JsonArray multitenancyStats = response.get("usageStats").getAsJsonObject().get("multi_tenancy") .getAsJsonObject().get("tenants").getAsJsonArray(); assertEquals(6, multitenancyStats.size()); @@ -741,4 +746,308 @@ public void testNetworkCallIsMadeInCoreInit() throws Exception { process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); } + + private static String OPAQUE_KEY_WITH_MULTITENANCY_AND_ACCOUNTLINKING_FEATURE = "N2uEOdEzd1XZZ5VBSTGYaM7Ia4s8wAq" + + "RWFAxLqTYrB6GQ=vssOLo3c=PkFgcExkaXs=IA-d9UWccoNKsyUgNhOhcKtM1bjC5OLrYRpTAgN-2EbKYsQGGQRQHuUN4EO1V"; + + @Test + public void testAccountLinkingStatsArePresent() throws Exception { + String[] args = {"../"}; + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + FeatureFlag.getInstance(process.main).setLicenseKeyAndSyncFeatures(OPAQUE_KEY_WITH_MULTITENANCY_AND_ACCOUNTLINKING_FEATURE); + + Multitenancy.addNewOrUpdateAppOrTenant( + process.getProcess(), + new TenantIdentifier(null, null, null), + new TenantConfig( + new TenantIdentifier(null, "a1", null), + new EmailPasswordConfig(true), + new ThirdPartyConfig(true, null), + new PasswordlessConfig(true), + new JsonObject() + ) + ); + + { + JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://127.0.0.1:3567/ee/featureflag", + null, 1000, 1000, null, WebserverAPI.getLatestCDIVersion().get(), ""); + Assert.assertEquals("OK", response.get("status").getAsString()); + + JsonObject alStats = response.get("usageStats").getAsJsonObject().get("account_linking").getAsJsonObject(); + assertNotNull(alStats); + + assertEquals(3, alStats.entrySet().size()); + assertFalse(alStats.get("usesAccountLinking").getAsBoolean()); + assertEquals(0, alStats.get("totalUserCountWithMoreThanOneLoginMethod").getAsInt()); + + JsonArray maus = alStats.get("mauWithMoreThanOneLoginMethod").getAsJsonArray(); + assertEquals(30, maus.size()); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testAccountLinkingStatsAreAccurate() throws Exception { + String[] args = {"../"}; + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + FeatureFlag.getInstance(process.main).setLicenseKeyAndSyncFeatures(OPAQUE_KEY_WITH_MULTITENANCY_AND_ACCOUNTLINKING_FEATURE); + + AuthRecipeUserInfo user1 = EmailPassword.signUp(process.getProcess(), "test1@example.com", "password"); + AuthRecipeUserInfo user2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "password"); + AuthRecipeUserInfo user3 = EmailPassword.signUp(process.getProcess(), "test3@example.com", "password"); + + AuthRecipe.createPrimaryUser(process.getProcess(), user1.getSupertokensUserId()); + AuthRecipe.linkAccounts(process.getProcess(), user2.getSupertokensUserId(), user1.getSupertokensUserId()); + AuthRecipe.createPrimaryUser(process.getProcess(), user3.getSupertokensUserId()); + + { + JsonObject responseBody = new JsonObject(); + responseBody.addProperty("email", "test1@example.com"); + responseBody.addProperty("password", "password"); + + JsonObject signInResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signin", responseBody, 1000, 1000, null, SemVer.v4_0.get(), + "emailpassword"); + + assertEquals(signInResponse.get("status").getAsString(), "OK"); + } + { + JsonObject responseBody = new JsonObject(); + responseBody.addProperty("email", "test2@example.com"); + responseBody.addProperty("password", "password"); + + JsonObject signInResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signin", responseBody, 1000, 1000, null, SemVer.v4_0.get(), + "emailpassword"); + + assertEquals(signInResponse.get("status").getAsString(), "OK"); + } + { + JsonObject responseBody = new JsonObject(); + responseBody.addProperty("email", "test3@example.com"); + responseBody.addProperty("password", "password"); + + JsonObject signInResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signin", responseBody, 1000, 1000, null, SemVer.v4_0.get(), + "emailpassword"); + + assertEquals(signInResponse.get("status").getAsString(), "OK"); + } + + { + JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://127.0.0.1:3567/ee/featureflag", + null, 1000, 1000, null, WebserverAPI.getLatestCDIVersion().get(), ""); + Assert.assertEquals("OK", response.get("status").getAsString()); + + JsonObject alStats = response.get("usageStats").getAsJsonObject().get("account_linking").getAsJsonObject(); + assertNotNull(alStats); + + assertEquals(3, alStats.entrySet().size()); + assertTrue(alStats.get("usesAccountLinking").getAsBoolean()); + assertEquals(1, alStats.get("totalUserCountWithMoreThanOneLoginMethod").getAsInt()); + + JsonArray maus = alStats.get("mauWithMoreThanOneLoginMethod").getAsJsonArray(); + assertEquals(30, maus.size()); + assertEquals(1, maus.get(0).getAsInt()); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testAccountLinkingStatsWithDifferentStorages() throws Exception { + String[] args = {"../"}; + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + FeatureFlag.getInstance(process.main).setLicenseKeyAndSyncFeatures(OPAQUE_KEY_WITH_MULTITENANCY_AND_ACCOUNTLINKING_FEATURE); + + { + JsonObject coreConfig = new JsonObject(); + StorageLayer.getStorage(process.getProcess()).modifyConfigToAddANewUserPoolForTesting(coreConfig, 1); + Multitenancy.addNewOrUpdateAppOrTenant( + process.getProcess(), + new TenantIdentifier(null, null, null), + new TenantConfig( + new TenantIdentifier(null, null, "t1"), + new EmailPasswordConfig(true), + new ThirdPartyConfig(true, null), + new PasswordlessConfig(true), + coreConfig + ) + ); + } + + { + // tenant 1 users + AuthRecipeUserInfo user1 = EmailPassword.signUp(process.getProcess(), "test1@example.com", "password"); + AuthRecipeUserInfo user2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "password"); + AuthRecipeUserInfo user3 = EmailPassword.signUp(process.getProcess(), "test3@example.com", "password"); + + AuthRecipe.createPrimaryUser(process.getProcess(), user1.getSupertokensUserId()); + AuthRecipe.linkAccounts(process.getProcess(), user2.getSupertokensUserId(), user1.getSupertokensUserId()); + AuthRecipe.createPrimaryUser(process.getProcess(), user3.getSupertokensUserId()); + } + + { + // tenant 2 users + TenantIdentifier t = new TenantIdentifier(null, null, "t1"); + TenantIdentifierWithStorage ts = t.withStorage(StorageLayer.getStorage(t, process.getProcess())); + + // tenant 1 users + AuthRecipeUserInfo user1 = EmailPassword.signUp(ts, process.getProcess(), "test1@example.com", "password"); + AuthRecipeUserInfo user2 = EmailPassword.signUp(ts, process.getProcess(), "test2@example.com", "password"); + AuthRecipeUserInfo user3 = EmailPassword.signUp(ts, process.getProcess(), "test3@example.com", "password"); + AuthRecipeUserInfo user4 = EmailPassword.signUp(ts, process.getProcess(), "test4@example.com", "password"); + AuthRecipeUserInfo user5 = EmailPassword.signUp(ts, process.getProcess(), "test5@example.com", "password"); + + AuthRecipe.createPrimaryUser(process.getProcess(), ts.toAppIdentifierWithStorage(), user1.getSupertokensUserId()); + AuthRecipe.linkAccounts(process.getProcess(), ts.toAppIdentifierWithStorage(), user2.getSupertokensUserId(), user1.getSupertokensUserId()); + AuthRecipe.createPrimaryUser(process.getProcess(), ts.toAppIdentifierWithStorage(), user3.getSupertokensUserId()); + AuthRecipe.linkAccounts(process.getProcess(), ts.toAppIdentifierWithStorage(), user4.getSupertokensUserId(), user3.getSupertokensUserId()); + } + + { // tenant 1 + { + JsonObject responseBody = new JsonObject(); + responseBody.addProperty("email", "test1@example.com"); + responseBody.addProperty("password", "password"); + + JsonObject signInResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signin", responseBody, 1000, 1000, null, SemVer.v4_0.get(), + "emailpassword"); + + assertEquals(signInResponse.get("status").getAsString(), "OK"); + } + { + JsonObject responseBody = new JsonObject(); + responseBody.addProperty("email", "test2@example.com"); + responseBody.addProperty("password", "password"); + + JsonObject signInResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signin", responseBody, 1000, 1000, null, SemVer.v4_0.get(), + "emailpassword"); + + assertEquals(signInResponse.get("status").getAsString(), "OK"); + } + { + JsonObject responseBody = new JsonObject(); + responseBody.addProperty("email", "test3@example.com"); + responseBody.addProperty("password", "password"); + + JsonObject signInResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/recipe/signin", responseBody, 1000, 1000, null, SemVer.v4_0.get(), + "emailpassword"); + + assertEquals(signInResponse.get("status").getAsString(), "OK"); + } + } + + { // tenant 2 + { + JsonObject responseBody = new JsonObject(); + responseBody.addProperty("email", "test1@example.com"); + responseBody.addProperty("password", "password"); + + JsonObject signInResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/t1/recipe/signin", responseBody, 1000, 1000, null, SemVer.v4_0.get(), + "emailpassword"); + + assertEquals(signInResponse.get("status").getAsString(), "OK"); + } + { + JsonObject responseBody = new JsonObject(); + responseBody.addProperty("email", "test2@example.com"); + responseBody.addProperty("password", "password"); + + JsonObject signInResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/t1/recipe/signin", responseBody, 1000, 1000, null, SemVer.v4_0.get(), + "emailpassword"); + + assertEquals(signInResponse.get("status").getAsString(), "OK"); + } + { + JsonObject responseBody = new JsonObject(); + responseBody.addProperty("email", "test3@example.com"); + responseBody.addProperty("password", "password"); + + JsonObject signInResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/t1/recipe/signin", responseBody, 1000, 1000, null, SemVer.v4_0.get(), + "emailpassword"); + + assertEquals(signInResponse.get("status").getAsString(), "OK"); + } + { + JsonObject responseBody = new JsonObject(); + responseBody.addProperty("email", "test4@example.com"); + responseBody.addProperty("password", "password"); + + JsonObject signInResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/t1/recipe/signin", responseBody, 1000, 1000, null, SemVer.v4_0.get(), + "emailpassword"); + + assertEquals(signInResponse.get("status").getAsString(), "OK"); + } + { + JsonObject responseBody = new JsonObject(); + responseBody.addProperty("email", "test5@example.com"); + responseBody.addProperty("password", "password"); + + JsonObject signInResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/t1/recipe/signin", responseBody, 1000, 1000, null, SemVer.v4_0.get(), + "emailpassword"); + + assertEquals(signInResponse.get("status").getAsString(), "OK"); + } + } + + + { + JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://127.0.0.1:3567/ee/featureflag", + null, 1000, 1000, null, WebserverAPI.getLatestCDIVersion().get(), ""); + Assert.assertEquals("OK", response.get("status").getAsString()); + + JsonObject alStats = response.get("usageStats").getAsJsonObject().get("account_linking").getAsJsonObject(); + assertNotNull(alStats); + + assertEquals(3, alStats.entrySet().size()); + assertTrue(alStats.get("usesAccountLinking").getAsBoolean()); + assertEquals(3, alStats.get("totalUserCountWithMoreThanOneLoginMethod").getAsInt()); + + JsonArray maus = alStats.get("mauWithMoreThanOneLoginMethod").getAsJsonArray(); + assertEquals(30, maus.size()); + assertEquals(3, maus.get(0).getAsInt()); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } } From 6e4237c9fced7cdaba30efb50966feb981478a50 Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Mon, 11 Sep 2023 11:23:08 +0530 Subject: [PATCH 107/131] Merge latest 6.0.12 (#795) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: add exp and iat to JWT payloads without scientific notation (#765) * adding dev-v6.0.9 tag to this commit to ensure building * fix: fix handling of b64 and b64url encoded access tokens (#767) * adding dev-v6.0.10 tag to this commit to ensure building * Update release.md * Update release.md * fix: ee featureflag cron job (#778) * fix: ee featureflag cron job * fix: test * fix: tests * fix: tests * adding dev-v6.0.11 tag to this commit to ensure building * fix: test (#779) * adding dev-v6.0.11 tag to this commit to ensure building * fix: test (#780) * fix: test * fix: test * adding dev-v6.0.11 tag to this commit to ensure building * fix: test (#781) * adding dev-v6.0.11 tag to this commit to ensure building * Update README.md (#783) Corrected all the grammatical errors in the README file. * fix: session concurrency issue (#785) * adding dev-v6.0.12 tag to this commit to ensure building * fix: fixing ee folder issue when empty database at startup (#786) * fix: fixing ee folder issue when empty database at startup * fix: changelog * adding dev-v6.0.12 tag to this commit to ensure building * fix: test (#787) * fix: fixing ee folder issue when empty database at startup * fix: changelog * fix: test * adding dev-v6.0.12 tag to this commit to ensure building * bug fixes * adding dev-v6.0.12 tag to this commit to ensure building * fix: remove print --------- Co-authored-by: Mihály Lengyel Co-authored-by: rishabhpoddar Co-authored-by: Abhisar Yadav <112550486+abhisar-yadav@users.noreply.github.com> --- CHANGELOG.md | 5 + README.md | 38 +-- build.gradle | 2 +- .../io/supertokens/ee/test/CronjobTest.java | 1 + .../java/io/supertokens/ee/test/EETest.java | 1 + .../ee/test/api/DeleteLicenseKeyAPITest.java | 1 + .../ee/test/api/GetFeatureFlagAPITest.java | 1 + .../ee/test/api/GetLicenseKeyAPITest.java | 1 + .../ee/test/api/SetLicenseKeyAPITest.java | 1 + jar/core-6.0.11.jar | Bin 658527 -> 0 bytes .../multitenancy/Multitenancy.java | 23 +- .../multitenancy/MultitenancyHelper.java | 28 +- .../api/multitenancy/BaseCreateOrUpdate.java | 10 +- .../CreateOrUpdateThirdPartyConfigAPI.java | 14 +- .../thirdparty/RemoveThirdPartyConfigAPI.java | 4 +- .../io/supertokens/test/FeatureFlagTest.java | 306 ------------------ .../test/session/AccessTokenTest.java | 1 - 17 files changed, 83 insertions(+), 354 deletions(-) delete mode 100644 jar/core-6.0.11.jar diff --git a/CHANGELOG.md b/CHANGELOG.md index ba7efbbff..e75a73162 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -76,6 +76,11 @@ ALTER TABLE emailpassword_pswd_reset_tokens ADD CONSTRAINT emailpassword_pswd_re ALTER TABLE emailpassword_pswd_reset_tokens ADD COLUMN email VARCHAR(256); ``` +## [6.0.12] - 2023-09-04 + +- Fixes randomly occurring `serialization error for concurrent update` in `verifySession` API +- Fixes `MISSING_EE_FOLDER_ERROR` error when the core starts up with an empty database + ## [6.0.11] - 2023-08-16 - Fixed feature flag cron job diff --git a/README.md b/README.md index da77804d8..bf258ea18 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Supertokens architecture is optimized to add secure authentication for your user **Three building blocks of SuperTokens architecture** 1. Frontend SDK: Manages session tokens and renders login UI widgets -2. Backend SDK: Provides APIs for sign-up, sign-in, signout, session refreshing etc. Your Frontend will talk to these APIs +2. Backend SDK: Provides APIs for sign-up, sign-in, signout, session refreshing, etc. Your Frontend will talk to these APIs 3. SuperTokens Core: The HTTP service for the core auth logic and database operations. This service is used by the Backend SDK ## Supports multiple auth strategies @@ -49,29 +49,29 @@ Guides to setup different recipes ### If you like our project, please :star2: this repository! For feedback, feel free to join our [Discord](https://supertokens.io/discord), or create an issue on this repo ## 🚀 What is SuperTokens? -SuperTokens is an open core alternative to proprietary login providers like Auth0 or AWS Cognito. We are +SuperTokens is an open-core alternative to proprietary login providers like Auth0 or AWS Cognito. We are different because we offer: - Open source: SuperTokens can be used for free, forever, with no limits on the number of users. - An on-premises deployment so that you control 100% of your user data, using your own database. -- An end to end solution with login, sign ups, user and session management, without all the complexities of OAuth protocols. +- An end-to-end solution with login, sign-ups, user and session management, without all the complexities of OAuth protocols. - Ease of implementation and higher security. - Extensibility: Anyone can contribute and make SuperTokens better! ### Philosophy -Authentication directly affects UX, dev experience and security of any app. We believe that - current solutions are unable to optimise for all three "pillars", leading to a large number of - applications hand rolling their own auth. This not only leads to security issues, but is also a massive +Authentication directly affects the UX, dev experience, and security of any app. We believe that + current solutions cannot optimize for all three "pillars", leading to many + applications hand-rolling their own auth. This not only leads to security issues but is also a massive time drain. We want to change that - we believe the only way is to provide a solution that has the right level of - abstraction, gives you maximum control, is secure, and is simple to use - just like if you build it yourself, - from scratch (minus the time to learn, build and maintain). + abstraction gives you maximum control, is secure, and is simple to use - just like if you build it yourself, + from scratch (minus the time to learn, build, and maintain). -We also believe in the principle of least vendor lockin. Your having full control of your user's data means that you can switch away from SuperTokens without forcing your existing users to logout, reset their passwords or in the worst case, sign up again. +We also believe in the principle of least vendor lock-in. Your having full control of your user's data means that you can switch away from SuperTokens without forcing your existing users to logout, reset their passwords, or in the worst case, sign up again. ### Features - [Click here](https://thirdpartyemailpassword.demo.supertokens.io/) to see the demo app. - Please visit [our website](https://supertokens.io/pricing) to see the list of features. -- We want to make features as decoupled as possible. This means, you can use SuperTokens for just login, or just session management, or both. In fact, we also offer session management integrations with other login providers like Auth0. +- We want to make features as decoupled as possible. This means you can use SuperTokens for just login, or just session management, or both. In fact, we also offer session management integrations with other login providers like Auth0. ### Documentation @@ -85,15 +85,15 @@ Please find an [architecture diagram here](https://supertokens.io/docs/community **For more information, please visit our [GitHub wiki section](https://github.com/supertokens/supertokens-core/wiki/SuperTokens-Architecture).** ## ☕ Why Java? -- ✅ Whilst running Java can seem difficult, we provide the JDK along with the binary / docker image when distributing it. This makes running SuperTokens just like running any other http microservice. -- ✅ Java has a very mature ecosystem. This implies that third party libraries have been battle tested. +- ✅ Whilst running Java can seem difficult, we provide the JDK along with the binary/docker image when distributing it. This makes running SuperTokens just like running any other HTTP microservice. +- ✅ Java has a very mature ecosystem. This implies that third-party libraries have been battle-tested. - ✅ Java's strong type system ensures fewer bugs and easier maintainability. This is especially important when many people are expected to work on the same project. -- ✅ Our team is most comfortable with Java and hiring for great Java developers is relatively easy as well. +- ✅ Our team is most comfortable with Java and hiring great Java developers is relatively easy as well. - ✅ One of the biggest criticisms of Java is memory usage. We have three solutions to this: - - The most frequent auth related operation is session verification - this happens within the backend SDK (node, python, Go) without contacting the Java core. Therefore, a single instance of the core can handle several 10s of thousands of users fairly easily. - - We have carefully chosen our dependencies. For eg: we use an embedded tomcat server instead of a higher level web framework. - - We also plan on using [GraalVM](https://www.graalvm.org/) in the future and this can reduce memory usage down by 95%! -- ✅ If you require any modifications to the auth APIs, those would need to be done on the backend SDK level (for example Node, Golang, Python..). So you’d rarely need to directly modify / work with the Java code in this repo. + - The most frequent auth-related operation is session verification - this happens within the backend SDK (node, python, Go) without contacting the Java core. Therefore, a single instance of the core can handle several 10s of thousands of users fairly easily. + - We have carefully chosen our dependencies. For eg: we use an embedded tomcat server instead of a higher-level web framework. + - We also plan on using [GraalVM](https://www.graalvm.org/) in the future and this can reduce memory usage by 95%! +- ✅ If you require any modifications to the auth APIs, those would need to be done on the backend SDK level (for example Node, Golang, Python..). So you’d rarely need to directly modify/work with the Java code in this repo. ## 🔥 SuperTokens vs others Please find a detailed comparison chart [on our website](https://supertokens.io/pricing#comparison-chart) @@ -218,7 +218,7 @@ Portions of this software are licensed as follows: * All content that resides under the "ee/" directory of this repository, if that directory exists, is licensed under the license defined in "ee/LICENSE.md". -* All third party components incorporated into the SuperTokens Software are licensed under the original license provided +* All third-party components incorporated into the SuperTokens Software are licensed under the original license provided by the owner of the applicable component. -* Content outside of the above mentioned directories or restrictions above is available under the "Apache 2.0" +* Content outside of the above-mentioned directories or restrictions above is available under the "Apache 2.0" license as defined in the level "LICENSE.md" file diff --git a/build.gradle b/build.gradle index 83265aeea..9d329dff7 100644 --- a/build.gradle +++ b/build.gradle @@ -19,7 +19,7 @@ compileTestJava { options.encoding = "UTF-8" } // } //} -version = "6.0.11" +version = "6.0.12" repositories { diff --git a/ee/src/test/java/io/supertokens/ee/test/CronjobTest.java b/ee/src/test/java/io/supertokens/ee/test/CronjobTest.java index 79102b663..27b03be0e 100644 --- a/ee/src/test/java/io/supertokens/ee/test/CronjobTest.java +++ b/ee/src/test/java/io/supertokens/ee/test/CronjobTest.java @@ -29,6 +29,7 @@ public static void afterTesting() { @Before public void beforeEach() { Utils.reset(); + FeatureFlag.clearURLClassLoader(); } diff --git a/ee/src/test/java/io/supertokens/ee/test/EETest.java b/ee/src/test/java/io/supertokens/ee/test/EETest.java index 0a8c9303d..3a896b253 100644 --- a/ee/src/test/java/io/supertokens/ee/test/EETest.java +++ b/ee/src/test/java/io/supertokens/ee/test/EETest.java @@ -81,6 +81,7 @@ public static void afterTesting() { @Before public void beforeEach() { Utils.reset(); + FeatureFlag.clearURLClassLoader(); } @Rule diff --git a/ee/src/test/java/io/supertokens/ee/test/api/DeleteLicenseKeyAPITest.java b/ee/src/test/java/io/supertokens/ee/test/api/DeleteLicenseKeyAPITest.java index 3a61bde05..c45cbf70b 100644 --- a/ee/src/test/java/io/supertokens/ee/test/api/DeleteLicenseKeyAPITest.java +++ b/ee/src/test/java/io/supertokens/ee/test/api/DeleteLicenseKeyAPITest.java @@ -33,6 +33,7 @@ public static void afterTesting() { @Before public void beforeEach() { Utils.reset(); + FeatureFlag.clearURLClassLoader(); } @Test diff --git a/ee/src/test/java/io/supertokens/ee/test/api/GetFeatureFlagAPITest.java b/ee/src/test/java/io/supertokens/ee/test/api/GetFeatureFlagAPITest.java index d2855867a..d2932f619 100644 --- a/ee/src/test/java/io/supertokens/ee/test/api/GetFeatureFlagAPITest.java +++ b/ee/src/test/java/io/supertokens/ee/test/api/GetFeatureFlagAPITest.java @@ -28,6 +28,7 @@ public static void afterTesting() { @Before public void beforeEach() { Utils.reset(); + FeatureFlag.clearURLClassLoader(); } @Test diff --git a/ee/src/test/java/io/supertokens/ee/test/api/GetLicenseKeyAPITest.java b/ee/src/test/java/io/supertokens/ee/test/api/GetLicenseKeyAPITest.java index 5a1dd82a7..e963404ef 100644 --- a/ee/src/test/java/io/supertokens/ee/test/api/GetLicenseKeyAPITest.java +++ b/ee/src/test/java/io/supertokens/ee/test/api/GetLicenseKeyAPITest.java @@ -28,6 +28,7 @@ public static void afterTesting() { @Before public void beforeEach() { Utils.reset(); + FeatureFlag.clearURLClassLoader(); } @Test diff --git a/ee/src/test/java/io/supertokens/ee/test/api/SetLicenseKeyAPITest.java b/ee/src/test/java/io/supertokens/ee/test/api/SetLicenseKeyAPITest.java index 9db322c52..847d8a58a 100644 --- a/ee/src/test/java/io/supertokens/ee/test/api/SetLicenseKeyAPITest.java +++ b/ee/src/test/java/io/supertokens/ee/test/api/SetLicenseKeyAPITest.java @@ -31,6 +31,7 @@ public static void afterTesting() { @Before public void beforeEach() { Utils.reset(); + FeatureFlag.clearURLClassLoader(); } @Test diff --git a/jar/core-6.0.11.jar b/jar/core-6.0.11.jar deleted file mode 100644 index 2704fcaef5d5bad97612a72005f23d0cb32f877a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 658527 zcmbTdbC9ROvNqV$wr$(CZM%D>ZQHhO+qV1Hwry+LoYv0Sv-^GboO9!T8(UHJ{_#el zqOzhYpUf;p84yrtARtIcptKGG4WPdb_#dCY4eIZZ6H^tUlad!_00C0`7s5tA;A@<} z1XzCu>VGAa6Oxw_7gJHCmlJ=Go0^oBp`)LNlcA%YnVM}D(N) ztZ|`A&-L2P;$TS1FcuD6zM+ZF;BdR{IsN#4yWx7#-z#W?-vP;w_2{whry3vwz#)k2 zxRz+#g(Taj+1ZS`wy;d>X(7Wx6v<#^n;hHd8x!dSYK(x6!J2`y{Ulu}x=^wS!;XFR z6mF>f)lP(~_EaNXc5xXSQaDhKtv;CJ)2sf4Zv$DrZ*Fg;xXb}wwE3YVr_rezI<9Bw zEF!wB(;Sc8pgl`XV~0B+8V!E#9navXKZbiIGu7NXZ(Wt;k9#CNmc#`0M$(mg-VKYA zBIwW2YWZOW-eOIf7gkbI%Y+2tr-~5TrQDp{CY3%om&pX_qEWVi0%vKk8>y*4xmF>% z%$ZJJQWkcii*>UvMpPlF@|}M&3+g(^LZ{rS zD>WWb(5Q4SVI`(|^oW8ZH5t=eatZp_?LKMfhwXww(F)pCH3CGc)%Lc~guH}Aw9p8Y zq3VF_seNR-jXcssg7vztoV(^mCCz^L!gvsoJ0e$&_=uOHOa%|J+GBYQ4B!N5e0)yc z!^lyuJgGk1K)LV@Hs(xKk`(4nAFIreDQL@}xrM4Ao%#ZT${h-jiIm+KvGPc{P9&4G zUJg*knzZK@p?3+VBD&-W1;)f7(=SD(-fVZl0ay`ZkiGj*ZR z*wF5gf&p!NW)mg3RPEuxjIj5I-Jd5*V7jnH(U1*_X5);4)(})7*=kGa3PjSDxg6P( zp70)CH$NE3WSKu`IH284MsUnEi+F+s`7R1(Jh|eRmr)D!W6jDM>is3+E}^-J#s2uK z*iw$ru%pv`IyRNRgH_F@8+yH4jW7u`|q*4d?sZ~ zKRZ2IA3xjTPVBFRBWl$>ZQvbm-0o}*`1e4VXHL)8t@aoV`Edvirl-3B62`jzFj$QT zEU`>3Wl^W2?Kx2+3@&AjfID4UBMyF{dp4leFyWx22)~acDUeY&sx~hyKNkkf!qX)oPyrZ|g&)pl>!+*}3x^{t$b@ssA>(8)Y+oOV;3I0QmDU(Fl z(hiQ~XX+i=))*s8Ldz5wZ~A)HXlME^y(7j!6?1QB7FMyoufJATNHu7@b{ zduO!HD|DUo##7zgS#wVexy>5&iuaH_1i>pDme-wJC^i|QWO<|7O( ztne{Ptxnqsm?M<&2BlyUm{J^^MQDzb~fup`2ZhmH}=bO#Zz$jj2@2SI{r2jHBM z@r~2-*0*3|NIaM#ao__rii_8kn5wb!aYZdGj$tLEJiK&=Mt2M1)BtqK0m#BK@rP5`qVE}E_2<$02j3T!;)T;Sa zT$qrd-)ERaD#TCZg~{lCmOvRv%Yu?3(ryxhRB~SN)HMdQ?|L2&+|S!Hb5)9o67jy_ zoMsdVvY+WOjIwMZjIRqNkH#ZMZs%aD?aTzb9`nAFq|`x^D+@`O4_`l9psw%Ts1rPs z8jl0}{DV)$cO;^XW06ySMxEzeR1)r%9%)|D?ddCekYN-il-EA8R?ORI6DD2`>~_EM z=qKMIU@`2QOb9`R1?}tK16C=4cUJlA|7nIW*i)oJ#DG8k-@n(-QP z_m8>Fe_8#E^R)hnO&&&|!~eK>EKPYtvAlG=kG7!@130%f?idHh1~oqC6wI|I0E z4#NK_;t-Kg!qHLkJ@(3yTdW}QR@P?xf!=2nk7Hin%Udi;Y$<9OZE)Pa6(BP;uINRp zlE4}!e`1iHmQYKYI?QPGTHCzH!EFT3v=8*n1`_>!3FFxl!~b@cGvC!^D6oe2xYyry zwoHO8W6#qwAr7$>V7KVS{!L?bF%0oCAl4Tkh4xOUrmFsAM9g+&<9dxILcZYP#GGw7 z=sn+%O8A^b{Kmb16gS;Rw7njqVbA@)kI_!)q zN_;B@eF{x}+lbqR<;lV6lm^FSUz3`0h+!;>r>0PnHOAWX$WJMRp!0Q9GXNy;y#@gs80UexJks;RPrO1t8vR48l z0AQ_}e!$QoiMuM{15)FttZ~yGW3c2l> z%BSXU(Z=Nmb9y~ZzJW8F`9vdam&NLA*s%KJ!ql#ioV-lejdBPAu8<&WWV(^3Tua)ML@nlx2a`lK=GvfxuZ`D)dF?fWs*{~;8S|Ip*Kq91sM^u! z+W3FFymSG!ZBGt*w85vd(E}b1RU5L0q6w-RLS)2}!ujx!z!U*@F zOu@LQ+OQH{|H)>O;I?q>j%8AW57iZiNn)BX`45;8G}hQfraS=Q-3+EIb^Qgk6b$`*Vqn0a;0y!*AA)x>-wT} zF$Xg#D#myu8l5C9F4@#;Ie@euv^p|)8;Lb`_sWP2F_pctqX*1)Hys*76XE=2knujz z!W}?W0KXvmo02Y8;;d^F;toZDP~>gAfC=liY5Tw|eG6H-^i*T%B|_;aHJA?%347o- zpQ&jYwwKhx)K#Mcf@!fFom)Ct>(~SxS@WR3fYN2s(igYS1N3p0p8>x9FmSgx*9jcW zsBF`5I(Ae)HqFhdrbpjKs?kY5m*8S4jjUa!APUSU#-l}`%GC6&3=(jTJ4<~P0JSQ2 zD3nSfo(G8IjV*+0M73VobIB@VIX5bpwMfJfX%ag*>R@WTIQU3+DJ!DVmAoSI7@EfGJ{R#;BqjLw={ZrjFzdZ$1iW zq_6iH6Dg{bP1YF{dH6um=SKDwE%RuB{7EebcCAL@CB*H#@z>i{ZzSMk`?n}uG*qv_EEv&QNJNi8cFQy zIheK+j3yUdL}7AVPk}?I>xW$+9(2A^Rd19qAW(XljG^h z57Xu>+mrY7>GSE>Xf^#UgN4&m((n!cw8vIiCA_G?9^fsqtrlIYy_yA%HuQ6yVk7 z&#zg(Zn8jc#hmqz6Pyx@I+9E8=Oz2I%46$_3R`kL-z&bwu^WC&o;^rg7us>aC~J>x zQ=PociIiM{(%kz-QTCyE^ob?!Mt1L#PDrifHiT1$nfc@1DS|brHJN2_3dwT5|Mx5=?nWl|&K7q64M;6p_);YL0R*)CcgXx7xz%j{B3OGn zM`sgbX$@5s3o}~_TQebRGdo8MXLB2||5wdF;b={Kk36stYRK%d9hwqib3>DeQMRU$ zEb6>uIGjMLssA{0`&9h8){})dFb)k03_*VwvA((-EuHnu6qnO=+W4ntcMpj9udZ;| z#vhJkM@ocCrT6-gl9`Il_!5@aEo{Mq4eT3vh>cG@LjGvNGCD{UPRHFnD&**k5DZJR zg0z1^2~Jl$*lc$nJGogmg3FZ5bDopd863&f=D3#uZ zGkm!#cll8q<#^PC9VKvFa;8Ejo{ks~iZ>caP9#ekzTusG3ylU%PJjAt5~6moJCL{( zq&)@+%;3?xMe2ceTl&`rsiD5<8l?$E*fs0&UxHH*KMDGNqdg|7PgIAnRu>Fv|Dpbc zvW%)Jqob4eucn7Cpb%vK8wF{^1_Gk}?=A7a*IeAi*69B>=s%ZwSQE+{bs6hhz;Wu@ z6qtxKprjQOHa$r!7zhf?D7KO4hcOBzYGu;40gJJlX-)=NHJVLJu3AM8ar1_H&6-NH zGm5NEe#^$jrnlq37n+Z`JGJ5hl(LS&&8F-T?ic z^k|Y&kVs^l-=yyVipq1Ysl+btq74gr)uPk1&TOozcAb7;pkAGYSDm#mvz00TB9#I; z5}a+jYO}IK#p4Hf@wUVY&;KU+AwEKrzTT4%-WhddluoxPXuI@G)`IK2!CcgDd zZpHewFq5fFI!O_z6IQA`Vz1rYv-9=YorO@&8Bby7?h5R-Vft?lwPE#hJ%lo2_wvrm zY&5&v_GX>=N8Ah7x0regbIgJG3n=Z->+@n6;BnlgoG!~o3bHAq+i6jiC;y)@kX3wq z-V-~9iT7D>U4Zb5fafJ0sAJfuks}WgBDV5iZ}x&@a>oMLhmJ6GM%uAQMTI^KSZW_4 zwRU47ee!)^EQV1U%oRDs73o()!5A&9+V$d*O7D5eAx0avIb>+;Qqz~Pais-cGE}v; zE0eWU5>iWYVgZut;FA#8sHSLJrl5I7E)J0FVc6eXhBP2s`rFb{m?+t)DL)Lz?sYW- zOOWYPm0~g-JdUHZTCq*jFXJ-FE9QzqmTl=!7ibdTK6hai{@o3?54g7YVR(nH@jUCNv||U~QEvqkyLyds5NVKx~-wpsfDx zZA~RUy;L>ly9+Nk!yara((07?VAIeLlvD{DI?=BK4QLN!K6*vYF!)7CiZIwuElfEw z0h#@X{8$E=3KMzhY>f%cI5=3aO>Y`?Hs)%9KA=;=wy}2Hn&f(8Dh0Cut1yGoam;50 z%Y!AunfYH(zfsSLUpo|danP#zbRyd4MpTM1aXi#sn0Z}tFI-|G2%lN#06C|oy0Hh- zHXEcxFx$rL9z|r9FATHxfP{yZ2VAsccS>Chp@L#8&xP(RyftC4aQ(Xr-;ijVTc#tG zRAnTpsu>F_26K^mk&!Aci*ZrlO3~JN93m4PG#R=<`fl+-TP&|z?a%`Uc#?qx%HM1& z#_ayqeq?5rjb*s)k?v~^;UGS&gpjz9bH4;(I2$m8pgx&duZ%Q3=pmI`6N9>k8dz~4 zAcRS!nrJbcF_DRxYeS{P&aZ?dA3vc}O7C^y>@ZG^Cd2RbloOC4t=Zse?WRJ6aN`k^ z9N_dvamF~y5*M1aWUM_gD=R6ub9UP9(?YdeJXB%8%DcPl5m%+!^+zu6z(ZlmwEOpP zi#6Ai{R^4Q+HzCpa7?<9IGEZwyJ9Md-G6HH5k-{^$PUsqFXHH|h>vvHv)CT$q;FFB zR`I;K9ur>2J5XF3#&-;f!dMba6z;ntrl2NvH&jAWR%;yW?Fk9Vaz+Y>?|g0 zBf!O5aazQT50Jn*apGIFV@8Afd_j|&ENTD31SHBL_9CLt}q zV10Kdq)Vnl87^*S`H!Vu;YEJCYtt0|;nVjKkO&vNoi_sie8I#Xf$$EstY8;GI448h zS65%pU)f=Ma!AEJ&8J>6dejc~I_$8)84r*?afmmV_&g;wld2@mJnKUBwk$MIrntbE z?qe%9|4`}AhT;M0Rlsc)^CC;>+i){19m=#vJcX32s+aVsu$exn$l`CQzdI&J zmrBW2J8aSCICrZtHmo^)8@p4#DwpET^^!smTvXlvc9k`>&ufud9C^F)Nw5r`4bSv# z5jh-V7*m>0yBkK;($~WXFwvq`%j#2+NvH3JeQ*?{-Tw(YY5JVwZ%E6PXdk-7r<07N z$2_AbMKPvz0HgQ=^P5se`V{6Qrjm|%zkstSiNMoa!#Y)M)on-*!oK%|01-K<+D8QzRc$zXS z)9G<8!gJ!1tgib|-l5_{=n4l^1x{rAQ9gkZWc*=eA!I49ywk@K^BA{cM^W=>w;$+TxQ?INCfVj>3 zW*s_Z){??*j6eo{ihe8XK@627ocM+lka2xu3LvfhMyF(EI-s0ECW$r7#dJV@7!EJe zBoxOLi+H>vH=z~8@s+mADc;2Xh!z(smJ?IPmhj@g??Fb-2~ZgvJZ&Hq_=Yu(fZe}M zLu`_Ll?egqE(UV*9}w6=SyrssWD1-iH`6;m+2Cv)nRf!_d8jX{eVkAxV|(nv8!aHu z#m*h-{<9FkIViDLC?tZr0{P6*u|+S0Hrm3oRolsFo4-kEyQ8JV8Le~Zu5!WjlNbLW z+8mC~52lA`wU;l}%lyc7^f9n5}CfwM6}35geEpRD*ZqOVwy zFw;T#!w^rf*`3wFYJ}|vcwfoXn;y3w*Y2tLV|4hJmf0Nr{t%XR{26BYCRrcG_&3DC zg4hQ#)EDD+$K%Szg4^ib4nR}Q4H^M|RHTK(-OtVN!vLFLQ`ZlB6rreapYJxnoFx9|jYHeA@m1U;3Mb^h2Lo7Nd0 ziP~=kTrtdVIJf#y7pmVabHXjH6w8PCxV<_ENYKPD!Fpa!T-T1C!X|%^ppEELeA3l6%H>Y0wwmV=5<6e@? zg{B0zL}@lKV|7nC+-DA+wXg3dT$GeyZAcbUEt82-&wpA<0RIM0B!?d`#*CMrd7v3o zn+dJ&*$TYH-KC3=I;Ya8eVuT4r;nJ1it@t8T0_i&tr(;iPiVes>2CF06Qzhn1R=Aw zt;$d{pz)A{PBUnp15#=DV6&KAlWt#impYp0EityQZO+iK=FC$d4lx3c%{mk20ECP( zvFleXF>|$$FST>r)M+*@g##E<&jlzKLFF^g=2_#AREva>%Wi`MDCDw7RA5OUeiRKw zBKX)U>31@Rj5MFVS1`|5);sGd1w$?J{*lcFu)u~{x-;XJAzn$* z8%U1!9!yA;O_kKBfy=^LrUb(`#(G6cJCBLN17Im8yhY{)kTo<)w97KeAd}V@%$|wA z`f^H_W)D1281}>tT4wlw_0fcArWhY-Us0zTH4}4n8;06#cp|OZ!qx14T^8Zi*`Az? z+3Ysn!wydkTogk+k%hXC)ie>>FrB7p1b2-^q+WBD4b_lcOZUXGA6n`m&pyP+K3f?S z=GMM=&uj@#cdI&_ z{DOeBgYP*reJ@q*Z^#El^B3dV=)mZ&nK_d^a1X@fh#>(fCP6KMmpom@TYA9xr`mZZ zOiD!o%Hv6ZxU_)bFZT#_8`7WT84GoY_9%O6e#ol@sJMd|{jk4K>VLeN`w4O?X}P>|CFks|KWquZ-UoA) zRUmAfcMd_t{UJ5v>Ar`LtTP7h9W->c2QP1Iah2}|qZh<6j)u*{*#t)Ng>}&IdyPFO z0*tdK81w@OL;!h4LGsWnn}RF&XJv0jGmre*M!VQe=0egFid2WUkoytPPJ_Wk^)b3x8pT8&0|4OW5{^#*y*xjIQC~gd>Lj?cyCxt6(_VkRu0~A55yLUJr>QDHu!Snw>8?GJp&ZFM%mz({VT- zoo>O!^x|_hNWXdwp8CE+rb@Szijp5$_!=be3hE8Y4>o+6aYr`Z5YrT;xA54!bLS<| z_wr}-XYUufAaGt-eW<~qkgenR`z&e*zzItSo#h#{ZHv)TsjNUhEEG{hbj_hn^(gky zH^vNaliMV&^NwHJ7}hPYpjSsz_?eNETe&P+io_OMoj*4w#VN=jtjjpd13wEKy{#3VCezq|^JJVFZRlRE6bA;14Yez;3 zwULnsESS%MAp^9bRjQ+=Sla%?9(ra)O2n|XIvE{uY%GV)a_ncv(=gz{O3kxdOQ7bu z$4OyTG=k=R>GV#*G0BF@|Z|JfEUX2GJr&R{uQ>r zzdTZm-3q-qX##$M64Q>}#imGaDaduW(-U*EueLW&Ie_& zZ518@d1AFDr=f0QqrAE2z5FsK+?}dc`2JbQ1Jl3XYbZSvbhc_QIcl(_UDA`x!l+w8 zEUk~pOsWtONQh$1VwTs+T-d+JD>HKwPJX%fx%`1E4h@2ByT}x71Uvl@yPHho5n$lc zdc|8^GZrMpEl1!mA%wS+eamk@?V($qJ z6_&_g+yFr9uq>~Kr;OA(tAbNpTc=5Ip@6W%w6yAXZ~hHgqE7}J+QPEFA5j9Hubfyw zMMmqZQ^7cq6U}7lIBa7E=d@-Y?1{i`b=)p}e^l73y+0=8kKMBrZ@Ylqbe^1o0A_qG zOsJX&!dPU|yp48KH8F+%r|d%}s@r5NQ3RSma|QQ7z#j>Ze6?yly`X(c>jGK)?0t9o zO2&N1hlcNoMq@s?NiBm@Tw<&#bibeavOChNiFVWEOe;q<(`GA!>hz>URL|R5vK*Xa zQQo3oHN?Un8k9#kCG?&D;@GH_o3Ham=^}va7$=o9v6;0I1w@>QbAJDeMN2<9P&zrZ zZ~oY()_?lz07IZW$%6Q@0g3uGXN?51e>>FPZ;mRMAZIWuzu^`n5{Q5`Um zv9+Ds_&Y-R?%<^tEV>d|ov6=hXJzkmT{W3M3Oid;S9J^Th|!LOf;}v zOp`R+T$D_R^m3NjbD^<)(G0tGlFE{R(x1pr4>ugqX!_w@;aG@wNYi|Y<^}CWQE|X!&Je;aUf>jU) zJN)v8wwSjxZ+KYu59VrmO;W8I{1Fn-Ps*C7^_)@jQwN1h6w*T}UbM4j$-YF~^K5Yd zhdH)Ebk69X)MvXn?gcEKI04zi#+$i6d0M2Gywh3Zsgq|r-RmNY*ORx5{W?^z*7%{} zVF6&6x#X}OydpQy0R0UmRx7m7)MpE8IMmuZ)DdG%RAa!7}&Rn9pOjW zQSR?3JPgE7XopY+hfj&}Peaoi9#ZhYr!R{~zU|23vmXBzSjLZBLFy_jDZu5&If9G&$DUUOlWOrkGLU@c+(DB0_fC3n1E zqPHZ@EFUcTglk<5a0~|wq+SGX_jS#cBv8}tPz)-n`ghHZ>fIMw6Svqt=EN}B>V!XX z7yE|BR~yDTsH?gulp6>TBjt+hZ9h9toSODF3+Qn;kg7qk=3-*j6kfhTntp5vj(p;M zLk=DT;03JhfCmEVBLV^<{~vBF++kr8oTr? z7vKd0JRl`=Y*R~2Febn$D#c<+u7+vbr%P>)b4Hw9VBQWq6A~Wps=jiGt7_a6OcH{J zwem(|QxwsP$3yPN@=g?xFIk6#{r1@Rqj{VMFOW*+~72RQn6+dX^Q|+gX`;gc!!;~FXm9L84FBZEt z3{3}H{?M^haQ>__VoHTf)|@eAS!d>@C3H=G=ubV@jKu-1#bS2Ns&OT@Y~Q4HizgXP z*?^`+z~IiWw5JZIR<$ahSBl~exGXKVF5B8q*4ed1P<;5&IjwA4KPdL;Y66Zy-Bx62 z#RZz0!or>gb{_Y0{tkX?$S7)^nVNlVJeN1E4fX74MKzwutLbmJN^a#QCKFI=LJ`;w zR>fMqGC$@#(4ul=Ex)0(=s;Y%&-^M~p_0W7oq-BB+@r?k;>wJG|6OCgax0J5Rv?yt zf!r*^;pWy-8z<^X!bitI zb}p3Zw9OCf`xN~+~O2V&mgnExRKPN&tZvY4J8kmCl_^ol#I?17(Ci$ zrggc-lFbya9*u=9-Ad<6oU3QU35B-OPLwjr1I;FE-II@)-vkrhmoi_n96b~TG@B+y z{`#^Fs3NCi$C~*yL>iWJn_7s`S2-{@A6DL9?X8;RFJgvO>wLduzf8(xlkrxn{1T?TmHGQS0l=`A{?ylNMwN}4)Ag&6~d); zvN<>z%IvS%51i^Eed+$)%oD) z&%Fb0!9EIi?hpl7s{E*PX;pRL32J1m-HqiAdgA)>c8bR0i8&+g zzomAK94z9nQ8j{&W73$&kYK67yDnJZYCNpGNpLhKwaNk+*sjx>@GT|jDBSXf(Jg#d zZe@If_LCwKF7C|a@|T<>2Jqd-H{*{&ZeUKeKgh*kc6r3Fsyps}Eku!adFRH{%`uc3ld$V@pi+o|XNxB@|`Q`Fj6D zvUtp_-i~wfXpW)cc5{#KIB>w?(kVm(f!$fl+`@@{BYDxNj!g&d*@bE6UDiW~ZY3O9 zack;RVoZH9-sQW-BI)O|NBq%1(9)4e3!BMr4VRE4_61>Q6=k77*?T08PZ z29Ke=bhOFV304711f2zmtnWGz!TiHW&69Vvd#hQuqm;(1aUBh3w=kyaLYsO>ah@}~}%*YMx z=d?C$zqc#)iQSf?Y?6U=g1yZWXrzf?B7~6eO;sY&#YC{2q&&##oGpBeN+^+WRN~?ZRNL%Pc4c{Vmu7DR8xUTw^TKi zxQP3$*p-MR?e=~?dpZ1xvPekV@uHfY70;hyKyN%pjB?&b+Cg^UOX9p7@ue}~3YzGH zeQy;Vlx-b>8)d?iqb+%%|7O$Jt0dlaGCT2^ofp;owceav8FA5F+lfh)z$16nmD`gH z0qhqERGUg*{v&ykq0{@%k(*}iC)Ox__u#q~rOG8tgCrk@5mX*G)#odch5qrT))RPj zH9we=5B$cnO1cB`z(rmo7LDbMA=MsV%&o)qxw2gmA!=V-jw~ zv1pMx#=;1rH+ZeY1`qd_rK@2d#HJ<7;X5Zjo}aDr$M{A?W9yMIp82Cj*oV<;i_+Ex zX8NrlWxPXxuD5+7g76P+BGj>$!PhfN__^Yr7@RS?#R*|;UJCmO!ZdYx)+Tgft2x7? z15rnI9*o>yB$j_RuY@%ISXPbR@*wa9&9l6ABhdI1-E#b)@s>a$9Zv&oU5?pb3I=`) zb799}^`-qqy4$~mJ%oQ{htckGMPJ0Gx8(ux zvNOKD4M{Ticp*g5dONG$o%PW3!Df%awF{SK2l4uhSjZWSi60WdP(Z+7s2A9tO`J?+K+@(Hl%nC=ihmdyTS-^kCQy$cVK`ICvmTH zhmMOVa}dQHRdN81i^8~tviuHqM^cDPyOp~0@s{q6REQ!lQtgf{Qw`(|&sggF;TM(1 z$fQqx^pQMXrqs82=LU)*P*SH{_2XhGvTsyi&Mxd$dumKd9G?6ykP(7;a0C-m@Q%9*B37f`|gk$+?lG*h;>3`8Zy{=R|z#B_%&05U6VeyL5qWC7Y(n}4JOBL8k%9l6*f)bScCVG@zJ(rH#XSFcNTZ$shVP#e&zmN4l zhm$n?zFOWNNtki51U$9Ywf(%E)Ul)_EkujHE|uuFzO_AhRS;142#AXWq@@BwuE##+ zqX5x21p8H@Yp-M;fwAm2#0vhY<+wd4+!3=|C2w>Bqf&2OkM<0FXcNRmnTxEYF$MhT ziPgqWuGEKbrW7pU7*csp)#6oRdkP`5fEv*HsbSWL~~O2H?s8 zXnVQvjEgqePyG_#({Shodc@VcJnuTxC@FzFZO^!M5<@wvZvXLg^*KN0@(w zoD+?4yo-=PK$Cxi!T%@7`H$%IKOtv}#+wGtD%ux5B>2!Tl=_CdUPPU0X}!$|I^jqy z+;w1F(x`(CDR2vaCd1HJ@ik`|j<)mOoA-;>bHg$wc2}!1*Q{oXuc)uNZ=zGy^A~Xv zB1HDRMMwBd4!i5FNw+?)T`#+Cp9gk-#GS_?&=P2G>Ip@9`?>nS^QmTAo|R(T(jXDf zjfpj=o>57jR;Ek`+Ab!`^h2uchN&%yfO~iVYqosAf}o17P>A5PhmAN)DXSANC#R>r z0;ew}WsNABD%djhia}3*E!hfkt4VH~_~L-A9pRpgR)kc*#+b4X5m+4LQpY0Js3cgI zwN6&doPvO5V=8<3TDWHbvxJO-vWA?okT!Ja$67Qvn2xITFna(VOdd*V#@Je`E%(4q zl&A2B9N1H-FHvUtTvA4IZnWH5XDY9W7F7U9fW3e-G&z#jJUYXgKA8f&FH>U;PovgkV-pm_JNvq_d5>=`VRn8qwy5dxGu6;i&8`7jiV z9d8?d5!ut8I>`}Mf`g5P2`75I3`Ksy&1h=QM&P+f%duK)Pcok{LkXK{RHQ1yB2puN znoQ0sfZ#XRLi4HQp#)+am3vwwhI&+WV{aYs$-yA;Z5U{w z)ty*h`dk?vpipMtjQ1xcUFK=#aq3={zmU1}mfWig6e;^cE-O1tmb0&h-I0_Gpgo%CpeLt1f-7@kN%4bW~515U=KT_rxD+vpTp`fjQsvAtD zFDN*%5ChNeTn_}`i+4M{m1d&ANhyq~$@cG&7wFBwQj~70gjS4GBd+0aHUtKivOtqN zbtOcZ^@dxXza>MXptniU5t*$YgUGZM4)VsCOa<#=UzuW=l{#;hOuehE?U5HpYq{2? zrvJ(XM$lbCO`BwSeN-=Q5ap)_cBzzMuS!cI>Lj&y^rk;Tl4 zL|IkFIw@nej85`!jv8$sljgx+(=)aR<*ry*wIS>9^ciA&3Yc0dw;d(bUHCy$P;!uI zGv;Qua4L&AwUV2{ljfiQan9!QsAem(ctuy#s@@)0;yR%dldKLSYZsS6xZp0IW-h){C)DuX(y-nZ&-`+%e^9H+i8{JW2cT z0NKV{c27|$p?2moGWcZ#vG95ze>NVfZbnVKOLem6Fx`ZUSKfT_vy;VuZ1Hl^0TAc0 zrD`uToOf%M=onxS$|X`Y5PnIoVJ?f{Cg|E#Suxng0ppd|MrpNYF)dsA=xj99NY1i| zz%Rz2O{w*6PtZh8(A`?`TE&YjI^L-WcfDnX5Q$RLH|BFz8(HPv0q%$9-W={SX(kDm zRch;2*6gfJQGidyJx=w?(^3O14rX_B9r%KF?3;-;V#}>N$hQ+l_X^z$9Z+{y16tD^ za-TTqP}6-|Gv6?$Dug>Q@@kzL2f2ngi4}+=j0M`x-CCiSK^rMm8*Kc{u%os zlg9SfBAa#J1@$Ys-z5=(#JjmSf*jjI$R{q?t<6lOmdh)A@1$7?FTk_Z)IznhyUPQ< z%g*3C9PxlV(^BVj?u)vzR@8R?R&vsX*I`smDNc@4@&+AtVr_%d_BhRTr{M3aGEeGl zS&?19%eG6`9mq%gEWur#5V6k>UP7wI9>ICvq{KF_Cn)&FiGwg=jwif4RCnZ> z($63}_`AuQk1XZQ(*Zmkc=o;Zw7U=95pT?1*j>TPp2=GRI;);lMb}em2d+rU1I{=& z%mJ+pzNph}GxP>P`N<86qCF@Yf43dwz8&EHyuboG71E)ePA&9(A;>43t$Bb~JgqZf}y%GWm?`VrnnM81eO zEPk?Bw&|gQV{+wRZkhB0%iVoi*$VOUpKsO5J4Fe&p9>~;$KLQqQ+4;oIE2p zatlI;q{YTVFrf%$jl_8Hg&iMt3dt$^R);HxhAnBNcM8fC2JpceXpIr^6;odIcJBNf z^ttgT6D9W#4cSAxzc+QXPi_ljKHkpA(g^bu8Lx()&XF^9PkX#wGYC0|q9LCoFqKIO zjCr!4BuPRdDv2X3*U;JbwN@UT%nw*3EnRNWowHFR65(M2M(+bgyZf!?R1n?qyw$#i zC~@w9S$yLJ+@~P#p8(W<@Q`=cTFF3hxK*Ts9qo(u$^4Ee1Lq50{;Dl!ragG z+WEf^LdkA#$()2%|)5mADj z%LOkaEnZZZIc_Y5FkoZ1dO)=>@EA3I4IAOU@P0aTO~X zFQMp6f-Rb?wLgXy;mxVA#577@Ys%X&rQ9xkn}g4_-k`(7K7b-MFy}P;g?ox+`rhi~ zt#IYh(od^cW(e_)hA$Z^RmSs3OVshbWXqh*(7O}3_rOigSg~IiAk#i&-++a0+ChElXF;ggCqDU0

NEw6>8 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%0Dm2LhLUseKP5_#NAOpeHzI-DC4Z%!W?3@>yYn1u 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 diff --git a/src/main/java/io/supertokens/multitenancy/Multitenancy.java b/src/main/java/io/supertokens/multitenancy/Multitenancy.java index 1aaa2a61e..250ae9084 100644 --- a/src/main/java/io/supertokens/multitenancy/Multitenancy.java +++ b/src/main/java/io/supertokens/multitenancy/Multitenancy.java @@ -212,17 +212,20 @@ public static boolean addNewOrUpdateAppOrTenant(Main main, TenantIdentifier sour return addNewOrUpdateAppOrTenant(main, newTenant, false); } + @TestOnly public static boolean addNewOrUpdateAppOrTenant(Main main, TenantConfig newTenant, boolean shouldPreventDbConfigUpdate) - throws CannotModifyBaseConfigException, BadPermissionException, - StorageQueryException, FeatureNotEnabledException, IOException, InvalidConfigException, - InvalidProviderConfigException, TenantOrAppNotFoundException { - return addNewOrUpdateAppOrTenant(main, newTenant, shouldPreventDbConfigUpdate, false); + throws InvalidProviderConfigException, StorageQueryException, FeatureNotEnabledException, + TenantOrAppNotFoundException, IOException, InvalidConfigException, CannotModifyBaseConfigException, + BadPermissionException { + return addNewOrUpdateAppOrTenant(main, newTenant, shouldPreventDbConfigUpdate, false, true); } + public static boolean addNewOrUpdateAppOrTenant(Main main, TenantConfig newTenant, boolean shouldPreventProtectedConfigUpdate, - boolean skipThirdPartyConfigValidation) + boolean skipThirdPartyConfigValidation, + boolean forceReloadResources) throws CannotModifyBaseConfigException, BadPermissionException, StorageQueryException, FeatureNotEnabledException, IOException, InvalidConfigException, InvalidProviderConfigException, TenantOrAppNotFoundException { @@ -259,7 +262,8 @@ public static boolean addNewOrUpdateAppOrTenant(Main main, TenantConfig newTenan .addTenantIdInTargetStorage(newTenant.tenantIdentifier); } catch (TenantOrAppNotFoundException e) { // it should never come here, since we just added the tenant above.. but just in case. - return addNewOrUpdateAppOrTenant(main, newTenant, shouldPreventProtectedConfigUpdate); + return addNewOrUpdateAppOrTenant(main, newTenant, shouldPreventProtectedConfigUpdate, + skipThirdPartyConfigValidation, forceReloadResources); } return true; } catch (DuplicateTenantException e) { @@ -279,7 +283,8 @@ public static boolean addNewOrUpdateAppOrTenant(Main main, TenantConfig newTenan } catch (TenantOrAppNotFoundException ex) { // this can happen cause of a race condition if the tenant was deleted in the middle // of it being recreated. - return addNewOrUpdateAppOrTenant(main, newTenant, shouldPreventProtectedConfigUpdate); + return addNewOrUpdateAppOrTenant(main, newTenant, shouldPreventProtectedConfigUpdate, + skipThirdPartyConfigValidation, forceReloadResources); } catch (DuplicateTenantException ex) { // we treat this as a success return false; @@ -300,7 +305,9 @@ public static boolean addNewOrUpdateAppOrTenant(Main main, TenantConfig newTenan } catch (DuplicateClientTypeException e) { throw new InvalidProviderConfigException("Duplicate clientType was specified in the clients list."); } finally { - MultitenancyHelper.getInstance(main).forceReloadAllResources(tenantsThatChanged); + if (forceReloadResources) { + MultitenancyHelper.getInstance(main).forceReloadAllResources(tenantsThatChanged); + } } } diff --git a/src/main/java/io/supertokens/multitenancy/MultitenancyHelper.java b/src/main/java/io/supertokens/multitenancy/MultitenancyHelper.java index 855a84273..a555cf05c 100644 --- a/src/main/java/io/supertokens/multitenancy/MultitenancyHelper.java +++ b/src/main/java/io/supertokens/multitenancy/MultitenancyHelper.java @@ -29,7 +29,6 @@ import io.supertokens.multitenancy.exception.CannotModifyBaseConfigException; import io.supertokens.output.Logging; import io.supertokens.pluginInterface.STORAGE_TYPE; -import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.exceptions.InvalidConfigException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.*; @@ -76,8 +75,13 @@ public static void init(Main main) throws StorageQueryException, IOException { new TenantConfig( new TenantIdentifier(null, null, null), new EmailPasswordConfig(true), new ThirdPartyConfig(true, null), - new PasswordlessConfig(true), new JsonObject()), false); - } catch (CannotModifyBaseConfigException | BadPermissionException | FeatureNotEnabledException | InvalidConfigException | InvalidProviderConfigException | TenantOrAppNotFoundException e) { + new PasswordlessConfig(true), new JsonObject()), false, false, false); + // Not force reloading all resources here (the last boolean in the function above) + // because the ucl for the FeatureFlag is not yet loaded and results in an empty + // instance of eeFeatureFlag. This is applicable only when the core is starting on + // an empty database as no tenants are loaded from the db yet. + } catch (CannotModifyBaseConfigException | BadPermissionException | FeatureNotEnabledException | + InvalidConfigException | InvalidProviderConfigException | TenantOrAppNotFoundException e) { throw new IllegalStateException(e); } } @@ -98,21 +102,25 @@ private TenantConfig[] getAllTenantsFromDb() throws StorageQueryException { return StorageLayer.getMultitenancyStorage(main).getAllTenants(); } - public List refreshTenantsInCoreBasedOnChangesInCoreConfigOrIfTenantListChanged(boolean reloadAllResources) { + public List refreshTenantsInCoreBasedOnChangesInCoreConfigOrIfTenantListChanged( + boolean reloadAllResources) { try { return main.getResourceDistributor().withResourceDistributorLock(() -> { try { TenantConfig[] tenantsFromDb = getAllTenantsFromDb(); - Map normalizedTenantsFromDb = Config.getNormalisedConfigsForAllTenants( + Map normalizedTenantsFromDb = + Config.getNormalisedConfigsForAllTenants( tenantsFromDb, Config.getBaseConfigAsJsonObject(main)); - Map normalizedTenantsFromMemory = Config.getNormalisedConfigsForAllTenants( + Map normalizedTenantsFromMemory = + Config.getNormalisedConfigsForAllTenants( this.tenantConfigs, Config.getBaseConfigAsJsonObject(main)); List tenantsThatChanged = new ArrayList<>(); - for (Map.Entry entry : normalizedTenantsFromMemory.entrySet()) { + for (Map.Entry entry : + normalizedTenantsFromMemory.entrySet()) { JsonObject tenantConfigFromMemory = entry.getValue(); JsonObject tenantConfigFromDb = normalizedTenantsFromDb.get(entry.getKey()); @@ -128,7 +136,8 @@ public List refreshTenantsInCoreBasedOnChangesInCoreConfigOrIf return tenantsThatChanged; } - ProcessState.getInstance(main).addState(ProcessState.PROCESS_STATE.TENANTS_CHANGED_DURING_REFRESH_FROM_DB, null); + ProcessState.getInstance(main) + .addState(ProcessState.PROCESS_STATE.TENANTS_CHANGED_DURING_REFRESH_FROM_DB, null); // this order is important. For example, storageLayer depends on config, and cronjobs depends on // storageLayer @@ -191,7 +200,8 @@ public void loadFeatureFlag(List tenantsThatChanged) { FeatureFlag.loadForAllTenants(main, apps, tenantsThatChanged); } - public void loadSigningKeys(List tenantsThatChanged) throws UnsupportedJWTSigningAlgorithmException { + public void loadSigningKeys(List tenantsThatChanged) + throws UnsupportedJWTSigningAlgorithmException { List apps = new ArrayList<>(); Set appsSet = new HashSet<>(); for (TenantConfig t : tenantConfigs) { diff --git a/src/main/java/io/supertokens/webserver/api/multitenancy/BaseCreateOrUpdate.java b/src/main/java/io/supertokens/webserver/api/multitenancy/BaseCreateOrUpdate.java index 16865819f..ac930fb7b 100644 --- a/src/main/java/io/supertokens/webserver/api/multitenancy/BaseCreateOrUpdate.java +++ b/src/main/java/io/supertokens/webserver/api/multitenancy/BaseCreateOrUpdate.java @@ -43,11 +43,15 @@ public BaseCreateOrUpdate(Main main) { super(main, RECIPE_ID.MULTITENANCY.toString()); } - protected void handle(HttpServletRequest req, TenantIdentifier sourceTenantIdentifier, TenantIdentifier targetTenantIdentifier, Boolean emailPasswordEnabled, Boolean thirdPartyEnabled, Boolean passwordlessEnabled, JsonObject coreConfig, HttpServletResponse resp) + protected void handle(HttpServletRequest req, TenantIdentifier sourceTenantIdentifier, + TenantIdentifier targetTenantIdentifier, Boolean emailPasswordEnabled, + Boolean thirdPartyEnabled, Boolean passwordlessEnabled, JsonObject coreConfig, + HttpServletResponse resp) throws ServletException, IOException { TenantConfig tenantConfig = Multitenancy.getTenantInfo(main, - new TenantIdentifier(targetTenantIdentifier.getConnectionUriDomain(), targetTenantIdentifier.getAppId(), targetTenantIdentifier.getTenantId())); + new TenantIdentifier(targetTenantIdentifier.getConnectionUriDomain(), targetTenantIdentifier.getAppId(), + targetTenantIdentifier.getTenantId())); boolean createdNew = false; @@ -119,7 +123,7 @@ protected void handle(HttpServletRequest req, TenantIdentifier sourceTenantIdent Multitenancy.checkPermissionsForCreateOrUpdate( main, sourceTenantIdentifier, tenantConfig.tenantIdentifier); - Multitenancy.addNewOrUpdateAppOrTenant(main, tenantConfig, shouldProtectProtectedConfig(req)); + Multitenancy.addNewOrUpdateAppOrTenant(main, tenantConfig, shouldProtectProtectedConfig(req), false, true); JsonObject result = new JsonObject(); result.addProperty("status", "OK"); result.addProperty("createdNew", createdNew); diff --git a/src/main/java/io/supertokens/webserver/api/multitenancy/thirdparty/CreateOrUpdateThirdPartyConfigAPI.java b/src/main/java/io/supertokens/webserver/api/multitenancy/thirdparty/CreateOrUpdateThirdPartyConfigAPI.java index 2aab9f831..eff3a93d9 100644 --- a/src/main/java/io/supertokens/webserver/api/multitenancy/thirdparty/CreateOrUpdateThirdPartyConfigAPI.java +++ b/src/main/java/io/supertokens/webserver/api/multitenancy/thirdparty/CreateOrUpdateThirdPartyConfigAPI.java @@ -87,7 +87,7 @@ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IO boolean found = false; - for (ThirdPartyConfig.Provider provider: tenantConfig.thirdPartyConfig.providers) { + for (ThirdPartyConfig.Provider provider : tenantConfig.thirdPartyConfig.providers) { // Loop through all the existing thirdParty providers in the db if (!provider.thirdPartyId.equals(thirdPartyId)) { @@ -96,7 +96,8 @@ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IO } else { // if the thirdPartyId is the same as the one we are trying to update, add the one from json input // to the new list - ThirdPartyConfig.Provider newProvider = new Gson().fromJson(config, ThirdPartyConfig.Provider.class); + ThirdPartyConfig.Provider newProvider = new Gson().fromJson(config, + ThirdPartyConfig.Provider.class); newProviders.add(normalize(newProvider)); found = true; } @@ -110,11 +111,13 @@ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IO tenantConfig.tenantIdentifier, tenantConfig.emailPasswordConfig, new ThirdPartyConfig( - tenantConfig.thirdPartyConfig.enabled, newProviders.toArray(new ThirdPartyConfig.Provider[0])), + tenantConfig.thirdPartyConfig.enabled, + newProviders.toArray(new ThirdPartyConfig.Provider[0])), tenantConfig.passwordlessConfig, tenantConfig.coreConfig); - Multitenancy.addNewOrUpdateAppOrTenant(main, updatedConfig, shouldProtectProtectedConfig(req), skipValidation); + Multitenancy.addNewOrUpdateAppOrTenant(main, updatedConfig, shouldProtectProtectedConfig(req), + skipValidation, true); JsonObject result = new JsonObject(); result.addProperty("status", "OK"); @@ -134,7 +137,8 @@ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IO private ThirdPartyConfig.Provider normalize(ThirdPartyConfig.Provider provider) { ThirdPartyConfig.UserInfoMap normalizedUserInfoMap = provider.userInfoMap; if (normalizedUserInfoMap != null) { - normalizedUserInfoMap = new ThirdPartyConfig.UserInfoMap(normalizedUserInfoMap.fromIdTokenPayload, normalizedUserInfoMap.fromUserInfoAPI); + normalizedUserInfoMap = new ThirdPartyConfig.UserInfoMap(normalizedUserInfoMap.fromIdTokenPayload, + normalizedUserInfoMap.fromUserInfoAPI); } return new ThirdPartyConfig.Provider( diff --git a/src/main/java/io/supertokens/webserver/api/multitenancy/thirdparty/RemoveThirdPartyConfigAPI.java b/src/main/java/io/supertokens/webserver/api/multitenancy/thirdparty/RemoveThirdPartyConfigAPI.java index 9b39d1e73..8f5b5570c 100644 --- a/src/main/java/io/supertokens/webserver/api/multitenancy/thirdparty/RemoveThirdPartyConfigAPI.java +++ b/src/main/java/io/supertokens/webserver/api/multitenancy/thirdparty/RemoveThirdPartyConfigAPI.java @@ -68,7 +68,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I // Create a new list of providers skipping the thirdPartyId provided in the input List newProviders = new ArrayList<>(); boolean found = false; - for (ThirdPartyConfig.Provider provider: config.thirdPartyConfig.providers) { + for (ThirdPartyConfig.Provider provider : config.thirdPartyConfig.providers) { if (!provider.thirdPartyId.equals(thirdPartyId)) { newProviders.add(provider); } else { @@ -84,7 +84,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I config.passwordlessConfig, config.coreConfig); - Multitenancy.addNewOrUpdateAppOrTenant(main, updatedConfig, shouldProtectProtectedConfig(req)); + Multitenancy.addNewOrUpdateAppOrTenant(main, updatedConfig, shouldProtectProtectedConfig(req), false, true); JsonObject result = new JsonObject(); result.addProperty("status", "OK"); diff --git a/src/test/java/io/supertokens/test/FeatureFlagTest.java b/src/test/java/io/supertokens/test/FeatureFlagTest.java index f2394b2dd..c127f666a 100644 --- a/src/test/java/io/supertokens/test/FeatureFlagTest.java +++ b/src/test/java/io/supertokens/test/FeatureFlagTest.java @@ -21,7 +21,6 @@ import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; import io.supertokens.ProcessState; -import io.supertokens.authRecipe.AuthRecipe; import io.supertokens.cronjobs.CronTask; import io.supertokens.cronjobs.CronTaskTest; import io.supertokens.cronjobs.Cronjobs; @@ -43,7 +42,6 @@ import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.httpRequest.HttpRequestForTesting; import io.supertokens.test.multitenant.api.TestMultitenancyAPIHelper; -import io.supertokens.utils.SemVer; import io.supertokens.webserver.WebserverAPI; import org.junit.*; import org.junit.rules.TestRule; @@ -746,308 +744,4 @@ public void testNetworkCallIsMadeInCoreInit() throws Exception { process.kill(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); } - - private static String OPAQUE_KEY_WITH_MULTITENANCY_AND_ACCOUNTLINKING_FEATURE = "N2uEOdEzd1XZZ5VBSTGYaM7Ia4s8wAq" + - "RWFAxLqTYrB6GQ=vssOLo3c=PkFgcExkaXs=IA-d9UWccoNKsyUgNhOhcKtM1bjC5OLrYRpTAgN-2EbKYsQGGQRQHuUN4EO1V"; - - @Test - public void testAccountLinkingStatsArePresent() throws Exception { - String[] args = {"../"}; - - TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); - process.startProcess(); - assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); - - if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { - return; - } - - FeatureFlag.getInstance(process.main).setLicenseKeyAndSyncFeatures(OPAQUE_KEY_WITH_MULTITENANCY_AND_ACCOUNTLINKING_FEATURE); - - Multitenancy.addNewOrUpdateAppOrTenant( - process.getProcess(), - new TenantIdentifier(null, null, null), - new TenantConfig( - new TenantIdentifier(null, "a1", null), - new EmailPasswordConfig(true), - new ThirdPartyConfig(true, null), - new PasswordlessConfig(true), - new JsonObject() - ) - ); - - { - JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", - "http://127.0.0.1:3567/ee/featureflag", - null, 1000, 1000, null, WebserverAPI.getLatestCDIVersion().get(), ""); - Assert.assertEquals("OK", response.get("status").getAsString()); - - JsonObject alStats = response.get("usageStats").getAsJsonObject().get("account_linking").getAsJsonObject(); - assertNotNull(alStats); - - assertEquals(3, alStats.entrySet().size()); - assertFalse(alStats.get("usesAccountLinking").getAsBoolean()); - assertEquals(0, alStats.get("totalUserCountWithMoreThanOneLoginMethod").getAsInt()); - - JsonArray maus = alStats.get("mauWithMoreThanOneLoginMethod").getAsJsonArray(); - assertEquals(30, maus.size()); - } - - process.kill(); - assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); - } - - @Test - public void testAccountLinkingStatsAreAccurate() throws Exception { - String[] args = {"../"}; - - TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); - process.startProcess(); - assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); - - if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { - return; - } - - FeatureFlag.getInstance(process.main).setLicenseKeyAndSyncFeatures(OPAQUE_KEY_WITH_MULTITENANCY_AND_ACCOUNTLINKING_FEATURE); - - AuthRecipeUserInfo user1 = EmailPassword.signUp(process.getProcess(), "test1@example.com", "password"); - AuthRecipeUserInfo user2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "password"); - AuthRecipeUserInfo user3 = EmailPassword.signUp(process.getProcess(), "test3@example.com", "password"); - - AuthRecipe.createPrimaryUser(process.getProcess(), user1.getSupertokensUserId()); - AuthRecipe.linkAccounts(process.getProcess(), user2.getSupertokensUserId(), user1.getSupertokensUserId()); - AuthRecipe.createPrimaryUser(process.getProcess(), user3.getSupertokensUserId()); - - { - JsonObject responseBody = new JsonObject(); - responseBody.addProperty("email", "test1@example.com"); - responseBody.addProperty("password", "password"); - - JsonObject signInResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/recipe/signin", responseBody, 1000, 1000, null, SemVer.v4_0.get(), - "emailpassword"); - - assertEquals(signInResponse.get("status").getAsString(), "OK"); - } - { - JsonObject responseBody = new JsonObject(); - responseBody.addProperty("email", "test2@example.com"); - responseBody.addProperty("password", "password"); - - JsonObject signInResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/recipe/signin", responseBody, 1000, 1000, null, SemVer.v4_0.get(), - "emailpassword"); - - assertEquals(signInResponse.get("status").getAsString(), "OK"); - } - { - JsonObject responseBody = new JsonObject(); - responseBody.addProperty("email", "test3@example.com"); - responseBody.addProperty("password", "password"); - - JsonObject signInResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/recipe/signin", responseBody, 1000, 1000, null, SemVer.v4_0.get(), - "emailpassword"); - - assertEquals(signInResponse.get("status").getAsString(), "OK"); - } - - { - JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", - "http://127.0.0.1:3567/ee/featureflag", - null, 1000, 1000, null, WebserverAPI.getLatestCDIVersion().get(), ""); - Assert.assertEquals("OK", response.get("status").getAsString()); - - JsonObject alStats = response.get("usageStats").getAsJsonObject().get("account_linking").getAsJsonObject(); - assertNotNull(alStats); - - assertEquals(3, alStats.entrySet().size()); - assertTrue(alStats.get("usesAccountLinking").getAsBoolean()); - assertEquals(1, alStats.get("totalUserCountWithMoreThanOneLoginMethod").getAsInt()); - - JsonArray maus = alStats.get("mauWithMoreThanOneLoginMethod").getAsJsonArray(); - assertEquals(30, maus.size()); - assertEquals(1, maus.get(0).getAsInt()); - } - - process.kill(); - assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); - } - - @Test - public void testAccountLinkingStatsWithDifferentStorages() throws Exception { - String[] args = {"../"}; - - TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); - process.startProcess(); - assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); - - if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { - return; - } - - FeatureFlag.getInstance(process.main).setLicenseKeyAndSyncFeatures(OPAQUE_KEY_WITH_MULTITENANCY_AND_ACCOUNTLINKING_FEATURE); - - { - JsonObject coreConfig = new JsonObject(); - StorageLayer.getStorage(process.getProcess()).modifyConfigToAddANewUserPoolForTesting(coreConfig, 1); - Multitenancy.addNewOrUpdateAppOrTenant( - process.getProcess(), - new TenantIdentifier(null, null, null), - new TenantConfig( - new TenantIdentifier(null, null, "t1"), - new EmailPasswordConfig(true), - new ThirdPartyConfig(true, null), - new PasswordlessConfig(true), - coreConfig - ) - ); - } - - { - // tenant 1 users - AuthRecipeUserInfo user1 = EmailPassword.signUp(process.getProcess(), "test1@example.com", "password"); - AuthRecipeUserInfo user2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "password"); - AuthRecipeUserInfo user3 = EmailPassword.signUp(process.getProcess(), "test3@example.com", "password"); - - AuthRecipe.createPrimaryUser(process.getProcess(), user1.getSupertokensUserId()); - AuthRecipe.linkAccounts(process.getProcess(), user2.getSupertokensUserId(), user1.getSupertokensUserId()); - AuthRecipe.createPrimaryUser(process.getProcess(), user3.getSupertokensUserId()); - } - - { - // tenant 2 users - TenantIdentifier t = new TenantIdentifier(null, null, "t1"); - TenantIdentifierWithStorage ts = t.withStorage(StorageLayer.getStorage(t, process.getProcess())); - - // tenant 1 users - AuthRecipeUserInfo user1 = EmailPassword.signUp(ts, process.getProcess(), "test1@example.com", "password"); - AuthRecipeUserInfo user2 = EmailPassword.signUp(ts, process.getProcess(), "test2@example.com", "password"); - AuthRecipeUserInfo user3 = EmailPassword.signUp(ts, process.getProcess(), "test3@example.com", "password"); - AuthRecipeUserInfo user4 = EmailPassword.signUp(ts, process.getProcess(), "test4@example.com", "password"); - AuthRecipeUserInfo user5 = EmailPassword.signUp(ts, process.getProcess(), "test5@example.com", "password"); - - AuthRecipe.createPrimaryUser(process.getProcess(), ts.toAppIdentifierWithStorage(), user1.getSupertokensUserId()); - AuthRecipe.linkAccounts(process.getProcess(), ts.toAppIdentifierWithStorage(), user2.getSupertokensUserId(), user1.getSupertokensUserId()); - AuthRecipe.createPrimaryUser(process.getProcess(), ts.toAppIdentifierWithStorage(), user3.getSupertokensUserId()); - AuthRecipe.linkAccounts(process.getProcess(), ts.toAppIdentifierWithStorage(), user4.getSupertokensUserId(), user3.getSupertokensUserId()); - } - - { // tenant 1 - { - JsonObject responseBody = new JsonObject(); - responseBody.addProperty("email", "test1@example.com"); - responseBody.addProperty("password", "password"); - - JsonObject signInResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/recipe/signin", responseBody, 1000, 1000, null, SemVer.v4_0.get(), - "emailpassword"); - - assertEquals(signInResponse.get("status").getAsString(), "OK"); - } - { - JsonObject responseBody = new JsonObject(); - responseBody.addProperty("email", "test2@example.com"); - responseBody.addProperty("password", "password"); - - JsonObject signInResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/recipe/signin", responseBody, 1000, 1000, null, SemVer.v4_0.get(), - "emailpassword"); - - assertEquals(signInResponse.get("status").getAsString(), "OK"); - } - { - JsonObject responseBody = new JsonObject(); - responseBody.addProperty("email", "test3@example.com"); - responseBody.addProperty("password", "password"); - - JsonObject signInResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/recipe/signin", responseBody, 1000, 1000, null, SemVer.v4_0.get(), - "emailpassword"); - - assertEquals(signInResponse.get("status").getAsString(), "OK"); - } - } - - { // tenant 2 - { - JsonObject responseBody = new JsonObject(); - responseBody.addProperty("email", "test1@example.com"); - responseBody.addProperty("password", "password"); - - JsonObject signInResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/t1/recipe/signin", responseBody, 1000, 1000, null, SemVer.v4_0.get(), - "emailpassword"); - - assertEquals(signInResponse.get("status").getAsString(), "OK"); - } - { - JsonObject responseBody = new JsonObject(); - responseBody.addProperty("email", "test2@example.com"); - responseBody.addProperty("password", "password"); - - JsonObject signInResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/t1/recipe/signin", responseBody, 1000, 1000, null, SemVer.v4_0.get(), - "emailpassword"); - - assertEquals(signInResponse.get("status").getAsString(), "OK"); - } - { - JsonObject responseBody = new JsonObject(); - responseBody.addProperty("email", "test3@example.com"); - responseBody.addProperty("password", "password"); - - JsonObject signInResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/t1/recipe/signin", responseBody, 1000, 1000, null, SemVer.v4_0.get(), - "emailpassword"); - - assertEquals(signInResponse.get("status").getAsString(), "OK"); - } - { - JsonObject responseBody = new JsonObject(); - responseBody.addProperty("email", "test4@example.com"); - responseBody.addProperty("password", "password"); - - JsonObject signInResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/t1/recipe/signin", responseBody, 1000, 1000, null, SemVer.v4_0.get(), - "emailpassword"); - - assertEquals(signInResponse.get("status").getAsString(), "OK"); - } - { - JsonObject responseBody = new JsonObject(); - responseBody.addProperty("email", "test5@example.com"); - responseBody.addProperty("password", "password"); - - JsonObject signInResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - "http://localhost:3567/t1/recipe/signin", responseBody, 1000, 1000, null, SemVer.v4_0.get(), - "emailpassword"); - - assertEquals(signInResponse.get("status").getAsString(), "OK"); - } - } - - - { - JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", - "http://127.0.0.1:3567/ee/featureflag", - null, 1000, 1000, null, WebserverAPI.getLatestCDIVersion().get(), ""); - Assert.assertEquals("OK", response.get("status").getAsString()); - - JsonObject alStats = response.get("usageStats").getAsJsonObject().get("account_linking").getAsJsonObject(); - assertNotNull(alStats); - - assertEquals(3, alStats.entrySet().size()); - assertTrue(alStats.get("usesAccountLinking").getAsBoolean()); - assertEquals(3, alStats.get("totalUserCountWithMoreThanOneLoginMethod").getAsInt()); - - JsonArray maus = alStats.get("mauWithMoreThanOneLoginMethod").getAsJsonArray(); - assertEquals(30, maus.size()); - assertEquals(3, maus.get(0).getAsInt()); - } - - process.kill(); - assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); - } } diff --git a/src/test/java/io/supertokens/test/session/AccessTokenTest.java b/src/test/java/io/supertokens/test/session/AccessTokenTest.java index 30247227a..31e482a7a 100644 --- a/src/test/java/io/supertokens/test/session/AccessTokenTest.java +++ b/src/test/java/io/supertokens/test/session/AccessTokenTest.java @@ -301,7 +301,6 @@ public void inputOutputTestStatic() throws Exception { TokenInfo newToken = AccessToken.createNewAccessToken(process.getProcess(), "sessionHandle", "userId", "refreshTokenHash1", "parentRefreshTokenHash1", jsonObj, "antiCsrfToken", expiryTime, AccessToken.getLatestVersion(), true); - System.out.println(newToken.token); AccessTokenInfo info = AccessToken.getInfoFromAccessToken(process.getProcess(), newToken.token, true); assertEquals("sessionHandle", info.sessionHandle); assertEquals("userId", info.recipeUserId); From 96954fedd9ee8b8b3e01db675a4f9104ae0f051a Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Mon, 11 Sep 2023 11:44:10 +0530 Subject: [PATCH 108/131] fix: tests for tenant association with unlinked account --- jar/core-6.0.12.jar | Bin 0 -> 718455 bytes .../test/accountlinking/MultitenantTest.java | 43 ++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 jar/core-6.0.12.jar diff --git a/jar/core-6.0.12.jar b/jar/core-6.0.12.jar new file mode 100644 index 0000000000000000000000000000000000000000..6e249ea878cf4aa80db56f257ee3a200dea808a6 GIT binary patch literal 718455 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

RriW@jQb+5ZXR~?=>7{(x3 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

f=T@A5)MZ9%oSfOn?uF=u8z39+kzfRRy~7fR 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 z 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 z z^`){>EsN^>H$8T5xQqY@`EpLYMi*)fnLEW~8S=98+g1$gI=Ox?0CsJ>;X`M!NTbg7 zxf0x)Nh=|_AA<7AzA(Axd<1qlk}y6jc8FX@D5%L8uEDeeHHT6B{=nnH0YUrU zGamLT1s|3&^FT{od2r~F_C$%d$pbFg zLR|fw_Vz@1uYaL(1K7}&SM^lC@$i_uEmYwKytE24=I8O%P_y=GxRv?kuq(eX?00}2 zdUeIbv22E+yRMT6V-g8zoM!4=QzID6sqDi}ItJveuc2WiZNo~STB9V?gvAJ9h%IRh zN{oWCCTVKHPl(txJISaM4#t+jH^8|xvpFAx)I1ay2~IqcDEUCOr@h2jnViwnIv|;# zcIr7ftMPq)Q_4Yhu2LexI)$s!sX)F-%!_7AVZKvTnv;tg7j_+l zPVl7F1o}9HC#*oaUlFi%YG~nml8>o&So5{rqwz%RUZ^<`lcz3}6LoN!W4($j!dThN ziNaRWFtnR-;kXysVY(RzEECdt$jKzEUmdIe8&$MUlyCsc$w*=tN7x+3lC8QZBgZ(! zxFrGp*X4{MaHLT=ov)bFJe`t~=pGFqETPm*gVVhdT+x)&{$ARdd9`pHr%KNQ|8|dk$YNhM5VTG zcT6|b{0J*eDTagf3^|gbS~5Kq!e%YBH9i0R*WWNI04T$#%><#Ybl|3_%*ndz=q=C> zY3dDyU!3&El(+N(P#w2Zq)3{0a%Xrh?tj5$?N3Bf`5sn#UBD7WYcsI5EhpjW|D|_WeLTud|bC0uhB`* zwu*io-565h>K(Ed_7R5q=$#0_uZjU|JMRY^hQT zK{bJRrEMQ>Al;&nIRWz$p`{To`#BI>Ua20i$uz7Ta<-XO3@N`W&6GW;39-C4#Qa(x zR1+2lOffvNaS?G6m3a#X>{`vwaIKnZiRP1+6IvfZgD#4f{%W}#< zuNqU1DWlT4wM)FhC)dExCf#aVl_al|?Y*Gl-^>dT@5QQwOuVM)%`(?g?$Ix(6tW}J z7oX2Ph$XaCM@&kH+XFBak)vE6sKU`*_jxa$cKx=#!KCgg~nol#BE$I-MkZ z4|I+TIpfkkcf93o2q4JHW)(>*;?@i+r1J_whm2OI_78E11maTU0CjmV-TSkga&z|5 z>b`3PqCCU9WmO=0ht>ZjduUy{^rk0h*jImnBp|W%Uwu!ziSf##*c8}vWw~dy1+~g* zd(h8+iLDiyRu+#Pbi2D?2<*U*?8vaL2()J}q;RuTKZx|L6GC<%78bdKe#6F%U29m< z&QZtk6iC?%$w@ES1C7zLxJKd~SG%zZ!D?fz6>G zgu=ecC+`}Af0#?(cEG;Ub8z*Xy6X=rD|>i>&Y&eOv2_g9{j+o2PTd`*kVTo<2dlE* zmF2OiQNJzKNJEU>u>LuPZ^RpM0(-Fnd%=G8JXqE3C-!DrsBPUZb=eC6MyX*Z{G-Jf z-z^`!wsABikkZ9xf0H;??I$@hFZFpAZ-((A>t60%2PzZ3bd*nfA8(vDUjpS}WKd zjawxf{CtIG-8Q9WsllEUKIHb9<`hxoOP~e$i<}v;-#C2*=k%uD{-ea9vBpFnb@e*1}W8W6$`ub=l7v+bqIRH<#2)D`&B((XP1h zgDy+8PU$;sJz8U`wkc>772ma$cg*(c^hpz_ACza9V~BM<5?&=hsn6SP7?Of)1-Q=* zH>@&y2#P;Sw{Fvn-i@lW#9ub-`i}^8Bb0#@?E7Dg4V*3VEtX>r(rPm8xD!#)*~ifA zW+NlE)>Jkd=svud1n6Wg8%wN?oKw2Co1_t;r{)Abb2BrCtp_1m`Owq_MKsjO$667$ zjo`C!kQtKeIEEq#0W;dF-cT)!Hc5*Q^|ZyCNUeZvr6C-8V>HC{v0EWgyv@E^LHUxZ zGC}3?iQO?dK1-5T;@7o&BkF6TRg%A)2AY=D8IgeVkKdS71PM7{v-!EILvEJ_8oFMQ zf{d9CWEXPGix65d-(&Q=wnZ@TsZe~G+e&MV;s7A#v)YlfHp1yxz|e6G@D@o)ZkuBY zdW(g&#WRWBxi16}%MG@sJJrx9HQ~TyN_vEY+Rs?&yz%ox%X#gHtR~VxOd!t`A z>wP>I2{EgjGT;lSyczAR%)TX&kWqar^rgua0-zi9b;;1w3Y}Pe4I+cRLW(3R1-J=Y)P2A zj-=IJ77o{tgNtLckjq$)PfoDp&;-qptB?q0Hg9hFV0`ylW=h=)Quazu7OO-Jnn@m;DTqHb5b|an@R>M@`B(LWK(~~EDG#@YIYH=mbGU0r zDyFDO88zs!w9-P}v-|Y&9q^}yri}082E5EV~!0@JBHqe5|xmswa zHR^Z&Dt)btnAszwvO!=^BocmcCYPQTsmDhU8AqRWNH>@0RY1Fr4<%B9!83$XS14lX z@8p+ue$=vCMJ1)wRQc!@7?NiRCGM(wm1u4GC&04FiF|z%t9b4eWvI@%fgUV5n7Ozh zrqqk!?;D>WQ0QLDi*<$*zYE?=&v;q=1M@ou8@hsg7^ChliL1|?gggn~Jigrxhf9_? zVm8rEW1F>nOKkipZwyB4K?T+Ka0ae&BeZ5u6I@^YJ77A|a4BFfKjo60ESP^}7>ajR zce6fhhHwvU2D;|eO_cw1%2ce4Hu^y~|AbU-I~EMLOGvx}nigHOn{Y2cvkmP@H? zZdYkC6SAlWJ;SDc0Q)c{s10Arvht9I?%yk>>HJ-sfnFz}R;X_k#@NA2{ZTtHo(9{? zWMm^OuN=5L>!rY>spdAzntDB&r2PA6%@{k@Os$i8qta<^heuJMf~em^v2DP9HlGARbOD78J{ zFI8LNCw&QHUN1pdx73(NT2Z_KqFO*D9VOCySCPchA#Qt1$$VG+87T?Ofe;PG&C2>m zltn}ZE3Id;>@bS_ReAKJf_<1cu>!YkFo&*@>DTNV)4lkpiTFPhDZfsIh z`Jr}S4ghq?sdAT<=^0XG0;cxWs)_D-l0=uX!J1Q4-Ks5ruK(=eA!nJrb$b?>I|TNJ zEA#3z&a$r9#;vKvfGoFpoi#eJ`t`!R*4=gBK{SU??e)7pBGh~iPerTc{emvv8DdMY zW(NrEQ)5_!LvKi-L2iZ!#(R;A z279|pg4b8TA$m~57#Mo=ihdP#XKRIFVM2YE)&JVn|LSe{0b;4AaR{)9A1)6KQEi39 zu0xqx6_tPAT8-pA?e%$_g`KNHU~gLZws0)!Hz&Gw7La?y@}dUx?XFT!$Ws98b`B!5Ym~ytuBr zX8P1#OaD*HXC}JDlck2IS6pBDBsIq5{v)uzhhl2d>l}jXmct!EuP~msl3dQgpJ*Tc zes}>lSd>zV4jO$GFrS46Jpq0n89&&1LOU!%5%Al@c!1-0D3E^wl)un+zMP`>4~kFl z_{w(`-+p_`^y#bb+A~#K-9l*ALHtgwdq$k+G@-CUHUA2F`Cm}3@6qUgqTtm3r#Y#q zt)Zoj!}tE0ySRGm$o{!>IXeK!c{sG@z^GbOT1 z>RU4F8nn;;08bcZXjDmo7FsY_N03d7E2O10@Xqp-#mkz^==NM_3{U_^8^Tqoge8(z z6tFC_Y`nCB4dae@4{5!j{`O;mJ0xi&i6-r@K4ZX_t)oomnSWo-GhGjlzxltQ{Jb2+ zIHeTI`$`64nf$FL5X|c>7aXr>&8Sf1$Q!oQs8XC{%^Q%|2d?IZHMAS4m%p>lU1WtT z+bXK^3XFZMxv9}vF}Pd11GIi}jVluVa=1cRsZ-hlWv;MJuVHEi>=nkJBLJH&D~!uS zP+VjNT>KZrDbJYMO=Zk8&Vp7DQ?S;JKus~UC?~qY%v58Wt)mT#&PS-qH8BJf1Q5B+ zHl;H5Xo|oY67x+cQ%ZYnGE4VJa0w@oG&rgW>r}WdngIy>;6hu)K+9zpx7z+wTURb1 zP6z!t2iwMax#bZ0fMawmPCCn$L+nNIK*j6nE!Fx|l_v86I)l40h(0)je6Rg8mYUMQ z7^rH&&`F_K%g9ut^%^C-Dkui((R}bkw8(UN-X2J%6l4pjEU~d#5iT$znW5rR#R(Fh z#-{q7N8OZLb-WaYrdOyft^{_noURcJ?N2PTX{?oh;ysX}2au6atW_ck){of~%#w1T zTDstn62mZ!&f&bMVrox4XI;3Ug0ELG>~<}Y0a^r4iw$NaW^CRG5_E=~SzuTeqkf>{ zPkQjSD4}g=s0nT@dG7G>8!n0(t~v! zaJjwd1hwf#BS7U9VVtR0S@m?pfsbJkc3L_T40?iFgyUbf7Qcfbhwcm`YqnY*=};G9 z%7s;{L*Vq74@R{#D#to+jSLm@)Z`+P7(=1Z#XDL&!`bH!)y zkZO=cC*PPgU2;)TNIai`s%PCvAMR!_KXY^)?kc6B%~b}6m#YA#)|aVIZ28LaCBSJw zLD7@5X{~x?R)_#6G7P41PYM2ti))s1SssP%W6Y8}OBSmduqCHUNqp8+Q~O;~2es*u z`Qi4r&QmgE*;S)vj|msMR?%SkRI^=;2}O@ON9SMC)gzW9wK0_jRHh<@7RGwzcVQc@ z`hHVXmWV=0X0o<}5HaWOec~Lr-INn3Ae6^@=l^PjQ52&wwVFQZcrjI8RxAqSdwL&cB-E@5sNp zs=5MU0MB4tJv4177aVtXHK~&|h=`YHQhr6Nr#msPiFzPQ9X$|^q~|vsB0aT^@QvAG^ZwJ#ImzRB=J1 zc<$v5ToxJF8Qb?iVcE^zeWx-QT!Z<5dvBz;F+Qd9=MBP0bt;Z*+`8fYY{yLmNiM68 zw{p$bZV;J%Q~zN4e-v6hZy;(L%`(0f=&F_ zzjMNAKM6veWJ+eZf!z-gI;n#*!jvS5 zukzp*HwDRZ@%g#~5 zgU%)g>$BOcIaqj&>J67FI2nmw$EqMtP`m`OMU?+D?XV~Vc1tkR8`xKyN%L75<9lgt zp=BYVLK(`c3GjU0bBc_{;u|@1)NmOvpf@!Kho~j>3k5r8TU{&1GtSUFS{E)MN!(>~ zHYfI!z*6x}iNcRP^+1iCyFkel25bKO?gesG#y?9Ycku?%CxNx#wB)VW&C|O+g>=Q~ zPuygvKLaZW`>uTm**=FeZ)w4)F==kC2;O&thnI)daT;vAbcs?K4x4B<>)41mK{^9? ziw>x}dc*7E11ITyhdD(|*6TA8)Vy6j32H0fRuk&I($amEM_O)aRN<#;2Uz#DbwUTY zL#xBA?hCeNWnfG4clw;Mx+@O8nX{H0MF^(;k_Nj!co%8`|Dc_fQ*N>yt0QA9eS_8C zLqV&=GbmY27j$4eFXMRc=}dfO4uNel!p^|PJWFBq$9fHM^U&0Fi&skdRFv$W4@nej z(VI){vSMc;CiBS zNBObIW%+X4c8=)b<$U$!;}UCBJVm$_D-Ld@6Pe{2%HY1yI~K21F*|!NpbH^gYkvx% z{MVqFjXBMXC{W!>59Nyju~lJ?HN==wk*kthQumSl%c3(2Cz7?yNg9g3%pdHqG}bSu zl`-Ul859~~_DbgD7$D?~wG5g~fb5Pdss5UH&sQ9(m@0!eIz=;AbrwVObNdmk@Z}h@ z676gQSMj@z>gh7O%eZsA@^hSp0#6?9NX9h=bR6f}1nst`8fl|hb;s=oIbvW&BUc6m z3V#gk{XJ1^4>lu7Pdhj7@{PISkxYv>%V5B1gccZE#$ktzFp@0x;pXqNyH1NphgE+a z!BB`;36W!qK@!Vm_7=#-yIB}9SF`NVE{X9^YoDE+LzN#ndp8Ft5q?nmilX(IL6z*Kqso_ zwMaCwhECzUWRWHgcq$U7{reNL&NNB}ImRXt2Z@d3hAxWSnSV{iWmS{huDaF)Zpj(s zj;5{qBdSQLxM06~%rr-FR~gue_0K`Jilb|GR?atJN)mX2m22z zAxdo!AH^%ZFkg#zM$=fVJ9j>6l?~260Y$Hn(hXhRujHTLrN}7CQpg_hKD^CR_LWlK=?<+k6Sf@PVf0(xDuta3Xo{~FMRoKPfwj!52XXQIM=h+Sck*iY zS~l<-stIzo!H~Z?0V}YJh3Hq~8s_GD>}0+a4}c1|PUXwDV@(|MHVa&h% zMkoXQ_uX;94(#xK3yF5+wcEE-pUtXI`@PPZcf^;b@af>-Z+?mB$2=in&*LDT7Z5G= z%Xu}bRD%r5(frznUHkBD^ZSp7zW60IFCq6&AcYmD8Kn_TQ>_bS`kcR;ixDsLG94eV z773u^f7Oa?KX7r2FzCpP?7=T{Da3x=`+YFbE@hXUqQvnF9O2lfY<&f(_W<#cv1>k{ zUljz?p)EeZawMuoD>>j&3}p{@cI?s!!L~fo7D2T~tMcgRpMn1)btQd^k3&{%H*Dzv zn9y7g_I#z+^B04E2ot;pCT*Q3Q?!XC)fPI^(eJYOr zWOC4(Q9JE-zBs#M0!b>=ToY-84x(c^i`Hc&ZZ9wG00(hW@pl4Z62fkGm_7Sr`|eM@ zHo5p4zIuvQb+qwkqa8Y#RGSQ^A3sH|Pw|GE>5^u*~SkI?gM^5M*pQ2m5S z23hMaWjmZ;Wc~$1VJ4?)&&0f+STrp9e(N!caW6gXV;M(tly@&!FVUL{^y^{QcOl6Y zOF-2}0Ef7Z2Jr?SS^yVMwhpB3VeI&$s6A+?gy+yo0a#kNJna-!agkTAh5p_jE|11 zty$~Wx!oOQcXB$B9TZoIIR7X&1Kw93e4BavKpdD}X|jtS%{@JQV_0{HZF9(N^?QX$ zMg|I(iHZEgMSsoAmXCxtl8dl*6QCbN5(UtQlP%W_3L*^<)*;D9aAYG(;$1ep*0&<) zD<1kpq0b!fkYsr`iXl573{2C)@g&TGXfX;aHfO%^dO?(W)?R}z*4fcfwt3XXxQ(`Z zRz{wL>C0hHn{IhGWnJRrKTi`{-ApQO&{w0AJBv`351DdSpffK@B)lUbQQzjm&>^4< zf#<`d6ZvAj8PX`&z{Ta#8|RHIairVt`f307e@)aQsyhrx`^FTUFn;{t`oBBYDO;M`sXF}E!R{NNMOi`n#7hCt z{$vXUfkJ15?h4qrEkyhyJA;}zwZldW0S@C@qumk53K;=f; z+6GGWi;L6z7y<3J$N{_g^Q66ij6TaV#tDTck^!#Y!W)jJGSJ1Sw1CN{c^yCnNo9&+ zuvLh4L{jFVFd6%BE7E0#9johil092_y44k7!^eyC3N-x{wZ&~~`ktV& ztkqf+_Ju=o&=ZqYt-s+#zgj=Ec=<=Z^G>*0r8Lt$*nN6iA5V=M)xzAr|>_vHoB$9_@ZLz{;*x$QEq7 z_*kjn#{5}wALic{a$`vAE4A}N(emmv+VV2EB98!nc!^tPn?H1c`0UMu1O|0k zdHp-H7sCiT^n?_~tZt_9->x@g0um4l(4tjFO=at zW-t?3s}l-4X)o@&I{y=jh)^3@09?3aS!!R15vK>?zo4aQOTe)USs*bJp~kE++c<2w*2{o*$>@YtDhK(70+GiRD;D4}Mb#3En+wTuT)%{EIOks*wWm6F5eg_1IV-i_pDI%*IJQJxmgo)`xG-_RXrsNg68ttf z0_dO_zu^VKbgFb_c6FOLIq`&9)AhE2+fyE=_Z(g&GU=4@1X3A=`eh0A808F@DZB_* z73}2!P&OZ?m~Uq)(Lh*^){;U+pl$e24eE4cbZ5iU}a2kTk_Xig@`o>y^!3xQLPTf$O~BfU~o zR$7Ru6jvL@lv7%dF9D93CM7w)9`B;{Qn-f*9$FZ=U3rK8+T)`V5hc*oF3;P-Rt<&1P@U?- zfXt%A9D_jzW6D<=G8q-%jagiD+J_7j`}cS6BI)+o86~LxM7d+Heo^nYg5wg2*3g6< zkr@Ckd<*B$k2%q%RR7*g3hp(&!Pz>IfEivovn*BGH5sXBl(H35hRpu$U&a_P#b1|d(-cI6QAsO5Wq zrs~{~cnk*S6p~f4-@FEOqnns2t;jNH@0Ds2#_z`?-SMl=q=BEd7%oL0JG0Vb%a!C% zQ>say9}aB1B5Sk}yb$&oc;sj>l=(N_B?DNBV-XpVL&>htxn3IiNf#=WNJQa5+hwrT z*R(kmsfkL5NcAn|n!l(UK4D>(n??ZnGa8P=wI}1{G#MVUN+Y$Fb%}n}ZUF|)CByMW zE_A7`{rQ-5Lu5g^fjI>Al-!$O?CT0*AD0SlyW!3%j?x&fStE#G5B*sT@ja+&+M!lUr3asGPU7B=2_z>^Sx zC#GheMoVihLduZmVv8@hDb(G=2Tgeu)9Lb-Z^BL`dnBcy^jVb;Vg}siB1dFk$UD_o zEp>)5_(F)9OF$G9#+J)Nao>kZ zo?)co0+wQ33~{`je%y?(Gx$lX#PWr2+@W6g^5-#x5H`a=s|mN1X|+?>^)tZEs@h

FUMO+KBHf?%<>IWt^ZbjB4+K``hMqYlB=@hDdn+mq+&d^h@ z2u(aBj#XPHM7SVY@46h4<#G4CTGFE#A#6=_d+(rSi*F#l5>Qnp;gHc^yj6j;poRh@}*4)opH-W&aYCn0p} zOU04CNeKII5<>3(PVkAF+L<~Tx|k~b&l!P|sq=pNo@nEgFYD(qW7?yx*xq-4= zvJi6E!NNcK5BRHdJU)g)zR5dyeOt_9F|^XnnGPOvy2l+o?^iqSPpMy>_gH=mpQ`~7 zEL0Xsb8tBVv$c&vjP$l6T=Y``DKqHIApmAv%bX=;CG4>0LFBo)RYL!!xr2C(9Sfoa znV3Py*10A1n>`0=`=<&E!3w{fx`Y_pKLCI3z$~AWM@ij@b2BmOs|2+nEO2Yp#sdWZ zEVNXavATdp@}jEf$tj}nKYsS#u*9@)JzC}AMXTMPy&?=(g!ehZTHOZI@lok22^QKK z3>f8 z!fFG(2M;(J_omUW0oiU*E_`Kps)Q8^rytq$W3^p+-jC1b5e%k!#4$ zrs$UXq_KYF92rs7DsB+y4wRf9Vt)Zk9ndZ>R#vrbgx^5htk~cB-i&T67JKZ>8_Tpa zIeGFDNuOWFO{1`fYxTl&nejBcoOB?S-EuLqwB#Dr6kaC|Va|4;(ViI{XilcS5bf2B zMo_<9(qX*7i~%}YX=#V}*9N?UB~%C|$`_k#Gt$f_`yIHE z00`bp7r81mctG`rWDY&Pny?lIP+~uDbIC6?hol|eOCYL;%6mw{CkIRKT0Hg=QyL-& z8O>7OKm;@TYM@!{C*sWCXJpXH7!8sXR-pw}aYQr2;HlpUM%8nu&C*OyDY~>fAbP&?t&%(ZOCm@hNjm88*zyaC@2 zi2oG`kDdO|ew;h)74R15GY3bgGzrZ}rw9HE&u=WjU}4HJmaOu`!r=f4gPusvOKn!7uJj z*X0gPYXerY5SMk$Fqr;kWFKZabaSwqYEtSHIuX@LWzWcT47BsA{BEY|ux)09sc;u^ z|D)t>Np08`4D?X#(%2%Dqg4#aQ*Cq3%3?lR)zfshf7Wa2s9b9Nt@2Ah$VARy!s&+G ze^833nkhDTYhI7`sTOY4so!?xq6xz%Oz3c{1h|!A9Kf_`PK(>4Gy?i=PtVP0)u|#) z3jBV|Fx0A9^AxYq0=0Uas2?fFDrUmqE2+ihs$;pkrv6W=lf<)J{PYyBTR3R+kwX|t zq*eJ@38C=pgEzWtdM~X%K&>W86y;M$68;+aB+~?6(fCsRNa68Dn{o1vsetf?kIrm4 z!2a;b<_>MO+bwl-)ervT?}Tfs&NGhjx^eEa5y{5mXYb_Z9*(}#-vVMk43Q)OxBzN#KHIL^!=+T)tu_Ja_h*ueGTBf79BDSi}0F|2!BxRG42iOd6xq3 z@F!K2o(PgSGIVAi^q)-G+cPKsluik4zDQ-33CsN)Qt{Y>6K}T8D8})OSMHY` z0T_FseSORqxHq1Emef~YJ|BJ|e1Qo2ViZ}DpElE|+d93ZA94EhaqsinqBec8x5o2b zF;Ckvd$zhFp7AdBULL8O1>BSG8``hXZOGe~=7n~-lV5}M9?kIyUxD3td9YOjJGb9F z=Z>%J_`z+Du<9$Sjq+_mKn2V^Of}^ne}&qHD_a}YTZxwqi<4_^6*c&AN;iZCTaTfK zV~E+fkAGb!do_C@7&HN7ig%EW9~l2#e4x)0ZWw>>ZBqgMuf>O?t%JRj%YUE*>Xt4R z|DObrFm$%~Pw`Qu0qub@g7yh1t);o`5T<-sDkMXMJu2i!RwpTd=?@Np4V&>pa?Ql2z627cJ#J?T*=;o)8GoDOtfvx@P-#xe)!!)nE?ZzFnc3GO!^VE> ztwP6YP%~L>!LEqDFmovxOH!%DUuNDGPv#(_(R3+1FY{R8(UWgpUwjD84A($05yygc zkA0?Cp|mk_l!>^QuL*!_FsYs8jC$JI2jBth-(9`tV z%`}B*5i76Duqw>L;6IQ|6=u{fPoh5rf&xzaEgA_I=ZlMYKGSp-O!I=q&GiDHMbE7f zN-Q{r1VqD1P(2y8#n0EnR3+1>msi=&tpV89#l|)Bc1@bcG?2}zB4h)d%E1K`w9gM9 z>_&C|i>QEfDN)ZKu*ou7l>4;wmZ?7#dNK=6o98I}U_JrX=4$#4zvQULoPHI8=F{f= zqA;M|wZU12zELDn?A^(;9FC!cgx*tUtp@8`k-X9jK3F{r4EM9iO{gY0i3Td6@Yem7 z&c*;H*Cy|-NsB9e>TswBZH##Qq@Jju*8WNVfu~RoFy`GgAk>Iro;J(MtG&!7_orTh zJ9oe?xtD5%sKs>_u8Yf5Q`iMn}K>-DVUW1hz&0KPm@! z1nVoieSi)3E&A>JTcZd$1^&Fsyl6;}KMN)hpq85JCk42pTWtLlZLe!Kw+ZMKYg^$_4w9JA;m z-1)h@EIc*B_S;9F`SL44L!NFwalre7`*%4&|Fl1(CT=I`?$S)eWS%APOQYU7Qgq`pZU6#9AOGNQJr`CYxk9k=!6; zS=Q@sB-ft&f{J<`1w2CKiT0S2vwAeh-irl}W1nu>yPR$&zQSdkO`|T0vO^0>@&FpX z06=tL>c`uy2hMe`y_q2p-Gb^vto+H^WV*wwQ}?2+wpNn3e}ZS$Y;FQaA;aQ=1TF%t zM{<~8uQfa1P(U`g-*c>0mKuMi?Zh(ZyJbtsTcQb^!1Vjw6N$9*w=jN)?dS)@Vtx2y zd3-!eRz$gIR2B*MZ>?cTC#FZKAY?!VElB|`)~qH`At6Ii5x7MGZyFB=`g+M~v=kmu zf-`d555a(M+^o{8Ewc$6twh-@eI5m;T<4P08=4MMXOuO+!= z?Q-J^X_jS+;wCeko@rL{DawE(7N&!G54!Y82bQcy@MSfe{Sd0;__0XzGiJWlq*=Q- zmN9Pr{@NxR5Ssa-N(@@2G7Aq6Ed*GQcl&2*kI5XRKld2)JWj1 zIl#KW$a*%6D35W-esZrYu0phbGWROVxCD++R`tivx>lP?t6Oo3AY6ylfnUw-LZ7BaIw|7AF6BuMl{;EEN&8 zznXQ=P@d+kTQ3`Oro?t2n?_JPvbmWsy=jStZbrc?`g=W%Z_6T1O~03AFsCJ6R$`I% zfuLf#A!*KOD~%> z5}Oyt?W3r_r-(#xF7B6guG?!?3PHsuG|0f>@d~~wv&snNvy$yGAUephv@7jkm*OM1 ze9SC{Vb>8JFPkb-<^>9461F$@gM+o1f?qeEwC(nZmG-N=(BK0SQ!15*Zgkm})Rn$8 z9kLp|6Z@dmct%|JKuE#b!Xmf^1XV%B(~Yu3$x!{3lpquPXKP}+G)^!21Q6{77Q6eb z`?viY|LtVxFkyNIpl1G>>%^YG#tI`A4~qbDT$DGwUGyrNibOCcw5h0cfYmoa!WFjh z1+yEWm8{(Xl?rf;Y#*?X`bOV)$BOS zY~i4d3cm=y-%mLmzXP`s;3<8h5`+DMxL?o5B5?2<-W@noyHAZtz@+eNtXM?M*F1sh zVA51Pf$IRlAVc!rZ-N)F$QMo0PPXELURPixFqK{yd#yYuBA7p6s+z!bkRjArB!z#6 zP*5Pv>(3+qFK!4gJR&OMZe{fV=@l<#=Lk6rQF`FSzTEv-04eW}qB%`d5A-~~?Y@wYWO)b)-qWnhZdutF$rMOs;JqG z%wd=^bp;fT09%a8A#YW8R$Rq1b|XXTPT|blGmal4CO5^)l|NXJ|1W!X5#N*Bg~fed z12n50P5p(=g?Ux27S&Un2IsoMvKj^$z4UXNFnpU=$KM@4WVV~%A3yx|yz%@(iDplv zUI?Om3#*WNfJDT*F@vQO-xyFD^1qU?AlU*Jb`*EF!d{tR6Jf|=(;I2@o6xWmE&liiKgaWaz36_wXIw=smg)hA+w;($jl<&` zL+_~)RY@8T#aND(y+7Pdlww#37GZ_yv8B(EGm6j{x zk;QvOJymGq<6)^0p-$u?$_^F^xPvW1PnWcx|M40nBKR3bG=lWOP&AQkHk3^9bM7O5 zJu53KE$emaqSE2>x~c~(bf+fEN|aAB1Qe+gUU*aOQbt=V`j-ZFvks#MY&!}UzH!!) zi->>2(=~<0Ur;;%@l253zFh^sPI36h%zcniCMMGJ|7`2w`1Zo477$VvWHC?WhZx)+vQ$3 z%GKO7lle3Qf6aNgEixor?jlEcr0Mv~(~eD(&F9YIh;ckL8t~0-MCr+eG^meEoMpG=Dwz%3>gaYofMn z=v;31leIf<%*nE$=AjMbI@l(N z$5|Tf<9{P$I!r~}0>%Ymhg{p@CF^oP^^g@bu+w)3@B5t|=G$u( zPNvj`Ss1E_0rU-Ot}K5WvYjeHzf_7&PNLN^Gn6%%iTm?d&6ddI^+v+#&-2rF3NaW` zB;_neDzRTz`e}h7*dAPjr5@3|DMT^519P5=v48(YZAc&V+MT_TNa^iQRJ{`Lhh$O1gD&p`bhZY z!+c^UELN^k(K|6CB>$cdXwyk2R)nzhvXl}7NC2kKQ*3HJ=BJUWg+A$vp#`C?-~UgM z-~(g`-iHbThI3=)CK{6ZJgaG}S)Fuj>ET5Uqq{3ofcVx~xGGMSOo`Y7DH<=x(?coy z=4&ta;o2}6aj?^)w;yYBU*iG|M`%^y{uonY|E5h6P38JvaGq~ABl0nY^oRvT=`>F9 zOQ5@|98rEMke&=QfFrNZW`+d<|Wy+j61@43SB;kl(_gzyq_7-Pa#8z~yd zX`Jva=41K%Y-)fY%(nERCKw;Ae?)XA6=cPfAB3XXz-2{|ElIGBW@5$H5&z7uU95jj z3$L}n)UPl!@mgu>$7~BP;$u4tO)EPn&L0Yto-dmt-C|;r96f_=NVZtfQJ5Pk^LCgu z|0L}myXAl|1ae`wU%!~-N}ZUjq+>NZg!y*;9BVB*ZWiBnP|<4Fz(-4#w=Jlzvjk#u zj^JL$<^UT_#JvpKTQ7V1mJRVe@B%GR;G!r8=&C>)FBR@)-lrxz#&%cUstlow3N5a@kmKh~o2v?wKYB?aF zsZn}VfjVF0t*d~{04`rByCDFM9SzrZYm`I<>{rA(T-~xg+Xbt{Yt|S_Z%_IV84x+5 z%NU-(=hNlu>Ptziw_7k=`>MOWkQbDl=o1}EX?P|SNA*2FPsGM&JHGv_+O!N0%d^uEf(b_}cNUQR_afhkk#t5ODpZJ)o=P=w?J7^XcI- zL)>uXyHYZ~M&S3L-N_o*=K{+X0`d!@K0)#~go1Z2qjZIn-E$Kjf&4?VgZ2XZ;AM3r z^#-GoUhrDMx_R`?KTc>!%@v;7R3 zc_g8;F6vlqfKRTiyvHMkv{(|x8w!G-XE~uxFQ>Zw2gSNyG}%ls z_KBraZt~i8!OZ|j^AXj$P5H*aAK`g}|4;OHDf}|riqPF>s~BHrp)vZ;g5L&jh&#tG z5T)B>e12h?J7he%R*x9@U6F!ow{&|xz|D7CTxFy4k+!Jqofbx9hkeghxgR$>+b;r_ z_9bZlB4+r+j829)I>Ud?tl18xLH^PFSK|`DA9IZo*@@r5^KbB0B58P(F0!EKTtA2# z(ba6bVEGqZHE9#EX2*yL%oJy*wB>^_VjgaIj*~qY=krDOkS1e*r=Eo5JI6))od0R8 zZ90@atbpG-^EKlagk#(4Kq<%{RIZuV|8l_dlo-3e_`ROB_y2D_OZtB{bKhE1H#8v5 z7eaDpeIut4vjq1pEx0x$cn~(}Uo>EHRPTz&9ZF{5m5$DMM3H`-4vtjdWc$^TUZ1C{iwWQ7DlEpFjvu** zU$lMr=rFaRwfU)iMON8nv?S*MPU;G2MbhMO+Hyklili<=SF&Sov6VjUl9~u0KSggj#nmIdO~yIWYBuBZm*gqbHqAwOMh|BXYvW@D zq$$d2%L(9=NsQt`W#}!mzOMy)(xq@fq)QY5<=}=FayB+T+k!mbiG%>G-J`eU95<`E zHS{gF5HCIi3}`mMW`qqhjp!Z3iYNT*kBkDY`(l+b!&R7#4m+h!e$X^qbmJ6&0$ziM zk%ndJNS@STSOp-H;3ZKW7KEqImX}c;pUwYNYs`bgKthoK#V(){nBq!O6bW9^I5PC7rw5Al_2s9|G{xlRM3&*Zn3{w!Bgav6&K04P ztx;t#lpKqu$gV!q9^%HR5s7LkJjO4urRa_s>Rjm{q)GIpge$oRhWXNf3oMGig{QXg z(+bj%%at3T4Kji??XYkvW7yCZE=mI{^$Cp^n(rK}XNUm%<-h&WSt}H|>VpoA@{4BS z)iiOfiBiZdglN?DeD_1O?0FO_ETgf%p~8}Bhggp%)2Fb|wIm%f0oQGU;(G)guyQNY2_n8y_M+hJ2UmhW~4!leQb?CXyXAg<^Zw4`RVo|UQh=%oLO*9 z%?ttJC_H7c!a$3lSZ#rc*<9d*U>1Cq{A-qPpgA9o3|^S!U$q5_A(i^az1a@Bj{WGb zR0LbWLrqWRcFCue}t%i6SZ8HE9DU&eWrDv# zD44O4Ah5Tgm@{5cXSAIXlazr{WP^^!l^+$o0Rab2gD$Qs$+mKN@Q=&O80j38(xl8A z?5eW`?Bll5#X_74#XRqwD!}7(-`7BVwUlR7UYeES166X7z^^@(T6dZX>JKPbm-V_) zu6Tmq6_IJ)`drbzYn}Rl3?DoGDF7&4*(m3T3r*XnM}_MSbDU)WW$;GUFhRHoNJpW!($&18 z#}!UmuCMk@Ypc!f58qEcZWKfU{;FK{!Cb^@&`r){C@}}O?HZb(YqgzQIlut0EZg$M zRQY5zH{2Mw!CtYgT+w^zNY3mu@j#5TS*_|GGzLrTc*0sY&i^WzPnKAQe#(|)GEVBG zn2NvazLUcJ#6Ry=Rhnmx?(4fIzz0P2TtBZhgdi2)>iAhP{o#({aznd7DG`X`QuIgQ zwzs_Ts2MnIRieXOOx8%$2&LL_=cSe5`Zb&`}$%(8f4J?-D!;4nJ42GP!6AVp>e5JRGOA%6@go; z4ViE!s<1@x8uO)c5`3?o?9L$}bJ@@KkH}t!cHkRRijld4<$4|RxT~gQM{6bsa>O!z zn7`;qkQm^^^#NUYhPq|G}f=1 z=kT9VVzlXjf6U%{Eee$49+cSk{SDD@jFmZ6Bpj=c*noWPPfD%L!8^1-VoQQ3NS#mg zpZint6*>D@2MZb=$UWpMq=_t1&m9L>NtHz7*5=}99Rxl3>MS~14Buy4LcNYKv%-l6 z09~OPf3ycpzu@n0l@`s8C#c`gX2*XJz2FPH|3VZ%7~B@bzP`jM1ei>l{C~1GA8ywg zQ`!VfgZPR^g#a&}P(s^NgB zHHr43ng;!1j`{kpVevC4Al2YIECRoggTnv4an%3Y9Lm}YZwSW^xLkcOS(M3|0u4z{ zWsDYIJUDi^0;{pN5^cukE@QhiC$^>aA62Qd9<_bmx2}T*B3{KC&3h(37U&@@D%yAeV z)uuF#!Sdg*jzr^l(r-$!R6+yVqiuj3991|p9EwF*aL1*rbXhZITPh z0(mA0+y|ZOZKys6?}M;^Chs?+A-YpcM5eP&s-;)Z#FrRg;wI0E8CbTmnNS92Ez`Cl zF0)m7G#&rWtZozFPU%bo;nkzyV+DnOto~wMVr9qmt!()|5=(b(2+~F02qzZxTC%d% z)<49NS4_z#K$~S8bG~ouOtToQ$N+5St*<$yQ{k)_!!W?xGOjObY#j2QibJ16xNtHr zK#GDHl&(-4Cj_q7r5Rfn{X`cIZ%S^)(SRK9WEdnDJKGvYE|t?FQH(=_$;N1>=*%)+ z?~GmRl$6E1(=|z{i}K%cMD|>2#LtF5n8)Qd(Frh#1ceZD5ZB^v?dsn{E$vzf%rXHo zhO53JV!@I|Gv2LZ6Mwg!_AD0xK^EA>XxK*}lh8MEOE^d?)oM%>ocTsfS7y*ft@A*H zNvC{i2ktkNEZq<{OY*DKyqyRP6_LgZ3bL%YOPrIHoriMBFL`Kcr{9P* zqp%k0dQcyAt7vBv+oZKgq_h?0!AxS@VpOv!$70rO+bfygD)RYR!sNHWJK1L9D$dfx zu+;4wGd)QjF^nJ@>f#IHfu&mG$)I{HBO^RU!*HI=)A9K%1{+I=BKjA)1o;VUv`J## z<77mBcTi65Z$R?YJdQ70S9~mq)ui9+uef1v9PM^QJ4siUX83j{Tkod`2uWc?AkDK5 z>#A*9|AceDT+*Iy7d{<#o)P|ao{MlA{!X&|keN=_MQ1o~n(ITOJeCRoTvhLS$cJL1 zWaqcnPYzPoiy~Z)k0+A$=IRX9{1kErbQ2<_8(|Vvr%e)QBwDXhsTE3SBIO%p;Zmx8 z%9P`MSV84{yZBq?A+BQ!X|cn3ScN^dAjb?m={PtW(0a9?Q{M|DiHc_0IQD7e=z)7^ zO#^fap?*u8r%CO<&vf9*P zF?>2E3w6+Qzl#XD)uIdHO@@(qS`#oJR ziT)N0U#MwT`ZoNCCw7t$WA*s;_MwELE%qx+j1~%k3(&2(CbvA)hX^3Xx}@eGJ{nKz zDKH&g;5kvppULw+OTx8l59dWzY~0*|We3XBPWsb1gsrS`c-IO=H?Mr?bQqPqIMQ^i zQ1_`&1&xbR^p@Bu{t4nD@FASM`Al{ndF4LHBf~yS7Y`a{Gmo#pTHa}g=nt*L48H`s zo^-mXK;bGpoPMWEy=i(twG4-*a%6u#RF%e0Xj{ymlsbv8N5MP7h-O0_ep@*Cv;`3< zOc*?t)zyq|$sBcw94+B0EBZ>ZFBqFc_Ycbbw(xzg-c-9S5Aao}dXSFRcCs&`&RDP5 zmo)1Xj*5kFe-dwoCo_|FeB7PkH8rx!*1`Gg&vvvUsMsYt-9GE^+j4fn+N%93ZKyxAGxg%VSyr#0U4BI6-*RAy=8!1wA zx24nFbCWedip&Y@v;En_h8q}x=%3!392ik6q1SU(>V8bwK!{#~S)VX+7T=hsBMJwn zW1%X?)dmV-1V-x!IZ2}9eo4~ zjyIbph`3X`#q(Ncj$SxXSjf1UgY6LzDw}dDzi~zlk2{P(Te613;Z|6^Ky;ygd&=dz zqw+Dn;rVu0pQ^3SW%r}nCh086=N72*{-vm@*Logjj9G9YeE)d-2cU6l8S}FS{q6UP zkEpnNr4h633S-c7%pVP%&5mQ#=>2JsbIQjF(qXYg2>epHtfC75OXhi7o*4XlU&dpl26yX{#?1|Af@DH1zpf^L4h*vsRq5p z=We~E%`Mr*9kt{Joc9!UtIuN}<=J-6HuyTN|M@)Fk?K(8;DsjWq$SA(d)UMdXmcU- zgRc&!qZX@!MaoE)7&L_YN)#Ol%}XIa!;OI4USQ$tVbA&j1r-vSD&&UrH`6Y&pUiaI z@(Il|FW^Se+xio4pb`?M8C}{#psPhE)+EC@@lq(dQocdp0skbp57M~a`0Q<7jH_(_ zCuGRxUly#tG4m*1@x(qTKmFx!iO6jwtlh$v3cOum_J|uXW5A*ehUq{z+23FcDAbl4Sn9th!e>k@X5#QEI%_O4_gH0|VQV{TlO;&^ zzWS$cT%}$hVA`R^@X$G>MH+$(WnP+s8o@cfh;Clf)3$u;#;PHv~j-8lbYk$9%R zAzc+CXdZY4>6C4=?|EK1SM^<=wvN3N_+(RU4vcTG^-L`D2RLQ})I1^Go2GY*8!$>b zhaU3?ImR5?-EXKoU#HQPZm*ew?f(>AZ*Ra3)F#ot^Zg%$!tcEHKTti?|6k$F&C1%v z%*oir?brXo>G1*JYugWwJ502< zb0cOlGJy&c3PRLE)~Rc!?@bQbOlG(8v`EXsfTnU*0K&8>u9G+`B#eiVh^ffg9fJeI zTt7!*KE`9z4n50Nl3aMW4W}7-HK8Pv*yja4-+XEe|6D(w|Gr#{U;-;0FdVOiroi>( zmD$BAWxkZ+Oo@xOMUtVz8!dGCEXHWkJAJ@`!3pEy5L-|8E{Fn+;CqtXkLt32yp1Ld zC{GTas)SQ97m~yY1O-`k@FbW&!_v}LUUlimW+kN=IEy*xQPBXRA~??1p^t-Kr4=6} z@@FSa#k!GhF6%zpD}#J7&a-ZAvR9b2>xaP4muHlf*3-%2>ZYo8P_Ga3XaxOl+L30$MK8p{0^ zw~5KiOLMXAw5N^u39j7}?cJFvSZtCW5z_2c{8(}Zx$-|HFPkEWrUd);me}pdmb9QF z9D*LsyJM+wW+{wB+O8!7I>0>`N47Jba7P2n_UFM?$Y(Z^<0UkbE%ALuXv7&Y{khnx6d4N9odhS3J_QObHDaz&REw)E+O zpIN556)Yw03W~AYE!H8dLvSj(#)+^dSd%5>S;u{3M(;+VQfDOeOf)*v90L`Xd6NM!MhRDAN;@ZJ*NSPB}LNJid87e%g){_vMI( z7+icF?X@AG(_63{Se@NC*;=Jl?&hW9Spa?BEeOchzg%Htwd7n)J+|HS8g?vb8eMpg zxKC$RKkiL0Cq-fCpDjGR*zQF^^U}ICqOF$dklsgqzRO)U#A96z-V_&kO&vfIcJ|_L z@&ge3g_-<)WF5<+UTO3TY$RNeDEfrbTnt>$wlCp7DmDgK~b>*na-h`G>M@h#? z*(7~jLR70PD6wJ$wj`GNRn_K%$dPj>5zaYl1f3@ux}Nsizar*sWW3RP*sf${b9oxO znEJhm_=k-Al9S`nG*=k|KbSxXl^?D#y#5V)m@}@BNY*$sUGOtHJw0jr;ZYAw zG*cRvkbO39=ETSStF#wKyg}jf6QSH7Mmh z!(r5)K#t6($*cRq6d^lKFQbp>V-NV%*@<;Mf72TMyblIydmM{#gK&AWNn7iuFi4E@ zvv7al1sd+3pCs#f9W`fV>bx7PWg)MfMlst84@BOb6f%pi8ZhAnJ8fretYE`-i_pSS zIV;^&14y-y+4Ra2KI}17rAU5}dDxt~M9igL^`iRivr0b4Ywz)_V0~%e6)b^AEH>lJ zXyNi_f=+A;9vPvf{VfelxkZ&n|5UK96pP~7`u zl9>r7H?*`2@idWAO@-=4QrZPKUg*=y1+nHxZYU=*Q4*axKvuTsk#TwwHh3KzX^LA0 z8qBv?q=IjxywqF9c<4Gc152XhLY9JO4C+ zUDXzdV7#pk-e06yd&kQ8q&J);kk=vUJWWk&P}Wuss43m8NB~s84dgE=`hTh~a$qU5 z1-{!h)qZ0IRsiHo$#O&VuvMXDhGIdpdg!b<5 zb~?o-WE}C%p&S*&eKHh(G$;3#AW5p8`LPHkLN!&uo`@Tu-h^KRXksXEn%X}>Z<~q5 zrQAA=@S27ed|}57`6Tn&<4+lW2HJuJ_bEp2u-PypzkNV=uS~WnhJca1jG3JjHTfNe z*BZX=fa-5YxO!o3b}Hhd+S?s&Z|{VzRURvi9xH%*b?Ha*vRZw=p>=FYH(!XN5t`um zp+kku!Hy<;f+G$X2fSmSuTzD3C!-!aMYc+Y{))Wt3DW(bmN4E?pYkI3{et&)>j}S; z-^()oKDx7^EdEPz~_4|KW>smZ7FM{}vQ2)@r4RimEDE6(v z{ZEQ=75OFpAC%(%Sm$cediO-vB>39sv}j06*`c0M2oiP7#ClZda|f?6_%6 z*5s_l-=~D^s&m*xMr{8Vb`7e6o;G+t^==GT-nJEdvJtPfR@E-sk6$QT%au)5v^M*LfoE91hDj!UUc>3g70f=2>&~tg zOLt~JFakQ(XmcCmBYkGdjI$8I&cELpF|k7y>J1qnlFb<=OfTi%(#WF~J& z8LgaZZy~$wgU{vU-`lV}eP_$w@LOykBrG8@fsHjfVN`}}pQrg2ndo;xh}jsDDO!Fx z_Aq$)y4q}e&f?U)e9umBt$J>`V@ZD4xfk-fR^g;u`?hx{VaGDy=n& zr@kv`q=T4x(n=m#SO|D}STV}moXf@d*mAUODN@k^b|MdJ^r9)2hwaJYY>1ymk+>Cv z&2R@IGnx+8AvPrtgy1i>$?<4w{EHC-^Q9n{bD@z%2!6ogy^}Le$kV5Bq)`nZ#QkEx zFJN*R)kk91Gcg24i?cr%1zA<)XZgOV%)!_k0fcbL4p5b*#OS?){+)ThG~Z1KNM(dd z8zgL`MA02qIe{_}+2P_UBdPP+YVpwxcAk`ics4@K6x=X(FQcGR$v_a|luD2pwwvBv zp2P4B_Pk4oqt5~2#{RA9n~eja1QfrLgRZ5XK8~6C`Ja8B0ZIlW1H4!IZV%^$S21v- z=b$21(1*Dx-T2FWi)0KoIu1sV1u2xSQyNxPwePJ7V)kJG{YgBQnFv=~;T&EpQ~;+; z1lm+WbRd=b4?(KhJe4B`LU@ihUjrc4O=E9VcH{inz5l~q^rdoBmi?$W4n4`eOl6?& z9}S~(j2*g|BC{>aa5gZRL&lSWum~V-H#M^tN0J*RP(}IDb@j!EpeKY`VcO(_!o;(h z$DGchwBL)+P&w(g0+i0ehwtR($VvkT*K4k6!mdpRp+Z=rHju-i08>yMo23Cl`+ezE z00loL2VP4Zml&fW-CiL*Kx^1Hp(}WU+Ep}+q~DW=CY+fj^!?5lfsZ9)@~G{i+Qh?b zvSv4)BEKzPMUKp*bNc}Z-Pb4z2}-8PF;9=fGV$Iocev(auOFj<5x83Et}Ikx)8cfF zA!X3QI8~YfH`OVyY#FOgou-MB8nnzU4Bj+e014W{uq3AS8i=cY#!W{T580KSC_>w- zui`;@xB$N=ll<^RVH#Oi?Dy-@$9dGs)C2L&;*6Q9oj8TPb6D9T43#7Eux71jZ0<%k z;Bpy85_byD%@C>a+kQ*8tk-JgB|Sg{?of@4+kBqZRI;mxRaPor@5s5BtLmg$4^*Kz zOMa!W%xV{}cSt)%GDVceCnE*JXRXY{uj7x{4P9>@cvXejgJy=KO1T3&sygP`Wx1;2f853bnWRuH84{Z;NPy#yUc#DJBjbqj8?VC}NjLM*} z$0t<##@H>X0J=?OA7*M<3ss)s;O!1j)^}RKV4juD8JGLcKAlpu236j{9TM64CtA9L zlU-*xVM~?vHdTUuk;H7tnk;K6b=J$aYzy48;@a6-pXoU-Gx{a-b;NAfHX4gUp{DXP zs03z}3K?~X%6_)6=ylcQwdL`AkSP20ToL}qk9C3{ zKY0Ed5#vA1E$RQ2y0xhN%NP9-1f@}) zsw2V7sG(tEf--k+wp_eUqY4v5aV;E+8bp+qmZ8bUUSQ3;xoK^{#`eN7G~i`npm9jcF|6t;wTxK)EO6t6b-3O^hNHDKhT@be>!#ggxB*${lw&EonFumvTl1W~fq_X+Hl-w;9raUaV~7gEf$kt7a-<2wFyq)Rbln1}z@) zpUbWENaUGB1?sF^liEwDlZ``sp&}&ry%I?Qs(KLr)UyspxmcBvR+LWvgG84_(}Gre z9(%GLJDS<>@&_#LfsDf#>;BkzrcM})$aw|L9@yLYVFJ=@j5LPg&>Zv3Nb-^Sv=#4E+yoY zS<3YMS$ZRRCT(D~LFEuH4Ay8Xb_zUXPkwAsQyI*-CD7E^L$1w4XG)O7!)=PYvUb3s zgj{b-g|PddR~=yypC?Es!k1&x;F@joU&NFaA;@FPZClwl2tAY?Q5fX8zkpFp=-1GG#m7$5hu{9Yg0Dd zq7QB@qpbvIX@Ub-M zb2C;FSuz{C5NJ=D$-p@eoaa5kYWxef{t2^T+;AC#ETsn!93`Ur))nQSQ+1bfv zK6`n(D3#gKCDvrx6^a6|jy6AWH^gIe+I5}@8s>K^;)2d+^{HXW85-l7NJbW9gu&xo z<=wvFG%~TRc4lqlO{#qs{wc<#A=|3>&jcd7w{S+)6LfaUZJ__a z=;v;1*LKZLD9j}lt6z8y%T1aMJAHn6ixT4`0wRv*fY7_xC06Pat8pI+d}TF48B51J z0=dwAXRA#m-Q`yD4Q<|$Rc|Yjc!`t-ZsasZyZW#Z>Xqn=`>=n&8F}ML+u@MyOR!b> zpV2H&!<5y8zb6y+;2p?4@wdfR@BEvenf^_N<1bPEP3%xhnN@@lM+J-uFN;>-b+LwJq_O+^V&R>fBGb`+_iO|=<%ZJRW0)=7yar0BD zSp{P572+H&CV-`K>~j~f-l?0t>6eJ0p0Sqc+NS5(zl>=j&F%EtxXU~<2(FrFl6oy9S6@qK7G=7< zs;J1Qejn1`kH`&5sVG0GI90LTQ~&e{xeTOjdyOS>Lsg(h;4L-Q8Wcr@l{maa&CMQQ zV|_{)5(^w-J8uAX9FVca%u+AA31}&SY{qj+!RcvXy~unQO6rluT`aR`Ilc_)Xg{;R zfuEs#jtxj0^U?iRuh~#Xq^TvL40;tF5ob(;Y4qMOSx#<==T{L zLYxcgL@V}`Fk_cQEBY3_fkTQ}FNNv=bJ>1}Fv@z41n$3Z&eLm&RxZyB%}%xT(+txa zO60eu`7IJLcqC17XfR2eRKgaaTy2361nDu>c`=V@G%*z?&5Q7h2f{&^BejjbaH7|84gS;CGVh}+zrf=IY)f!Tk&2T5tozO(T&-HwbwdI0h&$GPuy8v_7SdkY_CS9?VZjFc=hMZ1z$T3kA@vvev~~m z#H4+U|GDV`Mr^fsILmB_=$;~W zCcgL$sqa*~FYgx(dOqJfUS1<_9W5LsmQa*pe&GbkO$<>>6iKJXlxg|nM2a@xgkk1m zkK_E&Q3{Vx9xk|7A#|Xtg-L3KI^GpDxHiLu!ZtS8J$;j3*w@dT9%%}M{svJp7CKwy z;yX}T3gyAFtDuQ20GRbblQ?t3IPd;NX^HEin2(aVVd2ql2A)W7YcBI#~7Zdd}~Yr)t)m zbBuY7zI?U1A`EpjMD^v0)rNX0Hgy0iWIGF)Tz}|QB6K;d&8dOt#yYmwzSWrCy7m|<6b1OvUm^a zB%qfCShz5!5}xC6f3gck@L*0=)-T0>+07z~fhoYYTSa-1nAFdV>6$Evw~mf%eD%Ko zaYfH)D})P3aYzihgbxM<%=+TMb7tI-f070c;V%;iR}1^*$J8AH3-R~;Sr5-EM*@M` zU|W1Ct>t+A`6MhK=xUtOShqVA*lyOHDU$C0kYz7Na>&<2mP9CSc8~JVxPE8;^=j79 z_KWNG$a4P;SD1Jy?r=T4sRM3#%evuRWR{>(6R;UMS%VY}%K~8}G2E3C`)K69iEqy2MOw`szvPMs%G`@b#Mvoq zw$^Jz9__`9z9R$l>{$I-OcoR<>z(w~R><{y~O_=O3CyLr>)dYA!feOz42J8ld)uUQMe95QmHKhK@GvnoIA-b6?x~a~8TX?Gyd$C;FifLt$TAC)I7&3ns zo`GIj!u+~CgVS|uuPi5l0$W{hlTU2x(STUJ)K=HTeaJ~?teJ&PV#(ZT3ah!q`t}d{=hZpbf=W9YAgl4+l&1tj1 zhHx#M=^XL{%*cigp-xJK!6Dcm1^CQiUtV^!*;@PAtvn@Qs z=4{)kRH=<&vge|G4;56a)e}MTY}3PxgCpVZp*AUIb`Y{ZFc_D#ba3k&Ycvi}?YyR| z-^iY5P!vh=-+<7hFxdZdV=o=}Xn;$>PliM~1(a}eA$hqH!-av6z#vxygS)32xeQf0 zQqnh_Z$#DH6hDtr4DB5k`lDO=ZsA`VoeqO@n?;HI>swdM?xeYz>BJWxYE5aGLFK?_`OKU`WuCHF5Yp-gu zF+1PJiyKBrUe*&9D*g3w@X;`HyuQoN1oL0=^1HLJ-QA5lp7 zmanN?@g_Yg+e}0#N#cnLUr0hawz36Q{)O4@NPO>9l>4X8WG3)t7IHQplYAS zu`RpX#cM@8sHb=zP*$sV)m+`ppNX7mYIaXW2Rzfj*y$xysecGu6MNRvLT00Yg(e85 zRJ}5c-=o4Pxs^EvGg8vG$xF=)P1_XHMQ(Md_c`33j{DAQf=+d+eo6jys3=b!QUJ$k zgZVtJx?r21=j2uh@e&>0@ItQ>A1^i=@rmQ}wCPB7%WHs4+slQ)u>X&jj~i8oCpvjq zo*yN~5UGXX*4b7ka5UBV6iN9V`L}S$ljWI%tV9RKySTnjUD3`RIWT6#wa{>qgldn1 z`cr*apDL~|Mqo$|<2@~0U_@_r5N!|^xFJ4qy)4ap^@kzh!emig{Hrazr=O;9lvB}_ zlLLa`vnMIl=moRkfoeJ)aJK?xqg2(%Zl>sPB7d!nz$JF3)^^?cQ$a^c%xLTQsw=7^ zog@pzZAamzCBba56eDu9lakp?Zwlr88Eit-#yRNd3w(waO@p>2UAhCGM3FsJLMvk3 zv1111RgM?hEh9}{S&CeS2R-UM9b?^Wy=%=1j1J&*4I?BWrsWZr%cTYb`{_}Me}J=5 z=K!A9B1Vz8ICOAK#Ftz!(&LC^Z?j$7RP6#sH@iG`)Y5oqjZ1~@dyw0Zoc!?B$7;2N zWYWYsPc+V204ehm4sYMTj1x=HM=3dOw$fMZNdDdw;%h9cOZ@!E-2!asOG|%p> zf8~c|(Q*N;X6K!3njt`wSBw23R%f3)d+|(LRa2rvD#Glco(1Wu-O$YN?R;8BCXZ$e zHoKP(!8CGXlh!tCjm>pj3)dr_y#PH=0lB9k3%_H9@NdYP)+$=OgnQ{vc5 z-LeK%r}hYI2}g3VP8PZQcCz#AY_sckM%Ly=y+(|AOX>~cu9sn2uJ$?dg({0%Q(~8% z44bGScZABUBcCC!V&hl{VEtBlj1H4`1dgB>KKY0f<>Xw%G?>VvqwMP z1Z^QQvJPQa$8~Z%8SI#*z|)lTnC&vdrl%egA$8)=W~#7x)^mprP%z7z8iqFRE${Yv z0GGv0mhbth){28X$_@dfnNGJcob-TuMTvHZ2WyRkim5SDBVt*KzqlD%yK?lbX3<(0 zeO2xv3Qx+yXc1Xq5->~jOv!mL)F8`+FM6HR&tJC{KTh@xyfC*9@4>v-3lh8jXll5C z86uBMNDI(YK@i;V7@afW{S3TN-<=OfRiF-8y9;G@MD3T-OfhPDKki^LgA+#Dl4+fhhHeY2UqcH%O;w%tAeGCzs8YM(GOdoyyuT8+A3O#vz41g zJEMn7774DsKvxBw$tw?xVNBTXGfH*CId7LU0=n(KC9JxmW=t#R3^b3I|3zzcK#pRQ zl=r~gm=<)|p4mTEU0c=Q3*=+l&V9x`=Gw_V$#Y48>`Fw%>#br7FTF*~r8(sav5&>U zE4}^ZAnxZ{n{9^u%!7^M5*ECUShdFyYnR)nFVoq@r@nye{0-7j z$f~#74VnnS&l!rOHr@9eF#b2;WfkNpG9ATT9x>sbLT-0Y(EACy7^*A4-4KNT_0uql zf#>B&&|3xZ4QvGJ_Ax8di~02Dpi^6=*^+w^LV&JC=K*|z&dU#ce?M7=7S8aKhHI9G z!iO7}%HsxnepY?N0ZStAT2QUou*=M~V6C}mO50&j=U&rml&9?g+q=#n?dM&CY;FRR ze+`A+s9v|Mwgbql<7*)Vz!Qf?sG5AN9ueFcqCRoS@HECDq3#qyGS$7@(m=9hB0Baw zIp^KjQ_-H&zK)=<=g@&xrA>sgTd$$6R;kUHXRf&ykI7Dg6Kj`p!VeSs&Z%_?kY8#CN$ zI>qtC7aKCcAg{B+Jgx&4X2VgR&|hy1EfYgfimq@jyL_cS#pe?Q+9FFjEM))uTnQ94 zN@7mt@>%=IQdIYmXhBNGM7EQ33cbG9c}z0zn{%*)dT3%MP=zw7!JHMQb6(Zk z1haCIlcO&hTn_|4PZ8R5SpmsHN)$DNENHG|&11um)^)+~78EK;QbqTxG2$UK72u2O z`B1Ex@=Q76BpqJ=vn4OMdlO;bgl7|WHH?p<|UtcLtiHoXASkF6qlVQf{MR#067*Br_!E5ACM@|DD>2B-( z@xuEyiRT5JAqj2~@+|8U)ElXtk2+FLun z-!mE7X>zZ61BqWO;dh)Y`;_0@CZ?3FXTZsalVO;5kDuNsN7tv+i4TeIQ76xh)rk+3 z)AmqmN^SBC((E(Au(o$#-Md2K0jPEF$Xp~;7-bHrqBrb^aAJXbpmNUe#|wP)-uDhw zT41`9H;7@E7LxLrmhF|;*T1B6Eae8aTe?l|fa3+WWujs6Z$)Hd4by(M!6!aegncqj zw!XHnj`FTX9p(<Ky0K198Df%&Y>VAPMy)WgWP3tuVt!rFZJHoS${abQF zjbUp{`gk|SMFmNDsUewGOYIEaqGEL#4(tNwuQ95T4bvP!y!Jxq~4H?{U9a zOe+`hj2d+NCQg_V>5;XpD0A z$ne*@c}{u%PzmwZ`w8pY!HiXdowp?0QLZL;e|P``BR;7$svm2 zKqnu6K{&u1T?_dg??2~u6AxX(1PBli?QeyV|GzbD3!7M*IGX_Nj7^;WhkYqabwdeF z4f_jEyq>5sA2cYpxo#UQbVJdKtxl6(5Db)9@Ef&5KSoq(?Yh$NGgen`J&oU`m?i5B z==0q4B$&yqw9~jCP=+Mi(*T1_MD1#Na?$3!vg5{el5_I=6M``4T5~qg6ao}OFc2Fx zxT*~!1a3xmFS&xtw8&T;FmYfg!C-Gg&X+k>WM?9Zy|}5*FCf6j5-)Rd58t)6RbNb8 z$WoaPff8J6cEV?%P8J+3EcevhU2auuo7DK1ey)7AK))uO{lbW|PG$^8&$&(gNGM|` z6XBQ{J})BJ#64xSa?rJd++{%WQhAXDOEhsWl-#|~zV#S~Y@(JrTD3M}vuLZnYn#XxkAhGE8g|NAkhDl=aoT#F~g{HnT`v1(rl(YFW$Rr zWDVUjCh3a0`=sSpw3i(k;heGmOj19K&f{u7uU^YHcU)N#%l)3bj_+8x%8w?oU(TCiTHdCvh z%EV|tQp8-)U8V;FwR=jiX!}}usL^OwuE4bQ$Nv6l{g+hLARRLxn#IJcz;%dv8K#)^ z+}DL6OdlStzO){G3TDl`q!ieZeWkL==m;;ffEI5mDuUmzDEDB@Ak-SPyO19O1uAI#Vk}b|_6fsUjVwavin`)^=|bl<_MmZYy^AhnVG;0(V?@G>PPa#@!L_)BMnO7#w;uDdg#KN2ruAUnpQ- zx0kUmnS2}GK~FF!{lIPTgM|EsMd2fuil7D%ct#5b-U1^gWCr)?rS5lD;DKbK9%zW% z(@!2YRrpE)`+?Wrf+j#H+dwFB^(S`$`7EIio^RxjKNAGL&z24y5LQ*oVI@WlZF%f3 zx%bh?jg|L733x$EQn=mR9l?K=`&+_v@~R&oAfw-8ZNC4X<^F%F_F8IaYUrPc5~vu(So1mu(RS`jo@=i4 z(TDqO77j*4ED3bR*(M9{o(Ape+S3e$PHBd5L~-ZJY#1at3V0#NMxl}|ltGt?fano1 z$BN^23uT!SYgShY)e!>(G3SSU%7GvqS=~E~oCp{C$fL_OXQGK&pDkBwj|BZ8w}W=O zby>@rg192eH|%K~f34;bgdKXGW~FEN2|9=M%C>Xf-~-OQy~1cEY<p_@_bgDuN$|LF&Q4z_RR;e6ABO||_QYXD) zQO9I0mhoz>YR3ZUr^shfC56o5T8vd|i9Ozm0~YUTd+M8Q=sVyH%WcfsMn)cm%^6n+ z5rW>|7uXZP@bgsY*;=sK+ozOTV)^EC6l=*hZ+8y?`X`lB+bG^b+#0G|;m78&;aYN1 zzH^YqaYKI<+d?lPQhdFfvPqS0QnPY&mD;cvha7+slZFE z99-h{_x3l5rP=_FC_wgF&I(>aTiI<)A9_e->bI22ft1qn2PHj|^U*a`8Vz@~VP@V4 zZ4zw1in$&W{K)g0b%>D8It*TdI1}z;o01|3X6toZv>aC})=YC%r*zT^<*YK)1-mo^ z=#GBsKViw&8O-NG8ER1$kBEL}?*&CKt`=*pG#eNMC3q#M=I7k$p$bfr1^A(Wn@z(y zK@VMNukB76v#oX|OD5g{ds{cpbd9yR@I7|s@I#GYKh<&n4ZCV(m3=VNKpWMR)*Fel zyuWAG(R(-Wd9gnX#wwRR4!0O$Wa(BsKM|#ytcu2!TNO6DxHk6gAJ<{ABE}Jbdecp# zK@!pCTbu9`;WE#LhAnNf9S|xit)kTc*+!AUTm=rQ)Ow*N9Y?roOB|^zj;Me|0uSU( z3hJ}77j>SZO~;^TJ^+vNoQC)f)*?8Ms5lH^O}3{C&+s^M-BLrL_?nYh*i$}hRnxy> ztk}Z7{PS%hTm*E@P9;K|(<*`XpMKv&s~#EG(%)B;J_t)TRK)xlc1hIjE(W(6Jd_i2=aRdf7r%u zR{b&pQ%db~{_~HXmHT>x89#3NhlRi}T6!6&Ed^=R^(_!t4@Y31Y6yb}ZblrK*CuKF zF3gnf?2D=M6(12A?1ZDrn#fAZ#tXF|f7WLi+dyBb;AB(^;owopV|DzB#sl#}qA7Om;Onn<)kqL1f1RWH5aR-xtgTish?DeSesR)9< z5C*Pd;V-6XSsFk)6*?;nn^bG7sx<4G9;p!&(56-_B!O{!H-J`HH~ z66^*AbOQDFK6cw)JO8#nj?<6`xIuE0|Ay}hW(Rr$ASg)_twd)f$y65D*~{87q=%KV z((#vVmNs*Rm-MYl`8QGjLNkz!(z(QqmLD${MdgVH%CDu;SX9N#Jd9KN$Luymh1ytp zQWzL|h#ZI(X~7I*bg`rcGgn74p-LJ@4srHR3WiuKtZ0?5Ri*c-_*AqfGv?ip%B)<7 z(5k-bx2aO5>+(G0hPyII!ifElVJjzF$}MvFo1QHRSs*HXM6e}Erc6L?7#}#sJZ_I| zFkCwy_-H(Ct029NT1-B)&pI)iv0|rO;M%aZY=WaDRBUmxp){e9Didx&BPfWgcB!9Y zLlH3XLJ<$ySe~I&ZnC1vDjFqRyTHhOU|E~+3h^&(*^?=Vk=~-*g{p%lUo+YCbT2jb z#U3E0kCQhUN8+>N$Gn&lGw4xTzieEWhy}`SroF(i)G+ML2`eU*N@PTCic$_|cZw>vh#jBs{$G=w&3H==> zb_^?)%`s`S;Zxdw3uXkmS1S!aqCWoC`j?PyFbvhwcyXV!zu&;Fh_p#xv96LX7!c6_ z{BgdZOKa?qUzo4dw4tmscWE!ZWMdOhoL<5DgulUfvBH8y9r*JQ{f$}BH|jQMY0m<8%ixei;<7gB3qy_|hOV$~4c zQ~3`T(=r_U@<0>je$J=?uOHf&9bUdrJToXHjt@*Dq5zuNL5tc2WsGHIt(1I@jJzo- zvTf7a2qXz(a8BjQ+dPO8Df1*0L9Jxz>@#ROz)vBE#vKM|J4CKiqYQ?T#ecViR>AFS z_G+1NdoNYxNrzdQGrirU$M;u!Wup+emSYi2BN7G7`?%gM61^8Hjk zeOy8_-e2dX8~oMdV~e^^ak{Zahc1SoJQEaab^r$ zYMV_cre9XQ_h7ZytTw4pM{ZhrF$xE(Vr6A%-29~|{luj1;f0j(1u_gzkC*O<1M!hA z`GI4cqYp9l0$?SA21ca$ye+wPafz8f$V#15Nd{Z!!>NAhs!}L;-_jprrfbe6FupWl z)kx+qrh#T0Uv-KD9={CX^ft#n3#x7Wt;VJ__RjBK)PddZ05&2Qn%l(n$xr?^?_c1@ zhKsNUZg6m${b#?8v!3Iim8vnE^BFSauVS#vsOv==mB~t17o)Y5pd7pXUV#OSx{~hFF4)P%gQ1qc$;qR zx+-;|>Ls!Ul-Q7(nvdFq8d)3bZ==PRp=cAM2nU}HZNNiv+|OuR99f9 zBq?1RX%2RGP|6{ZIzUBY@G`r=n1D?WMi+m8RC`E)3y5*Qm}T9hj5EqkF=IU7(WuDM zndCpi?8i%Av$7SZYyi@q(y2$Q^NIyjN)0U#8Ij`D>nd2+tnM30qrD$A25-MMYp2Ns zBk0svG(NMC0Wxl}M8@p~R_tvC=FAt6u6OE?2fq<*92!_)?|y9Mr^8G!NkN9VwRTO- zy-B&x>oN)qUSS!92#gj$+a2ftzA#_wW|! zj(<9^PLFFgg#5|eG`E!8#3NJ-XV4ks8%X8Xz08Nk;`KZLm{gz+U2_?EHq)2;heW(T z{9;SiLgt8S2}99EMNm#?z(Jq z>nRw!)z02eV|xmW(b$JU%U>(b%QKoS4dyA)YFOog35i+ccXT2Y&3CRiQ)oez#lPJJ zqSGI*#L|3(K86A4CX(ie93RY$ahOh+Nfd{mQa?+_yjwU|fWa^}rg3KF888@JJhV^< zT3l58{3jRfo6{CTVw(~}%(fFK_;pFAm@UncU zSFxq>14I$79ztBL&7|d51BO8%87y_gvZB_r!u)BBj6AqBlrP#>?=}_ojdfrffYj&I|&`gh=t-@sYvb~L!rxh)XrALIWW5D@#F8I;n*Bgb($Bi zX=>b`-2suXc(XzwVVS;{%wrS;7%Z3xn9ltXb<&PfDh%YT3Nrjl=<+fKE@c8WSV~rf zf9FZO7=5sgRa5W>BDP{Hq-bzQ2R}+Ry9kylqyqjinh_j-)2tB{KJgce=iCfD3Em7k z?YA*+Xv`E^Dd1wp%keYi+psA9!M(@#%;qqFxF8WO1{`&nSKo3&zsBev{pIa(V^9_G zbV&Xqf=3K`CTPaMel?;{@l2$lbjU#6B&o!=8sBGuC8@pW^qjk@qpN=4q(FbwSJZ)h zMbniJrB-E#xB{D*)zz8@)#3h+tEUDkhoHB1W3L`dubk=$i$I<5gbtgttR8k3eX+Ne(vf-y)_eY*2ulz zqkGs0S%7cB0~ck+#eKj-^QG(Gd%-{N37^j)pU)|u&jAnJQ6KYcG-?7zX}>R6`H{Wa zz?lj$7$M4mGH89kG4W)qD60_!I;#OmO}hg(y%n_vn$L?4s65 zv_6dNhL( zIt35RdBa~Ykrb(FylH2CU_EgnmcX#0=q8&*!+GJc!DswnnAq!Arv}ZKtbN3usbJPE zN?kBGJXvym0k8+j9SAR`oPD42A!3C^sEN(8P6t3_XPkxoTh)j@JbDtArQQ<`AXdLuF2rYUT3_NYjb@ew*RFBSq3=vt0?)l4kbR$R70| z$q)As?EeTG599CUg>g&n3W;}9xIv*<*wcs$q*#`4e*=T5x!d_!g{nfEa`S{&X={v)6-mD!eVTx1r+HUE}u>y(rXFIhSCLLKpn?zt{xB`aN>| zzoiDo?_(yO|JMHL`(pY(wT32*@1bi6@bx>xD1IuCGy_@tmo}+BqCN%}s~}?_VFVEj zEpk8=_gWSy(A6wCZSB{hCPs|zGCpQgh>Dg}Q;H@$D6f@>)l##iO+|B4Q^?YGhzia8 z_0$Eq)atK4IX!y5Jl9>X-Pa#GZarfO*Owm1zrg9}0-?B29Ze32a^$N3f;4IJG8?B# zE;Om3WL~U%g&W^flJMg0`f;(m*L_gl(T}z+CfqA?t&YwjE?n}(=E#($z6mePQOdv+ zdHtUT8;cM4LLx3Q6XZpDFvI9w6sOh9Rgs1;XvT@TiHuH*uJqjrz`4-bG%P4&IqNxH0L{Ra{rJ#){y(qB~{(hQ6Tc z@6T4e-uLb7`=NFxx2mIFVJlmnoCWbbOe;y#t1P6DzcRbDY}_fu2FDnsf~WGYDHJCh zlkgX%Osb{P{1rk)auL9jl6ucr7muz2S|ydrAURS=UMBUhQ*K?8)_kqFd=LU40F7Ue z!PxTl4$o?%ld%}O6$|E0sT2_7Q>!Y9XNu-%!%sjYpCz%KwMfEV0N0#hsT@JsyiN!* zm4q@_W_@H!X9{NQdTt@`$@ZC>Gzgq8v#w`MXvMy89FmQlL^M=C8xlXox@D{T3qXd3<-!-=e>@6{u^l{mW(v_5+sT#C zX7rB%q-e%mgorwA{t<2e;9qOst06F240JOz+0jCKZ^g6lGQ>G%Rta_b0ssv(+%RuJ z{H#CBP<=DDDeEwy|4FH90yKaE(Q5T_rK~-FBd@f4L$jqg9S>)q2|?*hK`m93w+P*z z?~}2VOR+wT!l>CaB8mjOZve!c^3bU|5v8BnRZz8QT5}?usnfjA5~hlIeO5M=JDl9` zbW2mNcFY4A)+-Qm^EMf%IIECnGYWfxi~tYLsN!ZM+KFiZb8cLTwT;J#3T^;fU^`P= zRBIHmT(z#L{g@AaB5|5nhjL;@g`P$qOBJ9*Ev0kiUMb&s@N)uENTEM!q$E|E#q{}m|nyl(I}LfL^s3s3SRBV+jdOlOSteuE*dRJNja-wzoNfP5-aZK$^C0 z>N)cci_io_WZAL&QR^|aew9slW8j>5`uHEL4MzDut6LNpf}~p}@60IIo3ggphEM4s z%qrlKpf8qWm6J*bVosucSEStD!bMg{JaVD#kflmX`=f2a?n`Cl&l|4kfc6G&dx7LT zMHmj$Y0l+Y>Ot2{+JpNSu!`j>!@!1^LuR9Jw<0IRFI3yO8xugwK6`)s0oq8GO?!9v zjp3zYF0jO>~g) zf|+M+3=Y~L*2t>ZgDO>Sv(-PGCk-s5#0g~Z6?@s=5_{n109pO_qteSnd6%7Ooyw>j z+h!y;QU}|So3I(x;Ouq88Z8fTc?O@o78x@fbKjSja8D3`1anTykKczIh|^LNWB(AA zzBoviS;fu6OMURqJu<;@P{+y`ihfF7Fk&zD(xKV*Ft_{(oR?}Zq#s$X2x#-2{i)kk zZrX^BQZO_6g5o=wHz4tgwTfoHEp-7 zG2mUe$KHa>u&2iCOE;Ng2asUNfBW?k6}b~0&PG-J5QZ7Zy*H|&3DV}=CxXB@oQ-K z2~9?)*9hFUk$Ve@&eZs{dMB$E}KwK0@yg%(qqCNZor_&{X` zAdZAy+O?y!3(L;y@FjkwT5tw3LCo+ar8W}M)4iYY`ihpQhWl-u*9_`*38ytpT=$Y> zKMF%!%=rbU9JvC7Ufi%3ISde0X5TPDq{8}jua0c)kQF0`b}?B3!}abjtPEv7D@ou9 ztfQ``DR#3-hNNMW@(tcc6FOCvv zTjz-Vw1yI98jbL=E&ZZ=xFyvlY*9VtxFRh42nFniRo!+i{Op$F*%lc|TFm3bZF$KC zQ6Clwy(#wk=JGCOU=!-n7^@a?aujEV)G(LmJVF;6AR=jglc~EKTB5}Bj5VEN?o1mb z%U%HnWr zMuvB(&$eMjaQm2D2@TNGdXzTXB1bjxI7}l?;|->Z#0%IFlxM2^-bQ9$^!nHyQQ!2!4QiR-cFsNQS-^)%6O3)4U4E)vNPbjRK}k66e=dq?x8c#Uw|FV1=) zzQ2EXT70MgzPu53XQgj7Ao2TZWM{)_y1nQ;PbEIEf{TQychqz*>b>pu*JlQGhjoXU zv>3JZIOp5x`gq^~gL?W~HuSpDyn<$7cLogK`RZJP?DUFQ11Sbu2lbdjOKR@sqQSj9 zI&xm5Aa5&sMg{!T+;CIJabC)LLQ^a!ZUT3Bys&(m!J8fx*G+vbD16(XYCxzq+G^h8 zv3CX#7mxfXLN@GfGlSKXW_C)le;dQ@`Og^gf6)tqCg;POIcc*RLZ;US8|6lPsGG?( z&-z!XM^(%QpU+LF470UDSiO$p4dcCc!kya_EZ?hGgX4=pW%qixgZ^wA>iKhUE))$0 z@o!E<2_-Krbl$tP5sa@9vdbCetrUr=9UIaH=x;k@+KxuP1r>qsCnHyolYR1t#q_IY zAJr2tWnRx7&=oepby~fLrn@&bkmQ|jlMPb+w79QHk*PaCI($Cx`xbL#*A=m{$Jmp_$vKKFL3NDH6R3=pfi7}?e{35@f zL~d~zRy|ImwLQ148_Q;vK2X3nb%!0KKssFbahrqgdy*L^DSw(h3E<=4b({@DNn-w( zxWUuEmqbmLP$pMs3p^Y6y=ycxZQ{L=BGvk2b9gn97^!j8LAgKYl}ePS*Ut{Kaw$g9 zb5iyNQn8hyVo84w&(gYRI9a|A>sgX0~qw|+G`{j*#mdm0c_t==4-23(WKP9Cs z$9QS4Z&}?Q`@bu#1078a{vRjR|D-5=UoWX(eA?+YbO6jVgeIDs=vPO`ZMbuxB~Uqyk`fTdCcXscqw6Q@nb zNdwzeN!b}QRuWUrO&nzcwd zyH1mZMi!#Mdx`cwBf2ia-wD;Dl-snohujG);3d6DvuMlA>ukls&9$QmxRiWae$2mu{vagQSm;O<_2^ zRhJLJ-4er?O}H~n6EU>z1aXC5s%uZdvg2S=PYMRp?eUR$!r@bjOpm!OT3~Axb9!Br zKZVIL0t)Kh8y+fU6OaYG0Xb$4oa%c4L)sX3CvvpkZe!VwJd!ZsvpQ|>t9?>F1>~B* zotx(!L@K2cBS4sA3YzST{W+TfBpbnGu9Cl<1q(F{=?#41GHj*zgL#3Dn+0Ez^lxS4-{V!p`P<3 zGT3_M4;`p>I6q8>$wi+D{K@ng4Ow)L1V@+qCa#+DCdio!{COCie3YNKM(o3uwp{xm0^ONzZA3ciAfujnK$ zV#`_BO5arAIu!?`x*G*V+uKaFu2c5``dac&a*m%)QNcf8!~n{G#^d7}^;{qBK{``47r*a=^(cE)w#J*(!fK{`#+vZDDBqCHbml^-)$fHgaJJxK1UVFuYtf+lE0t$N_Rurt47r8{QWqy$7xH9vrY;YsW7C>b7I#xX-(h=_Z+-%LhOVKbOK4{! zQ5*~B^Gu=UsjKAAPN6l*=1QlMyZIC~@!=wPAvRQi?!R?HED%qe?w?~8FkNGgU&4GI zl&5=|eVHO-XeU<2fi zH^Cz9isA@}*kQb|QitjK&&=ODu`WnnW3Y}RWL$y%3}B63@6ef^Szy`q<$>GB3WN&N z+`HX}bBDI4YKM&{4Q4>Cp{SelfucT7-eRLF9nRt&!65d^ffFjfVeA&xZftZ6d>J)& zHD36U?8d$fF;e>mos6o$<+ngl`^m0cS6n{aE~E0Wt-Phaa*E2v_AGc-0k2B-uiSrW ztoyINj3byC-auP#SE%yr0kMBLP|SYZq45Dvtg+^5-(hfg?)I0FP6`bWRuaijMjmH? zj3cS`lDd}78V6`MprDOUiqMyy%L>{w#=*toH;G+;@)lzxv%^_gysmGf({1~OEgII! zeaaJV#rwA8?;9n?0sH2HzeRS+;r@{E=~KmPuLq^!N&Okl+v4}X#P8A_$>6?HF6`7yBpL&R85eP z=C8HI6wR~gK)|nvEue~hO&>|X5Se2?QXwH<^b@&-P^(&cpSP+AZ5+{NWM(5e1*sjf*!pf4p|4RIAfYAy)?Tq1Fq# z!V00d(+-gb`rjdK;=c;sYK4#rI|I*DWaVo_?|%xF4e2_er(NiS^~O)+~|tjj2moKqk8zXx}2+?cPa-b0Sn=Cl(X9|57~Cm^3vS`ZcbD$t)u z|Jl`k@96)>T|dMBX<={jUo2Bs6GsbE3nK$(3p?BY{kWg}jOgOqf65340wVI?e@?~r z|9<^{fu?4u+Nhz4q5G2C{TzVB(L-(uuMz@rpjp?138#Y8fJTNHwA4F|hc_Tv({VMc zdk^`H@%_uSgtS|B(nzqe;Ct0`YG>|c79gIW50rCsy`J1`d;b2M%zpnnAISkJ+#>)& z8$h67C=6mN7FT(659k=!*-tEFmg_OmZJ}=Jut{objmn%H@0otwrxA9*mkP(@4b8X*x%?ey8qMX;5l6DUTdy- zO+#0KoJDe`h_?@%0sU&7U#FlR(G?s(P$BJAu)TEE;5}zcf)-nAfaX#ym8MU!vkH#d zY*{`GIc7C0hsU%U&y}r%n%AA^_+J;`YN|!rK3|_C>wY?`H1*bnc`l|{e4d(<$Cz{L zQ37k!3T#7poPv`}2D;@Fy4)_yGzCXRKK3zYH6FBh3y$7jxD+5P1`egPS2Jr*{j`+f zwvM*b<*~*}lf|P#_>DDYH3Lgq+jwv@Sx|w@T!DO!`fdi_v#=+DTD_I#P&2hkwaRGr z)3P~N^gU|3aCHjlbOhD*04eaQ*Nb=7&nsKJYeLs4IlC#BB3c({9$OB&Jput;UET`h z1Kz`p-3DtsHj=&?pEO#xm@3P}m>sGDyOm*7HC2S6e?fz*!0vGkyySsmMr)8W?9R{t z`U`K8E4i}#7jjkUlv_tRqIHjw33#vg7>6_kR;v~EU^B$_j`*X0Pt`yx*-z9;Zmb*7 zQTAfbqI#F^lZj%AVnworpav=OFbvpl@?(+-Rh?pf@LNwgH*!fp+oHUNV5t6VxvArs zbHnVX^tiiH-16*4?8XpFCuHF#cDX8Rjd1|N7idp+G=ug`&F{>gKvYDp(DeXrWjx0RQzpfr%xjs!b z28h817tA{M?&oLbHw#Wqtr^ZgRo(p(R-(y+&{(T=_Ukx}ZCL4+1S3>RHGp3__Q`*;7&FSr9rTh z)7-x$atfqN&D0^n0O!oV-8L4kOj~4QiaLt++%4-QBAF-TTtEhV`vC~;LRrhl>a$vS z`{b>AU~r0TPrjU3>d%c#;J)@j2(>OOa$%o#%&=|0eJOmKcZa0Zx}`;USC&GI?GHy7SZ-SWz(* z&L_M?iC%E@V$p3(K3Npat2rK^OOAjEynAY`3|IhJUHBk#xd*iBNKuFo*V!GExPrZv z)<Xs^^fzMdcm0TP(dg*Qs?uo{YffMg$2 zH(40bs+tIAcvJP>*gwG-EV66-if5k24dM*aD%I z$)z!&mXcb9x5||_N4H-1EwrXNE;hXf6CxzXf9wARg?i-`kEW4~0vNBGnap^;>NvmN z^2*tEKR#3g3A!!eTFh1TmkP$l1LoQSFSL`jqB)1)xD#cL*{By7F2SXIdat77q8RA;+JH(W=xMyI0j1~Rpzo}%0p z!Sm^Hu!m-0szX#@Ze`}ZmcaM%pv;2=ww7r}YG-GIMNUkEn~MdwEsxYi=;U)3_3ubG zMFCnP-50=5!ibSa8id18txz)-URt&Eu zz5_FgWV$KSH{@a;qkV|wVo0=}b08}*4bjt9(E(w5pjKCMMyplb(e1|dBe~tIQ_#B8 z+|*v`{k#_KnQFCopU>KCwWrdlu`>I)+_@ZY1t&{5MgrkE+yl^EY?Cg7J(#{#u(58O zPL8?S8bB}r)CzZ|lb5LQUiDY~k;n#I*Plp5xNOxE5Fhfo1sOPV{55LwSDV&`xe_ip zRn1-yCd><8$jKuJ+W*vtX1YPJIpCT3q6#{7qrFj>Fn8!umuGaA$#V9p(t?v`E;GjH z-eSQrx!RKMF$tlgP7QNSq1Xj!gzuuCPoOTxTSb!afZm%jta<-WxY(>jf5IkU4`|8} z2g;6vJXBTCo`B^lEhTlwH!+EtXlkz-u*z*5Bu#gD5{-zH1GOdUUmIWx+)J`QfZ1+> zi8N9*W8>E6@-dwjNAsbnO@yrU5GtI#C~k75ZG$+BIoyCE58{QB@1I z;k+(_j#gA2)eFI7qx0+bIT5SCNNnP5F5u+ch!6m#uM*ZV6dGa@8>b|T!)g{IvJKQ0 z{9L3WpJUvoiZ>%s2tn9Q4RAmkaWJ&Fs;=ZY&0ElTR+D5eq1YPL3Al$)$?S(xz$a4~ z6!22r*c6AP4r$FQnsovS^@$JhtYjPk?mSwd8ICpLETCnXaoP%yL{4bIqy{-EioP=m zS2YK7#j_~UxTYCe%^?IVZHCehg|Gl%Kxh<=a65p^^xT50xiU}L1!@zfYvW6z5z?J# zcv5o_OR9)SKiWhMn3jJ5QAh*qbK=-{w%Jm<{Gv1&sgYZN+|fyvX7Sk)Te37x z?yvKy`$Sh)&qf$JC=89vt8L<2;@eu6{fx0FEeEjmu8e3Q{i z8eDqnqVm7&S3}IOd|SG)*8^s^oM1U>w_rRIv+h<>Dc>nzqGCFEk+BDi<22_15!!mW zkP(R=q;c^*(!6h%2{--d3R^h?_t4$t`^ep*zHK54S;f=gL8Pr!T09EYxj+-#Sf7Q~ z$-?AnDE{?EHmY>r<(b;@4E?j&G8`1ijvOz`0lh$aJ|FRBK+?1q%+gP_mNWu=7Gak_ zguhG)n^;J+p`$mZnZo8Iq1`YEbmT<|=n&&;Rh9m*33^b)f$0hPhKURiGIQ;bRVV7o zI_RUUg)b-J_Npdcg08KO^<-D2^>+zv=q(bc1H~JxFN?BUX$O+j8tTf`lEK0HCbt&l z+$s=%V9B}b_rLiE6r~?0`)4AY;~L#nhM}ESxL8?`8oCHp8ew{1>=eo>>!rJD&iNHh zvVAh`blpS#nFYZV;D_5V7+sN%81lH}V^js#?*pROWGU@&ZUYSS9_JLND& zS}krpI`tR~FF z+ermPQzLX~m5o}TY2IYsSNP_wj_2Fob=k!QnHw(_&6JX%=i6Vuqf0M|WMNan*khgU~ihqBdHX!5#xnqo*z1*z9 zv$@w634|A^gg~uByaC<3KS5IZ93)xagLD53`K;5x$?G%!-4P{YM173;d)J8c+7@?x+qz_731&MEcQJ90DU;Ez&g55AU zT z_Z)%(0HW>@i95(@1JoKrSi4<{FKL`F(R;gn%ifM%!&E<15;et(L`6o3ovewtGY?uwDXX_T0?VN9Fojaadbvzpdf8Oel;|Xm81sGwm}0d_WSB9Ac-*bgo6qRF?+8=`_<~(NT<^Tt38v)hR|KS>ebl$+?xHb( z={(9&>@KQ(wd3`~l~(rIoi@+hyo15k(;<^9^XweqlP>Zy&K~hoZsXEHH36TBcBzL7 z{;UK){jtK6yLO<@?M1nv`7?I#??F>=-OTh>Bn)!qC($M{(iIPpWi5mf1LTj_XTibe z*kgg)hxyEd7u4w;M^Yy=@}hTO!+!`v{;B z-j=JhfO+(P%NpK?$fzqOt(G+g6G`>LNQs$Sr3(0Y?=-pz6Ez1hOr(ta1;~SE|LF`G zd8uVbhX4UtMFas+{qH}i#7t~W9KY3^{}Jo)Ki*gWYZO+tvof*$-}+9K+NG0{3f8|Y zwo{1(nBd&sh-m6TEMV7XRE7ab))~pa*Mkb_QPpnQI14f2y#~6DRV%I{&dblcUz$V1 zuO=YOUj9z*UnO2~HC$94oSZUn=&{{sm< zulDNaOU2??e;8=VUV?X`>vd?dAI7wb(E7uTt7sniGR zR0a5#88;X5|57rV!B`2XpG__)5M>yiaOPsS;}A6vRk=o4wIk`3nd@`?K^)jBCx!M@ zDTS9%5)oOLpn_VGqbuM3BCs8}#KA_h?l%QyRjem4l}ArnqRRlTO4|bmnk|fvWWxd` z)f4WMUzO+UrSqdjMl%=ENm-Y+w9yr_QtHd`@_W2(2+5~8`jc<{o%>N33(vRj4xf{kA@JwgLO)M zysJa8I;{ zwyM*HYN_YJc`pBS{Nh)*0l_oIq=4e2gq?~l%V!{YC9MSLOdl6nSdcPWrX_g>0RC3} z*&`FH-cKx|(-{gDc?1U^v1(G&gZ6 zKNs|&4f+mlec`$Eu4-*Ek>WFv?=32OPrgx}VYzj6ib`u~OMp#e`_wUJfC(qfn_) ziqU-vp&V5jQ`&_l^Ed-H?9LYY9FOu}k3?nql6@?un9FPZ6z$-DgLFt+A%Vnk${Y0w zG!f8X6x)42O2de%a2jAkY}5C_wXMcv6KV6%FswY#R(8+OYYAJpKBW5&+ykK9jErAtUUuDSlz zr4sKJZ(vnbLq6YTY-rrPy^}yp9^a--Ho`b_b0UMvt`f}E9>dkIX-x%DQu@d9%|^kV zMu)B(#dMo;l6;&rq4++DOqZ&8?%d7AC)ZP(hY07rM zkBJoy7$stGo=m~!DYVV~LsyF9>c`SN>vQFXaE2p2*9F5sy1|{OB4|ldJ@H4%4BiHd z(&>ATTIu?ssebzSLahc#=5vXQkTgdUr-4knNTjW-MZ<3@7DeIvw|u$jb7PTemCa|{ z6bEqGEb71oeY}a!A~fRB=aw*40V1MjepC`)R4z>4z z=8_Qg2)47Lrc>W7`56kX=kzUl7aP}O+JQ|w!N1>E_MK8{qinB6kZi+jBkyW$;&xOE zgJ>M`6f5}XeVwQ3V2tG*V(u$4Gy7fDJk}*&`Lp*GFk1RT0e!coFz0L?YNlB4=2iG| z3;&3wL}z7`e}}>Qf;NNtLiu2H;m*wZ%@$k=;ImemSv1xSM;zZc^eFnVo4PUKEVYX`P-LcaKe@?8T+&8GpIom@$k9gAM@(=S5f2}ciV07SI zg?*vK1o_bE^^-=_#S7@;6y~9l&~qsCBDrcHLfh4_9O8Zz0wazi$#G3VoZS8VpZ zE0+E5cVa~o*YB1qU~T zh~m-&s$O+MjJqUuZ@3Qn%}4mz>Ylj(NunD3Xx8b6+~=@g(2Q z*MBh#$o;HyI7~vdnp-2Gr&1Rr^Gr&ZHO!Rb1>;uM!QJ ze)`*nqkqlFC-(z2yA9DN$HF`sSVkDMHz|M9$z&^Wxe`vr#_Os;Z`d&5jKZVgYfvZe zsoO4?*Th#vZ(%*1oDAp!nQ>*aZ-)LvpwUHk)QF;(BoKNmx!QPSnVWEXOldY}Y@C^w zCJO@hDhb`Up4yO*)BCiNFEK$a`L$W|KOfo-t8Q4}cXNS4TyGAkdk@Wx(kloiYsf4n z9A_1M&bw_zLt>qDQii?~?s9j6?hlWy$?CLPf||hf3wsM1iBoK)htg&Yst z%U#KH51qvWQDl378UZItoIFs9FH@mgp5g;y9J7;{&Xxh6g?r;C8+R=ym!1fD%1mQ% zVJN8sBk%mZ#%N}a3Hw4~&b-E~UUhjnAa6CfX0j0kPfc??$Qs?cb43IVkOlS}vyT&4 zW&>91-9c+qn6J}PD6^;X0u7MmnD#c-yBQR$)#VbBp*wD~YMZyVSDVM9yIOZFqvADS z^$>8casgoeeHVXG!q`!I zckMVS@TKi5G`FHNfKtlX8!I|k7`JU)PbmOH4Hupoc@2p9c!OBM=)4k^0_*D2)@W?l zF4Y1B^z8kfafa2R$o4w`BfQ z*nl?#RKZe8RmevLz}v|>EXr1jh=F05D6S@#xBX$GEtLwCVZO*FL64_cq#^$s_RBBp z^>`%b3!bRdR!37{h`b9NYEG?DhYzC}y)9ME_5_{E)5XysjyS!Be)uq6#(g2n9)A;| zbb8-dM(jvmEgTl>F`}L{rIZ6(cTLGAulc_Dh7U}*rD(Nu(#XzgEz!0bvuD{%NZ<_} z<%99D4cDmA2`E+GmHq6{y zfzy>~c9-$uIvyAJFw8MapsNx0U~18zvIHZNg<-AYJknB?eQ(JuJEv2$5JP9_mO6yf zCBywO7Qek zY~-LI4P}miG1lE*=^$(`?idp%v|{ZvCIE2R40kDt8%;u;Epl|WLs7#8it-C6!r@D>c;#Cbd(G7unvWW&dQFk!w+ z>;IPcNdLd;tkXBL`~OYYizb@!Z~iv+kl_9QF!uc4Klz{gXQlu7%l~IK(tvVTUPAkt zG4^1lmrJe%$|(0r5WpfDOVlC?#s>X1G(}D>e_^_3@?c0~?|cR#x(%YOv@7>nKF_af znw%mIwl_9uZmcg`Ug}y^C8=(#>wsOf*^!Mgqy-5dPOv|Bd(Ch@XFAQiUbm$j%ksQ& z{DIco_lNoR^`L$GdUluKa?oNdifydt@{{S&!$Ka-WfqqwRar}4u_`JovC7$(Dhb5D zEPHy2eQEOwZELmBnCOpHcm1X9H-I~AkLIZi>ss0^8e7G0M3yWUQqfFR0ShFsN(?x} zkPFP{r?Z+gQ=7$U-7Z^Q<3KFIFJ#{G4Sb`f&NR;|g-3Ido#60{Eeevz$23j(7z;kB z2AZwVH}(AO@6&yjVR6g~eVk#G;>QY6neV_LY~=)gC={c7B`d((Tk@3VY&H8Yo8mbI zn{@D*>|+z!=F;M-H&7?x1|OSd-21A1d_Turg$=9~nvVQdI`hW&ejrk`c{jtv{Udah zz0uFS-*Dg>2Y+V8D7Yp1jHL-9os`Ab-S6&Hw*7;iTxN%9Iu81jGYR*k!ORS>_A-69 z@`oSrV%WIx9?5$$x3E}cno?(v?5u)q%V?2kg>$2jO%aEmt2NhT4pRw?xH^gG!l5{I z>-?CZO3DdnjTVW5*o6X5F*J9yhX$5>DQlzp@Er2HLMp&qC8mTSJ_FJW(=$g>4`|Z z%1q-9)vJoro44C6eq7kZnWG+5MgS^O8Z~(^VD1q9>P_N%70_Ku7=f%wE=T7GH(iaH zl;gBcvCl(YWYorNlQ41&T8zoEPE#-|fK{I+D^UQ2CBMpZRqc=k6v1k1>-r?nPDqKd zg3D*}N}+c#9;RU!jzdqd;3xmj&V~|00%^qVE3mRdB-U2ae68ld&&(cdutj!i^^nI4 zGBVRlM98{%mUSfsS@lOkCL-H&qzIwvuC=xFsF&6t7*y@h2o6N>T++Wb87&rg1w(&- zh>X+R8G=!1zRpnWnxoVzcCl8A{siwRo3YIz$#XA&AT&qTL<^b|#ax3>;E4Sh zC9@QnrZH#AOsp^eXA^o!RZCeaK|^0yfIF3055-gcV(Vd1@<;lOA0#7~){rk1twh|E zCCM}s%_{OfbaP%H1L;7Jt+pH?m%!1~`K!C7`43OU0MnT1?%52m$Od;aqnyx_W$l<| ztgW*Z`yFdFFsv#jB0@7gZilcSDKu^11k^o$DB@G8%>y3(h43}y^aHg=4~hG{zU9A%NNp* zzEmfKtE5`3KDA3{XfP{C{Ce|1{*a552$jooIpJyZ9Yq4}s)p9b66m!oEw zD^1pJ&n_9aE?`eC)9ptw(7d6%I4#!*Daju!SEbH&-K!#Nb_JmHRBl*#2AAY&m|TFp zo@0y!-AO`IS{b@5VW-eYhX@gfet#1kI@q?iiR2qJ{=E8vL4d{|GpW*}%YVRuLE#WZ zn$KOp%N(piiS8$NM>X-d>0|PNiB51tijm?d++cQ(d936_K`44U38 z%HZ=dz=Kd=Lj++f#B=F-EA}Z#&?noZ6L-GYfbAebq~pv9-_lMqvv*Hqqz3TT>C8~Q z;AJ079iI?PPNDm1VAMpLUs@y8eEs^6@4tukB>{2W2Wixv;wIIqxw8Eb-Xv0TiIP7q zf!0Mm`4hMA`m5z2$ zo;+aE)MEPk6G9=LM8u&~@&(u6l#-3Fs#q57=y-&+fw?O6i#N}8RPbjpR?}iGW(9M< zCxgs3KI9VM4ik0Wghj-I$uzYYy<7)FT*S$Q#5&d6$6OBOQ4HQ6R>8t>o~jqTiP^A} z@VK3HPtJ$>^&;inVq!_Yd|0Z3y%Sgf{Y9gTD*RXnT+t6S>5P6S?G(9rtWi)>j@q!X z1{MX+gW?VEpnl-mIq^IpzS~r5*d%mepLjG;$Ra%BeT7%b0sam4kbG$M`Q7M=Zowu+ zw&F%?zy|Eq5_8ZOHVwAfoF9W73?zMO$U??(>#}bj{x#RsT0>P=*-<~J(cK$nlzo1( zjp+_6K;AySuknikpPUx0T3W1RGt2SH+$Y5?sL zMK6`o*1iVM8#kFl5nDOq{Np2bPCxWr=6?5@T}S};md^2R%FX=N>27ZT-t;~LNMaB> z14`4;dsdYP1;eL4b(PyQ1bt)TnHwmlJ=#!2pGV`grxb|kbeN_|)4#O0`J=VrC;WYT zqlb3~=AXKrPQz^+rHyfQmXqDR6BACxTWjtU2E%jb+xD@(zaV>9tVhNH!pxj7%s)i( z73p@t%VLE20^DlT#aYy}mELTz1O4Q7;>~^8(K;H7`J%?VNH8V35GH(yZNZz8+DA_q z(QkZCNDZFY;229gPNpw zyB@l6oIy&oqqxKAk0%e`grcG43_YV;B$4mwRbskFOn)*G`kCQcV9y^=6x<%0)|(8L zP1Hg_J5D=J9goZqemCOYuAe+1;`no80wx?9x`v{Y%D-YE=)ylI#Ev6C2RnXKr?BAQDQk{qqB%IQMUV1x zG%0WA#&NAq4?X$nfWz898WP=rI}LTjPU(`mtl8^BrDUt`G1vTh&rzh2eh;*3k4a}T z#ZphADMInGBJ)QkfF)Q7k^{rz1rumMNq)nWn2T(p9-UMw$Xj9#W2uqCS~+DYtPpa^ zfxnWA#NI!{{sDsb8)c{Am$!*-%%}G|$K>TfE27*15l>9SYj&Pv*?K?=;vn}XKM?Uy z=-^Q(GteMCi0#gi8%C=X2dP{nad{w@E!B9Ru6aHmesFEvrV$0#`p|VfO4buGW%h@* zWXv9uXR@6lJ;S9`Yk=->(gV2b>_%()_5dp#LbXfOfJ-%C6aBzb4Pa|eyXQ*j+ID82 zV8z>lv8$?vZR?cQn$YFcQSr;I)pgevf>C!6vzb3+c%Gwm?H5y236cBiSHbFJ?va5N z!aQjDD8T~@UcLki0IaeuH{&P5IZwJ*iPJTRY)Kgpb;DPL#|12&R|c!guy(+K^ik|u z0EEXAP4cAqBAnZ1hUfyAr-L1M)|7tgaeHRxWJaI?9X5(R{(u$K26KX!2ZhrTGfp$( z+fu`n$!0?B7Ag51FkQV{%1zKJCIE2Lx!G- z?Tq8x=7@3m>Gtfvebmkww^?jV4GvZTPA`JPD-m1xcNabI)f6u9qrM`V1I0jejs+MC zq!yG_y$<_SbfHf9t$xPiy@&)sw448&skYv`JO;kaKzKMHAgup?XDUfGWnlwnga17U zO{rTrV=n=^=#yFMnfm(t5lMdtX8gej5bwAA=38>tA&;;nU&hu3)v?r($C)M0Q4ALa z)dv^N1~*&eVavdr>OwBMCMasQ*EY>|H5R?=w$DG|+j#Col1$X^2NxbcoMg^rv}|5` z?Q~psUr#ms<9q-Ed3;ghG|iLtmkY-K;ZHRXUdT+jEGXAE|)k{JN@Q!3`{_oB2(Cj8V6IZ`$v%s}k~VqgYm{4Voh8$1Sl^nap~-k1 zs2PQOR``lh+3&xl{&R|=B;Q`@_A+wNK~*p_Xd29h zmDuw`F8qEbWeGNAm_(H^l?oGBrVAqf0y5fBOq!H)m+0Q&v>E64FT&|p?eC1Fc&W`2 zZn!dKmb{GUx~D>0e zJgJ`pOqWUYo%_f}Gd90$ur>4e(@EY}Uu1lC?!im0Uu^~>tL6KTFU&VvQ6g-!arD{o6Du8>;-(_f!!%8p;Rx=!+ODW=SZQXzU%r2p7uAuHxPe zy28<8*ZDYw{1c=sZi=>&ZXYi zNHLtO6Bs+RMaT_*HbB6vaPt=FaYm*g^|=2$XxpJh%z#Sw)zOw~n@<3W>wR@(@tZQMyMEoT#Yo!}sH2hu&%5o95s>lH^!NR4pb0_~#s4n(_e_sXK%F&mR^d3{Mv47ULmS?w? z=C=V+tn6U72HStZ9jP7Yui^j?LR9d(1wRd{<0o*oZ5sV=pF^WhbJP|(l*K<^Tf^mc z6YzNJpTYd|{uOOY$K50BFHrFXhIs7dGC@|-6hS6j+EBtn+?EE6D0`z1{6sT4z)t7{ zW0aCTu^3oJd7Y4*Od@}dZdgJ?-I$GE`&FZ*+cgB9Eh=4EW>maTK+4?yZ#F@UjnDVw z5Mzs`(AaZjV6$;fQa*-C6Hdu)0`}n!H3bm<+oS@m1S$&nl3n${`3c^ot^muv^jMu8 zx&P*3F5C0Bbt%rkbAg$-U0Mqs08w@GMp%3GqFsjRRuU@y(<<6V%5e{K`3m4K&8(5c zxO2%Uyqmw04titW_{cJK@5#E>jcA@~iL8VxFsQB)$@mC0sNkz5M;anI>sbEF-)O@6 z+YaTuF8vozpGWZyW~Gr+0s-!oQ{1*3O0ln?I9260pCGvlzxb?c+H1;5zDN@-QTt_-32LiVSJy25jU#$<6c_*WbpzqE!~yjt z(n{C^@7Ge8cTtBiNUZY!gD*?Gfp!IJ%McToydBQ)cc(uO34ag}V}m~(2gMw}7YARE11;$2*3Je9*#q~h=U@Lq zIa@ydVJmeox&5#uqyL-Y$4}P>CEYf?+ls|>=;jj*NuNaZr>QCYE*^x|V33rA$ccz85+&H>X#uA9t!Tf%>>5exH-*G4nrtr4|b*G_^b6)TVa}=^vR`0bB zdc%^WfVZQc@~vOp^RohRNLDAqx2q2JUnIVm}+M&Nq!!e_|cr-nk#C?k|kkdS0JG#qwLBC}4yJ5MGX z)9PY=-fp{9sCGuYlaZAqT9MTFQ}gBXN#~on{M`NgFZx+@$M0Wo3W)M7b8sq<2kMoH zhk_u{(z923P-S2~rj(HxF@}t*Nu9y8Of&>p^;ZZN|F@Kb?w86f`n4gWetHXWrHmhKOa6!BbgRbus2 zCZ3J2R$&8$0^4XxHPsm~OfnsB(wj3xGs2+aJ`sV+VWO)P~c6Mmx-t?NnFEKfR%!RM9MEmJE^a9*GKplq=*Y|Bcf5v0wooZ z?jO5873t9cvzt;YKTJeJMuYFx(5KF0z)Atm4i&MVU%#?w^mrMvRI6gg;sWrri!wOj zY4@V)4}QG*T~h5Py7Zg;9ww5Z>c$YNg0+t)0EzJUkSTdjDCqU+jFL23x2@cn$k#|N zmv-V7!;n)|yj5{nG7!s%(W0;8FI9W^luWhIVdFaVrr@`prMg!;RI1~mB5IYjgeRjira;^oT_e4v$PwN zmIFuQRSl^a=1`3~89DOO#sYc5PaVXB+Th>fO6pDF1QaQ8MA(k&kg|_A`i6-%99snw zTQJ$O*d(;5vZ{DUF&1t55?Z~p(5bRmT8InMQQWC;JBor-!(jhjcQ5GA)!sG20%UHe z*DdQi>uwERe}tvdxLT=U+Y3UP>0GGuI`}GPc@vBv!ELwpZSLVFX^Hjl%KgZ>-dYe~ zx@Icrc7zYIrO5>Z_x1#N#&7j;MK$1nHM&dyS)t1?Ciwee_gQ!9d{wYbYztcc4+s*L zamc)|t#Q!o@ZHljck=`Aev<@_MsS?Oxqz5juWJ_NVAh32Qt84YbHOmSQ<^VK+UR}x z_Jo~xh!;iwiP+Mh{h8W8RTJ_PbK?O{(H`ytX13R|`L1_l>CufV>&KR_v95)>@ ztRo=2c@Y`l`p*D1iEbqGFx8d10JG1C8p$Eh5m7zV~62bS}!l0oa7q- zfc$n)U6*KRmS{_oXL``zl)FjVfV`%@nl)nfKxVhV8(L1vU3B<*w;}W4n8D7kd}ul< zhOgj!b5YuEQ)zO4&~a<7kRx4RuJYOJ)&oi}_Ey9Jc!)6_UQo`v@DypUGyZ}#ZBNtm5ckr**Wldmtl65zNz}qceQ-`xH-5e5Y~D2*32qD< zJ~@h^GIS|KBC5-?JAl|txhDdk-@~bJ^Io!@ADpYKVqAb z+to`L{tm^YZ8oA(8+hK91X(=3gO>28+@gGd<%x$0W5McVVuZ{lX|Sb!g8x(MF1Cmh zZ5oiDqQm~C#jO~B@Q_Xp3vC1){nH%Y*|h^?UzRUblt%{Ivly)3bb9U^TgMwOg{<7V zA%E!(W4|1#Z-^_fN9c94+~HX*$&zle$InBnjSD^N21P|0VWOCbC+w;X^N+rWeW09A zC>tR$e)HZPn$eb5y61O;UIJ@v^cgln_|6DnO*zMy2{?Xx831>IwkQ&hM4Q#wLxl_KxM0N}?Q24mR z{3E6K%81#NG3Y@K#Q^gC|)bs*VO~blG&Em zQGT5sjsfcz)!Ur^)n}eqMbyq*9ugDOn6cRi20vn;$yW-_M!eh@F)pQ^qryATZY>X6 zq;)9MW_sJE%NXt^`Oj!=B4}2*a-pa}IbPSUwr}}#nT{7Jq7`$7V9}U?Z_}#nn0>^Z zp&C0GYi(oxZ);Q?(x9hiync=OiobXwhy#<3O z>Y$<Wxnt zU5W+3C$x6;@s2dRP0*H>Ik91QziwuoB_+8*DHrV`!7(`^Z=H7h1%R^kQ89(WndFK7!GANBkyrs{l~peQAbWI}caci${3<8?aYEMB4t zc}*Q6ELP5n{O@q-WLNw4(e6l-QD95hlf0|^g<(mgyqxiR`BAbF-0T9^~(uYyKX+V@mr}o91Y%{3Yv=aG{>=svJ&&+=#5)!Y*xkw zWlkQY!tq$WhWA537mm)Vo8uc^!v%zhocmZpOXDU}?i!`+atL`cV~=FajReMuC1Vra z+}5-jUffq`$uHhXDT+?SmaJ*-3aNufcH{btiwrrDGo*5Xy7rfFq&aNoTcrKn#C^?a zK*hbvu#4WdFC&H<+g{L1YO(s2o2_ar)6s2e?V}(*+uEvhi{F}~!FA@7&y4XT6VXd2 zn~7r@k3$V2zDa!=Q3sU}FO}{eM_!g;7#I|cojfN#K5$7n%``k2Z^grdMqKcmkSM{h z=ms!jtZZR?vm%|;JJR;R>jus%$$W08qP8osd^slH`qG5JRr{^PlWi8XYXbG_{5#=? zt$kjD_WkK}$PT)xKsc-UaeK=kE4pb)TteI&I`+1^MarPu}Q0eBWF zFXu5vFrY!Q7hAFcr!mMvZ7lp;6oH~}#JU+Bq916l3xb|<1Z6)lRZDr?NaLSQOBa3$ zC<_n~vkL(3$9cnuYc6Ec;#iXhrREtC+ZLA;z8#kevLYY)1vG{nhA`O>dcofc2T`-yK@9BIlSoS{Wq3BDSSf12-%n*vKv z{qm1WzLCIlcY7ZXe`a+CPkUwrTgsG70gH!SHm})pSWlh63RQB2x-*(;TwxXDK5~M? z96{gh45Qn~zBlB4^NW$(KB8vK=49|;N`F36@$cAmBCTPfd~9)d7p-<{2Vfx++S zy38uOT{1!bX#S9{{C8QuIgL#xbcDx)IsqOHy7$9t{cc`Er-Ye(r$G0s#EzLX12->s zQ3d24J-Ue7FJGP<;X4x3_rrKQl3PJ!`jpP}K{qXTgdKe5yAi{n4SuTE1d^}GWk1gf z=GJ%mp&Lr=tN7u(*EE?YwZIpViE@pXY&~7(KldfI`JMKVc1`emm#TgS$p6^>=)vvs z&lGun2Iu>NGvC1Lp)~KZL@!J{KyNor zipX+iDUi>;ApiX6h*4kZ;6OFAkg@QA$R_`)vIl(erK-mN=g~bk>MQZJk6_aO0VpzuE%Vg zfG3xske6b(%{pvKAnej{g>{^AZVG*BlR5BrMUJRbBg|H~$GUwYe&WQ;)8Dq*h?Fo! znB3MWv)`%YgIJYi7&*)D`Ww5P8E9BHFS8oZkH|$}|(x0F&OXAJ=M%kLsy{3xsqwBS=mRaW?YgDk; z%VM86IOqfpYM!99;JKf&nF55y-YF7h2@J&ZW4a=SkgHq#LKJ(P+-5t}C9eaWf>4mJ zl5Q^SRwpFB!4(r}y*<{q5S>GUVOq`|!a10x!=~r(6`@znpVKVu5O#Gb zZS#P`<0QFs#d>t%_*e@r|1!1eRRY& zSBYa=498&8XZ>er^Rt|vUqXeLm=cC=A8EtUW`SBfO?Q@7yltd;*}KOq_6c5b3lXTE zOQsczKUHjcbV8phLo3*?+UJ`(2jDv)EfwJ%c;N!R$N?Ply#wupDVf{DM*PVE9C8Lo z*?Y@obw6_kJb5B6t!bLvqv6hBtd;ftrs)|byfM)nIdcE{zbJdFs5+o#OE(Z4f;(&o z?(XjH?gV!Y?!n#NgS)%S26k|Fhv2$_;Bq-dRkdc#`sM?gZ>nY9 z?v}qVz!Hq`@%|-v;$yvg4iRaUq&v`YkDp+zZq;ZT$RoI#ZjIb4A$J{R59#4luN|1` zDnt+;u9YK$GAHKy7uHT#Xu{7J7wJ{1-9n@x@JD~n@NrrDl>mK_)J5;@%9vlgGcnL) z1u6|WyI&OqgGqQ*oUe6$^>oy`s+PO9-1dBb43=3jD*Bhfkq(% z(j0w!tXRk zMxo)Y>-S0BGSS{9Q3jG4m z<~0iOsjRtmKs4MMMw897_!XWGUB(ZqV+M3$jI}ItIfwO3oA^ouU*Xv(zdv(yix{00 zPaH<-*l_t*3&*;yT0=$TZ>)K8kFUX;4cPc&x>FG}tj)BO$cM>YaGd$i^s6p=^QjeZgsez? zjYGeSMN~HY))4A-!(pn{Cq4qs<)^!v%BID^9@KjAB+w5g7q@bp#Pmo5% zIle2W7HE{9iH>1@m0U~t!vdgeWjjUJF>+%WGU08bm@vkd$kYQR#BsaB(aUC!W)NPo z%+qw{&~K^|WTE{IIld58NeK(-A8vYNig(gcs({J@vBSg|K}c^E_7;DF1L^tlI8D8V za)3-k3?8QOVO4H1JNSXg z2xhu?;i01SoA`w4{aYD43A-*R)sWoAvTNQOt#z5d9 zbD$1vFV5BcwLgHkU(nk+tfP4Ia&cX=j#R)hGB^1@e1L2tzr0guBGyw#~{Kj*whEP0bxR9CdGv%Q??rE--IvZgkk> zjeZ=Q)frk8U-SMeP$pwh1ncUHM0^zcJ$dh(UO@z$&;@ zd!yx8cP2|T_sj2&Mode&^)fZ;NOUEYBsKNE>bveuzx4K+&!DY~TB$@DY! z;kS0T2!r)ZF+UkbFWQV!4G|IXop$%mDd2A5ZbPk2IWxnUhgY(1+eI3Mvb1N#J0f-I zd`Q#RpTU-V-;_1lZ|Go}yH|t=hF$^~2$tSjt@ZK*rf5fEba|J$3_dS#&O>2adc{L}O`;x_-y2@Rs%Wq0PiiT9hJ`qD&S|SL}!ikJIrb zqtn1zH7;K8n!CohsZb{QCUju`Tb#}65swL2f-ekY(vAWYUDTsbJTcYmng%F}OF_y1zHKHeMp$RE(x-=>-3f;n?W zfGip#N&xHzEg(E;tkjJJWrR0@+4bZ$Iv&w`k;<2bwUx*N>zrz%+f{qM9%z6Bvc6U- z8yr!iZUUx)Jg+r;WBc@VTSLaI&-pf#TiSgIsa%PMZhRV?4%jjtFg5iB!?*+)@CE*T zE8^~%eZf3sLh+PxE`8N$`AK*bQ_#>r+TZs~xD?USoU5Pg}09X8-(Oxd@vNr;`GKR_pKV z-cyjpR8PoR+b34;zA%_uFLEW6kH?glad`81Kn!mGi}%TgAU;=+Si5}YJN5+CCEg=% zuZSAgXBRinI;vn^uNv6+ZB%tKUb|d zlyES>SF7WJxi|c3oar^iiQoS>y%Va?Q2g;Ju}e1uR-H*^7>*$j-;+b#)>8#2cRq9N z0{2btPzT@LVV2of(7ys#J?w{bSD$8(fAYbCBUZ3KNFj{|MrN1}LS>mbE}K`27JqZ; zScP8~^ZGBEwR!4510R0RD$5mSl14lEJmx674C{L}f@dTBE%akLs1ae5Re?n!Sw699 z+Zpu=Z`k1Bw{B1DesC=QsLXxDFV495OVI2288=bR?=8kY=oeT$}^Qmyip;CFVPmYMUuVb#H{iCX5813;e1Y9YrY2O!@z({jaLI#ZPk=tN(Mf_P--_ z$c^2(z6LrmQWqB5p#y&g6GVlW1%@UJol{~~#wKsdu-12dPSzk*`h&f(tS?RiTVQKe zfM1K{P*V+wTFM0MJ~K=gTbFe0ZJF#(9xS*O}_dRa=w+3cqK!@yjWsMFN;{ zt;(rVmJhnB#)i)cS#zAw2-Ap~kr=i_GLC7g)~oH*x>YE)b`|1pbn#5dV^URuoI?%X z1wA!wDo-ln{C9e3sS&O5((j*8dy&P9<+!gh8F{F=Eu}<`R_oPaH85lg*2n8_VFkD6 zHYm{z5-jw?bevKe3pFx}&6Q)PgWePC1!n-2#r1)^Za?uV!VJj- zwxF;OVbQfFvz%DG>O=-H-Ep<2WzVoe-kHxe@kdZ#W(bSy#+;3O(Wb>^zgI|ujlOks zUB$}0UumW|y^_0;bY0@-lc72YH#T*_6G(ee_V^I*^RaX#EneuEs`Kw^Tb zN62D`J({C5lCwD5KkrT55~_W)fa&UxXn3|4+hmnOk5oiC6$<&5n2VFqn_>X^;FiXq zKWo}}I}{yU@`^u0B?*7?*o-NjE$UKE;6YLvHiu3#E`~+3P9#2j3+t=4M8>?My;42( zF7v~=(Szp0KQ`n`g5yLDE*l%3-<(MOg=!fnsP?$-O}eiNkUkf`evpy@zUa^MOJsL_0MG<1gUKGk~ib}&)0jzM$Ns_pQW#n{D$am%?% zA+9}~T*M#cS;cndK70LUxGPtR_M?AD)AS27>OE`Q_-iZ2NAhPOl*Ual3=!zGlJ)5> zG@jOgf}bHs`!f{eW_Hj70lO#Slyr9Y#U@(~lQ|6J6n4oC06w^;+YL~ORuYiApUV8C zKgV@XR-eMSEXH6(AfbGvLc&CrbRfECk~6ot2%++f>oCE;{u|-;@+W0_d2}WC!5J$9 z)cfy*X^dQdmJR_{bLDg)1Aa97G-2;JBQAoDg27S&6pP%X{fQ8Tg5Bt0t zj?TuYbZ$Y?{h~));;ghSt~U{M2h6 zyoa6dKJHF6+*jW?N)ZF5ZvQfSD+WIn*~AD~>;1iD?fpKX(rzzure83fZni;n=PaS2 zVua{MxI#6%Yw`O-W-e(MJBMke4 z6<;{yr#hncNO=@qF+wIfNVzEQblo7Z>;);N%M^b@qA-U1=7_cw$9*RjhW-id#jTp> zYj)Bs_y7d;5%)~4*Vy4tI_JqBhm}Y!Ilw`;#Bo;{04`>}eKL))E3TZ+@5oXmD67z0w3;Uq*MpbL6lI@qO~8P#I`WP{#cC&dsEih-hgm^H(<{Gp_To{!llRyFnZ z=^*_vHRQ+m<2OSy%u|q>l5S-2!9m7xJv@%YLMwS&Zg6*Jj!r`cn;Y`4+dK2A`NQYa z!PqP?XY*$iubK5$*Ab0k%nKE* zdM%a1)Ja%c$lU{92yL{F>Kwn-fa?%imY&7lvl0@H>tYCFj1$XGhipC7Mz)7l_$k$- zYZ@#GR~RWjDE7DS%?4*oqGnTJh*@r1W273ytloa$QNbexA@PJfb^NxVo-vxArKU|N zS1u)W#-l%3|AC<)A6GDnwzz=AG56Ao*6chzLM^6Nn7MA>m65Ne5QTQ_HwC> znb#0-6qYN+@#2u*G<7s?W*kejJ3&)1zh~pex-`Pgboo~3xd5CHHI{tM#iMWLfvzZJU1NF(2hvRhqnN&{X?M4ZBnL*hU?2=~kc~OX1xO>HM2gpUS~m_wE)P5oG3OKhH@% zCapESSuiZ*fl})AmSayZC>FAjVGr0zw$BR$QE#2`sTE2HXiqOde$&yu(#DRnc=zeY zT1b}EHYkDj%;f*Vav1UE+X}VkyjX9nV*Z-Q`s&2Uuipt##2hP`3`H9LNP5&>v3HUA z5*jl@p+3Q8V!(jl$?!d0ix8&dJ3suH!>s!h||!9Wf8Fk&j0y-%2?BrPjr6Gx~>p}eNu(tU)h+fUmnKOPBr9#i!Y;VOMW zpp%=X!OSfjn{)B{>$`mn%5vkN+}khT@WkB0$SfGFCb^M0z@nLWkbza%b0GxH z#X5jg9u-rx2#33Tk=u$076r@nXoZ3nFGW2PeOwXFHl`f(PhBE&35LYEwp@_xz`)~n z*cMKawlg>rDI+3DEw0*otgh(%{vo8yJ+k&*f-9jFz62fHcr38476S3%h%+v+(rztZ zf0vw4>YycOM1ham;~pTRU+kgmS{NZ*F8*E~K*0PjLO?_RS^Gpw^-=f9lmCFR_n115 zI|3+JR#T&9b>_16Q<~;g`1pR_;dPH2xjxkY%%#Ji`-Ng_GIzTpm*%w~Jc&-B!ewQ% z?lvwDtzi7e#uV9O_IDlqXO!6w%g&F8B1lNpZM`btG zMRQK2&w3f##h(0&eV0M}mBrfC`h{Vw6vFv0pKltkU62yxBihwNm`GQ&5cVDM7bt34`YlUP+WTu`e9`4gz@_bX)J@PVPJmV zcI3tOxb+w=wEaJEF4|O9ysfIx^15)*Y5Uk|+%g<1Q%RSn}X_vnL5g9 z_a2iEatRe4Js}SjMXx-|BLkx#Jl2ho9Qg+2EI+*ObQgsFR-7KquBJDEgCUpwnv8sq zOGHi30NvT+HY0ppPbeHiy|V*mmoE)nJ?<^*ulAV{J|o`>DhXGr;=y2QdQZ3}lz+h> zuhu0AT#C9KTYiRyvh=xOPE?FZpzKYJ)5rh82Crc&i0y{?^5rMWmoHrZ)31lLv4fe5 zmGLK;MOlW?)Xvz&rA6z-<&!x3UVC9?Z+=@pS1Oa72Bk|%r;%1{O`<{MhF+3Zv*J*^ zGNe4{?Z#l=y_V{s<*rY}Fr#cW`lVzB_5eT~|3f;wfz1`h-#laYYL_eQs@6;|4NE9& z7xrSa=V8NTN?_Y#s^j@6;Qg8Fi}h;-EZ}%yro@z>6_MO-8qP-JBz&vc^=r}Tf^2KH zo7jm`W*U2~1*3%&8&_jGRI@K-{&XA?BDqWUW@_B5ep?*T5LBla6Y+Da9=a#>Yg&n0>^ANX?=#{_1Un(quFn`*2MXP{PLnkU z@0x<%28SVN?{7bJ-8Jyd=NVwkU36(th~~h|_wfAsNxK<$U)NojUJMT^b1Fu^W8z9H z?>3)=mlotWnOUU(>ur#(6YG@N&F1}T-WNpI;bXe1m7P0bUz~-3MZCy+3H<|ZX3(vZ zdEL%8A^oOjl`$#@`@DjIKqTkUbV>-CYgaOVi&6_-Xr-O&tnW2*GhDN=N(gsIKD)a# zb%)p*(w|x#xgbM5Rf0pqcSrCqkwurUG}peQU7WqyaLRr{i8BePo(ng$lB2h>aqO~z zNBF_MG4fj}nz%p}axAhqcvniypEROGAr{(*oyKskF-SC-7`%#m)`0+v2JTazQ_ovS zDGd7_JzzNQr#6BwSherM&ET{bxWHD$sf0zXiThQqInv5A!S|$2v*{CWeV@Yc=FCTb zBsR?@7c)Qc(Vw++Ub&DngIr?Uxzr8*o?N`lCVj>o+yrTWMF~l>$ti@1GJWQ0m{=dp z&R3)DDgu&VK6#KIb5fMmZOhR$n!cg7T(+Enxr^FmHJoAoO%^Rex~=(~<&AJomBypP zTJzLx@H28DA!oW%jVOIfe}Cw(sCi`AJJXXHb{@m;tj3*;>bl)p=dXl_b@xujQnM*6 ziNRF+VKG^Blo60ul9_f!suVND)L8GYrbhPl$8feAp%Gq3RCicVoGmT}_d?+g@ud3c|UT!0w3UOz@;NSQo>_!bkrn z*IuwY&MPn9s8g*TiU3Tkq0rZ?RN7P*X&c4T06D8$dM+V1+%E{*j%TaX#^V3Y5vUP-P#&5YHG7#{kf=B z3qznexvOigdcZ1B(=h$`b1YrNE4Ipo*MUZhn1&{x{ z*BL`b_jepSdtw=S%CTSGYdmQ&AvAZfG7pYvsaPmqCu`}tvVSKn71MYAo4nlQL3ffp z$H_~nus_|&Tyb|E51Ak$f!;s!#fogqKFS1Z6<-%dyRCr91CzyDF&#oeCiXGgKD8U) zr9j!_N=LSAU9wHM>qd2?HZWkZsBb)Lb0SxC_2E$cjSL3uPp>_1tlT1pTN$Pu1Yc&- z`H{mX7}a{cZvyBswb^weQe0`z^SiP(+AxuDHMhw?r_59&6D{J1Kble=f6v3Tk&b@7 z_}_fZDW}7kH6_mppG}zg7H`y6d0EuW%3<`4rnW4iJUZl5QkP+$@k+7HA)T|KWRPuc zZ;o;NYI8UeN{!}XjsJWvdb97JAg|$SNs^^&Tg_C?cetUn?XTbZ43bDuR}(ENy0vZ@ zyjC@x{&A8y>5r=IBvs$EP9fT>?DQ|<6G%$Ltp+Nbz15z91zfh6)l=}PL^yLe12!v(O)uO-M>OZ8!0UowVrfS|w5cTWaIf5*F3d&6+9c|lx{ez0Ggxx}M&x|JCE zL>QxPY2FV61_4(;OOojXUf)t2W3E_#d4M7Ov3H$ugg{IYKqj&kK9pC2I;%$wlopyh z-gf_Np8y<^XA*zni+8cFt3r?;(Eq8$m2pyjiGu&~%1NMJDvNe)YeJ93AwGR+D*;k7U- z?Y}9MiyITv;{7@Ye+9_maFc|h<;qSw*_3V-9rnH8l&^BWlW6IIP)DSP<72fbHD5`s zwOzwbhqsmiU7Ml0Flzz%0b0hr{f#U-k=iFQ%hufb2=ww*ql(L|ywAv^8Oyt_4}ABt znxF=+P62U6WP&)$5Uii7-n;(Oa^W7=7?bv{qhxp|-mxwAEPPmsEAFH;K=?VtcvO2N zqd?(QwCKb$hv9gWdmBKsQ*6J6AR-SBQ(rF+JJIZ{?Anh(QMCprIS&1*eQ0gUdTaR> zTx%EYs@aVdj%xjFo2GO;#z29*c7{^hI|_4s;q&EsQKR=ilL}(Wy|^OuQqoUSHNra5 ztIj3;%}64;NYfG8d=Kt)_6u}f22AxnH>~kriHq;5+^Pq3vv^5*N?m!;)6*=8*h`w0 z@1BeDE^y&#oXl8DdqwF>SY@W~ip{kfOm`bd zaq^iC>xC{Pi_ zlxn_u-KQZ4KpSm79A$~D<`@O!vFX4>6RJ3&Do_Lff7f&#qmVBX>r7Hlsa|N6`nQUM zMvWc6#T_o3gWRn+g%vyQtK1Kj(Q!Zr=NCN>U+N}4ysSJ8 z%f#A%ew9i_EkPe@7jmM0mY}Y@UGt4EhLp@Drs;*QU2$4Fsg*{<`?+y7=VJY5vT)j! zidAVli@b)x%Lo&$C0JVW9x_~$&BOd%beMQrbQpgqcvQP(foISTouWgSOh#MH9jlw(9ZENvXyZ;sRvuD=yN#KJCtR3`iTsCG2VEheBa59657f zXWyWbmSaV}{RS80`ArPL-Kh5Sai??QqR0Tnn{-76=Mcz8_FHWwA2p?ixVT#B<`#-y z)2uFK#~tkQsAbNQKpg-|IqbwadLi4zynC0+HNN2VUx8LNj#&1K+>wT2`=Pg5P2dwa z>tyg~sVFXpoGis=BWT@X+ zO12MV@K~5W^e^k#p6-jYCcib%i}(JFbvy6Prpc7@d%z!!scv_C+du3VP(tiOp*J60 zGh9lr;8U#2pS93!6i7ZbomCeo6n4u7>|?<&?eX^MT>i`cYm$N@|JpGtojfO-8&+nc zyK&j9QMbwmHgsVP4Y47a7f8NYf#=*$DH;Pf2_%pl|}J@htRez)PAC(Y9ve1#rWZvL;lUtM^i zj&SJw{`oYkQH0)(S}1y$ilwRa)mYu2|Hkjgd2{&rwDU%!+NK=!TdEsf6C9U%-e3&O z1tjNrRRI#0zIxX;OJ`GT1}f(1@Cv?g0LK9bRBO;Y`yU0z46P2G7bsA>;95Mx`g=Dk`m zYs!J{Kha2t?f;rlan3Sj%?1ki`hlL@o`?jd9u5I3(LPUG-NY|Cj*cS|8=_e>Ti$d4 zl9Ba2Pjqyp-S6*zz)Ze30>S?JK{Ta%2^q1NJUsM=WHg!sH-J;dpAC`k4+6f#_>QN; zDA!_BakuZpyUtM3I=Sx|KGQ}l{(DyfGK2x~&4*6+P)aA>V!e~AO7LW_tJBvmomQc6 zyEsD(K-Y#0@IN_T$6RWl?4`!XTd-(n@LKC5-#*ty$wvS>C z(;s=iX6kEgWU|Ayvow+yNBK9Q|7`OD z-sAv`pNROXPu=7H)pGbB4BD1Y3Os=n-bW3^i`kPMm28e~BnJ8(d!vCCh4r_VdCHLn zQ=YgB?N+s}(4T3X_rpnw9eg~_M!yq8X;N-<*Gid$5&xRX7@s5K;al*2MFJj8x$**$ zfC+tpD52Nu$<^(r6e^ZuuooAXO)uT=_g+MA|N8m@2!x6Kb>i`%*rD7O3KhRw=sK4r zq_^5KjOJiWTOuG@FebW`_^nBgKK|ViHZi#d52-*f@MOqSj)0(erK)-saZ1}0;=Dtk zMQc-C?b+JC*K;hfc#mjv?%|QOp)y_GRL4nvQ{c^NkTLoZn&9$tEAhUudB1GQ@(lw% zuv5%p%9XfOw?Vc?5!a&jswjxpLNr1-vYkQA!f*s|!d;pgjq`^iq{ghmBGMIi-6=gx z6QE*;W7St88mZsAqln9y4mk^bl1DG?6G|ZIzLBTfT;melYM;o7R>Ub6c7gBg#zD6E zSa^204bQw}S(Tc?#x=T(u90>#0ai*U-| zl1EAblrjCM`B%pLwRPZ9HtWl>lGjp51MCH@<_i=g%fb8`BtzLJGV5GvHEsXSl2wh> z@~=Ks46>`u@tku!n5ycD6x!a=6=MUAFPSRtj)+tn94zbJx!SH!=!9RAj* zZVJam+6b0xEq|J7j`b+3Ou;&zb#u7bc6B5OlUvVEp6~L`sWCLhwsXw7BK5IGT(vFS zSsRE3czf*buSF=uGCq`3eYRvZMcR1kwjOtlWVQP{hgb2+enIlE$3QegRoG0NNBmC? z(w@806jclou+SCp{YRQuYbt}6X8&7aa-XCSVoinJ&5WATdS3GFu!bZc3)o$o*+ zHY;oI4)ZA)VN@My3|jMN6tKZjdzhY+Xa;IIo=`1ZGH+7G9*}MB1b$srw+`++-cLQ| z$NK^^Q2dChbGtP*mAQ1b)>zZ1Y&#QveIl#2W8&crj!1*%Z$p%&0d_IBp9*OI5j`16 z18W69@Nb_dBT?shZv5gFQei6mdo8^p9j9*{?5kb3fSg7qwk!Rh+09Qg#+pCLbE9nI z3HxB}y;h`3VT;7nqg9GPpoef0e}0k|lL1IB1B@`W^V}L%Eqt@z@B9@+$_ldY{mU{A zFj;9NTnm=nV6Ix;ZZZ%K&d8GcM zHNnEj$lBIn4VfjPaafJFb9t(=2;!18m=d^{LvGe=$BbDEdx5gAQe+2?4#488A z)uizVPJ0=qSv$1s*$osYZz`{<*L+Wp)+4JQ2wtvMXsYw=$j?>fsA9Tt{J$w#dnP_q zsT%_2>>8^y^ni3RS@FwIg9jZ&yGLhba05e+VQK|p74j7X$W%2!xk+NBgXd`|z-vRd z%nLn&Tl|#*M-T#|@Xq+wDP6X%2G^)pg3sh5EvU}f4HH5YqNnLhAwt8-)~bDP(=*6) zrD%AjKkwo!$DS^4n9B+6+QP2$kIart9>dS``z7J;jZa+Wl5Sg?Z6I-`Bsuw@N6Ow| zwQk*8D6Pqjt}U!pbWcPEQ%4gJ1-r)w-Z!lVOZRU+b#4-u$VqbqNcRhp!2zN!?Cnw7 z#XeZzytUdtBJtWd=d*=wQQBIRHViFHd$m*ByMa@+8vHApg*~C+-eTzuGBvQ>{~ESt zG-8yetQ`HC1%HyV_C_UZu}tQw_3T=ew~?$}yT=n9TiaRN&Hez^T8YW+2X2*GJ<<|o z#&XseJ7E3rY*f+j1*2?n74B*j^K$Jo%|81qIX$yxrT*HmV{Z<7ZJ}%SM!4D&$f5CL zyNkM6Vs-z?4|m^(WQuY>H|nu3;#!c78~`Wfg!`2+e6>X07_NzXgq0g<)Gfs9if1eUX4~|5`{+u?H(y<{4k`ZZ5yJmi;)T~ggt)3N_K~kO z>Ble5p5S~Z>|*nPA%D@PH0-x5{(MSZm}_49?Qfrc#tVssfL|B*yzjxBafNr*Z(lmY zjvN!jl1p0+XPhFTBChi=ttwfMR>#1dHr4ffc?)R9NCfmBmL9&|Ahbl z0380`T8q7-`~PnMp$+efhwHODQLe zSo2O7(04;h@byy5@$Kx(QQFw`2@NT}OW>0xI=LkGzmDj4*!Zd+CGLhQ^_!lcjnAOx zLbB0T;WKG?rx@wWxWdd=6{o6@7ideu%R(y>6q$vu8COci+dy%lOxoxTLPC!>hgdL_ z>7<%o=+;?9W))#+w0J2~I_b|S7wM{g%c``t@ea!0^o~CTBBjW2D@sCJAWYyb$2gH@ zHtJ#q=R;DFUySOiDY1L#S4!4cn3|>+Tv;(A<#yT9S`Tv$*QflRB-E-gN;jtnKDwn! zGPKfYD^s7ZtgwkW-5gLfxQeM7P#i-fLet$J0K0Na;O%DEA4Re%4$=oANcpVw%x0a@ zQ6+h>bLuq`#=t@Z?jgw@(M4+ObJ$;${o%%Pfh#WRSz=DfElTB5H1Oi|mSP2dQV(Rx zQ&d8qAm5#7pf^h0J7^QMbg&8>c%c#6S_Bkr`Kn+#>VE#|6$k1)=0*l79Z?@lyezN=W&yz z$FKpO6NNyfg<}ZF>PJH_NoHk3W&zd%PrryU{HA&gu>B5jgV=CjEhb*dP@oOC$*p1ai@luf`&Zr$Qx0T_L%aL;XRp0!CPu_Ur(4tCMG_ zO$D^Y$^a$FSZQmjHn*Cj$~(}}Tix8XGfYZ*Des z0IWXOiY!hsWLCaIGKEj(8UsSwlcyfuCyO&ncFENjnliXgT*+jBWt?y){_%?u51$dM zoOIztdYX2QF$j+ty4n=_6$*xX1!TX|1?}(%*Pkt|HT8HRExW=9{N?3ZWnNmsLCcqk zmWW0SDOe~#2+{%^Or{_SjoHIS%LEeNw4l=5Nw1LXvtC=|W)l$DW&)V`l5?QxsI*!e zI$GQnfWrjqdiXZo?T}is?Xn|OPA&aGG}nzt5}fP^>PjlRE$xbf|5)93lObzuM8dC^pFVEqtTiY><7==6~23M;v989F<|? zs!_x8_^_XhD(+x7_hGfkv>S{7R>)AFw3P@#zzPS4K6tdZD|1bF*PqMAFX6g;U zDR$dY?xnL|XLTZ1yYY?jEiA_Hj)hvD1Ffk|PlHWn2oLj&7MRTOoNN$gtuo~$M24Cv z8xza@>U!YFiR-S=*^T(4Y`%l@1=!W6X%w?wC&hkYaz`?i#;`>fYGHq z9~1whDKIY&YqD_RIH}|Oqhb@2i+1t2ydaC;KM;e;+aJ7{$02cHi9qlSpZDRa&_WC? z#ZQ3jaCv}t>i$AIg=Lb*a-pymFg}K77wCILVxCSOY_QfGf+77$ZB_+)uBdk5vCcPj zuSIAKsTQtB* zi!Ux_`Bfg62Ky9D0+)YqCo0E2x0__S_hjT9)FE(ZUcushVIn5%za{J&X2LNPCOV>c zzR-l9`@uV=_EFYcc#)+!$f42mcScB0GVRG<@vs1{g4dzNI zH2JoL4Og3~!lALazV*$Nknx zE&%%&2BoX-CX{tnuaZ2Jj`om^av>%9g#YPjrTDn^@kU#X#yAv;oxH`l+av8;zdBGA zN=a}ewm`1&K(2E{e9D3LDI<=w?AKq3A15BZLHh95pN}UH4SgRBE_Yh8Qw`XiZ!aPC zxCAzn**aK~2&r@bdRc$D*2%6ocQOBQJPiDrF`nv2vU-Jx^%RXv&p4&0uWG1j_=;-r z^odTGLP&i+fZ$)cWV!&R`+!poD8wHbZUQEm9`-7p=C2$3uI~i-dr-K-eK}G6^JDwb zln|f6)jcls8tF0WZPV5+=xo!zhme(n$G3w%4F5%fCKEx&5`z?Wo+~t6TMLFdkxqQJ3T&=sd zT}t;|<}LJ%XvOL}^-S~8_M+971MA6-g1~pI!T{)qh=BcXp!iMkN@T~mHo=DUvRBG( z&*+BT!3mMhvqY!BkWbDD-L5}8BKbt>HIasYgc{1c_NbOOZkeq1**KC5vMCG=vS~+% zo{+do?od5}S(8DQ0lHUCz!gu`6ne=AA{9y@VQ-{oKlmXuSlhQ+d_P`M!GpyR2mHXK zm>WPG;8u%t?@$d62!81M&wkghI@Jf|GYu*KGwzw`|2A9HGB;5(|LJc2Q{K_i(%QlD zzfCqh8k;T{8kirO#iN|YYQIZTf)+8cl1t4I;jq;jh>Ot`RP99R9#y!q*UgN(2t2OR zX2Gj{fn3|?O)+V46W7PV0m=KGmy|U13eJRc6q~Hq6fV*nxacoghmI81F}ZLHGta#tgQcmZE4g7(No;wu zj@mlta$q;^o5Q;=ojt<4)?n(izE&=PLCp0v*_=Qt8Z$LYXTt$hp!To9IT~+`cC+Tc z*_y#!GR3G0JLF+lG3DH$Dq%83C4T#!=ne*4L{?$@KkYN)%t7}d4v6u_Og z(YDboiyUFQs@$T6&qQj@F#9PLTMjN=1I)=hzAQe{3ldU{Q@b3{7_^2U0;D74 z5qOdgMa~XtcT`mdR)f01jYC~6LgB2GgsJd^>ZL0ev2MuG?^}hIvH|&N35?~Quz0dr zq?&+jj>pClA47@lL!>g+@E)j0`HDX>Rf%7#SaBH2NG6N@C5I&{(!EkTj!q_p`K$Jl zFw3Orf8=xt1k=%O4QqsnRP6F?6e9c!>eDB$AV<0~Rvd7{x{?bpmD(T+7(D4u6FJoI z{bDl<1zpjie?-EuwjxIZquS%(9992I0$PqX%{h73t{h-auk@MjCQ9pVp&?>GslU0? z^65u>m*4*4N1NS`AgJmjCxJ%HHW6;p%8;*0w-(f;hbgjt&tQz;aMlX0(MiJ09YYTlq~+Kxt49&o^L+}c4tR!*(0;Y^UvDj&kn+iUeA%7=S^>tPW7 zDuLAAFsNFvAv1OEcQ3f{|@9eWVJU0x1DX& zj4~#SkFdDf8h9v-dW3#0G*|s`FnAefnBxUgaBANO zHp3L=mSl(8BC1=Vq$mK9J@$rhV7W*77&Ap8ej=YdOpeYS-!+tbxiKQ(iwu==H+Fo5 z=(`FYJscI)y%dxOe2RhzAwtd~>$^QnPCox0>r$0C; z%!wZcNF72e_!AS$dY|G@$E$po$`^l7SsNb3A%D?MhL;Ksp~Ee>g-oRn#`t4GsFva% zS8hFF0<&!Wk5RY?z$)kB9f|N0@5ep@y?$u8?PHw#5lRM-Y%N#FSddz~Q!Oeu=wgq~ zPPG6AOtyNeKE*485%C%+g`i5KiWlMWg9@jc@GVbBmLHWKs&_H2@hrOlJRQdMf`7BK zw6y)7Z;JzS9VOnUp#6^gxkvPWds{SIt?gX?`$5UovUR~&#`BL$WYD1|fn(+@uEz~F z4`g6Y_;rzmPXG)3gudO6^WE0kp26^fwo8ihaPBUPQ(^sXOL$$L>c?{hn+)Gqnu+G^ z=YoLW0i|u09_BKw6r#NQJV%Q>S1*?@{8#47ACJ@XeUQh)AmasNDbzr?)7kIWWA<(u z3b>UWIkRe?BY?JQhVRp8F*v9V%hIk`%|DsJgsU|@^rfy8m8+r_-e zVyMBIbVavaE3B$pjkG10>-TKDNXp8b*`l#hT;-P?Qb0SGYU+Q_=`8%FYoVLE?3!8f zS9&oet{=Q|-(DrU8vK>f8W#FqgN;O^3yh_;pCIf~GNQiJy0jo6cAJ7p2w%t%RAhz0 zt&XwLyzotO*+JJ`k>jpw?N?@IL4rz2G|;RP&UtMqygH{cd!ZVAK|eN!F#h+Z!jYnN zNj1kLkvf1O1UYd2JbZlplvHMP2Z@$p!v+LC*9)BDmTQJ16unxt`!5{Q}i z1Pg(6#&Cy_TJZ#^mx^VJlM*9LyC`%tO_A~Py=JOCWYGm>MaD%9z0l04DQ4Ubtqdft zMC=r7aiVZ*O^ht8>=rhDRvW8}hqHk*73X06z8Ro5SEATi{OM{CwKOO_Vh6hhrmO2! zi%eBrMQwmdcsxvtQt%AP6 zfU;;&`XcOrV*#yrd>f*5a;@|8VGcxhFWPrq4cpnracpJFxZC6_72dnh zK-R{Q55}xzTFlmAd8Sbpy{r^#H&fwJ)NPn_j3lZTU=pln2D+sWTSgTg)Dx{~D-AHW z+qF)==_Yha+MfJC0aJa1DV+MN-nS$G{gc`w+Y%+3ulRUQctR5V`kMWjYgVlPVJr~b5aY-O#PQH;xNREAtJxyb`8Jj zVX~s52veV^?I*@nU1n-(H{Fe*$ya1#&pfp?hOpC`Qfjl(sJf&j?Lft2mztBtKi3aOud^fkYB()7t%6&|QW z#+h`ip&kNm(Sd~`T;J)eYepT8x@O!>7BTK*CnOzquhlz?S3l)`RDBp}YM;xOrG9Fo zVDgjAZ_yA;aQs8>!19K)PuIRSjz8dKQhCh5<_2Edz%k|px-R~BE8NXMd4iSA{fzIx z{DU!2y}j!J^51lw*Av#$WUhOpPcKRnPhX{9l%G^HUhesC5FLcs2L|1Hl*l~!E2B(r zb$dc!c`5~DjYpaxq$)+o8v}MDj>w}wuw`wXx2%J|^7e1av=N)s3C(-)rXNlDZF&gm zhyBky?#IAWjYI{!*nvciL>XpZBp!Yqe_-Uy8;E@U@N^V}K&{dl1(>#XW&1*{Z5Qd< zLNSNp8Gik{U)MVSKB6k~BaT=4^#gF!qO1hn@GL@>vy}m8^k>HDj`+W}QoUeI*!d6zkUJ#Mas%B6mMv3a6-aD|SeFwo`8FZ0g00yz0bfdPXO9ISk*T zk7W_(%om??S!4{sp8m#|bMc-aFkXrnoUUm&kY3n=ICj+cY zt{!Efb$@}%1}Iy|ZQ6f{uC*fmy0clctwm9@lgAow%)@;5!wT&b2u#J#U3{PTJ<~q) zet)O4ejtNoTW?Mub2w-&AL^ItFX2^ugLXIEO&|Y}k$<}zFtGX=tx!K+G39?ZS``eP z&6Vt2oJ}16Yrdwaum7atvA*#%@5b+rN_DWqniMm$jI|XLQ_1Uvb(Ujs$nL-<>LcdK zt4e~J>$@s1YO;$_wQ6V&wSLclj(0!84hWwrWRdWEjx?(S5fye}0z3 zSj;%LY2V{czrWYq;(lM#oBSXr!#IYZpooHqL~#GOSc1Xe(_P1D(~9?3ndcmpmk*Pv zb2v|n$y3$3Nh|`6luJ!K%fQz5GgOI#LM>d(YOtNe0OlI=6;;RSOU`b8bc96=D*q}z zP5uEaJ49z;%|8;G2h`xM5YC{=N4FZFuvx7Xl0;U6ye~ak4BkK&xvnO~3e5b@sOdeL zYyd9l>_l6y7asV<Wm4pPw)dV;xs}E)S$4&^4bbN9_plG^i+4dM{_rp@cJk$jetS4lwT zTALMwo1as5s5PxF-bdvV6cZCuHUb;KVd>Hl>`37dPYJ{txQ$K*cqw{T1Rkd-nZ1sx zK^8?zHmpP}8dSyU^B3@P;fvSyg3sv;bCH_pAw%bUhqX(Vl*^0%Ra%frb7EHU)7!Eh z0?J*mR}4cB;$0&{9IJlEg%*+HRz63zvjTbLJ`0 zdMt36_Y3e5K)$qhGSbj zwGGI5jDt8-s+*otelLx-7%RBDNOm4p!Q$j#c8#sUc23_`{WC*de(P)Cvpx|9d7Qtbrpc=CR{vbJxPW8fXb=XLra!4Oykk5PRnNBc4P`3qr_TDF0O|n z!J^|x90E-(%-Dfm2BOVlQJ;S5z0@d;(voC0td$e#EJ|~Nhz8r~@L=Dwpdh>AXLbr{ z20u5@jOS=_*^rdT(-H%C&-DQxBC5@G>qwwCUbeYvk zEJ)x*;3u4|(&v}aMv?TGx7B1a{UtoWfmR!+IOHrmg*bhF!Qx#-7hQ#&#jKuAF8=4NEak1$L%J* zm)5x5AonYHN5Q6%dIi18mO!;~+*kegD!M0LXA(?!Ysia+z%i|LTUZfBQ5?T^X`b^W zd!>3yC3)c4lM))QtZmA+gx3g2$BW+^dnF65zG8^PzcI+3NQ^cD6^!&pXWn|P<%+lk zG=;SG$Ql9vEz1qBxJ4e4I)A62=1e%UN;K}QDzs>mhY9Bz>IL-3T#eoxn-iOY&KJ@d zv@;dai>?^jpDzpe=6rPn-P`HwYu)NG*G|RG`GeN`?Ag7evc0idQxUF&>u>Q}bu-|( zMy=X;$@_JkS4KZNGb-Jq47}RmD-p;E*RXV-V;~ZqL0;Z5<>=ojt{pFyOnrCm2&7)2 zcC7(wE#t6<6x|;XH;?JJU*xxRbi3EdFAxE*@=_I2JVSZ%BanF*x2y!QBR(A96~_`O zY}3A85tXKA*MO^@sDF!S=gO6PUW-&><)7!tkS{=2fnP)|77}lme0xS5?L~pR2r~_d zZ+x!H;qRz}L1(c8t^G@cjRkAP&4JZZV5n&$VuKJWxpt>vXq9zKI#$&krE1%~>|>v_ zf8juTWT(=6c(;e1U$%y?)g}I3^7~!z`vvB`vVr=>>hHNUef~$G#?O%cKR8^R|9@gT zS(w@WsA^Uw9!~VXfd1cebja^dG7hXCV!0>~5Zix$si2Y35BBzdp~;p1>yFYU9{)A@ z|7k-Rs4OmiO&KSqO?DXz5hf(i5*i?<5<>ndG?xG&4+WJ72iG=C=jrb?CZCe=p8>d7 z(WY9St1W0%s#$OLjw73gES+tySz0x_*1Og>M>R)PyA=HXcH7R%m^2n*68w6L8v8*$ zc}?>^(`<7d_oS6=Z+~<6OKI2qVly_cJ0BHE-Er2h2=*?@%T6xJ&m z#)u8}w(211n>jGm&mXW82@F^f7F^Qo@uzPPh*wlv%LMLf;ez0_H@82VNuW+&jI&)? zMk36B6O(W28b4?4-$j4SUp?d7K0(Uf*h0B+e(Un_k;RFL-=1$S)+BVX1Wt?fjz(PJ z=I!y%uPfN5l3vlR0FPZ@gboSRkylsO?L-QHS$d0(&u{pSrt@4brSmXg9*@?!HG?#^Y~?=S7F$-lTFmd!sZoI z6HY%W{%;|^Y(QhlCfL)`XcIdU>~Vm6u8{5U7SI!F5H^idcHB#vU1-=4OPNl^ao-jq zF{VhYx?8eo^}%Nsg^euf?MVm5guC}isbX3r!cfG93LP5BUVZ->v)3#(*cppV|CDa3 zZkPC92ysuL#n~t3=C8@R5o;tWWpuG2a9Ux=>;p=AyvvEy#j|v?9M`> zO2aRj1wi&+sSzO+Z7%>!Fv&rJSI670OAA6}>D8rpLTTp;G!}%gIMC#}s%A=&CG4OC z(Y`AIFOWSNbeR(riJWbD;|F$eNN*^H21pmF{KvrA2oM`h3}{=W6-g*`207LfjX*J} zc$pfwP%dqrUAVx-k()MO$hB`i+DrN~cz5cQUAo13*og?`Ebj!DU(wc(o@3*ylvTJ% z<`(^YSxZ&yUC{g5N+bvMkfh;lDS{1gD|fb9;%0@;>iJUvY*h>P3I8*69+>!r{s-76 zsB8psNMXARiNm~l7Qe4#fO$y;S9(J4eLk8_{+n{2|}8;n*ej-GxRQqHMA zgcXasOH)uM=6YN(JEq3@`R`gb`as+(j-{;!{Z;$yfOtb%nWXbxE;v5CbHPq3d`plV z8L*jv?9O4Syvc0q5|o_$Ay60RUF1C<&qNIK;;p&)A+^_;g0}V&3y;OUHC0+0X{!v^ z5Tx0e>L0)XR4JShou;kcQg0h?6&NdnMHT)Zv2@2myW}wXTk&U&Eo&EtA@jJW%wRtbT6!Gar9Ood!*2c_K z)~KNO+QAp4NeN1pLr5&OLkeMDJtL|lCUKj&1iy?^4zK|`1d4r3to)ub2*!x3*N4N6 z%Fv)dL~5u!%-NiZ5aCL8Vd$^{L0V;coi2q&b|@Q2U%*3x%w&XBz3hmYLwA0(uhk;) zueDhKDG@#9S~F+a;{G+;8HiO71_Lsidw&9&?q&4RW%Af@=+m_b2xl7-yG5Jk2yL6H zg2RUu^XFDM{_KEKBYpFvD_-laE4r^<8G#!UJw9PpCB2O* zNVoJznnSBWLs>U3oMJ9_aX0xDIira+Bp^7U{!NdHv|B?R?dtS`Wz<$ErfQVSp1{?- zO@BoFmh}kov;3z?Qm04gvjxa^D6#{)uRL*1g%Y(l6vBlkI1G%!#gnbPZvRmv8GA*k z-I}y&JS-sbBDu!;qPRbmeL9zWI>76uJn?I};ya)?PtWsmQ0-!=9>PmABs2yxB#|bv zqd7*94FgPA^()pO+}dr))>I5lPHjCy7(h#PXM9%L;|bPmAtNV|ZPn4KaXE*JmhluE zYy$%SJHxw3pDSDAm@>;-XGGBDjDf`18Jr#4;;lW@4(g~BO1C~AX`+bhCOGzbN0Kfx z+iu~3zqp=fzF1gyid>}Zrum(?3f<4&jD@FiKg?3aH*m?T zAE`*0p?@6@nsHt!w`=hD`MqBbx8I4&H~LspPfG|(0e3e9d;+55o0FjU1<*R)k$2*b z#E%xe&!ynsgdi(kFHmmXB!Tf^{gPtTW#s14j-)B`dMHLBhBlvw>rYk4c{ zJ!iCSUuw)%_8KbGyNB{V_z!Tb@l?Osebo;^hC>YtYkv70RVB~aI>My@{@rP8z4==V z6M2uZ@QJGJ=_pQ~FeJlH>@RjazY+d680Hq1>O*v?GwFAu=1@OqQ~1L1U|yM65Ha8j zcjdm*zZnLk^U)C1IQB`u>~`=%K(C>x=D94&34j>TG`fPnPdEaf%|l@gEN2&Z%AHG~ zF~wcPL=7=d7(m3r={C%*wbr^#Ek@09hf|A>AfX(L9lec9gqa3wBunWusa|B{%n}K? zk(=6c-97x9 zO6BPrL<5#Pg$_-}K^4ER+=GP4-E$=o+QqztcaY+9AtHH`q^COs;lr(%_)Uq*!K;IY90WkIQnd{yiX2N=4?mtf|NbSY20)L{{z zuS46hO=NVaNR&j*nPy~xZm|wj9K{kzIVCh|9*u(Z{UWH*3aeP8b*!ONZBSrn@vF(i zJ#hX0tMX8#&iiAipLZ&FWpESY1_B}M!3wGv?Y5*VDM!~Bx%cyTu* zl1r9L=Ymdid_I@oFHpa1=Pnc!uj0O1E}cX}*5jC@2uUy1zuaNYKkMGkVl`Jd0qt_6 z9?unAG0G+1HoJnWXhc7AC}cr`46!o%*ER}|azeC%^;JN8{Ges+8Y&F0L5UC0zcCb8 zLh<4vU8$@VC7#6E;U3~Z&Mw&`BU_&DD{4%`DtW2Ypj#;&Hg3_--6t@(4-=f?>fKR= zF+PnY=k4OWGkA6QvW1KH3{!@Cs=`G?%98i8ELt7Cr@FY<{O@0-R_0W#jn1M@Yg>Jd z!z}<1IwZTGRMl(uL(&}{#?RAVZviv|3TjGgN^De>tuF6E0VP;~4wRD#tE?@uB4nSoHAK6sy>% z_pe&Hut2@IzbU6l9dWMEeisQYd`#?KFJxF?3%IxLKcP-p&rEM(wKGzTOAzs{ejV!?S!U$ER-~`Fe%W^GS`hJ9@z*WMbQzMB^2EgGzi7{S z63$#utE5z-5*Npuc>!}|!kV)T*}Zs+e}HTIhI7Kn~?oH>mmJg zIi*+Kd{?J5dSh7c6L|wJ65VDpf)dAnG|)*H+lxRx#nPZ^TAHiJ$S; z;z?%+(A=m`8k6#}*EHho7X26=v!FJkPFN=UNAKt@)yuS+z(z@uf0pbAI+zk{mBA)2 z(#yrF;*Qt$p+vU>vI*&TFUEDIN>1pY!MOKBnVj59+IC1qi95^}wcr6y>7l&`7XB2~ zVxz4s;z_!h7m|EkZ1>`-AkX~s95pg#I_@{ZW?qsxAU3q-E>NW}yg62Dyh#x6Kn{1f zec6KYWe|VLn)qbT8o^o-e1PJQB%h#uZz{@U*p z{qpfIGTq$7@3FKr&5;~`r&$#g7nAYYMWMg5hun9@Jzc~nTu3l*BLwZM+uR4LuR-K~ z3%Btf5tymOc+%QSX<$)*EWi7|yVr^a=&?UGD*i$@9=DsEDGa_S|O3tc;rpWG5k72r7 zw&0p_SYQ>IIzbO3@FlO-v0|bm{K5f(qmV{y6)Kj-E80cc(huBN=?-2gyT`vkpoq?j z0(pFVZs+w-r>3p%6jo~zNCzPL(YJ?5g}v@=1S&4wL% z{3$=2{|d&H@UO#L6K}ePz2$JU&+ko9>>>vEp^y3+lz1ZEVr6g$KlLP2GHq7-f>aW8 zBbKTCwtLQoSg)!PSe8+Ar>>mm4sY1JKrV)CN`f@$e{L?!vc5ys6?m%$Of69c+)~6? z*p4hRa||$rV}~6&eP%y8T)_vrqmPx_V{t`shWOUR1oY8WA68g=<9u@X9^15z6J<$v zw4hUgySn~4u4Ub`KP_Epd)xk z$r5rZBCCHe?%nZo7E!gQg|)siHB2LMEzagw958d`6{?4p?c^P`csi;UHr^R)%T1^6 zl$sZUC}=6`?m*OQrWZqC5N;VwcZ|sY>awgb1iPVhhk7Pf-W{%Pl`IeM+Q`K+c)aHh z?7bIaR}$qZrJ1ys8%&bqM-Gc~ZDRO4=>TMKEv9h)I2HHbVO4oDaTqgNQ%JJD{naP% zg@J7%)4F`x>I~P2bh=0@>>lx}bQ!8KeEW(ZH^ZplS9J{Yd?B@|m^Po7T79y)o%o*v z^t}7QuY9dVQ@PI>bv|hqtix>C5DJd+xzBgtt@kdh{;g^+Ug&v?<%jZz#Ko@1{%QRL zvNcy$=BxGfF{HnW*Yl%dd)lRbzXdYjqjw*~?ZoKxyiITaakaVqp=@kz z=${BwV7!&{c}#$-D?@&e+POncD;Ui|h{Jc!stfOn$?vS`!Sa8oYKE-6vG40s8u=zX zIyvqIoA!v@Zy1)0D7Yx2=)Cc8ZZX|@vY4M8o$1A?xA2dngYpHIR3yc z<`$&4=85^^N>cds7NqZI`Pe>JMr%OM@#&86pp!&A4Q2M_0I z_Y*p8y;(`z&4rRO7kjqR?qdv61kJ19Q5m>j9br82%p3Ih{i+;E? zVjpNlD!HZ{bt~e_dbCD9f5#0q@rB(fS*-z=j}yAykmd^s$+DG3@Nzw_A6bas4l<>f z$gn|C^bi^oytWBQ%b3WYdD0D9b9_^{FJPJ5}K+bSW4wx>T*(apF~C7sn`?0tEJPb&fq>J|6+K(SbRt z?m6=5QPI+0UA-r5jFH50j+O=CL7$h7aT`%cj`%D}Z3uK0g?-lsv56G4gM60OrHTWy5>P%7L45-u~tSoFh z3#&2T-;)}1YTjB?OFva!B!24+$`_q=%hwiC0+n;-1Rv82j=hCTDV?DBrEdlcWn{J(%df!mCb+ zxM@$dQ$3H;6y5WoZd<3Gp;9-`RRgm~fnRxNQB4$jjCP|&O4)D47G?S54n)IVUFc=x z0EZ-?b!hu}>)8%>b@g}kZ*eO&Jwxc>$tQS^^ zoQ#t=U(z^RkB~ukKo&!HID_f7lRF;{=Tk2S*XTs0>m}omDep!)T7slY1E)`b)ahkd zg%Ky)A&;{sA@Mb%=axRP>Gvh~mrs=M+cfR+!aC72@n~W;hmK+2fZuPhQ}(plGi?o> za>%T|Av8XdnD@RzHr9$W*=HECSDF18uz`e#ho5bbIe&1NYXy0 znG*&|7O+}RTV-XC8%uaLOv?W^2)MKHv`Q?KFDN4zULd9Cmuez#pR@IR$AQ+cO#e6_HAX-& z@7_+Jpgm8)8O?GsMH$BVl@KK%Zz?=lo=6GYv4&qKP*c$fGCkpAT!*)yR=e^n28_vdSLw=qY z>oHxM5igHxD7P_oC{AfNb+E3ABaYM_Y-tm9Q& zL(>!=%S~ywE!AsDU+d^oN{Ev@dj&8uNh-s;#9}O}us;?zjAyCV4`ZvKvkq%L&U<26 zDH^IH>yS5HN~=_=rkBYAMzb9R)LPn^VUDg+;vUASQo2{GB2DI;-N=#J)nReKSfyr_ z_ct`XjPaCH5;aDE58?$@^=`EhCXr1NGz+uc;>VIMidooppqB_4uk|GeevwgDaWsU3 z!Mj&fxMqU~{WNn>S46o^t@cXoDIIaB8=#oEHF~Z}6g=#RSu?T@yiD^+!(f{&jkz6q z6}w}fhm~dUmUn#~xyN`obq#l|0vOw`T<&~cfiKCAJk+L{XCwdcFl`TD%Bop4eDJbl zlmWNER7AS%^2p4N5Q{O7@LPq35{$>W=Zt7P1qek%Er{Y}|sEdAj0{Kk0A z^7ux$|H`{7?V`O-Zx|ytyFb+BIx^IYGd_&}^7e>(zGl8zbN>#d9Y`H}hutun*mEH} zS~urzvCbMO9r*l@ymYIa_IxTb5KuS^5D?ve@5M-|tNf=2Go`fC(4 zS88i_>Q*l;y1qMJ8eO(*cBpA4e@%5KjdhVG`FHvmU$r0K=3L*t;~#Hy-R$<60WFN_ z!WR?i2FZ$pUj%}*)^Z_YGkgec%MIX^+d6b~2cG(f)ont)2>GVf&*4G7ZSof6Pt&LC zYc1in5|4Fp!DR%_wM78C zTuf}}x!JexOwNfsB{FOr`iEk1McM;OvdHZSM`_EmD&-{DC3}<0bK?eqRhgt`7!y8Y zCUl1dW-j-;j~|>ta|B74#scOQs#rWb z48)peh*F?BdjYZ{!h)ZZ zXBw#vTox&J@+yi@O#(AIH~a7;P1)Xcj1Rj&)%X(4HIuV#c+;?J2&)x{$|Vy7lAp#E zON@X4U>U(GLCmOxxpI`W(f~;~7_b7mYnI4;T5e#pD^^U)K8p3VOcMvrP@fY zswT|>lod-PN3^O?P*Ff^zQF$qB2+#<)vyKXG>;$0$zWVa37ihLNZ(|;n^1EM#arCj zJ|lV12HuZoVu$p@|LI#bvxda_>ia#}47DVeE>X693df-fs9jw?j(y7I`H?ka;osO) z)d@5R&TiXu)fA#aSHL2$UfKww$^@uwMAAZKx#&dP5@~E4)-G^aK8mcf=s_T`V4fjs zay1Gi3`m-FkmQ-Wcw^c`?bR%w?AUd#fJwTTaIzc{Vx#J1^A&Ta`hcO8d3%(&xz^Xt z)!A*&FZ8x0^j!+YgQUgj+q(XMSYe59v9Z7a$eRj6`8>^`@4)HqgK06z-a?P+9$pdK zTy&txBfQa!WKie$alAi2uksCkyX4`rFU3qxy*wSg-E5rIn~>ews-(qSo-F_D&&E$E z4HHCX60b1PsVQ$zShRG>FtD;!P`iUVgQBrDEwL$Wv?vVF^_`)r96gdLYgO7s*V@1| zBK03~fjflkuoQ9(GX&T$_8-TY?$l=r52yx$IdqALR>Bdv*>`U*AG^hxtjg$+aPU%* zVT5_mz|Vz5dc_bn8$NK^lkpWKUbx8UO9EyaYOG;*!137k$dB6nf7*zv*(wfhCGlE! z)RakUcg>Cz<`^snMZWztbI4-wO?ACGW92Ek4=NbWZbeSX^2GDF(cbuJ-H*S~PY9e! zrIC8D`I0o=ij!FzXCkLhU%0*X;)N55$|wS1Rm-ARIp=iCV0~I+z3k~}WsH6cu#ny6 z(`PT`=O6-%?Z3n4I?{4{So2S;@zs?(a5XtWN!r(nnAJ{<{UR%ag&eK*<@G$HZe5KFB1%Ut+G+DgLnn-oS*Skg zkV&XmsGc~6YsiEdBs-X0lo{4ZIL|=)M{qOR;ra?nvr_GtD46<5GtP!=w0y zZ;{rW=IyIn*jkxwsqZduSnm+S|0;_nPor~z64_yW@q8V%aP^8=Qt*r0y;f;;0ifTv zXXnzDZC7s%(AxRAvt%rBBvN0-ShC7)oR9jRHY>E zW=rbrxh%0bAws_k5Kov?3Uvh+_9fAbl`cI(LD@5w&bPLPxl&*?dlezf!J*SCy5ddCTQQ8c z-8<4xA~%DAPE@&eo~*Z~GUtFAQCBUjgX;{r!yE*0WT^^CP4028gm%RNLzdZZVJ zx+Jr3G**cfveXRaMouA6<b<)^a~qM@j+v4YGMp|iaZbhxtCIA| zmZjhaz;<)9mtm*T_)Gv{>BCX3{4^Iv2ceOKb)gvC9mNt!`BQI(-govQOa8o=oE%u+ zgd((YD~Fa;6|2hQ?3{k7YTH;B9q<4$ggEi0MuKR~sPb7cUWCS{A1{*gi&o4&td>3A zD9r}GT_ij6;bbpyD}DV}s~ZLfyaKb%iD7()}M%E8l%S1iAH z^>`f%o8ya2Y*I)yQPy^5?e4{D0$CgDItP+J<}IjnNt6D{3)7$kF=+nD-O4_ zYA24Fu0Ruxzx${nGA0OZk*`isR^=ZFXq50_EZw2W7;8OUie%tjRN@Yw{Id?;C(|61 zJ&fP{(`sJv$}ASYOv@AoobwOjA*uZo^%ys@yq#%9O^kP_w;X!^USdo}z=60>OxZ+5aKy#ZcC437N7j_OE+X zt$LuGlO<4eQ*}VY`@|v1lMY$|K5Ois^*B6BogKGVPI@T*Q#%6zIAGSyaisksPx9(0 zqiZ?XWhym!6pQ>HcE+??W;WJrJ~nksbc%zFWKz%rJlvsp!t^%DXmokA*tl$?ioHVt zYAD=F^%sFMEj7XL>LHqZK}**iC5<1?pFVG81ogXTsRI2yu|kq~lF@KTwxq&{w>7H(vC))@s(fT) z$n#f?9)g^(`u6rcpcd%dUa~j?wIM@myK1UhE(z;>ARY^j=zvt--*`C(exR?}sFu}S z{+l51vRHj7o3H8!`)k4}5cb_Wd-6u0W|?`KJlrD$Cm(3q?&~QU(d$trl{ffV#DXI*VL`38+aq8 zGpDMuWDo!C_GB3OFt56mJ5KT}t5V%dRR3#Gun~Jh_A2Q9J<#-Tpqy&_cN0M`#-yxe zGx4S5#t6{s5D||zJ+7!$wGBdpZ4=5mJ^E^{S^VzKvP9LWE6{g5xac|G%FhA!6JMsX zIWE7sbCfT71Yxn^dXad&hml30@xFY$v7(^tXWxFn=;&US2TN=q2Nxo@B(+DHg-05P zSDb}sdJCiNEKVbm-w^Ce#^(!6*BxU0MjgETd3Oj2)E>3-h6s-j&P{^r3InHy2+LjN zVo(x0lnh6HLWPH#(_PN-w(ww|jIx)S39pthrfZ7yj=cH-o$-+ye5=)@CZdKq-)S2< zwDW#+nJ4?_oBm9vn>h6TbInD=*u4;okwA+f!K+<6(1%970&Tz$-LJbs2B%{PF)GNr#`m+&D==0G)x3@A9N^e}3{0+RIjkQ_Mx3om+HG_-Q7{yI73?Wci+TR|aB zsJdu(%yE8${t#e?Vqg9qT`Qcv`_1>;?w}W7U8z3FfP3+?lI8}> zr?Y4j$oV94`y@h$bN&!)kda`$2)0Rz6ht~nrAZ7{SE^`o>!ea{MZWWlSdqguL`HwgB?JwyiFV#za zr@*SbX*iy#5clt`Lu5Fn4W%hr=oG>rioqsL zGr3R1L7-tiU961K(^4 z*J~etHaHI+fd@q0Ym%&4$B@v>Dv8qy%iVvbcU#2T13qw$NZR+Dj*ODn4LN>*!_=HT zxK?mdLL#wz>O;#ZUBTZphAf?-(fSj|E>AQ{ONXA3+r9k?5SZM?tR11EzH1hmZ!1Vr=SD_s9q-umC$yWLt)?kbC`zJJF^ z*|F%11Aq+m3qwf}1xln4C58M2*d#$@>-LO~$g!9*u*`t}G)IYATWMeZ(YF5SxYDXt zD@PQl`eS3$?AltrR8qa!q?>&g`&x8_LIx zK&r7{IbOM{dF1qL0oZJJ&a3ee*rnDv=Bd+{B==@I47#)7< z$@hzET(^q16K%>H;*xSee-(@8{n`=eWL}_e`#J3%?G`x`8^*N)64v6M_~`b8zH)1g(6)%{5Yn#z930Jokg@Gz0%sm0^&$d(F~1#%;v3oxam2=ksEIs=>n z_2kttPgGflMIDfVF+7#6e;6k)vui+<7v;5&v^3ixy9JUE0Uc?)0R4NbRU>Ov{v$(H3Jd0f^2Bx6U_hUCq|#()CLZMwZj7NEwBBV3EK!s)Qw=oOaVemWdlX= zvgAyM1BJZ{L-wa06uOTy_~$yu#+lzwCw!%$k0WANKWn;t^=&&HJn!$A_87$*+*15~awn7J@*^A|p zGWwz)NdU~&g>|c7H-~F0#0}=C7?I@Ki>F(p5f7cz?Bt|ouIGh+Xg%VDa0ch z;p8R1A&E0tK)NoL3zf~%`zod>(|t)Xm}VpY<18i+EaQ@6)@Nylgo`gSZWbzxM_sH) zG3Xb;2nCC`(g^T|OfI?mVV}ZEz94!BaUUVV>Qws5!L27Rk<+0m*!?`0ts?00FrF-~ z{)8>bi3@n<+QL%n*$WNdNJ{pYzxq=A?5U4lowYZXdd1<@ojuNv*aE*HS+>XYdQihy z4;_qo-M@+5Q@oGw(g9{!Fs4#Xc6)hk6Z=4VU$tivN#cwsQ zB-dKCI)WJoAqyKrmP!dK@7SG6F8uz1WT8wsr(tlvy5aS61SZbnsY^NTv2dKU7Eusj zKZOV%cc0&Q&uu~!C}`aHir|l|eJzX`85zSI4e39^7&v~B*W;^?SQ7Ka843+9wfd=2 z#}h4H+&+{bl;+BbN`;E>Z&b#OTf$nU%3C$sl3ORU{ZQhA9qzN6ZCNNowFds9`kvZ- zh%=3Lx!Pr(lwbV)%GkbDEUy%>Hk9RBLB1wtrj!H56i!HquBn5nu<-KxSaAIFw=}sZ zG*~9K7UxT;9d9iyIX0K9mog?8E=@JH7Ix-aZH=W<7j|~5O50tH)rLlmXB$e}V;i-9 znl_Ut)hg$I-v9AS$e)09o199zOP!(_iJOS}y8)U_7H{!^+7t0TrE)0noFFMS_^xoY z@sTgJk?R{|x3UO`oTEcQ$MH3TM`fMABLplTiX(Y9$HmFbL@g@kPNA7G-G=k)ZWU;L zi?{4u{WrVJont_3#6D#m>;~meSnpg#mob!DJlF%Sn1Rd zdu>_k8uBD};xxZ}!1A>jrOqNw;rz-ZzoLml_%CWl6KK{dejW)$sQ|hVr9qZKSVHv7 zr#}A#fX%>OnuSu;n6s{F`h*e%g9+?AH^_ld4zsa6Kjo}RYKLp42uukP)fep1I4)S< ze(ByAkM0pQl1Y*>+?9^zC{O=!{E!l0OtT}K_89isXib{-w1$gbYsMM0`;jkgyPD~d zQ!*4IN7XV0`3aeFxmYMoO4Rm90_A`3^-j^5MB&zM(lI+7+cv(~w(XAd#kM-O&5n(Z zZQHhO^JI_xk25y@b1rJsRjrFvwbrb9YrfCa>?vJC_9b1HYUY_D9Z522RT;T8KiS-O z6>pD=ZGHIHh+`N0R%XH5F}m!LKhRk*f1pD&_+HcH5-XVBSDYwVNLDw>T&-1`uV$cA zIHtY-Bacyr=i<|#N{(SoF+&iyr8a$?8FiGkRi{7!ebA}V$JTFW5@Qn06j~E-rc-J_ zg3Rnt7>!_L83Me~w)GA_`9&Y8`5$jvRxm3Q( za0bSyDHEZh$XrRPs})6u$Be1zt`_LN;IMArLUCwh8{7rM>mrN_AMO5v6kMqq)6N&1 zX2(SEpC#i4w5X&sjhO6<_g?t5=pr~Bx>L4K?#1&4K8J1w? z($Hxgf1b>ney&<5PoXO^`zNVNzjSOZsp_Z}35I1&aSLkSLU)tssvs{I`RYUd-L*&d zeAU-qA1?8yN0Z?#tAFE{PgHG`fwQHzw)D_qt4C5(++4-sy!fAMHnb_={3v|pjzsLm zYT~%d#7X+pszp!HztG5PHHCt6=lhELi75P>8CXK~_k;jF)WmNIvg9Ae>V4quJ zm4W6zpGwOMZi1^%&FOz#m6~{vFY{!JczZ3DjL_AWG9{BQ3*a=2uUP6JGx-PH#7H{O z2xhUxJ^r1;a7fQ>p4mE+eC8m_L76X}R(?h?XUxHo+u17)FPvgh3fC+paGUJ5KWMI? zsS`EQ-(}8%!$y64{RbL3^f4*(?h)(dSpPVFimwV)a&NfM|JV~`kek7cZ}uuu*8mX$EI#w;fBNr%VZMT6G6!g!e2*@9gdNFk=x>B5|<3q2Cj7& z=%`e0)G>>d^|;$IYnO4y>YgZBzr;Cx;gTHfHK8-AR7rGkXyd<>swRQREx6?U0gB(ZJm!uYt=e5CB2kqJv9q5IsOHx?;O;?IXyFylSlQfG z#BvbyU!_&H(`aF1IcLQxOIFMQT7G;oN^*h8iZxGNAs!qJOC!2#`c#h)IKAY*hf#@S zrr~ed?3P*pL3xx3Xnaa4Z#_0%YZK)+__1a6UdiB?y&oxO;_?p5(0< zTMv#cQwm}hITyrhYZP_;)Q6M0%7k8QTX|@v(1yia-gTYdbLi#ASRiiY@1W>V0FghO z;x{c%8(;Wa+v$tJ??m{m8Zht7*^f2d2N^r{mU-#S`t1FiGM=)D|6E|lx&a68Qi zqz72n3{4~Rh^h`F+?73CW3iTO9=}9=@$4nnm%6iHQ;usHJI>k zUCFz3A+j_Vf!8hjFnF?&M6mc#{YUjSuboJS3+Cc4=aY9?&K{vjR6T`JLwkgXVmn>6 z-fUK{MPdTyf4p~b6y*m2hwo`K%3~qRlYan~7V0g6HuaOv6s`?-oYgv6<$1TDv_FFF z9v``xT3*1Zxl`~5YD0de)x$EP9n@ra%2R~=fqc{*+>~ZQuhLN!HbWJ5S`PuAe;DIIW#8mN_fumtkmjm6}Z zwckQd(o}Q!!yyh7C>dv0X87vvH)LdfBg)|N#`Y5&y8DxCMdW+>ipOAEo{C(T*@|d*=ov{&bfkhhqk$+YY-?DQJ|P3 zl%Rx2k&0x9Q~Msh&Wj?2$O(C+xL_&Kf(P!`IF4!!IBJ7Y(VlGR_#@$hYf@tFqIb%~ z_^MyX!^c!z>utY~s`&ja=lvkwgwf}a|FWs#4~0NLDY(P4lU07qH%yLJ>^(=OyQ~?k z%ZNx&6@ustbX^gCZAn7-VvL%-|NNm*O?$$z8vA|w;{u!DafC-X2N zzIaACXSZ}_zMxR&29CJZ6O^6W&E1j)gfbq;HBQFI@4X@6D1e*?MfvCkT^*gc*JvUg zcFrbKhX?{li=o%7hlS8Ncg4IG)w{a)Htv4}$Vz==q@=eO=Wvv!ew9SM9VRo7t)Xz> ztF}&|^j|n3x&Fm;HGsJsWM0Q{L@FSt^FMel_KNd^s}Z=2Z%c|o*+>~)s=(Brz;8XE zy<vRMzQ8&(|{V!fTK&wJ7zFSWpQc z5-;SHCE<^vy4ehn-m{{lpk^%F-($444RvuvJxTy#C^|3gfpc9Gb{zKIzsHc)n5fH) zxM)bRA*_vSn2-d~eVaEky3rkJi)@q$ZU4rciZz-y(`{4^r<9T?PU?(qByNrq(R7aV zRb=Fh6FWD0D&wv;#Y%5Z61tl0-GGO%4fSS9^SCk=6>!%FothaZLya^Vo;Z)P@C46- zL)}=92~PL$m8Negzcz4$ABX7o$e)aJfpGgG@18FANLU0L3~QMzEYeI=_9Aw>B{6(C%lcT7-~qE{&ZHa zV6!<(KW`XO0FLMu&OL_RrjID z0FH-<>@$)n2fyt@^TsMY9vMn~prVNv(k0yR9-4#!BRI*2f`=&gN{Hly7jaTewTQkx zKKY-&1JYVH0W-LBig+m~qnesZU(~p#>hLq|!4zZUeBWtuq#29j^wacHEzD+D*qyAb zrB(jx-Jrn`bq5Xld>Ov{5$B@;nwB&KS|&}V>ybKdtKN_73+DeeI;gc-@yB(ulsTJ7 zi#ghsePrmVM#9A776Ktx!mLgPQSA-NFg2(KAc)A6Wd816R$~AqtrRsN$b&La*NP-Zrnh>m{GQ%JGYF0=EAb^_FtaE*lJv@c3+K0bkZN%ds)%>7( z*=gl{Rv+X3PZc~E2*tJ0r`7PkyQm8^2&DAy?8dYWUM&wY>0&l2wTi~*b+S6{1HrkZ zYa?!>rLLPrRIvZ)yqpZxl*IdBdB%+WjWX~|&nrGm+J_%erJa@v=9Uv6`E#3pOYgWn zkwJE;oX=#PGXgUsxl?Mu_?-aJJwxDTXJ|5^Vm2# zfM`^k4)vXk)I{@sGdfuR)ZlF5*P4ubRlS9KF%j}dzHpxzT{G?Rfvsa*ku7$ErNf5R ztop(k=-X5b(@09~Vm`RGe2H3c&3I3)#P=B4ikrD$_LK|Ekz&syy)a z-G7i#R|PJ=C*Ig{CS@E(+c=T$0?uK}w?tP%SDTc#{{{NrtJ3i=9W%yYARu25ARyn? zOAw$fgQK&ZiG!1^rHPFr!+*tR{FkaU={NmC(#F*G|FExAWR-Emk-VeowSaX$f}o+* zXh~L>YKWnIe^a3M{IgdggmdWL*-n6BidziTt!;A%3K)aZ_mV4ALi_T|Y%ZVM&BmhH+`>ss1idA-Zo@_|z5lSL5@+zHu@D{%>2D*MimG2wY> z`uZ2^4aE%w(SGYJjj1$ni6D6o+T8JfD#^eJsMFoi!s3tu7{ z+&j^uv6vd^3}D8H6w@RfdE+E-2oJM?Q4zN|(zG63O8!__IDulDEK`>DX}f}iI+bXd zT1iy`GUv_Rg0Wj%X;SXBhrzM&^$(yhzn!lmyvV9M3H2`D*xW~yOl+#dm|BhZ5*4fw zTU9@ED`7N=HQ%@(cqxBDkN6bD1)q&XU2ZnQUxr;otMJ}tAgIcb^L zS@(W2MdZqC&s*v(p`E#eG25s-3}6OQD`!%bC~i3G2D)eF0>4A@+2{r}(N{!$h^22r zj3|Z%=huiz1(cpa1WNy+s4t{MkTo9&(Wk)Pe#<^zh2=DQk)tD#Tiu*nYwJE>>9d43`>ExSZgMW$1LSIm)tmvK9GR z7rD<}jGtxyi$!raH6d$)6wMyRJg%|LYdWD87RgK9kQpuHVXC@Rsg1N9B%zu~Y zD-BYRV->+QFv{7F)S%7wMbqtVBqq<36~n!Nm-PjyD^e#{Un|=IM&w|UBOle?1)p*#AN>foOvo?2 zgU#$dx=OT1(1l09+L+$}21oZFjKAzlVJ=~TuPOF5_ke_jJ4z2)-z#m%Hh2MH1&acG z;6s~9_UIw8wCI0O7FfHN?Tn|$j4j@l3CmolYe@J&=VzC@+?q-Z^1|Oa-$-vW?jw(2 zs|Q=yNvWkYEe?x23bHpqRJWI{jGdMV#ub)lE>PppXoxOoC|YMlfPcI8g!adDmqzpg zmCLHkP~;>?)k{LjCV2OBHcO`kRO}aa4=o#zkwzD|a*{hVd4qDqdeicLo&RCSaFHr% zq>@W;mi))lR?;#3G4heb(mNpGE9kh+Ic6`qkBR(MF2Rz)E<5q)%B$brNJDEpWIk6v zYl1TBawGl2{N#`#gP8b%1heP{(D9%+Y2} zM&kG<&fJ2a*9_3#>T1Nh9yJt;>M-~eRd}i+* zLhk()4}}yR$K}`TnoRXFivvK&x?UJH9%jEPX4-qw@t;9DVTepO{ItSD2~3Y7&89NbR<8KF ze83d?NbraPSw-MQei=zzUjJ<8+Lr5DJ-L9EX70ydi>AwA42&q8;Mj#vKU2r$L*;TB z|4#B5-(4?l+W^xKC+d^YA5O_`qu_Dxu2HkfmWa!~xVJ26cjDt-#Ejuoh% zwQhauEqBysEC~N13?q>Zf3g<;r_20}BM)vq;EG?1o{HrxE$p1=sB+^+jwZ7xY=WUR z&=g+mHrdC#ZPb1cFN1Pb=kRF|rcY7XZyrmMOA)JGFy}wBic>e2lclg%mspS0E}ISi zqpC{2zFI=lZu7Uqu71V5%O^i~Joba1Ot|2(GhcF?)mpq>yf6D&?`G4F*mSPp`FDb6 zV{gK55t}uD|2k?R>#V?=@Dx1SIPs+oS3=t52k+S_-`!@ z5BhKOR3)%RqbTGW6aK+z=4_z_WB$D{-$`g6*n(kl*ZYg!C(tEyyxHo|H=y?!$)>qNEY5A@e%2T zQp6<-P0Y2e2pvXGo7@X#49P5$8Kg$q_+(?DaHUMO=(GsY;1CnzezDxck%-J0Rt1(v zlGev7Cf_uS09|gmFRiQtfGCVqQio5A`*;gTx!yFDZL^C(%jD-@8wt~GYEtQUN*XkD zJ=GB?uUoHB;JuKiqVLgk;MYjE`*%J#crN&@Jl?JNuTeIJY~=)WGO;nRF>?Q37&yCHQxOF+5Rf#B|7+{ff4f%pzYO%B zcjLkf#_QXf@pPA&wojgb15E@5PbgLZ4MS902185)jv}#)oD7Z@vL75NW7<0o9w{9G zo2VsNPyaU${Tp?NmJ&<`TTj1H(NNj&;9XhSxq;EPyae&9^NaIoGC9pE(1*Wk>&ht2 z^Sk?q=V&Fx@pAm993H2X$iS{RBC_$^aFsG_A+zkmGh9F=ofzT1zU8xjkaXnVE>;EG z@f&bq(5chKSboN$)jjP!Rv{n#XCmcO_?W40=UUlX*{p=wp z{(3t!|8RtaS+R=}B7#|>F6!O#cMuyOBgLvcWp{MycrmPtB3Z>sdNJTZ;TS(#?US;| z)mnHvcPFztC<@)Yj#_koE78JWmdYF`oOp=-~LJi$?&W`K4U4`6nAR za_}K(%;m{yiz*dmRN4x)f9sQ}1RYzGWr=iY8{Uwq<~AppFRC?)v7Aav8w_a7Mq%Ix z7fEN2nI&2Zog0>vul^#B&YQbvpcoDU0TeTB{U?rr5kYku2oaG2brhKWx`BUyfRvM2 zEniC;M@z=UHEe)`u&h{Bi$fKEERGX2Zh<#xX?B+X{WMe zL&*RQ)N_E=sFS@s&%fh>5aZIVBbLNZ1_Zam=Zu~B*^=l|0B7q% z+)j)1w}`Nnz(_(XSQgHp$s~%YGgZofEd2(w_B28FK7@sm8p*^PAhxEjV^=PgX8--y zl2s6d6_w2zRQTzqSe7mWCh*MMv^2-KWaNgk{SR`WCC!2ayoVJTwONwlr!E-WIXEEn z_*BnGrFAV51VH6s^dsj25nGL6&SV77H^#yQ)Gy*rsZNY9>pi}6Qm-HaSmb6@QgNNO z&uxwe0f0S4jo%Jlddq-Ydj1$jfn5SO2)nyBXtkikL*<-?^f8|HSp7TGz|}D0Y^Pv9 z-pf(q+Z`43&jOc*rosow9&Dpz=E(W=hqVZ*6mD+;YfAuJq@^%5dualT&Eg6AYM}YH z^2W{Na5x3FwE;G6kotiQmNNOwY~sao406g z=>#XN5eKS;`Vg{HSQ0&Z5S&Ia=j^A(kC0CRf8{oby;dUa;8bUFt(dcWa9$X&a7a!4 z{G5}=x>#It=u+E1G-@V^Z=O=c#V1(97|mjtqfs!Sj|jz|ZN0W9^KwveTOgI9puW(! zY_vx3lL*flw<|Do7wX^B9oaIs&^r_nRoU=ncl9aOf1l`E-3^T}w zhQ<)3W!1;`BpwSY3&_<=iKHC$1rc`13Ig|{YkK)P=J(23K4R5yLvKV`{KPuey zks}c_lv~3aHd2dqJ`l2e1iM5`K$hX&|Cla&GM1@+3ebY34?YCU+kA+a=%6Zxex%5K z$=WhSIY8wOD++3qB!R=D?WU=@?g;J*K7!rdj1|oJY8UIz*&0g#Z^5zATflM~Yjj>Q zmAEFvCEQa!#q+mL@3~*S^>{t($`6fv! z`$|dj4Dht7FsyUBUt!&IQ#FO5&na?Ob6o}(lm_USIK7zp;98iB#f+*stow9$6$w>7 z4BmuGpRvFb%@BQc3KVSy?}|B?S8%H8aj;b_!qDFAv+>jPAZaOj2JP?GgA(||TdJ(t zQK;8~6McbHC04{+I%q^C%2BiwwTejfe8j+q}B zIAWANw-(d2anm+#uy`mpuJCG6GTBM zpDHjk_?tI_OHF2jD6ovnQ$^2qK58a~essnY%pY=#c}a1eOYOyaDPYB{GdpFhQMM0Z zXXFivtQ?wtjSjhu61P0@(DaIJ2ye|1v&0#5rx?xl3idFF+;`^6PWT5p2LhbzAq$v7 ziN;9BBb<0x-{`i0UlyhfjyEzZl*N$PdT~!eXanuGYpu68V%Pkk$A*kelh;1&W?}e( zi&9bfN{=hbosvsucpJ%c|;rmFY9!TO1<

|o`+v`Jz)L!VQu_$pY0quYAo$A6YHPF5o_?`*aRh; z{*hFbKif8|7IzD*+DIO3O&Z2od!S-qbSy0ly;3ZfbRbZTz;yw|nEJu2 zbaKp%)2lx0&%S=%?f7{N(%=$MCnEl7Jdu24(<2hLl2P>-HmiESJoQ!vt@pJDSzlNs zAs)oQLc+zty%9}puRIM>bWga2ds)SrP*V#+xcEV*+A&~VqJc=H$ev`5HEa|jNwP@u zxGEJzwZho*YlC`KgzA$#i`QX~eCk}jw*=gIm|V~Z@BF|t%A4yAOU=XG_g=x~N=$`L zJiLZ=z8;*uiR>G>C$8%%Zi+?@;)na25v?NxN$04cXz+)&4%W_}C}FOqTaO~EkwU32 zYL>g&W2ie`!5C012tX0}_9MiGlp^YZ9ogBlR@T}x zS5d`p=r~?e=($n+m6yeIq%;B|E z^lwO2^6TR3JB}B!p-zzpx1rNjo-B-^Y{D-M)N4Q+7%VH$or#C?Chv!pIZYdSnjWKR z7B*(8=j1NX$spPJ5fy!DTkhz6g!Rx&`>E;g4%wuWC7}_5|Q^b3u9) zTi!EY*3}VIYA|@DsS15j|GuDbbN%Km!Lm?C16u&_YMXkXBe>e*^HCZNuH(VpCn$Bze^t~szK&_p+gnP7?6(!#0p%sl`IITne{bbUtdxl7 zk&K&48Chd+`XuWb0Z3&*i}!}rAE?@wu9utD5nhqQMQQ@HlHSeQdwkE{(Z9l}=1?Mu zvck>^4&JqnwywElsHlGmcIJcz;6CzmeW@XinTMJFto?f8`ug)VybnEqYc|2h!oWn1 zy`nn!VV3pP>ubWLZzD4B7e8~O2szSiEx6)aklXiQH9)+z>=rRo^^1mzcgrL~0d2j;hv)Ny3;VYtP1< zk;Ex#>;ClSU5-bcuT6Z|%&^W`$=AVY2US?Xd8zy&nxo+8zHnZ?vx0}FO%Hx9NE+U3 zc5$rC+aTYIu9luHP&3GfDWPM2>-VtNAWGvu>=)#g`-M-BCf79X?YJkkV%^IWvKvI* zA_xZv$EoSGdMQD4^K8hVaxC5ghxlu2a;4AL2GvV+I8brYx1Xam#9MWdR;c}5KAKh? zSeQOTY;zuFY+n4N*`1Baj8TpWf*EoBfvuiv7UaqN{CwgvKZK$U!?e9E34e>k{6b1?Hfi9bhp51TmMG>G+hlb+aC&&=Nx0#}0^WBQcYod~=PD+6F$h4)zpE7?WFWS zjUw((INpz!LA-?Wu|9H<1HGJ4P&&w)fCOBxo{W%QA6?rA_r->?0NDq+q`=Gws%KyC zJ4Iss6_j_rOhLa=5+PlqZWa5*P*B?dgOaMlk8dRAd)ss$Om;eOEDu`^p zrlomKa5!7EIZ?A_N$7IhsB@mj=Q6veLPTX7Fz8rH_0|A2d=z=)^UfKt^&^dJv4pom zH7)g09{FGoL|+6%$*9Y&jE1~68S1Hr)(my_<{?sk7EG2xdHtzC9Ts>aRipj0);_vp zn9twi#zhHVF}PcgUGq{}@+<8V2@6N0Dtwg|477y{?})=D9~CtQptztdKwDL^2DVnfQ4si+NcoW@=KkId(RBHLsPZ93`Zf%+ z+~H-=T)F#sclBh?=LhKR36MJ}{o6IOZ&B$~ht+{b}0p>eYElBJd1_lb8LX ztGW8|Z$imCi{|il6^oc)kYvKg8F(iV>P(M(vso9ft9;D~snURk1POBG6%9mRC zm6R!h7fWO>Thjv<@$Vlaolw&ZW+po!LD{izyrmIr_PWV#W$ai_Sni?~_mMg$nz|Jj zINPPvj68p(Y)DGN0uQqOHq3|1HFMKEj z5;-zviF-=MzVDGHcM%HHifvaRxkg$_Z3^xdPa_$U@+qqxiSFWhogRWPNo7V@w!S<3 z5;I^X=;y$x0wZ^dRU35 zC!2scD>_78ulyZ%p6B*dQB<)Tq)~X^lzc{EIiGM-3lB0WuGAPTYXeA zK7wsvU;zN1b7zh;QGBB!nNSjLs(_F*=b$A;bKRcY{*4BhOBoifo@Kq4wkT>E5$`HQfAw4Z7xg1ka)}z|AYBQmWtWG_pk6Fsq(bW`pR6B~ zjMG|dXIk<^xENN#zg8k&X;nCgC;2MRnkNYN7H%%t+!2HsEx(FWq`d4FG0r}9k(mx; zf-a;qsdV_VmZ0biYnsORj2yBtBSMM1SqX6t1Ixctb=o^o9m$u!J)m(xOLwSrA9_R1 zDYnzWYU{*|bnQIcI^Pd#2En)(2MFQ~%GDZ&J}Wyf(d37ffjXj`+ZUuT{crb2y`NdC zTg@pbos)71TO`FDeIMz1~OL4oVph z=%hsF3aBRz=l)ZQjNS@$Hx-ndi}+`qlX_^EnU=b8me!!zZU8f7J{3Ai0 zxcFtBI+r=#Yp14hN|q~L-iIhvRbtA>j#N)7u_r3=zTGxsZakHgb0L0K)k;w@=UnD0 ze)^7-bCE2)REmJE#`82i7yr7?q=|Zp3m3lH97aL9jntlZvA$cn-YG@RmG~dfE&}uw z>p5ksIpwTU`nMTgr$1wtGFkAoq`+_zFcW#725*m1KB=w5aQ7~|qIK~(9hacckJFTN;xlS)m7_zxpPQRQS&^LbKc!N+kW+cz_)pS(PYU$Ye+j|A! z_iR^1ua;|GxWWch%Ryg+#;$W0qD{sYUDlDFsLMM!WD9une0Io8&ZxZuI^#+Wnc zOpLaJalZEQh)$Wg^J1-va*F)8$3vWsSPwsQzDM4Y**c=T2ip{^rbOF9Oll7!5B=c- z)%a#nz#0)95!StcSg|IW+Z?N;F*GM>$Xs~)1G`8Idoh!;D75rgx)7%yP=DTCCo6NL zZ!qYbNKTK8Ax(0`O`N@lXb-df&jQULQ=!tZUS>~_w#IbwuZjs}5`ghbd34S^OUyMV zFD|_~@g+Wn@&NV3^sOGiB0Jvi2)VDEm>Z@!p2ICjl@qVrC_g=olK9AUK>ZEQ9VQzk zO7hR~6u5cIn+LFJ{n%@eJW5L87hDAnt_;5cqvaEm?eoQEGN@w z7$xQIi!k`pR&bOQF%q<3NRS?}auwHA3^`cM`f({PBR@AHo~OJwR2r;Er)Dx@hV{-g~S65KRZHST8c8Ksa{=)4?p~)gR}bWMvoamC^xi zq2x>P#^(@kMTdIXMQ;bQtsv-J>d{$Rhkx-eA86lEcA*w&Hv{$v!I36oUhhjG*hVj z_YDHHyrl-oPb#n@dq%Svb~hy6k(FzMzGDcN znI|_6yDX_{gYyjYt^tx`YkY|I;#tq*H>3-cOlb;tfO+buOZH$I4|Zba)Cv#eI@Z|H zEkq{NDD1Ttb2dgkYXY1*`Zt0{W|#WDs=H3rZL!~Uumxq_!@MAshF+ZdnoaA#yztK` zZawvHzLi9x!>||gta7a+HxGVZe=M|Xuexb=40+ZnrAnzZh3nPO6m1+f>H0EShNi`P zhq49`I&R(J0mIz;Bqtbo^oV}LXRmpO)qRqGQ{+2D&1iD2LRpWcSSnai4z2=wk?RURB12bp-NF$LM$829z`8pCY8?+06l0E7K?;i5PeByqt`f^R!oI!W21kiP9 zjy)iG3h3bkr1L>7n-bGyG2Ot={*rMd+;l`*7jwiC8!!6DlR|!TpwfT9#OD_LzCd~q zU`F|mCH@Nj+dcQ2suoatK>I^lw#*LdauP)K#4xC&!Q6cVmTl~A)!imBc}!3`s37PF zufbP#k-5;07S0$u@J~c9Aa5mYJRO{QXs!@IBXlTo@QC3z9z^KK6tznRz~}4(=*gwr zN=51jl6gU>-X_&2R)-V0wv?dIptX$AIneA4%TjpCP1ZtpAQt>E>Zwx_y+PZRqB-OD zGfxga62H|8_EbrVA$Et5sQN~2Rh(|CHajw;5=#5!t5qGztOy^KsZ>kQ*a4}Rw7pW* z;k2rh+P6?kz&S?gg=?iv8{Mhvl4SaPjTj1-ABS86Qf3e%@~K2!A_^_RWt+s^PV28i zK3Vfz+%O9^LrEp|WW`&-xkT3m(yU?=mZqU?^6plIS)Q>uz7nYdCy&BOLn)DXJ2b3y>0mPZ4)rgtxiQPVDIEB@u zLy$-&(2@b_5gyX%{2^!Y4KpDlW%deL71&d@M-4;n8~*<9{xIU*F#FXw;CM%8YcGQz zO1o3H6I+LLq1k2bnN7HLuZ?1Fjlpj=QI8wgmpC{VI5?MWbgQn~&9MH1i_l^{K zRSxwMX9Sx77+_|o z&Ry~MkPU2$oM|6Q20jRGT9IQeLlW)YQW8zY)JArCR-6KNw>l%pfezNhr`8cm+al>- ziTaB+N+QLWVLD70CKQF>v$`?HtA4 z7jY`P#+i!ecx_HH9LjTyZ`CQr7f&XB#8na=c#9y0vrvp?5+(lJWkBsKg^p~7C#eEy z%jlW4M&=*t=R0rg% ziqHhfKPN43MTVYEgqCc6pJbI)$`>8I_K0&MC2TJ;2=^k|O+Gqs0&YlCs{PYSgj-Ba=b!%6jAlly#>uYf=ZUyD2;%uR^gE3RGMX4hID> z>!@`ZwpN${Q7v|O>|xOU@`{ctj8A}Ri)mKr+62o+)HtO}FYyq+4?r!nL;!JQ7+7$A# zU{~PXvCa4uII=1N%0K?byZ^%v_4e2*99s1#QS1ny7dF6#Oa4MgLUbK>M3zk( z)E+6Q!teM;CLyyWj@KOoG3gT}G3bofqQwh}#5N!*ZijIUHe}dZ_A$G_F|IQEZ+*;u zv`|q?ek@$D{)oCJ{7)_TF1o9>#Pkk_Ppms{&Fs}n-ZrnCu*{5D!!=O^r3;fpvgAI+ z3cvi4eCh5s2YZMK40T0>%d@}?_18xeMhYV$WcBS& z(#JpMOVHWBxPz|0;-_;edO{4|)Z!9<1|td@r+h4>}1tInH~ZF71k#ssos2z1IB9Sg%)CywY6i zcJZo})zK6${#h$KXv37%=-`p;VLo01>`1_SoChC>JnU1}D>&pbcd*yHrpa)dVsJYRK_|{wY~}Ca zk$w0TeAD6L8+ggH@z{Gt;b)aZqN(1S-98tMf+8<5th+Xsof^zL?w9HICQL`m3&FU! zOeR;Y$X_0`Mw?Uo^vrj^XAS@2rD^%}7%*wXvOL6MdI+rE4r-~F;l`Za$*#RM`rc`r zd1N?mSm@d|RB6*reSn_2?zT}ST?r0!CQKR`r+E>Sg}*6k@JTehBelF3+}_}8O~p*p zy;FLpJ>`{nA!#17AG?2&cl5rUJiT+U@b~0DOVV$H@x4t~C*4j=C2#dgt|-n#Zn`3PDaU<`DaUx|H@ynEZn#)KcepxNX0 zoG!f^-GFwPj_u|w-@X(#I((*Cb9ULl-Aq~QAFx|NP1}fnIld=futs_Y_*#1=_}adK zef2!zfBj1Q8hHf$5^YtR6zWut*jFve=9mwlAPlXBygj4L`532w#x12=9Z&;zMJ$EVP5wczX@6t>zZ|Ovoer zj*Ye`_DMo4m3<*;j{Zi zri`qdMU9TeUd*y?nfRx;HAO$hj9D@6n*M-Vnv?i9V!BLXzEX9jRt-&Gs49%mxGiHa zv*@K@tUqO`G}fb>`WZ1W26svOnCQR}zT5VTV03-F;~~i%|B(_HL9E zt}hbc$;+3%l2hb`y?W%{&8CEb1gs(>Q(x;dlk+^KGpH0=dC|+Qotl=T1ADuE3r~R8&LB-uI~k$+2;t!c z_K3bb5>{Ov@vHEBiQbqn+XxJ$xfj%?2n_o#Q4Q1#&l-~Zw3Dq2b-M*hY^ zt14~?Ji-%ytVLP*8 z%jn2D2yN1Hm6xBo*N{kd?UGmO^7+u~@bFdrP_5Sf&ZmtS0-c{$_Aw?@_Lmn!uZ2ko zs;4%hEIVbgPQhj>TU8Opne|+FAR;LW@d!3d*F%ODeeJW5iRdRlu2l~v&Rw<;G6k+Q z>U?vb{q@E0jeFx!;{t?tG$@P25+$zeRO`-6^RW*V!MFY*ha(lS7s^;2USjjZ@ym;W z!on!OEofMCtkV|_MMCQ0=xhmVsA{>Wp3a-QmGGkiBl}f4O`;^6)|vr2DT-U@THt>Zy!U)|Pk z?aGgyC(W{}!nGPts=ZpOc4|vkHVTDlMUj^Tyi{^pvPTQkyw}!k>xyruuXn*%BviFz!Gq(wS21%Apw7; zie+(i9t4ZKt`L=hFsc@WCd3TzlNZZCmCuafZPg_^^Z0U^kCG;{l69;HBPPaA^>Ed? zXc}QBQY?YQ#(5AOX3&A~sb@pg1!vPz>HRh!8%7{=GGwYg{mAwbm76fI^y4&CEf**l z!4Zx~UahL@>mLU@hz;;3BZmN?6%`jr;{2hECjC-7_iV$`6l^pDyO;Ja4B;xJ04-%7 zdSi6&lZ06r^-RJ%ITb^p_H{JX_pP#mzVqWo1v~`q8(C~MucE^K{e(sIwW=T?ff?KC zrvxeKR#D|vQEyf5Zbj3-4mijVJdBUhTPTKqD&@4d!s_(Vb3+G^2um;#8GeAq{-$t}nala_Wi61zlEBJZMP<(2`-J zv+yL!()zTi3;tD;w2@@3yVj?S(DrGERTVJyZaz{rP`?(>#}H!a4{R~K>;qNG+8+vl z7oQuLXEB)U`W=6ynjzRGUgJtX$&xDOEi-bRJ<0bEW~&%&2d<(pBd-=7`&@@sMOxPz z3gFoL7M)`&*e%c_9+$%eF9sN|2gSe}UV;pPEks!@x;h5yOg9B5v_(>;DiN>u%^5pF z4fE_(kY?g*W9`S-gE{-OLHc=sO1&UDNTFqv>LCxP4y+5W2R;#TZzWz!NFYgP)g zC?h>yy-}8v{uph!+u3=ZWDV9$H{{w7O7c=15O7PUr4$Aqc=$V2hm@M(9_f%ZmMyuM zsFxm&5bgi|MmLb$KNB572QU4&1YmS(WA)G;Mhc*VG_sfu%#FW8{)&)?Uts@5mXj`a z1#yT+U0mlJ0UblH5?D-z?vJ#M5fO!)S@PhI7KBK&eak5LiNKq5 zsTdkvzP_zX8@WCd_EA^7M8bfvC0fKS$2QUWT zV1;yCNCVDA99z?()h40wE{ni#KEhHY(q0E7-I8?ZRRJQ?gjXyKzCJk!wCH$ZL?v*n z;cQxMs3IBcg~7cewEf%r31NAcAM z^bk_ej37VSLAY<7>wSM6n`A!VZ;ZH2Q*tQIJY4x!hf!r+i1U z{nEW^&z!fOW$yQRrNj7+R`MRKzp$EsZ4%w59{zADj>9|vAT$1sjdHuCH2|Ej-J^_v z0}HX#S{FB1^`9R*+^&;NsBX1U&Cjyh9?>JtN`VlTXbZ`8tTw=YGGMxrrf``#qKz*h zS0cqTx#L$AQJYkYV@d#HUH@e9AXbPGZex6-h?PIY=`RTf95Pq13roM5=opIfPMd0L#$|-h@Eowmv#~HyBQ|u2Be&`BKcSat#SPn)RxLx3GmoE-hHtcGZ zd(!7ElC90S9RxrFX%sJxEKBld7Gx_Y?T+@PEP)KCn57HgTrNvyQ4G(OTEMI0yE}@qb=-<=vyHhc)xZalx|EVqL7+sp~GN zsxujgX?_oBe(xQ|fw8HY;##gTsvMce*RYIUy*py0DWO^=Xl1N_Q9W7PKxdpD#pP%e zEVdMAV|t5QtY|KCO~z{B9ch|uZ*gs0TDn=)-Wd|uH6e=;fHjKmrg1ZgT(S`8MrIBs zw49jLk1cPBRKqZAUakmgq3{&n+{o@;Dw#<-+kACt3?9Zmv=7yFiiY;(AChKh~Wo90paiBf0&fMJYs)a_! ze-NygU0gvJ4x&Zm0!%I1^p`rrb_wfmWnR8-Js60H$R|dr!}j=C-IQfrql+%%`mdzHWyr3-8S$6>19Podb{sF9po&(@5);%L!|wGJ z%DFz(rfy343eAzkw5Itq;t~x(#}X7d-mS>u%p^7R91e(2?mI9yn@CP#`eH$NqAWbc zU`S?_>-IgxPSHH45d?TE!q5V-W&*Ecu&555hYrDp>{97f5$vdW~#;eJq~j8fd8Hwjdw zm?uU(4|X6-M5z@H1`i7p5Kw!foTvRNJHN z4f>M>5NFWXGEOI#$`vGr#yAYNAgljR&bfDOH>6nrgpV1CgOW6ZW%7}rh}0Wy1j&T zc$p|FsFQC;Ohm_rm1lw>OV~%80JjRmd1_o<^5#2QVAx4MO7ssMNN-L0E|c(4#7jMr z8=M90XuwZPU5%Ev5b}^+Pjo6P+EC3M1Vs)X@ged^q;WmV^CD>k)&X#g0>p(ImggmV zFKQ3DVWiFUc*IRTD@z|(0k+iFB4adW{HW&0PEt|w0a4bt11fsFf#2)(CJh+ERw2yn z9`sDS-JpyPC8q_TG^Ti&sxy_PECU|1*C8t4wD@@I%oOP5f!T&9hs4{&==I*0#2UJy z<%Y!W2IRjHwcdg_!m>o?cmDY7QtE-F_nYM-rxyb;sT=5{K-Oh!RJ@L1PWEexg=>r! z!8Fcgwe+sjvN&>KEmG7aX|E&i(3{jN3P!7!V-}2_8N!!~)>bxf-|dp8_@PB_*40zZ zaYQWXbQ7s_MYPl6xh=*T7M!j!M@;GbxW0kV{K&wG)ipif-y>mhO=8lB;t8JC)?`07 zO?2y;=+)HKIDpU^KL9;-E`Uw;f%&$F=h_`00A3nffHiZOUoy|FTIVA;<@NpL4UP*+ zY%304#ca&Ua2Esww(=P8;ntSk(EQ>qELe4bo~)fQSXpS@d_g3NqtJJUU5PdN_7_Tt z$}`&i%}%VnusP;&nZN6Ux@TD_mRr_jO=HQCP-caCs#EFafv5o0w5|!;08U_>qwZg& z&ihwn6DVpo25Hr+RHIkt>~)S=dSpw&DzxpyLG&EL>FRBhZ9v#M@(k?(+bu^-t1KF= znWs#se+!vDFhASt4c)ThA+Borqm>hdZoiy=%IsN@r+xL_C#V6Sd?%~rZ zE%apJoMmUuiq~b9b@|VNfb64n3rlhw0rOOo%GLhQzFmb$MukhG6h4Jc`Dlj%MeMF_ zAjan7cz}rdsg$bsAv^hjhw5a5Y6PboLahzeci|EVO09vIK{tlZ8#z}TKC0=OoNH%A z`wM^LYd2x)vnTvOcEW>+kdm;#Jt*6jkI3hDO|B)veTC0Z_rXL1W2%|lOis=d@-oWb zVl%pm6jU^+nS%u)_j`2SO#jbO-Z0SQ@NPQOKMO2}8r@w?nXoy&5~n(2<~|zcHX1cq zPhh&HII{KtJ~+v*U``8cb*MX{RNaAAZFFI3^kFyY9qmz7I^ibX+?^3sgPvTiqSV?N zig-FxRnRGJG&({n^*Wq3dbOf9ZlYKAqB8-f&RF zmEOP5J}@+B|90ZILAMVNJMMgwrOb?AiS0+D9Ym)h($k6Ye)}XZ{k7V@wSwM@ML))4 zn&dM`^Bbq{8K(cvGWY^@JxFausd4)KNlA0~7kl)DBW~xKpZC(=I~Vxv3ifco4A3KM z1o_to^qoiVbQF&(Y>p|zaX9J4dsPI2Hy=V4sz+B}d*@jC2aVcq*#@`d0fL20;_$%|qI-ijRea@N1gRgD0h$o*- zOeiR(VT5y}0bXrt;$_G&E?G{JO2_3Krhh4r)g}?%L^h{1TtWEYHq-mA?_EU*J=BBV zxMWSpG$MBjf@+Rl1BKE&q}P2G?XIimMM==0rgYRF$%zhp4k56*2HMwM8C(wQmzVD( zbVW{ETeE0xIaeV3s4(rq`TSFNn*Zqw)ztz^TBE0yI3kCpoEZ#ML)H?tvXrv81f5{@RUJ}3Z$YN8@<34mvAI93j~o`d zf?_oaI};`*_A8B?5%vJ?i_Mr^kj*rnfRcH{q|Ae8GrMvVMI4sFb;cx=nW?32 z+2Nj_ZDG?#yL$H=60+Z59xN5Yrdbm0|56KM;AcY|dD0phk(>6X1rk(a*y*#iL(sHh zY+;idkj8Jd!o2Ll(uQ9TBCSTw?9)!qyEPnorLEiywcG+Y{SA~BhGV$no|Jf2&~@P3 z-lzG5h)q|Gt`~AOqPWVZ4>e57c8b$#>zt;Hh#P*M?7u)ZIhoY=(Ji`=FymAIojSUk z66@l*-RO&IRU2Zc9c|q3p%Igs<=Aj4EHuPTCZ88c_dtia*Ds7Wt1}+m7jN8AWfHIeyd3FQf{qW|#5LPL1{2w5qF-Iv z*MDMHt2$m+EPaxAMPKepWlSl$wbIfHnIj`&9V$FAT1wRe#8|EG7*#NEim-r2&=_FuXP;kg$v z?tVr`|Fm>U{kLZkGO)F^bC$6)wlMV&G;lHzva>a{FcbNozw#dkSXJ^2vIG3^SyI?s zY5edWfQX>-_Hf|&y^{0jnE8)rCy1z7U8B52_tccckhr}C@K(TxI4~Xams94hav3kN zD{KHl*n{SII0UD8f$|3ScDT{gFg1^-6b)E%F(~~|hWDwESBVQ6LDl_XS+o_=G@V|x z+UG9GUiT?5W?D9WtS&z^=y8S8w5^f0KpK(K{A|Z2wlzR`r>N^HTXxctp5jJ3qIXFj zGb{d+;TP%OQ=n*&@HKHG6#TlZB4RqCtjG5?;HPUANF0-4mbe< z)z_P2cjAGO-%fX~ALwkaYH~<0BWk>nI@Z0vxh0C@i)uUXROP|AM#x_{O|p-qUkGX= z0vi}*F<5XXLL!HI9^^?57$PCb6#6gJ$aRUD42La63D+1xSA_FKa3&!yioaZup$;DT z$+4it;uB@btNV%J`Pu?-igfhSfQdpki9=P>l-)|`WNv(yd-QfS!8F& z$6KA}=$AGYBG!lvRMX>ZN0TesoW+TWZH_u;gF02yjfL7?_(56GQx7dPe(F-1Pm_ii zYQ;*EKO2`{zx90INQ_U_eh6BbW$)cl?Y*~~QF&<78RD>UZ%WsU(1g4zhbWgceoIds z4qx8QS|rkYXYedTvlIA?sn8)dZ_j(uM_@f{yz3Z}g%5$=x-sRkZ6P7F>(QEs&K)W& z7Bg8Q*Y?$BYaE%8{v~3Z_!hT)qivdqRqFAU2zm3~Poo=7;d#Ok^3bC&$UtaORfo%j z*79r8fO|n_{emuQjE1i1%A>mz%CrdCQ9!?$Fv zEfQ>6fkd9-;3BOeh(eXr6lQghg-P%mNM!l6E1Js)77Wi#VdNd|9~TRf(g(S#|HCjB zHnBEwHj#C)wpRYPR%GXYVqBHHfb0N0Ja&r(P~L1rH$XVBeJ>pOz9M95DO&mVRxMCq zM>XQFz6N#zhf`p8WJ4VO{o!C8i)E$^9jS+}yJkT8VS2G&ThJ1CJcW=2UJvGtOx_hG zawJuOPnB^w8qIs!d0x()qqRl!O}Z;n8RffpVOCw1+O__x_HUJEW=e9<4RC0OZk*j5=)!Z+2ntN>7upL#zntqNcX0fA=QR@K6q6}YkZWFoh=TY z2KYWo7_hMM6GsgxEFfg;wbE=kX1P3qJ>$iGwdt=%5M6>l_dqkcZ9H`?v%G@*BdQ!4 zzjwZV7S7Kf;RT)l%Mcf_F|e@y-$(X@EfNDVkF5|XF$D#M1w|~Qjnbfy;a~`;fH;Yy zNYQPaq9%dr^q}RY^kw`N#Z>MsR%KlKF7PY)V79S*fFe9Ky46Q3@5}4og7`N8m~hZ2H^J1(S& zwrIS838Z;;Frlj=RmcZZS)tdod`H;htWf%%_<1jB9lb1EP<iGuC)TeXcxF zHN0gX^pT^Y_3}-oZ9B^epZb<{M&*wclnSxUlj{fWITXj`T3nD7#(&2P-;0zd%#bm@ z8CEVR6sZ09zEx$Y{{c(F z<>n?s>fW6{c{JIFd>Xn_jUHA}HgD$iH^fjIQ6^EY6V#1tWqmF~03wewiT>JXWjfZs ztqbN{{^##Yzo04!0e^A}>Q8Pl`oAQeyt$q2PjuTDnmGRN>GxkJ7Mgsd`BGMfvfjdI z2Y+}+C99{5_4uo(CmDB`>NwXv;9L1HI*Vdd7yKaM%GZ;LH`$ojPoHmxXBd7cEEmk< z@-#7j1C!BT`Bf^TTU=-F%ndPAW*FCer`QijxWd{vpns>uT^2pru_z0`XN1ZkCdiqn zWdJrx^1`wgBl21)lAUKTFwx?1JxdwA<2Y+2sG}Bu^Q`Hm3CXhMwSx=zvz$P+Bs;q4 zy+mQ#FuI!h3)^4bHN#iNYN;kwjX`dro8^jJTqT9_s=Na7mg7%Gf9OZ9cys!n^!o+% zlT(6Xv%kyBhic*F99_N2GB5(s$N&Bt{o>0Sr~E$F5|h#l?t+)6IR_5Z`k!28mLpnq zhbe8{Thq5Aw4kTL!OCRKPui01MAr8Hoqitg%uc&$|3N=fu^;-in7%xA3#Ozg*lZnB2T?vSlE7Oc09mX`vu+Js(YLAV~HK^z9zO$BIbi>HCe+KhMgm3C_J-i{&iwTwub z$@kq`THN+)`=p0y#!;x>hFqq;{cquWOIQgB!+?!`9>x9w)-WwGO5g4ymj6fGiyi$C1e$B0Wq2c=T z;0dM|q=Ulqlq@U+9bXlFC;eAJ_8ykcm-;NH`V8bRUGw9)+GbZCKx@r)en_`>&@6aK6mPmwWGvMZklF7% z;@>w1wb-ETDGqw@qf{+r|7sHF)Dd~(Ts=qjk4Q5O%xmky_ zsQV-loL5y4gh7xQVx8?WMqz9*DTfEyTXz8>KqxPWhw${V-mZT%MNnqJ7nhK*6+R=w za$2gwu8q-IRf39IO&TRO$b>~m(J(owQBC-1mr6`$38%n6d6yofq|}ORlQvz z;VfV*D>?s~FSNT0HDs6ZFBuwz0Q0?P!xZ8)f8;htHnE!_j$-NWfcZxEQE4l~kYEZC9t2gF*TkrgDo1FKyWH6*0*gYYd`N~K zR3q_`;-;cbWu4=%9omvDY}mM2f{u~X1&Xq2#PRT}*VorWjLYzLT*Yz=eK(1=(56{X zly`MFS_W2naNSG;(~Ey{Z~DRY{}C8M`&W=%O&tFX$A38$;0NjhFZq$y)PVUf6~+GN zFxCGU{-4`@RU0QnHDq7fKI%#=meoIX8F7nf_z)LTm2~FoY49Qt;ed68O|JgI*7cY3 zCgH_3dVlly3Yb32abRSb3Vbi5{ggMH&;-!~j2dB?HalK6ou-{~oUYqmZv4KyA@Cz| zMOtTxdT9*sJTXXFl&Y60%S|$^$`z7R7t1vo?$0Y#n`mg%8e}evC7C4KEi;Fkh2-dy?)rgi02KOt#i508NLN%U%F{!#ETW*V>+|R6<4#xE=t(q;# zMSX>2ALe4vln0h0GAzT9c4fg4Hpwgy26 zql@m~;tqVV`l9y>oQD_O~Ov*qT_?3y^~4B=B*N3$XYZh(mRycG zO4W{Qe&O6Fk6(f+Q$aw$40ykma0kXXk%&|PyIzH*$b~y31JMFc*2-3_R2O=t?#P*L zn)dEzak~rak_tj_^k@ANq&fB?L~$8d#4-Z`;XxcyJ+3ya1DcEzJCmhDvEe94gYcWA z9g(@foYCB_v>8sDXCg1^H}Zh`HuZ!^k)$hbs*8upSd9vW6zGtuV}?Q;nq!6Kc8(h5 zp=K{nh?AY_pP*zEtz+oXwX?Uutb`KFb@+JE*f0|BKBp9t+&*D9B9HM_?@UvfDe_-$ zB!P`S54RKIgN&x2-d!EPe)^`hrYL z$22)gG%dE>Tbm_Nauukf{n*gb$Mqm}Z1w6do7EaAg?4R5qO+=QX6s8=se&RfjeMgW zFJ2M&U141U%vxV$i&{oxW!#@zvfaQOPJS~9@6?pArx%85td*&TgtH}Stn8DD)PcsP z!7It0Sar4)7KehDgz_GQQ7(~Kyq+N;c(;Vg0{uXp$Re%KH{qN-5!J&V5Xs&Y8^*~LrT?P8h52RR ziOvnbmuNQ#07|;|HyU9Nct#l8?1eBV;`ZPjO_V8ykxSNN_L}f13%4Ai7JDjA#X&#S zNN28R%VyAJNw0qcyD(dQ?l2L1Y(eWTKgkO7iM)-NU7-Y8=n|Bx9zK=sqUIot(ku3` zJXsWtd%eN^_ZeyRrr$ZS|6kr+czcY%RzbR{3Yy%k(GW7;3ih%GL!8(Q-_m3bcd!9E z`%k*ZyQtUVi!_fRw4A)}@Wdw^v=xX|-@eueJy(9>fDccquj5yP(2EXP7u<8 z8T=1_jvbp9DELUJHJZW(7vu9*KECc?lj(D{?q8Yiub6NNNSUK@+!jMCyRN7{VSDVy z@O?6dFRB&LqrOyciBs&cNC&YLd9>?W7fBXy-9&Af#=wp5T}Ge3KEw;et!V2Xkvjib zN4d~-_wM3nBU$sa*pvT%=ZF7X=tV88P5xuCk5;ykLlS_`DJr5ll9U21>c$O|k9bCo z#-v9VWcF7UrjmJtTHCgn=-<-q+93R(l8z$IIAxRgP#9jja9C~*XFZ#~V&a;ab@(VI%+;)vAG?^O_&=Piz@Vyh+H zGMl_}IXibLHgeJJAW2tQZm|*dDfXIl}r?dp@QK^Q|{L#rq{T%>mWKw0s~!oRbO>ix53m=V{V09nO!n=#1W~? zwqjS%Mz7q5fpHdL#Y})WuSK5RMgDwYO=XYnzi5uzG+?=YXrEdRvL}H-Ce~M$USYso z6g!K>-uSW6@Q*zxoFgzodMT~9e02}z_uguXW7gUWuxZNXgcQE8JbjC>QtS}rseUFv zPxZoHm{k4pv5Dhy5x22bP;oA%tV=>`L_tK6kGuLa5dC0wYUSM3?EqpKX9M6e_#6cZjpbVTmG&jI(JN8>NLK}J9Mht;)8^o5*z_UY`l3}qb&9zv!VjGR5^C~0el2}!vND;~W`R{^eunuo>X}PM zif>-o3INMX+V7NuhwPK-UGMOb+2{HaDAOdtTo#Vr=_dQd=jMU;<+HW(?@r4N0DFWM zf^wdwfVc>ZB`7SWYd_7Q%SgkC^MnlaMYWE7y=TpyXAFbJt+m-on88$9yO|ah9;jt3 zM#>%!p*+q7dL(`nCny36)*r~ra{6?ZXz6VxYuH5KvUz!ZCs);(>7wFTE+*S?ne~_g zGamot)nI?AbrTu`%VlZ9ApFYo0vsa)Yvk3qrYZ}g;vM>@9B|CJ!ao{Aq}HhBm5T^r ztvSc;*Bk?xDCXgy>O6GOjpeHWtp1p6c9l>O3SrKAxWjWQ2i{)S$% zk&^Hh5k}wynYE6P>1cVV#~IDAYgXHtutaSEg%i>U zWw2p2eIA)t?|RNj@RVyPR5eO4-}tpmzJa?MYN0c{XSLwODi6<<>DNT~oFbZQ0_W6o z`-r-M^m|M>xXAJiHY7FP{fU^-@A>Vh!35=Ls)=h_Zls^xR$zdAxZTiv@f75suD{SY zRVukbVI@EFlyiEQ6@WWD0OXQ$?TN(UC7DXLRK@r^mf`i$3^L;(%S?03{06GDKt|giCV6_YZh^&|F16+$Rv$N`N#YPK`7M>hS6G<$6y-HF}Isn1l3`VoS71vnjKnorHUs0;-^Y(B%wc5AJVEM zF{oBgS;&ecMI0!o)`~|dN-ihgvP)z}=SYauI^Lqez%A&&6zRDs&Xs1_M)l~GJ7l#+ zKh4`|213QvZJx{%QL!1#tSA>@vS^i+dM;!x#Z|aRIR@S-;R*>;T+b#(lCddh5r!pKVmh6ewO+ohU84#n!J3; z7!!_0TISco{4bIL4Z4+dz5{qe}XwK+I zm`CCs<_*cU?9(O#1ZWhqJR3rsZ?0pa?lEi7sCxlnvaoz>7h})cI;~x(*NXC}Cx#3r z+5@z;Yw+$GFE{_ByzEiB2h)eYU-FpD>X5cNlbyKLr1Y{Mn_TLv z44BpDKiI^8hD;VG7gAbzUcPJN+;`|mQJ0>fk;uj?tR-%JN%DFSVcsFBU(*5GJ)w6L zGO(^AHD58@HGTZKo?2J36{7iq@58$b3E&$Ga!;Hxw$U4Cm)?;ojpUfGG0|X5)2vA` z6O*Vle@rr!NpJ74>TN!Oe2cv{!js3&p1A*!_w{J!&c^kHV?URiS3Yvd8O=@&)$}ZlL5IdQ+P? ze#+w0W8E2eTNyw8#qO79{RMwQmof#Hr?LHq-8O}d?t4lq@f(+N$^&Cx>uaVzMr0}# z;Lc9DAg1Cwf=+0VEA@}F>J1*U#T*Xe7Ryqol~8ChYSF{Am3v!*$l4B$EgahlUEUt0 zE1DQh-sjndIo?i7RFzcgH)i9;D#v!G6$V_VP&iNbWy*e7eaox^RQUBQP>lW6@9(BE z_WhOX4A9Hn%eWq0G*0b~T|Xal2(fBGh%MonhX{3dzMr|mjJve&CLFsKSf>LEGNx3oloqr7E7V7Q zqey^Vcme4bYQl;dMA;Ks$;9vFq)UhoOBIi(`TGr3%3SO2`k)yd5%rjwiII(0Ao($W z2`3Im{nYerO~$Tq|6Z$C7l7V{WPmRI-!;EHQ>&G`5lcU?hL=5$nCz6v`LOo=;2npIwANOd~6O9rp$fu{bLa`u)3Ig00{t)g8yGu{r`oI_kR>3 zUFsg%*hd(?yz1zy+1T)55aFbv%)J4bX+kgzlJpGd_!RgIW$UmL08||f>|9tva2IcHnze|f*2&8%|mOkG#0YW+X%e@(LOy!hVSb>95- z-noSO_Bu!ZZTc}1ONdN$TShKW*4uy4^r=Z+juDur%jaJAWdbc;aDk3owE>Ae?eYKe0;O-RxKegLiQIm)?!u4 z;I*+HGvakTq3Zj&iObU(taJsp7BEkCD3eN7FbWW0hNJ`*j8%aSs3Y%i&6y!82IBKm+3GX`Q5+Vej>OQu{uO^_a65fbyfaw8CP#4PcIedD$ z0&(@cAcft|_UB7VQUDGmNKDY>889@^6DaE(DSKaLgXlpE8mUHFws}F*bFZQl7s_QL z;11;;f-;dcj-|K!oswNa(?etJWw@Xx1S-Umtb;SO^{D06GP@;2&u`iSm$0;pYi+Ad^f2ga#tks+Iak#rqFK7y zQd>PA%z?2cQ;m%c6>Wc<$>UnybsDR-d|I6tCFBl7E|kWKQ+_j1s8y`-(~hz=?{;=0 zyG$-Q(2hGWP#wzlIU@OVD#Aa|7U8m=zfBUqc-sophH$lI?pZF+%GoW-)>7$&u_F`L zRCff^It>b9TY^4I=89v!B!sYut~Ep)OS4@+?NJ(%X-yyEglB89*dx$oIyYS<{karr z@)z3}Kb`alCFBcollHSqNdMx|c3f+0QuoKro60>lqQm5-k{00ON_pTs556Gc$vk`y z09JzJ1+tRH{yQg(WwA(0Vu9RDY`bLNw=?!hR@R{71dN2hgMTRS9-U(OT|(|?D_%gT z04Sw88ROwtkdoX5C(R_;)&f=C5$O7w1hi@RuZxFB6uPGw<8nzRkEWh-YTeL)W}_Mt z(fwBTqlOT|vyly8_3Wh$*-9w}+pv<`4Plj4y4xH!v0Oh5V2_(NQn-qaMD^g~wm6|+ zkgHWO>OxuZ5dD72G33%}9X7bdwsC2qb#|0!nJfgEj8jQvadBcfSba;fQ|vY)nG zChnAN7R%;V3M#ItM~8@$NzQhz5N$zm^;4`Z7r%g^R{SU`Vj6doi<_q0FaPAd(&}`Y zIdexONomx+9)E-%wdx zFd<66{JLXv(`%sK9CX;AL^{=TKWNFMeM_bhs;#48?JEj2D{J&BvbzVY-b8%=PO|kZ z%r+$8NIhH45r>c$C1j-@{(RDVj9l50qVozK$}y89XoQ2bbpJ$MZd$Sr@x@ST>LR1QNh;`@Yb+5mS59X?1XA6xQQ zf8U5aYi-TM2IgWe;43Gy+&H+~I~;xF>o&Gd5}Ub1#YtSashZKTS<0WY-3H1)+fS`L z?eEs{3g4+YBHOf6_C}+l8?*xjgS0H>NClwz*46w9jJVkq0c?ua`zWqb`Wn}YZwM9p zmc0?&b>a;!D6rfnKtH-@xf_Aib1%6=7sXGX_N~k=pSNCS|3i$Vm zey2iy>JDf+c{lLmo1put-)w@;7CqEFEvSBlE#{u=4=YG6|B-^%P3T)sg5&N>1>A}J zjiqJzfY>3g8QYKZJm9mqCc@(ZHFuEV#xN{zjeX~yLusOV8`~;-j0zHa z3%H(8ZNnKy5;G2jY`#SeydicTgSNdor0pga_K1j_liE?D`Ezuh$&54R;W|h&g!CU7 z{Y1MmJD?5_f7^tD3J?)BTS^U0#*i+f){uBTrVzFgEYHWMRHb(tGjhb^9RQtiqv?&| z(FmKuBX<_ureue$nQ=ppw@k0pJnz_M6?vT@ zN00$hYCR#KkghczC?YTmW>*i3gpq}r8i`a?jP!L#$kIYY9gfv975ykV1yt5AK{j#- zIV?yJE9*p2(!~btPwEsR2XH3G~zae0wVvZgFGi_xgo^ zGbJ#%?pWHlhoe8b2|#zCG2X0ECh#=|pazYfaL8WnW9ZKdVMqgk%=np(R}kfo9!X(D z8di1r++QQF%<6&;!}h>}K@#sfyW}q)dKYg!%TiyA-g8pjS5NmzD;OHA8dhc0f32GU zLm@MU=@YW&fGw7RbDn;WfC9pG{f^YtO~Q4gIgX1>wz<6mO^}mS7rP@#70Ep^4L80Z zw%z24nS+J!s=cG=1T748L(X#mY5{j5=m_j)YuvPTxsE#YD@jAi+7M17XM8Tsy&X4Ug)*xrBgC{^7i7SSr4wS2~2Ntdc zmijc(^-0+Mxj>77oT<69hFK!a1Pm~Hrk|AArzhFiCGWPGZ1rd6SDgwsw2fX4F}*H> zhP#uEE;l@)x@u?XYUphJF&Am93fu1-I$QcU!UCdc^Khml4+&hCySTNlr7i|7xqNEH z5}&&SW3J84P}T}kxpB!uO7m@W*R?koBE(Ttl$wDBSO@<`I?C)wF8rzs6Ja%JA7|1r zQER9bqgYT$FGh2ic(DfPj>TKWPp%~m2s(n_I1Z&Py$_eFr@IcG;qo6tY@_41nowA!C1N?H|2!R6GWc;NL*}u&TCU&eQ^zOmL@^?tJ!lX~qu@Hnxw`!49H)0$VmX>ne zwCK6Do1%%%@sUWTN!zHYz0Q{YQ1@w;UkogjrH}(Gt_W9V#EB-v-#f3}=sH7W#93?T z{W)d{8iFf`3NwU5)IOQ>3$U^yo3&}1B|ApNZ=g1guTR%$5}!Pm96e7Ccbtq19I%XU zm?6drI-F#qX}~0;??c11XjYuCdZBD2L7b#-=mI9jVloU+91C#3xqEg3ymXB&r2V+7 zc;Q9OW~JO^eIw<&f77+J{uK@_IH!?GrN)92W}zimk+`JYbOX`LOxS|X+bS_R$)H7> z>GCy5={^{2E3l{-3C3h^@ifJ=wqAO@eb42i_-PQ86in9tIGD7lOhe#x5lRpS4t;nA z&2$%KrOEp(dI-eG#i&SF&nWB*`+U5%^#))_8M!U&48cJS0-?kcmnn!&o>fBOh<;96DQSwT$PH8s3c=*zP(;1H<|JBVBdIGvrsjQ}jJGu*epmlwxi>BUX!3!SZRXs1L6K{rz! z5a37><{HAps0RM|=J`1&QV=5!06^u>wF-=XWv$H5b)f&;&uKyED=#hao|q=2Lc;+; zXsHufp(dygMFSv;|K?{E7lfu|m(We{ACpc44eMCmYHn@RY+TkUccB0$;HOg2O0{Iu zvO=@Ey5I1rTUyrid+Tx9$sC(B2AXT`$!NRT`mz5p<2%{@`!W10G>16fv1vvoVjkKt zJxLZr+KA1o*<;)r*H2BuXT!9DXELd7_2MNYm9Yo~*>*HvdYp08X8s2W$1eYR`N9>* z5MV88ztYO(gC{1LbAg3LoJMXOnj9KcCp#MebRdD^tPJCiBU?LdALs-ub9Dm%uBqe`pNfG#G@a*P6GDedyV3|V#b^Xv5Udan}C$4GG&dD;9 z#FWzNJp=t+sgTRB#fhn4oiijBEWj2P*?og%j<<{XjogSg5msSsJqj}K&p-lA)0s1m ziAQI12ASJ=5TP}{>>qjJIXl$oIA#jplmV)LrAiai!8VT;8Y=X!#tBWF^Xp5={PBr| zPDxXyY|PYepgKJ(yLVH^vE_{CHJz0p&6b8j0=Kz@3a-K}c2E%P;!&+yIe7?a++G16 zSCZk3mi@&6b8(@j%))AGXs+? znDCc?ui4-ZZP(isZtl$a=*#=Vr#lqaK#E^x<}^X9d0VuFpaA81P&8~JH3ZiJzl5E9 zC&FnvcXx!V!xXCAEZi!!x=pIY)?qLW7~!sKKr|dSGXTaSVD2deT9B6Xy#z6R2em<; z>n-+==9vWG`BI%lWE0`8-Aj;G=bkOMK)~0Jm!mEvWvFW#g*ZWAKdDGi%O-LXJx164 z@k56N*A;!~Od-^%U@4EpF>G+Of!wMgCTKEMyy*+?ftGWPmvE;=qqN_Ta)WYZLA*Sxm#$<8Q(H6;ZDdqzX>e-u46+Oeyr^sa9lhpI15VHl?M4K|i zlSiLT6KxUtG6*YWsHn=VGFH^add;NURluNekSZuaugqlDNz6xHMEjfS>|+-98Y*?u zokJyZ6D10;)Y#ZqR933il{I!0$&~o)=5-jCz=MIiP`ZA1hhDRs@+MxF{7OS5t6RB< zboTgM`l>~_d~DP*;X%+_#cKYjE$pr*wHR2!2|M4GtqGpxu=EfyS}HCsZ;#+K_qM37 z-K4FsNTP_La0MwB9mo%n5 zbwzic0JYQ{K_YCVhw&S5@0!})gM{gKgoNBYg80(YN+qP}ncJjsM zif!Aro!soR?>%+T-gRnMb=CaSZ&z2>?CRNbyyF=~(dfk1okfv>3kIV_w)B%~NGCrp zo7l-Ki+-_p4z6xDB3OKi1kh`Y_;zt>JX4-|2{9pT8M&ck+s=Epcum;hfe1*`^l|(w zxNIvYgvD(nkgwTX?8&f{I}lcqjIfzB*-mWA0x5#1glAr96(!=yow~8MA1!tkDDY2m z57tAwIBy|q8gv3;&_zPg(7Y+wIM%+-&K2@fT#g4YA4a=hK9R44f=57P!#getRv7-!% z+oqoMhh`agg?h{fS1BwaIZYS3IT*?)#@*!;?Zhwf>t0bDLc8C6PDhZ@bgT9_pofJg z-fy~rJangI7B(5I_2w~f16x{PVZq>(C5$3hwaL~BZ}9b_G-28Ep(+Vbvo$PN#!ZJH zO7LxZ>{+Ag;2qMq#3_jrqNUo%t_4Iik(9A@yt88npu%M?dSHLJupUuk^);9UPH{u~ z90G?$EP=Xat02L-y_r{3TiLN6Syd*O-MM7>kdY!|LA`gMx5=Ijrk2L)D zy%oVr)3}uBjK}qGY8Y5H4>0TK!&m8^n^bg1cDza|h)P&&5r!)(S817@`CPaxH8QeB zQM;@85RGkRJDcvP5=|Ldsu}eF&2oYY*sSkenuIEjBhW0Mz&xv@(|={U#jQHrai>{Z zfoMj9rBM6ZjLN3By9p4T42r7MS3>|NGcqO7gwb1nu~-St|F*6ifN24H7eqM&gUuJj z8-E8u&L2Qo&sxy#qriQoiR}<24fIQ&))ECPU!FXI17bv!2%9U5&y4T>Q0fg~Zq|bO zge~Uv2YW^ImO?|6(l^CGng){oUc-fS(W8Y|tjDK}1^MSR=$lj%D*)2U9f*#pMYBO; zau;P*T!9D*}Ue_;4 zeazj1bHD+L zOt?>%3?EsfNXTL>>#DFFgeJJF1o0~K1CD3MVh9H$c1C^535wz?WHjyIfrI)qWoRqr zO2Iu^qPGJ6lm$Woa3cjs*`i_SBAIJudl&vOqV&a*t%ILuI;>m&86PC)Sf8Pa3Dj|F z+FvI20ew#&LB6TjP|mO#+`)CXj$Tfwt9_U>61Y9b6LkOrs^54UyIw|yU0Xbm3e5@4 zkA0=hN9_F3`VU^ zuI0@`MvZ@zie5j>@cgWyMXQ7>o`;(@vaj2uYQpg3@;w~5Z{LCJU1^Ao&H3#~Jk)Gu zq7!68cf7exFCvIoK(^wxTFxNroizBi)gASRc?n7kCkV1ld39x#ZSVXA5>*_?=>|nY zm$c|;{`z}t4#SH|psguGA;hLZg3T5&@YIKY%}eG`g%6%tA1k7NUR=XWIF(C z9V7f-oM-P)bTBrYPn5aA@9#S?)6!rJd^wew9a{Rsfg0=Qj#v9F%Rb=Te?#?n@oeO~ ze-A$y)_7YR`pdYLV{7(*B_fQ4&ybN)uafeod#Qnb8?VvSfG}Yg#k^YL-g3P>4?X>y&^S!mhoAx34 zg<|C@w$mWnVn~aEOi(|sO{4Z@aHsj9>K2+(u!zwjHrJKycuSS@#4cn4a@g>xzBW8&<^r+h>u1V@YXW30^VfF!n(VqdB3*u&`i$XJ5Lw zM>%eTa(TwaM@E`q(I}Zz-pO)cABI?*$K$A-UQzl75pB!|Z(M#_l96u`5TiH1dy%c0 z@Usc)*cN9GOrVa&ZH*OA5N&H<28 z23-y6pnhKjpp_vDCTCPgWka6uGf{fkDT0!X6wg^a<2&-jryw8!4%k>59ONLOlFe@% zS~$eBt;&K+ID})wNZ~+2|AjLdpvUV#m8ZF21>|vXpHi?x4u5 z#MU&5`Xb9^*hNz}hZunjp=VlzYd~)j&V^kStFf_IYLKa!VMg^I@1)nHWLgAQ1nm>< z1$SL$5%Pv)P4*wyJ<~_%U@1owuA3a9czEav7T{hidfn!sgEz6=wrlaS zX2380W4MBH(Pw89(MZTzrZ4-Rq9I!o7ztPr1r~1+l}dNuHc`b(Tb@um$<9sm|{}C-sY&k9Vd2+_FS#S&h zkWnQhiug3T_&AXW_tfB`o+Y0O*eG-W@v0pw)c2eCU^3z2Q3iX`8M1U(GDc*;OZ&mHiJ$sS80!jz9nx|cc7YR z;B|)C$8K_7kLiDJj_(Pkwqxw#ws)xB*!h2Na%c4upS*c_sit4GW4rX)gpgc0SH3TN zhIsr_La5lhxx$xPGIR86oj)w@YRWBuZs-5|VHIin9&cJ;ckQvSMx=@$jWH5NqolDn$5lr_pb?nG>I}P1(eVxu5mP(Neh22aJ4pTRt|$0HxBO7FLE^Lh!{`f_9vD+o9;g;oL@4%(PhtPIBxyo z_#VNlr^g!TOOnBo+^j&dH6JkaZy=V4z2o~x$9S<^Lynt+x9T7>rFGsjzQ?~2{A@%G z*lbRv+4E^HD0B0x9$F!FnQ!Ij_%1klkcE)US(UyLu1+w$(-L)2oGoyI zYak>1MMeuESBQpbb{|FPekxjWSum@iHPra)?GE>~Bx~%1Xf3=svpKIT6t}c14qRmk zP_TYFZk8Kt{)Kpna9Y~_$-rIyKHr#UTf+Xb34DSJxh{LheW|ntU8UbD32OPEpKR^K zU>)jw=~t-*_mTYgv=33)Bm;%)PHzT7z=VH5v3!XTU5zE-;}rOA?E_ZEtZ=n zI_nw76TDahq0BlHdE$xbZJT$U>i0#sDP}u6x#oc;&_X(Iwpf&r@Ah}nB`42%nBh>| z3PwNltAM2pEiEQ(RD)r=w7j!7FT0R+-}Fg7LzpIz0nQl>+n^7e_Yv2aO4RXpC)N}q z5(oHjw5xdZ(0VBz8u3xe6GzTm_zT^`m+}b)7M3>QkfxVkx4n?&DC?j^1l`l6&)XLM z+b^UX-9LG`L;*uUJ~B*vJ^#*aZ=O9*e4H&O=bK+YR_Svqw3%eU-%e{fV!xVPo@aSc z4Ri%zxtiyzKY3^zIiH8xZceG+-)@W3{^F1RW)XQ^9GG};AZ+klq?92F6X~+ zm#5k9mZniq>V^dmh&!bv!MXCmU)-;N&{2EeTbbVM93FNbPWls0`tMHqDcmu3GJI@W zv!ynh@Ht$Xxm|d^*52DX%5qsFKfrLNxn(+>{Rkmm1xf_Hpt4XZRs&BC0gm$-t&uG@%Bhh$sc%yb(b$KYZqHi-C5kB zWslPjkbU>)8-Wd5^piOc0(Gk%*k2a1nf$MYBAykYxc$Vuc$dC_ReTZ2%~-w8BE_g< zvlag25AV*Q=>YM!nfnjZrP6VkBhuq^ z!+pej0~Gh(tum87fPdOFo0j)l5kIT7H_vsBqKUA>0@lRIl*q-@3hCq?HYdE!L@>K% z%}>IsD_a-DfHI|;E5Ut6W*N$40!Wu8-ec1S%Q3CSYVxoW;iiehc@@dI03P|yc;)xl zUO5x|UG@;;zQdkVCarWw8mH_VeNpzLw}W9W=gWu<}2Ju@nq5_<)Sc$dF|Mb0r|%WLX8 zgpp3vVt`-eO?XZ41b|SnW&Wb*zbZu%HUpPI(5wyTErQ53mf$b#U7h#*VM|kDgUF+Y zrsw!yu=pc~#TYMC@|XHsi|so=+w!V&)43hlcQYGJBgw}eDfM?+pI*8!9Jp5G_Qj9) zf~D;dXn9pU6S2*>N5h(u@HaUiowo8GIvz}_KV2&S@OgsX;(HQOx2AOm*CH9RNEIXM zxe9Z0N5^LjI)LN9czmGkKlMF+1E`zf!d?cj$+9)gU|SSe3Qm|B?_aGbcA}l2*NVO1 z0eg<+G1G-4TS5RY9_u}pvmN-mI$_PW#OfWI_P~xh0n$H4wp2MRvucORnMl0VdGiZs zhlyo^p;>WpGVjW zYI-|vwml2_i;-Cw=`CCZ<6|wXaR*r@#Blk0a3?-UnKnuau`6A;94CZofUWCg(+~o+ zzNikx5}#0gZ2WXFc5It2O6r&sMjc<@&B>OZuVz!2k+9RVp{nNt4JKt#*daA9ipiq_ zk_9!`Q2c$MoBt@#jBUKytE7vYqofuR1$9;AK^H@|O76;Gv z_4P~YSkwiq5d{^o$4974Hn4t!?$Paxwup&HK9IJ5gqSIUw)ob|J?c%K15D&00cEc& zln_Aw(o|JugpaJUZjhwJZe;{R{P_Xin!Y~WgR`MHioQ0Ws()Eo_ry#$Zn>W}?xGSw z;0uwofvUL9FRo;k`ulR9HNW}S7l7;WJ~y8swXojR>9JC>-Z#%3!S@?RA_fhPj96}u z8hj+}pD#$od^y(azlPUyJ%xI(Z(UAD0Z`S6M08w|#dhAwm51dH?~4QL<{4L`%8C!`7#Hl$5E9Ll+MVww zih+y&xL%-c4H4h=#qx9ofHy&|o)qwTfAwrb{^5(IRhSvR_3%7tkHM8Z}u4}1V z$a`A=lWAg1pNk<7Ej_m$4}L*lrbzCAImc1@QyEzs7?IHuk^W2`OOpPapLFC+&k`)? zZD><(%avY_aoZafDwC7M^?It5c*wAs?gjYxdtr8LP_KV&QnDqpnqXHdp(?=8Ky8Ud zVb!-ywK>GO(NJ4;tamf5NYmqIcS|t2o4N;?hHc8dn)JjY?cczDNGNw5A^!-vrIm0F zZdMX@@XO&+9M3LGdVQvA4c9Ia0(CmX!y)eo>|Z?)fd(RBQ`iRH<9Zm>Houe`X3Pz@ zP>U*Au5R(_2T0rp#oTagoU<>N)KWGdW?{(h_P&4f^x$V%h85<*q2xVHCvh1fPJD;g z$E<}k029(b{Wr4m{a@gkXWK*6oRjkD1-$~V#%h3-OUyP4pK6K=)j{ULYM;kCgDcfi zALYrKNo7;mvb**KEoTyN^ft64D!vdERE(N>kf1!BFzP-TPtL>E>0JE89=zag;l$k~ z##d|5U{^%gFaFZoZ6w64=s%dPBhHC8RwEB|2)k+&oYRCf zG-6F+q%Sz2=NI4`SmPTtB3O6MsqyuLWUNmYK@lyc^x@fDslJbKQJKEr_>5gh-CS1A zat)=zBi7}%7p3DcO%BYUdP$>4M7#2RnOIvDvv2gtEMWkDYyO~P%$2(O^29;$L4NP}JTrh6*_bMT%&Jq$+f>#;ZfV(x3F|q+^)H3f1vt zXps5pL^>|o8?Q}Pjl4YhJgCW_66-f;7q#zoPo?%sVCp$-v1h#EOqHd>)=ID-rU9|@ zHxY##0Fqg%skIKt`Flw=e8N-}HT56hTd_+i+2ut`u=ayhmgSqxETwa(sVmOJkUuz? zkAax|%!(!g&(fj&RVXL%H0Q|Gh7jFX(%>QI95#CFr%b|e8IQo;3JvxutZm@iP_1qf zmuKdLX$}eMk)CccdWA_YrEm<277{2cLsnhDvJxyXR`ChU;)1doG6oD{?CP24zB+n* z8GBI6GwdiGXDE+5>!8h&j-jS2;?@1NQRJ%?jqp&gj#~9^l+v`27fB^w7f$uKu7g@! zjMn4*;)KJfF_0H2cTMx{$CDg33?nBxBxz!;ZRWbrI=@#a!LljA+B2&W9*O-wxq^RO zXr4CeVX0v765`8+38DNVU_(U)^Mw&fr&uWMrl4E>W}=`m5|`lIqk;C(VkarY#p1sLGnp&f?4r`7?(e8?22&XoR`@EtXOYKQQ8i0T4LsP1l?PS6 z1{Vi7=AfC_rMnv7>jGkEIBzVH4Nm(`?qC^!hI3KUhS|X;4F?tKT!LI|ZtP_p%|wJ1 zDV=#s3xtE^W~&LR3nYbOBY04;m96CI^9(v7^iesLFI4JO2YpZ>l%vYoDhvdI^9c$K z6oLVJtx?bZ|3X_yN=LhtP%p4>JW|{h4iUJpQ4SrM##}1ZC{`pR9fKosb2al43uVpB zPtqL*0C!$K`XsvKV`1_uLkd21c-FCCq?(ro@e&rd5$F=HYYM?maDjr159i!0J&4k8 zH-yjhPq4&)@^Jt^1P2CPF;rvvKjVWPMtWn$=oL*;Z5olg5MbW2;WXPM#R~a-m4v3N z%>65n}?EjJJ_86Ml955LXyCfDDAV zTD1m#OqR9|-xO2*(XBgbU!wS%Ul!LhGZMVLwDkRpcp0&vcbqxoA!V95xRJi?DDDe= zWKh6NM+R>8^pJ_;j3lo?2bg1nZwA=_9IC_~GR095)KLoPdJg&V;!&f6BqRW-h)JonG_45w*)*-6Y#9dinX`5qlSmEimGP>g z#boTXB68iPw z3uZvz>MxTTOM8>hw61(-2F{5KX=~~RdlOr8?FrhW2violi(f@zV-~UUE@&Cuz{O<13q+cg@^*6R!_Bc34fkV7V;_{J_9jd18wQkD z*<8D$ln^?J)r^iKcl`<}T4;HA#nf|yYD$~eSd2H(YN*sxUK44^(Jsr1jSai?bGOXX z&QB^C#BumFK?h@)fQ~+;VgD~T5RYV0gmAru@Lx}$5ZmD8@7i7~Z6dn=XPX}irYqjb z&r@^x;rMa<&reRu+}hkp!C2qW?7xA3vK6Im=YQZ;l7d+nEl||ONwFlszcX-e!0hP- z0>Ypv(2Gi-A!M*k&719A0GranUr@e*AOdIpfB(e_L?z~lNbrXRnwpwybH#2=e7(4T z{oxDRROYA#RTJ>nH~gWb6)X2`dY--=s1GMMz@B$NOui)L-qIbO5m|8(E{Y%g2P2J~ z50j^V4QywzRLDriVo=VDw7$Y2FQW^y`j;jK!BxmeK;A|$E{=Z0l`SUa|gSIT# zLk?t{@jK$W4@W-87`qcMgQBDp_)X*R7|;t2p)n~}P$3dj$!?rjF2U?an@J3nbuw;i zyCO%oRO)u#;*YZX-{yn{H|rio$aS=i_3NnxxW@!^O1c)8Rg7GmzpHCJn-vEB{B^aT z?pNW7#S+{Xpe>&&OH8+#N4mTpFlRJ*;iA2OWh3^URHj93MzZ9_FrF>=@}=OyHQhsg z32j^GxGERZTF@#oyJfPLBrW*S7gA#HW*3Sp8s6d>;kL+#r6>=VpwIJrYwu+@J>5oX zA6?0;*MCP@Yg7^dSS|&+)fj7{&W-MqW5sugIVwGy93^(KtFIQj_tsKB*!7|>#tync zb1Nu}Y~`F1ugbvB5A^pAH=^RO6WcXcvKD48PAm6$gn8T%Y6C@AKEVFZ`SU5E%hmPc z22T|S0>b^j%%A^w&;E1Scc=;FrL@@g?c1^f_ z5AestCLw-QDk~j`ys2_U_`Jbl9o@omvHABoa6lDcWqGN(rV`+NNqOzu5b4s8*+g;e zJGIf4P6Q&(_x|QnGnmeKoO7RT`{}#wdNrx4!*hq>C!$*Xm!=iYP0DQ-B}oT;4Y?vR zcrV)-kXhZ(XYOxn&x9+jOJ%3JTt&&}pSM$gCD(dcU*hibB5(AMNj?ghUygc91zqOp zu?WRERnwai(3%Wej3Ul2Uz%(aQ#zOLwt~E%+yZO0kRl_ppB>FiO{)E)uuw3|hdB6) z*7et%9c4I!lR!>M6LFpkaOc~lDx=FdxsvXY`-oQAZI+mEC0$f%B1@>l9KF-y+6bRe zp`aiwP?^7>wIEAX;P&xlqRRXrPb{hWjHKx476(?-NXkZdrFTlB#`i7G!}e-S_K};8 zolqFY1MUmL+m&o%lch*mG#A8Wt6CVbY-36RMxta^i^a^d zOHY(3>Y4>(6g*SWBqJS6@A8rNunXyIfA2vLGsc-%0h&wWT?OH-d33Yv6I5QrzY6;Q^L6u8SLmsEIF^osq*rI-wp z^Q`!)q9W(HH*W`2`13eaDW4`qnmPvuEUNNR<|xQTqfrAF*=qBkRnuCLEx{BUbT|iK z>U9?NC<8q%q($bvDdMp1XD0UDeH!;EK+dJKqAkJo`BPe_+q+qROOp3$gb)ZDPJ)AA zgU&k|m|mqF1E%hCACGJfDjtqRcBU|Zz@ zXMst{Zg#1TDd|{)q|BEYkF^7!49s?1y$jIw*InJ4)I#&)0k;k???y-P&8UacJ>cMui%r#lqB6<))F3@ z=G}dGsGK~?^cI(f0dU|}{AEwAQ*dUXZ*2HW=bVbWni(=GqDbeGRZNcDlT`<(N^ZxL zu1l2IFgJ~qOHh*Y$5@&yJGPn_vUch*E`J|)PO9W)#H;J1UlFip-nsj11Z5`U+F}Ba z%3LYnJ64AK_F6k>4HXzngb$aT&Av*Xy`^=DbjYnIQ-dJ2fwg3|g1B1TrB5V@*9Jf> zx+hLhdGXbK@b~V`4%*@RZp$@PLw>H9?+B7y^@W0=XUF3KCvqh|TS*P;ibG(NVHGo5>BE*GWX((sp8f7B=^IRWAxtX$xnV~ z#Q--u2f>fm5SBCR#?k|a!l31lckQOkgRaz zn+i8dP27@=cYJy)kVcO2lBGS#%pq!^>#z~h+z=6&xrg2^9M<&n; z9$9T57iR*Rlr&Q^|KK7~VdxiYGg$oltJbPp>%|1WgE)7}fgpg*hKG?%3m4ZOVZE%t zf-Oy7AQV(g1>>hA!@(KU4<3`o79ooQ3E|Y7F^N5HLcyDGvPAvBuwAS?^L@W>@3r)= zC;bJ7hqr9Q1nqzZ*p|XXox37ctJq_c6x8&XNM+EnzJ)ZKFe4zImfY^M9?vObhod>+ zw34Jd#Fx~|M*n(cHZ^z}A?BwWDIC636ZH`48%~0Mo9ogY+ zp4Dh9b35bZj}L7c6VZjk6QY(22~yfP;P3QqEclnv-t>zknwIDMteUnW1Lm2b(wl;L z0Tusi=UKEnJfjA2b49#B2x5En>lp_5A&en?J;sHxy>z|e~^cVfp-ArD`t^`>dJD2 z@NK$*nE$`L@T3$_^aEo;S8ZwBL(Dz&2~N1QHG&fIL4`iAgvNFh&7a(W_hgGYQB;{j zJ4td&Q*sopX9ViR>8M-PvNI(Y4BIhKC^|b2PXZauqPyT7>Tf>HEKr9-s=+NG{$@f+Q!^Z(!{5S2@PwB=!udvOf@6U1RScP z_pjA~g{5>$2|!jUG>`WQUD&>{>C}%e>0UaW^d3yv$-hL6YQ3U3MZMh4l7T@ZBAFA5mt2vmo19@-a0pu6;?ktC5RLZglYD^gg|hl>hZ>G|6|IbX+e?EK&{Nr_c6T{|VVI*r|V zx`AK(9b8eMBJrV2wAKR8N|Bj& zNxM^V*L(`EUwT9K$Ur5q!PFF})2VC{U2X8?Kj(x>3ORhM#PX8za($Ot zvZlt#wRHKssZJ#;3F((b2wFTFJjM;Za-LB^2dz)#-0m$xS{eNC|FRr(TWKB06e~rA z?ak~2b~2V5la+dC6-;&C6LUn&?s7f;{bcn0p-(9Ku4!T3Au=b2{uR7pHLBT3pyx$u ztzVXSI((VEL3>~H`l4#JkYd3)oHw0&d_=CzAPm95cx4}r+n0R5p!rD^e2<$Ej|Hf7 zGDzc%0}ampdq*~Pd&q-tyD+T_zJL()p1fUrSWAf?iP^nHdQQvYc`eV+dqw5+GlGsX zid~Rw%yGcou4ebXo>bkl3%!!#*cjAZB`k}S@ePGzK2K{BtWz^MSH8W#?6`N+yFEow zaf)83ECflQF^SLgyi>$!X+HYGoxK!&bn^?@q|=xh-r`0XHq5`0j+axu3O85;3rPLG z&br8UFXj4(OL=9j(gU1Eg*VKNX0g-_%w5jAY3_r_5>%i;qt(xF!`-*FC}-!U9su_S z&_6R$G?K11hHwX0WYE^%eeYT#)x>zdF20vgtB7hGW|7y$d8inmmq@HiI%01H=#--5 z@IDpfoKkn>Xi~F9&;L$|w_MOP!Fu%7$$W4cAK4K9U^!wh=rf2n;t20S;hN*Bfi&kuk#Kx&Dqv{af#LGi zEM&`I1qiDMsgPrqJ|=euEwp2j$Y8t1_XhpyFOyp`}tA`(=qA~CPtKlE{95= z`4jk!QFROZAR^S+3WxnWhkZn7&7-8*0`U^Tln0#YgK2i`{aE?;*y@9-~+$b+Hi66WDXtixYqx`OUCepDV zaQYm+Zd@+e{KgUTk~>=-`_J9=(4#_tpFvo$>dyxfuOP~z4qMUmt3ov+=lzgbB4L=SRJZ>|OUX;z-&D&V^r2qh{Gf5~@d5(NQYHOc zqyS5t@lp9;nMu6xi`g0)$_wimhl;{T35_ zSPM7p@TwMdrir`ejVHs1i;Tyk!-psuEAI7@JmoQq^3)xNKJ`UGX|T^wW7muaiXmKP zBmEzxpk;13)$ScG>ZupKE;Q$fqE_rY1M_k!vqc zBN1hv#``q5>;u!VVJ*^v0Zb(~2-ucF`$Xg#51#?%lXzTC#XXC?lpK07rH~&`pSL?b!3_TkR-@%8TjE8YAhZh9d*nZ`% za?yBI_JJFW)oI_b{JsEYwY-X(o)gfqjvl<}88H+1 zRt9(J2?OFCj{WN!Krf!vsu_%tF96M~_i#@#ALLKl31r{JXSi9t-yhnw5*mu>(&4=0 zE&Vn5w7u>oJ2)Iw*qZ78gY-bhbZ-j1Nru4U;<)0H*#N_E58RV6$rGrclV@Nz%YUks z+90pV+#T42CA~6Xvu72QyqFSMZ?|eBk9po@Yn!^!D%)ah(x#QV%*b+-9C{4WIXXOC zKE@qt*)*dI7&7FPcl!OFrqS(6HIju$-QKz7BO5JMfAi>>JD<-?^H1QVF``lP#b%%6SV&r_CEh(n3Jz|xaK@?EZQUFd9u)Dw90zwj!IuMHJSC*5kcY= zHbE+pwhmID;P~-f+ULYiug5s1Ub6l8=ivqKRXgZtmYtv0x*2D0v!R>PI>84Du7k+U zgIX7MUuKGM{$`xM%t#G~npm)Fzn#In#9s=6Yc0=&MmS`VjdulHn%-1h3RToA$YJNB z`X(Z@F_#^-qRJC51Aud~({PDezjqjD#uWH@2-L#;wy%vOKhVkBRb%xnZ zPz3Sq1B3zsN}+f)p*Yvo#2ji*i4 zKfuF$#^lkqT>)IBqNt;!Ic1DjpwopRp7dlkecz3@!i~{TQNs;U$;~N+eJawh{Xs>o z#WU{oihyKWI{ZnlpzC2>)QMT1GSD-A$ZcXdd=qfp; zlhRt~pE0OoL@x&^#-F=rmzFp4eY8z*T6XJ2lE6ou_9B@yjWnx#@}s3X+mo5 z5sIn;y;;m<8y7SE>m>>^_n+Ge1FUb3wAFu!wg=Var^2DMqIiSz-DiV+o>3 z4Wt8NsVTp-qZ&Wf-@faTpJz9{M>nC(-&I+E9?YiG_kz#DT?#(PTxweG-}uY9N=R{1 z9J$nWDVjx1+|?-E;Ky;$jrdY9HxLhs)UNUku8R>a3kG?`-?yjdZuzNFLmg^8i{VY# z^l4>QI^({;v^*!(t0v}heF(aGDWU52PS$RAox7X_yvN$ONE zPxc=%{X5-fFnbd>K(#)!LwaXGz?b+DvK;EJT-sj9hhkJ(LI-0gDi5z#`E%#A|1_CT z;EzzT;YFUo1b{Od=jjdWw+&2kwJ9Br8q@ex{nk=5o9^hG_#Cf(D~J$~>2xGT1A}}1 z_^dw?xQ}bpRa1~x(AV%U_~W`2Z2mFF3xJVi9P)DW7Cz-yubDWlW-ZQVJ25ANO5sxB zozm9DB7=+SKv(Wf;5+>Ysn=7V@f2fuDqK;t1s}(XaLvCNQb6Ueg+k-?=c{$& zoqDX8Cf3ejb*i(5N?M3j6iC5MUV3y4+yWBzQ4}VBM8a5ZA}liU^b~AUu&({6y^!-c zo%bn6^y*aLx9d#c*5kP9N*!}+!Aj(=_&k6W{47ARO={JO6}`OnW?H+nx~WgI_ltqp z5LNU|(D(d7Hb=;P1D)OCS+0xPu|F05i8bJc@9RT)DiOV9@q%aFyLTh?tn$1Ou6{wr z?Mr2Ur_6cZ z!{|v)e(ACraGdk!tv7pvY5Pnpx-mtQm>yrh1-6Vx9eEwU^U0dIdc{FeK*)dREs9Q- z=Ip6_>`u#H`9<@cio|EQR6>jhqXCEYM}2^blH*@yG4IQ zbtF5MN{ulo)t=BGyh$Xx71UVS?Ia7B{4AdMc4k#8R_@Oz!7woG+wnAw^x)>R_s)` zGUMA(LQK)Q*h_UV37(G;$(O!>0P?h2!l?{q=`{=0J%-(L#MvMIhSTFPAF|qAM6?}3 z+HG1ivEw5f&ZtJ$?GakUZ&z%p>6QVMd67n`m3_>!%%iF&85bF686i9WwE^l!>-yg+ z_YZIa$lR9J(b>+}!O7Or*v668_&+ku?0y1MTx}hU{+rz06CSx5hyVm6_(MZx_@9d9 z{@<&J={uU4+nD~RlGdc|&WE$?{_R~$R~tpdcPvp)WJTpzL#(fkoIu5gXB`Dp2MK0` zjnDxSrRKug(2yXHvwpit;&3n^UbSL_-BOpJ=3=xeGGbvxYB9$mea&e-|1J^B)RoC$ z-}7wZx-wpim&&{1>N9-Jx%Ir|bv|s{36&R&>skmD46Q{GAtzFq&<%nI`6LuisdB(@Qew&+91g*FEeV`CR5+p-RVv!u7j#%sg7>Q|s^4oU9XbJLjinnYw!x zA;9mkmq@jE-;k@7_}7}TQ*pZppVSxF1ie(Q7JCi}hl%qbJ_#w@q;_F60PEq25)EoD zP)wrUB)qqqhnpmaRMFwkmjPb3U{x3)EdanuX`82eL>ijdwviDAz>0|f394PLY~>eg zcVV&?t$;LGjizAPCs9d2g+x&~YV^ZyDpQoJDfkr~bjZGhs8Z*|&J|VLMlv42zSKl^ zgY05T6&12)D6)dy?Bu8fbFww+Ho`!~T@?(XxaD(e`5F+FL2q$Vf-Sv55Y?>wmLSAh z6g=GNV}gupIci$uk_S3D*DX!%z*M~2LJs1>+Ptp<#;J5MK-2elPbo%o&aCKnUqs<7H)?{Z z0-EW>=0V3!+tme$Z&2`pcnb`GM4`@wzG5DSL=jjm%X_*PEkjbQz$St?l(a0|H6eMcXG#V&x)_iRr0XFr7ICxrJVM2Kbwd}ES8 ze^`KD1Rk4^jrwm?8l1JYcW}>ls1_Opc-A4h>VIxs%xkw2cP4(zBMpW^HaS)St}f5buW#N%KR*GVe}YiWpvXty&%djYflTi^(eN~OBWQh*buuE_B!JaE# zAg}h3@eN|!Nu%v%qSUtb2^|0}(kU>Y#!87qQxW!lE zbuC75k#k;ZBSeF+(ORDVoSbe^L_MTM3+=gCsOc)#G*hG$oEyM15K%C z8xJ15`#emIcDFO)!Z(KDyaz?d?!clekg_*4lfv}Dy$B2n4<fif1y^@UiIwfzOU;q{4ASsat9C8tX&X}+R$imlLq^1bEnpQ9=C`5JKbjrzEByM7@KS4=35Byb z#c2T2oPPqAr!jqBJV)~`DPlYw>x_KCf~RZc$WfZ)5CWqPx+YrQkJVWO%lM%(YW0pc zd>Uq(U7{=rT-=K^0##yX_nutx4^m`%d$9>*It(EhrM@=vOXxM9`9iAZ$yjuYDCwZS zGVpU>Wp1YZ!k& zcWSLj5j_m<@3g4hnUQ(Hep`G9gTAVR7jT0rR$!x|iSQYc+9ye`=o;W+9yyzb;?*2s z3f*p@q7xBXkrg!S&fFQ zF3VMVSa}Fs0F&>%Pe>ipqtVPMWZ&zYLCixm{$o7tw=}v&ZHAK!R_e-&`X`+KpH>C0 zd>n-Z9KyC2q|um$dV560Gu6zuX{Um+I|tf-^=RQWQ>vyH|Lcx0tT+;s%D*KCeL$hB z3M#Rf=NM=5W%v@zd!r4?`MG}T z&mr|r3@f75DUHRY(jiry>xO4mTJm%eaaxSzf#p*l)dPNq!=GBYqMLwgQV;KPH42X? z&oFFNbsf?X8FLqN9tPD4JuUl=hu2epP@*v*+CwVs_{$)y#Wyk$+sV>3nTdOeOx4g8 ztVLRp7+mOM0!lWzOpWrmsEzidlNGUI?CdE3$3?B9Ij>gLGFS%B3at>>gL1Y6iV3G= z_wru$o0n*x;$Pydo}k-hjbwBOlj|SBhFYy=A+|YFiR0FuGtloi-OAK>%GcL0V^AI( zmVvz8-;^=9o*#7_4zHSJCW&^r74J z9^S>-Rl80XkPw^J8#m-q`4cTP_95iOX}X3J=hlYw3cmG{2;X+|C|17`59 zhlANkDw@&_!w_pNGWh`c(bSq#$;J?Ve;=mGAMM{)# zrW_v!yJy>f#BBdo1z8>g0}U~f-tNU>8IQT#&k&9Uk##@)eK5HyHEb;PZ3l5XGh4|&no~bE zRo;!ZLeA75%Evo;FH{o|?i#a(EU*AaXMfT4fyj;Up31Y=b>vQ4%@aI8%f zZCX;z);99YF`w(lPFzOxP{9AKQ5{y6X4m z^}oxuUo-4bOVvui{VN$&X%XykYS3opOE>kAdgBD`#N+ZWN6qxjc zD?cH?4z#T*7JUqm6zT>SZK}|_xHjRqK1d;5uF~Py9yHvCD1frYgH?y?l+aN$U!rWl z{6K7Krw3_i%8c^5kux*oa=F<(zHh%U5PgCdlpdx4*JO^Yhy6BV)& zi={QeeKQ)&ojvEnWCM?HCJ(hPA6Ad(QGn^u=&~J}``LvwcorPi#WKJb|FFY+i@0wr zcK{s}RWQUkQ>gT4MR7_hY8# z<4eER<<`IMo3&SN?HF~?kmiqa{N+F`X_@LH=A_d2xUoQQ1nNphW+W$|>(fozHcM&b z@9qA&YWKaM!3RiPZ@wt-$MbEt=PViiFC<``BzCYQ1*RK4E8HpZoQ&Z6FR_ADdkYH& zBWu2jXv7BIZH2KYb%Am9939xCjP77mh}l;R-}&V-rCWN+NcyGG>whJTklo$UJGf^X z)z4mIT&!6SI3i0|`~aLTx6Z?W97zSXK`ML-TGTiu^Htu9>J|dSR{nv4apbcW(LIjt zJm^4_G;!|18S3!{>eV z=&?QLJGS~9=kr9{){*-=?ua{h*TV3w-TY3{WH|p{vUet`uN6>H*zk?;nMt@Q$1v{j+SSBDHJW>iRkZ}2RQZ0cm<_e$GXFw*>`%bS$sv!8%PJj z8yu~8`6ATGBxK{{Eh>%8GQ$?>yZ!9F5Sv~WTZ%~g)ul90Q&8?T7*-jU0!?x;8Wn=Y z{t&Ttg;}mQB7{7{=e4BJ$wu?Gm}?~KN=_A8nSk z{E=7@jm*NIJ45ShLqnIz?u8{y(^2auQIO-lZYbtFo8mt~CQ(dZR-MQsIv`=Vq0Du@ zNYMDl(_D|x;~5IOLNHZIgPn<#ToPAO)6*TY3M(}$u1L3!TxA$l^c#bTdP%?BIDPCa zR@Vbt)`lZ}v}C-wtY8$FoGFv~C4RW49|&-W>g?tCFIDa0=wR>iUzV{F^<7n6arAG_j&X$Z&;_FeV!J?* zK=3S7-Hk9J4*B3%N^(kcSaBdKqodVW?2Ew$aCu(n500TDz8f!ZjKeF)wbmrr47cFA z!e8vshP>@xAw;}!3Q5~8GkF51S8u)BpP!>hn;?)Zm!V(^I7y&bTkubl7k(UiYO>M$ zQ(KK1^*CFi-v1CI*g4w*XdHJ15@DMCFx2p?u4J|~X&j53H&zsM)iveb4b?g;V1qeG z7V3)&Cz5We*{Tc-L|j7jn>^FBcoeb#rRPukiWSsgrj~qOhiq>vrv$1hRmUWYB>EOh zmq8i`LHqLKux0b1MJZuFc)6>4 zy~Rc_A>`~pwfbHSkXS6V2L1uW50;U5YOlq1xw%Gq+kh9{v@Xty=^Z5z*cl|-KILe= zOQGG^B0gP=ZWae#5r9TE4$xC}#8@p?d|5uIOw%^9yi#~BjteOQAgbh^)I$xj>kj%p zxIs7&OJqJK3G~0fbt>s09pvb6;XuZZe>%{Qo6d&BD2Ii8l>jLgJquzF_gE+!Uscax?fd!e~d?cN%1C!N7#76o(FBt#k@K5#)%xQ1+G#i zw+Op3>)d%<$R+9Bh{MpiPAbxpX?ludYig&gPCSpXdsCE^qR}cVT>Mt4*JXJVEtsG3 z>u^Za+l+AOKI5+Ed0p@!zBOS>(Hg2J^p{SWpl;j-S+GK@NAIEz<}$x307j)8R?;C= z)0@`&209GUnBy4*d`Vmy(?Oh-4{0r5suftCmdz+M1bbMiK+PEPkzH(-4|fZ1sUdu1 z7Z8V}+CjWNTNUp(d>la zp6qgjXS?5mXX%{t$d6r|z>61?7wlLI;>9&cnCtR>o~1%CgtKDa*+crygu>-S897rR zB5^U=Z%9VqB5%mh>?Jy?Py;!+zd6?MG>Kuw-2QK0LLDEJM-GQV3XF}~^gapVqnOu? zwz-C|&IELzTkK@!Yd0eaNTt5Z0E+Ew0^es&4TZ zK+iWOu-t}#Yyh|BK0?E%eRZC+T6CbuUy(At{IOwb#jy72rl7$As`poO@WwA2sJvev zR0b<(o~7O&pelcQLKA;Qhe@_KB%0v;2Fvvf1An@eWDy#!GjWgdpDm0WL3SgN>*{eW3(L3*;eJf< zypL%I=D0gfnU`NG(*tsl6V4yt>;XZ{& zh+=8B>2sx+$c1w{mPyu_3_;kwg(OQxo{1r@@8jA7^4;$1(^vMD-(Y_O`+l?W0v6+P z&mXhdzNtrD(&gpRXy<@~H~+A^0<%1`F8_W^R(AvVp%MlK2*9wHklF4q!=R%VIKDJg zJp(^}a`$#n`L-P|4|P6*DH1k=u68M>DGXu;Ube2f-2sF!WI^&v; zyW#R+Fc}-6W5KY5xj<{m2ne^WJP|NM%!fu zGw(KOEi4<$g>P7N-H-MD_G?x2Az;f`cAWm>)-BoY&#ONW%Gd)fROZdWi}jS8kt6*0 zop!`Jg)(tEF8-;7HvO}+`e|*!_RdMuk2yl>sLy{pwRg#GX0?k)xYZVXYGIpgtij{HVzSYL# ze^>?ggLjferrjCh>r06bIUlNMZ}jeZM7%5lm<(TA+ZCjqHDfBA@P+m!Bp4gIeZ~eY zrFl)d++2yfquh+(j;~kfyR5xbrpW;GuL|!Wp#}$KTh$$hgF5hJRR-NY62gk36cU?4 znt*&@GfmAzpt?`J83)<(;&(tgc$RzJv-?kK`ZNm2)F?5+#GW!|+* zw=){rl-Um}df=M0Z4erxG2NqKoTtMB8+d0K3ogs;HuX5i2;mV+UZz(RlGd+)j!WZb zO4+WtVV>_K7yMnLcw?#DJ!t#*S>{rfY z9n;+O0ql_m4|D+EmaH@+L%5YX_b-{4S070Aq5thz-7+}9!6gL&(X|EvVgCOb5dS$G zuJvF%_0-q>`?9CDrjE?57OeR67dcbR$(lozC{vPL3u&ZvvKUh|F4#Nlppr6~*$TnU zP@<#CBYvy=oQGY4D7HKoDTFXX!*J5{Z`-~0*u@Yd|LZfel{rPeNNE)$Bse>(YCa?I zLcjYq^U<^WlIue?+xvc}4DvKW$^?IuG1jsdq^sm>br)r1XTjXvAK( z&AsM5j>~Or-_ufeEY90_pvrs_D@UNMBcQj{lO27>Kqx0G>d64TU`{MC)FcK?4KZQt z1~!bhE4X28Myk2UJ+o8i-y=Si+D3&B zy`BH8uU1aPQ(HjH=f+Nm8E=ySZ7B^7SgF+G?QpS`ATTE;5vs&&_>)HnPy= z$8J-N?Z%Fel(|j@PhME=b(dssWNU9(T+zvmp`k3*$p||SssIbIiw|iFZCz!YHv~m` z%AmjXm3QT==cM?#q#&nvD~@BHl~LlztgFPS1{HgdYLFAeL#fchjD-=@0p>k?kZ9@H zwT*w(QWT>1H}uaW2Ome!a4=@B{#9V5BV;+3G6i=4yZ0t?)UBh$!*5g$>tIDRaK(k% zFgnkHZ-q8yqz7r_hPmgvPW%2-NvNRaVbjsU`f_7xlpp~hfz8#kQk(u}0&Q*d`h4kK zD|FjYO#f%vHnk02FhSL|r6x7zp?X%~@;*Hp979g*A0u0{nB^pQ5^CaB{9GmU{^|97 z4qj0_6(Q};GTR#PQ|b@~RQ8yK9l8~tdhF9o%lf$pil9&`y0n5Nh0_;PyTJ%>Jxrdw>Yr#eekq4Aay#Vn3lQVOUhO}XO0rWa2-LMLLdeoW$$+e7X6vm|&2sTL zFD5^9Ql51odg-5QY@In6*&9Yg@K#_e70=L;8*89P-0QoD^UrKxM-R~Rms)KzmfQz~ zFEOZU+UkE`!9*RP@YeZ5Lt{=8;9#?Lu2wA@*czj8!pUqDapvY`X(UV4v(-x99ZAkg z&<9s`w}b1-bp~<$$*rOxxs6z_Rw&*GEO*)KFRJvvc4&uR{IWC24!xd;F9|ia z>uG4;rodj^L5o%CZHZCV5y{Oh%vi7=8z^a)V@@2jFk(C|-UwT9KA{|8ymjTZ_}zXz za6-;^S_B8rM>_E%;YBkR_!<;0fv1p@@}bLPeJZS0N0`XEAktkTu$^1zKA;u9ns}lF zi`+IBOou+y*-19vZe92*&)y*@-4N==V=SV^*G*J+Gfdqk&0c$cuJw1t(0 zVWc~RG_yxslu=m4e)19(T8qtCebGWCmgXVvvC|HzWsVQ<%vMzTaV1ejobz%{r7T92 zWgY=@C#)?QY&`PcsY*$cd86timgD(iBuIB8M}d|zR)K(UE5akIeSDzn?xv9K0u7O} z-gR%zWYj~97y3Aq45hZCkFKN>=a$1MI`n1#D~L_e8a;4Qp}dDYJO2Yj(DFbO(|+(q zPN)ozIlG?e7KS!>3fdVp9OVfEiw@ek}T8Je%9>Sc%S{$yR#t!b~?S%{kFy zzlc)M+&CWB)aaN0R$S1483Tg+v~CinCg`3$+{>N;V=5iuzx5PX2+pmWTj&Alvgm;Q zP~Hm%;MJJ)h#a-HS^bHr*w>C7I}Tzq6~U(EJmUZjQ!r8y1ruJh2-r*+Brkg5objLr zFOhm*|DxS4lsd&3s&4!AE7Z?3;Rha|%D+l>@>`Kn?+od;U^t%K71mO`2{sy3#$`^z z>7tgaC_Cy-FS)gi)ZD~EP)vA2k?m!DXn5+i?zR=7;_ybimM6L-1X^ut(6&*+`2*OZU3bQPH9-k}C7(vtlZWn}FRuj;}JaMc4Grqd>4C0^YUV1cpb* z%$jqj(Q2On=wy;i#%t2v)sJnD4c&8wcDEM1q>`{@`QupO+T(jdKFnFIS512U@H*Bl z{_g<@B42`#Y;Cp*8Q#8wrafz`?*Euqk%8nE+>!xZg=&)^w+Sl zxgw@sE!@iNl#!P2uz9h^6O)1RI~%Ve9iu4|Ec~FS#jet*4knC`RNvqeo9E)t0@FXv z5SrikW5N^Quw@RvpVd{NOc0|NWT^9s;m<(5R(Vq$OFkG2Qz`z;ecXoWAHM5Sc92NH z7c?Jc$-YK6maE%f6^RRs$NUZvS_HT{^i%`z^us-FN-45&v$JS9IVwK_TJE?wis%Tn zjSVZT4Vv#zze%V~&}UN@5^y_SJK4sV*AZPIHIMi6LA6uD17Hfou9`dJ2lfK^eyqphU#P`9h7TkOOKKiMwjfUs#X&^Wv4V9Kcx~WhFRkp0QE&=coscw^s24N+Eo+5#cK6lOI~w( zz(pE8-!wTTbOU7dm*m*gI7;n<^l{LDl~JR}kb{*5K|VaVD6`jIDA-J!qUi2I5+>*v_R^QAego?MVLWdTAt zfsk>@8wl|!lND)Gsj#Tzc+4$9e7qUiDH3K{6q$dH~&m)Ld58;g;C^YNn-diOA6nihTz$Kblw=|>W zyS$FLBbcfjJL#VnINy)19^6oaEImu=^WUpD5`)VCmy_&k?SFJ4DL;Or2@~PPJHQ;f zQZYprjb{q6ByLI|dz2o_(L}h=&6pej9QZQE!t7i{=p41*lg@1GqerVHs3%k%YW@dj z$G%PC03kOwG1q-$5JB61{EgO$)|K_Q8w+D$WW&U1@pw|Ib6|nkFzpvrS7grl_TLlY z#!_S%I^=jGYht>B_^l~$kKWPUX&2r(d7kiz=}}ZW z89MaVDoq{HKocSj>CkMxK$nLrCAX3YJAz=Gv@|UQSKJjemI)25r0eu3GW>Ez*)s9I zmRC0=;_rpyH*&aiom;#&5=W6a6e#0p0CvC$)T8o{D~2>ZCI<{Tg0o{bjtAgsrg>a? z@UGGL=905{F$2|k^MYpaM(Y()l1?|19L^ZR64QJ)v+7@96E- zRGvO#yjW^+ChgznOSxmwU?;7}<_WJ|n{fu(Q@8WLl&l?x@tXS|l@)_G&4=z9^23dl zRw414hCUoMnslED6m8@7e!gW*-#1Fln#6QoNViWD{2i(&ci|9ah;+_%T8fprsOR+< zKf(vxZ4_5E_&POuN=x!wB{qU{FN5b3I^Ahq4*5iiFE3Dz!egEkdG*|@aEItALId1=~aZTmA?F&N3Mc)K%);chJJfVsA1p_>EgYud4XswPt|)R zgq~3^>HC%d^%OytG~YDl;veLqZoGjxx;el%N*eFX~rQB*9Cxkx4x7juP(!9yQRWN4F=3(j*bMZ>;oRLXJVV2HCfehv;fiqI?e|uVGsV`l9`#5IxG2W)bYq0 zgry=&d`bc^_{Yzd^*Fg9c3+OE3D*_9zJ^MfZ`kZBc5SP?@tlnHKAWEFe~XCU3XK%Z zKUSo46+hz8(~H;d2KIaQ3!wQI&>YUr3Z!Pb+!%`1*M-$%Y=TDpO!|CnXU?R^mA}q1 zHXADvb51)56_HxYs4aJ zrP$#>7H_b=ej3ANGI1K2bD!VaRyS;K0W+PX=Ko1VlA6!hc=uF2-Qa!s)k0$Apj%9} zVb6qg&Nd8hms6URETVg@)jg~s8PA?}Fd{`xNLJ;}*Y`#3jCa@*Hk)CDi^c#*+NbJz znni;(6t=R-eqwBtO!%!bW`MYv>i=~72&P=7`tLqBCY$eC%gcq_SzGZXvM^)b$y_8uw83GFnJ@m#I1fy{o?f{&K? z0LtRF)QML$o5jnapZx@GhC8#+(lQu|Pt|dv-k=dH#oTM!fEiG}niA8*Gh7cp&n`u{ zsJN9PE%uwm{5J^XQh+MS#cc1-BeL$whD+=7PQB$No zfqSQp&2#*NpVmr$Q8MtvkBIqG!;Q9geZdd8Gzf-50G&1@Q3VG39kS^zuj@lusnjD7 z-a#<<#Tj6`WG@%b{_Ax4AV$TyWA#L=i6KBW3^uEY@7GP__Gb2+glir_l zG5t%g0OL}%{=tz{&13d6K0W-@`G5{7H_Y|?_s8_JCIK{?nH&kVY14RjHL82p%Koj zCRc6{It|Hwbw;-a)uY)hVJpP(FJBk2dbLd`hsPxA)AQ}nJU_LB(;uHJl2%d+FH>rr z*PLVVlLnXmid@_puXHqjTPJ?N`ch)X2S7kj3eJVojIk+OO`sT@2SlSmfDQ~D>&zP< zii*4NVOg5RPcA=LRCY-cy8U{fk-0i}!H3BCSsRQWU=Pa65stiBWj1rosx8lv=t+vTS^Ef44Xc6~Lk)8*L!%qd^Z}!g* zOXD9m+z8X8k3Jswc7UZ(-oFijc8Gfi@=UAUg6}Y+c{;!8<9Iro^(POgsK*MuAiJ9f z(d+?z&-Yr%_IaSmru*vpW1`l?-+#hEAXN1eB1bw}OY4DI5D|aG01PiB zro&%$yuHnVu%!!L_fF!sq2Ffv&U*i4q}N`s+G==#|K-Ktb#+Iu#vO{!MET1(Y`aDO}>#H3^N~$Z>+bNN?Ax~c82@ZKlm@TPEGjFwN6>_$!PP*w4mdP za~qG=Xb>hERMQAo+$(Qr)lLSAHlY14b{kY3h?bAXnU&wH2V`928zUPRN+@e@ z2VK#L+HmkP?m5u7vf48t3fcYGbAf}m1yT2I%MGpZCj!4K41%iS!_Ek|!eQGLebh7u zz*X_t<~bq&Dq+;Sju|HkqvF#O2fxLDQy@efiM^#mZ!@Nur|rdcjn9{+1rEh;YpX`X5&X!b{AmyUAI!n zziNQ0QOZ-DSmC!0(xLgv-F8vkzXUQ1@SN zQAhNBZq2pCWwmpfXLM`9I(^4a+3`mNe-Z9F+>922aXb6`?dd7kXv9|~f^w`rdhnLxvPoh9^%_sw?TOFoL7Lr@ zHKl&~;vc!ZH9Ul|$gk~(_y5j{0pf}LZ8QD7n~e4Mr|4fcg;7IC%x?+*H$JjoSPg+I zXjIwTA?Z$R-{ZMnFkRXKSZx>fxT}5#1HlI)fTVB>Q~q9Z17GobV7Jz5C;FbGN<_XC zi$4R!YfZ{Q9jh?FB>g>@Hlr}JKB9@;nsRDXlloBLUY_*jGUFe2Nb8;9{%BK7E^lK= z?GtE1`|Zhs5zw6xCG>qZL}a1dc$=W8uQy+GuRpVY(n*+h>tvt`uoZ$Os8z*J6Jo|4 z7ts}`vR#~2TaKy-!%a9cmA7ICB5S88&vW}fD}M_sD>r7FY*F!#|B=!jiUcdNID(*( z>x-4ubXP5m#I_JQcky>UW-m-g0TA?m+|}jrSqI&QCTu?XziwD45dhWd&NZAlu}J8atmRzin|OI?us=9lDY z@FT9|P*cOkt+7^}?KM9C<5=8FSB?#H?qf^F%_E=ay@ zp;a$RY9m6~r2CR|M2;_wP6?XbLZ}<+NQpC#Y9qDF5cUp<(-y7f9^(11 z&%JCKzsy60pFe5KtRnZvR1#$S!Mh}y-mPf{4-ID@#*Q%kJKcBkPz$WTEITsn{tQS^ zxWdIKzS8>ehob2mtKfD4h1uMB*rx*N2PN)(m(|!UPc6wV-&leJk3GLy66+C-jOB<& z-%9u2Y%#dE8dCqK$-^O3vE%v+5K@(zpUK%??FCIg+jeWLKRd$BNJWVk9ca7KY4DIH zngKCVro1F&xNi@i9sa9>N1tM}&)9VEJ$M2vTg`%j7qeCsR)avvyr$m0y!*5dFOn8cZ zSCnP1_)mpk@+44ca)_h|jl4qH$X|bA6T;LPgA3&&!C)w#E?qm1Bp}izZ;z4w5fP_W zK_(Y3>?IV4fV_XOsJ`7Y50v-+#Qa!S-mrrL9K$AT6`Pjs+v2vdY__UT5)nxFcOAZ& z!$10}hZnPM-&Rn>mj%xZZFBJ~w^^?47Dj*DXZ6v!e{O?AdC17KskCP&{?SLZyKF3w zhtoc+H|3C2I~BoFruK*iRkeu?QgH*7j)RgWrO_n$hs;(z_fQv-Q@r{mb#kqWd!- zemnXh@DR;{68{C)w!KL><<@JlA$d%JpnFb3GZdf7sKz89)BO*=fu+cJ5Y4WBKkP)GQ~KO4_jc+#OA6OAivUQ>0S>T}Zh zuh*la(aC6B_aH(M)qUZa@LeuyZ|k}QwKW$EH4m1?lhktQjdL{&QBD%vSYOArB%T-~ z6B|HXVzQNuuS8Ovz%F9iP*fa7Wey1}+3bfXM)(@G+hLRs^*xh;gnEE(E;m;lab%hZ zn!$FBaI(1)av}X}XOmT41-a$x#m^&&y0F~nM%T&wV@vGEfn54o@?*tGSJK6@Jgoi9Oh)6yP6W->TnVgRgtEGD%djy zso!iX3^-!{8nd|=Q8q?GXow4xQ|!0zGzH(VNghg`?1XA!J$`%5BfCX%!v?X{P^zrv zrKfYAQ~6?V=%~wbvap$+@7&7aO`koU+zt-q5M1>Y=YlHkkg-@WJHn%~dO5$i_-edJ zs0MZ+jf;P{cVtuxF?c68OOZSqDiKT=Z$|dU%!%d&AxVZpS8btEQP@8 zv|Xab()#_LSIMpTby5tv!~%(MX{m3ZkUM$`JiZn#*?sgnf$H{W5`7ABAE|H_;k#;b zNDsewP1Be-T}WY-)ouoA(;ksbu_Df?A)53!LE~xf4DfDxl4E?(mo93;0vlIJsi?}v zXc%{Hav-!_muWo|%S**`58hPZCw;jy6BVV0yuMPuXkII5A&8dDq;fHRmdO@Fq9;2P zj+?vtN*gB}7V7L~LSg^pON^t??4FVHKpn@9mWv1Esn9NaG&2g7&D?AEehOrqzr0t! z1%d_$t0d-a@y7Gsww4bQz4Nyr;Z~l|TpemPT668x^llTmoI9bAYi}DTinLA>-!g`IiMuDa+EL(}sLAo@qmI{iNh4R(Z={|o zwyhP}F$p0Ndt&U>^_hESytb10)u&Jc(14)xN2gI7Gpo`(5A#a66jC}Lat!L-IiI?mUWo5x_t^ z^Ov?Z9jmj?J8N&H-wxe9nzS}FR0clyoKxOZysJ^ty;il;CZ$FBvQx6AKucTY`YdwTO~d6B zbm@Um+`9g?M9H;kX$|1n({L~6zW%u2UkU6*)WrDt0p)MVsHXKUe6rTI6FHtrt4W#% z)#_2HzPOn`qPzCxMG+yWAWatgO_;^9R3Wp1S5cJO9aEGVXcs@$9cw*t{*{Y3LGbZj zmC9X7IlQVYS8||Me5Xjs$F#`PCnNq+n6;-Z)sqjvj_LTQpK@e7Vi`X)uw{eAxk2Bj zEyCu)EomRdRs>{6Y{p8rPu{UyWH z9a=Hn45Z>-P@WfWvLM=*L2|oFzk$z}6qBf(z3SnJgzj^y{Zb-NMpztM3kuT|KMB(;Y?UMFeKyQU=f=%qZ@D9yN@x9%?x3a6`XD%C4BW{B zXVgR7dwc+W`Ev@<^pTJC2*xO6MEl2!V{hv4$ILOvj2u)B9o=5eGpxtax@pzLcdkJN zBDOT*MDz);E24@n@k5y*zh4B>^fW;X^T8WObRhK}dHf2wb(}0>qNrUaZoz&PF%fJnb{T0i?(V-PoY)M5) zYH6BACD9Tt@4Xx0ZxtNei1XjX>0txfF(!Z=LeY6x&^S6oYT=|YG#Gt(Q=Lx9Jnn)ew!TQrTO?Nz?XKlp(S|6^V?sn z{~1K%c%S#(h6Mrn!UO@~`oD4}|D8emuRg81tCg+Gf5uHpv|v2(&2apwR&84s$>p;K zI3X5PWEz|4(W%*gT$@{ErLx1Uv$udtQ*|Yqj@?YLvf(w75>FHsDA|{*--$I?M`uXH zfkqWX9K^mbz|!ulKHNpz3N>ZhC)tUxtmnSP-~PS1+_m`rm=gx6d5tHcpiWgTEx^Y> zwPw^9T~<=zkPz7x#j<+p&~IZW z?zIstJ6~OQve6dyR=9AA**ri9F14>w`j)#vX$QJj1bNdZD_L2DUEw#rms&pP!MCLB zTSEMxPj^(4Ay_0WMbRV>P9j_2!q*+hGHE_#d}{z59pl#Mcd|^WSLPCRr>xPA<0-{) zmy)gUX06$HE-6igekxC^q1bSf#Z$3kz-(x=D${IQEUuI;$8DWb3%8Cxahumq30;*B z?5-UEvTD$A|FAi^&r&d#a3!g5Hky!n5oU9tRISheqUl31Vvi&((9SjmJBQk#O$1JaLBGgu}XdIJBubTSLYT z{5}>|2mB;c$=GIyeZ@zKbtcX6yE~*MvOUe+wbRftTe!a2ibux;laH`<+DnNjgHz+! z7&lY`*R4yL>lH^c+55K>_oyiyXCyqjpj_!$of!D(#9DO$fi8JYLxQr#O9-Ihdb{To z(LDq}Fkj0MyswZ~1^r@we)C&>R&Nd|%A}x%G=kO>ZHA2XIw^*>St!_y$*yzUt}5i@ zE#!qhVKFeFURO9Cx$ZZ^LRV4QZ1@fYb90APA&6*xkR7O~n@_Wci!OJo7jv3ui~hj- zo6<2sJBvodVmN`liH0Axk!P)je%ezAl0)L&q@o`-KfA0NYhopA7;-vsS+m+uv$jNW zxz;K=d@zZsd6hKd%1nzJg{vMtKu#Ey28K=_BzPq%J}Tx6S%Cw!GBv`})z=D;A7mCm2O$lg-Ask-s5*n~I`hwW-2bIlSW);h8A z)+D2H4QnB+@WP#nBGKVtuA(iCg`Aa6gXgH|wN3&;PaFu%;zdI==!xog8if(VA}A~j zH9D059sh2G^vQOS{#4J8smiPtg4p+RH3r$~k!kiO>H3IW@o0K+@z3f%eCXY(YsCYs zY!U;{hq!ulhrAHC16>sOXk4oNX}B`wu8AxXA&y{dM;%~_>rM^@O7J8RbWt&QT1NUw zt^dLSoyiDx^BK2Gru1A)W01G0_2k_=m{E^UVVzTi9EkTz4p`CC6)}6XNuy#q2ZFqDP0Rh^m15$6rB|D}A`se9zJ0^1iTZ}QG@;%X4X0V^^ z>Zlu8&Yy}l@t|!pI?mciRFZNt5~^(EmQPyWJ?wq;0^R*WQZr@i?~jzWUt zMvF0`HyO8b|3FP99OtU9%`vmoYvEtbS#W#aou_K>IkeGl@zO<;?47LRN7aIK`H-iK zFq)s$K~SMDbmu|uB%tiHhd&jrcM7d;3nxgA@+xXnxteR`tfiXXmkNt18zcU``(KqM z(O&iqQ#i}x$5wD|+Afn;RUt0)47sM!%1Y;mZkY7W2B8f6TXou7rh<1`7%*x5(|Q<0 zo0b0rVxgYDVurx^{22LIka~2};O4zT)dup&Y=X=m-G})KI$Hg6OX@rFK`Ys^$9dZ0 ze(74rmKNS$MBh)_W?Yt+k_CFiSiRJn`HG8NNsVE-vKu_C*(DY+Cg-9(00IwdE~XR4 zQ&#FWd9S$%ei;uk6RaM%{<}dMt6GdELBSljJL+-LdzExI=QFf)>z+F0~FX zU#d5IvZ9%>{?~MfXs|wQ*4L~b63NqjitfD8my8@doZC(U;(GwhN1|-5JI7!HO{C9E z3n3S5*yO|0jW~MLT_mPM^z{RGKY&o5$|I8pCm&QWRY+Db)A-`kUSX0w`ms$71?D%j zk9GJ3;myGa>MBi|9$6<^lmdN=)7)$6@;2gspib(*c+gQeGGkbgW;{|Ggf?8y`+ws)< zqokJvLW+L*_4|b|xB)eR2-V_XOvN^iGCy)D@rR&yM7ZAa=7(v7_G_ZTGo=1Q1a)6TDkq5kycIg+i;$J&+$< z)DW4}s=VUj^=5xM^N+4OHx9R|A=vidh0@6!WuSEifa*ceMBe1s>VN^O4GYS_Rdt@hK~L`82l(6bOcRuHJL2az(@QbF!FK z-png%W!3T#&KnE$BE^~(6{90=bf!#%j2~-y&5|rOjjEDue_=&fn-kxqVcOPhy%E03 zSyR$zEzBtz0>jP?nO8w6r$$i2&k0&4b@4K}X=EZMf`3K4@f})K!AXz>OD)mM7%=-m zG`d4*A$)68&}cB%QQ6yVzKC9H58Q?7FjGF?X>kEER{AMM-!t29q1>gfu&>fX6+MV&g~8SGIUiT4@~ z5KLWqbYl8I%0`x@FwZv7^rrFqgYA+-y|5zpiql24c0=rrc+_XSuIzWc66PLDL(V?D z3Rb>V4Vlf{{xI<*(Y$=UVn_*`Wv_R=CN&8HA&Rx&s9c5Y-YtfPw%IL-!@1DFVstvu~c0qAaqMr)%No4iX9P z_CQ=Ih2@Y56YB`-w3W-u=@uP-JiL!-0DWzl69a{zV@1*FAvSy#?NqVM8&`VqaqH6} zq~)I%54G*``cYQl6k&W54p0bSRBcbo0^a>e+@C4j<&` zPm;N#>D{+?+xGb&x7+zTt4_aH+?MiExg)fWnb$eY&s)#mP$Q1{)=5Z&h0|}R3fa5c z^jQqLp6JWEZ-D)y#X|@+RmD7EOe=ml^+c|Ne5AA2eA?g~wX*ksir%;Es|MM_OwyC+ zE+XEU0ThW2`oYB%yH2m#4{@asE7aiKW$XK-(-x(}%oeg%8vE66_64_ps1yN6|EFsa z<^Klz|IlzpTt$!`AFzBT0Ymm*_?>@SNg@`8cIKw?_AWv;HumoSrIAdfq8x*W5Gr4D zuBv6D&Dp>gm|M^=(K5JM)!l+uOYegIJGoo4oEL^&2~8>5fz{YPUxe`FoGtRmh zN!r`CE`7porW^3 zB^wix$g8&P8+?tOTMBjfBAoYIT(-njZlKeIdHYEip@K5H3P(C7g>`4kh$t$W0@wBP za9vmU_d3~i91nT)*3(*7VoJDcQjzhmbL%t-v4d|lxMer_4X(XpN~%MFyALQwO!f0% zGU>~rx-~DRJDp!jSPW*N^%bDni$)X>C6*LSfHm{MMr=-{RkZP6hVqEw;r=vFna{cwh~YcHe~ zqoPX8?5XP5)^x&4f{g+79y^f`*DHU%oPpc#-1jxylD~Z?>4i`Uf%wO zznT^#8Bu+)!s{(L5nmDzK$J(ju#rPiqEA}W8c{4U+djG#HccFa4Pgn2JN0BB z^B+(ZDayzT1ETOjdDyofI2hy?)4`WOh$y%7q9W@hE%-kMXNF}^^#V~^m>qY3 zc|%}g4Q+BVS{#4>Y%5pRJ)S=uzJ&>3SEux5r1<&DR4HQpmgIkL1~bFtDbK& zr%;Z~#_)4%k@!KH@H98pC{2KabX)R4T;Pxv>TLXt*e%RNoxGXnp_GmS5 zpHd3tgATo~=pm@(^-^IiXf#}IBLLx!*7IHA@S%dw<_B#NND$k20kN}?CcN5;g?jyV z0;LjwK8yc1`c#_O&=UgYe-$tk{{;*H8=tAmKZx_6-NXRKZ-0PMsU)WW!i1XD2LhnY z(O@Qp0Q>`lV8;*gxM*zA3vj)Lwg!_mvCqhB2>@*jcwYw|3K7h|-c8nxUZVxzfPeGK*~B$RuCL?jl~ZzDbEE&cz-8gFFDDx zD;;XTqZ{Bw^VX!7vv>W`Q?!`v$Npc8*aD1y^%jp*+||zb-*54f6(<0}3Nyg&Py!M)UXaJKV4wKPP{|CJ*9lht6Hw2?7 zgSaSXaz^VcgWuZ??S)^^1m|R?|GmIz^%a5*7R>&Q2opO5bz&Y zn}5iA6UOBG1W`jiL4VI`17G_^W~4z{oPipwBSvF>6Nd~R13|&1r8{nT(V-6uhau=G zzPiC5N{?U)38r5k=Q^BvyUpfeQMg2i1sc`lKosExpAZF+60Wk}T23);=R2*~FwYK% zK$Y#IdZa~yPbctL8b?FNSzgCl6<+u9TjEw;cy3v(@~DD@ zIQs^L?h`I&nkaolsYscoe~x_9cu9{PIJKdt<;X%_2b~c{8I;|(-z8PFOl*NxJqEdg z?XJa#+mOE1um-O}T5Zfjw{}>DAF1sy2%e|@+C7#GCEgG74$pId^cd=`&n73ilnyD+ zi&R)6O!=NI&sbhH_7*_`PX2Z4sR++z#BpdJA}nxTWHbt3EJz6vm8D~D;#%q#8nrUZ zSJJ>ET-LzRcc*JIF~)`<=arK;1LNW)7P84mb3dVgbHIoon%<|FL>0&FM#OE3B%`D= zR2CJoo|y8CeTVROfHRydgb)D$j{?Sj)8G$)mA$Kzv8kw~vx}3Zk*kZn6R8ToWzxpf z#r`iK|Lwzn=T)|%jLZNd;)fZ*xn1Rj2zYe_#JvH-uIb=d1Z5JM&Wg8oe?}vDLg5EP zCnWwG@CT)%_I)8?T8~d`x=Dhkx4$PiW1m;hmkc6prd3GD_h1i1DWW^x)?et%k3rj# zmcL`6CbBoQ!r*rV(+83tXT6+h8fEBv&of~6_|ME5&=2oVKTsD7OD7WtLnjx{|6&Fz zry5`az}VDSHs-~5lA2GCsD&Cp?$^5z zY2}#5MU;?2LDH9NCea1Qb}KgDK4=xbLHGvjA(2hV%DJj9g+lN5lzsm$(+gM9DY&t!B=alyY-0Ih(;O$;Bk%Bx^` zlunIrC(EQObd8+}t}+!`NoHd+9gT_Z%6kj#;9yTjJ|ZhzFlEc7@}@HfCwU(ztV@%Y znRo;Ds?dSXBK>5G0{_-ZEtB4i-VKioN%6C;l)HGy{?Wd%z>I(g@+n>pQr?ejNi9b_ zioM0(;R*J7Ko^2&sYVQ99M>qieTM=@x1yPvpMBc8k$o+@@(&i|Y!x!a1Ln5@5axLP z$2nI03n<0EfCBKq+)m0)%>h7ze|Bb3ma|q-LgY&vIw+VT6+hKbLv6G8{)FD-rbK0t z$BLMLM`Ga4!7z8Xc<3AJeOZ2I?y3Za@e%M*vb(OK^s8*!>|mVHd6JPuV@bdtczGa_ zoI0&M4|@mx04iI>_2;f?y5*at&uBQsc=wX{D_!6E=t)T+H#Qq92`wRU#VM_u*yN(Q z{z}Vot_9CX^8MkpP(UuI&gPQJ97pIgemgHq3{>Sn-3>3+&7;V0il9VbD_UMOe4cjF zNM$dqZw|T&oodc`tu&{4u)xZm=pnBE6AY-~7Or__P%Mqar$jAxf?-pAc+@(+MSuV9 zTH1Lac5%Wne4S_!iK%PYCuw;dh{ST*Lg$8=$-#l2`f`xFi-GlLxi1R6ND0Eq^Tzly zb_(QJvl5Z0uko^7@hDfy4tIT>2iaKlFA-J2otkfyI{9~`xsD1p8u<)6Uk94K1q3^b zWaho985|$;Z8{m6BFmPWjh3c+xV1e(Ard@2=R(AI!K6`5N<@Vyr}?S1@b{#Sb9d99 ze$LN>3rOs7{GEq~`q1%jxIjR*>OepMcisPihyNea{}7vd`k?-P{^U=bm^5=|hk}Bk zrLMvx+$W4cWCdo`L6!xAG61E9Um?<3IkR);khHO_9o%eO)cYp!wXA3nh87M=wPsM7 z&(ivA|7|B&?`|w5VOLWwP7XO z%ac5f^D;B5W+UVwlS5~*juNHCmcneLzJVI6JHS5oqQsp;2|vxhHb}1R91J59;q9<0 zZK-XEeq>V@PjuaJxZ@^iVRh9PBQkW#Xkfz|cWyV_0=Ni^32O}oNflx7R@g>LR0Ec7 zw00gD0eMMUi6Qb^kj${;Vi1dVbYY=?C(>a%T^r#@S9@(kE?9kYg7QiVQ!DlLLrtTg z+GjF@y<}-?*u*_7ocS)TFj8zJtLRq|I@kE^T?=pTu!*~HKXNwF^XPOk_)(uP<(8Au zi$nL4!uCjS(m7UqoH)Q}hZboO&QKa1QpBd_3Zz=3Sg$Z>F-RzYmt7L5_7T>;h~5?{ zo`^=bhuJ2Y{X8%h)+8hHyZ{K7Bg0qx-c+H0jS1Eyl#GyqYuInHzpEpb293^I$MW&w zWxFd$b$D$K=r0*}v#}<11F3h`4^6q;Gk|y2>m36lxUdiMGZZr#&HHp(gf3ZkOLh0b zdnY!nX+C%TGE(B56-nf^+QXPIptTu@kBUfOT0}O+#(5gC4~&6(6GzGGIVMPWU-i?a z*`s(plC(o5l4vOB7-G$sNzdBrPFd7?|U9}8YK~`*muwK zM88!tFLt4hsm)t!29CXCYUDOzv(Q88u`|eY#M3GD4S#8>Q7W}|c*Qk%*|ji{DM_Yl zf{pGo(m*~DH)pen_tcCYQ>&Pn4r&a7k&s3Y-6^z1D6I8!R!b~0gpiYR0xy0J5f={%Y5NOW=4QOjSL22tBXPCt4V_`<;K90O?U|p%g*)0d zcse%4x=}x*Mp&^&nk*Q5ztlNm4zLv#Gpn6%-yts`_OHE0T3D1jTq&3V-H4GC6p>bf zk<$bgM!>_ZIpFOi%+O0J3d@=)`O`QSpe&Vpgoe29hVYX`a0SSq?wx17p=gPl^~R** zfR$b@)`(0Wqoybm(1Psnfyj5c6Yg`GVVQ(cInBxGHsvq`>&3h%UEs>W|1GwxMXP_<0cMFsi^iQ)WJh#?`+8(2 z7)raj$`2}0o!go4d@Dks_aVuRC`#i{z4)2=N+L&%hDmu@BGbyg|1e&A# zogK;74ap1^DsNopviH12WH1~1qR$-a-k2P@i8sDrX%MnHiuo}LybgQ4TLab#p%~$( zUCdc7#_U%A&QF*ABlOHxy}^wjLrn|p1KQ)TV6kiCs+92MZb?m^GPJo}WNULlN`kJ! z9X+3Pm_(DLvEYUWNSH5=4g4>cEV_LcuWY;l5ONywFf^bmv9hjIhibL<4r5cAMEy4` z==F44-$SozLaMich~Xy(wfAV5quv40mbW%5Q1Hapj9O>jxTAnb5W2DCG|N>l1FdVtG9F*9Xo2*mltMofvRNj%{jTrVYsQpTJ z&}t>!lKn@+aYq;FtM%(m=1$t!tTV+ZeboA^#o~|Q$e0IChU~9-(7KndAl~AMw6#Se z^Ji-EB$iv6k9XjW7trC2i*T3&a{(S3(-8vJPcR>&+ofc74t-?nj$VYR8Lwu**S+T- zmms%q-)pQa%#HfFfnQU@U_Qy$*`b~Jd?_)DfJDK(b^HAt5*W$WX6jhpbDDu5Vu1zh zEsn_OF(@8}>2F-JudbQQO&T!8$bxG{u(jp~_q@zO=Pt>sqIr{K#;r{5s|DjOi^CiFFAfFM9o78peUKGcelmm^BA~wRZ zLz+LD95Sumg;hd_tbA|AHt)x;dhM5_LViki?)-%A&tttWIwi3VxG>-`W9p%xwwF)SFpj4aQjG~ zJ1F`KW^pSpe=_ArqciWj%(h}DPqQ`!DjHdO%i_*CSa$Y`u|Sfg*FiTNAvr>}tU!u# z_w5L>;xi6-Rsh6E7YP^vvkQ}3>h18u>H1H z=w)j@?l5E1*U`W}=l*4}Xt26SYHoffx4^ zD3*Cy9L684chQoKxKR!RVq;K;zidIX>AZ05_M);y9e1{yX5g*Txke;`|2N=ixJ^HP?6CNyg!E zHbh77F&D6ye!-O&A;vs1RN-+l%KkC^;6}8{hRrskoucvJ(U%UVv#!=CUQlURP{xK7 zty;N_AfrRCJ%uYHyne@SrEd9M90z{`Rph0S*NR9(q0M?|T= z1{hPE%yn=P@|m0g(Iyqqq7rc?p?$4lyonFQx-dtiP}Sm6T@0u%vnOhnIM(EHqq{XZ zlC+B`BT+})*(hnP3cp=vo)$%;bUAkNPobo77@p+eDQuZ_?&{-{TC+#-b?@kfHxG^; zCP#xdC(XLQ8^F$a44%1KW6N6?M|#NqD6OjPZBoPN3^i_LTlyYqQ7q0e^9qUfJ^RAu zK*d5w`-FID5e5py4r3rLqv1AMr7qpBIV`IXCJC^3VN~7g; z?KwQWa&bAdF4{Y#n|aFBnGw$*L#-8@k5X#nn;B^E3XG*fer2r~Kct@(88^^kygghq zes8VKPx)?gphVB0o0TVUI-e~uakRzL-91RRUvtY4Z&*<`d65n=}EuxUYx%a(<_ zlDDifvc|?=ElCriLpY%ku{@MmGh|8kyvDuCJ9&mk7sLZL&9W!JvF(Pb? z%gV6}Z9GFJNm**Mj(E@0H5ANqioYg~!=;?Y8!SsEv5FRacYvK#d!>mf@{PfvH!b^#O1<|~CaNPhm*i6vP;C}gt}LgN zgc}2M-=VByMj~QCzb2|yWONm}DB^7^t7e9D5M@I3lg68=>DRc%jqdT`=(8_MZP_@H zEKb5kNrcC)=SyA`#&|C$1U)5Ci9-Ts$eucrzhBc9+tMAcl<^!;HnYc?6y}TVfSsG^*{j~c!bv}%sm0aU zakAMhM;}>1Z}IHNY%7JJj-}5RaY_3M!x;S9HAL6DMndYhPNyw#tV9)AKKK<`xk6)Z zsWq8Idly`Z(`1W7bGmiCJwE2zOWsTN7jkQ-?2od(;#m$gmyA!zVEB(KuPS9Frv&@_ zeb@aN$5JqGG|B?|@Qq_A6}r2^QZTOwH8R#G!bk4s_J#_a!=-)vkm~fXw^j%yg`Mb_W#iG?(Dd^T9g4@UMBAM&BjdtU4waSMur8_Ta$`08jQX8M$C@MgF(YCU!+`8WIFYcS%z`VGE;`x>0)iQ@z)x7 ze_D>%ZpbpPfz?~vb@r@hd`!@IJ%7|Xp>Nk=7jJput{Nt>)$4QApy2hCfoeA6Z1!u; z>NH#vR&i%MXsWdwK?L9GIs+^Kb^Mm%Pdc)!ny1dk*1o50{dS(Jx_;t=K=i}K`TYny zK+{&^i-BD+=B_q62HrO^_Ld{k!t{IJPXWX2VsIRLqPmDrckIqZ&f`U%k@hTb*^ zt>CY3`p7@k_IamvS_3LcMzlTs zleS!SY+TWYvYOSV$gmW$!}`t6GHDgcKrqABSHXB`~;)7*QNLN9ruai_Z}NJX!V@S9kXp@ zNeue5kzHkBtg)?KMpQwPD zc}q?!>1wCM94$d+sEDN^Sz)&20?MoA8rJ>{~(+}SCQ&IMIr zf#THVWMN(!O7j}F;;f{vDTz*FPRe##^3qacQrW0GHJ{9{qsFtYk@9_|yAOJkBUQBa zdv+f#kPNSV$Y_rpUqWZctZv{LWBl@ViO?#kJ)d*0wh~MBPnY8R{YoQjas_^|Hhj&| z?`OH8u&eQHPX%X2v~^;xC(qg(l!Y$F2;T<#ur*?SOY#P{YfbRI;4%Hp_oN4pO%ua% z`%1lBs;-V->b9?X_~PUn!`D(Xo67T*G?MZF5y#$2=yY8tKh2yBF5Db(7+&Fvbh0agyQ3sN6UMD*+*`d^(i8IUlm ziGjrS41H3sMF)l6Dm!pKWhqRXS<~e@F4z)=0=S07!S?7zost`B>&v+N{{pwk$?Xbh<&`tU3%>_r>!sAA`LQty;055Gv1r^`1z> za678zYUuScQDv?`vfwnL_0`*$Q7pg8)od%!4@osSGrRxXn!WUZ3*_FFtsQcA%aDE8 z_FrCxM`0r<%t}UMgYVvxbhVRjoIAYxeIb;%wc|X=4(un!ZUn~(B%Mh8!&e# z%#st&knh?NA3Li~U;sD>HI`bqBuSP(Su=-5FlVYkf%#S(^pa@`c_+6kobHkvz^@8p&^7rHq zGpv!Z3hEu#`_R&W1(vG3irU>@eOXqaQ;v)Ht7<2pI zT@GecAGI`|oZBr&qiQ*e2Wl*t2FNQ(4EyevYE8DA+M3fdG+nZ$%^Ro{% zO0y}GKCX|eOJEFBpg+b12j@bD7QEMzdp3=XkKsWBA)$ ze@bS?d2YoGd5mrlcdWrVH5)K>t|e3su0^MJY@{`=MZbHtJ0fmA)Wgx{6W=I7(B3wuXen|S~!PD#z^wBoOdvn?QrlY)rW=#?d;I767-!V>|?SgGamzO}CyFl_tzG*)q zt&cF>0dtQEy(sx)jul}XV}1z6A{}KWNo?)D=N#bW7DC;jX3I?}g7b~3yM3d2g16l- z=}68gIBjWJ6%sIs@Dt=Y_I#n-8~=JV+>lxOTlbEoE!WWqp;J_0dh8D5{afB3jqIa+ z+KmPm7*(Folxh(qelBBy^iSv)Ms23)Jnp$ za@&?CB`xuX*zf=I)?SByogBMgT^5>^$U&GzWm1n zcum8JiHe%H7@{Oig$QICeN`^LJ3sJ-OFGKNtWsrbjtg|}cPk7#^ez4KJOq@1A-+2% zqq646MT+q9s7Y+OnhW=bXUvGVb>#c{LhoKhPMjq|x z@s-HEJR(a!9Ccknw+0EEPV@UIE%{*?hM#y4W_2ap(U~iCy}jLEoQvWH^>ST_MOfJf zI+H;wfyB5989Q8^EYz8DMMFmWP4|1LFTM^tx`LwR2M;+i62!9K?2tFbcv}!(>~X## zKDV}A=Veazk+6afb8-2`N?(D<_2cNEaa(-xEQ$K5#5G~~nk!M?@=%tq=7O&~@h)^> zTV(Yxgzbf~AT6`mPgM&$S3!MB-aeWU5{U}&7;U_ok*qUBt=ADq7m;YImY70DP5mpS zq)xI)d0`H|-D{PoHdds`#C(MO-UkTiHi)waEYTxn!60Np4Qh~ zy5=1Eo>cavA>KYi$(*_B3|UvzRd_HC9_%%POl>v8_9Lu84>T` z#!A!&Eh<`--OEzWCI0MWGIV~1k5hyYWc}@<8VHouApw-fZQQfp@%lzEolD|(WW-i8 z^>ryF4cFF1qRlso`d0E%cot2ClO}VtfU_7adQSac{yn~y9ONnK5J3{Sxxt5w8^e=# zJxT|bKCK4w>n85?L;K@R&$I$;xv5626UAWCn$aUIS%$aR;=Xce!&|qt=&qWfrcouS zd(iWHw(QmSSRGaIp^tHNy^Wgwa$l;clSa<9#`}s;S7L<^eo^(b5X0jjT!fq~!$?eO z9Lc;BM26ivflJ)qTo$9o+Wq+F8l&o|VPO2t3 z`cZ07CsP+cZ6&WJ-=@_XRcK5eUP^!DOgu6u9#G$If9n7J4HfWg`k%E7fTz?y-%uU? zqamq{sk8Hcsb;vx%fpcbytI=7UfTbgdXv9>_a7B>fBg`txTUF$iJF6nq04`?K*?76 z_ezGffuB;wU&|{AIC@3C_UaTuqcH^ME0!rv_^#2qTFXki8T+R~An`tdzA5dJLZf$t zrLK6g@LWy|zYd?`1mHj5q8ey2>Q?0aQp~)SxCrLW@YG0bO|uHwO^+(%H)qtg-4cgt z0v{H`qVKPb;!CC%&ENAzqO$Kz)*g~Vgf8khMRQ<`BZjrZ0ABi*hZ5CJZ`i=2?6TvdHBVaxYpEK)nJD+jG>%DsO;OT%uI*e4n{|B6wk1;UZ^q;QG~K z-yVg>Gi8X_ALD`I@7V6w&Pc8S@X-PEZTMI45jAzQG&Yqo`AaqXAN4>9atf$Ih#^}U z@0jkmZW`$7s)|bUhEiEDJ9R_-`JxHY83Eoq^E_)>*uz-N1el*_q~C_V3-l0}R$O4s zK1ot$U9T;Hy_1NnNu)2iC2>dq@tG$0JbUx^^ey>sk=VwnWJu%MNmapmn+ z#^CCR^oQ*2a<6aQ@T%kqAeOFcQjE)*KF~wyiwp6_LmAF|#rr7Nrs{scW1Xp+adQN% z4+<_13i`=9W7m;LMxBA&wz>I(6sKLRbE^mUz}7`~eE`HzchE0`V3ag+YmD+9K`g^B zDp87ZZr=Y@<3Wp?VIRa3Qc-_VtR)N$3GLb^#nBM0HQH%7Fj%zYof;bgmsD403LWTK!Q z)w#uSnVrJDT+W~y>WB%8NwS?yYB$&zuVrLv7(to&gi`Pxw%xU7W3{kaipAB<; z@RzNRewWGgnCtR%gsU2p7jg)z&Z#5Z+`W^Z%S@Qfh)I|*GzUIG!(T>dMio524gFCg z*uGXBeFRuC-vLWT=zj?Zf1@;@N*Tar5qp!riUj|ugr-t?&2CN*kuTFVOJ?J%v`l}c z;%Y*g3_oFy%nwo`DIXoh7Aj6+PFAAv85k*g9a@oRTp-p@}MJ2Ot;D*+*|h0XB`pY^qA-KQ0V` za|AVt@mMm;XceYZjkxD}(iAMw2S?K;+i7~`8B~O-yetbhX;|b?3cxxfb&nL`^8F0t z!LSPiCSq@7DTa0Jn)eOUy<3U+@I`4Wy)0@}J864%;Sf$~=_jK`zl^w~ICFHh9vja6 z9OmX~N458CiRJ-;Y30}G-Vg&ZqP(hg-T3vX9CBV8(2oz6BEhW$QGXjg$`6*Pc?~wp z7&a{QlPf1u-Y-A8Cvrw$yp2XtK-yfZG3ogf@^gQAntgA(8^gcqGa3^Bq2xdRV>Oi8%pqC}FcJxJNxd3y)mik_E1<%(g)GQaN! zMb!Exv8;m0jb122z7lUrM$P=@*=MviLzymuE2|Ex@3=uqjRQ)YV(KDK*dt11K2+WU z{~6l2+dA)7r6In%ZiCNyKZv2sV4##jk^C+SR{FLo<}KCYo+36%M?3nRnoHdeE7HG zc>L&oicKmHxDPa%Cf>OP{L)REXK!Gxe!adApmSSHj|;>EHI*zNKAfif8DD&89p^{A zYogG4yb!L#u)^=vVGeqR1dDFrob{`Y%Gg(GSpDEBEpRKe+GF58ymZRz&rs&+|0wqU z=NyCLe+eq49>%5)E|!3LZn#Wp_BC|KcuFiNtIXH?ybhfL>2s25yy4^&hI^A!+&e~+;yyh0^2 zGxKr!#mRBT$x`eg29TUhN}Nzrm=2aFD&)O2-yHS)Ofq)|jK@iIG-Pl}c$5I42~PWW z;uJ_c<7AnixZd9V@hU3k>y^LQQG5Cyd#7Z#;Z49x<(rNmbDaXVk`@ok(m8c+Y zycK>7&nqL*78>EH_1>fcpOzE_-p@B)L;aoI@13+tUH}j@0POy66k&hM`~C?}DLZ3( zCnr zFl3MBL+t9s@BoGg8VH2Af`#V=oxw+U-0LgS-MFX%^wr$V%>llv(Gi6{=4B=`i8oH` z+}2okYV|iem&Zl2!g_*jg*%q%9Er1Y4*{3Wi<77gvP{B!Ffl0QM2&~153a6=qjAL( zHP)as4Y!9kUFkv*jd!>c4FC1euW1iqd@WPxHCjY7^fN<;=8G9ltrHYlLBr8?_?^*Z zv9XNxPVmoK>8yly5hSWz>}4KS?;gf_xzUVQuU{LQCJOrYc3T&#?yxIOw(cRYSp9>F z$1?i3Lz|MK!#?}UMVkn?yL$uD&l*exQ)J=ikDrHQd?C9^h!Qc&i6MUHu^+B`aO##M zR5$d|U&=}XH>I_p>w`uR``GH~xh)|~PH+c3N9ChtT-4iQ4K@xyZ#EV|%`Osmz$08u zDCw1Tgadz#Stx(0@QG1s1Sb9Us|E}!4|T_^Z}#O6&c0#Y)?WcgmIJ_9>HkhPMGJd7 zQ+ZcgBS6XW|0ewtlJ93k#BH_&$x{^7?}>KmL7)^CM(aeRCRMz&t0VqmEa4{nj7~uc z&l?~?#2zS(%Vi*AJY`{KJ28B?#uGsGpmH~@fC$GVNXyuzp&*TIdbvO7EU#9XjTWZR zxNx5?_-RomTSMkhnhh%_*5!s^^1VV8()tiagh+Cg^m_+=c9Ga^3$Xx=CpDdpuU*tv zm1}E1R%dXiDCSP4t+Hgp--OPs%rd`S$NUFM=v@(8MnA3*R08jY*!YWE?Y7e1$XsSq zf<;Kz4co!&?cyCX)6X~=IsJk?c4QE!jMu}B&lusm;e0OKt~)2$Uj7jC$%A7}d;B%E z01EvJu}sXv0nkX&3IW&9d?+a7WPQ0bXrTR%1U28jz9GOVo*a(yaeO^iO$2iTdC(Z$4|{4WMo1y`Q|7=$BA0$@<2 zeF(7*B?;3B4HB0h=BXPaayO~;p-!8TN!y6|U=J=yqoUx*y`dG*xfju%fGYTKXaY}Hoa|+^$To@Arq?iEQUm_J9$2e z-+^-REKJD2o@miax#h8pl+nhfzfQ|woB;f;1!dc8PxEKiCR3F`q#lV_RZ(pS^5~Dx zLh4}Z-zMO{O>b3QX5U99#Uf->v#}$OY1s*SGa9MtChn;Y2oipqMxbe;YEV`YOI(4^ zmw>PfES0o>Ax)1rlrjraPFg}9efdKoQ1Q}pt^&Xp9e}Us|D8USX$!v_jp_>DM>cXtTSz=&fAE(8+^8P4&$n?Mqh#A%X?HbM6e!xcDvR5*-4 z&y>TCIi`KNILhAWM7FotWR^3*lzprM&}vDhBw>1Bya=>q$ShX(e#+Rwz=0>Yip8qdI8kO`dOxodEZ(secNVn3)nxGOVv@D*?`LBT3)^r8INNh4x+Zq- z(d!{)Ill;NhC94oMO>du1khYf+mLVQUHesZK5}P!?|fg}dg3k<%zD%eoHqe^Hzy^R;1sFpAchCR_9v6U! zKpY@&{u8l(J>_l;Ff_KaihU)szX zi60_;MiTPhV90gT(?m*O&^`f#8G@qI5nqt4%=ZxcjU=oYVyzv5 zvjU*QzkxcyLdOI?)dz@5h_F0F@yAwpzI*rqPY-#afun*ZSy2`ULo)o1Mh$1h*uA1x zSE;TfQhjKc@yk`Tu|)u(MQmGzPL)$TIZtMmmo0K`X2&BN<;wHZ~lvXp|a69`HD9fyd-@PHwF{RBSTxu*4p%~ksM6q(O zSY+;$<}e#&GtQT=w(K8DCNLA@fcie=z7}*+Ix(y1df(x9is0woMb3_N^$W-lG_vHT zb)YH$d&`2K=M@E+E>3aqsY$<~O zYqv)KIJ>DB{zDL$t!k?Ri!MY=I$#MVbb@7SbTRZVIDCok}w%bN=&hv3coQH*~XG~AJ1?hJBLfM zYNeWf#_SO*|F&_6#f~V{A<`(Cv>wua69I{n(hC{bJ*>(##}#MT^7tueRV7&{=_npS6o?Pbqeu zh)s$)hdu%KcRYN`U~rCak@K!Zrtj1hFFoj;PW*6hO$@*cYk)G7H1(RLPCZXi**gsh z%Q_DAQW8bZfE(MDA0cJ9o-CZ)mgx;PN+IVJ?FJIRjLBh^0wta6nJY_Zl2BV}T0qTeS{7oOFQO4RXF9%+NW*Wm&( zHOBE-kw?sn$MYQ1d5cPTKh`+I%Eg-O$Rugeq*(nMHSJ#fr9kkohw!V)3$Z~ave=;% zd?K6N7IY4{E)*%T(GeJjQ!Wu6k@vDC&2A?78?y8oodreEw{HGnnAJqmBFJTGw$y^o zu;o84y>?%Jv-tu-4=3Q#>%URu{p~#Sw@a^oKU)43g8n+m{KHvLc|;mi2+=n?y&^_y z-5r(Jq`x%+35_@(QB{$6*OJ*XF=j7HR$+HT;06&4!7qS-OkIIDC=3x6PPZC=<5ErO z{`vj`_{XBr`y-*XHPniY@hZ&%;UTu8Ds9KVhN_3uQElco+QBZM(( zA#W|}utVvmPTePnrQ}Ii*F)&hOu{l)Lh^JNd081$V&W9iSdDTkm8+T&>AN_Zweu1k znis||Y;W8$kFYL+G=fVJl2&Aejm!sOTev>`yyyXlR5<9wriCULBFSX5p^o6zs492l zLLPV{_J7~dE9okyaj1ZR?xKKz=>L}u{hxvT50RKJl#lAd{T0`v+zoSLYBH$_;V3#$ zP!OT;G*~aNpeQ+^l%mw^rfo+Z)QEtIrhe1iaBy(>0$--f$99*D z>^xRKzMKGx1-dQrj>&;-Ae*El6!^_X=XWiK@y$Wu5>zD@3Bi>~;ugSs=%=uS58S*K z!4{=WlG!CgiKtC1;-Iu3`}ZBX>#J4{p>1f(c0!!v`&BM&MHTrb;R2-*W~@ib_chpl zE%w0oLz-=9hgOe!DBFF<O#_3ocb~5>9?>#g7-zW2*taVmLRqJ`G)_q;|dr4}3m1lcq76Ji! z!|V94x+sRGba17Tn)wFm?YCF_Xn%Fi6v>3|R!ayUPtD1mO z>HMXxSK<1EgAs}4?2&`}PmV}J>9-0w2iw%iw3%-omRkT(X=k)(#1}z*l}`XvD|qT$ z_XM_vqhP}b|EQTlUfAhKrr*ffkaHO(0YvltE0jOnXd*1hX9#~)G_4q|V}5Pj zh-bN~K{Qy+h+?P(lpAVi8EutYK#}MdiMo`j6R|@$Nit?Sz#6wEXbFYAeae+msM5KE zh7#+M_O1*w%0K!rkdTngxEd38D zLc{LQENBNS^}*smtSsqqs1?KrIDw2A_d3@V9v<9t<13(uccd6vyiYp1PucPQ(g0G9 z@GG?^3BJ&HM?0dib3g@bc=g7Ng2T4~6@GJ0FFc@@#?D?iLr8kur70u$d?UDAvpr<; z4cf-ui7}^q=oMEFL`XPeSt}DVZ~?u}VVNS@PG?uyY!NT@gm%nxqnXW2^PU0T1d13mE(;qgaOKr^@$1Jtvnt6T{dKKH-nwR ziJbrp(a@H)q(pewL=ITqRyfq90=6WJv8 zGi2#A8<`Qi1{3OhTfGCP(8Z|PC-PK2-lKQSu12pxmBkNlXub|Cd*#w)jn9v>0|TAW z*S~6zvm@EYsL8rFEO&2ve1&3&B}}$Rv#V4jYl%PErOpYGL_`Q=t7YA`x^7b01Fip|M7e z7}A#T$mtH#n;wZ4_Epw{6EIbgY08QgfG%Wu9^8>aN9ROvb@&`mde=p)`E!kplgH(o zC1TI}N>;ac?tAD(GuMSb{@OGk<%ufeKRxOHN#BRpALM+Gft(w1J&`%(!l@<69u?y| zAA)CfTy*|Y4Bl%l3(MkYb*f!EB!6zr`j-tGFuxB$>1U?C*bVP56Pymj2pQ&$ikgWX zg`J1mOU5X`_y-eg3@O&xfba=jUwPXF84x5ycH{=CNmYGZWVB$HIxFK?FEpOW_Eu#Y z0g}(wa@k0F>#J6<&mjks?fP%kX@Yt^!Of+ z$mbYH)u~a?2NQx|a6mF2OVL_Q>oH^dUB_sx2c9$^?lb4C7r97ADicV?Ft0WdQkoV##g;LjIP1rC*R| z0=*#s9YKvJWrT0e#=YYoZ5i40wdt$rY*w}Yr=(QDtU8VNl;?MDO-9kMc=4Td1kmM!uXy&iZXg|dG{7IO_-zgkW zzAd)g@Zn2#3CC5|&_gsV81qv6huu^nw1gr{)R3OZ0yqubL5P_t7_LqsVPwweqOb_W z!3Y9m{nn(!%L?o<=iMS&c2+L0IZtvQiuo}bHTk!ssDu(M2FIS$_qdHBfgC zJL8@>yW^Z5@h7aL)C0lk3wmOFRgDVgR?cqikDdXY2VX@o&g@XDj4rF4SveBsg=Mpt z^^%9;^s3HDVN!=j>mmhOMm>2mK9DQ313Heq`9sbD5!JB$vg!(hm9&_)+z9a!8N9^Q z>LTxER7^|9V?|~v5?;qcQ5vbuFkUc8I|^&Jo|3!v#l7F5&VQli92`@a^jZ2EDrx}! z7*jDG5dbS^%5a&}>dc919ng+5ovbT6nnuWynQHx3Jg3^EH0;6C+(;WMse}I(ivigG z<^oKjNNvWENDW)z7HVmpn*Y+0+kY_O9o|J)fjo3745KC?-*}AJ=7(UU@c8&4~E^HzH!_hI&L!ssMjoAU%?CP)YZ9AyPY=l~T$Q%$IU# zNJNd!9DP?z7Ai27-&G)D^=o391F8dHKDjA8LWwXZ8U=SQXj$x$%jWXyqD8E$Yh)~* zxxkq1a4v3us8c-jf@6Ci*bXRyzdyTri_iFtR-GwALCl#l?3J=W0Cs0zsTMo-xGxkDLbpBL=|(HOl4&iSVP1G^pP?FNor&a}4#;7&xOUL37UjhMVVJwCm~VksZ#z4Qn@u z%zB7j?#g!#2iyDx6ONCRgF|C6hpASEB&+{Y{!4S#rc&Zz5)E=E8(@aE3zzIG=ZS;N3E zbW3U0%r1rvO)?$BZ~#lgR|b&ogpX--q-gg&XYXkGV}+1$*feDvtISNLr0=`@)-SgZ7?5B^V% z)W_WhilV9o0t=}4BdJSJltRUrV@ z0_1zCi@LGSh`ND#K-@tY|EZwprk8R~bQJ+f#TBq}>-a*_S@0Vr@%$fp@{cNK$Z-w7 znEZ;D%CLa30A)h-yA*s7oR~S0q02u#w6XqRw@e@MBLTKAJpL%a9$K;bDqDij*I5M6 z&g{ORy%$-)*Z$hkoIn%by>{!MIB8!P6#EBezZmusplz@!0wI47i5B##J1{dLDfofg z`~uldIKc(n3d{?QtuRi%a_aCD{b7uAdya$)N=v1b>42j!3Oob|q^F8)#L0kw%) z*c6f%<-%^S61Zp4;UsXu`Y~pGt{l!O*wG0~PnQ z;FgF3v1V6)&@Ubhv=RQDrWuEVi(w&6w6VJadOu_|3}B#94)w~gn=}8&;cu>?1yXz> zpWJdRq1=e6VR?ld;z`lC+C|4r4wqFa+?_eZ_kxC?ZD4a}_O|KXQMqF(Yov$ zZ}^r*K$2^*bp~HVhIWOXk@tS&J&Ov`$TEHS-cSD0AfZX}Ul|cZy%F+TD`$Ft?zDzrlq&VT)ZginolrK1KH4V9jLWb&spNW1L zJ-sLaw4_Gr+(?Fk#WMfDrixAi@UgyVJ$Jkp-8CKdNbH zA73IdMfD8{Ziodli-}&4R;bWUUNPzK8^U7Qslw}`wBsS zCN?^msxcGh;a`QG|43heZ^Hc~{nm(&9zrWyiNCxCJecN)Br>k;zS~IZ3xbJS5uSAi zm%Oe5s7@p9DUd|NUe)S(1Raa}@Y41XbJqjDeC{=t3M7n|$ns}@?G+oGT?Bo8;7g$Z6Nh6#!+0);?TVT`@<}ugjn0eQb8@9{Uk7lh32^Uj@@+T9smSHB!zkem zYX4Bef8lt!tECfTe4;ZHvrM*Iq%?5K8K5ehPH8x&PfD#mykEkmPo`8Dlm*&C5(3^U z;kN87ljkOImDSx6O(+;t1WFkx%e*^jSji-Y<8)sx3jJZx&I5 z`P%%RsZf3+FUnKh1`n{}tNg`zR}OtNT}r|)VzR?9gE&vhHD_%1Npo#$7RgWCpgic} z$31XoudDMo3d6c{EiW8@|MD65dj9(xs^4_cFF6sue}lVD`OwM@m&)gA79j-*wbX?! zl4#bWjw_D>+*UoL@I;F&PChmQlGr3fb*xo$qrgjbUH>1SEGN+)OI& zZ3h}_4LuRi`Gm&vb|M!lG7JBk{zPu}A>Bo$#>cxgQMux3pw%!nRBvBCQ6>Sdy?F8_ z`S5zV7ZM)Qo#7|_KjI4GdAf);T*v16S)Q?x=pgG860}SM+2>Qfe}OhsQZn}{HzO}1 z?by01>VdQO-akHn&n=kwX6B$WecGS}pb=FakSX}m3yNS9q@_$X$2Fj9$V?S+;SXa* znvpH6SoQhL=5S*(okO4S^2;kYq^|i`xT@Vu37|0Do1702{-O2>@bG;uKSqbO8F!LX z8`+y^CkXk$w_`~o^PbPCJkrm8SLR8l@qq_C2oAJLaF;go8#ME)jN}^j?|Mht^Q(zf+a%A$?K&R?i-ks(VK99(0PW3_lzoRYqYHl2Zl< zSR{ZmBot-aZ(}*Cm`~9hAaoGXmSCV_d`yfeurd zIy$n%!PJi+nL2zG=G+=#6~6^@_&|k@zR6&(+z|X;zU*S@9B$^oeKbIR$eT&b(&yGN zzt;&~o8(=+FI|z=ahfE2Kq6OzUXJ=M6{L_8R$L*$Jwofud;4|&BE?P~>-9kggDREh zq%4Kv67wpbgPo_t}pUA4)Se3!r#3Edv55l5#V7oS<1&?yAY;F$*j_+Q#uuQyF_peBx zKpjF_D*5_~Z<3-YT+SveVqcwgINM>MuH_YVqr;7Fu7pP%zLk(HpHyZ?u?qi=XL*dBE?9%CPKP_||4D%8`*WXxTf2BudlsiQZ! zYXUV*jDa0*?P>HEnO$K1p#JjQ8kBK$kD%z>Fa2C3vNK192}g+A`gn_>N@i5WU|**M z11*4h1(h@cGt}yUZ`bPYXvusLvR?K-^iDzf*I@JFYfmA^dfHIgXI`lgXIbSd8()YAx8 z8UN&c9p^mm{2qSYHE(HtkWAVXOMH-Q*lR`XADS4Wl?PxL>`MX1kIED(#k7ncxb%BN zDzrJ(Lmau}qr-WI`y_9+^LDwVL#whFs{sm}lP5VM@)8+rC|p0Y+${Ko(Tm$6qh+|d zq>S=1*MfEu6d_%vWV(TxUAqRQ`1#4W{RoR2>o-=R`3zg+PClpQXgWZ`zZhKt@X3Pv zxJfsy?A2|;oP&50YtBx=%PZ1Bxrmu2$r)mvsKjfx|LJA>vJQl)(B+{E=USmvI5% znCU&_ez%VLh9covLTW-tZ3Cec0$Gc-D}nf?K+xU!aDzqz>qsfV!Q3Ax7y%%-|0{CO znzl2qBX{jX)$&Wj6|Q$z_P~KGwJkZ#Jz33PJ&9cc3Lo|P1zD!kB?;Qwwj;+Mwl|rY z5lai(fd}PQcRYd9UjcmQ`&_DPYs3xAw3uoN;C4gVR~S*ksw z&Ng8ded(O}=O`&8$oFqm|LSYgCWYl+@a|M?+fb@>d)0}&{C3?8g=jNZb{g1yn4xEK zlpd_GA;BzJr9pU4NK9oPm=8|$Obg*GGk`3ar1a{`&M^rJJr}7O{&I*ov(kt1M}#;t z`sA>MGlg;QWJII)+)9|TWYC$J$vRO3l2dh%MF7-42^S5+g(E~6Sv|TdGdMYUJauQ0 z#q)b%MblSPk-=GVQQl2eMSSG2lDD$@Pa^cErjEf|a6WU3k-UQNZ3XLKtZpht9;Xob z-51=<9zTWszQWxbYJoF$3P-WnBl{2hab1nTf5+!6Sy~ z;YJ)OQ^qpmIGPidU?CQ4Hyn7%^UL)^axvsYph*$ATbLlT8KO*K46#fSmDz=4a0R(Q zaS8(^lVTEkj64~x40>25y{81#RGNebV7%O*lq#W0KUSJ}B@cBR9MP}7Z3u6vM)D%p zBy)%1sO0`%l9`VSov(V5nbLSWRqIgs)vZ#NG|k_X-UMqU>%ci(U^Zt6ef7he?hwwA zLq|(mp2@ai4AVjz;1X>1ySBs0v4@q0x+n#i&kCq<--x%8ujJV&wd#KB%??koV0Br% z=we4&Hgma4f=^h)CgoeG%zwhU8$$~7S-^S1*U`vOmcVyG#QWk)?SW_635FH(#q@1E z8P+X8WsCVWWWMn>kV9l9VB&XU&03mm+l}3cyJrd1lJDg8m3v00<|Jk&@|fW*Z!WR1 zQKp-1vgtN7kWb~cmU~vH=HzDP^0?ulZ#ACw8{l(-z5#hgf;7-bBBbPq7pp>8?w0vc>lcL~%ey)J?_rInGM-By z%^xQH&vUsEPiu9P&Hcc7*=1ShlEzq%uhBtDMyFy?dTZB<(o|q#K*vFeL1wA3j78G} z+O{e%Ap}!0!$LA+6#A;NB)T_&tX?G74uZkZy`?a{9%J-(!cou<+Rgbnayoq(N~Bv9 zN+Z&f2)L2l1BWig19N;k<~rNsCjovj-%2xTF%~Hge@~PIGC*O038$46l@LF^Sq zK4`KNxjaR>R#34qWnAC}HzF-f0psvp13cA$Ed6f{H~RrmZX~K93T~J>z=RVS;g9xx z^$xUXAYl&V^Wu{TKc@SE^L$|GExTg4mNR8fFctC&hWPWb_^Ve&$f+r(!SCW*QTl;s zCt8Ogv|H4g;i(4LfDkw*HpU@3X9$B(#vVwfQQJGj?V#>E-phfDJD48OXVpE|$4iJF z7{k%p7slP7Jz%W~_bw$R@Tv&=2ASeTjpavsBC086@xDGKS<{EnoLv2U1b zn5)B3W-vYl{)0;9&2%I#pSts|Vs6P!j?-EO2m|z_^>xdcjn2gVzx417nb7!nG@X7=aR_w9I;d z)*AC7&Oj!pG8Lh;oOCDCb{%Zf3>_gLYq08=PiG?&q@YPdHT2 z04?ATjBgb>UnP2s>T?*B7}e1etYWB~2vdUBrSj((jSrsOIA~anD$A*ERn8f!nKad@ zFUuY)@{w~Zs!v4}nnq4h6x5keir$g1`UZ$bTkz&bF^zl zJ^1uk65y%eq6t%tj}4j&sHJuhk1!J37qrK!*sX+j=5pi4wZ zG7@D;+(b6FsTBX|C(e?WP$4hg`=cjNmh`%Kt0?8nQj|PEp9c|gGhC!D5k-YpewdcX zS;O~d7rI7at+5t+!mxOIrPF5WIGa>{(_;}6@HoG43}xGCJ5!+|a=q=TH_vR2iN@G& zZ>Xec^|e&*yE0?Ha3QMT9)Gb-rk`S6)3i~KIA=O%rrA5$8Cz_d?c!UF;8Bea`;`a? zW-M7wI(VO#arTT5?u7~MG}6Q+z4&1p*x^mtE{hxXa3iocMwPhoD&(#(2T~5ws~|;J z_KMaZC=|k|)3&SOpYXbuu2ZX)q-l~bD*>VZKEdnBPQ`F?i$LQda5cSz2)2@ zG;J0-Y-nvFefl#kij0jL_9A!XuFaxru)WQuXoVNPKW+949dNakaMy)#*P)9iOe~{Z zjcCl3T9*wZ{1oqk=NC`qu_-wQzVaULSouu!YhZr9;RYKcC?%{se^fIUc3Yst>XBpUvQMH z+RD`GO4qI>>eN%U?h4!V#w~Y+E_9LHk8Z7>IV5#9@XnMfUX_T zbr9{DRNeaL(gG*Bk9a&%wC^WI3HLRO4CIH>HgnfEjpNA(+JTC&ZCx&%lG%Z|Zg`;q&!KXMSHlE`n!{H}hJ-+{<{iLg$G zlf=^(C0rJ&w*X*Fu%TFyjYf51cy`J|G>il!n|Ju?W>90cf(U{FNmG z53;m9W$=L>sqio2IIxB^8xfOE%PIKN>XQ4?=Hv=fEQ_33R&5UDRgWw|mZ4OI2AZXs zt2WG=$`@;0y~ zkKWrG-=-uO7vLF_F3=Bgl523q?X`eFNVIcrH||Tk17`MQHC?T`zT zTBe06&C1z%3YVw9d;e|Zp)be897PiZ;B=f?Ae!# z7p))H$@2B+zgE7`CN*tdPKxBi&CcEj($2Abqxkf7aXtK7#lBRjty+GkAodSv#b#QV}$ynnu?k6SCfw?y4l&H5x8pURf+Syp!WOR@H^4&C#Jw0n*ydo&h(Oz`*i z1#+>Yo}rm{uoh0xY}7n+>>w487a!!adGm}-;L7|B1Nt8~R3_2R_5-?i{=Nw@lKpiY zl)FsSye9IY^jOsZa><1vX$D__7ZBLk9rzOeFW1jXWC z@R?8y_n(&AHGE^+qa5%2A?`#LA{k)!2_^QEqk`(G?>Y=s?ucYbKcyCq#Cwqz3kc&d z-aWk_PX=A?&?QR6sRLWZ|NT0O@OTi6d4cFWOJ%>t>5O5E9u=56ytQ&VKa^%2<<6sZ z5e>GxOOe<(u%*t_+^&jU3>7@OvMvDtW z$1vRI&5k}^Q}5Bn^v3mw+FMA7821E`obRXyQpaVt?HV3k(Wr>EdK}CbLhVyW+TRzB zv}v0(XT#yTh|MMUo`o}NVq4;dsm;2W_D$<9Qi3<*2*~eX$?6_Cq*`(0+F~<~z&1UB zRg1&2A%^?|C+BBM68IxCXtM~lwtoJK1`D@2!VGtW1!*Z_#_NQtErC($vQjq5ub*a4 ztef*NUTjoos(G#&FN;NJcn$N6B5;@|+Yt@(+>+7p;63s$%5!DlAI;#tp8c;+@TfBe z>@`M7ZKIzr4)9*gvSORV9YLF%iT!rz)FskPl#PI*s#(JJF&8L!nD~4X60DTr=V1W5 z$%q5YgUKx863I`q0TdHzc?|aqN*ALrZq$=#751sofpgNemh%qY;ln@3@zuxP;R${W zqh*IS6SWX=zhUpBoFOPef3b>aAr7!>^rV?zk%q5LK%KjIW_a^7(j|2>))tjG1sV9q zQQTV&|L{N)YtBHK#hK{WS?`xsl>}gCm|4WRbaVS=ZvzaaF&qKPzECc=`q-g20PGKt zxS{?qmx{Hs8ycdZH?2*ne@;Ih z*DG#HSm08KqVCE3VGoB)%Nub1h}2FQ5t_E&N;YsOf~b}bvn*ilLdPA(?pJdYaO;A# z6FA1Ql)`}BxqkkFjAxnZaLx}TZuO~CYJ zR(m!P8D72p*aGV$pW-Q5LL&*Rqv6J4ZAfzSLPa;?z>j?}hSiS;Kq!a78I!CIsOo{W zx(N`Z4|(fBo*vLR;f##Vc+%=AtX1gMWeV zrJfJxd^~ir8)fN0j2~^jq+>^9%R>xEBLq+~Frw8!8O6w+Z-Zi24`GeTPs+~D#%OCbE3*z=KQkQ= z?2|D#H{cT-==%mPraf0$Qt%uiNtfhD1w_onte!pw^gd}+JSkK>={J-X?5fg#6KY0} z8=`(PvQbHr*s{Wpys+=Pl3-V{q4u5w^ zJO~9ty=dYhMgx{ZC2gT}gtUsVU&(xSb0#?>9-ofSd20Xge=ZnA+Lt1^EESgU0dcVA(-i0wWKAWDEamH7Sz0>qH4+Zrvl|FIZrnp`;W8i_I!ns zrBs$Gr27H%AJDYMp?ym)r}}3qxg>>4iAQz1G)`A*IZyR=JFJraIUusU7mo8?OMtc`!fQD@} zp$Vi{W)do`5q_Lob?z_zv+T-wErnA<_j!v7f(rWB^H3)REhbFvv{JnE$NX*u@t%Z_48QI@&3o6b{d{|W76LIdnu;h? z5Y-dmi7-|9sc`n?aF5{lW`ib!%Lp?ahH{@x9{$#T@$47$GC zu!iiM{zu`!O>xZ%UuF8bL|dH5PId_UM*c|G5d|6+_<&B6r`lk0ru9Izfa$x|GL^|P zNE?_UJr*vmxub)g?hD2-tqJ1k&_+sQP8aJ8(~co;bYEk~ zVcg}_u<0ZkPimWz)SfBb0bur9aMxbxjf$c1r8U+Wgtx#^EoTO&Q}Use3^NObhppbI z%-jfVh~)(*P+>=)fqBNRgIb0br#92q64I`J_2J0(bm^rvMYK2RsNat+7@E^xI?eSY z-h7*a3rXO*+L4%)7isr}fy3g4Ly!|Ol^JB!#P0GfpGYUEgCtQf)(}B7R57R1*nvJQ zvr!p8b#uU^X)=J{&xkVk9jR&afj# z%`()O6b-c(tzQCxn!m4{izOM{3u<{nvs5N@%){GKZIV$i^6@#df`FxO`F?GGjZVIf z`asETY+8S_PWKVv(2C3)I?R?OV}+G+GmIqwB|mddQ&?Z%0OLYz8O_hXrsxF@pcwM? zXqS#5)g5{%s$B*vJ0T5OB!1}eRjVft#bq4ZWp_toT1PNNE%(rroY6&~bfKEK8LGBN z`owEqwUprnjq+bbRnh@ejzN@w?qS3Vt1^HSt7Z;=Vh>VaIfCP>}w8TkZZtc z$zIo9!py*2!|6jb|6oTRpfHvqId{g{*58j|)BZ(VbAC{2LVl5fPl`t~^m{DJ@6oq^ z`$K{RVF&(36Lmr}ll|!SyH?KZA4BaSIYH4WA9`_x#ki`r8v*d}(N;Pf+h+y7nhY5M zk}*Y@0X7`yV<#c9O(rUu#vi7MjxE)pJ7!sK9HwiOgd!ij#OCcktc0>J=Z#hVQ(#oD zF>Y>+Qq&?d@@_4o{1toSV3-8ckqBr^5U(dXGpfYhP}v2GHx#aNe_q|0r)O#g6Lmz8O~nk#E9 zciqwyG+<@Ta&9oXb`DG1F%92>@5^;VCV-ZKp6Upm)wyaaK9|*LeO(3@^9khBP3s$f zNU>#k96QLYRuRkN!V$;nqIewd2sa4Z%?7J174R6FN1M`P&-q0^Lrs1dqz!^nIPQ0V zw1kSUhdr2 z{@V)*K>1|vAq`zYzgosM;n(uVV3y8(7W^t(h|bEA*;y0^w-{t(m~;*d!0qzyqXZ?w z5G|k~+Wx_JV=>3nd}liCVI;+Xd4isB6$hoe=4acz?@u)Kcig}@Sn5Te73PlC0<{os z_e%d_3P*JoVXZZ(e$GXSk0Przd7rL{7RBUNc>^F4C9P3pqGLi|!Z858mcXh=qb$a~ zC5b~$Q;8$XKh^bjH(Eu^M?H&@U|^>b4k@im+`wO)O|L0b98 zQG#>#gNg@%Hd!m`qk+`Lf4TYeueFsD_V*H#L`cJafyY3HXTP27|Fz3+NOdRaaQ8YxcV-^5w4jQ~{f!8=3QV*>!ZdCybKIb9|Upnw7mB1haU($m8yCiQm zd#AnBY`UAfhh^vW?X_EyRP6$Lum$dF$l2(x#qLX2z|efZL-qs!mKfM+q= z@`3@OyCHjhQnd$uOe+_ODNxqz%G@fc&Mt4<9Kn(o6$`AtLU+P#GF(7;*swW_SQLEs zKuvM21z->9vG1bwkPw9#_M;l%fIXibl|So|BqoBGq2z#AY)dC;GyF%yOGFYUf@5*+ zAloQI9z_g18`D;VOR!$=Xj1cPPZ|SCc&}Q6i#R~VZN5{<#hLKFkuApyreKASmJ~II z0F5w??Xhm^2z6XAXPA#x(fUl3OU$TM5}T9ZdZS>bS%ogywaXl19oxzPCl^V8yZAsK z!7?qRx^q!Eu0CZNsZK)rDDk2U~H%3HMRasxZL(FrbNF zk@yEztrhwezp9LXU-JiBC2>9hU8CoyD^6((QHf<)C3DJUs?)5*Gv7EXjDhy6B8)|u zvm&`@2q}h@9o#!R-B7RSj~?2w?Eyxqg*{olb-R}9-6BMiQWJt%%=4)#8NZJV5sP(+ zV4C9DNkSfnct7)*%P0b`hSZwg*4blZGnN7@6dhQ}K`wpH_wlO%K2jD-G*!_H_T-%U zX&NSiqv)G_UQxK}mCH1|-vzBF9jF&vD_ySnI;PJPgRERN7751nY}Ax~N?@r{_fSZ^ zCG8+voXA>p6ce^PBL`k9HB;;nkD$I@`2izfw{+@l=}yn&BZVie?Ao)+>`Fk^sx!?< z3MvW>er+vd^n#?pA~AJDx2`WGab$4yzMcmo{*2@`z@|sqwM4Gn%SSi919#jIhC+h^ zn2ILZ7?u=CN^EQ*X8ksavMSe!;_)~vo}Wj9&tb@)J4N||Bj)x;7U2f#aw+iaw9S>% zUMz5MVU%aQMw9ENp@pVEJp`xM$P_5uuQKI zFgSfvY>=m+U{Bx&N5}6!K;`KE#hXk(7|!yN@S&3&^`+K7#(qmo-ILo&9~V6XS(d?|kP8bY&66oYAtSrLZ610K#Uogj2S>L?1Ap#@WDI#R9G zX}C(?+j?{cdlUK?oi=_$CCPD}Z+J>~X&mV>pCtvpRLnr#$HJEi5WzkyWYK2zBY)ui z;aAMb9y|Y>r!OL4e2bqs@D1lfN1P?yRXMNE8Sx>gvJ%V~2`9+&^7UQs2AN|Xx%PNp zY1l)vdz=lP8}wv!RR*!f_*t=CcGMx(vm08rO=fVTmc&26cuW#BN!CK6#m#2hydKCAgN3*08L7O-#tJzbUcnoo8kMMiZq*TOf zJEV~z^d@&KME907^Q9s)&ojp>+xO|j655yA$QxE#UQIqCd9{pS0wljc?0)^1&hER) z{69uZlK->hnA;m$+qs*&SX)?|8vjQ$^?!J#x(%c-zz{(|hUveN;Qv1Z{NH!wYW{zQ z{Ga@&4veSz3dX0B1tn8vPX@BI8FC=82`Z|T$^s>H2neclP!=-o!D4!5`q7}7MUGeS zXfI}K#fs)|)Vrn%9at2yxI|@b?WRPneJbNOLfWchUu$3cxXs7ZOpZoLGI(?GoI2q1 zymP$W{9)Od&i_7AtOQa~K(zoN};qQb#*}E&LjYDDsa{3h;guR@nczFVKvZ!MnjK+a8Hagx3!C6 z(?BBd_$GIcuWBE8Xt=|~)=#ik?13XIt8$EzhXpYlR!xC8MuA;J z&63)wZS^GD#uGIPLtfJvy9{~s*?1N0wjGb)8uyjdteMeg%aRB=_vtE@1$nU;Pm6~I zLyDRjC4-tmUbbEM?SM3}#F}Xs@ck)4SwmJExsE8$3g$XVXL68R#`=p9=;MGP z1+*F>sZ=I_!>OgPE*1;egULT`hJN%Y9##9}O@XS1UwojAFE!5g1AeaKCO7Zob&w1Y>n)mgcXzhl8v+d1uCA``Ca+culiHoe zUU`nIqfSr^2UBGt14t9EF=q<(+MorDE9KFW^2oIK<5n7KzjP0lf1pOfj zb1p)(mCMYOm}JzgB1Bj8F5tZ){Ez0@`qXdx=h@L3J4&2pg*J`0P=vl$A?EdRFY5>O z87Vy>>%H1olTLuoKqN>I?5rvZDk$3m?vAnA>5e3ektqBLrDE7q*qmiZ?(HCYCz)sk zzIv8nis;tNQ2v~zGvfL<G{MdoM>|9V398m!TH*na`d`m^-#2_wNrV7~BXr{!*HfS3s7Oa=J~L)n{AO&Atv| zkg?jK8A?8>O&Q8270Zj%Tqu0s1#~#l@ttYVh@>H-3CPSS?{yMc^%>3eB#}8Ls408ss(& zEDuSluHBaa#)!|q}r(s@>K3jKSHh!R{Xjuk*ulylv4x>qB0BCs`GU9 z4^$?!C^8YI^x@VCsvEQ3T><)=f2=88dM{r(A38>tTp?tTsmrq2uL-(nw^7;QynuQx z@vhBB<>ZcFv`~|Q;@7O!1d_g5#qJGj=J9w1!^-DidyGQ%t6Zw$E*Ye3<8z8iN2XV3 z{OevO8QZko-!tO}=W^j@BO(UgtoGgCjq6HMlYGqU*mN#?Qd+%(^L!9F%N zQkkF0hAv6cQ_IC4l|4jzGjNjDWJ6+BJMH2cPS}1@)YK?bhTJJ-kS3!+wvG$3W>Ji2vx4dTeVS6k`a@MH40#ot-$+Z))+ibnDsQy0N~2Ivs+zCKC-3jWQFqP?7>1Xg;l zzc(dY-96|6##dXwTjkgU8u<)(sr}d$#~MX>@O|)pBqm518b$bOEyo9wh5B)$%p!nK zH{dJ=J#}s1+=e}{TE`^AR!SEJYv9T;4jpySAoO$3bX}1D@d_RNV5ZXJJaMD&7s%KC z(2g#`qd#H?E&WOo#g+K?AouEq~>O%WRZ<4@A3y@8X zlaJtxPEQl%muGsVilLW|HIqHJ1ATkK*1Qx@9REUoQfnyfRxvY_zl1_t z2sgNp_CwHZm@j!VJL4hltxKh$9YIPg$aq5C$eNqUHH8Q{Z_VB`ZmY!!>58x2>kL{o zCe#lm)&F0dy?7T9$WVq#L#LN|;#F7bgIVA?lQ>frx~=qLZm`nqt@%pgb>-t7>iV zl6QS)N9epvL-Bvc)}pfLH<)cTKXQRhG{5i8R-@K7HGelb4sC9?g>DqvT~Cj>t@$<> z;hyWo4^Aq(IU_3k7IrP&SMix8F#s>e%iBe z$El|42CcSpqRydh=DhsV++gcOcm?cC|C`>-5)7fSgrc!Ljul}60c4RAhZQyFw%B7^ z+;!79$9^llRm3g0`|=nz&voOMc+Kgn_j6z@iCa8+I`#qO@$_c019;I&3@RqsS zg@$2>a?^!L9yD-hM|*9;xvIlIc(4UwsC@GmO7w`}O%~fc1uS<_s|1d83!u|(UQacN z*@3YRx$iZ#dba@P{Aefe-Y_~KXy-)6HQLmzo*SHuVfX|CgwayLu7^Owk+>ljdCZ3| z(6y)E@I`>HCI`A;qUWTMXC@;g!#S`%!hD#Ij&xv1>;gKfPt5Q_d@Dk0Dz+{~&cLkD z0bxeP%a5GZd=WN=Q!O>^x_^X2ZC(G-66{%nwS!^fsL65UC@nrZnk8e%oG}nh6NJ}E z9wL8ueJU-hZnF0IN>!_epvq1lYN@NHxAC^Oag!2^!MMC&q(%%pKUgoo;Mbu3bZnim zxgIp8ZbzoQQi!ehv4;ncfbJMR=Z0!}Six=IxiU3u@&ixKRQ)YN+*sH|!@#<7a3J%q zn$Fs|mM^^FO`R!Lg0J*&9Li{1e~@U^rPXR5QlG*whk!w_BjYHkYRC_PWlQbAtrM!l zlT$1xpH^BSC26x@p#x;&DG^$@twoaJS$Yzvtq&o(Y3@?&0*sXaX4U(mOl??GMysy0hyxmjguu%3t^kwD>u z)R1jk$sKt^V_`#c;U_W}9MNCB;9FRz4;T}fDXLiauegc0Gt|0t#>JyAId7yB)O=}`GRAf(ev}sGK@DcX_6ip@~+&!XxsLa z@K2q8u%vKtn`>BfHsxm~+2`!hxnWrBL{Zn(h@3q9!qc4A;G;mw2E$UAFuQEBom$(}3e+O6oL*MS7P%E&Nli zyjIos$$+tfs7*Z=vI2^vgGRjuL$A!frjiwV+$_5Os0fO|?;D|voXVUyCySHI-3<3m zL9SmwPvAGJ!t~HkH4Fy=Ycr@{raXlKLL*~+=Lu^<$Yu(iyXUb)>W&Y5bo~<~89eNmSZY4s0H+7zN(Z-xbKjP{6ViFC?Py8e|>b_(S=bmsS^6JRaWg z)h6-WHJDjvY3B@a%wHipI1`FjRPNzRyxGLty!piUmfAGjYX+B`Re|(gs}jh(TJyiI zYTzfdzS~*YMNY*gg{tRa;?dV?uW0MF@5cR$W|XE`#NX+!!jSn_U7GK|vr7L3{i}WH zLH-+It!}T1tBw|cAeL^IFUz55pW&Wyo$h(6@B8%nP80#j6V6ma zY_UrpiJZUkQ7Im?)S( z8WCOD{8okk@)4UH@^Tjj$8QZ$9&t(}gRLs)`wnwR7Nq#2#Q>J4B`mZA04kI1-dW~w zOVv~tpj8cW0|wH#H&knWNx2(ynRp{fvaK9pt=D~)Iffhl9>QVQe=zRe8kNEQRN|R? zsdg!h>=bDA?bdfAb)r$ILXQmd)>NJ?4J9B|V{OBV!*k1M!k$|)thtZJbBkWR6wSCQ z!hY59tO|w2beBWGDELhw*x>CNq$f+jk*YeLP7YocLSPUG7{1U={*kBUA)9*9J)yF3 zc7@`)o@iFvM9^YH-fs)<6$r_-jz8BCtAv*-PtylH8+i>Vr3d z*zq$-mhPJ1mdPMFe$^hI5XNzbrzPyp!!}*Nm9O$Ri5bI>(SLBu;SMyvqjMon7miiQ zUz;NE}4G`8;< zZVw$iKh3-BZIo3}7~2Pg-t`rFGyoRA?K#g3@}>~Qw1Vq6aaZYSjm7yqyj}@xkP&>Q zC(_;zKln2w$!W=^ERJ@D`6V$je?RX~4tM2q__V&%;ng)(;7|->L#p}{t{6mqpW=FFK6;^f99NN0(#w&dljZ$w=;j|CucKgT@>_Nzs?TW__mArFcb%<}2 zwRz@-81{zIlYesC%sPXI3ux#Zf1rEG3|F~@KY)LOgYLm|&)bqAb;I-s=ee%sTSI5> zcgsbWGN=d$zGkX4U-l-CHO}>sWcC5RR(-9={2{HipZuOY^@Ia{l*U{g>b)?ciZ-XKkkDXlw59uS%%O@`@nL zNdBO(r_mulFdetwq%IMIyoBYL#EOZYn$&6;=+}Oue8${Lda>LZ@x2x=o>f6hA{vzW zJ8tJ>d29S^T?Rv0%reF|HJEYJuaA>x@6Xg!P#i+%-pS zJp1`Ht_yH7Y9>2DnP&mgpg2RgptJA`X8r{!QpUu*A>O;2GCtVXvkm?`kAS? zAohQQprE5qt+G09uV(*G#{qmhAF3Z(;+5enG=SyWd~0+Y-&&ui?_UBJbYCiAHPQ&s zBad~4BPe@|T>jR=HS18rMhP4qA!1TrlY%O`;VJmD@Lf&I$Y*L0jBhsh4I85R zU`*-Z=rG`8w@+Y`*b9>(4n%-?4fG#=1I+@BaaD_ONNo*piqq)E;ERV6+P5LLMHRWA zM?I9YrVf>`8ZM1t;YPnFg525e`!%lCpTYj~tHxu{zhm*0G@*Sd(*7Tc$ihxeUy7m* zZeNW4KlOlB-5gzvEzN};{wb7hCU5NI^d+bLZ+o(ut|F>1+D93^9W`tZQCf_rQlZ}C z$S~FLY7iM2IO-gZH?BtnOGBQ$hh^NyH^VpRx9-oi<#ED;#0f*gTe{$$tq%eeAx9By>ON z4(;5AEIQ~xDWi2s76;Oml2q+mG58Bx^fE~NX#R{wBZzDxsUPu2iFKUP-)hKKK@5Ep zBu+Cf^nhzU0>W2fq+#swc!n*+`qQ^6>2)$H(s7$sJg$!w_s^md)(X6~qfi+7OuhS^ zKs|wVm=zvXZg^9d-$Z@?wtA9N(st@v#UHH0p34ch2E*t@T8E< zV`b}ENrHE+ZV+Mj}jpKAoufosy|akK4mP(+Jxdv3@Ij1uF4dA*}K zyGib#QXa2*lhaJ{dlbKAjRa4jdNjU(AqLIigb9nWt=E=}LSnMYBPc|hvH3PpN(ygtH2aZS@O+;;(vaC|KnbP`Co&H>z60m|EUo5AE)8|->2Dly8V|e zMeTEp&H?7zx4p01k^R4?%4n+n+nb@~?T5C6|Ji%VoMR4Uj7}y+L}i39CZaSPwD}!G zmMcUUiFW8J5pM0WrmHz=vFcCF5Pa43Ge_0qzr_7(@U*0RKA}coibfy9Aqc$#J=(`Wx{d;vh+@W7~NOqcZM{3rgfn{0>jzUG`nP))HOKX4}INw0yBqH(^X! zO>t(PP?k_&9&T-V4Pp#(bIjV=TcK9c|dfEG{4DJ0{f8Z&vu}AeL7o76reb{DeGCC$A|Skw{Qx;{S43`ad9TBW9~ z52E6lF|n&U_!qOImyuFd0SKTli0ep3@ug9O9RC&q!~DA;B!un+;{ z7yTSvv6$`VW(!Se+v2L${h}e7&Pt9_>mtG@WYYE#g3B5wyb%KJBla!UmC*<9@@MnU zIg>St;%SZTBr^a>`XH(@(!Y~+u9q7jGmzCMXTxC4F^YWj~%FW*)ADD^<@g=ups#t_lt36=v zkoe&tXCsoEHbu$SHA67&pBKL(!b9%esF!>b3J9guOBsjmkC`S#1l3 z2xnS@`aKT(_5=7M*CsiHMvh9$*q~(hpPS1hvof4hFqATI-ZBTA?s{D|X3%2Ss!Lo7; ztoc;nS3B`7axm*D(nWue4Q=PUG;Q5anG3Ghf&6fOV!q)~D*ZB#wpCnfovrUfGTG4Z zgs4_(cG|4@6I#V!6-a=n|q0CE0_p)8(G}aK_>ev8S>hz@>agKz!EUx4h z@yciNvVE&2gf3RhYwf(twwJX9%^&EQ?$H`zktRl`Ztf~BlVZL!c?VC}ymkiHCR8ZazbhdF%d=iCgXkf&h@m{aPG(F8Q1No+W{m`9e&P9ktbVtC``8bx}G(@ z_EU}Kfm%0%y_l0LEKoW&E=y7H(gV#nKS3{q*kX`A##E^cv%yn(_(!yA<#Dum(go-? zzyI0C{dRk6UMZw9bC2`E!1UB&w|t{Wvb9=rImW56$c7j-RB&;?+IRvHhq9N$6E3?I8^9CDlj6F*xO(29Gk%lOAPZ++# z8OvV#9c2+LUYQV&8jCq?8;lWJfDVX;Aw{329jq=hj$Wtahfn~yxS?tQwtmzY^=pYG zk6;Fal?tL-}8jbMaak?lq2w3=AY zhqEfd^!kc|M-p$OI=&)oR2i0bY0w#KGK^~_A_+_63CX8@GC>YE3oQq(c)u~ z8r$wG#FQqdnEG5L$!?6O``|J)BNIAO}z@jyBBFIs$&}D8ToHId)OghNK=IL@zyKZBOm^s!!b=eNYGxsFm`r z`O{WyBOV{zDPw00Rjjkzx#NX)_2j0#q9TO0P1Y(se?3B`R?}6a`~|iD z!HqNjf99)XUpec)AnG*b4TUdqgFkIzFgk?DD=sPuk!J4w8ysT=a?>98@JR?f#tMUV z*LlQg#IxCcDB-Rowk0O2w49vX{B+h?7VEu* zA)h&^kbd%JwsFog;k|sGRt_Yh=m4Yp^2c@`2c;5*J7bSrQ$6ugpgh7FF$)_JIIuiE z>VQgCwB&BIyWw?XUxysJoUE`O^z29wGcHNIVX2Tr_z0RN0NH3U-fKgDX;)h4sn|+jMCUtDk!+wYsfwF&WS5ekhVoW{>9LU0luocl&mpn*KkKH~&I?p?Q*H>yOB2t+rWlB{5-AVl3MRBpI7nMugu` zuoHI8UE*)-y_HcoN5P3!A=e*Ne-wy`+Li!pT~E`Uo>!fg9u9$TZ$A-@V4hH+h$oSm zk;G#Q?e*=TCy;HHbhNZHMuLbr>}h#BW{?!%(Ydm*=G$yv(wZJXJVj}C?nYA1f z!IuxpCqN%2?J>={i5j4x!;fp$QR4N_%cVf@!Yyo#PZg#J?~g!dG^$la!?<+jM&}%U zfabwveKynkxF~z|PF7_wuy<34OZ&j#w=TK=b^x_>H#Wei$Jb0{^Vk+rF?HTCP$#MI zQYr3%VAv%wcaTOVB%`JeSZfRwN^h#tE6iEd zmc%wc0#)AhBKf5*5YHAd!sN^|g2$-t}fa9A;u&KtA5S~(O7J3o9(H8{ILGXhO{^0 z_}YSpuPuhvf=8rmLQZl#mo>t3&NTkA$T8wEj9Kbhc=+r+ou)?=RNfXl-vuW1rgpoA)<%92E$-&&CtN}Yep6Km+uGxWVEOVaGD)zFt!bkxWqq)-c6gIUKt-b1Gqx%80A~KDD=%hp;cw|8b z23w!o?zD}V{h7N^H2X8e(}d*d{9qAck)w_*OAE(*OGk?U!Oc$)rI9W?l}x8Z59TPi zLZ-wF6)={|7!A)K-I<9mrx5{fQB2C)=Vr`dHKDmNm`>0+y%-i=g+*Egm9tRAvA6D2 z5Soo#d=8E`x`>MG1D%O0!Rh>iDnkn=MfL82N=1&(2cjGaChD?+zey56GW4p(l94@d zP0dP9m~A#np5HJX-Fp4_tJ<^G2c2K2J5TN$+@a30uc$?O_adse`M%`7=)1mKTHvmy z2un)z#z~I5y-jff?zlz*aYi|3@Xw47EB$xU6`ccs@!`g*STQmDYTG;eNAfB>Ko1P-Hcz|jq!C2914;vDi$9HU_g?U1W51L_Y7$HJrL-`V z<@}&!p$((!H3oGpbS={V^z8r3;T7zKW2vey-h(P!FqPZ$s^^;FB>z3%o9|^r)c5-j zHQaNE@7V|(pqINsr><>Je2&ijxOQ~;?RR#D{%8ZvRf4Ap3gWYOqYNIQsCqmd@KT_l z#<_xOc-GdBVa0{6egjg@!ioDCi~DzB?CAGZvRjGYvv?kue=wR6!(EtV;-Jo7{xK$$H~6iKT0NNCeK?*2N+0)O zB+hgWTYtCw8l9s9q8zx0`2caTg0aF9tHJghqWnqI$JjkgsIQL-AJ!AsJAW8_?DIY_ zU*!Q<79097GPPFsuc+)`Mzeu?j>aWz>6ZD+l;pqIR@yT%Kd*3mfmjDK(x57Q0X_9ul95zmQ?MEAoSG3u%_8&z;dffq2%t#e?WAQSNH^?0 zpzO*#xYa4RO~y#7rI5x-26#BOnh~P5-L#;+Dipv0Cb5V@2)NKaDDUxCHpQ}EUCWpZ zby2S&yA&d7>`>*NL?GTC_&4d@FRSAhOd!UO&PAl0hl49#2gOL;-qfe%yzE4VldPv$@s#02Evt6UdUs#*c(Nu1r4o)kYh!iBt(zjpx#aS z4>U)3dFO&|H#y^O2t`8>P>~h*F&?=0$WYX#=xISed^qKTQ{1^mf>5aB6RjFYxcyGr zyg-owmtGGv9JpOOBOEDZ7ZJ*0gME-LpCK*y+m!DoUuBeN6}?+#qZO~HgsHIaR8Gq&VJFe4scH1r3B zDTO+jP=>Hv>Y_i2Yviza7;j=05zCg7Eei(NMVD@@muRv}u0Xj!)*#C0H$Y)rGz+YL&mAWM|* zwx>d#gVlehEzIwj;K}yF1v~j*ERaL|9ahY5h%QqI9iK8ct3X0$>jzN-f>PtJU75W_ zhap&MYn@_NK2m~(TCfw;yoZOecEb5OWQ*L8T%7|4<>H?0JqX|WL1Bo)Hq^-jSi>UQ z=dRRDA+ZzGIOn>G(DPm#Ev*^C6o%D>J4qUp8K6i@GmVOxk<47g1T1zeis zikTMACK}bzZYWG=_35Erht__;HoiS-PShJ$Qv>G-r$Zy+0`8gk4a)eu5nl4xkxg1? zE)z(xyL$2$npbay)bD;msdUSuYm@1FfK9*8s!+C(d>3RG(wU2XI;7j?W}rYPE__E6 ze5sqeg6o(!%yZ@j2euRJX4TuY7T^BQ11!JOY_zV~py)S;&<*#Fp&(vk*g-%(qHvBQ z@x6Z;=XouZ216w|>8Dlqv{u(dP}d#-*n78@-07id6x5bw|NG6i`r^)DKz7(~%U(IV zCyuIxjktLI$xPqbpx<_oo$~jQQ$(DXK?N5L3qks`*{2_Mabo`RW~EE*fT0%JEB+Ni zsAp1^HqCKR0keJw>Heu(1MK!oW+TRSEaS7mR!%;Ym<4T8(8(QfGwI)M5mHjyg)J_t_`K zQua3>3H+BVRDvJ+ub-k*^Bq-Zg-ZP=@Q*yxr{S8r>i7n%yH7VfbIK7(XJDGyr6Yca z$vPQLZGl5Uf`bJ}5GBixP@aC^V`qLFTT3rI-Al@gqd=7*+k=-;m?WM(`*mJcd1ZcQ zd53o!!*}k^>h6!A`T=;y!7e-W4|3{B^R=K$#-16m2Ti~=zGr#4=DK7=A2FI|e5ANA z0%h3XM`S%Q0_-QzOj)kh6y|hkf*!lem5n3W3Z~r+`(gOt-God5V`dl2ZmnR1nL(=a z!}sn9kkbMPzsd7y>;*lCXMkzsC&le7H|ftk)@QwlPb?=W1X4H!ou@aa$OLVP)+sN` zFgSwcg?8yJI4)pBuV_US0w zIs$WyM_e&JK%7;5XWze-MNeT`n>U{DN$N;Tcbw2l3> z-~j*Q;Zp4&uGM~GE;QomD1s^&_P-YsA@Hms^70>4``rwf%lN}?-YIhz8=d(uX9z2f z_|EgZjuZi}%e|6I;XMP9?fEaFCh3vBT*v2JgZ)X*?0|Dq{8kba7%6(aBiHseB=$Hu|IVOnC8!uUGf->c)$ykr_HO_>Lur(4bOz-ON7 z-~rw6M|)0GlPWIV!Khkd=S%lcptPeGT+oF@Id3>{OK!WX=SsX3nz4#QQCw^N)Ltyq zvmOx{FIM7%Nt`8c=Y&H%UNFahX&lLZO5r@hmnzT^qg5ev$_IoDjOqn4wHuehDvm6; z<=X6?T?V<0tMTV>hbgiR#F=yH1F<2d&7SDmX$wX2QHhzh-$-#+cXku%j#_6}oH!Wy zSZ4)nXAT5$s8TDrgS3|etZ3`;WbB?&P%>dU^L zHwlrh4bLsf|Hj7XKr|5(_r!maJ(KuC#~uj{f~tXGm7!;- zXa9q6Mq*59(_Gx^K5FJIkLF-FIuCi4!yH~!eWIreSC)UVZS~8Js>_%zfx>4#y8pO@DjAdpjRl_g?f>utwYJ*p9 zt;T&Xwy9d8wP7bFytM^cZh(@|af(K4e8;2iUZAuK1uit=jLrU18xBIxIGnkLdV~fW zc<|H<#=Y%@!9RzspK*~D$j9PT=SjRTe%~n z+Q=o0$qLQO?MUPQx#-D{&rq6B$@Puq!M~0QTQx_aR9)9|Y{tEYh--K43r2J@Sx)SD zljC4Oc}xyR-1oo_wAe*ZXq0m#j1S+h$yH*K;f+aruQ*Tu1pLPTHE4XjP7_FWK3Whu z3fhYOjC3V2o2^?UZJmY>Z%HfqLlhmc71-a?kq}-qLTMiB{_!I|f$Xs7Zh!v9*l)5Q z=2) zaNs0E3TdcpH=(UcE0-T!pM6tRLSbDdg!2n+_J+(v9{-_u4iGo~oJ0bGbxst(`KeMs zx>nJm*5gv_!pqq>vYvE@I$hm1*Z&*`b9jU8EkAgL_&^to+)8Rop=_)g!Zqd*;vy!` zuR<9Y-+#V__y83=l*E2xB|bUP<7vE>Ke4zl%^0$THsNR8gw`XjZY}?fN3CIJ==PFYyjB~WS3-?mW*M` zrb+H}v)|8QozVPeKgXEF%1#$<%Tb_1C*xj4CLQ+|Hrb04j)gk@cD!Z)%JU0Cn&PGB zg|ufo;e6k~FFmeG`+=XD`vgCW#Du;bX|5T_4|vl_5o$ zK+K{bPyQBFi^9L%0~nI9IStfbi^V8KN0{{CIK>!Ze%H-gsV10wG!>S;bEh$oNDQ|A ze8!=#Z>N+`R?h9WW-ZJ-(VNV1G0jhl++gZN%_WL+eOx$xyQJvGP`XDfh{#4OfHJdd$NA1s7nRkVrJ zJZ$HI>2LQvH{0_;Z|PB3vlXl~oH1V!>Tcye&;QFPC+#QK5R$qHL!LY(?Gfys-Stkq zOcAUk&j)58gL||$lVK9JGTc*rh?-i@THC2RTdEM4PdKlQ#G02KJ*u}k$mse?+Y z>$h*Wim{jC4%0A@czNUD>QL!~mqI~snu&VC5YP~u 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 RNnw+wo`>x=wvY~B{tr0ReO3Sf literal 0 HcmV?d00001 diff --git a/src/test/java/io/supertokens/test/accountlinking/MultitenantTest.java b/src/test/java/io/supertokens/test/accountlinking/MultitenantTest.java index 17ff75764..76c5ca96e 100644 --- a/src/test/java/io/supertokens/test/accountlinking/MultitenantTest.java +++ b/src/test/java/io/supertokens/test/accountlinking/MultitenantTest.java @@ -32,6 +32,7 @@ import io.supertokens.passwordless.exceptions.PhoneNumberChangeNotAllowedException; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; +import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.exceptions.InvalidConfigException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.*; @@ -446,6 +447,32 @@ public void testVariousCases() throws Exception { new CreateThirdPartyUser(t1, "google", "googleid1", "test3@example.com").expect(new EmailChangeNotAllowedException()), new CreateThirdPartyUser(t1, "google", "googleid3", "test1@example.com").expect(new EmailChangeNotAllowedException()), }), + new TestCase(new TestCaseStep[]{ + new CreateEmailPasswordUser(t1, "test@example.com"), + new CreateEmailPasswordUser(t1, "test2@example.com"), + new MakePrimaryUser(t1, 0), + new LinkAccounts(t1, 0, 1), + new UnlinkAccount(t1, 0), + new AssociateUserToTenant(t2, 0).expect(new UnknownUserIdException()), + }), + + new TestCase(new TestCaseStep[]{ + new CreatePlessUserWithEmail(t1, "test@example.com"), + new CreatePlessUserWithEmail(t1, "test2@example.com"), + new MakePrimaryUser(t1, 0), + new LinkAccounts(t1, 0, 1), + new UnlinkAccount(t1, 0), + new AssociateUserToTenant(t2, 0).expect(new UnknownUserIdException()), + }), + + new TestCase(new TestCaseStep[]{ + new CreateThirdPartyUser(t1, "google", "googleid1", "test@example.com"), + new CreateThirdPartyUser(t1, "google", "googleid2", "test2@example.com"), + new MakePrimaryUser(t1, 0), + new LinkAccounts(t1, 0, 1), + new UnlinkAccount(t1, 0), + new AssociateUserToTenant(t2, 0).expect(new UnknownUserIdException()), + }), }; int i = 0; @@ -698,4 +725,20 @@ public void execute(Main main) throws Exception { Passwordless.updateUser(tenantIdentifierWithStorage.toAppIdentifierWithStorage(), TestCase.users.get(userIndex).getSupertokensUserId(), null, new Passwordless.FieldUpdate(phoneNumber)); } } + + private static class UnlinkAccount extends TestCaseStep { + TenantIdentifier tenantIdentifier; + int userIndex; + + public UnlinkAccount(TenantIdentifier tenantIdentifier, int userIndex) { + this.tenantIdentifier = tenantIdentifier; + this.userIndex = userIndex; + } + + @Override + public void execute(Main main) throws Exception { + TenantIdentifierWithStorage tenantIdentifierWithStorage = tenantIdentifier.withStorage(StorageLayer.getStorage(tenantIdentifier, main)); + AuthRecipe.unlinkAccounts(main, tenantIdentifierWithStorage.toAppIdentifierWithStorage(), TestCase.users.get(userIndex).getSupertokensUserId()); + } + } } From 627ba81968481f92f30faac6d151720b9c85bfdf Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Mon, 11 Sep 2023 12:04:10 +0530 Subject: [PATCH 109/131] fix: email password update email --- .../emailpassword/EmailPassword.java | 2 +- .../test/accountlinking/UpdateUserTest.java | 84 +++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 src/test/java/io/supertokens/test/accountlinking/UpdateUserTest.java diff --git a/src/main/java/io/supertokens/emailpassword/EmailPassword.java b/src/main/java/io/supertokens/emailpassword/EmailPassword.java index 2ac0a96b9..940de4a33 100644 --- a/src/main/java/io/supertokens/emailpassword/EmailPassword.java +++ b/src/main/java/io/supertokens/emailpassword/EmailPassword.java @@ -591,7 +591,7 @@ public static void updateUsersEmailOrPassword(AppIdentifierWithStorage appIdenti } boolean foundEmailPasswordLoginMethod = false; for (LoginMethod lm : user.loginMethods) { - if (lm.recipeId == RECIPE_ID.EMAIL_PASSWORD) { + if (lm.recipeId == RECIPE_ID.EMAIL_PASSWORD && lm.getSupertokensUserId().equals(userId)) { foundEmailPasswordLoginMethod = true; break; } diff --git a/src/test/java/io/supertokens/test/accountlinking/UpdateUserTest.java b/src/test/java/io/supertokens/test/accountlinking/UpdateUserTest.java new file mode 100644 index 000000000..899ca6a6a --- /dev/null +++ b/src/test/java/io/supertokens/test/accountlinking/UpdateUserTest.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2023, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.test.accountlinking; + +import io.supertokens.ProcessState; +import io.supertokens.authRecipe.AuthRecipe; +import io.supertokens.emailpassword.EmailPassword; +import io.supertokens.featureflag.EE_FEATURES; +import io.supertokens.featureflag.FeatureFlagTestContent; +import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; +import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; +import io.supertokens.storageLayer.StorageLayer; +import io.supertokens.test.TestingProcessManager; +import io.supertokens.test.Utils; +import io.supertokens.thirdparty.ThirdParty; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; + +import static org.junit.Assert.*; + +public class UpdateUserTest { + @Rule + public TestRule watchman = Utils.getOnFailure(); + + @AfterClass + public static void afterTesting() { + Utils.afterTesting(); + } + + @Before + public void beforeEach() { + Utils.reset(); + } + + @Test + public void testThatUpdateEmailFailsWhenPassedWithNonEmailPasswordRecipeUserId() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + AuthRecipeUserInfo user1 = EmailPassword.signUp(process.getProcess(), "test1@example.com", "password"); + AuthRecipeUserInfo user2 = ThirdParty.signInUp(process.getProcess(), "google", "googleid1", "test2@example.com").user; + + AuthRecipe.createPrimaryUser(process.getProcess(), user1.getSupertokensUserId()); + AuthRecipe.linkAccounts(process.getProcess(), user2.getSupertokensUserId(), user1.getSupertokensUserId()); + + try { + EmailPassword.updateUsersEmailOrPassword(process.getProcess(), user2.getSupertokensUserId(), "test3@example.com", null); + fail(); + } catch (UnknownUserIdException e) { + // ignore + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + + } +} From 6986b334fa00dca3f46d104df39492d170f5bd6a Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Mon, 11 Sep 2023 12:47:38 +0530 Subject: [PATCH 110/131] fix: shared user in pless --- .../passwordless/Passwordless.java | 4 +- .../test/accountlinking/MultitenantTest.java | 76 +++++++++++++++++++ 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/supertokens/passwordless/Passwordless.java b/src/main/java/io/supertokens/passwordless/Passwordless.java index 62f92b037..798d0b6ae 100644 --- a/src/main/java/io/supertokens/passwordless/Passwordless.java +++ b/src/main/java/io/supertokens/passwordless/Passwordless.java @@ -399,7 +399,7 @@ public static ConsumeCodeResponse consumeCode(TenantIdentifierWithStorage tenant consumedDevice.email); for (AuthRecipeUserInfo currUser : users) { for (LoginMethod currLM : currUser.loginMethods) { - if (currLM.recipeId == RECIPE_ID.PASSWORDLESS && currLM.email.equals(consumedDevice.email)) { + if (currLM.recipeId == RECIPE_ID.PASSWORDLESS && currLM.email.equals(consumedDevice.email) && currLM.tenantIds.contains(tenantIdentifierWithStorage.getTenantId())) { user = currUser; loginMethod = currLM; break; @@ -412,7 +412,7 @@ public static ConsumeCodeResponse consumeCode(TenantIdentifierWithStorage tenant for (AuthRecipeUserInfo currUser : users) { for (LoginMethod currLM : currUser.loginMethods) { if (currLM.recipeId == RECIPE_ID.PASSWORDLESS && - currLM.phoneNumber.equals(consumedDevice.phoneNumber)) { + currLM.phoneNumber.equals(consumedDevice.phoneNumber) && currLM.tenantIds.contains(tenantIdentifierWithStorage.getTenantId())) { user = currUser; loginMethod = currLM; break; diff --git a/src/test/java/io/supertokens/test/accountlinking/MultitenantTest.java b/src/test/java/io/supertokens/test/accountlinking/MultitenantTest.java index 76c5ca96e..e48be3045 100644 --- a/src/test/java/io/supertokens/test/accountlinking/MultitenantTest.java +++ b/src/test/java/io/supertokens/test/accountlinking/MultitenantTest.java @@ -160,6 +160,82 @@ private void createTenants(Main main) } } + @Test + public void testUserAreNotAutomaticallySharedBetweenTenantsOfLinkedAccountsForPless() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + createTenants(process.getProcess()); + + t1 = new TenantIdentifier(null, "a1", null); + t2 = new TenantIdentifier(null, "a1", "t1"); + + TenantIdentifierWithStorage t1WithStorage = t1.withStorage(StorageLayer.getStorage(t1, process.getProcess())); + TenantIdentifierWithStorage t2WithStorage = t2.withStorage(StorageLayer.getStorage(t2, process.getProcess())); + + AuthRecipeUserInfo user1 = EmailPassword.signUp(t1WithStorage, process.getProcess(), "test@example.com", "password"); + Passwordless.CreateCodeResponse user2Code = Passwordless.createCode(t1WithStorage, process.getProcess(), + "test@example.com", null, null, null); + AuthRecipeUserInfo user2 = Passwordless.consumeCode(t1WithStorage, process.getProcess(), user2Code.deviceId, user2Code.deviceIdHash, user2Code.userInputCode, null).user; + + AuthRecipe.createPrimaryUser(process.getProcess(), t1WithStorage.toAppIdentifierWithStorage(), user1.getSupertokensUserId()); + AuthRecipe.linkAccounts(process.getProcess(), t1WithStorage.toAppIdentifierWithStorage(), user2.getSupertokensUserId(), user1.getSupertokensUserId()); + + Multitenancy.addUserIdToTenant(process.getProcess(), t2WithStorage, user1.getSupertokensUserId()); + + { // user2 should not be shared in tenant2 + Passwordless.CreateCodeResponse user3Code = Passwordless.createCode(t2WithStorage, process.getProcess(), + "test@example.com", null, null, null); + Passwordless.ConsumeCodeResponse res = Passwordless.consumeCode(t2WithStorage, process.getProcess(), + user3Code.deviceId, user3Code.deviceIdHash, user3Code.userInputCode, null); + assertTrue(res.createdNewUser); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testUserAreNotAutomaticallySharedBetweenTenantsOfLinkedAccountsForTP() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + createTenants(process.getProcess()); + + t1 = new TenantIdentifier(null, "a1", null); + t2 = new TenantIdentifier(null, "a1", "t1"); + + TenantIdentifierWithStorage t1WithStorage = t1.withStorage(StorageLayer.getStorage(t1, process.getProcess())); + TenantIdentifierWithStorage t2WithStorage = t2.withStorage(StorageLayer.getStorage(t2, process.getProcess())); + + AuthRecipeUserInfo user1 = EmailPassword.signUp(t1WithStorage, process.getProcess(), "test@example.com", "password"); + AuthRecipeUserInfo user2 = ThirdParty.signInUp(t1WithStorage, process.getProcess(), "google", "googleid1", "test@example.com").user; + + AuthRecipe.createPrimaryUser(process.getProcess(), t1WithStorage.toAppIdentifierWithStorage(), user1.getSupertokensUserId()); + AuthRecipe.linkAccounts(process.getProcess(), t1WithStorage.toAppIdentifierWithStorage(), user2.getSupertokensUserId(), user1.getSupertokensUserId()); + + Multitenancy.addUserIdToTenant(process.getProcess(), t2WithStorage, user1.getSupertokensUserId()); + + { // user2 should not be shared in tenant2 + ThirdParty.SignInUpResponse res = ThirdParty.signInUp(t2WithStorage, process.getProcess(), "google", + "googleid1", "test@example.com"); + assertTrue(res.createdNewUser); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + @Test public void testVariousCases() throws Exception { t1 = new TenantIdentifier(null, "a1", null); From e90c778081b7616b5e82570879352ddc37e3404c Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Mon, 11 Sep 2023 15:28:02 +0530 Subject: [PATCH 111/131] fix: recipe user association --- .../multitenancy/Multitenancy.java | 30 +++++++++---------- .../test/accountlinking/MultitenantTest.java | 13 ++++++++ 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/src/main/java/io/supertokens/multitenancy/Multitenancy.java b/src/main/java/io/supertokens/multitenancy/Multitenancy.java index 250ae9084..a5c74845f 100644 --- a/src/main/java/io/supertokens/multitenancy/Multitenancy.java +++ b/src/main/java/io/supertokens/multitenancy/Multitenancy.java @@ -424,10 +424,10 @@ public static boolean addUserIdToTenant(Main main, TenantIdentifierWithStorage t } for (String email : emails) { - AuthRecipeUserInfo[] users = storage.listPrimaryUsersByEmail_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), con, email); - for (AuthRecipeUserInfo user : users) { - if (user.tenantIds.contains(tenantId) && !user.getSupertokensUserId().equals(userId)) { - for (LoginMethod lm1 : user.loginMethods) { + AuthRecipeUserInfo[] usersWithSameEmail = storage.listPrimaryUsersByEmail_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), con, email); + for (AuthRecipeUserInfo userWithSameEmail : usersWithSameEmail) { + if (userWithSameEmail.isPrimaryUser && userWithSameEmail.tenantIds.contains(tenantId) && !userWithSameEmail.getSupertokensUserId().equals(userId)) { + for (LoginMethod lm1 : userWithSameEmail.loginMethods) { if (lm1.tenantIds.contains(tenantId)) { for (LoginMethod lm2 : userToAssociate.loginMethods) { if (lm1.recipeId.equals(lm2.recipeId) && email.equals(lm1.email) && lm1.email.equals(lm2.email)) { @@ -436,16 +436,16 @@ public static boolean addUserIdToTenant(Main main, TenantIdentifierWithStorage t } } } - throw new StorageTransactionLogicException(new AnotherPrimaryUserWithEmailAlreadyExistsException(user.getSupertokensUserId())); + throw new StorageTransactionLogicException(new AnotherPrimaryUserWithEmailAlreadyExistsException(userWithSameEmail.getSupertokensUserId())); } } } for (String phoneNumber : phoneNumbers) { - AuthRecipeUserInfo[] users = storage.listPrimaryUsersByPhoneNumber_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), con, phoneNumber); - for (AuthRecipeUserInfo user : users) { - if (user.tenantIds.contains(tenantId) && !user.getSupertokensUserId().equals(userId)) { - for (LoginMethod lm1 : user.loginMethods) { + AuthRecipeUserInfo[] usersWithSamePhoneNumber = storage.listPrimaryUsersByPhoneNumber_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), con, phoneNumber); + for (AuthRecipeUserInfo userWithSamePhoneNumber : usersWithSamePhoneNumber) { + if (userWithSamePhoneNumber.tenantIds.contains(tenantId) && !userWithSamePhoneNumber.getSupertokensUserId().equals(userId)) { + for (LoginMethod lm1 : userWithSamePhoneNumber.loginMethods) { if (lm1.tenantIds.contains(tenantId)) { for (LoginMethod lm2 : userToAssociate.loginMethods) { if (lm1.recipeId.equals(lm2.recipeId) && phoneNumber.equals(lm1.phoneNumber) && lm1.phoneNumber.equals(lm2.phoneNumber)) { @@ -454,16 +454,16 @@ public static boolean addUserIdToTenant(Main main, TenantIdentifierWithStorage t } } } - throw new StorageTransactionLogicException(new AnotherPrimaryUserWithPhoneNumberAlreadyExistsException(user.getSupertokensUserId())); + throw new StorageTransactionLogicException(new AnotherPrimaryUserWithPhoneNumberAlreadyExistsException(userWithSamePhoneNumber.getSupertokensUserId())); } } } for (LoginMethod.ThirdParty tp : thirdParties) { - AuthRecipeUserInfo[] users = storage.listPrimaryUsersByThirdPartyInfo_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), con, tp.id, tp.userId); - for (AuthRecipeUserInfo user : users) { - if (user.tenantIds.contains(tenantId) && !user.getSupertokensUserId().equals(userId)) { - for (LoginMethod lm1 : user.loginMethods) { + AuthRecipeUserInfo[] usersWithSameThirdPartyInfo = storage.listPrimaryUsersByThirdPartyInfo_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), con, tp.id, tp.userId); + for (AuthRecipeUserInfo userWithSameThirdPartyInfo : usersWithSameThirdPartyInfo) { + if (userWithSameThirdPartyInfo.tenantIds.contains(tenantId) && !userWithSameThirdPartyInfo.getSupertokensUserId().equals(userId)) { + for (LoginMethod lm1 : userWithSameThirdPartyInfo.loginMethods) { if (lm1.tenantIds.contains(tenantId)) { for (LoginMethod lm2 : userToAssociate.loginMethods) { if (lm1.recipeId.equals(lm2.recipeId) && tp.equals(lm1.thirdParty) && lm1.thirdParty.equals(lm2.thirdParty)) { @@ -473,7 +473,7 @@ public static boolean addUserIdToTenant(Main main, TenantIdentifierWithStorage t } } - throw new StorageTransactionLogicException(new AnotherPrimaryUserWithThirdPartyInfoAlreadyExistsException(user.getSupertokensUserId())); + throw new StorageTransactionLogicException(new AnotherPrimaryUserWithThirdPartyInfoAlreadyExistsException(userWithSameThirdPartyInfo.getSupertokensUserId())); } } } diff --git a/src/test/java/io/supertokens/test/accountlinking/MultitenantTest.java b/src/test/java/io/supertokens/test/accountlinking/MultitenantTest.java index e48be3045..103cfee08 100644 --- a/src/test/java/io/supertokens/test/accountlinking/MultitenantTest.java +++ b/src/test/java/io/supertokens/test/accountlinking/MultitenantTest.java @@ -244,6 +244,19 @@ public void testVariousCases() throws Exception { t4 = new TenantIdentifier(null, "a1", "t3"); TestCase[] testCases = new TestCase[]{ + new TestCase(new TestCaseStep[]{ + new CreateEmailPasswordUser(t1, "test@example.com"), + new CreatePlessUserWithEmail(t2, "test@example.com"), + new MakePrimaryUser(t1, 0), + new AssociateUserToTenant(t2, 0), + }), + new TestCase(new TestCaseStep[]{ + new CreateEmailPasswordUser(t1, "test@example.com"), + new CreatePlessUserWithEmail(t2, "test@example.com"), + new AssociateUserToTenant(t2, 0), + new MakePrimaryUser(t1, 0), + }), + new TestCase(new TestCaseStep[]{ new CreateEmailPasswordUser(t1, "test1@example.com"), new CreateEmailPasswordUser(t2, "test2@example.com"), From 18ca94ce85d9e630b593135385ee779bb6028b29 Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Mon, 11 Sep 2023 18:27:37 +0530 Subject: [PATCH 112/131] fix: ep login issues --- .../emailpassword/EmailPassword.java | 4 ++-- .../test/accountlinking/MultitenantTest.java | 24 +++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/supertokens/emailpassword/EmailPassword.java b/src/main/java/io/supertokens/emailpassword/EmailPassword.java index 940de4a33..0c7fb08a0 100644 --- a/src/main/java/io/supertokens/emailpassword/EmailPassword.java +++ b/src/main/java/io/supertokens/emailpassword/EmailPassword.java @@ -177,7 +177,7 @@ public static ImportUserResponse importUserWithPasswordHash(TenantIdentifierWith LoginMethod loginMethod = null; for (AuthRecipeUserInfo currUser : allUsers) { for (LoginMethod currLM : currUser.loginMethods) { - if (currLM.email.equals(email) && currLM.recipeId == RECIPE_ID.EMAIL_PASSWORD) { + if (currLM.email.equals(email) && currLM.recipeId == RECIPE_ID.EMAIL_PASSWORD && currLM.tenantIds.contains(tenantIdentifierWithStorage.getTenantId())) { userInfoToBeUpdated = currUser; loginMethod = currLM; break; @@ -246,7 +246,7 @@ public static AuthRecipeUserInfo signIn(TenantIdentifierWithStorage tenantIdenti LoginMethod lM = null; for (AuthRecipeUserInfo currUser : users) { for (LoginMethod currLM : currUser.loginMethods) { - if (currLM.recipeId == RECIPE_ID.EMAIL_PASSWORD && currLM.email.equals(email)) { + if (currLM.recipeId == RECIPE_ID.EMAIL_PASSWORD && currLM.email.equals(email) && currLM.tenantIds.contains(tenantIdentifierWithStorage.getTenantId())) { user = currUser; lM = currLM; } diff --git a/src/test/java/io/supertokens/test/accountlinking/MultitenantTest.java b/src/test/java/io/supertokens/test/accountlinking/MultitenantTest.java index 103cfee08..ed1494005 100644 --- a/src/test/java/io/supertokens/test/accountlinking/MultitenantTest.java +++ b/src/test/java/io/supertokens/test/accountlinking/MultitenantTest.java @@ -23,6 +23,7 @@ import io.supertokens.authRecipe.exception.AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException; import io.supertokens.emailpassword.EmailPassword; import io.supertokens.emailpassword.exceptions.EmailChangeNotAllowedException; +import io.supertokens.emailpassword.exceptions.WrongCredentialsException; import io.supertokens.featureflag.EE_FEATURES; import io.supertokens.featureflag.FeatureFlagTestContent; import io.supertokens.featureflag.exceptions.FeatureNotEnabledException; @@ -256,6 +257,13 @@ public void testVariousCases() throws Exception { new AssociateUserToTenant(t2, 0), new MakePrimaryUser(t1, 0), }), + new TestCase(new TestCaseStep[]{ + new CreateEmailPasswordUser(t1, "test@example.com"), + new CreatePlessUserWithEmail(t2, "test@example.com"), + new MakePrimaryUser(t1, 0), + new LinkAccounts(t1, 0, 1), + new SignInEmailPasswordUser(t2, 0).expect(new WrongCredentialsException()) + }), new TestCase(new TestCaseStep[]{ new CreateEmailPasswordUser(t1, "test1@example.com"), @@ -830,4 +838,20 @@ public void execute(Main main) throws Exception { AuthRecipe.unlinkAccounts(main, tenantIdentifierWithStorage.toAppIdentifierWithStorage(), TestCase.users.get(userIndex).getSupertokensUserId()); } } + + private static class SignInEmailPasswordUser extends TestCaseStep { + TenantIdentifier tenantIdentifier; + int userIndex; + + public SignInEmailPasswordUser(TenantIdentifier tenantIdentifier, int userIndex) { + this.tenantIdentifier = tenantIdentifier; + this.userIndex = userIndex; + } + + @Override + public void execute(Main main) throws Exception { + TenantIdentifierWithStorage tenantIdentifierWithStorage = tenantIdentifier.withStorage(StorageLayer.getStorage(tenantIdentifier, main)); + EmailPassword.signIn(tenantIdentifierWithStorage, main, TestCase.users.get(userIndex).loginMethods[0].email, "password"); + } + } } From 1971c2ce8bc27f6c15c9037de37302e4b9f9ac11 Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Wed, 13 Sep 2023 10:18:18 +0530 Subject: [PATCH 113/131] fix: allow user disassociation from all tenant (#799) * fix: allow user disassociation from all tenants * fix: multitenancy * fix: updated changelog --- CHANGELOG.md | 21 ++ .../multitenancy/Multitenancy.java | 34 +-- .../DisassociationNotAllowedException.java | 23 -- .../DisassociateUserFromTenant.java | 10 +- .../test/accountlinking/MultitenantTest.java | 209 ++++++++++++++++-- .../emailpassword/api/MultitenantAPITest.java | 23 -- .../api/TestMultitenancyAPIHelper.java | 5 +- 7 files changed, 226 insertions(+), 99 deletions(-) delete mode 100644 src/main/java/io/supertokens/multitenancy/exception/DisassociationNotAllowedException.java diff --git a/CHANGELOG.md b/CHANGELOG.md index e75a73162..06af34a47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,9 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - Added a two new columns in `all_auth_recipe_users`: - `primary_or_recipe_user_id` (default value is equal to `user_id` column) - `is_linked_or_is_a_primary_user` (default value is false) +- Added a two new columns in `app_id_to_user_id`: + - `primary_or_recipe_user_id` (default value is equal to `user_id` column) + - `is_linked_or_is_a_primary_user` (default value is false) - Dropped existing fkey constraint on `emailpassword_pswd_reset_tokens`, and added a new fkey constraint on `app_id_to_user_id` table. - Added new column in `emailpassword_pswd_reset_tokens` to store email @@ -59,6 +62,24 @@ ALTER TABLE all_auth_recipe_users ALTER TABLE all_auth_recipe_users ALTER primary_or_recipe_user_id DROP DEFAULT; +ALTER TABLE app_id_to_user_id + ADD COLUMN primary_or_recipe_user_id CHAR(36) NOT NULL DEFAULT ('0'); + +ALTER TABLE app_id_to_user_id + ADD COLUMN is_linked_or_is_a_primary_user BOOLEAN NOT NULL DEFAULT FALSE; + +UPDATE app_id_to_user_id +SET primary_or_recipe_user_id = user_id +WHERE primary_or_recipe_user_id = '0'; + +ALTER TABLE app_id_to_user_id + ADD CONSTRAINT app_id_to_user_id_primary_or_recipe_user_id_fkey + FOREIGN KEY (app_id, primary_or_recipe_user_id) + REFERENCES app_id_to_user_id (app_id, user_id) ON DELETE CASCADE; + +ALTER TABLE app_id_to_user_id + ALTER primary_or_recipe_user_id DROP DEFAULT; + DROP INDEX all_auth_recipe_users_pagination_index; CREATE INDEX all_auth_recipe_users_pagination_index1 ON all_auth_recipe_users ( app_id, tenant_id, primary_or_recipe_user_time_joined DESC, primary_or_recipe_user_id DESC); diff --git a/src/main/java/io/supertokens/multitenancy/Multitenancy.java b/src/main/java/io/supertokens/multitenancy/Multitenancy.java index a5c74845f..e97d3edf1 100644 --- a/src/main/java/io/supertokens/multitenancy/Multitenancy.java +++ b/src/main/java/io/supertokens/multitenancy/Multitenancy.java @@ -426,6 +426,9 @@ public static boolean addUserIdToTenant(Main main, TenantIdentifierWithStorage t for (String email : emails) { AuthRecipeUserInfo[] usersWithSameEmail = storage.listPrimaryUsersByEmail_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), con, email); for (AuthRecipeUserInfo userWithSameEmail : usersWithSameEmail) { + if (userWithSameEmail.getSupertokensUserId().equals(userToAssociate.getSupertokensUserId())) { + continue; // it's the same user, no need to check anything + } if (userWithSameEmail.isPrimaryUser && userWithSameEmail.tenantIds.contains(tenantId) && !userWithSameEmail.getSupertokensUserId().equals(userId)) { for (LoginMethod lm1 : userWithSameEmail.loginMethods) { if (lm1.tenantIds.contains(tenantId)) { @@ -444,6 +447,9 @@ public static boolean addUserIdToTenant(Main main, TenantIdentifierWithStorage t for (String phoneNumber : phoneNumbers) { AuthRecipeUserInfo[] usersWithSamePhoneNumber = storage.listPrimaryUsersByPhoneNumber_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), con, phoneNumber); for (AuthRecipeUserInfo userWithSamePhoneNumber : usersWithSamePhoneNumber) { + if (userWithSamePhoneNumber.getSupertokensUserId().equals(userToAssociate.getSupertokensUserId())) { + continue; // it's the same user, no need to check anything + } if (userWithSamePhoneNumber.tenantIds.contains(tenantId) && !userWithSamePhoneNumber.getSupertokensUserId().equals(userId)) { for (LoginMethod lm1 : userWithSamePhoneNumber.loginMethods) { if (lm1.tenantIds.contains(tenantId)) { @@ -462,6 +468,9 @@ public static boolean addUserIdToTenant(Main main, TenantIdentifierWithStorage t for (LoginMethod.ThirdParty tp : thirdParties) { AuthRecipeUserInfo[] usersWithSameThirdPartyInfo = storage.listPrimaryUsersByThirdPartyInfo_Transaction(tenantIdentifierWithStorage.toAppIdentifier(), con, tp.id, tp.userId); for (AuthRecipeUserInfo userWithSameThirdPartyInfo : usersWithSameThirdPartyInfo) { + if (userWithSameThirdPartyInfo.getSupertokensUserId().equals(userToAssociate.getSupertokensUserId())) { + continue; // it's the same user, no need to check anything + } if (userWithSameThirdPartyInfo.tenantIds.contains(tenantId) && !userWithSameThirdPartyInfo.getSupertokensUserId().equals(userId)) { for (LoginMethod lm1 : userWithSameThirdPartyInfo.loginMethods) { if (lm1.tenantIds.contains(tenantId)) { @@ -513,40 +522,15 @@ public static boolean addUserIdToTenant(Main main, TenantIdentifierWithStorage t } } - @TestOnly public static boolean removeUserIdFromTenant(Main main, TenantIdentifierWithStorage tenantIdentifierWithStorage, String userId, String externalUserId) throws FeatureNotEnabledException, TenantOrAppNotFoundException, StorageQueryException, UnknownUserIdException { - try { - return removeUserIdFromTenant(main, tenantIdentifierWithStorage, userId, externalUserId, false); - } catch (DisassociationNotAllowedException e) { - throw new IllegalStateException("should never happen"); - } - } - - public static boolean removeUserIdFromTenant(Main main, TenantIdentifierWithStorage tenantIdentifierWithStorage, - String userId, String externalUserId, boolean disallowLastTenantDisassociation) - throws FeatureNotEnabledException, TenantOrAppNotFoundException, StorageQueryException, - UnknownUserIdException, DisassociationNotAllowedException { if (Arrays.stream(FeatureFlag.getInstance(main, new AppIdentifier(null, null)).getEnabledFeatures()) .noneMatch(ee_features -> ee_features == EE_FEATURES.MULTI_TENANCY)) { throw new FeatureNotEnabledException(EE_FEATURES.MULTI_TENANCY); } - if (disallowLastTenantDisassociation) { - AuthRecipeUserInfo userInfo = AuthRecipe.getUserById(tenantIdentifierWithStorage.toAppIdentifierWithStorage(), userId); - if (userInfo != null) { - for (LoginMethod lM : userInfo.loginMethods) { - if (lM.getSupertokensUserId().equals(userId)) { - if (lM.tenantIds.size() == 1 && lM.tenantIds.contains(tenantIdentifierWithStorage.getTenantId())) { - throw new DisassociationNotAllowedException(); - } - } - } - } - } - boolean finalDidExist = false; boolean didExist = AuthRecipe.deleteNonAuthRecipeUser(tenantIdentifierWithStorage, externalUserId == null ? userId : externalUserId); diff --git a/src/main/java/io/supertokens/multitenancy/exception/DisassociationNotAllowedException.java b/src/main/java/io/supertokens/multitenancy/exception/DisassociationNotAllowedException.java deleted file mode 100644 index aad011c8b..000000000 --- a/src/main/java/io/supertokens/multitenancy/exception/DisassociationNotAllowedException.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (c) 2023, VRAI Labs and/or its affiliates. All rights reserved. - * - * This software is licensed under the Apache License, Version 2.0 (the - * "License") as published by the Apache Software Foundation. - * - * You may not use this file except in compliance with the License. You may - * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - */ - -package io.supertokens.multitenancy.exception; - -public class DisassociationNotAllowedException extends Exception { - public DisassociationNotAllowedException() { - super("Disassociation not allowed"); - } -} diff --git a/src/main/java/io/supertokens/webserver/api/multitenancy/DisassociateUserFromTenant.java b/src/main/java/io/supertokens/webserver/api/multitenancy/DisassociateUserFromTenant.java index 023f91342..9553efa91 100644 --- a/src/main/java/io/supertokens/webserver/api/multitenancy/DisassociateUserFromTenant.java +++ b/src/main/java/io/supertokens/webserver/api/multitenancy/DisassociateUserFromTenant.java @@ -21,7 +21,6 @@ import io.supertokens.Main; import io.supertokens.featureflag.exceptions.FeatureNotEnabledException; import io.supertokens.multitenancy.Multitenancy; -import io.supertokens.multitenancy.exception.DisassociationNotAllowedException; import io.supertokens.pluginInterface.RECIPE_ID; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; @@ -70,8 +69,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I } boolean wasAssociated = Multitenancy.removeUserIdFromTenant(main, - getTenantIdentifierWithStorageFromRequest(req), userId, externalUserId, getVersionFromRequest(req).greaterThanOrEqualTo( - SemVer.v4_0)); + getTenantIdentifierWithStorageFromRequest(req), userId, externalUserId); JsonObject result = new JsonObject(); result.addProperty("status", "OK"); @@ -82,11 +80,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I JsonObject result = new JsonObject(); result.addProperty("status", "UNKNOWN_USER_ID_ERROR"); super.sendJsonResponse(200, result, resp); - } catch (DisassociationNotAllowedException e) { - JsonObject result = new JsonObject(); - result.addProperty("status", "DISASSOCIATION_NOT_ALLOWED_ERROR"); - result.addProperty("reason", "The user belongs to only one tenant and cannot be disassociated from that"); - super.sendJsonResponse(200, result, resp); + } catch (StorageQueryException | TenantOrAppNotFoundException | FeatureNotEnabledException e) { throw new ServletException(e); } diff --git a/src/test/java/io/supertokens/test/accountlinking/MultitenantTest.java b/src/test/java/io/supertokens/test/accountlinking/MultitenantTest.java index ed1494005..3b63f2e2e 100644 --- a/src/test/java/io/supertokens/test/accountlinking/MultitenantTest.java +++ b/src/test/java/io/supertokens/test/accountlinking/MultitenantTest.java @@ -21,6 +21,7 @@ import io.supertokens.ProcessState; import io.supertokens.authRecipe.AuthRecipe; import io.supertokens.authRecipe.exception.AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException; +import io.supertokens.authRecipe.exception.RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException; import io.supertokens.emailpassword.EmailPassword; import io.supertokens.emailpassword.exceptions.EmailChangeNotAllowedException; import io.supertokens.emailpassword.exceptions.WrongCredentialsException; @@ -32,6 +33,7 @@ import io.supertokens.passwordless.Passwordless; import io.supertokens.passwordless.exceptions.PhoneNumberChangeNotAllowedException; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; +import io.supertokens.pluginInterface.authRecipe.LoginMethod; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.exceptions.InvalidConfigException; @@ -45,6 +47,7 @@ import io.supertokens.test.Utils; import io.supertokens.thirdparty.InvalidProviderConfigException; import io.supertokens.thirdparty.ThirdParty; +import io.supertokens.userroles.UserRoles; import org.junit.AfterClass; import org.junit.Before; import org.junit.Rule; @@ -237,6 +240,97 @@ public void testUserAreNotAutomaticallySharedBetweenTenantsOfLinkedAccountsForTP assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); } + @Test + public void testTenantDeletionWithAccountLinking() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + createTenants(process.getProcess()); + + t1 = new TenantIdentifier(null, "a1", null); + t2 = new TenantIdentifier(null, "a1", "t1"); + + TenantIdentifierWithStorage t1WithStorage = t1.withStorage(StorageLayer.getStorage(t1, process.getProcess())); + TenantIdentifierWithStorage t2WithStorage = t2.withStorage(StorageLayer.getStorage(t2, process.getProcess())); + + AuthRecipeUserInfo user1 = EmailPassword.signUp(t2WithStorage, process.getProcess(), "test@example.com", "password"); + AuthRecipeUserInfo user2 = ThirdParty.signInUp(t2WithStorage, process.getProcess(), "google", "googleid1", "test@example.com").user; + + AuthRecipe.createPrimaryUser(process.getProcess(), t2WithStorage.toAppIdentifierWithStorage(), user1.getSupertokensUserId()); + AuthRecipe.linkAccounts(process.getProcess(), t2WithStorage.toAppIdentifierWithStorage(), user2.getSupertokensUserId(), user1.getSupertokensUserId()); + + Multitenancy.deleteTenant(t2, process.getProcess()); + + AuthRecipeUserInfo getUser1 = AuthRecipe.getUserById(t1WithStorage.toAppIdentifierWithStorage(), user1.getSupertokensUserId()); + for (LoginMethod lm : getUser1.loginMethods) { + assertEquals(0, lm.tenantIds.size()); + } + + AuthRecipeUserInfo getUser2 = AuthRecipe.getUserById(t1WithStorage.toAppIdentifierWithStorage(), user2.getSupertokensUserId()); + for (LoginMethod lm : getUser2.loginMethods) { + assertEquals(0, lm.tenantIds.size()); + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void testTenantDeletionWithAccountLinkingWithUserRoles() throws Exception { + String[] args = {"../"}; + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{ + EE_FEATURES.ACCOUNT_LINKING, EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + createTenants(process.getProcess()); + + t1 = new TenantIdentifier(null, "a1", null); + t2 = new TenantIdentifier(null, "a1", "t1"); + + TenantIdentifierWithStorage t1WithStorage = t1.withStorage(StorageLayer.getStorage(t1, process.getProcess())); + TenantIdentifierWithStorage t2WithStorage = t2.withStorage(StorageLayer.getStorage(t2, process.getProcess())); + + AuthRecipeUserInfo user1 = EmailPassword.signUp(t2WithStorage, process.getProcess(), "test@example.com", "password"); + AuthRecipeUserInfo user2 = ThirdParty.signInUp(t2WithStorage, process.getProcess(), "google", "googleid1", "test@example.com").user; + + AuthRecipe.createPrimaryUser(process.getProcess(), t2WithStorage.toAppIdentifierWithStorage(), user1.getSupertokensUserId()); + AuthRecipe.linkAccounts(process.getProcess(), t2WithStorage.toAppIdentifierWithStorage(), user2.getSupertokensUserId(), user1.getSupertokensUserId()); + + UserRoles.createNewRoleOrModifyItsPermissions(t2WithStorage.toAppIdentifierWithStorage(), "admin", new String[]{"p1"}); + UserRoles.addRoleToUser(t2WithStorage, user1.getSupertokensUserId(), "admin"); + + Multitenancy.deleteTenant(t2, process.getProcess()); + + createTenants(process.getProcess()); // create the tenant again + + Multitenancy.addUserIdToTenant(process.getProcess(), t2WithStorage, user1.getSupertokensUserId()); // add the user to the tenant again + Multitenancy.addUserIdToTenant(process.getProcess(), t2WithStorage, user2.getSupertokensUserId()); // add the user to the tenant again + + AuthRecipeUserInfo getUser1 = AuthRecipe.getUserById(t1WithStorage.toAppIdentifierWithStorage(), user1.getSupertokensUserId()); + for (LoginMethod lm : getUser1.loginMethods) { + assertEquals(1, lm.tenantIds.size()); + } + + AuthRecipeUserInfo getUser2 = AuthRecipe.getUserById(t1WithStorage.toAppIdentifierWithStorage(), user2.getSupertokensUserId()); + for (LoginMethod lm : getUser2.loginMethods) { + assertEquals(1, lm.tenantIds.size()); + } + + String[] roles = UserRoles.getRolesForUser(t2WithStorage, user1.getSupertokensUserId()); + assertEquals(0, roles.length); // must be deleted with tenant + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + @Test public void testVariousCases() throws Exception { t1 = new TenantIdentifier(null, "a1", null); @@ -545,12 +639,12 @@ public void testVariousCases() throws Exception { new CreateThirdPartyUser(t1, "google", "googleid3", "test1@example.com").expect(new EmailChangeNotAllowedException()), }), new TestCase(new TestCaseStep[]{ - new CreateEmailPasswordUser(t1, "test@example.com"), - new CreateEmailPasswordUser(t1, "test2@example.com"), - new MakePrimaryUser(t1, 0), - new LinkAccounts(t1, 0, 1), - new UnlinkAccount(t1, 0), - new AssociateUserToTenant(t2, 0).expect(new UnknownUserIdException()), + new CreateEmailPasswordUser(t1, "test@example.com"), + new CreateEmailPasswordUser(t1, "test2@example.com"), + new MakePrimaryUser(t1, 0), + new LinkAccounts(t1, 0, 1), + new UnlinkAccount(t1, 0), + new AssociateUserToTenant(t2, 0).expect(new UnknownUserIdException()), }), new TestCase(new TestCaseStep[]{ @@ -570,6 +664,70 @@ public void testVariousCases() throws Exception { new UnlinkAccount(t1, 0), new AssociateUserToTenant(t2, 0).expect(new UnknownUserIdException()), }), + new TestCase(new TestCaseStep[]{ + new CreateEmailPasswordUser(t1, "test@example.com"), + new CreateEmailPasswordUser(t1, "test2@example.com"), + new MakePrimaryUser(t1, 0), + new LinkAccounts(t1, 0, 1), + new DisassociateUserFromTenant(t1, 0), + new AssociateUserToTenant(t2, 0), + new TestCaseStep() { + @Override + public void execute(Main main) throws Exception { + TenantIdentifierWithStorage t1WithStorage = t1.withStorage(StorageLayer.getStorage(t1, main)); + AuthRecipeUserInfo user = AuthRecipe.getUserById(t1WithStorage.toAppIdentifierWithStorage(), TestCase.users.get(0).getSupertokensUserId()); + assertEquals(2, user.loginMethods.length); + assertTrue(user.loginMethods[0].tenantIds.contains(t2.getTenantId())); + assertTrue(user.loginMethods[1].tenantIds.contains(t1.getTenantId())); + } + } + }), + + new TestCase(new TestCaseStep[]{ + new CreateEmailPasswordUser(t1, "test@example.com"), + new DisassociateUserFromTenant(t1, 0), + new CreateEmailPasswordUser(t1, "test@example.com"), + new DisassociateUserFromTenant(t1, 1), + new MakePrimaryUser(t1, 0), + new MakePrimaryUser(t1, 1), + new AssociateUserToTenant(t1, 0), + new AssociateUserToTenant(t1, 1).expect(new DuplicateEmailException()), + new LinkAccounts(t1, 0, 1).expect(new RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException(null, "")), + }), + + new TestCase(new TestCaseStep[]{ + new CreateEmailPasswordUser(t1, "test@example.com"), + new DisassociateUserFromTenant(t1, 0), + new CreateEmailPasswordUser(t1, "test@example.com"), + new DisassociateUserFromTenant(t1, 1), + new MakePrimaryUser(t1, 0), + new AssociateUserToTenant(t1, 0), + new LinkAccounts(t1, 0, 1), + new AssociateUserToTenant(t1, 1).expect(new DuplicateEmailException()), + new AssociateUserToTenant(t2, 1), + }), + new TestCase(new TestCaseStep[]{ + new CreateEmailPasswordUser(t1, "test1@example.com"), + new CreateEmailPasswordUser(t1, "test2@example.com"), + new MakePrimaryUser(t1, 0), + new LinkAccounts(t1, 0, 1), + new UnlinkAccount(t1, 0), + new TestCaseStep() { + @Override + public void execute(Main main) throws Exception { + TenantIdentifierWithStorage t1WithStorage = t1.withStorage(StorageLayer.getStorage(t1, main)); + AuthRecipe.deleteUser(t1WithStorage.toAppIdentifierWithStorage(), TestCase.users.get(1).getSupertokensUserId()); + } + }, + new TestCaseStep() { + @Override + public void execute(Main main) throws Exception { + TenantIdentifierWithStorage t1WithStorage = t1.withStorage(StorageLayer.getStorage(t1, main)); + AuthRecipeUserInfo user = AuthRecipe.getUserById(t1WithStorage.toAppIdentifierWithStorage(), TestCase.users.get(0).getSupertokensUserId()); + assertNull(user); + } + } + }), }; int i = 0; @@ -618,7 +776,7 @@ public void doTest(Main main) throws Exception { } } - private static class TestCaseStep { + private static abstract class TestCaseStep { Exception e; public TestCaseStep expect(Exception e) { @@ -639,8 +797,7 @@ public void doStep(Main main) throws Exception { } } - public void execute(Main main) throws Exception { - } + abstract public void execute(Main main) throws Exception; } private static class CreateEmailPasswordUser extends TestCaseStep { @@ -843,15 +1000,31 @@ private static class SignInEmailPasswordUser extends TestCaseStep { TenantIdentifier tenantIdentifier; int userIndex; - public SignInEmailPasswordUser(TenantIdentifier tenantIdentifier, int userIndex) { - this.tenantIdentifier = tenantIdentifier; - this.userIndex = userIndex; - } + public SignInEmailPasswordUser(TenantIdentifier tenantIdentifier, int userIndex) { + this.tenantIdentifier = tenantIdentifier; + this.userIndex = userIndex; + } - @Override - public void execute(Main main) throws Exception { - TenantIdentifierWithStorage tenantIdentifierWithStorage = tenantIdentifier.withStorage(StorageLayer.getStorage(tenantIdentifier, main)); - EmailPassword.signIn(tenantIdentifierWithStorage, main, TestCase.users.get(userIndex).loginMethods[0].email, "password"); - } + @Override + public void execute(Main main) throws Exception { + TenantIdentifierWithStorage tenantIdentifierWithStorage = tenantIdentifier.withStorage(StorageLayer.getStorage(tenantIdentifier, main)); + EmailPassword.signIn(tenantIdentifierWithStorage, main, TestCase.users.get(userIndex).loginMethods[0].email, "password"); + } + } + + private static class DisassociateUserFromTenant extends TestCaseStep { + TenantIdentifier tenantIdentifier; + int userIndex; + + public DisassociateUserFromTenant(TenantIdentifier tenantIdentifier, int userIndex) { + this.tenantIdentifier = tenantIdentifier; + this.userIndex = userIndex; + } + + @Override + public void execute(Main main) throws Exception { + TenantIdentifierWithStorage tenantIdentifierWithStorage = tenantIdentifier.withStorage(StorageLayer.getStorage(tenantIdentifier, main)); + Multitenancy.removeUserIdFromTenant(main, tenantIdentifierWithStorage, TestCase.users.get(userIndex).getSupertokensUserId(), null); + } } } diff --git a/src/test/java/io/supertokens/test/emailpassword/api/MultitenantAPITest.java b/src/test/java/io/supertokens/test/emailpassword/api/MultitenantAPITest.java index 7953013d6..cc4ff6fdf 100644 --- a/src/test/java/io/supertokens/test/emailpassword/api/MultitenantAPITest.java +++ b/src/test/java/io/supertokens/test/emailpassword/api/MultitenantAPITest.java @@ -718,27 +718,4 @@ public void testThatTenantIdIsNotAllowedForOlderCDIVersion() throws Exception { assertEquals(404, e.statusCode); } } - - @Test - public void testThatDisassociationFromAllTenantsIsDisallowed() throws Exception { - if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { - return; - } - - createTenants(false); - - { - JsonObject user = TestMultitenancyAPIHelper.epSignUp(t1, "test@example.com", "password", process.getProcess()); - { - JsonObject requestBody = new JsonObject(); - requestBody.addProperty("userId", user.get("id").getAsString()); - - JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", - HttpRequestForTesting.getMultitenantUrl(t1, "/recipe/multitenancy/tenant/user/remove"), - requestBody, 1000, 1000, null, - SemVer.v4_0.get(), "multitenancy"); - assertEquals("DISASSOCIATION_NOT_ALLOWED_ERROR", response.get("status").getAsString()); - } - } - } } diff --git a/src/test/java/io/supertokens/test/multitenant/api/TestMultitenancyAPIHelper.java b/src/test/java/io/supertokens/test/multitenant/api/TestMultitenancyAPIHelper.java index 2338576ed..fd249ac7a 100644 --- a/src/test/java/io/supertokens/test/multitenant/api/TestMultitenancyAPIHelper.java +++ b/src/test/java/io/supertokens/test/multitenant/api/TestMultitenancyAPIHelper.java @@ -24,6 +24,7 @@ import io.supertokens.test.httpRequest.HttpRequestForTesting; import io.supertokens.test.httpRequest.HttpResponseException; import io.supertokens.utils.SemVer; +import io.supertokens.webserver.WebserverAPI; import java.io.IOException; import java.util.HashMap; @@ -207,7 +208,7 @@ public static JsonObject associateUserToTenant(TenantIdentifier tenantIdentifier JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(main, "", HttpRequestForTesting.getMultitenantUrl(tenantIdentifier, "/recipe/multitenancy/tenant/user"), requestBody, 1000, 1000, null, - SemVer.v3_0.get(), "multitenancy"); + WebserverAPI.getLatestCDIVersion().get(), "multitenancy"); return response; } @@ -220,7 +221,7 @@ public static JsonObject disassociateUserFromTenant(TenantIdentifier tenantIdent JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(main, "", HttpRequestForTesting.getMultitenantUrl(tenantIdentifier, "/recipe/multitenancy/tenant/user/remove"), requestBody, 1000, 1000, null, - SemVer.v3_0.get(), "multitenancy"); + WebserverAPI.getLatestCDIVersion().get(), "multitenancy"); assertEquals("OK", response.getAsJsonPrimitive("status").getAsString()); return response; From 3bdc24477336a378cab6b04dd8e4d9f391cf9111 Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Wed, 13 Sep 2023 13:44:30 +0530 Subject: [PATCH 114/131] fix: rename userId to recipeUserId --- .../api/multitenancy/AssociateUserToTenantAPI.java | 9 ++++++++- .../api/multitenancy/DisassociateUserFromTenant.java | 8 +++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/supertokens/webserver/api/multitenancy/AssociateUserToTenantAPI.java b/src/main/java/io/supertokens/webserver/api/multitenancy/AssociateUserToTenantAPI.java index 3eb502dc5..739d182bd 100644 --- a/src/main/java/io/supertokens/webserver/api/multitenancy/AssociateUserToTenantAPI.java +++ b/src/main/java/io/supertokens/webserver/api/multitenancy/AssociateUserToTenantAPI.java @@ -32,6 +32,7 @@ import io.supertokens.pluginInterface.passwordless.exception.DuplicatePhoneNumberException; import io.supertokens.pluginInterface.thirdparty.exception.DuplicateThirdPartyUserException; import io.supertokens.useridmapping.UserIdType; +import io.supertokens.utils.SemVer; import io.supertokens.webserver.InputParser; import io.supertokens.webserver.WebserverAPI; import jakarta.servlet.ServletException; @@ -56,7 +57,13 @@ public String getPath() { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { JsonObject input = InputParser.parseJsonObjectOrThrowError(req); - String userId = InputParser.parseStringOrThrowError(input, "userId", false); + String userId; + + if (getVersionFromRequest(req).lesserThan(SemVer.v4_0)) { + userId = InputParser.parseStringOrThrowError(input, "userId", false); + } else { + userId = InputParser.parseStringOrThrowError(input, "recipeUserId", false); + } // normalize userId userId = userId.trim(); diff --git a/src/main/java/io/supertokens/webserver/api/multitenancy/DisassociateUserFromTenant.java b/src/main/java/io/supertokens/webserver/api/multitenancy/DisassociateUserFromTenant.java index 9553efa91..a37c3a595 100644 --- a/src/main/java/io/supertokens/webserver/api/multitenancy/DisassociateUserFromTenant.java +++ b/src/main/java/io/supertokens/webserver/api/multitenancy/DisassociateUserFromTenant.java @@ -50,7 +50,13 @@ public String getPath() { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { JsonObject input = InputParser.parseJsonObjectOrThrowError(req); - String userId = InputParser.parseStringOrThrowError(input, "userId", false); + String userId; + + if (getVersionFromRequest(req).lesserThan(SemVer.v4_0)) { + userId = InputParser.parseStringOrThrowError(input, "userId", false); + } else { + userId = InputParser.parseStringOrThrowError(input, "recipeUserId", false); + } // normalize userId userId = userId.trim(); From 106ad73ee82291e48c8a2e40882ff99ea80a413a Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Wed, 13 Sep 2023 14:34:33 +0530 Subject: [PATCH 115/131] fix: function changes (#802) * fix: account function changes * fix: function updates --- .../java/io/supertokens/inmemorydb/Start.java | 57 +++++++++++--- .../queries/ActiveUsersQueries.java | 5 +- .../queries/EmailPasswordQueries.java | 44 ++++++----- .../queries/EmailVerificationQueries.java | 46 +++++------ .../inmemorydb/queries/GeneralQueries.java | 74 ++++++++++++------ .../queries/PasswordlessQueries.java | 78 ++++++++++--------- .../inmemorydb/queries/SessionQueries.java | 12 +++ .../inmemorydb/queries/ThirdPartyQueries.java | 44 ++++++----- .../queries/UserMetadataQueries.java | 12 +++ .../inmemorydb/queries/UserRoleQueries.java | 4 +- 10 files changed, 240 insertions(+), 136 deletions(-) diff --git a/src/main/java/io/supertokens/inmemorydb/Start.java b/src/main/java/io/supertokens/inmemorydb/Start.java index 53e6b831a..212cd0576 100644 --- a/src/main/java/io/supertokens/inmemorydb/Start.java +++ b/src/main/java/io/supertokens/inmemorydb/Start.java @@ -534,7 +534,12 @@ public void updateSessionInfo_Transaction(TenantIdentifier tenantIdentifier, Tra @Override public void deleteSessionsOfUser_Transaction(TransactionConnection con, AppIdentifier appIdentifier, String userId) throws StorageQueryException { - // TODO:.. + Connection sqlCon = (Connection) con.getConnection(); + try { + SessionQueries.deleteSessionsOfUser_Transaction(sqlCon, this, appIdentifier, userId); + } catch (SQLException e) { + throw new StorageQueryException(e); + } } @Override @@ -871,7 +876,12 @@ public void updateUsersEmail_Transaction(AppIdentifier appIdentifier, Transactio public void deleteEmailPasswordUser_Transaction(TransactionConnection con, AppIdentifier appIdentifier, String userId, boolean deleteUserIdMappingToo) throws StorageQueryException { - // TODO.. + try { + Connection sqlCon = (Connection) con.getConnection(); + EmailPasswordQueries.deleteUser_Transaction(sqlCon, this, appIdentifier, userId, deleteUserIdMappingToo); + } catch (SQLException e) { + throw new StorageQueryException(e); + } } @Override @@ -949,7 +959,12 @@ public void updateIsEmailVerified_Transaction(AppIdentifier appIdentifier, Trans @Override public void deleteEmailVerificationUserInfo_Transaction(TransactionConnection con, AppIdentifier appIdentifier, String userId) throws StorageQueryException { - // TODO.. + try { + Connection sqlCon = (Connection) con.getConnection(); + EmailVerificationQueries.deleteUserInfo_Transaction(sqlCon, this, appIdentifier, userId); + } catch (SQLException e) { + throw new StorageQueryException(e); + } } @Override @@ -1070,7 +1085,12 @@ public void updateUserEmail_Transaction(AppIdentifier appIdentifier, Transaction public void deleteThirdPartyUser_Transaction(TransactionConnection con, AppIdentifier appIdentifier, String userId, boolean deleteUserIdMappingToo) throws StorageQueryException { - // TODO.. + try { + Connection sqlCon = (Connection) con.getConnection(); + ThirdPartyQueries.deleteUser_Transaction(sqlCon, this, appIdentifier, userId, deleteUserIdMappingToo); + } catch (SQLException e) { + throw new StorageQueryException(e); + } } @Override @@ -1203,7 +1223,12 @@ public int countUsersEnabledTotpAndActiveSince(AppIdentifier appIdentifier, long @Override public void deleteUserActive_Transaction(TransactionConnection con, AppIdentifier appIdentifier, String userId) throws StorageQueryException { - // TODO:.. + try { + Connection sqlCon = (Connection) con.getConnection(); + ActiveUsersQueries.deleteUserActive_Transaction(sqlCon, this, appIdentifier, userId); + } catch (SQLException e) { + throw new StorageQueryException(e); + } } @Override @@ -1539,7 +1564,12 @@ public void updateUserPhoneNumber_Transaction(AppIdentifier appIdentifier, Trans public void deletePasswordlessUser_Transaction(TransactionConnection con, AppIdentifier appIdentifier, String userId, boolean deleteUserIdMappingToo) throws StorageQueryException { - // TODO.. + try { + Connection sqlCon = (Connection) con.getConnection(); + PasswordlessQueries.deleteUser_Transaction(sqlCon, this, appIdentifier, userId, deleteUserIdMappingToo); + } catch (SQLException e) { + throw new StorageQueryException(e); + } } @Override @@ -1795,8 +1825,12 @@ public int setUserMetadata_Transaction(AppIdentifier appIdentifier, TransactionC @Override public int deleteUserMetadata_Transaction(TransactionConnection con, AppIdentifier appIdentifier, String userId) throws StorageQueryException { - // TODO:.. - return 0; + try { + Connection sqlCon = (Connection) con.getConnection(); + return UserMetadataQueries.deleteUserMetadata_Transaction(sqlCon, this, appIdentifier, userId); + } catch (SQLException e) { + throw new StorageQueryException(e); + } } @Override @@ -2032,7 +2066,12 @@ public boolean doesRoleExist_Transaction(AppIdentifier appIdentifier, Transactio @Override public void deleteAllRolesForUser_Transaction(TransactionConnection con, AppIdentifier appIdentifier, String userId) throws StorageQueryException { - // TODO.. + try { + Connection sqlCon = (Connection) con.getConnection(); + UserRoleQueries.deleteAllRolesForUser_Transaction(sqlCon, this, appIdentifier, userId); + } catch (SQLException e) { + throw new StorageQueryException(e); + } } @Override diff --git a/src/main/java/io/supertokens/inmemorydb/queries/ActiveUsersQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/ActiveUsersQueries.java index 7ca5d26b1..af8732478 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/ActiveUsersQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/ActiveUsersQueries.java @@ -1,5 +1,6 @@ package io.supertokens.inmemorydb.queries; +import java.sql.Connection; import java.sql.SQLException; import io.supertokens.inmemorydb.config.Config; @@ -101,12 +102,12 @@ public static Long getLastActiveByUserId(Start start, AppIdentifier appIdentifie } } - public static void deleteUserActive(Start start, AppIdentifier appIdentifier, String userId) + public static void deleteUserActive_Transaction(Connection con, Start start, AppIdentifier appIdentifier, String userId) throws StorageQueryException, SQLException { String QUERY = "DELETE FROM " + Config.getConfig(start).getUserLastActiveTable() + " WHERE app_id = ? AND user_id = ?"; - update(start, QUERY, pst -> { + update(con, QUERY, pst -> { pst.setString(1, appIdentifier.getAppId()); pst.setString(2, userId); }); diff --git a/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java index ee8db83fc..003a100b3 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java @@ -294,27 +294,35 @@ public static AuthRecipeUserInfo signUp(Start start, TenantIdentifier tenantIden }); } - public static void deleteUser(Start start, AppIdentifier appIdentifier, String userId) - throws StorageQueryException, StorageTransactionLogicException { - start.startTransaction(con -> { - Connection sqlCon = (Connection) con.getConnection(); - try { - { - String QUERY = "DELETE FROM " + getConfig(start).getAppIdToUserIdTable() - + " WHERE app_id = ? AND user_id = ?"; + public static void deleteUser_Transaction(Connection sqlCon, Start start, AppIdentifier appIdentifier, String userId, boolean deleteUserIdMappingToo) + throws StorageQueryException, SQLException { + if (deleteUserIdMappingToo) { + String QUERY = "DELETE FROM " + getConfig(start).getAppIdToUserIdTable() + + " WHERE app_id = ? AND user_id = ?"; - update(sqlCon, QUERY, pst -> { - pst.setString(1, appIdentifier.getAppId()); - pst.setString(2, userId); - }); - } + update(sqlCon, QUERY, pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setString(2, userId); + }); + } else { + { + String QUERY = "DELETE FROM " + getConfig(start).getUsersTable() + + " WHERE app_id = ? AND user_id = ?"; + update(sqlCon, QUERY, pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setString(2, userId); + }); + } - sqlCon.commit(); - } catch (SQLException throwables) { - throw new StorageTransactionLogicException(throwables); + { + String QUERY = "DELETE FROM " + getConfig(start).getEmailPasswordUsersTable() + + " WHERE app_id = ? AND user_id = ?"; + update(sqlCon, QUERY, pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setString(2, userId); + }); } - return null; - }); + } } public static UserInfoPartial getUserInfoUsingId(Start start, Connection sqlCon, AppIdentifier appIdentifier, diff --git a/src/main/java/io/supertokens/inmemorydb/queries/EmailVerificationQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/EmailVerificationQueries.java index 777a28bab..2c5d2c207 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/EmailVerificationQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/EmailVerificationQueries.java @@ -278,36 +278,26 @@ public static List isEmailVerified_transaction(Start start, Connection s }); } - public static void deleteUserInfo(Start start, AppIdentifier appIdentifier, String userId) - throws StorageQueryException, StorageTransactionLogicException { - start.startTransaction(con -> { - Connection sqlCon = (Connection) con.getConnection(); - try { - { - String QUERY = "DELETE FROM " + getConfig(start).getEmailVerificationTable() - + " WHERE app_id = ? AND user_id = ?"; - update(sqlCon, QUERY, pst -> { - pst.setString(1, appIdentifier.getAppId()); - pst.setString(2, userId); - }); - } - - { - String QUERY = "DELETE FROM " + getConfig(start).getEmailVerificationTokensTable() - + " WHERE app_id = ? AND user_id = ?"; + public static void deleteUserInfo_Transaction(Connection sqlCon, Start start, AppIdentifier appIdentifier, String userId) + throws StorageQueryException, SQLException { + { + String QUERY = "DELETE FROM " + getConfig(start).getEmailVerificationTable() + + " WHERE app_id = ? AND user_id = ?"; + update(sqlCon, QUERY, pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setString(2, userId); + }); + } - update(sqlCon, QUERY, pst -> { - pst.setString(1, appIdentifier.getAppId()); - pst.setString(2, userId); - }); - } + { + String QUERY = "DELETE FROM " + getConfig(start).getEmailVerificationTokensTable() + + " WHERE app_id = ? AND user_id = ?"; - sqlCon.commit(); - } catch (SQLException throwables) { - throw new StorageTransactionLogicException(throwables); - } - return null; - }); + update(sqlCon, QUERY, pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setString(2, userId); + }); + } } public static boolean deleteUserInfo(Start start, TenantIdentifier tenantIdentifier, String userId) diff --git a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java index 933618843..0cfbcc447 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java @@ -20,6 +20,7 @@ import io.supertokens.inmemorydb.ConnectionPool; import io.supertokens.inmemorydb.ConnectionWithLocks; import io.supertokens.inmemorydb.Start; +import io.supertokens.inmemorydb.Utils; import io.supertokens.inmemorydb.config.Config; import io.supertokens.pluginInterface.KeyValueInfo; import io.supertokens.pluginInterface.RECIPE_ID; @@ -1030,50 +1031,72 @@ public static AuthRecipeUserInfo getPrimaryUserInfoForUserId_Transaction(Start s // We use SELECT FOR UPDATE in the query below for other plugins. String QUERY = "SELECT * FROM " + getConfig(start).getUsersTable() + - " WHERE (user_id = ? OR primary_or_recipe_user_id = ?) AND app_id = ?"; + " WHERE primary_or_recipe_user_id IN (SELECT primary_or_recipe_user_id FROM " + + getConfig(start).getUsersTable() + + " WHERE user_id = ? OR primary_or_recipe_user_id = ? AND app_id = ?) AND app_id = ?"; - AllAuthRecipeUsersResultHolder allAuthUsersResult = execute(sqlCon, QUERY, pst -> { + List allAuthUsersResult = execute(sqlCon, QUERY, pst -> { pst.setString(1, id); pst.setString(2, id); pst.setString(3, appIdentifier.getAppId()); }, result -> { - AllAuthRecipeUsersResultHolder finalResult = null; - if (result.next()) { - finalResult = new AllAuthRecipeUsersResultHolder(result.getString("user_id"), + List finalResult = new ArrayList<>(); + while (result.next()) { + finalResult.add(new AllAuthRecipeUsersResultHolder(result.getString("user_id"), result.getString("tenant_id"), result.getString("primary_or_recipe_user_id"), result.getBoolean("is_linked_or_is_a_primary_user"), result.getString("recipe_id"), - result.getLong("time_joined")); + result.getLong("time_joined"))); } return finalResult; }); - if (allAuthUsersResult == null) { + if (allAuthUsersResult.size() == 0) { return null; } // Now we form the userIds again, but based on the user_id in the result from above. Set recipeUserIdsToFetch = new HashSet<>(); - recipeUserIdsToFetch.add(allAuthUsersResult.userId); + for (AllAuthRecipeUsersResultHolder user : allAuthUsersResult) { + // this will remove duplicate entries wherein a user id is shared across several tenants. + recipeUserIdsToFetch.add(user.userId); + } List loginMethods = new ArrayList<>(); loginMethods.addAll( - EmailPasswordQueries.getUsersInfoUsingIdList(start, sqlCon, recipeUserIdsToFetch, - appIdentifier)); - loginMethods.addAll(ThirdPartyQueries.getUsersInfoUsingIdList(start, sqlCon, recipeUserIdsToFetch, - appIdentifier)); - loginMethods.addAll(PasswordlessQueries.getUsersInfoUsingIdList(start, sqlCon, recipeUserIdsToFetch, - appIdentifier)); + EmailPasswordQueries.getUsersInfoUsingIdList(start, sqlCon, recipeUserIdsToFetch, appIdentifier)); + loginMethods.addAll( + ThirdPartyQueries.getUsersInfoUsingIdList(start, sqlCon, recipeUserIdsToFetch, appIdentifier)); + loginMethods.addAll( + PasswordlessQueries.getUsersInfoUsingIdList(start, sqlCon, recipeUserIdsToFetch, appIdentifier)); - // we do this in such a strange way cause the create function takes just one login method at the moment. - AuthRecipeUserInfo result = AuthRecipeUserInfo.create(allAuthUsersResult.primaryOrRecipeUserId, - allAuthUsersResult.isLinkedOrIsAPrimaryUser, loginMethods.get(0)); - for (int i = 1; i < loginMethods.size(); i++) { - result.addLoginMethod(loginMethods.get(i)); + Map recipeUserIdToLoginMethodMap = new HashMap<>(); + for (LoginMethod loginMethod : loginMethods) { + recipeUserIdToLoginMethodMap.put(loginMethod.getSupertokensUserId(), loginMethod); } - return result; + Map userIdToAuthRecipeUserInfo = new HashMap<>(); + String pUserId = null; + for (AllAuthRecipeUsersResultHolder authRecipeUsersResultHolder : allAuthUsersResult) { + String recipeUserId = authRecipeUsersResultHolder.userId; + LoginMethod loginMethod = recipeUserIdToLoginMethodMap.get(recipeUserId); + assert (loginMethod != null); + String primaryUserId = authRecipeUsersResultHolder.primaryOrRecipeUserId; + pUserId = primaryUserId; + AuthRecipeUserInfo curr = userIdToAuthRecipeUserInfo.get(primaryUserId); + if (curr == null) { + curr = AuthRecipeUserInfo.create(primaryUserId, authRecipeUsersResultHolder.isLinkedOrIsAPrimaryUser, + loginMethod); + } else { + curr.addLoginMethod(loginMethod); + } + userIdToAuthRecipeUserInfo.put(primaryUserId, curr); + } + + assert (userIdToAuthRecipeUserInfo.size() == 1 && pUserId != null); + + return userIdToAuthRecipeUserInfo.get(pUserId); } public static AuthRecipeUserInfo getPrimaryUserInfoForUserId(Start start, AppIdentifier appIdentifier, String id) @@ -1108,11 +1131,13 @@ private static List getPrimaryUserInfoForUserIds(Start start // which is linked to a primary user ID in which case it won't be in the primary_or_recipe_user_id column, // or the input may have a primary user ID whose recipe user ID was removed, so it won't be in the user_id // column - String QUERY = "SELECT * FROM " + getConfig(start).getUsersTable() + " WHERE (user_id IN (" - + io.supertokens.inmemorydb.Utils.generateCommaSeperatedQuestionMarks(userIds.size()) + + String QUERY = "SELECT * FROM " + getConfig(start).getUsersTable() + + " WHERE primary_or_recipe_user_id IN (SELECT primary_or_recipe_user_id FROM " + + getConfig(start).getUsersTable() + " WHERE (user_id IN (" + + Utils.generateCommaSeperatedQuestionMarks(userIds.size()) + ") OR primary_or_recipe_user_id IN (" + - io.supertokens.inmemorydb.Utils.generateCommaSeperatedQuestionMarks(userIds.size()) + - ")) AND app_id = ?"; + Utils.generateCommaSeperatedQuestionMarks(userIds.size()) + + ")) AND app_id = ?) AND app_id = ?"; List allAuthUsersResult = execute(con, QUERY, pst -> { // IN user_id @@ -1126,6 +1151,7 @@ private static List getPrimaryUserInfoForUserIds(Start start } // for app_id pst.setString(index, appIdentifier.getAppId()); + pst.setString(index + 1, appIdentifier.getAppId()); }, result -> { List parsedResult = new ArrayList<>(); while (result.next()) { diff --git a/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java index 31736e30e..80ca321e9 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java @@ -460,46 +460,54 @@ private static UserInfoWithTenantId[] getUserInfosWithTenant(Start start, Connec }); } - public static void deleteUser(Start start, AppIdentifier appIdentifier, String userId) - throws StorageQueryException, StorageTransactionLogicException { - start.startTransaction(con -> { - Connection sqlCon = (Connection) con.getConnection(); - try { - UserInfoWithTenantId[] userInfos = getUserInfosWithTenant(start, sqlCon, appIdentifier, userId); + public static void deleteUser_Transaction(Connection sqlCon, Start start, AppIdentifier appIdentifier, String userId, boolean deleteUserIdMappingToo) + throws StorageQueryException, SQLException { + UserInfoWithTenantId[] userInfos = getUserInfosWithTenant(start, sqlCon, appIdentifier, userId); - { - String QUERY = "DELETE FROM " + getConfig(start).getAppIdToUserIdTable() - + " WHERE app_id = ? AND user_id = ?"; + if (deleteUserIdMappingToo) { + String QUERY = "DELETE FROM " + getConfig(start).getAppIdToUserIdTable() + + " WHERE app_id = ? AND user_id = ?"; - update(sqlCon, QUERY, pst -> { - pst.setString(1, appIdentifier.getAppId()); - pst.setString(2, userId); - }); - } + update(sqlCon, QUERY, pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setString(2, userId); + }); + } else { + { + String QUERY = "DELETE FROM " + getConfig(start).getUsersTable() + + " WHERE app_id = ? AND user_id = ?"; + update(sqlCon, QUERY, pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setString(2, userId); + }); + } - for (UserInfoWithTenantId userInfo : userInfos) { - if (userInfo.email != null) { - deleteDevicesByEmail_Transaction(start, sqlCon, - new TenantIdentifier( - appIdentifier.getConnectionUriDomain(), appIdentifier.getAppId(), - userInfo.tenantId), - userInfo.email); - } - if (userInfo.phoneNumber != null) { - deleteDevicesByPhoneNumber_Transaction(start, sqlCon, - new TenantIdentifier( - appIdentifier.getConnectionUriDomain(), appIdentifier.getAppId(), - userInfo.tenantId), - userInfo.phoneNumber); - } - } + { + String QUERY = "DELETE FROM " + getConfig(start).getPasswordlessUsersTable() + + " WHERE app_id = ? AND user_id = ?"; + update(sqlCon, QUERY, pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setString(2, userId); + }); + } + } - sqlCon.commit(); - } catch (SQLException throwables) { - throw new StorageTransactionLogicException(throwables); + for (UserInfoWithTenantId userInfo : userInfos) { + if (userInfo.email != null) { + deleteDevicesByEmail_Transaction(start, sqlCon, + new TenantIdentifier( + appIdentifier.getConnectionUriDomain(), appIdentifier.getAppId(), + userInfo.tenantId), + userInfo.email); } - return null; - }); + if (userInfo.phoneNumber != null) { + deleteDevicesByPhoneNumber_Transaction(start, sqlCon, + new TenantIdentifier( + appIdentifier.getConnectionUriDomain(), appIdentifier.getAppId(), + userInfo.tenantId), + userInfo.phoneNumber); + } + } } public static int updateUserEmail_Transaction(Start start, Connection con, AppIdentifier appIdentifier, diff --git a/src/main/java/io/supertokens/inmemorydb/queries/SessionQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/SessionQueries.java index 3e21293de..dda532612 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/SessionQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/SessionQueries.java @@ -180,6 +180,18 @@ public static int deleteSession(Start start, TenantIdentifier tenantIdentifier, }); } + public static void deleteSessionsOfUser_Transaction(Connection sqlCon, Start start, AppIdentifier appIdentifier, + String userId) + throws SQLException, StorageQueryException { + String QUERY = "DELETE FROM " + getConfig(start).getSessionInfoTable() + + " WHERE app_id = ? AND user_id = ?"; + + update(sqlCon, QUERY.toString(), pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setString(2, userId); + }); + } + public static void deleteSessionsOfUser(Start start, AppIdentifier appIdentifier, String userId) throws SQLException, StorageQueryException { String QUERY = "DELETE FROM " + getConfig(start).getSessionInfoTable() diff --git a/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java index 27a691ce9..cf76f9306 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java @@ -152,27 +152,35 @@ public static AuthRecipeUserInfo signUp(Start start, TenantIdentifier tenantIden }); } - public static void deleteUser(Start start, AppIdentifier appIdentifier, String userId) - throws StorageQueryException, StorageTransactionLogicException { - start.startTransaction(con -> { - Connection sqlCon = (Connection) con.getConnection(); - try { - { - String QUERY = "DELETE FROM " + getConfig(start).getAppIdToUserIdTable() - + " WHERE app_id = ? AND user_id = ?"; + public static void deleteUser_Transaction(Connection sqlCon, Start start, AppIdentifier appIdentifier, String userId, boolean deleteUserIdMappingToo) + throws StorageQueryException, SQLException { + if (deleteUserIdMappingToo) { + String QUERY = "DELETE FROM " + getConfig(start).getAppIdToUserIdTable() + + " WHERE app_id = ? AND user_id = ?"; - update(sqlCon, QUERY, pst -> { - pst.setString(1, appIdentifier.getAppId()); - pst.setString(2, userId); - }); - } + update(sqlCon, QUERY, pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setString(2, userId); + }); + } else { + { + String QUERY = "DELETE FROM " + getConfig(start).getUsersTable() + + " WHERE app_id = ? AND user_id = ?"; + update(sqlCon, QUERY, pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setString(2, userId); + }); + } - sqlCon.commit(); - } catch (SQLException throwables) { - throw new StorageTransactionLogicException(throwables); + { + String QUERY = "DELETE FROM " + getConfig(start).getThirdPartyUsersTable() + + " WHERE app_id = ? AND user_id = ?"; + update(sqlCon, QUERY, pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setString(2, userId); + }); } - return null; - }); + } } public static List lockEmailAndTenant_Transaction(Start start, Connection con, diff --git a/src/main/java/io/supertokens/inmemorydb/queries/UserMetadataQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/UserMetadataQueries.java index 2fb93b620..4d673562f 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/UserMetadataQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/UserMetadataQueries.java @@ -58,6 +58,18 @@ public static int deleteUserMetadata(Start start, AppIdentifier appIdentifier, S }); } + public static int deleteUserMetadata_Transaction(Connection sqlCon, Start start, AppIdentifier appIdentifier, + String userId) + throws SQLException, StorageQueryException { + String QUERY = "DELETE FROM " + getConfig(start).getUserMetadataTable() + + " WHERE app_id = ? AND user_id = ?"; + + return update(sqlCon, QUERY.toString(), pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setString(2, userId); + }); + } + public static int setUserMetadata_Transaction(Start start, Connection con, AppIdentifier appIdentifier, String userId, JsonObject metadata) throws SQLException, StorageQueryException { diff --git a/src/main/java/io/supertokens/inmemorydb/queries/UserRoleQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/UserRoleQueries.java index 028a1e459..0b7bba5b2 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/UserRoleQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/UserRoleQueries.java @@ -324,10 +324,10 @@ public static int deleteAllRolesForUser(Start start, TenantIdentifier tenantIden }); } - public static int deleteAllRolesForUser(Start start, AppIdentifier appIdentifier, String userId) throws SQLException, StorageQueryException { + public static int deleteAllRolesForUser_Transaction(Connection con, Start start, AppIdentifier appIdentifier, String userId) throws SQLException, StorageQueryException { String QUERY = "DELETE FROM " + getConfig(start).getUserRolesTable() + " WHERE app_id = ? AND user_id = ?"; - return update(start, QUERY, pst -> { + return update(con, QUERY, pst -> { pst.setString(1, appIdentifier.getAppId()); pst.setString(2, userId); }); From 8daf1d1ea3c6058e928cf185be043807c2ad968e Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Wed, 13 Sep 2023 15:25:38 +0530 Subject: [PATCH 116/131] fix: link and unlink accounts (#803) --- .../java/io/supertokens/inmemorydb/Start.java | 18 +++++++++++-- .../inmemorydb/queries/GeneralQueries.java | 27 +++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/supertokens/inmemorydb/Start.java b/src/main/java/io/supertokens/inmemorydb/Start.java index 212cd0576..91f3daa1d 100644 --- a/src/main/java/io/supertokens/inmemorydb/Start.java +++ b/src/main/java/io/supertokens/inmemorydb/Start.java @@ -2865,13 +2865,27 @@ public void makePrimaryUser_Transaction(AppIdentifier appIdentifier, Transaction @Override public void linkAccounts_Transaction(AppIdentifier appIdentifier, TransactionConnection con, String recipeUserId, String primaryUserId) throws StorageQueryException { - // TODO:... + try { + Connection sqlCon = (Connection) con.getConnection(); + // we do not bother returning if a row was updated here or not, cause it's happening + // in a transaction anyway. + GeneralQueries.linkAccounts_Transaction(this, sqlCon, appIdentifier, recipeUserId, primaryUserId); + } catch (SQLException e) { + throw new StorageQueryException(e); + } } @Override public void unlinkAccounts_Transaction(AppIdentifier appIdentifier, TransactionConnection con, String primaryUserId, String recipeUserId) throws StorageQueryException { - // TODO:.. + try { + Connection sqlCon = (Connection) con.getConnection(); + // we do not bother returning if a row was updated here or not, cause it's happening + // in a transaction anyway. + GeneralQueries.unlinkAccounts_Transaction(this, sqlCon, appIdentifier, recipeUserId); + } catch (SQLException e) { + throw new StorageQueryException(e); + } } @Override diff --git a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java index 0cfbcc447..eee427e20 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java @@ -879,6 +879,33 @@ public static void makePrimaryUser_Transaction(Start start, Connection sqlCon, A }); } + public static void linkAccounts_Transaction(Start start, Connection sqlCon, AppIdentifier appIdentifier, + String recipeUserId, String primaryUserId) + throws SQLException, StorageQueryException { + String QUERY = "UPDATE " + getConfig(start).getUsersTable() + + " SET is_linked_or_is_a_primary_user = true, primary_or_recipe_user_id = ? WHERE app_id = ? AND " + + "user_id = ?"; + update(sqlCon, QUERY, pst -> { + pst.setString(1, primaryUserId); + pst.setString(2, appIdentifier.getAppId()); + pst.setString(3, recipeUserId); + }); + } + + public static void unlinkAccounts_Transaction(Start start, Connection sqlCon, AppIdentifier appIdentifier, + String recipeUserId) + throws SQLException, StorageQueryException { + String QUERY = "UPDATE " + getConfig(start).getUsersTable() + + " SET is_linked_or_is_a_primary_user = false, primary_or_recipe_user_id = ? WHERE app_id = ? AND " + + "user_id = ?"; + + update(sqlCon, QUERY, pst -> { + pst.setString(1, recipeUserId); + pst.setString(2, appIdentifier.getAppId()); + pst.setString(3, recipeUserId); + }); + } + public static AuthRecipeUserInfo[] listPrimaryUsersByPhoneNumber_Transaction(Start start, Connection sqlCon, TenantIdentifier tenantIdentifier, String phoneNumber) From 36098ffe17e7f6f2fa44479fa42cbc7c66d81e45 Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Wed, 13 Sep 2023 15:37:59 +0530 Subject: [PATCH 117/131] fix: password reset changes (#804) --- .../java/io/supertokens/inmemorydb/Start.java | 2 +- .../queries/EmailPasswordQueries.java | 42 ++++++++++++++----- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/src/main/java/io/supertokens/inmemorydb/Start.java b/src/main/java/io/supertokens/inmemorydb/Start.java index 91f3daa1d..10b365bfa 100644 --- a/src/main/java/io/supertokens/inmemorydb/Start.java +++ b/src/main/java/io/supertokens/inmemorydb/Start.java @@ -777,7 +777,7 @@ public void addPasswordResetToken(AppIdentifier appIdentifier, PasswordResetToke throws StorageQueryException, UnknownUserIdException, DuplicatePasswordResetTokenException { try { EmailPasswordQueries.addPasswordResetToken(this, appIdentifier, passwordResetTokenInfo.userId, - passwordResetTokenInfo.token, passwordResetTokenInfo.tokenExpiry); + passwordResetTokenInfo.token, passwordResetTokenInfo.tokenExpiry, passwordResetTokenInfo.email); } catch (SQLException e) { if (e instanceof SQLiteException) { String serverMessage = e.getMessage(); diff --git a/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java index 003a100b3..360186f63 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java @@ -81,7 +81,7 @@ static String getQueryToCreatePasswordResetTokensTable(Start start) { + "token VARCHAR(128) NOT NULL UNIQUE," + "token_expiry BIGINT UNSIGNED NOT NULL," + "PRIMARY KEY (app_id, user_id, token)," - + "FOREIGN KEY (app_id, user_id) REFERENCES " + Config.getConfig(start).getEmailPasswordUsersTable() + + "FOREIGN KEY (app_id, user_id) REFERENCES " + Config.getConfig(start).getAppIdToUserIdTable() + " (app_id, user_id) ON DELETE CASCADE ON UPDATE CASCADE" + ");"; } @@ -215,17 +215,30 @@ public static PasswordResetTokenInfo getPasswordResetTokenInfo(Start start, AppI } public static void addPasswordResetToken(Start start, AppIdentifier appIdentifier, String userId, String tokenHash, - long expiry) + long expiry, String email) throws SQLException, StorageQueryException { - String QUERY = "INSERT INTO " + getConfig(start).getPasswordResetTokensTable() - + "(app_id, user_id, token, token_expiry)" + " VALUES(?, ?, ?, ?)"; + if (email != null) { + String QUERY = "INSERT INTO " + getConfig(start).getPasswordResetTokensTable() + + "(app_id, user_id, token, token_expiry, email)" + " VALUES(?, ?, ?, ?, ?)"; - update(start, QUERY, pst -> { - pst.setString(1, appIdentifier.getAppId()); - pst.setString(2, userId); - pst.setString(3, tokenHash); - pst.setLong(4, expiry); - }); + update(start, QUERY, pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setString(2, userId); + pst.setString(3, tokenHash); + pst.setLong(4, expiry); + pst.setString(5, email); + }); + } else { + String QUERY = "INSERT INTO " + getConfig(start).getPasswordResetTokensTable() + + "(app_id, user_id, token, token_expiry)" + " VALUES(?, ?, ?, ?)"; + + update(start, QUERY, pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setString(2, userId); + pst.setString(3, tokenHash); + pst.setLong(4, expiry); + }); + } } public static AuthRecipeUserInfo signUp(Start start, TenantIdentifier tenantIdentifier, String userId, String email, @@ -322,6 +335,15 @@ public static void deleteUser_Transaction(Connection sqlCon, Start start, AppIde pst.setString(2, userId); }); } + + { + String QUERY = "DELETE FROM " + getConfig(start).getPasswordResetTokensTable() + + " WHERE app_id = ? AND user_id = ?"; + update(sqlCon, QUERY, pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setString(2, userId); + }); + } } } From 57ce07368329d71e1fdab44671ab30b254335812 Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Wed, 13 Sep 2023 15:46:01 +0530 Subject: [PATCH 118/131] fix: adds recipe user id in session (#805) --- .../java/io/supertokens/inmemorydb/Start.java | 7 ++-- .../inmemorydb/queries/GeneralQueries.java | 15 ++++++++ .../inmemorydb/queries/SessionQueries.java | 36 ++++++++++++++----- 3 files changed, 48 insertions(+), 10 deletions(-) diff --git a/src/main/java/io/supertokens/inmemorydb/Start.java b/src/main/java/io/supertokens/inmemorydb/Start.java index 10b365bfa..2dc25ca80 100644 --- a/src/main/java/io/supertokens/inmemorydb/Start.java +++ b/src/main/java/io/supertokens/inmemorydb/Start.java @@ -1254,8 +1254,11 @@ public AuthRecipeUserInfo getPrimaryUserById(AppIdentifier appIdentifier, String @Override public String getPrimaryUserIdStrForUserId(AppIdentifier appIdentifier, String userId) throws StorageQueryException { - // TODO:... - return null; + try { + return GeneralQueries.getPrimaryUserIdStrForUserId(this, appIdentifier, userId); + } catch (SQLException e) { + throw new StorageQueryException(e); + } } @Override diff --git a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java index eee427e20..fa87d7e11 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java @@ -1126,6 +1126,21 @@ public static AuthRecipeUserInfo getPrimaryUserInfoForUserId_Transaction(Start s return userIdToAuthRecipeUserInfo.get(pUserId); } + public static String getPrimaryUserIdStrForUserId(Start start, AppIdentifier appIdentifier, String id) + throws SQLException, StorageQueryException { + String QUERY = "SELECT primary_or_recipe_user_id FROM " + getConfig(start).getUsersTable() + + " WHERE user_id = ? AND app_id = ?"; + return execute(start, QUERY, pst -> { + pst.setString(1, id); + pst.setString(2, appIdentifier.getAppId()); + }, result -> { + if (result.next()) { + return result.getString("primary_or_recipe_user_id"); + } + return null; + }); + } + public static AuthRecipeUserInfo getPrimaryUserInfoForUserId(Start start, AppIdentifier appIdentifier, String id) throws SQLException, StorageQueryException { try (Connection con = ConnectionPool.getConnection(start)) { diff --git a/src/main/java/io/supertokens/inmemorydb/queries/SessionQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/SessionQueries.java index dda532612..b325f53b2 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/SessionQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/SessionQueries.java @@ -107,9 +107,17 @@ public static SessionInfo getSessionInfo_Transaction(Start start, Connection con tenantIdentifier.getAppId() + "~" + tenantIdentifier.getTenantId() + "~" + sessionHandle + Config.getConfig(start).getSessionInfoTable()); - String QUERY = "SELECT session_handle, user_id, refresh_token_hash_2, session_data, expires_at, " - + "created_at_time, jwt_user_payload, use_static_key FROM " + getConfig(start).getSessionInfoTable() - + " WHERE app_id = ? AND tenant_id = ? AND session_handle = ?"; + String QUERY = + "SELECT sess.session_handle, sess.user_id, sess.refresh_token_hash_2, sess.session_data, sess" + + ".expires_at, " + + + "sess.created_at_time, sess.jwt_user_payload, sess.use_static_key, users" + + ".primary_or_recipe_user_id FROM " + + getConfig(start).getSessionInfoTable() + + " AS sess LEFT JOIN " + getConfig(start).getUsersTable() + + " as users ON sess.app_id = users.app_id AND sess.user_id = users.user_id WHERE sess.app_id =" + + " ? AND " + + "sess.tenant_id = ? AND sess.session_handle = ?"; return execute(con, QUERY, pst -> { pst.setString(1, tenantIdentifier.getAppId()); pst.setString(2, tenantIdentifier.getTenantId()); @@ -306,9 +314,17 @@ public static int updateSession(Start start, TenantIdentifier tenantIdentifier, public static SessionInfo getSession(Start start, TenantIdentifier tenantIdentifier, String sessionHandle) throws SQLException, StorageQueryException { - String QUERY = "SELECT session_handle, user_id, refresh_token_hash_2, session_data, expires_at, " - + "created_at_time, jwt_user_payload, use_static_key FROM " + getConfig(start).getSessionInfoTable() - + " WHERE app_id = ? AND tenant_id = ? AND session_handle = ?"; + String QUERY = + "SELECT sess.session_handle, sess.user_id, sess.refresh_token_hash_2, sess.session_data, sess" + + ".expires_at, " + + + "sess.created_at_time, sess.jwt_user_payload, sess.use_static_key, users" + + ".primary_or_recipe_user_id FROM " + + getConfig(start).getSessionInfoTable() + + " AS sess LEFT JOIN " + getConfig(start).getUsersTable() + + " as users ON sess.app_id = users.app_id AND sess.user_id = users.user_id WHERE sess.app_id =" + + " ? AND " + + "sess.tenant_id = ? AND sess.session_handle = ?"; return execute(start, QUERY, pst -> { pst.setString(1, tenantIdentifier.getAppId()); pst.setString(2, tenantIdentifier.getTenantId()); @@ -383,10 +399,14 @@ private static SessionInfoRowMapper getInstance() { @Override public SessionInfo map(ResultSet result) throws Exception { JsonParser jp = new JsonParser(); - return new SessionInfo(result.getString("session_handle"), result.getString("user_id"), + // if result.getString("primary_or_recipe_user_id") is null, it will be handled by SessionInfo + // constructor + return new SessionInfo(result.getString("session_handle"), + result.getString("primary_or_recipe_user_id"), result.getString("user_id"), result.getString("refresh_token_hash_2"), - jp.parse(result.getString("session_data")).getAsJsonObject(), result.getLong("expires_at"), + jp.parse(result.getString("session_data")).getAsJsonObject(), + result.getLong("expires_at"), jp.parse(result.getString("jwt_user_payload")).getAsJsonObject(), result.getLong("created_at_time"), result.getBoolean("use_static_key")); } From f73a02f31b84211b182b8a5a579070a3c3bf2934 Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Wed, 13 Sep 2023 16:01:38 +0530 Subject: [PATCH 119/131] fix: fixes query (#806) --- .../inmemorydb/queries/SessionQueries.java | 67 ++++++++++++------- 1 file changed, 43 insertions(+), 24 deletions(-) diff --git a/src/main/java/io/supertokens/inmemorydb/queries/SessionQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/SessionQueries.java index b325f53b2..dbcb6e215 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/SessionQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/SessionQueries.java @@ -106,28 +106,43 @@ public static SessionInfo getSessionInfo_Transaction(Start start, Connection con ((ConnectionWithLocks) con).lock( tenantIdentifier.getAppId() + "~" + tenantIdentifier.getTenantId() + "~" + sessionHandle + Config.getConfig(start).getSessionInfoTable()); - + // we do this as two separate queries and not one query with left join cause psql does not + // support left join with for update if the right table returns null. String QUERY = - "SELECT sess.session_handle, sess.user_id, sess.refresh_token_hash_2, sess.session_data, sess" + - ".expires_at, " - + - "sess.created_at_time, sess.jwt_user_payload, sess.use_static_key, users" + - ".primary_or_recipe_user_id FROM " + + "SELECT session_handle, user_id, refresh_token_hash_2, session_data, " + + "expires_at, created_at_time, jwt_user_payload, use_static_key FROM " + getConfig(start).getSessionInfoTable() - + " AS sess LEFT JOIN " + getConfig(start).getUsersTable() + - " as users ON sess.app_id = users.app_id AND sess.user_id = users.user_id WHERE sess.app_id =" + - " ? AND " + - "sess.tenant_id = ? AND sess.session_handle = ?"; - return execute(con, QUERY, pst -> { + + " WHERE app_id = ? AND tenant_id = ? AND session_handle = ?"; + SessionInfo sessionInfo = execute(con, QUERY, pst -> { pst.setString(1, tenantIdentifier.getAppId()); pst.setString(2, tenantIdentifier.getTenantId()); pst.setString(3, sessionHandle); }, result -> { if (result.next()) { - return SessionInfoRowMapper.getInstance().mapOrThrow(result); + return SessionInfoRowMapper.getInstance().mapOrThrow(result, false); } return null; }); + + if (sessionInfo == null) { + return null; + } + + QUERY = "SELECT primary_or_recipe_user_id FROM " + getConfig(start).getUsersTable() + + " WHERE app_id = ? AND user_id = ?"; + + return execute(con, QUERY, pst -> { + pst.setString(1, tenantIdentifier.getAppId()); + pst.setString(2, sessionInfo.recipeUserId); + }, result -> { + if (result.next()) { + String primaryUserId = result.getString("primary_or_recipe_user_id"); + if (primaryUserId != null) { + sessionInfo.userId = primaryUserId; + } + } + return sessionInfo; + }); } public static void updateSessionInfo_Transaction(Start start, Connection con, TenantIdentifier tenantIdentifier, @@ -331,7 +346,7 @@ public static SessionInfo getSession(Start start, TenantIdentifier tenantIdentif pst.setString(3, sessionHandle); }, result -> { if (result.next()) { - return SessionInfoRowMapper.getInstance().mapOrThrow(result); + return SessionInfoRowMapper.getInstance().mapOrThrow(result, true); } return null; }); @@ -386,7 +401,7 @@ public static void removeAccessTokenSigningKeysBefore(Start start, AppIdentifier }); } - static class SessionInfoRowMapper implements RowMapper { + static class SessionInfoRowMapper { public static final SessionInfoRowMapper INSTANCE = new SessionInfoRowMapper(); private SessionInfoRowMapper() { @@ -396,19 +411,23 @@ private static SessionInfoRowMapper getInstance() { return INSTANCE; } - @Override - public SessionInfo map(ResultSet result) throws Exception { + public SessionInfo mapOrThrow(ResultSet result, boolean hasPrimaryOrRecipeUserId) throws StorageQueryException { JsonParser jp = new JsonParser(); // if result.getString("primary_or_recipe_user_id") is null, it will be handled by SessionInfo // constructor - return new SessionInfo(result.getString("session_handle"), - result.getString("primary_or_recipe_user_id"), - result.getString("user_id"), - result.getString("refresh_token_hash_2"), - jp.parse(result.getString("session_data")).getAsJsonObject(), - result.getLong("expires_at"), - jp.parse(result.getString("jwt_user_payload")).getAsJsonObject(), - result.getLong("created_at_time"), result.getBoolean("use_static_key")); + try { + return new SessionInfo(result.getString("session_handle"), + hasPrimaryOrRecipeUserId ? result.getString("primary_or_recipe_user_id") : + result.getString("user_id"), + result.getString("user_id"), + result.getString("refresh_token_hash_2"), + jp.parse(result.getString("session_data")).getAsJsonObject(), + result.getLong("expires_at"), + jp.parse(result.getString("jwt_user_payload")).getAsJsonObject(), + result.getLong("created_at_time"), result.getBoolean("use_static_key")); + } catch (Exception e) { + throw new StorageQueryException(e); + } } } From 825c13f4df652c2c6f4a011db6226929ccd892e3 Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Wed, 13 Sep 2023 17:15:25 +0530 Subject: [PATCH 120/131] fix: user pagination (#807) --- .../java/io/supertokens/inmemorydb/Start.java | 2 +- .../queries/EmailPasswordQueries.java | 10 +- .../inmemorydb/queries/GeneralQueries.java | 148 ++++++++++-------- .../queries/PasswordlessQueries.java | 10 +- .../inmemorydb/queries/ThirdPartyQueries.java | 11 +- 5 files changed, 99 insertions(+), 82 deletions(-) diff --git a/src/main/java/io/supertokens/inmemorydb/Start.java b/src/main/java/io/supertokens/inmemorydb/Start.java index 2dc25ca80..919459a93 100644 --- a/src/main/java/io/supertokens/inmemorydb/Start.java +++ b/src/main/java/io/supertokens/inmemorydb/Start.java @@ -2885,7 +2885,7 @@ public void unlinkAccounts_Transaction(AppIdentifier appIdentifier, TransactionC Connection sqlCon = (Connection) con.getConnection(); // we do not bother returning if a row was updated here or not, cause it's happening // in a transaction anyway. - GeneralQueries.unlinkAccounts_Transaction(this, sqlCon, appIdentifier, recipeUserId); + GeneralQueries.unlinkAccounts_Transaction(this, sqlCon, appIdentifier, primaryUserId, recipeUserId); } catch (SQLException e) { throw new StorageQueryException(e); } diff --git a/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java index 360186f63..77cb8f4ed 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java @@ -259,8 +259,8 @@ public static AuthRecipeUserInfo signUp(Start start, TenantIdentifier tenantIden { // all_auth_recipe_users String QUERY = "INSERT INTO " + getConfig(start).getUsersTable() - + "(app_id, tenant_id, user_id, primary_or_recipe_user_id, recipe_id, time_joined)" + - " VALUES(?, ?, ?, ?, ?, ?)"; + + "(app_id, tenant_id, user_id, primary_or_recipe_user_id, recipe_id, time_joined, primary_or_recipe_user_time_joined)" + + " VALUES(?, ?, ?, ?, ?, ?, ?)"; update(sqlCon, QUERY, pst -> { pst.setString(1, tenantIdentifier.getAppId()); pst.setString(2, tenantIdentifier.getTenantId()); @@ -268,6 +268,7 @@ public static AuthRecipeUserInfo signUp(Start start, TenantIdentifier tenantIden pst.setString(4, userId); pst.setString(5, EMAIL_PASSWORD.toString()); pst.setLong(6, timeJoined); + pst.setLong(7, timeJoined); }); } @@ -446,8 +447,8 @@ public static boolean addUserIdToTenant_Transaction(Start start, Connection sqlC { // all_auth_recipe_users String QUERY = "INSERT INTO " + getConfig(start).getUsersTable() - + "(app_id, tenant_id, user_id, primary_or_recipe_user_id, recipe_id, time_joined)" - + " VALUES(?, ?, ?, ?, ?, ?)" + " ON CONFLICT DO NOTHING"; + + "(app_id, tenant_id, user_id, primary_or_recipe_user_id, recipe_id, time_joined, primary_or_recipe_user_time_joined)" + + " VALUES(?, ?, ?, ?, ?, ?, ?)" + " ON CONFLICT DO NOTHING"; update(sqlCon, QUERY, pst -> { pst.setString(1, tenantIdentifier.getAppId()); pst.setString(2, tenantIdentifier.getTenantId()); @@ -455,6 +456,7 @@ public static boolean addUserIdToTenant_Transaction(Start start, Connection sqlC pst.setString(4, userId); pst.setString(5, EMAIL_PASSWORD.toString()); pst.setLong(6, userInfo.timeJoined); + pst.setLong(7, userInfo.timeJoined); }); } diff --git a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java index fa87d7e11..9b336371e 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java @@ -87,7 +87,7 @@ static String getQueryToCreateUsersTable(Start start) { static String getQueryToCreateUserPaginationIndex(Start start) { return "CREATE INDEX all_auth_recipe_users_pagination_index ON " + Config.getConfig(start).getUsersTable() - + "(time_joined DESC, primary_or_recipe_user_id DESC, tenant_id DESC, app_id DESC);"; + + "(primary_or_recipe_user_time_joined DESC, primary_or_recipe_user_id DESC, tenant_id DESC, app_id DESC);"; } static String getQueryToCreatePrimaryUserIdIndex(Start start) { @@ -558,7 +558,7 @@ public static AuthRecipeUserInfo[] getUsers(Start start, TenantIdentifier tenant throws SQLException, StorageQueryException { // This list will be used to keep track of the result's order from the db - List usersFromQuery; + List usersFromQuery; if (dashboardSearchTags != null) { ArrayList queryList = new ArrayList<>(); @@ -732,17 +732,16 @@ public static AuthRecipeUserInfo[] getUsers(Start start, TenantIdentifier tenant usersFromQuery = new ArrayList<>(); } else { - String finalQuery = "SELECT * FROM ( " + USER_SEARCH_TAG_CONDITION.toString() + " )" - + " AS finalResultTable ORDER BY time_joined " + timeJoinedOrder + ", user_id DESC "; + String finalQuery = "SELECT DISTINCT primary_or_recipe_user_id, primary_or_recipe_user_time_joined FROM ( " + USER_SEARCH_TAG_CONDITION.toString() + " )" + + " AS finalResultTable ORDER BY primary_or_recipe_user_time_joined " + timeJoinedOrder + ", primary_or_recipe_user_id DESC "; usersFromQuery = execute(start, finalQuery, pst -> { for (int i = 1; i <= queryList.size(); i++) { pst.setString(i, queryList.get(i - 1)); } }, result -> { - List temp = new ArrayList<>(); + List temp = new ArrayList<>(); while (result.next()) { - temp.add(new UserInfoPaginationResultHolder(result.getString("user_id"), - result.getString("recipe_id"))); + temp.add(result.getString("primary_or_recipe_user_id")); } return temp; }); @@ -771,11 +770,11 @@ public static AuthRecipeUserInfo[] getUsers(Start start, TenantIdentifier tenant recipeIdCondition = recipeIdCondition + " AND"; } String timeJoinedOrderSymbol = timeJoinedOrder.equals("ASC") ? ">" : "<"; - String QUERY = "SELECT user_id, recipe_id FROM " + getConfig(start).getUsersTable() + " WHERE " - + recipeIdCondition + " (time_joined " + timeJoinedOrderSymbol - + " ? OR (time_joined = ? AND user_id <= ?)) AND app_id = ? AND tenant_id = ?" - + " ORDER BY time_joined " + timeJoinedOrder - + ", user_id DESC LIMIT ?"; + String QUERY = "SELECT DISTINCT primary_or_recipe_user_id, primary_or_recipe_user_time_joined FROM " + getConfig(start).getUsersTable() + " WHERE " + + recipeIdCondition + " (primary_or_recipe_user_time_joined " + timeJoinedOrderSymbol + + " ? OR (primary_or_recipe_user_time_joined = ? AND primary_or_recipe_user_id <= ?)) AND app_id = ? AND tenant_id = ?" + + " ORDER BY primary_or_recipe_user_time_joined " + timeJoinedOrder + + ", primary_or_recipe_user_id DESC LIMIT ?"; usersFromQuery = execute(start, QUERY, pst -> { if (includeRecipeIds != null) { for (int i = 0; i < includeRecipeIds.length; i++) { @@ -791,21 +790,20 @@ public static AuthRecipeUserInfo[] getUsers(Start start, TenantIdentifier tenant pst.setString(baseIndex + 5, tenantIdentifier.getTenantId()); pst.setInt(baseIndex + 6, limit); }, result -> { - List temp = new ArrayList<>(); + List temp = new ArrayList<>(); while (result.next()) { - temp.add(new UserInfoPaginationResultHolder(result.getString("user_id"), - result.getString("recipe_id"))); + temp.add(result.getString("primary_or_recipe_user_id")); } return temp; }); } else { String recipeIdCondition = RECIPE_ID_CONDITION.toString(); - String QUERY = "SELECT user_id, recipe_id FROM " + getConfig(start).getUsersTable() + " WHERE "; + String QUERY = "SELECT DISTINCT primary_or_recipe_user_id, primary_or_recipe_user_time_joined FROM " + getConfig(start).getUsersTable() + " WHERE "; if (!recipeIdCondition.equals("")) { QUERY += recipeIdCondition + " AND"; } - QUERY += " app_id = ? AND tenant_id = ? ORDER BY time_joined " + timeJoinedOrder - + ", user_id DESC LIMIT ?"; + QUERY += " app_id = ? AND tenant_id = ? ORDER BY primary_or_recipe_user_time_joined " + timeJoinedOrder + + ", primary_or_recipe_user_id DESC LIMIT ?"; usersFromQuery = execute(start, QUERY, pst -> { if (includeRecipeIds != null) { for (int i = 0; i < includeRecipeIds.length; i++) { @@ -818,49 +816,31 @@ public static AuthRecipeUserInfo[] getUsers(Start start, TenantIdentifier tenant pst.setString(baseIndex + 2, tenantIdentifier.getTenantId()); pst.setInt(baseIndex + 3, limit); }, result -> { - List temp = new ArrayList<>(); + List temp = new ArrayList<>(); while (result.next()) { - temp.add(new UserInfoPaginationResultHolder(result.getString("user_id"), - result.getString("recipe_id"))); + temp.add(result.getString("primary_or_recipe_user_id")); } return temp; }); } } - // we create a map from recipe ID -> userId[] - Map> recipeIdToUserIdListMap = new HashMap<>(); - for (UserInfoPaginationResultHolder user : usersFromQuery) { - RECIPE_ID recipeId = RECIPE_ID.getEnumFromString(user.recipeId); - if (recipeId == null) { - throw new SQLException("Unrecognised recipe ID in database: " + user.recipeId); - } - List userIdList = recipeIdToUserIdListMap.get(recipeId); - if (userIdList == null) { - userIdList = new ArrayList<>(); - } - userIdList.add(user.userId); - recipeIdToUserIdListMap.put(recipeId, userIdList); - } - AuthRecipeUserInfo[] finalResult = new AuthRecipeUserInfo[usersFromQuery.size()]; // we give the userId[] for each recipe to fetch all those user's details - for (RECIPE_ID recipeId : recipeIdToUserIdListMap.keySet()) { - List users = getPrimaryUserInfoForUserIds(start, - tenantIdentifier.toAppIdentifier(), - recipeIdToUserIdListMap.get(recipeId)); - - // we fill in all the slots in finalResult based on their position in - // usersFromQuery - Map userIdToInfoMap = new HashMap<>(); - for (AuthRecipeUserInfo user : users) { - userIdToInfoMap.put(user.getSupertokensUserId(), user); - } - for (int i = 0; i < usersFromQuery.size(); i++) { - if (finalResult[i] == null) { - finalResult[i] = userIdToInfoMap.get(usersFromQuery.get(i).userId); - } + List users = getPrimaryUserInfoForUserIds(start, + tenantIdentifier.toAppIdentifier(), + usersFromQuery); + + // we fill in all the slots in finalResult based on their position in + // usersFromQuery + Map userIdToInfoMap = new HashMap<>(); + for (AuthRecipeUserInfo user : users) { + userIdToInfoMap.put(user.getSupertokensUserId(), user); + } + for (int i = 0; i < usersFromQuery.size(); i++) { + if (finalResult[i] == null) { + finalResult[i] = userIdToInfoMap.get(usersFromQuery.get(i)); } } @@ -882,28 +862,58 @@ public static void makePrimaryUser_Transaction(Start start, Connection sqlCon, A public static void linkAccounts_Transaction(Start start, Connection sqlCon, AppIdentifier appIdentifier, String recipeUserId, String primaryUserId) throws SQLException, StorageQueryException { - String QUERY = "UPDATE " + getConfig(start).getUsersTable() + - " SET is_linked_or_is_a_primary_user = true, primary_or_recipe_user_id = ? WHERE app_id = ? AND " + - "user_id = ?"; - update(sqlCon, QUERY, pst -> { - pst.setString(1, primaryUserId); - pst.setString(2, appIdentifier.getAppId()); - pst.setString(3, recipeUserId); - }); + { + String QUERY = "UPDATE " + getConfig(start).getUsersTable() + + " SET is_linked_or_is_a_primary_user = true, primary_or_recipe_user_id = ? WHERE app_id = ? AND " + + "user_id = ?"; + + update(sqlCon, QUERY, pst -> { + pst.setString(1, primaryUserId); + pst.setString(2, appIdentifier.getAppId()); + pst.setString(3, recipeUserId); + }); + } + { // update primary_or_recipe_user_time_joined to min time joined + String QUERY = "UPDATE " + getConfig(start).getUsersTable() + + " SET primary_or_recipe_user_time_joined = (SELECT MIN(time_joined) FROM " + + getConfig(start).getUsersTable() + " WHERE app_id = ? AND primary_or_recipe_user_id = ?) WHERE " + + " app_id = ? AND primary_or_recipe_user_id = ?"; + update(sqlCon, QUERY, pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setString(2, primaryUserId); + pst.setString(3, appIdentifier.getAppId()); + pst.setString(4, primaryUserId); + }); + } } public static void unlinkAccounts_Transaction(Start start, Connection sqlCon, AppIdentifier appIdentifier, - String recipeUserId) + String primaryUserId, String recipeUserId) throws SQLException, StorageQueryException { - String QUERY = "UPDATE " + getConfig(start).getUsersTable() + - " SET is_linked_or_is_a_primary_user = false, primary_or_recipe_user_id = ? WHERE app_id = ? AND " + - "user_id = ?"; - - update(sqlCon, QUERY, pst -> { - pst.setString(1, recipeUserId); - pst.setString(2, appIdentifier.getAppId()); - pst.setString(3, recipeUserId); - }); + { + String QUERY = "UPDATE " + getConfig(start).getUsersTable() + + " SET is_linked_or_is_a_primary_user = false, primary_or_recipe_user_id = ?, " + + "primary_or_recipe_user_time_joined = time_joined WHERE app_id = ? AND " + + "user_id = ?"; + + update(sqlCon, QUERY, pst -> { + pst.setString(1, recipeUserId); + pst.setString(2, appIdentifier.getAppId()); + pst.setString(3, recipeUserId); + }); + } + { // update primary_or_recipe_user_time_joined to min time joined + String QUERY = "UPDATE " + getConfig(start).getUsersTable() + + " SET primary_or_recipe_user_time_joined = (SELECT MIN(time_joined) FROM " + + getConfig(start).getUsersTable() + " WHERE app_id = ? AND primary_or_recipe_user_id = ?) WHERE " + + " app_id = ? AND primary_or_recipe_user_id = ?"; + update(sqlCon, QUERY, pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setString(2, primaryUserId); + pst.setString(3, appIdentifier.getAppId()); + pst.setString(4, primaryUserId); + }); + } } public static AuthRecipeUserInfo[] listPrimaryUsersByPhoneNumber_Transaction(Start start, Connection sqlCon, diff --git a/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java index 80ca321e9..82cd39bf9 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java @@ -383,8 +383,8 @@ public static AuthRecipeUserInfo createUser(Start start, TenantIdentifier tenant { // all_auth_recipe_users String QUERY = "INSERT INTO " + getConfig(start).getUsersTable() - + "(app_id, tenant_id, user_id, primary_or_recipe_user_id, recipe_id, time_joined)" + - " VALUES(?, ?, ?, ?, ?, ?)"; + + "(app_id, tenant_id, user_id, primary_or_recipe_user_id, recipe_id, time_joined, primary_or_recipe_user_time_joined)" + + " VALUES(?, ?, ?, ?, ?, ?, ?)"; update(sqlCon, QUERY, pst -> { pst.setString(1, tenantIdentifier.getAppId()); pst.setString(2, tenantIdentifier.getTenantId()); @@ -392,6 +392,7 @@ public static AuthRecipeUserInfo createUser(Start start, TenantIdentifier tenant pst.setString(4, id); pst.setString(5, PASSWORDLESS.toString()); pst.setLong(6, timeJoined); + pst.setLong(7, timeJoined); }); } @@ -829,8 +830,8 @@ public static boolean addUserIdToTenant_Transaction(Start start, Connection sqlC { // all_auth_recipe_users String QUERY = "INSERT INTO " + getConfig(start).getUsersTable() - + "(app_id, tenant_id, user_id, primary_or_recipe_user_id, recipe_id, time_joined)" - + " VALUES(?, ?, ?, ?, ?, ?)" + " ON CONFLICT DO NOTHING"; + + "(app_id, tenant_id, user_id, primary_or_recipe_user_id, recipe_id, time_joined, primary_or_recipe_user_time_joined)" + + " VALUES(?, ?, ?, ?, ?, ?, ?)" + " ON CONFLICT DO NOTHING"; update(sqlCon, QUERY, pst -> { pst.setString(1, tenantIdentifier.getAppId()); pst.setString(2, tenantIdentifier.getTenantId()); @@ -838,6 +839,7 @@ public static boolean addUserIdToTenant_Transaction(Start start, Connection sqlC pst.setString(4, userInfo.id); pst.setString(5, PASSWORDLESS.toString()); pst.setLong(6, userInfo.timeJoined); + pst.setLong(7, userInfo.timeJoined); }); } diff --git a/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java index cf76f9306..8a1fbef92 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java @@ -101,8 +101,9 @@ public static AuthRecipeUserInfo signUp(Start start, TenantIdentifier tenantIden { // all_auth_recipe_users String QUERY = "INSERT INTO " + getConfig(start).getUsersTable() - + "(app_id, tenant_id, user_id, primary_or_recipe_user_id, recipe_id, time_joined)" + - " VALUES(?, ?, ?, ?, ?, ?)"; + + + "(app_id, tenant_id, user_id, primary_or_recipe_user_id, recipe_id, time_joined, primary_or_recipe_user_time_joined)" + + " VALUES(?, ?, ?, ?, ?, ?, ?)"; update(sqlCon, QUERY, pst -> { pst.setString(1, tenantIdentifier.getAppId()); pst.setString(2, tenantIdentifier.getTenantId()); @@ -110,6 +111,7 @@ public static AuthRecipeUserInfo signUp(Start start, TenantIdentifier tenantIden pst.setString(4, id); pst.setString(5, THIRD_PARTY.toString()); pst.setLong(6, timeJoined); + pst.setLong(7, timeJoined); }); } @@ -357,8 +359,8 @@ public static boolean addUserIdToTenant_Transaction(Start start, Connection sqlC { // all_auth_recipe_users String QUERY = "INSERT INTO " + getConfig(start).getUsersTable() - + "(app_id, tenant_id, user_id, primary_or_recipe_user_id, recipe_id, time_joined)" - + " VALUES(?, ?, ?, ?, ?, ?)" + " ON CONFLICT DO NOTHING"; + + "(app_id, tenant_id, user_id, primary_or_recipe_user_id, recipe_id, time_joined, primary_or_recipe_user_time_joined)" + + " VALUES(?, ?, ?, ?, ?, ?, ?)" + " ON CONFLICT DO NOTHING"; update(sqlCon, QUERY, pst -> { pst.setString(1, tenantIdentifier.getAppId()); pst.setString(2, tenantIdentifier.getTenantId()); @@ -366,6 +368,7 @@ public static boolean addUserIdToTenant_Transaction(Start start, Connection sqlC pst.setString(4, userInfo.id); pst.setString(5, THIRD_PARTY.toString()); pst.setLong(6, userInfo.timeJoined); + pst.setLong(7, userInfo.timeJoined); }); } From f721e1592931e7d091986f7faba7eb98e46f705c Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Wed, 13 Sep 2023 18:00:33 +0530 Subject: [PATCH 121/131] fix: multitenant user association (#808) --- .../java/io/supertokens/inmemorydb/Start.java | 158 ++++++++---------- .../queries/EmailPasswordQueries.java | 38 ++++- .../inmemorydb/queries/GeneralQueries.java | 113 +++++++++++-- .../queries/PasswordlessQueries.java | 84 +++++++--- .../inmemorydb/queries/ThirdPartyQueries.java | 85 +++++++--- 5 files changed, 322 insertions(+), 156 deletions(-) diff --git a/src/main/java/io/supertokens/inmemorydb/Start.java b/src/main/java/io/supertokens/inmemorydb/Start.java index 919459a93..1fc7dfa90 100644 --- a/src/main/java/io/supertokens/inmemorydb/Start.java +++ b/src/main/java/io/supertokens/inmemorydb/Start.java @@ -2288,77 +2288,64 @@ public TenantConfig[] getAllTenants() throws StorageQueryException { } @Override - public boolean addUserIdToTenant_Transaction(TenantIdentifier tenantIdentifier, TransactionConnection conn, String userId) + public boolean addUserIdToTenant_Transaction(TenantIdentifier tenantIdentifier, TransactionConnection con, String userId) throws StorageQueryException, TenantOrAppNotFoundException, DuplicateEmailException, DuplicateThirdPartyUserException, DuplicatePhoneNumberException, UnknownUserIdException { + Connection sqlCon = (Connection) con.getConnection(); try { - return this.startTransaction(con -> { - Connection sqlCon = (Connection) con.getConnection(); - try { - String recipeId = GeneralQueries.getRecipeIdForUser_Transaction(this, sqlCon, tenantIdentifier, - userId); - - if (recipeId == null) { - throw new StorageTransactionLogicException(new UnknownUserIdException()); - } - - boolean added; - if (recipeId.equals("emailpassword")) { - added = EmailPasswordQueries.addUserIdToTenant_Transaction(this, sqlCon, tenantIdentifier, - userId); - } else if (recipeId.equals("thirdparty")) { - added = ThirdPartyQueries.addUserIdToTenant_Transaction(this, sqlCon, tenantIdentifier, userId); - } else if (recipeId.equals("passwordless")) { - added = PasswordlessQueries.addUserIdToTenant_Transaction(this, sqlCon, tenantIdentifier, - userId); - } else { - throw new IllegalStateException("Should never come here!"); - } + String recipeId = GeneralQueries.getRecipeIdForUser_Transaction(this, sqlCon, tenantIdentifier, + userId); - sqlCon.commit(); - return added; - } catch (SQLException throwables) { - throw new StorageTransactionLogicException(throwables); - } - }); - } catch (StorageTransactionLogicException e) { - if (e.actualException instanceof SQLException) { - SQLiteConfig config = Config.getConfig(this); - String serverErrorMessage = e.actualException.getMessage(); + if (recipeId == null) { + throw new UnknownUserIdException(); + } - if (isForeignKeyConstraintError( - serverErrorMessage, - config.getTenantsTable(), - new String[]{"app_id", "tenant_id"}, - new Object[]{tenantIdentifier.getAppId(), tenantIdentifier.getTenantId()})) { - throw new TenantOrAppNotFoundException(tenantIdentifier); - } - if (isUniqueConstraintError(serverErrorMessage, config.getEmailPasswordUserToTenantTable(), - new String[]{"app_id", "tenant_id", "email"})) { - throw new DuplicateEmailException(); - } - if (isUniqueConstraintError(serverErrorMessage, config.getThirdPartyUserToTenantTable(), - new String[]{"app_id", "tenant_id", "third_party_id", "third_party_user_id"})) { - throw new DuplicateThirdPartyUserException(); - } - if (isUniqueConstraintError(serverErrorMessage, - Config.getConfig(this).getPasswordlessUserToTenantTable(), - new String[]{"app_id", "tenant_id", "phone_number"})) { - throw new DuplicatePhoneNumberException(); - } - if (isUniqueConstraintError(serverErrorMessage, - Config.getConfig(this).getPasswordlessUserToTenantTable(), - new String[]{"app_id", "tenant_id", "email"})) { - throw new DuplicateEmailException(); - } + boolean added; + if (recipeId.equals("emailpassword")) { + added = EmailPasswordQueries.addUserIdToTenant_Transaction(this, sqlCon, tenantIdentifier, + userId); + } else if (recipeId.equals("thirdparty")) { + added = ThirdPartyQueries.addUserIdToTenant_Transaction(this, sqlCon, tenantIdentifier, userId); + } else if (recipeId.equals("passwordless")) { + added = PasswordlessQueries.addUserIdToTenant_Transaction(this, sqlCon, tenantIdentifier, + userId); + } else { + throw new IllegalStateException("Should never come here!"); + } - throw new StorageQueryException(e.actualException); - } else if (e.actualException instanceof UnknownUserIdException) { - throw (UnknownUserIdException) e.actualException; - } else if (e.actualException instanceof StorageQueryException) { - throw (StorageQueryException) e.actualException; + sqlCon.commit(); + return added; + } catch (SQLException throwables) { + SQLiteConfig config = Config.getConfig(this); + String serverErrorMessage = throwables.getMessage(); + + if (isForeignKeyConstraintError( + serverErrorMessage, + config.getTenantsTable(), + new String[]{"app_id", "tenant_id"}, + new Object[]{tenantIdentifier.getAppId(), tenantIdentifier.getTenantId()})) { + throw new TenantOrAppNotFoundException(tenantIdentifier); } - throw new StorageQueryException(e.actualException); + if (isUniqueConstraintError(serverErrorMessage, config.getEmailPasswordUserToTenantTable(), + new String[]{"app_id", "tenant_id", "email"})) { + throw new DuplicateEmailException(); + } + if (isUniqueConstraintError(serverErrorMessage, config.getThirdPartyUserToTenantTable(), + new String[]{"app_id", "tenant_id", "third_party_id", "third_party_user_id"})) { + throw new DuplicateThirdPartyUserException(); + } + if (isUniqueConstraintError(serverErrorMessage, + Config.getConfig(this).getPasswordlessUserToTenantTable(), + new String[]{"app_id", "tenant_id", "phone_number"})) { + throw new DuplicatePhoneNumberException(); + } + if (isUniqueConstraintError(serverErrorMessage, + Config.getConfig(this).getPasswordlessUserToTenantTable(), + new String[]{"app_id", "tenant_id", "email"})) { + throw new DuplicateEmailException(); + } + + throw new StorageQueryException(throwables); } } @@ -2804,13 +2791,12 @@ public AuthRecipeUserInfo getPrimaryUserById_Transaction(AppIdentifier appIdenti public AuthRecipeUserInfo[] listPrimaryUsersByEmail_Transaction(AppIdentifier appIdentifier, TransactionConnection con, String email) throws StorageQueryException { - return null; // TODO -// try { -// Connection sqlCon = (Connection) con.getConnection(); -// return GeneralQueries.listPrimaryUsersByEmail_Transaction(this, sqlCon, appIdentifier, email); -// } catch (SQLException e) { -// throw new StorageQueryException(e); -// } + try { + Connection sqlCon = (Connection) con.getConnection(); + return GeneralQueries.listPrimaryUsersByEmail_Transaction(this, sqlCon, appIdentifier, email); + } catch (SQLException e) { + throw new StorageQueryException(e); + } } @Override @@ -2818,14 +2804,13 @@ public AuthRecipeUserInfo[] listPrimaryUsersByPhoneNumber_Transaction(AppIdentif TransactionConnection con, String phoneNumber) throws StorageQueryException { - return null; // TODO -// try { -// Connection sqlCon = (Connection) con.getConnection(); -// return GeneralQueries.listPrimaryUsersByPhoneNumber_Transaction(this, sqlCon, tenantIdentifier, -// phoneNumber); -// } catch (SQLException e) { -// throw new StorageQueryException(e); -// } + try { + Connection sqlCon = (Connection) con.getConnection(); + return GeneralQueries.listPrimaryUsersByPhoneNumber_Transaction(this, sqlCon, appIdentifier, + phoneNumber); + } catch (SQLException e) { + throw new StorageQueryException(e); + } } @Override @@ -2842,14 +2827,13 @@ public AuthRecipeUserInfo[] listPrimaryUsersByThirdPartyInfo_Transaction(AppIden String thirdPartyId, String thirdPartyUserId) throws StorageQueryException { - return null; // TODO -// try { -// Connection sqlCon = (Connection) con.getConnection(); -// return GeneralQueries.getPrimaryUsersByThirdPartyInfo_Transaction(this, sqlCon, tenantIdentifier, -// thirdPartyId, thirdPartyUserId); -// } catch (SQLException e) { -// throw new StorageQueryException(e); -// } + try { + Connection sqlCon = (Connection) con.getConnection(); + return GeneralQueries.getPrimaryUsersByThirdPartyInfo_Transaction(this, sqlCon, appIdentifier, + thirdPartyId, thirdPartyUserId); + } catch (SQLException e) { + throw new StorageQueryException(e); + } } @Override diff --git a/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java index 77cb8f4ed..315ca7f44 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java @@ -397,19 +397,18 @@ public static List getUsersInfoUsingIdList(Start start, Connection return Collections.emptyList(); } - public static String lockEmailAndTenant_Transaction(Start start, Connection con, TenantIdentifier tenantIdentifier, - String email) throws SQLException, StorageQueryException { + public static String lockEmail_Transaction(Start start, Connection con, AppIdentifier appIdentifier, + String email) throws SQLException, StorageQueryException { // normally the query below will use a for update, but sqlite doesn't support it. ((ConnectionWithLocks) con).lock( - tenantIdentifier.getAppId() + tenantIdentifier.getTenantId() + "~" + email + - Config.getConfig(start).getEmailPasswordUserToTenantTable()); + appIdentifier.getAppId() + "~" + email + + Config.getConfig(start).getEmailPasswordUsersTable()); - String QUERY = "SELECT user_id FROM " + getConfig(start).getEmailPasswordUserToTenantTable() + - " WHERE app_id = ? AND tenant_id = ? AND email = ?"; + String QUERY = "SELECT user_id FROM " + getConfig(start).getEmailPasswordUsersTable() + + " WHERE app_id = ? AND email = ?"; return execute(con, QUERY, pst -> { - pst.setString(1, tenantIdentifier.getAppId()); - pst.setString(2, tenantIdentifier.getTenantId()); - pst.setString(3, email); + pst.setString(1, appIdentifier.getAppId()); + pst.setString(2, email); }, result -> { if (result.next()) { return result.getString("user_id"); @@ -439,6 +438,27 @@ public static String getPrimaryUserIdUsingEmail(Start start, Connection con, Ten }); } + public static List getPrimaryUserIdsUsingEmail(Start start, Connection con, AppIdentifier appIdentifier, + String email) + throws StorageQueryException, SQLException { + String QUERY = "SELECT DISTINCT all_users.primary_or_recipe_user_id AS user_id " + + "FROM " + getConfig(start).getEmailPasswordUsersTable() + " AS ep" + + " JOIN " + getConfig(start).getUsersTable() + " AS all_users" + + " ON ep.app_id = all_users.app_id AND ep.user_id = all_users.user_id" + + " WHERE ep.app_id = ? AND ep.email = ?"; + + return execute(con, QUERY, pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setString(2, email); + }, result -> { + List userIds = new ArrayList<>(); + while (result.next()) { + userIds.add(result.getString("user_id")); + } + return userIds; + }); + } + public static boolean addUserIdToTenant_Transaction(Start start, Connection sqlCon, TenantIdentifier tenantIdentifier, String userId) throws SQLException, StorageQueryException { diff --git a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java index 9b336371e..a3acdb0a8 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java @@ -917,21 +917,21 @@ public static void unlinkAccounts_Transaction(Start start, Connection sqlCon, Ap } public static AuthRecipeUserInfo[] listPrimaryUsersByPhoneNumber_Transaction(Start start, Connection sqlCon, - TenantIdentifier tenantIdentifier, + AppIdentifier appIdentifier, String phoneNumber) throws SQLException, StorageQueryException { // we first lock on the table based on phoneNumber and tenant - this will ensure that any other // query happening related to the account linking on this phone number / tenant will wait for this to finish, // and vice versa. - PasswordlessQueries.lockPhoneAndTenant_Transaction(start, sqlCon, tenantIdentifier, phoneNumber); + PasswordlessQueries.lockPhone_Transaction(start, sqlCon, appIdentifier, phoneNumber); // now that we have locks on all the relevant tables, we can read from them safely - return listPrimaryUsersByPhoneNumberHelper(start, sqlCon, tenantIdentifier, phoneNumber); + return listPrimaryUsersByPhoneNumberHelper(start, sqlCon, appIdentifier, phoneNumber); } - public static AuthRecipeUserInfo getPrimaryUsersByThirdPartyInfo_Transaction(Start start, Connection sqlCon, - TenantIdentifier tenantIdentifier, + public static AuthRecipeUserInfo[] getPrimaryUsersByThirdPartyInfo_Transaction(Start start, Connection sqlCon, + AppIdentifier appIdentifier, String thirdPartyId, String thirdPartyUserId) throws SQLException, StorageQueryException { @@ -940,29 +940,29 @@ public static AuthRecipeUserInfo getPrimaryUsersByThirdPartyInfo_Transaction(Sta // finish, // and vice versa. - ThirdPartyQueries.lockThirdPartyInfoAndTenant_Transaction(start, sqlCon, tenantIdentifier, thirdPartyId, + ThirdPartyQueries.lockThirdPartyInfo_Transaction(start, sqlCon, appIdentifier, thirdPartyId, thirdPartyUserId); // now that we have locks on all the relevant tables, we can read from them safely - return getPrimaryUserByThirdPartyInfoHelper(start, sqlCon, tenantIdentifier, thirdPartyId, thirdPartyUserId); + return listPrimaryUsersByThirdPartyInfoHelper(start, sqlCon, appIdentifier, thirdPartyId, thirdPartyUserId); } public static AuthRecipeUserInfo[] listPrimaryUsersByEmail_Transaction(Start start, Connection sqlCon, - TenantIdentifier tenantIdentifier, + AppIdentifier appIdentifier, String email) throws SQLException, StorageQueryException { // we first lock on the three tables based on email and tenant - this will ensure that any other // query happening related to the account linking on this email / tenant will wait for this to finish, // and vice versa. - EmailPasswordQueries.lockEmailAndTenant_Transaction(start, sqlCon, tenantIdentifier, email); + EmailPasswordQueries.lockEmail_Transaction(start, sqlCon, appIdentifier, email); - ThirdPartyQueries.lockEmailAndTenant_Transaction(start, sqlCon, tenantIdentifier, email); + ThirdPartyQueries.lockEmail_Transaction(start, sqlCon, appIdentifier, email); - PasswordlessQueries.lockEmailAndTenant_Transaction(start, sqlCon, tenantIdentifier, email); + PasswordlessQueries.lockEmail_Transaction(start, sqlCon, appIdentifier, email); // now that we have locks on all the relevant tables, we can read from them safely - return listPrimaryUsersByEmailHelper(start, sqlCon, tenantIdentifier, email); + return listPrimaryUsersByEmailHelper(start, sqlCon, appIdentifier, email); } public static AuthRecipeUserInfo[] listPrimaryUsersByEmail(Start start, TenantIdentifier tenantIdentifier, @@ -973,7 +973,7 @@ public static AuthRecipeUserInfo[] listPrimaryUsersByEmail(Start start, TenantId } } - private static AuthRecipeUserInfo[] listPrimaryUsersByEmailHelper(Start start, Connection con, + public static AuthRecipeUserInfo[] listPrimaryUsersByEmailHelper(Start start, Connection con, TenantIdentifier tenantIdentifier, String email) throws StorageQueryException, SQLException { @@ -1005,6 +1005,32 @@ private static AuthRecipeUserInfo[] listPrimaryUsersByEmailHelper(Start start, C return result.toArray(new AuthRecipeUserInfo[0]); } + public static AuthRecipeUserInfo[] listPrimaryUsersByEmailHelper(Start start, Connection con, + AppIdentifier appIdentifier, + String email) + throws StorageQueryException, SQLException { + List userIds = new ArrayList<>(); + userIds.addAll(EmailPasswordQueries.getPrimaryUserIdsUsingEmail(start, con, appIdentifier, + email)); + + userIds.addAll(PasswordlessQueries.getPrimaryUserIdsUsingEmail(start, con, appIdentifier, + email)); + + userIds.addAll(ThirdPartyQueries.getPrimaryUserIdUsingEmail(start, con, appIdentifier, email)); + + // remove duplicates from userIds + Set userIdsSet = new HashSet<>(userIds); + userIds = new ArrayList<>(userIdsSet); + + List result = getPrimaryUserInfoForUserIds(start, con, appIdentifier, + userIds); + + // this is going to order them based on oldest that joined to newest that joined. + result.sort(Comparator.comparingLong(o -> o.timeJoined)); + + return result.toArray(new AuthRecipeUserInfo[0]); + } + public static AuthRecipeUserInfo[] listPrimaryUsersByPhoneNumber(Start start, TenantIdentifier tenantIdentifier, String phoneNumber) @@ -1035,6 +1061,47 @@ private static AuthRecipeUserInfo[] listPrimaryUsersByPhoneNumberHelper(Start st return result.toArray(new AuthRecipeUserInfo[0]); } + + private static AuthRecipeUserInfo[] listPrimaryUsersByPhoneNumberHelper(Start start, Connection con, + AppIdentifier appIdentifier, + String phoneNumber) + throws StorageQueryException, SQLException { + List userIds = new ArrayList<>(); + + String passwordlessUserId = PasswordlessQueries.getPrimaryUserByPhoneNumber(start, con, appIdentifier, + phoneNumber); + if (passwordlessUserId != null) { + userIds.add(passwordlessUserId); + } + + List result = getPrimaryUserInfoForUserIds(start, con, appIdentifier, + userIds); + + // this is going to order them based on oldest that joined to newest that joined. + result.sort(Comparator.comparingLong(o -> o.timeJoined)); + + return result.toArray(new AuthRecipeUserInfo[0]); + } + + public static AuthRecipeUserInfo[] listPrimaryUsersByThirdPartyInfo(Start start, + AppIdentifier appIdentifier, + String thirdPartyId, + String thirdPartyUserId) + throws StorageQueryException, SQLException { + try (Connection con = ConnectionPool.getConnection(start)) { + return listPrimaryUsersByThirdPartyInfoHelper(start, con, appIdentifier, thirdPartyId, thirdPartyUserId); + } + } + + public static AuthRecipeUserInfo[] listPrimaryUsersByThirdPartyInfo_transaction(Start start, + Connection sqlCon, + AppIdentifier appIdentifier, + String thirdPartyId, + String thirdPartyUserId) + throws StorageQueryException, SQLException { + return listPrimaryUsersByThirdPartyInfoHelper(start, sqlCon, appIdentifier, thirdPartyId, thirdPartyUserId); + } + public static AuthRecipeUserInfo getPrimaryUserByThirdPartyInfo(Start start, TenantIdentifier tenantIdentifier, String thirdPartyId, @@ -1045,13 +1112,29 @@ public static AuthRecipeUserInfo getPrimaryUserByThirdPartyInfo(Start start, } } + private static AuthRecipeUserInfo[] listPrimaryUsersByThirdPartyInfoHelper(Start start, Connection con, + AppIdentifier appIdentifier, + String thirdPartyId, + String thirdPartyUserId) + throws StorageQueryException, SQLException { + + List userIds = ThirdPartyQueries.listUserIdsByThirdPartyInfo(start, con, appIdentifier, + thirdPartyId, thirdPartyUserId); + List result = getPrimaryUserInfoForUserIds(start, con, appIdentifier, userIds); + + // this is going to order them based on oldest that joined to newest that joined. + result.sort(Comparator.comparingLong(o -> o.timeJoined)); + + return result.toArray(new AuthRecipeUserInfo[0]); + } + private static AuthRecipeUserInfo getPrimaryUserByThirdPartyInfoHelper(Start start, Connection con, TenantIdentifier tenantIdentifier, String thirdPartyId, String thirdPartyUserId) throws StorageQueryException, SQLException { - String userId = ThirdPartyQueries.getThirdPartyUserInfoUsingId(start, con, tenantIdentifier, + String userId = ThirdPartyQueries.getUserIdByThirdPartyInfo(start, con, tenantIdentifier, thirdPartyId, thirdPartyUserId); if (userId != null) { return getPrimaryUserInfoForUserId(start, con, tenantIdentifier.toAppIdentifier(), @@ -1399,7 +1482,7 @@ private static class AllAuthRecipeUsersResultHolder { AllAuthRecipeUsersResultHolder(String userId, String tenantId, String primaryOrRecipeUserId, boolean isLinkedOrIsAPrimaryUser, String recipeId, long timeJoined) { - this.userId = userId; + this.userId = userId.trim(); this.tenantId = tenantId; this.primaryOrRecipeUserId = primaryOrRecipeUserId; this.isLinkedOrIsAPrimaryUser = isLinkedOrIsAPrimaryUser; diff --git a/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java index 82cd39bf9..c2c2a2aa0 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java @@ -736,42 +736,41 @@ public static UserInfoPartial getUserById(Start start, Connection sqlCon, AppIde }); } - public static String lockEmailAndTenant_Transaction(Start start, Connection con, TenantIdentifier tenantIdentifier, - String email) throws SQLException, StorageQueryException { + public static List lockEmail_Transaction(Start start, Connection con, AppIdentifier appIdentifier, + String email) throws SQLException, StorageQueryException { // normally the query below will use a for update, but sqlite doesn't support it. ((ConnectionWithLocks) con).lock( - tenantIdentifier.getAppId() + tenantIdentifier.getTenantId() + "~" + email + - Config.getConfig(start).getPasswordlessUserToTenantTable()); + appIdentifier.getAppId() + "~" + email + + Config.getConfig(start).getPasswordlessUsersTable()); - String QUERY = "SELECT user_id FROM " + getConfig(start).getPasswordlessUserToTenantTable() + - " WHERE app_id = ? AND tenant_id = ? AND email = ?"; + String QUERY = "SELECT user_id FROM " + getConfig(start).getPasswordlessUsersTable() + + " WHERE app_id = ? AND email = ?"; return execute(con, QUERY, pst -> { - pst.setString(1, tenantIdentifier.getAppId()); - pst.setString(2, tenantIdentifier.getTenantId()); - pst.setString(3, email); + pst.setString(1, appIdentifier.getAppId()); + pst.setString(2, email); }, result -> { - if (result.next()) { - return result.getString("user_id"); + List userIds = new ArrayList<>(); + while (result.next()) { + userIds.add(result.getString("user_id")); } - return null; + return userIds; }); } - public static String lockPhoneAndTenant_Transaction(Start start, Connection con, - TenantIdentifier tenantIdentifier, - String phoneNumber) + public static String lockPhone_Transaction(Start start, Connection con, + AppIdentifier appIdentifier, + String phoneNumber) throws SQLException, StorageQueryException { // normally the query below will use a for update, but sqlite doesn't support it. ((ConnectionWithLocks) con).lock( - tenantIdentifier.getAppId() + tenantIdentifier.getTenantId() + "~" + phoneNumber + - Config.getConfig(start).getPasswordlessUserToTenantTable()); + appIdentifier.getAppId() + "~" + phoneNumber + + Config.getConfig(start).getPasswordlessUsersTable()); - String QUERY = "SELECT user_id FROM " + getConfig(start).getPasswordlessUserToTenantTable() + - " WHERE app_id = ? AND tenant_id = ? AND phone_number = ?"; + String QUERY = "SELECT user_id FROM " + getConfig(start).getPasswordlessUsersTable() + + " WHERE app_id = ? AND phone_number = ?"; return execute(con, QUERY, pst -> { - pst.setString(1, tenantIdentifier.getAppId()); - pst.setString(2, tenantIdentifier.getTenantId()); - pst.setString(3, phoneNumber); + pst.setString(1, appIdentifier.getAppId()); + pst.setString(2, phoneNumber); }, result -> { if (result.next()) { return result.getString("user_id"); @@ -801,6 +800,27 @@ public static String getPrimaryUserIdUsingEmail(Start start, Connection con, Ten }); } + public static List getPrimaryUserIdsUsingEmail(Start start, Connection con, AppIdentifier appIdentifier, + String email) + throws StorageQueryException, SQLException { + String QUERY = "SELECT DISTINCT all_users.primary_or_recipe_user_id AS user_id " + + "FROM " + getConfig(start).getPasswordlessUsersTable() + " AS pless" + + " JOIN " + getConfig(start).getUsersTable() + " AS all_users" + + " ON pless.app_id = all_users.app_id AND pless.user_id = all_users.user_id" + + " WHERE pless.app_id = ? AND pless.email = ?"; + + return execute(con, QUERY, pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setString(2, email); + }, result -> { + List userIds = new ArrayList<>(); + while (result.next()) { + userIds.add(result.getString("user_id")); + } + return userIds; + }); + } + public static String getPrimaryUserByPhoneNumber(Start start, Connection con, TenantIdentifier tenantIdentifier, @Nonnull String phoneNumber) throws StorageQueryException, SQLException { @@ -822,6 +842,26 @@ public static String getPrimaryUserByPhoneNumber(Start start, Connection con, Te }); } + public static String getPrimaryUserByPhoneNumber(Start start, Connection con, AppIdentifier appIdentifier, + @Nonnull String phoneNumber) + throws StorageQueryException, SQLException { + String QUERY = "SELECT DISTINCT all_users.primary_or_recipe_user_id AS user_id " + + "FROM " + getConfig(start).getPasswordlessUsersTable() + " AS pless" + + " JOIN " + getConfig(start).getUsersTable() + " AS all_users" + + " ON pless.app_id = all_users.app_id AND pless.user_id = all_users.user_id" + + " WHERE pless.app_id = ? AND pless.phone_number = ?"; + + return execute(con, QUERY, pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setString(2, phoneNumber); + }, result -> { + if (result.next()) { + return result.getString("user_id"); + } + return null; + }); + } + public static boolean addUserIdToTenant_Transaction(Start start, Connection sqlCon, TenantIdentifier tenantIdentifier, String userId) throws StorageQueryException, SQLException { diff --git a/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java index 8a1fbef92..e38b6b336 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java @@ -185,25 +185,21 @@ public static void deleteUser_Transaction(Connection sqlCon, Start start, AppIde } } - public static List lockEmailAndTenant_Transaction(Start start, Connection con, - TenantIdentifier tenantIdentifier, + public static List lockEmail_Transaction(Start start, Connection con, + AppIdentifier appIdentifier, String email) throws SQLException, StorageQueryException { // normally the query below will use a for update, but sqlite doesn't support it. ((ConnectionWithLocks) con).lock( - tenantIdentifier.getAppId() + tenantIdentifier.getTenantId() + "~" + email + - Config.getConfig(start).getThirdPartyUserToTenantTable()); + appIdentifier.getAppId() + "~" + email + + Config.getConfig(start).getThirdPartyUsersTable()); - // in psql / mysql dbs, this will lock the rows that are in both the tables that meet the ON criteria only. String QUERY = "SELECT tp.user_id as user_id " + "FROM " + getConfig(start).getThirdPartyUsersTable() + " AS tp" + - " JOIN " + getConfig(start).getThirdPartyUserToTenantTable() + " AS tp_tenants" + - " ON tp_tenants.app_id = tp.app_id AND tp_tenants.user_id = tp.user_id" + - " WHERE tp.app_id = ? AND tp_tenants.tenant_id = ? AND tp.email = ?"; + " WHERE tp.app_id = ? AND tp.email = ?"; return execute(con, QUERY, pst -> { - pst.setString(1, tenantIdentifier.getAppId()); - pst.setString(2, tenantIdentifier.getTenantId()); - pst.setString(3, email); + pst.setString(1, appIdentifier.getAppId()); + pst.setString(2, email); }, result -> { List finalResult = new ArrayList<>(); while (result.next()) { @@ -213,25 +209,24 @@ public static List lockEmailAndTenant_Transaction(Start start, Connectio }); } - public static List lockThirdPartyInfoAndTenant_Transaction(Start start, Connection con, - TenantIdentifier tenantIdentifier, - String thirdPartyId, String thirdPartyUserId) + public static List lockThirdPartyInfo_Transaction(Start start, Connection con, + AppIdentifier appIdentifier, + String thirdPartyId, String thirdPartyUserId) throws SQLException, StorageQueryException { // normally the query below will use a for update, but sqlite doesn't support it. ((ConnectionWithLocks) con).lock( - tenantIdentifier.getAppId() + tenantIdentifier.getTenantId() + "~" + thirdPartyId + thirdPartyUserId + - Config.getConfig(start).getThirdPartyUserToTenantTable()); + appIdentifier.getAppId() + "~" + thirdPartyId + thirdPartyUserId + + Config.getConfig(start).getThirdPartyUsersTable()); // in psql / mysql dbs, this will lock the rows that are in both the tables that meet the ON criteria only. String QUERY = "SELECT user_id " + - " FROM " + getConfig(start).getThirdPartyUserToTenantTable() + - " WHERE app_id = ? AND tenant_id = ? AND third_party_id = ? AND third_party_user_id = ?"; + " FROM " + getConfig(start).getThirdPartyUsersTable() + + " WHERE app_id = ? AND third_party_id = ? AND third_party_user_id = ? FOR UPDATE"; return execute(con, QUERY, pst -> { - pst.setString(1, tenantIdentifier.getAppId()); - pst.setString(2, tenantIdentifier.getTenantId()); - pst.setString(3, thirdPartyId); - pst.setString(4, thirdPartyUserId); + pst.setString(1, appIdentifier.getAppId()); + pst.setString(2, thirdPartyId); + pst.setString(3, thirdPartyUserId); }, result -> { List finalResult = new ArrayList<>(); while (result.next()) { @@ -271,7 +266,30 @@ public static List getUsersInfoUsingIdList(Start start, Connection return Collections.emptyList(); } - public static String getThirdPartyUserInfoUsingId(Start start, Connection con, TenantIdentifier tenantIdentifier, + public static List listUserIdsByThirdPartyInfo(Start start, Connection con, AppIdentifier appIdentifier, + String thirdPartyId, String thirdPartyUserId) + throws SQLException, StorageQueryException { + + String QUERY = "SELECT DISTINCT all_users.primary_or_recipe_user_id AS user_id " + + "FROM " + getConfig(start).getThirdPartyUsersTable() + " AS tp" + + " JOIN " + getConfig(start).getUsersTable() + " AS all_users" + + " ON tp.app_id = all_users.app_id AND tp.user_id = all_users.user_id" + + " WHERE tp.app_id = ? AND tp.third_party_id = ? AND tp.third_party_user_id = ?"; + + return execute(con, QUERY, pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setString(2, thirdPartyId); + pst.setString(3, thirdPartyUserId); + }, result -> { + List userIds = new ArrayList<>(); + while (result.next()) { + userIds.add(result.getString("user_id")); + } + return userIds; + }); + } + + public static String getUserIdByThirdPartyInfo(Start start, Connection con, TenantIdentifier tenantIdentifier, String thirdPartyId, String thirdPartyUserId) throws SQLException, StorageQueryException { String QUERY = "SELECT DISTINCT all_users.primary_or_recipe_user_id AS user_id " @@ -293,6 +311,27 @@ public static String getThirdPartyUserInfoUsingId(Start start, Connection con, T }); } + public static List getPrimaryUserIdUsingEmail(Start start, Connection con, + AppIdentifier appIdentifier, String email) + throws StorageQueryException, SQLException { + String QUERY = "SELECT DISTINCT all_users.primary_or_recipe_user_id AS user_id " + + "FROM " + getConfig(start).getThirdPartyUsersTable() + " AS tp" + + " JOIN " + getConfig(start).getUsersTable() + " AS all_users" + + " ON tp.app_id = all_users.app_id AND tp.user_id = all_users.user_id" + + " WHERE tp.app_id = ? AND tp.email = ?"; + + return execute(con, QUERY, pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setString(2, email); + }, result -> { + List finalResult = new ArrayList<>(); + while (result.next()) { + finalResult.add(result.getString("user_id")); + } + return finalResult; + }); + } + public static void updateUserEmail_Transaction(Start start, Connection con, AppIdentifier appIdentifier, String thirdPartyId, String thirdPartyUserId, String newEmail) throws SQLException, StorageQueryException { From 21e4ec99467059ccfc3c1841bdd7751586bf74ee Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Thu, 14 Sep 2023 12:34:11 +0530 Subject: [PATCH 122/131] fix: remove con reuse (#810) --- .../java/io/supertokens/inmemorydb/Start.java | 13 +- .../queries/EmailPasswordQueries.java | 116 ++++- .../queries/EmailVerificationQueries.java | 45 ++ .../inmemorydb/queries/GeneralQueries.java | 443 +++++++++--------- .../queries/PasswordlessQueries.java | 112 ++++- .../inmemorydb/queries/ThirdPartyQueries.java | 74 ++- 6 files changed, 520 insertions(+), 283 deletions(-) diff --git a/src/main/java/io/supertokens/inmemorydb/Start.java b/src/main/java/io/supertokens/inmemorydb/Start.java index 1fc7dfa90..3ae732b5a 100644 --- a/src/main/java/io/supertokens/inmemorydb/Start.java +++ b/src/main/java/io/supertokens/inmemorydb/Start.java @@ -2815,10 +2815,15 @@ public AuthRecipeUserInfo[] listPrimaryUsersByPhoneNumber_Transaction(AppIdentif @Override public AuthRecipeUserInfo[] listPrimaryUsersByThirdPartyInfo(AppIdentifier appIdentifier, - String thirdPartyId, - String thirdPartyUserId) + String thirdPartyId, + String thirdPartyUserId) throws StorageQueryException { - return null; // TODO + try { + return GeneralQueries.listPrimaryUsersByThirdPartyInfo(this, appIdentifier, + thirdPartyId, thirdPartyUserId); + } catch (SQLException e) { + throw new StorageQueryException(e); + } } @Override @@ -2829,7 +2834,7 @@ public AuthRecipeUserInfo[] listPrimaryUsersByThirdPartyInfo_Transaction(AppIden throws StorageQueryException { try { Connection sqlCon = (Connection) con.getConnection(); - return GeneralQueries.getPrimaryUsersByThirdPartyInfo_Transaction(this, sqlCon, appIdentifier, + return GeneralQueries.listPrimaryUsersByThirdPartyInfo_Transaction(this, sqlCon, appIdentifier, thirdPartyId, thirdPartyUserId); } catch (SQLException e) { throw new StorageQueryException(e); diff --git a/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java index 315ca7f44..374aba078 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java @@ -151,7 +151,8 @@ public static void deleteAllPasswordResetTokensForUser_Transaction(Start start, public static PasswordResetTokenInfo[] getAllPasswordResetTokenInfoForUser(Start start, AppIdentifier appIdentifier, String userId) throws StorageQueryException, SQLException { - String QUERY = "SELECT user_id, token, token_expiry FROM " + getConfig(start).getPasswordResetTokensTable() + String QUERY = + "SELECT user_id, token, token_expiry, email FROM " + getConfig(start).getPasswordResetTokensTable() + " WHERE app_id = ? AND user_id = ?"; return execute(start, QUERY, pst -> { @@ -201,7 +202,8 @@ public static PasswordResetTokenInfo[] getAllPasswordResetTokenInfoForUser_Trans public static PasswordResetTokenInfo getPasswordResetTokenInfo(Start start, AppIdentifier appIdentifier, String token) throws SQLException, StorageQueryException { - String QUERY = "SELECT user_id, token, token_expiry FROM " + getConfig(start).getPasswordResetTokensTable() + String QUERY = + "SELECT user_id, token, token_expiry, email FROM " + getConfig(start).getPasswordResetTokensTable() + " WHERE app_id = ? AND token = ?"; return execute(start, QUERY, pst -> { pst.setString(1, appIdentifier.getAppId()); @@ -308,7 +310,8 @@ public static AuthRecipeUserInfo signUp(Start start, TenantIdentifier tenantIden }); } - public static void deleteUser_Transaction(Connection sqlCon, Start start, AppIdentifier appIdentifier, String userId, boolean deleteUserIdMappingToo) + public static void deleteUser_Transaction(Connection sqlCon, Start start, AppIdentifier appIdentifier, + String userId, boolean deleteUserIdMappingToo) throws StorageQueryException, SQLException { if (deleteUserIdMappingToo) { String QUERY = "DELETE FROM " + getConfig(start).getAppIdToUserIdTable() @@ -348,10 +351,10 @@ public static void deleteUser_Transaction(Connection sqlCon, Start start, AppIde } } - public static UserInfoPartial getUserInfoUsingId(Start start, Connection sqlCon, AppIdentifier appIdentifier, + private static UserInfoPartial getUserInfoUsingId_Transaction(Start start, Connection sqlCon, AppIdentifier appIdentifier, String id) throws SQLException, StorageQueryException { - // we don't need a LOCK here because this is already part of a transaction, and locked on app_id_to_user_id - // table + // we don't need a FOR UPDATE here because this is already part of a transaction, and locked on + // app_id_to_user_id table String QUERY = "SELECT user_id, email, password_hash, time_joined FROM " + getConfig(start).getEmailPasswordUsersTable() + " WHERE app_id = ? AND user_id = ?"; @@ -366,14 +369,47 @@ public static UserInfoPartial getUserInfoUsingId(Start start, Connection sqlCon, }); } - public static List getUsersInfoUsingIdList(Start start, Connection con, Set ids, + public static List getUsersInfoUsingIdList(Start start, Set ids, AppIdentifier appIdentifier) throws SQLException, StorageQueryException { if (ids.size() > 0) { // No need to filter based on tenantId because the id list is already filtered for a tenant String QUERY = "SELECT user_id, email, password_hash, time_joined " - + "FROM " + getConfig(start).getEmailPasswordUsersTable() + " WHERE user_id IN (" + - Utils.generateCommaSeperatedQuestionMarks(ids.size()) + ") AND app_id = ?"; + + "FROM " + getConfig(start).getEmailPasswordUsersTable() + + " WHERE user_id IN (" + Utils.generateCommaSeperatedQuestionMarks(ids.size()) + + " ) AND app_id = ?"; + + List userInfos = execute(start, QUERY, pst -> { + int index = 1; + for (String id : ids) { + pst.setString(index, id); + index++; + } + pst.setString(index, appIdentifier.getAppId()); + }, result -> { + List finalResult = new ArrayList<>(); + while (result.next()) { + finalResult.add(UserInfoRowMapper.getInstance().mapOrThrow(result)); + } + return finalResult; + }); + fillUserInfoWithTenantIds(start, appIdentifier, userInfos); + fillUserInfoWithVerified(start, appIdentifier, userInfos); + return userInfos.stream().map(UserInfoPartial::toLoginMethod) + .collect(Collectors.toList()); + } + return Collections.emptyList(); + } + + public static List getUsersInfoUsingIdList_Transaction(Start start, Connection con, Set ids, + AppIdentifier appIdentifier) + throws SQLException, StorageQueryException { + if (ids.size() > 0) { + // No need to filter based on tenantId because the id list is already filtered for a tenant + String QUERY = "SELECT user_id, email, password_hash, time_joined " + + "FROM " + getConfig(start).getEmailPasswordUsersTable() + + " WHERE user_id IN (" + Utils.generateCommaSeperatedQuestionMarks(ids.size()) + + " ) AND app_id = ?"; List userInfos = execute(con, QUERY, pst -> { int index = 1; @@ -396,14 +432,10 @@ public static List getUsersInfoUsingIdList(Start start, Connection } return Collections.emptyList(); } - - public static String lockEmail_Transaction(Start start, Connection con, AppIdentifier appIdentifier, - String email) throws SQLException, StorageQueryException { - // normally the query below will use a for update, but sqlite doesn't support it. - ((ConnectionWithLocks) con).lock( - appIdentifier.getAppId() + "~" + email + - Config.getConfig(start).getEmailPasswordUsersTable()); - + public static String lockEmail_Transaction(Start start, Connection con, + AppIdentifier appIdentifier, + String email) + throws StorageQueryException, SQLException { String QUERY = "SELECT user_id FROM " + getConfig(start).getEmailPasswordUsersTable() + " WHERE app_id = ? AND email = ?"; return execute(con, QUERY, pst -> { @@ -417,7 +449,7 @@ public static String lockEmail_Transaction(Start start, Connection con, AppIdent }); } - public static String getPrimaryUserIdUsingEmail(Start start, Connection con, TenantIdentifier tenantIdentifier, + public static String getPrimaryUserIdUsingEmail(Start start, TenantIdentifier tenantIdentifier, String email) throws StorageQueryException, SQLException { String QUERY = "SELECT DISTINCT all_users.primary_or_recipe_user_id AS user_id " @@ -426,7 +458,7 @@ public static String getPrimaryUserIdUsingEmail(Start start, Connection con, Ten " ON ep.app_id = all_users.app_id AND ep.user_id = all_users.user_id" + " WHERE ep.app_id = ? AND ep.tenant_id = ? AND ep.email = ?"; - return execute(con, QUERY, pst -> { + return execute(start, QUERY, pst -> { pst.setString(1, tenantIdentifier.getAppId()); pst.setString(2, tenantIdentifier.getTenantId()); pst.setString(3, email); @@ -438,7 +470,7 @@ public static String getPrimaryUserIdUsingEmail(Start start, Connection con, Ten }); } - public static List getPrimaryUserIdsUsingEmail(Start start, Connection con, AppIdentifier appIdentifier, + public static List getPrimaryUserIdsUsingEmail_Transaction(Start start, Connection con, AppIdentifier appIdentifier, String email) throws StorageQueryException, SQLException { String QUERY = "SELECT DISTINCT all_users.primary_or_recipe_user_id AS user_id " @@ -462,7 +494,7 @@ public static List getPrimaryUserIdsUsingEmail(Start start, Connection c public static boolean addUserIdToTenant_Transaction(Start start, Connection sqlCon, TenantIdentifier tenantIdentifier, String userId) throws SQLException, StorageQueryException { - UserInfoPartial userInfo = EmailPasswordQueries.getUserInfoUsingId(start, sqlCon, + UserInfoPartial userInfo = EmailPasswordQueries.getUserInfoUsingId_Transaction(start, sqlCon, tenantIdentifier.toAppIdentifier(), userId); { // all_auth_recipe_users @@ -543,6 +575,28 @@ private static List fillUserInfoWithVerified_transaction(Start return userInfos; } + private static List fillUserInfoWithVerified(Start start, + AppIdentifier appIdentifier, + List userInfos) + throws SQLException, StorageQueryException { + List userIdsAndEmails = new ArrayList<>(); + for (UserInfoPartial userInfo : userInfos) { + userIdsAndEmails.add(new EmailVerificationQueries.UserIdAndEmail(userInfo.id, userInfo.email)); + } + List userIdsThatAreVerified = EmailVerificationQueries.isEmailVerified(start, + appIdentifier, + userIdsAndEmails); + Set verifiedUserIdsSet = new HashSet<>(userIdsThatAreVerified); + for (UserInfoPartial userInfo : userInfos) { + if (verifiedUserIdsSet.contains(userInfo.id)) { + userInfo.verified = true; + } else { + userInfo.verified = false; + } + } + return userInfos; + } + private static UserInfoPartial fillUserInfoWithTenantIds_transaction(Start start, Connection sqlCon, AppIdentifier appIdentifier, UserInfoPartial userInfo) @@ -567,6 +621,26 @@ private static List fillUserInfoWithTenantIds_transaction(Start for (UserInfoPartial userInfo : userInfos) { userInfo.tenantIds = tenantIdsForUserIds.get(userInfo.id).toArray(new String[0]); } + + return userInfos; + } + + private static List fillUserInfoWithTenantIds(Start start, + AppIdentifier appIdentifier, + List userInfos) + throws SQLException, StorageQueryException { + String[] userIds = new String[userInfos.size()]; + for (int i = 0; i < userInfos.size(); i++) { + userIds[i] = userInfos.get(i).id; + } + + Map> tenantIdsForUserIds = GeneralQueries.getTenantIdsForUserIds(start, + appIdentifier, + userIds); + for (UserInfoPartial userInfo : userInfos) { + userInfo.tenantIds = tenantIdsForUserIds.get(userInfo.id).toArray(new String[0]); + } + return userInfos; } diff --git a/src/main/java/io/supertokens/inmemorydb/queries/EmailVerificationQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/EmailVerificationQueries.java index 2c5d2c207..3cfc1274e 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/EmailVerificationQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/EmailVerificationQueries.java @@ -278,6 +278,51 @@ public static List isEmailVerified_transaction(Start start, Connection s }); } + public static List isEmailVerified(Start start, AppIdentifier appIdentifier, + List userIdAndEmail) + throws SQLException, StorageQueryException { + if (userIdAndEmail.isEmpty()) { + return new ArrayList<>(); + } + List emails = new ArrayList<>(); + List userIds = new ArrayList<>(); + Map userIdToEmailMap = new HashMap<>(); + for (UserIdAndEmail ue : userIdAndEmail) { + emails.add(ue.email); + userIds.add(ue.userId); + } + for (UserIdAndEmail ue : userIdAndEmail) { + if (userIdToEmailMap.containsKey(ue.userId)) { + throw new RuntimeException("Found a bug!"); + } + userIdToEmailMap.put(ue.userId, ue.email); + } + String QUERY = "SELECT * FROM " + getConfig(start).getEmailVerificationTable() + + " WHERE app_id = ? AND user_id IN (" + Utils.generateCommaSeperatedQuestionMarks(userIds.size()) + + ") AND email IN (" + Utils.generateCommaSeperatedQuestionMarks(emails.size()) + ")"; + + return execute(start, QUERY, pst -> { + pst.setString(1, appIdentifier.getAppId()); + int index = 2; + for (String userId : userIds) { + pst.setString(index++, userId); + } + for (String email : emails) { + pst.setString(index++, email); + } + }, result -> { + List res = new ArrayList<>(); + while (result.next()) { + String userId = result.getString("user_id"); + String email = result.getString("email"); + if (Objects.equals(userIdToEmailMap.get(userId), email)) { + res.add(userId); + } + } + return res; + }); + } + public static void deleteUserInfo_Transaction(Connection sqlCon, Start start, AppIdentifier appIdentifier, String userId) throws StorageQueryException, SQLException { { diff --git a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java index a3acdb0a8..1282f9ca5 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java @@ -85,7 +85,17 @@ static String getQueryToCreateUsersTable(Start start) { + ");"; } - static String getQueryToCreateUserPaginationIndex(Start start) { + public static String getQueryToCreateUserIdIndexForUsersTable(Start start) { + return "CREATE INDEX IF NOT EXISTS all_auth_recipe_user_id_index ON " + + Config.getConfig(start).getUsersTable() + "(app_id, user_id);"; + } + + public static String getQueryToCreateTenantIdIndexForUsersTable(Start start) { + return "CREATE INDEX IF NOT EXISTS all_auth_recipe_tenant_id_index ON " + + Config.getConfig(start).getUsersTable() + "(app_id, tenant_id);"; + } + + static String getQueryToCreateUserPaginationIndex(Start start) { return "CREATE INDEX all_auth_recipe_users_pagination_index ON " + Config.getConfig(start).getUsersTable() + "(primary_or_recipe_user_time_joined DESC, primary_or_recipe_user_id DESC, tenant_id DESC, app_id DESC);"; } @@ -150,7 +160,12 @@ static String getQueryToCreateKeyValueTable(Start start) { } - private static String getQueryToCreateAppIdToUserIdTable(Start start) { + static String getQueryToCreateTenantIdIndexForKeyValueTable(Start start) { + return "CREATE INDEX IF NOT EXISTS key_value_tenant_id_index ON " + + Config.getConfig(start).getKeyValueTable() + "(app_id, tenant_id);"; + } + + private static String getQueryToCreateAppIdToUserIdTable(Start start) { String appToUserTable = Config.getConfig(start).getAppIdToUserIdTable(); // @formatter:off return "CREATE TABLE IF NOT EXISTS " + appToUserTable + " (" @@ -548,13 +563,11 @@ public static boolean doesUserIdExist(Start start, TenantIdentifier tenantIdenti }, ResultSet::next); } - public static AuthRecipeUserInfo[] getUsers(Start start, TenantIdentifier tenantIdentifier, @NotNull Integer limit, - @NotNull String timeJoinedOrder, - @org.jetbrains.annotations.Nullable RECIPE_ID[] includeRecipeIds, - @org.jetbrains.annotations.Nullable - String userId, - @org.jetbrains.annotations.Nullable Long timeJoined, - @Nullable DashboardSearchTags dashboardSearchTags) + public static AuthRecipeUserInfo[] getUsers(Start start, TenantIdentifier tenantIdentifier, @NotNull Integer limit, + @NotNull String timeJoinedOrder, + @Nullable RECIPE_ID[] includeRecipeIds, @Nullable String userId, + @Nullable Long timeJoined, + @Nullable DashboardSearchTags dashboardSearchTags) throws SQLException, StorageQueryException { // This list will be used to keep track of the result's order from the db @@ -746,7 +759,6 @@ public static AuthRecipeUserInfo[] getUsers(Start start, TenantIdentifier tenant return temp; }); } - } } else { @@ -827,7 +839,6 @@ public static AuthRecipeUserInfo[] getUsers(Start start, TenantIdentifier tenant AuthRecipeUserInfo[] finalResult = new AuthRecipeUserInfo[usersFromQuery.size()]; - // we give the userId[] for each recipe to fetch all those user's details List users = getPrimaryUserInfoForUserIds(start, tenantIdentifier.toAppIdentifier(), usersFromQuery); @@ -927,10 +938,34 @@ public static AuthRecipeUserInfo[] listPrimaryUsersByPhoneNumber_Transaction(Sta PasswordlessQueries.lockPhone_Transaction(start, sqlCon, appIdentifier, phoneNumber); // now that we have locks on all the relevant tables, we can read from them safely - return listPrimaryUsersByPhoneNumberHelper(start, sqlCon, appIdentifier, phoneNumber); + List userIds = PasswordlessQueries.listUserIdsByPhoneNumber_Transaction(start, sqlCon, appIdentifier, + phoneNumber); + + List result = getPrimaryUserInfoForUserIds_Transaction(start, sqlCon, appIdentifier, + userIds); + + // this is going to order them based on oldest that joined to newest that joined. + result.sort(Comparator.comparingLong(o -> o.timeJoined)); + + return result.toArray(new AuthRecipeUserInfo[0]); + } + + public static AuthRecipeUserInfo[] listPrimaryUsersByThirdPartyInfo(Start start, + AppIdentifier appIdentifier, + String thirdPartyId, + String thirdPartyUserId) + throws SQLException, StorageQueryException { + List userIds = ThirdPartyQueries.listUserIdsByThirdPartyInfo(start, appIdentifier, + thirdPartyId, thirdPartyUserId); + List result = getPrimaryUserInfoForUserIds(start, appIdentifier, userIds); + + // this is going to order them based on oldest that joined to newest that joined. + result.sort(Comparator.comparingLong(o -> o.timeJoined)); + + return result.toArray(new AuthRecipeUserInfo[0]); } - public static AuthRecipeUserInfo[] getPrimaryUsersByThirdPartyInfo_Transaction(Start start, Connection sqlCon, + public static AuthRecipeUserInfo[] listPrimaryUsersByThirdPartyInfo_Transaction(Start start, Connection sqlCon, AppIdentifier appIdentifier, String thirdPartyId, String thirdPartyUserId) @@ -944,7 +979,14 @@ public static AuthRecipeUserInfo[] getPrimaryUsersByThirdPartyInfo_Transaction(S thirdPartyUserId); // now that we have locks on all the relevant tables, we can read from them safely - return listPrimaryUsersByThirdPartyInfoHelper(start, sqlCon, appIdentifier, thirdPartyId, thirdPartyUserId); + List userIds = ThirdPartyQueries.listUserIdsByThirdPartyInfo_Transaction(start, sqlCon, appIdentifier, + thirdPartyId, thirdPartyUserId); + List result = getPrimaryUserInfoForUserIds_Transaction(start, sqlCon, appIdentifier, userIds); + + // this is going to order them based on oldest that joined to newest that joined. + result.sort(Comparator.comparingLong(o -> o.timeJoined)); + + return result.toArray(new AuthRecipeUserInfo[0]); } public static AuthRecipeUserInfo[] listPrimaryUsersByEmail_Transaction(Start start, Connection sqlCon, @@ -962,41 +1004,20 @@ public static AuthRecipeUserInfo[] listPrimaryUsersByEmail_Transaction(Start sta PasswordlessQueries.lockEmail_Transaction(start, sqlCon, appIdentifier, email); // now that we have locks on all the relevant tables, we can read from them safely - return listPrimaryUsersByEmailHelper(start, sqlCon, appIdentifier, email); - } - - public static AuthRecipeUserInfo[] listPrimaryUsersByEmail(Start start, TenantIdentifier tenantIdentifier, - String email) - throws StorageQueryException, SQLException { - try (Connection con = ConnectionPool.getConnection(start)) { - return listPrimaryUsersByEmailHelper(start, con, tenantIdentifier, email); - } - } - - public static AuthRecipeUserInfo[] listPrimaryUsersByEmailHelper(Start start, Connection con, - TenantIdentifier tenantIdentifier, - String email) - throws StorageQueryException, SQLException { List userIds = new ArrayList<>(); - String emailPasswordUserId = EmailPasswordQueries.getPrimaryUserIdUsingEmail(start, con, tenantIdentifier, - email); - if (emailPasswordUserId != null) { - userIds.add(emailPasswordUserId); - } + userIds.addAll(EmailPasswordQueries.getPrimaryUserIdsUsingEmail_Transaction(start, sqlCon, appIdentifier, + email)); - String passwordlessUserId = PasswordlessQueries.getPrimaryUserIdUsingEmail(start, con, tenantIdentifier, - email); - if (passwordlessUserId != null) { - userIds.add(passwordlessUserId); - } + userIds.addAll(PasswordlessQueries.getPrimaryUserIdsUsingEmail_Transaction(start, sqlCon, appIdentifier, + email)); - userIds.addAll(ThirdPartyQueries.getPrimaryUserIdUsingEmail(start, con, tenantIdentifier, email)); + userIds.addAll(ThirdPartyQueries.getPrimaryUserIdUsingEmail_Transaction(start, sqlCon, appIdentifier, email)); // remove duplicates from userIds Set userIdsSet = new HashSet<>(userIds); userIds = new ArrayList<>(userIdsSet); - List result = getPrimaryUserInfoForUserIds(start, con, tenantIdentifier.toAppIdentifier(), + List result = getPrimaryUserInfoForUserIds(start, appIdentifier, userIds); // this is going to order them based on oldest that joined to newest that joined. @@ -1005,24 +1026,29 @@ public static AuthRecipeUserInfo[] listPrimaryUsersByEmailHelper(Start start, Co return result.toArray(new AuthRecipeUserInfo[0]); } - public static AuthRecipeUserInfo[] listPrimaryUsersByEmailHelper(Start start, Connection con, - AppIdentifier appIdentifier, - String email) + public static AuthRecipeUserInfo[] listPrimaryUsersByEmail(Start start, TenantIdentifier tenantIdentifier, + String email) throws StorageQueryException, SQLException { List userIds = new ArrayList<>(); - userIds.addAll(EmailPasswordQueries.getPrimaryUserIdsUsingEmail(start, con, appIdentifier, - email)); + String emailPasswordUserId = EmailPasswordQueries.getPrimaryUserIdUsingEmail(start, tenantIdentifier, + email); + if (emailPasswordUserId != null) { + userIds.add(emailPasswordUserId); + } - userIds.addAll(PasswordlessQueries.getPrimaryUserIdsUsingEmail(start, con, appIdentifier, - email)); + String passwordlessUserId = PasswordlessQueries.getPrimaryUserIdUsingEmail(start, tenantIdentifier, + email); + if (passwordlessUserId != null) { + userIds.add(passwordlessUserId); + } - userIds.addAll(ThirdPartyQueries.getPrimaryUserIdUsingEmail(start, con, appIdentifier, email)); + userIds.addAll(ThirdPartyQueries.getPrimaryUserIdUsingEmail(start, tenantIdentifier, email)); // remove duplicates from userIds Set userIdsSet = new HashSet<>(userIds); userIds = new ArrayList<>(userIdsSet); - List result = getPrimaryUserInfoForUserIds(start, con, appIdentifier, + List result = getPrimaryUserInfoForUserIds(start, tenantIdentifier.toAppIdentifier(), userIds); // this is going to order them based on oldest that joined to newest that joined. @@ -1035,24 +1061,15 @@ public static AuthRecipeUserInfo[] listPrimaryUsersByPhoneNumber(Start start, TenantIdentifier tenantIdentifier, String phoneNumber) throws StorageQueryException, SQLException { - try (Connection con = ConnectionPool.getConnection(start)) { - return listPrimaryUsersByPhoneNumberHelper(start, con, tenantIdentifier, phoneNumber); - } - } - - private static AuthRecipeUserInfo[] listPrimaryUsersByPhoneNumberHelper(Start start, Connection con, - TenantIdentifier tenantIdentifier, - String phoneNumber) - throws StorageQueryException, SQLException { List userIds = new ArrayList<>(); - String passwordlessUserId = PasswordlessQueries.getPrimaryUserByPhoneNumber(start, con, tenantIdentifier, + String passwordlessUserId = PasswordlessQueries.getPrimaryUserByPhoneNumber(start, tenantIdentifier, phoneNumber); if (passwordlessUserId != null) { userIds.add(passwordlessUserId); } - List result = getPrimaryUserInfoForUserIds(start, con, tenantIdentifier.toAppIdentifier(), + List result = getPrimaryUserInfoForUserIds(start, tenantIdentifier.toAppIdentifier(), userIds); // this is going to order them based on oldest that joined to newest that joined. @@ -1061,121 +1078,100 @@ private static AuthRecipeUserInfo[] listPrimaryUsersByPhoneNumberHelper(Start st return result.toArray(new AuthRecipeUserInfo[0]); } - - private static AuthRecipeUserInfo[] listPrimaryUsersByPhoneNumberHelper(Start start, Connection con, - AppIdentifier appIdentifier, - String phoneNumber) - throws StorageQueryException, SQLException { - List userIds = new ArrayList<>(); - - String passwordlessUserId = PasswordlessQueries.getPrimaryUserByPhoneNumber(start, con, appIdentifier, - phoneNumber); - if (passwordlessUserId != null) { - userIds.add(passwordlessUserId); - } - - List result = getPrimaryUserInfoForUserIds(start, con, appIdentifier, - userIds); - - // this is going to order them based on oldest that joined to newest that joined. - result.sort(Comparator.comparingLong(o -> o.timeJoined)); - - return result.toArray(new AuthRecipeUserInfo[0]); - } - - public static AuthRecipeUserInfo[] listPrimaryUsersByThirdPartyInfo(Start start, - AppIdentifier appIdentifier, + public static AuthRecipeUserInfo getPrimaryUserByThirdPartyInfo(Start start, + TenantIdentifier tenantIdentifier, String thirdPartyId, String thirdPartyUserId) throws StorageQueryException, SQLException { - try (Connection con = ConnectionPool.getConnection(start)) { - return listPrimaryUsersByThirdPartyInfoHelper(start, con, appIdentifier, thirdPartyId, thirdPartyUserId); - } - } - - public static AuthRecipeUserInfo[] listPrimaryUsersByThirdPartyInfo_transaction(Start start, - Connection sqlCon, - AppIdentifier appIdentifier, - String thirdPartyId, - String thirdPartyUserId) - throws StorageQueryException, SQLException { - return listPrimaryUsersByThirdPartyInfoHelper(start, sqlCon, appIdentifier, thirdPartyId, thirdPartyUserId); - } - - public static AuthRecipeUserInfo getPrimaryUserByThirdPartyInfo(Start start, - TenantIdentifier tenantIdentifier, - String thirdPartyId, - String thirdPartyUserId) - throws StorageQueryException, SQLException { - try (Connection con = ConnectionPool.getConnection(start)) { - return getPrimaryUserByThirdPartyInfoHelper(start, con, tenantIdentifier, thirdPartyId, thirdPartyUserId); - } + String userId = ThirdPartyQueries.getUserIdByThirdPartyInfo(start, tenantIdentifier, + thirdPartyId, thirdPartyUserId); + return getPrimaryUserInfoForUserId(start, tenantIdentifier.toAppIdentifier(), userId); } - private static AuthRecipeUserInfo[] listPrimaryUsersByThirdPartyInfoHelper(Start start, Connection con, - AppIdentifier appIdentifier, - String thirdPartyId, - String thirdPartyUserId) - throws StorageQueryException, SQLException { - - List userIds = ThirdPartyQueries.listUserIdsByThirdPartyInfo(start, con, appIdentifier, - thirdPartyId, thirdPartyUserId); - List result = getPrimaryUserInfoForUserIds(start, con, appIdentifier, userIds); - - // this is going to order them based on oldest that joined to newest that joined. - result.sort(Comparator.comparingLong(o -> o.timeJoined)); - - return result.toArray(new AuthRecipeUserInfo[0]); - } - - private static AuthRecipeUserInfo getPrimaryUserByThirdPartyInfoHelper(Start start, Connection con, - TenantIdentifier tenantIdentifier, - String thirdPartyId, - String thirdPartyUserId) - throws StorageQueryException, SQLException { - - String userId = ThirdPartyQueries.getUserIdByThirdPartyInfo(start, con, tenantIdentifier, - thirdPartyId, thirdPartyUserId); - if (userId != null) { - return getPrimaryUserInfoForUserId(start, con, tenantIdentifier.toAppIdentifier(), - userId); - } - return null; - } + public static String getPrimaryUserIdStrForUserId(Start start, AppIdentifier appIdentifier, String id) + throws SQLException, StorageQueryException { + String QUERY = "SELECT primary_or_recipe_user_id FROM " + getConfig(start).getUsersTable() + + " WHERE user_id = ? AND app_id = ?"; + return execute(start, QUERY, pst -> { + pst.setString(1, id); + pst.setString(2, appIdentifier.getAppId()); + }, result -> { + if (result.next()) { + return result.getString("primary_or_recipe_user_id"); + } + return null; + }); + } - public static AuthRecipeUserInfo getPrimaryUserInfoForUserId_Transaction(Start start, Connection sqlCon, - AppIdentifier appIdentifier, String id) + public static AuthRecipeUserInfo getPrimaryUserInfoForUserId(Start start, AppIdentifier appIdentifier, String id) throws SQLException, StorageQueryException { - ((ConnectionWithLocks) sqlCon).lock( - appIdentifier + "~" + id + Config.getConfig(start).getUsersTable()); + List ids = new ArrayList<>(); + ids.add(id); + List result = getPrimaryUserInfoForUserIds(start, appIdentifier, ids); + if (result.isEmpty()) { + return null; + } + return result.get(0); + } + + public static AuthRecipeUserInfo getPrimaryUserInfoForUserId_Transaction(Start start, Connection con, + AppIdentifier appIdentifier, String id) + throws SQLException, StorageQueryException { + List ids = new ArrayList<>(); + ids.add(id); + List result = getPrimaryUserInfoForUserIds_Transaction(start, con, appIdentifier, ids); + if (result.isEmpty()) { + return null; + } + return result.get(0); + } - // We use SELECT FOR UPDATE in the query below for other plugins. + private static List getPrimaryUserInfoForUserIds(Start start, + AppIdentifier appIdentifier, + List userIds) + throws StorageQueryException, SQLException { +if (userIds.size() == 0) { + return new ArrayList<>(); + } + + // We check both user_id and primary_or_recipe_user_id because the input may have a recipe userId + // which is linked to a primary user ID in which case it won't be in the primary_or_recipe_user_id column, + // or the input may have a primary user ID whose recipe user ID was removed, so it won't be in the user_id + // column String QUERY = "SELECT * FROM " + getConfig(start).getUsersTable() + " WHERE primary_or_recipe_user_id IN (SELECT primary_or_recipe_user_id FROM " + - getConfig(start).getUsersTable() + - " WHERE user_id = ? OR primary_or_recipe_user_id = ? AND app_id = ?) AND app_id = ?"; - - List allAuthUsersResult = execute(sqlCon, QUERY, pst -> { - pst.setString(1, id); - pst.setString(2, id); - pst.setString(3, appIdentifier.getAppId()); + getConfig(start).getUsersTable() + " WHERE (user_id IN (" + + Utils.generateCommaSeperatedQuestionMarks(userIds.size()) + + ") OR primary_or_recipe_user_id IN (" + + Utils.generateCommaSeperatedQuestionMarks(userIds.size()) + + ")) AND app_id = ?) AND app_id = ?"; + + List allAuthUsersResult = execute(start, QUERY, pst -> { + // IN user_id + int index = 1; + for (int i = 0; i < userIds.size(); i++, index++) { + pst.setString(index, userIds.get(i)); + } + // IN primary_or_recipe_user_id + for (int i = 0; i < userIds.size(); i++, index++) { + pst.setString(index, userIds.get(i)); + } + // for app_id + pst.setString(index, appIdentifier.getAppId()); + pst.setString(index + 1, appIdentifier.getAppId()); }, result -> { - List finalResult = new ArrayList<>(); + List parsedResult = new ArrayList<>(); while (result.next()) { - finalResult.add(new AllAuthRecipeUsersResultHolder(result.getString("user_id"), + parsedResult.add(new AllAuthRecipeUsersResultHolder(result.getString("user_id"), result.getString("tenant_id"), result.getString("primary_or_recipe_user_id"), result.getBoolean("is_linked_or_is_a_primary_user"), result.getString("recipe_id"), result.getLong("time_joined"))); } - return finalResult; + return parsedResult; }); - if (allAuthUsersResult.size() == 0) { - return null; - } - // Now we form the userIds again, but based on the user_id in the result from above. Set recipeUserIdsToFetch = new HashSet<>(); for (AllAuthRecipeUsersResultHolder user : allAuthUsersResult) { @@ -1185,11 +1181,10 @@ public static AuthRecipeUserInfo getPrimaryUserInfoForUserId_Transaction(Start s List loginMethods = new ArrayList<>(); loginMethods.addAll( - EmailPasswordQueries.getUsersInfoUsingIdList(start, sqlCon, recipeUserIdsToFetch, appIdentifier)); + EmailPasswordQueries.getUsersInfoUsingIdList(start, recipeUserIdsToFetch, appIdentifier)); + loginMethods.addAll(ThirdPartyQueries.getUsersInfoUsingIdList(start, recipeUserIdsToFetch, appIdentifier)); loginMethods.addAll( - ThirdPartyQueries.getUsersInfoUsingIdList(start, sqlCon, recipeUserIdsToFetch, appIdentifier)); - loginMethods.addAll( - PasswordlessQueries.getUsersInfoUsingIdList(start, sqlCon, recipeUserIdsToFetch, appIdentifier)); + PasswordlessQueries.getUsersInfoUsingIdList(start, recipeUserIdsToFetch, appIdentifier)); Map recipeUserIdToLoginMethodMap = new HashMap<>(); for (LoginMethod loginMethod : loginMethods) { @@ -1197,14 +1192,13 @@ public static AuthRecipeUserInfo getPrimaryUserInfoForUserId_Transaction(Start s } Map userIdToAuthRecipeUserInfo = new HashMap<>(); - String pUserId = null; + for (AllAuthRecipeUsersResultHolder authRecipeUsersResultHolder : allAuthUsersResult) { String recipeUserId = authRecipeUsersResultHolder.userId; LoginMethod loginMethod = recipeUserIdToLoginMethodMap.get(recipeUserId); assert (loginMethod != null); String primaryUserId = authRecipeUsersResultHolder.primaryOrRecipeUserId; - pUserId = primaryUserId; - AuthRecipeUserInfo curr = userIdToAuthRecipeUserInfo.get(primaryUserId); + AuthRecipeUserInfo curr = userIdToAuthRecipeUserInfo.get(primaryUserId); if (curr == null) { curr = AuthRecipeUserInfo.create(primaryUserId, authRecipeUsersResultHolder.isLinkedOrIsAPrimaryUser, loginMethod); @@ -1214,47 +1208,11 @@ public static AuthRecipeUserInfo getPrimaryUserInfoForUserId_Transaction(Start s userIdToAuthRecipeUserInfo.put(primaryUserId, curr); } - assert (userIdToAuthRecipeUserInfo.size() == 1 && pUserId != null); - - return userIdToAuthRecipeUserInfo.get(pUserId); - } - - public static String getPrimaryUserIdStrForUserId(Start start, AppIdentifier appIdentifier, String id) - throws SQLException, StorageQueryException { - String QUERY = "SELECT primary_or_recipe_user_id FROM " + getConfig(start).getUsersTable() + - " WHERE user_id = ? AND app_id = ?"; - return execute(start, QUERY, pst -> { - pst.setString(1, id); - pst.setString(2, appIdentifier.getAppId()); - }, result -> { - if (result.next()) { - return result.getString("primary_or_recipe_user_id"); - } - return null; - }); - } - - public static AuthRecipeUserInfo getPrimaryUserInfoForUserId(Start start, AppIdentifier appIdentifier, String id) - throws SQLException, StorageQueryException { - try (Connection con = ConnectionPool.getConnection(start)) { - return getPrimaryUserInfoForUserId(start, con, appIdentifier, id); - } - } - - private static AuthRecipeUserInfo getPrimaryUserInfoForUserId(Start start, Connection con, - AppIdentifier appIdentifier, String id) - throws SQLException, StorageQueryException { - List ids = new ArrayList<>(); - ids.add(id); - List result = getPrimaryUserInfoForUserIds(start, con, appIdentifier, ids); - if (result.isEmpty()) { - return null; - } - return result.get(0); + return userIdToAuthRecipeUserInfo.keySet().stream().map(userIdToAuthRecipeUserInfo::get) + .collect(Collectors.toList()); } - private static List getPrimaryUserInfoForUserIds(Start start, - Connection con, + private static List getPrimaryUserInfoForUserIds_Transaction(Start start, Connection sqlCon, AppIdentifier appIdentifier, List userIds) throws StorageQueryException, SQLException { @@ -1274,7 +1232,7 @@ private static List getPrimaryUserInfoForUserIds(Start start Utils.generateCommaSeperatedQuestionMarks(userIds.size()) + ")) AND app_id = ?) AND app_id = ?"; - List allAuthUsersResult = execute(con, QUERY, pst -> { + List allAuthUsersResult = execute(sqlCon, QUERY, pst -> { // IN user_id int index = 1; for (int i = 0; i < userIds.size(); i++, index++) { @@ -1309,10 +1267,10 @@ private static List getPrimaryUserInfoForUserIds(Start start List loginMethods = new ArrayList<>(); loginMethods.addAll( - EmailPasswordQueries.getUsersInfoUsingIdList(start, con, recipeUserIdsToFetch, appIdentifier)); - loginMethods.addAll(ThirdPartyQueries.getUsersInfoUsingIdList(start, con, recipeUserIdsToFetch, appIdentifier)); + EmailPasswordQueries.getUsersInfoUsingIdList_Transaction(start, sqlCon, recipeUserIdsToFetch, appIdentifier)); + loginMethods.addAll(ThirdPartyQueries.getUsersInfoUsingIdList_Transaction(start, sqlCon, recipeUserIdsToFetch, appIdentifier)); loginMethods.addAll( - PasswordlessQueries.getUsersInfoUsingIdList(start, con, recipeUserIdsToFetch, appIdentifier)); + PasswordlessQueries.getUsersInfoUsingIdList_Transaction(start, sqlCon, recipeUserIdsToFetch, appIdentifier)); Map recipeUserIdToLoginMethodMap = new HashMap<>(); for (LoginMethod loginMethod : loginMethods) { @@ -1325,7 +1283,6 @@ private static List getPrimaryUserInfoForUserIds(Start start String recipeUserId = authRecipeUsersResultHolder.userId; LoginMethod loginMethod = recipeUserIdToLoginMethodMap.get(recipeUserId); assert (loginMethod != null); - String primaryUserId = authRecipeUsersResultHolder.primaryOrRecipeUserId; AuthRecipeUserInfo curr = userIdToAuthRecipeUserInfo.get(primaryUserId); if (curr == null) { @@ -1341,20 +1298,6 @@ private static List getPrimaryUserInfoForUserIds(Start start .collect(Collectors.toList()); } - private static List getPrimaryUserInfoForUserIds(Start start, - AppIdentifier appIdentifier, - List userIds) - throws StorageQueryException, SQLException { - if (userIds.size() == 0) { - return new ArrayList<>(); - } - - try (Connection con = ConnectionPool.getConnection(start)) { - return getPrimaryUserInfoForUserIds(start, con, appIdentifier, userIds); - } - - } - public static String getRecipeIdForUser_Transaction(Start start, Connection sqlCon, TenantIdentifier tenantIdentifier, String userId) throws SQLException, StorageQueryException { @@ -1364,7 +1307,6 @@ public static String getRecipeIdForUser_Transaction(Start start, Connection sqlC String QUERY = "SELECT recipe_id FROM " + getConfig(start).getAppIdToUserIdTable() + " WHERE app_id = ? AND user_id = ?"; - return execute(sqlCon, QUERY, pst -> { pst.setString(1, tenantIdentifier.getAppId()); pst.setString(2, userId); @@ -1396,7 +1338,50 @@ public static Map> getTenantIdsForUserIds_transaction(Start return execute(sqlCon, QUERY.toString(), pst -> { for (int i = 0; i < userIds.length; i++) { - // i+1 cause this starts with 1 and not 0 + // i+1 cause this starts with 1 and not 0, and 1 is appId + pst.setString(i + 1, userIds[i]); + } + pst.setString(userIds.length + 1, appIdentifier.getAppId()); + }, result -> { + Map> finalResult = new HashMap<>(); + for (String userId : userIds) { + finalResult.put(userId, new ArrayList<>()); + } + + while (result.next()) { + String userId = result.getString("user_id").trim(); + String tenantId = result.getString("tenant_id"); + + finalResult.get(userId).add(tenantId); + } + return finalResult; + }); + } + + return new HashMap<>(); + } + + public static Map> getTenantIdsForUserIds(Start start, + AppIdentifier appIdentifier, + String[] userIds) + throws SQLException, StorageQueryException { + if (userIds != null && userIds.length > 0) { + StringBuilder QUERY = new StringBuilder("SELECT user_id, tenant_id " + + "FROM " + getConfig(start).getUsersTable()); + QUERY.append(" WHERE user_id IN ("); + for (int i = 0; i < userIds.length; i++) { + + QUERY.append("?"); + if (i != userIds.length - 1) { + // not the last element + QUERY.append(","); + } + } + QUERY.append(") AND app_id = ?"); + + return execute(start, QUERY.toString(), pst -> { + for (int i = 0; i < userIds.length; i++) { + // i+1 cause this starts with 1 and not 0, and 1 is appId pst.setString(i + 1, userIds[i]); } pst.setString(userIds.length + 1, appIdentifier.getAppId()); @@ -1462,16 +1447,6 @@ public static String[] getAllTablesInTheDatabaseThatHasDataForAppId(Start start, return result.toArray(new String[0]); } - private static class UserInfoPaginationResultHolder { - String userId; - String recipeId; - - UserInfoPaginationResultHolder(String userId, String recipeId) { - this.userId = userId; - this.recipeId = recipeId; - } - } - private static class AllAuthRecipeUsersResultHolder { String userId; String tenantId; @@ -1506,4 +1481,4 @@ public KeyValueInfo map(ResultSet result) throws Exception { return new KeyValueInfo(result.getString("value"), result.getLong("created_at_time")); } } -} +} \ No newline at end of file diff --git a/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java index c2c2a2aa0..e95bc7eb1 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java @@ -433,7 +433,7 @@ public static AuthRecipeUserInfo createUser(Start start, TenantIdentifier tenant }); } - private static UserInfoWithTenantId[] getUserInfosWithTenant(Start start, Connection con, + private static UserInfoWithTenantId[] getUserInfosWithTenant_Transaction(Start start, Connection con, AppIdentifier appIdentifier, String userId) throws StorageQueryException, SQLException { String QUERY = "SELECT pl_users.user_id as user_id, pl_users.email as email, " @@ -463,7 +463,7 @@ private static UserInfoWithTenantId[] getUserInfosWithTenant(Start start, Connec public static void deleteUser_Transaction(Connection sqlCon, Start start, AppIdentifier appIdentifier, String userId, boolean deleteUserIdMappingToo) throws StorageQueryException, SQLException { - UserInfoWithTenantId[] userInfos = getUserInfosWithTenant(start, sqlCon, appIdentifier, userId); + UserInfoWithTenantId[] userInfos = getUserInfosWithTenant_Transaction(start, sqlCon, appIdentifier, userId); if (deleteUserIdMappingToo) { String QUERY = "DELETE FROM " + getConfig(start).getAppIdToUserIdTable() @@ -687,7 +687,7 @@ public static PasswordlessCode getCodeByLinkCodeHash(Start start, TenantIdentifi } } - public static List getUsersInfoUsingIdList(Start start, Connection con, Set ids, + public static List getUsersInfoUsingIdList(Start start, Set ids, AppIdentifier appIdentifier) throws SQLException, StorageQueryException { if (ids.size() > 0) { @@ -696,6 +696,36 @@ public static List getUsersInfoUsingIdList(Start start, Connection + "FROM " + getConfig(start).getPasswordlessUsersTable() + " WHERE user_id IN (" + Utils.generateCommaSeperatedQuestionMarks(ids.size()) + ") AND app_id = ?"; + List userInfos = execute(start, QUERY, pst -> { + int index = 1; + for (String id : ids) { + pst.setString(index, id); + index++; + } + pst.setString(index, appIdentifier.getAppId()); + }, result -> { + List finalResult = new ArrayList<>(); + while (result.next()) { + finalResult.add(UserInfoRowMapper.getInstance().mapOrThrow(result)); + } + return finalResult; + }); + fillUserInfoWithTenantIds(start, appIdentifier, userInfos); + fillUserInfoWithVerified(start, appIdentifier, userInfos); + return userInfos.stream().map(UserInfoPartial::toLoginMethod).collect(Collectors.toList()); + } + return Collections.emptyList(); + } + + public static List getUsersInfoUsingIdList_Transaction(Start start, Connection con, Set ids, + AppIdentifier appIdentifier) + throws SQLException, StorageQueryException { + if (ids.size() > 0) { + // No need to filter based on tenantId because the id list is already filtered for a tenant + String QUERY = "SELECT user_id, email, phone_number, time_joined " + + "FROM " + getConfig(start).getPasswordlessUsersTable() + " WHERE user_id IN (" + + Utils.generateCommaSeperatedQuestionMarks(ids.size()) + ") AND app_id = ?"; + List userInfos = execute(con, QUERY, pst -> { int index = 1; for (String id : ids) { @@ -717,7 +747,7 @@ public static List getUsersInfoUsingIdList(Start start, Connection return Collections.emptyList(); } - public static UserInfoPartial getUserById(Start start, Connection sqlCon, AppIdentifier appIdentifier, + public static UserInfoPartial getUserById_Transaction(Start start, Connection sqlCon, AppIdentifier appIdentifier, String userId) throws StorageQueryException, SQLException { // we don't need a LOCK here because this is already part of a transaction, and locked on app_id_to_user_id // table @@ -779,7 +809,7 @@ public static String lockPhone_Transaction(Start start, Connection con, }); } - public static String getPrimaryUserIdUsingEmail(Start start, Connection con, TenantIdentifier tenantIdentifier, + public static String getPrimaryUserIdUsingEmail(Start start, TenantIdentifier tenantIdentifier, String email) throws StorageQueryException, SQLException { String QUERY = "SELECT DISTINCT all_users.primary_or_recipe_user_id AS user_id " @@ -788,7 +818,7 @@ public static String getPrimaryUserIdUsingEmail(Start start, Connection con, Ten " ON pless.app_id = all_users.app_id AND pless.user_id = all_users.user_id" + " WHERE pless.app_id = ? AND pless.tenant_id = ? AND pless.email = ?"; - return execute(con, QUERY, pst -> { + return execute(start, QUERY, pst -> { pst.setString(1, tenantIdentifier.getAppId()); pst.setString(2, tenantIdentifier.getTenantId()); pst.setString(3, email); @@ -800,7 +830,7 @@ public static String getPrimaryUserIdUsingEmail(Start start, Connection con, Ten }); } - public static List getPrimaryUserIdsUsingEmail(Start start, Connection con, AppIdentifier appIdentifier, + public static List getPrimaryUserIdsUsingEmail_Transaction(Start start, Connection con, AppIdentifier appIdentifier, String email) throws StorageQueryException, SQLException { String QUERY = "SELECT DISTINCT all_users.primary_or_recipe_user_id AS user_id " @@ -821,7 +851,7 @@ public static List getPrimaryUserIdsUsingEmail(Start start, Connection c }); } - public static String getPrimaryUserByPhoneNumber(Start start, Connection con, TenantIdentifier tenantIdentifier, + public static String getPrimaryUserByPhoneNumber(Start start, TenantIdentifier tenantIdentifier, @Nonnull String phoneNumber) throws StorageQueryException, SQLException { String QUERY = "SELECT DISTINCT all_users.primary_or_recipe_user_id AS user_id " @@ -830,7 +860,7 @@ public static String getPrimaryUserByPhoneNumber(Start start, Connection con, Te " ON pless.app_id = all_users.app_id AND pless.user_id = all_users.user_id" + " WHERE pless.app_id = ? AND pless.tenant_id = ? AND pless.phone_number = ?"; - return execute(con, QUERY, pst -> { + return execute(start, QUERY, pst -> { pst.setString(1, tenantIdentifier.getAppId()); pst.setString(2, tenantIdentifier.getTenantId()); pst.setString(3, phoneNumber); @@ -842,8 +872,8 @@ public static String getPrimaryUserByPhoneNumber(Start start, Connection con, Te }); } - public static String getPrimaryUserByPhoneNumber(Start start, Connection con, AppIdentifier appIdentifier, - @Nonnull String phoneNumber) + public static List listUserIdsByPhoneNumber_Transaction(Start start, Connection con, AppIdentifier appIdentifier, + @Nonnull String phoneNumber) throws StorageQueryException, SQLException { String QUERY = "SELECT DISTINCT all_users.primary_or_recipe_user_id AS user_id " + "FROM " + getConfig(start).getPasswordlessUsersTable() + " AS pless" + @@ -855,17 +885,18 @@ public static String getPrimaryUserByPhoneNumber(Start start, Connection con, Ap pst.setString(1, appIdentifier.getAppId()); pst.setString(2, phoneNumber); }, result -> { - if (result.next()) { - return result.getString("user_id"); + List userIds = new ArrayList<>(); + while (result.next()) { + userIds.add(result.getString("user_id")); } - return null; + return userIds; }); } public static boolean addUserIdToTenant_Transaction(Start start, Connection sqlCon, TenantIdentifier tenantIdentifier, String userId) throws StorageQueryException, SQLException { - UserInfoPartial userInfo = PasswordlessQueries.getUserById(start, sqlCon, + UserInfoPartial userInfo = PasswordlessQueries.getUserById_Transaction(start, sqlCon, tenantIdentifier.toAppIdentifier(), userId); { // all_auth_recipe_users @@ -919,6 +950,38 @@ public static boolean removeUserIdFromTenant_Transaction(Start start, Connection // automatically deleted from passwordless_user_to_tenant because of foreign key constraint } + private static List fillUserInfoWithVerified(Start start, + AppIdentifier appIdentifier, + List userInfos) + throws SQLException, StorageQueryException { + List userIdsAndEmails = new ArrayList<>(); + for (UserInfoPartial userInfo : userInfos) { + if (userInfo.email == null) { + // phone number, so we mark it as verified + userInfo.verified = true; + } else { + userIdsAndEmails.add(new EmailVerificationQueries.UserIdAndEmail(userInfo.id, userInfo.email)); + } + } + List userIdsThatAreVerified = EmailVerificationQueries.isEmailVerified(start, + appIdentifier, + userIdsAndEmails); + Set verifiedUserIdsSet = new HashSet<>(userIdsThatAreVerified); + for (UserInfoPartial userInfo : userInfos) { + if (userInfo.verified != null) { + // this means phone number + assert (userInfo.email == null); + continue; + } + if (verifiedUserIdsSet.contains(userInfo.id)) { + userInfo.verified = true; + } else { + userInfo.verified = false; + } + } + return userInfos; + } + private static UserInfoPartial fillUserInfoWithVerified_transaction(Start start, Connection sqlCon, AppIdentifier appIdentifier, @@ -928,6 +991,25 @@ private static UserInfoPartial fillUserInfoWithVerified_transaction(Start start, return fillUserInfoWithVerified_transaction(start, sqlCon, appIdentifier, List.of(userInfo)).get(0); } + private static List fillUserInfoWithTenantIds(Start start, + AppIdentifier appIdentifier, + List userInfos) + throws SQLException, StorageQueryException { + String[] userIds = new String[userInfos.size()]; + for (int i = 0; i < userInfos.size(); i++) { + userIds[i] = userInfos.get(i).id; + } + + Map> tenantIdsForUserIds = GeneralQueries.getTenantIdsForUserIds(start, + appIdentifier, + userIds); + List result = new ArrayList<>(); + for (UserInfoPartial userInfo : userInfos) { + userInfo.tenantIds = tenantIdsForUserIds.get(userInfo.id).toArray(new String[0]); + } + return userInfos; + } + private static List fillUserInfoWithVerified_transaction(Start start, Connection sqlCon, AppIdentifier appIdentifier, diff --git a/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java index e38b6b336..60d53e294 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java @@ -16,6 +16,7 @@ package io.supertokens.inmemorydb.queries; +import io.supertokens.inmemorydb.ConnectionPool; import io.supertokens.inmemorydb.ConnectionWithLocks; import io.supertokens.inmemorydb.Start; import io.supertokens.inmemorydb.Utils; @@ -236,7 +237,39 @@ public static List lockThirdPartyInfo_Transaction(Start start, Connectio }); } - public static List getUsersInfoUsingIdList(Start start, Connection con, Set ids, + public static List getUsersInfoUsingIdList(Start start, Set ids, + AppIdentifier appIdentifier) + throws SQLException, StorageQueryException { + if (ids.size() > 0) { + String QUERY = "SELECT user_id, third_party_id, third_party_user_id, email, time_joined " + + "FROM " + getConfig(start).getThirdPartyUsersTable() + " WHERE user_id IN (" + + Utils.generateCommaSeperatedQuestionMarks(ids.size()) + ") AND app_id = ?"; + + List userInfos = execute(start, QUERY, pst -> { + int index = 1; + for (String id : ids) { + pst.setString(index, id); + index++; + } + pst.setString(index, appIdentifier.getAppId()); + }, result -> { + List finalResult = new ArrayList<>(); + while (result.next()) { + finalResult.add(UserInfoRowMapper.getInstance().mapOrThrow(result)); + } + return finalResult; + }); + + try (Connection con = ConnectionPool.getConnection(start)) { + fillUserInfoWithTenantIds_transaction(start, con, appIdentifier, userInfos); + fillUserInfoWithVerified_transaction(start, con, appIdentifier, userInfos); + } + return userInfos.stream().map(UserInfoPartial::toLoginMethod).collect(Collectors.toList()); + } + return Collections.emptyList(); + } + + public static List getUsersInfoUsingIdList_Transaction(Start start, Connection con, Set ids, AppIdentifier appIdentifier) throws SQLException, StorageQueryException { if (ids.size() > 0) { @@ -266,7 +299,7 @@ public static List getUsersInfoUsingIdList(Start start, Connection return Collections.emptyList(); } - public static List listUserIdsByThirdPartyInfo(Start start, Connection con, AppIdentifier appIdentifier, + public static List listUserIdsByThirdPartyInfo(Start start, AppIdentifier appIdentifier, String thirdPartyId, String thirdPartyUserId) throws SQLException, StorageQueryException { @@ -276,6 +309,29 @@ public static List listUserIdsByThirdPartyInfo(Start start, Connection c " ON tp.app_id = all_users.app_id AND tp.user_id = all_users.user_id" + " WHERE tp.app_id = ? AND tp.third_party_id = ? AND tp.third_party_user_id = ?"; + return execute(start, QUERY, pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setString(2, thirdPartyId); + pst.setString(3, thirdPartyUserId); + }, result -> { + List userIds = new ArrayList<>(); + while (result.next()) { + userIds.add(result.getString("user_id")); + } + return userIds; + }); + } + + public static List listUserIdsByThirdPartyInfo_Transaction(Start start, Connection con, AppIdentifier appIdentifier, + String thirdPartyId, String thirdPartyUserId) + throws SQLException, StorageQueryException { + + String QUERY = "SELECT DISTINCT all_users.primary_or_recipe_user_id AS user_id " + + "FROM " + getConfig(start).getThirdPartyUsersTable() + " AS tp" + + " JOIN " + getConfig(start).getUsersTable() + " AS all_users" + + " ON tp.app_id = all_users.app_id AND tp.user_id = all_users.user_id" + + " WHERE tp.app_id = ? AND tp.third_party_id = ? AND tp.third_party_user_id = ?"; + return execute(con, QUERY, pst -> { pst.setString(1, appIdentifier.getAppId()); pst.setString(2, thirdPartyId); @@ -289,7 +345,7 @@ public static List listUserIdsByThirdPartyInfo(Start start, Connection c }); } - public static String getUserIdByThirdPartyInfo(Start start, Connection con, TenantIdentifier tenantIdentifier, + public static String getUserIdByThirdPartyInfo(Start start, TenantIdentifier tenantIdentifier, String thirdPartyId, String thirdPartyUserId) throws SQLException, StorageQueryException { String QUERY = "SELECT DISTINCT all_users.primary_or_recipe_user_id AS user_id " @@ -298,7 +354,7 @@ public static String getUserIdByThirdPartyInfo(Start start, Connection con, Tena " ON tp.app_id = all_users.app_id AND tp.user_id = all_users.user_id" + " WHERE tp.app_id = ? AND tp.tenant_id = ? AND tp.third_party_id = ? AND tp.third_party_user_id = ?"; - return execute(con, QUERY, pst -> { + return execute(start, QUERY, pst -> { pst.setString(1, tenantIdentifier.getAppId()); pst.setString(2, tenantIdentifier.getTenantId()); pst.setString(3, thirdPartyId); @@ -311,7 +367,7 @@ public static String getUserIdByThirdPartyInfo(Start start, Connection con, Tena }); } - public static List getPrimaryUserIdUsingEmail(Start start, Connection con, + public static List getPrimaryUserIdUsingEmail_Transaction(Start start, Connection con, AppIdentifier appIdentifier, String email) throws StorageQueryException, SQLException { String QUERY = "SELECT DISTINCT all_users.primary_or_recipe_user_id AS user_id " @@ -346,7 +402,7 @@ public static void updateUserEmail_Transaction(Start start, Connection con, AppI }); } - private static UserInfoPartial getUserInfoUsingUserId(Start start, Connection con, + private static UserInfoPartial getUserInfoUsingUserId_Transaction(Start start, Connection con, AppIdentifier appIdentifier, String userId) throws SQLException, StorageQueryException { @@ -366,7 +422,7 @@ private static UserInfoPartial getUserInfoUsingUserId(Start start, Connection co }); } - public static List getPrimaryUserIdUsingEmail(Start start, Connection con, + public static List getPrimaryUserIdUsingEmail(Start start, TenantIdentifier tenantIdentifier, String email) throws StorageQueryException, SQLException { String QUERY = "SELECT DISTINCT all_users.primary_or_recipe_user_id AS user_id " @@ -377,7 +433,7 @@ public static List getPrimaryUserIdUsingEmail(Start start, Connection co " ON tp_tenants.app_id = all_users.app_id AND tp_tenants.user_id = all_users.user_id" + " WHERE tp.app_id = ? AND tp_tenants.tenant_id = ? AND tp.email = ?"; - return execute(con, QUERY, pst -> { + return execute(start, QUERY, pst -> { pst.setString(1, tenantIdentifier.getAppId()); pst.setString(2, tenantIdentifier.getTenantId()); pst.setString(3, email); @@ -393,7 +449,7 @@ public static List getPrimaryUserIdUsingEmail(Start start, Connection co public static boolean addUserIdToTenant_Transaction(Start start, Connection sqlCon, TenantIdentifier tenantIdentifier, String userId) throws SQLException, StorageQueryException { - UserInfoPartial userInfo = ThirdPartyQueries.getUserInfoUsingUserId(start, sqlCon, + UserInfoPartial userInfo = ThirdPartyQueries.getUserInfoUsingUserId_Transaction(start, sqlCon, tenantIdentifier.toAppIdentifier(), userId); { // all_auth_recipe_users From 1c9ace27ad2a84f39ad3ba113cf85b9b9d062db3 Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Thu, 14 Sep 2023 13:08:00 +0530 Subject: [PATCH 123/131] fix: index updates (#811) --- .../inmemorydb/queries/GeneralQueries.java | 45 +++++++++++++------ 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java index 1282f9ca5..67672989b 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java @@ -95,12 +95,27 @@ public static String getQueryToCreateTenantIdIndexForUsersTable(Start start) { + Config.getConfig(start).getUsersTable() + "(app_id, tenant_id);"; } - static String getQueryToCreateUserPaginationIndex(Start start) { - return "CREATE INDEX all_auth_recipe_users_pagination_index ON " + Config.getConfig(start).getUsersTable() - + "(primary_or_recipe_user_time_joined DESC, primary_or_recipe_user_id DESC, tenant_id DESC, app_id DESC);"; + static String getQueryToCreateUserPaginationIndex1(Start start) { + return "CREATE INDEX all_auth_recipe_users_pagination_index1 ON " + Config.getConfig(start).getUsersTable() + + "(app_id, tenant_id, primary_or_recipe_user_time_joined DESC, primary_or_recipe_user_id DESC);"; } - static String getQueryToCreatePrimaryUserIdIndex(Start start) { + static String getQueryToCreateUserPaginationIndex2(Start start) { + return "CREATE INDEX all_auth_recipe_users_pagination_index2 ON " + Config.getConfig(start).getUsersTable() + + "(app_id, tenant_id, primary_or_recipe_user_time_joined ASC, primary_or_recipe_user_id DESC);"; + } + + static String getQueryToCreateUserPaginationIndex3(Start start) { + return "CREATE INDEX all_auth_recipe_users_pagination_index3 ON " + Config.getConfig(start).getUsersTable() + + "(recipe_id, app_id, tenant_id, primary_or_recipe_user_time_joined DESC, primary_or_recipe_user_id DESC);"; + } + + static String getQueryToCreateUserPaginationIndex4(Start start) { + return "CREATE INDEX all_auth_recipe_users_pagination_index4 ON " + Config.getConfig(start).getUsersTable() + + "(recipe_id, app_id, tenant_id, primary_or_recipe_user_time_joined ASC, primary_or_recipe_user_id DESC);"; + } + + static String getQueryToCreatePrimaryUserId(Start start) { /* * Used in: * - does user exist @@ -110,15 +125,14 @@ static String getQueryToCreatePrimaryUserIdIndex(Start start) { + "(app_id, primary_or_recipe_user_id);"; } - static String getQueryToCreatePrimaryUserIdAndTenantIndex(Start start) { + static String getQueryToCreateRecipeIdIndex(Start start) { /* * Used in: * - user count query - * - * */ - return "CREATE INDEX all_auth_recipe_users_primary_user_id_and_tenant_id_index ON " + + * */ + return "CREATE INDEX all_auth_recipe_users_recipe_id_index ON " + Config.getConfig(start).getUsersTable() - + "(app_id, tenant_id, primary_or_recipe_user_id);"; + + "(app_id, recipe_id, tenant_id);"; } private static String getQueryToCreateAppsTable(Start start) { @@ -193,6 +207,8 @@ public static void createTablesIfNotExists(Start start, Main main) throws SQLExc if (!doesTableExists(start, Config.getConfig(start).getKeyValueTable())) { getInstance(main).addState(CREATING_NEW_TABLE, null); update(start, getQueryToCreateKeyValueTable(start), NO_OP_SETTER); + // index + update(start, getQueryToCreateTenantIdIndexForKeyValueTable(start), NO_OP_SETTER); } if (!doesTableExists(start, Config.getConfig(start).getAppIdToUserIdTable())) { @@ -205,9 +221,12 @@ public static void createTablesIfNotExists(Start start, Main main) throws SQLExc update(start, getQueryToCreateUsersTable(start), NO_OP_SETTER); // index - update(start, getQueryToCreatePrimaryUserIdIndex(start), NO_OP_SETTER); - update(start, getQueryToCreateUserPaginationIndex(start), NO_OP_SETTER); - update(start, getQueryToCreatePrimaryUserIdAndTenantIndex(start), NO_OP_SETTER); + update(start, getQueryToCreateUserPaginationIndex1(start), NO_OP_SETTER); + update(start, getQueryToCreateUserPaginationIndex2(start), NO_OP_SETTER); + update(start, getQueryToCreateUserPaginationIndex3(start), NO_OP_SETTER); + update(start, getQueryToCreateUserPaginationIndex4(start), NO_OP_SETTER); + update(start, getQueryToCreatePrimaryUserId(start), NO_OP_SETTER); + update(start, getQueryToCreateRecipeIdIndex(start), NO_OP_SETTER); } if (!doesTableExists(start, Config.getConfig(start).getUserLastActiveTable())) { @@ -237,6 +256,7 @@ public static void createTablesIfNotExists(Start start, Main main) throws SQLExc getInstance(main).addState(CREATING_NEW_TABLE, null); update(start, MultitenancyQueries.getQueryToCreateTenantThirdPartyProvidersTable(start), NO_OP_SETTER); + } if (!doesTableExists(start, Config.getConfig(start).getTenantThirdPartyProviderClientsTable())) { @@ -318,7 +338,6 @@ public static void createTablesIfNotExists(Start start, Main main) throws SQLExc // index update(start, getQueryToCreateCodeCreatedAtIndex(start), NO_OP_SETTER); update(start, getQueryToCreateCodeDeviceIdHashIndex(start), NO_OP_SETTER); - } if (!doesTableExists(start, Config.getConfig(start).getUserMetadataTable())) { From 91673ad3acd7acd9515ccc4bfb6e4cfef1fc254c Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Thu, 14 Sep 2023 13:15:13 +0530 Subject: [PATCH 124/131] fix: fkey constraint (#812) --- .../java/io/supertokens/inmemorydb/queries/GeneralQueries.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java index 67672989b..510cfb59c 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java @@ -80,6 +80,8 @@ static String getQueryToCreateUsersTable(Start start) { + "PRIMARY KEY (app_id, tenant_id, user_id)," + "FOREIGN KEY (app_id, tenant_id) REFERENCES " + Config.getConfig(start).getTenantsTable() + " (app_id, tenant_id) ON DELETE CASCADE," + + "FOREIGN KEY (app_id, primary_or_recipe_user_id) REFERENCES " + Config.getConfig(start).getAppIdToUserIdTable() + + " (app_id, user_id) ON DELETE CASCADE," + "FOREIGN KEY (app_id, user_id) REFERENCES " + Config.getConfig(start).getAppIdToUserIdTable() + " (app_id, user_id) ON DELETE CASCADE" + ");"; From da21390a90b7f0035c537c288e68e6bc30163213 Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Thu, 14 Sep 2023 13:20:48 +0530 Subject: [PATCH 125/131] fix: account linking stats (#813) --- .../java/io/supertokens/inmemorydb/Start.java | 21 ++++++++++---- .../queries/ActiveUsersQueries.java | 22 ++++++++++++++ .../inmemorydb/queries/GeneralQueries.java | 29 +++++++++++++++++++ 3 files changed, 66 insertions(+), 6 deletions(-) diff --git a/src/main/java/io/supertokens/inmemorydb/Start.java b/src/main/java/io/supertokens/inmemorydb/Start.java index 3ae732b5a..c60ac5b57 100644 --- a/src/main/java/io/supertokens/inmemorydb/Start.java +++ b/src/main/java/io/supertokens/inmemorydb/Start.java @@ -2882,19 +2882,28 @@ public void unlinkAccounts_Transaction(AppIdentifier appIdentifier, TransactionC @Override public boolean checkIfUsesAccountLinking(AppIdentifier appIdentifier) throws StorageQueryException { - // TODO - return false; + try { + return GeneralQueries.checkIfUsesAccountLinking(this, appIdentifier); + } catch (SQLException e) { + throw new StorageQueryException(e); + } } @Override public int countUsersThatHaveMoreThanOneLoginMethodAndActiveSince(AppIdentifier appIdentifier, long sinceTime) throws StorageQueryException { - // TODO - return 0; + try { + return ActiveUsersQueries.countUsersActiveSinceAndHasMoreThanOneLoginMethod(this, appIdentifier, sinceTime); + } catch (SQLException e) { + throw new StorageQueryException(e); + } } @Override public int getUsersCountWithMoreThanOneLoginMethod(AppIdentifier appIdentifier) throws StorageQueryException { - // TODO - return 0; + try { + return GeneralQueries.getUsersCountWithMoreThanOneLoginMethod(this, appIdentifier); + } catch (SQLException e) { + throw new StorageQueryException(e); + } } } diff --git a/src/main/java/io/supertokens/inmemorydb/queries/ActiveUsersQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/ActiveUsersQueries.java index af8732478..819b991eb 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/ActiveUsersQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/ActiveUsersQueries.java @@ -38,6 +38,28 @@ public static int countUsersActiveSince(Start start, AppIdentifier appIdentifier }); } + public static int countUsersActiveSinceAndHasMoreThanOneLoginMethod(Start start, AppIdentifier appIdentifier, long sinceTime) + throws SQLException, StorageQueryException { + String QUERY = "SELECT count(1) as c FROM (" + + " SELECT count(user_id) as num_login_methods, app_id, primary_or_recipe_user_id" + + " FROM " + Config.getConfig(start).getUsersTable() + + " WHERE primary_or_recipe_user_id IN (" + + " SELECT user_id FROM " + Config.getConfig(start).getUserLastActiveTable() + + " WHERE app_id = ? AND last_active_time >= ?" + + " )" + + " GROUP BY app_id, primary_or_recipe_user_id" + + ") uc WHERE num_login_methods > 1"; + return execute(start, QUERY, pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setLong(2, sinceTime); + }, result -> { + if (result.next()) { + return result.getInt("c"); + } + return 0; + }); + } + public static int countUsersEnabledTotp(Start start, AppIdentifier appIdentifier) throws SQLException, StorageQueryException { String QUERY = "SELECT COUNT(*) as total FROM " + Config.getConfig(start).getTotpUsersTable() + " WHERE app_id = ?"; diff --git a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java index 510cfb59c..9814a77be 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java @@ -1468,6 +1468,35 @@ public static String[] getAllTablesInTheDatabaseThatHasDataForAppId(Start start, return result.toArray(new String[0]); } + public static int getUsersCountWithMoreThanOneLoginMethod(Start start, AppIdentifier appIdentifier) + throws SQLException, StorageQueryException { + String QUERY = "SELECT COUNT (1) as c FROM (" + + " SELECT COUNT(user_id) as num_login_methods " + + " FROM " + getConfig(start).getUsersTable() + + " WHERE app_id = ? " + + " GROUP BY (app_id, primary_or_recipe_user_id) " + + ") as nloginmethods WHERE num_login_methods > 1"; + + return execute(start, QUERY, pst -> { + pst.setString(1, appIdentifier.getAppId()); + }, result -> { + return result.next() ? result.getInt("c") : 0; + }); + } + + public static boolean checkIfUsesAccountLinking(Start start, AppIdentifier appIdentifier) + throws SQLException, StorageQueryException { + String QUERY = "SELECT 1 FROM " + + getConfig(start).getUsersTable() + + " WHERE app_id = ? AND is_linked_or_is_a_primary_user = true LIMIT 1"; + + return execute(start, QUERY, pst -> { + pst.setString(1, appIdentifier.getAppId()); + }, result -> { + return result.next(); + }); + } + private static class AllAuthRecipeUsersResultHolder { String userId; String tenantId; From 0a2212110786e3410cf8fdd2e80fccea604d9b54 Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Thu, 14 Sep 2023 13:44:30 +0530 Subject: [PATCH 126/131] fix: allow tenant disassociation (#814) --- .../queries/EmailPasswordQueries.java | 29 +++-- .../inmemorydb/queries/GeneralQueries.java | 120 +++++++++++++++--- .../queries/PasswordlessQueries.java | 27 ++-- .../inmemorydb/queries/ThirdPartyQueries.java | 27 ++-- 4 files changed, 155 insertions(+), 48 deletions(-) diff --git a/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java index 374aba078..ceba78bf5 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java @@ -24,6 +24,7 @@ import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.authRecipe.LoginMethod; import io.supertokens.pluginInterface.emailpassword.PasswordResetTokenInfo; +import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; @@ -251,11 +252,12 @@ public static AuthRecipeUserInfo signUp(Start start, TenantIdentifier tenantIden try { { // app_id_to_user_id String QUERY = "INSERT INTO " + getConfig(start).getAppIdToUserIdTable() - + "(app_id, user_id, recipe_id)" + " VALUES(?, ?, ?)"; + + "(app_id, user_id, primary_or_recipe_user_id, recipe_id)" + " VALUES(?, ?, ?, ?)"; update(sqlCon, QUERY, pst -> { pst.setString(1, tenantIdentifier.getAppId()); pst.setString(2, userId); - pst.setString(3, EMAIL_PASSWORD.toString()); + pst.setString(3, userId); + pst.setString(4, EMAIL_PASSWORD.toString()); }); } @@ -475,7 +477,7 @@ public static List getPrimaryUserIdsUsingEmail_Transaction(Start start, throws StorageQueryException, SQLException { String QUERY = "SELECT DISTINCT all_users.primary_or_recipe_user_id AS user_id " + "FROM " + getConfig(start).getEmailPasswordUsersTable() + " AS ep" + - " JOIN " + getConfig(start).getUsersTable() + " AS all_users" + + " JOIN " + getConfig(start).getAppIdToUserIdTable() + " AS all_users" + " ON ep.app_id = all_users.app_id AND ep.user_id = all_users.user_id" + " WHERE ep.app_id = ? AND ep.email = ?"; @@ -493,22 +495,31 @@ public static List getPrimaryUserIdsUsingEmail_Transaction(Start start, public static boolean addUserIdToTenant_Transaction(Start start, Connection sqlCon, TenantIdentifier tenantIdentifier, String userId) - throws SQLException, StorageQueryException { + throws SQLException, StorageQueryException, UnknownUserIdException { UserInfoPartial userInfo = EmailPasswordQueries.getUserInfoUsingId_Transaction(start, sqlCon, tenantIdentifier.toAppIdentifier(), userId); + if (userInfo == null) { + throw new UnknownUserIdException(); + } + + GeneralQueries.AccountLinkingInfo accountLinkingInfo = GeneralQueries.getAccountLinkingInfo_Transaction(start, sqlCon, tenantIdentifier.toAppIdentifier(), userId); + { // all_auth_recipe_users String QUERY = "INSERT INTO " + getConfig(start).getUsersTable() - + "(app_id, tenant_id, user_id, primary_or_recipe_user_id, recipe_id, time_joined, primary_or_recipe_user_time_joined)" - + " VALUES(?, ?, ?, ?, ?, ?, ?)" + " ON CONFLICT DO NOTHING"; + + "(app_id, tenant_id, user_id, primary_or_recipe_user_id, is_linked_or_is_a_primary_user, recipe_id, time_joined, primary_or_recipe_user_time_joined)" + + " VALUES(?, ?, ?, ?, ?, ?, ?, ?)" + " ON CONFLICT DO NOTHING"; + GeneralQueries.AccountLinkingInfo finalAccountLinkingInfo = accountLinkingInfo; + update(sqlCon, QUERY, pst -> { pst.setString(1, tenantIdentifier.getAppId()); pst.setString(2, tenantIdentifier.getTenantId()); pst.setString(3, userId); - pst.setString(4, userId); - pst.setString(5, EMAIL_PASSWORD.toString()); - pst.setLong(6, userInfo.timeJoined); + pst.setString(4, finalAccountLinkingInfo.primaryUserId); + pst.setBoolean(5, finalAccountLinkingInfo.isLinked); + pst.setString(6, EMAIL_PASSWORD.toString()); pst.setLong(7, userInfo.timeJoined); + pst.setLong(8, userInfo.timeJoined); }); } diff --git a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java index 9814a77be..8989563bb 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java @@ -188,7 +188,11 @@ private static String getQueryToCreateAppIdToUserIdTable(Start start) { + "app_id VARCHAR(64) NOT NULL DEFAULT 'public'," + "user_id CHAR(36) NOT NULL," + "recipe_id VARCHAR(128) NOT NULL," + + "primary_or_recipe_user_id CHAR(36) NOT NULL," + + "is_linked_or_is_a_primary_user BOOLEAN NOT NULL DEFAULT FALSE," + "PRIMARY KEY (app_id, user_id), " + + "FOREIGN KEY (app_id, primary_or_recipe_user_id) REFERENCES " + Config.getConfig(start).getAppIdToUserIdTable() + + " (app_id, user_id) ON DELETE CASCADE," + "FOREIGN KEY(app_id) REFERENCES " + Config.getConfig(start).getAppsTable() + " (app_id) ON DELETE CASCADE" + ");"; @@ -882,13 +886,24 @@ public static AuthRecipeUserInfo[] getUsers(Start start, TenantIdentifier tenant public static void makePrimaryUser_Transaction(Start start, Connection sqlCon, AppIdentifier appIdentifier, String userId) throws SQLException, StorageQueryException { - String QUERY = "UPDATE " + getConfig(start).getUsersTable() + - " SET is_linked_or_is_a_primary_user = true WHERE app_id = ? AND user_id = ?"; + { + String QUERY = "UPDATE " + getConfig(start).getUsersTable() + + " SET is_linked_or_is_a_primary_user = true WHERE app_id = ? AND user_id = ?"; - update(sqlCon, QUERY, pst -> { - pst.setString(1, appIdentifier.getAppId()); - pst.setString(2, userId); - }); + update(sqlCon, QUERY, pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setString(2, userId); + }); + } + { + String QUERY = "UPDATE " + getConfig(start).getAppIdToUserIdTable() + + " SET is_linked_or_is_a_primary_user = true WHERE app_id = ? AND user_id = ?"; + + update(sqlCon, QUERY, pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setString(2, userId); + }); + } } public static void linkAccounts_Transaction(Start start, Connection sqlCon, AppIdentifier appIdentifier, @@ -917,6 +932,17 @@ public static void linkAccounts_Transaction(Start start, Connection sqlCon, AppI pst.setString(4, primaryUserId); }); } + { + String QUERY = "UPDATE " + getConfig(start).getAppIdToUserIdTable() + + " SET is_linked_or_is_a_primary_user = true, primary_or_recipe_user_id = ? WHERE app_id = ? AND " + + "user_id = ?"; + + update(sqlCon, QUERY, pst -> { + pst.setString(1, primaryUserId); + pst.setString(2, appIdentifier.getAppId()); + pst.setString(3, recipeUserId); + }); + } } public static void unlinkAccounts_Transaction(Start start, Connection sqlCon, AppIdentifier appIdentifier, @@ -946,6 +972,17 @@ public static void unlinkAccounts_Transaction(Start start, Connection sqlCon, Ap pst.setString(4, primaryUserId); }); } + { + String QUERY = "UPDATE " + getConfig(start).getAppIdToUserIdTable() + + " SET is_linked_or_is_a_primary_user = false, primary_or_recipe_user_id = ?" + + " WHERE app_id = ? AND user_id = ?"; + + update(sqlCon, QUERY, pst -> { + pst.setString(1, recipeUserId); + pst.setString(2, appIdentifier.getAppId()); + pst.setString(3, recipeUserId); + }); + } } public static AuthRecipeUserInfo[] listPrimaryUsersByPhoneNumber_Transaction(Start start, Connection sqlCon, @@ -1151,7 +1188,7 @@ private static List getPrimaryUserInfoForUserIds(Start start AppIdentifier appIdentifier, List userIds) throws StorageQueryException, SQLException { -if (userIds.size() == 0) { + if (userIds.size() == 0) { return new ArrayList<>(); } @@ -1159,13 +1196,14 @@ private static List getPrimaryUserInfoForUserIds(Start start // which is linked to a primary user ID in which case it won't be in the primary_or_recipe_user_id column, // or the input may have a primary user ID whose recipe user ID was removed, so it won't be in the user_id // column - String QUERY = "SELECT * FROM " + getConfig(start).getUsersTable() + - " WHERE primary_or_recipe_user_id IN (SELECT primary_or_recipe_user_id FROM " + - getConfig(start).getUsersTable() + " WHERE (user_id IN (" - + Utils.generateCommaSeperatedQuestionMarks(userIds.size()) + - ") OR primary_or_recipe_user_id IN (" + - Utils.generateCommaSeperatedQuestionMarks(userIds.size()) + - ")) AND app_id = ?) AND app_id = ?"; + String QUERY = "SELECT au.user_id, au.primary_or_recipe_user_id, au.is_linked_or_is_a_primary_user, au.recipe_id, aaru.tenant_id, aaru.time_joined FROM " + getConfig(start).getAppIdToUserIdTable() + " as au " + + "LEFT JOIN " + getConfig(start).getUsersTable() + " as aaru ON au.app_id = aaru.app_id AND au.user_id = aaru.user_id" + + " WHERE au.primary_or_recipe_user_id IN (SELECT primary_or_recipe_user_id FROM " + + getConfig(start).getAppIdToUserIdTable() + " WHERE (user_id IN (" + + Utils.generateCommaSeperatedQuestionMarks(userIds.size()) + + ") OR au.primary_or_recipe_user_id IN (" + + Utils.generateCommaSeperatedQuestionMarks(userIds.size()) + + ")) AND app_id = ?) AND au.app_id = ?"; List allAuthUsersResult = execute(start, QUERY, pst -> { // IN user_id @@ -1217,7 +1255,10 @@ private static List getPrimaryUserInfoForUserIds(Start start for (AllAuthRecipeUsersResultHolder authRecipeUsersResultHolder : allAuthUsersResult) { String recipeUserId = authRecipeUsersResultHolder.userId; LoginMethod loginMethod = recipeUserIdToLoginMethodMap.get(recipeUserId); - assert (loginMethod != null); + if (loginMethod == null) { + // loginMethod will be null for primaryUserId for which the user has been deleted during unlink + continue; + } String primaryUserId = authRecipeUsersResultHolder.primaryOrRecipeUserId; AuthRecipeUserInfo curr = userIdToAuthRecipeUserInfo.get(primaryUserId); if (curr == null) { @@ -1245,13 +1286,14 @@ private static List getPrimaryUserInfoForUserIds_Transaction // which is linked to a primary user ID in which case it won't be in the primary_or_recipe_user_id column, // or the input may have a primary user ID whose recipe user ID was removed, so it won't be in the user_id // column - String QUERY = "SELECT * FROM " + getConfig(start).getUsersTable() + - " WHERE primary_or_recipe_user_id IN (SELECT primary_or_recipe_user_id FROM " + - getConfig(start).getUsersTable() + " WHERE (user_id IN (" + String QUERY = "SELECT au.user_id, au.primary_or_recipe_user_id, au.is_linked_or_is_a_primary_user, au.recipe_id, aaru.tenant_id, aaru.time_joined FROM " + getConfig(start).getAppIdToUserIdTable() + " as au" + + " LEFT JOIN " + getConfig(start).getUsersTable() + " as aaru ON au.app_id = aaru.app_id AND au.user_id = aaru.user_id" + + " WHERE au.primary_or_recipe_user_id IN (SELECT primary_or_recipe_user_id FROM " + + getConfig(start).getAppIdToUserIdTable() + " WHERE (user_id IN (" + Utils.generateCommaSeperatedQuestionMarks(userIds.size()) + - ") OR primary_or_recipe_user_id IN (" + + ") OR au.primary_or_recipe_user_id IN (" + Utils.generateCommaSeperatedQuestionMarks(userIds.size()) + - ")) AND app_id = ?) AND app_id = ?"; + ")) AND app_id = ?) AND au.app_id = ?"; List allAuthUsersResult = execute(sqlCon, QUERY, pst -> { // IN user_id @@ -1303,7 +1345,10 @@ private static List getPrimaryUserInfoForUserIds_Transaction for (AllAuthRecipeUsersResultHolder authRecipeUsersResultHolder : allAuthUsersResult) { String recipeUserId = authRecipeUsersResultHolder.userId; LoginMethod loginMethod = recipeUserIdToLoginMethodMap.get(recipeUserId); - assert (loginMethod != null); + if (loginMethod == null) { + // loginMethod will be null for primaryUserId for which the user has been deleted during unlink + continue; + } String primaryUserId = authRecipeUsersResultHolder.primaryOrRecipeUserId; AuthRecipeUserInfo curr = userIdToAuthRecipeUserInfo.get(primaryUserId); if (curr == null) { @@ -1497,6 +1542,29 @@ public static boolean checkIfUsesAccountLinking(Start start, AppIdentifier appId }); } + public static AccountLinkingInfo getAccountLinkingInfo_Transaction(Start start, Connection sqlCon, AppIdentifier appIdentifier, String userId) + throws SQLException, StorageQueryException { + GeneralQueries.AccountLinkingInfo accountLinkingInfo = new GeneralQueries.AccountLinkingInfo(userId, false); + { + String QUERY = "SELECT primary_or_recipe_user_id, is_linked_or_is_a_primary_user FROM " + + Config.getConfig(start).getAppIdToUserIdTable() + " WHERE app_id = ? AND user_id = ?"; + + accountLinkingInfo = execute(sqlCon, QUERY, pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setString(2, userId); + }, result -> { + if (result.next()) { + String primaryUserId1 = result.getString("primary_or_recipe_user_id"); + boolean isLinked1 = result.getBoolean("is_linked_or_is_a_primary_user"); + return new AccountLinkingInfo(primaryUserId1, isLinked1); + + } + return null; + }); + } + return accountLinkingInfo; + } + private static class AllAuthRecipeUsersResultHolder { String userId; String tenantId; @@ -1531,4 +1599,14 @@ public KeyValueInfo map(ResultSet result) throws Exception { return new KeyValueInfo(result.getString("value"), result.getLong("created_at_time")); } } + + public static class AccountLinkingInfo { + public String primaryUserId; + public boolean isLinked; + + public AccountLinkingInfo(String primaryUserId, boolean isLinked) { + this.primaryUserId = primaryUserId; + this.isLinked = isLinked; + } + } } \ No newline at end of file diff --git a/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java index e95bc7eb1..1112b90a3 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java @@ -24,6 +24,7 @@ import io.supertokens.pluginInterface.RowMapper; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.authRecipe.LoginMethod; +import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; @@ -373,11 +374,12 @@ public static AuthRecipeUserInfo createUser(Start start, TenantIdentifier tenant try { { // app_id_to_user_id String QUERY = "INSERT INTO " + getConfig(start).getAppIdToUserIdTable() - + "(app_id, user_id, recipe_id)" + " VALUES(?, ?, ?)"; + + "(app_id, user_id, primary_or_recipe_user_id, recipe_id)" + " VALUES(?, ?, ?, ?)"; update(sqlCon, QUERY, pst -> { pst.setString(1, tenantIdentifier.getAppId()); pst.setString(2, id); - pst.setString(3, PASSWORDLESS.toString()); + pst.setString(3, id); + pst.setString(4, PASSWORDLESS.toString()); }); } @@ -835,7 +837,7 @@ public static List getPrimaryUserIdsUsingEmail_Transaction(Start start, throws StorageQueryException, SQLException { String QUERY = "SELECT DISTINCT all_users.primary_or_recipe_user_id AS user_id " + "FROM " + getConfig(start).getPasswordlessUsersTable() + " AS pless" + - " JOIN " + getConfig(start).getUsersTable() + " AS all_users" + + " JOIN " + getConfig(start).getAppIdToUserIdTable() + " AS all_users" + " ON pless.app_id = all_users.app_id AND pless.user_id = all_users.user_id" + " WHERE pless.app_id = ? AND pless.email = ?"; @@ -895,22 +897,29 @@ public static List listUserIdsByPhoneNumber_Transaction(Start start, Con public static boolean addUserIdToTenant_Transaction(Start start, Connection sqlCon, TenantIdentifier tenantIdentifier, String userId) - throws StorageQueryException, SQLException { + throws StorageQueryException, SQLException, UnknownUserIdException { UserInfoPartial userInfo = PasswordlessQueries.getUserById_Transaction(start, sqlCon, tenantIdentifier.toAppIdentifier(), userId); + if (userInfo == null) { + throw new UnknownUserIdException(); + } + + GeneralQueries.AccountLinkingInfo accountLinkingInfo = GeneralQueries.getAccountLinkingInfo_Transaction(start, sqlCon, tenantIdentifier.toAppIdentifier(), userId); + { // all_auth_recipe_users String QUERY = "INSERT INTO " + getConfig(start).getUsersTable() - + "(app_id, tenant_id, user_id, primary_or_recipe_user_id, recipe_id, time_joined, primary_or_recipe_user_time_joined)" - + " VALUES(?, ?, ?, ?, ?, ?, ?)" + " ON CONFLICT DO NOTHING"; + + "(app_id, tenant_id, user_id, primary_or_recipe_user_id, is_linked_or_is_a_primary_user, recipe_id, time_joined, primary_or_recipe_user_time_joined)" + + " VALUES(?, ?, ?, ?, ?, ?, ?, ?)" + " ON CONFLICT DO NOTHING"; update(sqlCon, QUERY, pst -> { pst.setString(1, tenantIdentifier.getAppId()); pst.setString(2, tenantIdentifier.getTenantId()); pst.setString(3, userInfo.id); - pst.setString(4, userInfo.id); - pst.setString(5, PASSWORDLESS.toString()); - pst.setLong(6, userInfo.timeJoined); + pst.setString(4, accountLinkingInfo.primaryUserId); + pst.setBoolean(5, accountLinkingInfo.isLinked); + pst.setString(6, PASSWORDLESS.toString()); pst.setLong(7, userInfo.timeJoined); + pst.setLong(8, userInfo.timeJoined); }); } diff --git a/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java index 60d53e294..89d876750 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java @@ -24,6 +24,7 @@ import io.supertokens.pluginInterface.RowMapper; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.authRecipe.LoginMethod; +import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; @@ -92,11 +93,12 @@ public static AuthRecipeUserInfo signUp(Start start, TenantIdentifier tenantIden try { { // app_id_to_user_id String QUERY = "INSERT INTO " + getConfig(start).getAppIdToUserIdTable() - + "(app_id, user_id, recipe_id)" + " VALUES(?, ?, ?)"; + + "(app_id, user_id, primary_or_recipe_user_id, recipe_id)" + " VALUES(?, ?, ?, ?)"; update(sqlCon, QUERY, pst -> { pst.setString(1, tenantIdentifier.getAppId()); pst.setString(2, id); - pst.setString(3, THIRD_PARTY.toString()); + pst.setString(3, id); + pst.setString(4, THIRD_PARTY.toString()); }); } @@ -372,7 +374,7 @@ public static List getPrimaryUserIdUsingEmail_Transaction(Start start, C throws StorageQueryException, SQLException { String QUERY = "SELECT DISTINCT all_users.primary_or_recipe_user_id AS user_id " + "FROM " + getConfig(start).getThirdPartyUsersTable() + " AS tp" + - " JOIN " + getConfig(start).getUsersTable() + " AS all_users" + + " JOIN " + getConfig(start).getAppIdToUserIdTable() + " AS all_users" + " ON tp.app_id = all_users.app_id AND tp.user_id = all_users.user_id" + " WHERE tp.app_id = ? AND tp.email = ?"; @@ -448,22 +450,29 @@ public static List getPrimaryUserIdUsingEmail(Start start, public static boolean addUserIdToTenant_Transaction(Start start, Connection sqlCon, TenantIdentifier tenantIdentifier, String userId) - throws SQLException, StorageQueryException { + throws SQLException, StorageQueryException, UnknownUserIdException { UserInfoPartial userInfo = ThirdPartyQueries.getUserInfoUsingUserId_Transaction(start, sqlCon, tenantIdentifier.toAppIdentifier(), userId); + if (userInfo == null) { + throw new UnknownUserIdException(); + } + + GeneralQueries.AccountLinkingInfo accountLinkingInfo = GeneralQueries.getAccountLinkingInfo_Transaction(start, sqlCon, tenantIdentifier.toAppIdentifier(), userId); + { // all_auth_recipe_users String QUERY = "INSERT INTO " + getConfig(start).getUsersTable() - + "(app_id, tenant_id, user_id, primary_or_recipe_user_id, recipe_id, time_joined, primary_or_recipe_user_time_joined)" - + " VALUES(?, ?, ?, ?, ?, ?, ?)" + " ON CONFLICT DO NOTHING"; + + "(app_id, tenant_id, user_id, primary_or_recipe_user_id, is_linked_or_is_a_primary_user, recipe_id, time_joined, primary_or_recipe_user_time_joined)" + + " VALUES(?, ?, ?, ?, ?, ?, ?, ?)" + " ON CONFLICT DO NOTHING"; update(sqlCon, QUERY, pst -> { pst.setString(1, tenantIdentifier.getAppId()); pst.setString(2, tenantIdentifier.getTenantId()); pst.setString(3, userInfo.id); - pst.setString(4, userInfo.id); - pst.setString(5, THIRD_PARTY.toString()); - pst.setLong(6, userInfo.timeJoined); + pst.setString(4, accountLinkingInfo.primaryUserId); + pst.setBoolean(5, accountLinkingInfo.isLinked); + pst.setString(6, THIRD_PARTY.toString()); pst.setLong(7, userInfo.timeJoined); + pst.setLong(8, userInfo.timeJoined); }); } From 35dd8802e97c3812724a7d966cc23a9ddf7ffe5f Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Thu, 14 Sep 2023 17:48:31 +0530 Subject: [PATCH 127/131] fix: tests (#815) * fix: tests * fix: tests --- .../io/supertokens/authRecipe/AuthRecipe.java | 34 ++++--- .../java/io/supertokens/inmemorydb/Start.java | 85 +++++++++++----- .../queries/ActiveUsersQueries.java | 22 +++-- .../inmemorydb/queries/DashboardQueries.java | 7 +- .../queries/EmailPasswordQueries.java | 8 +- .../queries/EmailVerificationQueries.java | 6 +- .../inmemorydb/queries/GeneralQueries.java | 30 ++++-- .../queries/MultitenancyQueries.java | 2 +- .../queries/PasswordlessQueries.java | 97 +++++++++---------- .../inmemorydb/queries/SessionQueries.java | 10 +- .../inmemorydb/queries/ThirdPartyQueries.java | 54 ++++++----- .../queries/UserIdMappingQueries.java | 54 ++++++++++- .../queries/UserMetadataQueries.java | 9 +- ...RoleQueries.java => UserRolesQueries.java} | 62 ++++++------ .../useridmapping/UserIdMapping.java | 34 ++++++- .../test/AuthRecipesParallelTest.java | 5 + .../accountlinking/CreatePrimaryUserTest.java | 5 + .../test/accountlinking/LinkAccountsTest.java | 4 + .../api/CreatePrimaryUserAPITest.java | 4 + .../api/LinkAccountsAPITest.java | 4 + .../api/TestMultitenancyAPIHelper.java | 4 +- 21 files changed, 358 insertions(+), 182 deletions(-) rename src/main/java/io/supertokens/inmemorydb/queries/{UserRoleQueries.java => UserRolesQueries.java} (88%) diff --git a/src/main/java/io/supertokens/authRecipe/AuthRecipe.java b/src/main/java/io/supertokens/authRecipe/AuthRecipe.java index 5633ebf1d..8596a28d8 100644 --- a/src/main/java/io/supertokens/authRecipe/AuthRecipe.java +++ b/src/main/java/io/supertokens/authRecipe/AuthRecipe.java @@ -73,7 +73,7 @@ public static boolean unlinkAccounts(Main main, AppIdentifierWithStorage appIden throws StorageQueryException, UnknownUserIdException, InputUserIdIsNotAPrimaryUserException { AuthRecipeSQLStorage storage = (AuthRecipeSQLStorage) appIdentifierWithStorage.getAuthRecipeStorage(); try { - return storage.startTransaction(con -> { + UnlinkResult res = storage.startTransaction(con -> { AuthRecipeUserInfo primaryUser = storage.getPrimaryUserById_Transaction(appIdentifierWithStorage, con, recipeUserId); if (primaryUser == null) { @@ -93,28 +93,23 @@ public static boolean unlinkAccounts(Main main, AppIdentifierWithStorage appIden // we are trying to unlink the user ID which is the same as the primary one. if (primaryUser.loginMethods.length == 1) { storage.unlinkAccounts_Transaction(appIdentifierWithStorage, con, primaryUser.getSupertokensUserId(), recipeUserId); - Session.revokeAllSessionsForUser(main, appIdentifierWithStorage, - mappingResult == null ? recipeUserId : mappingResult.externalUserId, - false); - return false; + return new UnlinkResult(mappingResult == null ? recipeUserId : mappingResult.externalUserId, false); } else { // Here we delete the recipe user id cause if we just unlink, then there will be two // distinct users with the same ID - which is a broken state. // The delete will also cause the automatic unlinking. // We need to make sure that it only deletes sessions for recipeUserId and not other linked // users who have their sessions for primaryUserId (that is equal to the recipeUserId) - Session.revokeAllSessionsForUser(main, appIdentifierWithStorage, - mappingResult == null ? recipeUserId : mappingResult.externalUserId, false); deleteUserHelper(con, appIdentifierWithStorage, recipeUserId, false, mappingResult); - return true; + return new UnlinkResult(mappingResult == null ? recipeUserId : mappingResult.externalUserId, true); } } else { storage.unlinkAccounts_Transaction(appIdentifierWithStorage, con, primaryUser.getSupertokensUserId(), recipeUserId); - Session.revokeAllSessionsForUser(main, appIdentifierWithStorage, - mappingResult == null ? recipeUserId : mappingResult.externalUserId, false); - return false; + return new UnlinkResult(mappingResult == null ? recipeUserId : mappingResult.externalUserId, false); } }); + Session.revokeAllSessionsForUser(main, appIdentifierWithStorage, res.userId, false); + return res.wasLinked; } catch (StorageTransactionLogicException e) { if (e.actualException instanceof UnknownUserIdException) { throw (UnknownUserIdException) e.actualException; @@ -788,8 +783,8 @@ private static void deleteUserHelper(TransactionConnection con, AppIdentifierWit // in reference to // https://docs.google.com/spreadsheets/d/17hYV32B0aDCeLnSxbZhfRN2Y9b0LC2xUF44vV88RNAA/edit?usp=sharing // we want to check which state the db is in - if (appIdentifierWithStorage.getAuthRecipeStorage() - .doesUserIdExist(appIdentifierWithStorage, userIdMapping.externalUserId)) { + if (((AuthRecipeSQLStorage) appIdentifierWithStorage.getAuthRecipeStorage()) + .doesUserIdExist_Transaction(con, appIdentifierWithStorage, userIdMapping.externalUserId)) { // db is in state A4 // delete only from auth tables userIdToDeleteForAuthRecipe = userId; @@ -823,11 +818,13 @@ private static void deleteUserHelper(TransactionConnection con, AppIdentifierWit if (primaryUserIdToDeleteNonAuthRecipe == null) { deleteAuthRecipeUser(con, appIdentifierWithStorage, userToDelete.getSupertokensUserId(), true); + return; } } else { // this is always type supertokens user ID cause it's from a user from the database. io.supertokens.pluginInterface.useridmapping.UserIdMapping mappingResult = io.supertokens.useridmapping.UserIdMapping.getUserIdMapping( + con, appIdentifierWithStorage, userToDelete.getSupertokensUserId(), UserIdType.SUPERTOKENS); if (mappingResult != null) { @@ -866,6 +863,7 @@ private static void deleteUserHelper(TransactionConnection con, AppIdentifierWit io.supertokens.pluginInterface.useridmapping.UserIdMapping mappingResult = lM.getSupertokensUserId().equals( userIdToDeleteForAuthRecipe) ? userIdMapping : io.supertokens.useridmapping.UserIdMapping.getUserIdMapping( + con, appIdentifierWithStorage, lM.getSupertokensUserId(), UserIdType.SUPERTOKENS); deleteUserHelper(con, appIdentifierWithStorage, lM.getSupertokensUserId(), false, mappingResult); @@ -963,4 +961,14 @@ public static boolean deleteNonAuthRecipeUser(TenantIdentifierWithStorage return finalDidExist; } + + private static class UnlinkResult { + public final String userId; + public final boolean wasLinked; + + public UnlinkResult(String userId, boolean wasLinked) { + this.userId = userId; + this.wasLinked = wasLinked; + } + } } diff --git a/src/main/java/io/supertokens/inmemorydb/Start.java b/src/main/java/io/supertokens/inmemorydb/Start.java index c60ac5b57..f05d07f8a 100644 --- a/src/main/java/io/supertokens/inmemorydb/Start.java +++ b/src/main/java/io/supertokens/inmemorydb/Start.java @@ -49,10 +49,7 @@ import io.supertokens.pluginInterface.jwt.JWTSigningKeyInfo; import io.supertokens.pluginInterface.jwt.exceptions.DuplicateKeyIdException; import io.supertokens.pluginInterface.jwt.sqlstorage.JWTRecipeSQLStorage; -import io.supertokens.pluginInterface.multitenancy.AppIdentifier; -import io.supertokens.pluginInterface.multitenancy.MultitenancyStorage; -import io.supertokens.pluginInterface.multitenancy.TenantConfig; -import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; +import io.supertokens.pluginInterface.multitenancy.*; import io.supertokens.pluginInterface.multitenancy.exceptions.DuplicateClientTypeException; import io.supertokens.pluginInterface.multitenancy.exceptions.DuplicateTenantException; import io.supertokens.pluginInterface.multitenancy.exceptions.DuplicateThirdPartyIdException; @@ -80,6 +77,7 @@ import io.supertokens.pluginInterface.useridmapping.UserIdMappingStorage; import io.supertokens.pluginInterface.useridmapping.exception.UnknownSuperTokensUserIdException; import io.supertokens.pluginInterface.useridmapping.exception.UserIdMappingAlreadyExistsException; +import io.supertokens.pluginInterface.useridmapping.sqlStorage.UserIdMappingSQLStorage; import io.supertokens.pluginInterface.usermetadata.UserMetadataStorage; import io.supertokens.pluginInterface.usermetadata.sqlStorage.UserMetadataSQLStorage; import io.supertokens.pluginInterface.userroles.UserRolesStorage; @@ -103,7 +101,8 @@ public class Start implements SessionSQLStorage, EmailPasswordSQLStorage, EmailVerificationSQLStorage, ThirdPartySQLStorage, JWTRecipeSQLStorage, PasswordlessSQLStorage, UserMetadataSQLStorage, UserRolesSQLStorage, UserIdMappingStorage, - MultitenancyStorage, MultitenancySQLStorage, TOTPSQLStorage, ActiveUsersStorage, DashboardSQLStorage, AuthRecipeSQLStorage { + UserIdMappingSQLStorage, MultitenancyStorage, MultitenancySQLStorage, TOTPSQLStorage, ActiveUsersStorage, + DashboardSQLStorage, AuthRecipeSQLStorage { private static final Object appenderLock = new Object(); private static final String APP_ID_KEY_NAME = "app_id"; @@ -1183,6 +1182,16 @@ public boolean doesUserIdExist(AppIdentifier appIdentifier, String userId) throw } } + @Override + public boolean doesUserIdExist_Transaction(TransactionConnection con, AppIdentifier appIdentifier, String userId) throws StorageQueryException { + try { + Connection sqlCon = (Connection) con.getConnection(); + return GeneralQueries.doesUserIdExist_Transaction(this, sqlCon, appIdentifier, userId); + } catch (SQLException e) { + throw new StorageQueryException(e); + } + } + @Override public void updateLastActive(AppIdentifier appIdentifier, String userId) throws StorageQueryException { try { @@ -1850,7 +1859,7 @@ public void addRoleToUser(TenantIdentifier tenantIdentifier, String userId, Stri throws StorageQueryException, UnknownRoleException, DuplicateUserRoleMappingException, TenantOrAppNotFoundException { try { - UserRoleQueries.addRoleToUser(this, tenantIdentifier, userId, role); + UserRolesQueries.addRoleToUser(this, tenantIdentifier, userId, role); } catch (SQLException e) { if (e instanceof SQLiteException) { SQLiteConfig config = Config.getConfig(this); @@ -1884,7 +1893,7 @@ public void addRoleToUser(TenantIdentifier tenantIdentifier, String userId, Stri public String[] getRolesForUser(TenantIdentifier tenantIdentifier, String userId) throws StorageQueryException { try { - return UserRoleQueries.getRolesForUser(this, tenantIdentifier, userId); + return UserRolesQueries.getRolesForUser(this, tenantIdentifier, userId); } catch (SQLException e) { throw new StorageQueryException(e); } @@ -1893,7 +1902,7 @@ public String[] getRolesForUser(TenantIdentifier tenantIdentifier, String userId private String[] getRolesForUser(AppIdentifier appIdentifier, String userId) throws StorageQueryException { try { - return UserRoleQueries.getRolesForUser(this, appIdentifier, userId); + return UserRolesQueries.getRolesForUser(this, appIdentifier, userId); } catch (SQLException e) { throw new StorageQueryException(e); } @@ -1903,7 +1912,7 @@ private String[] getRolesForUser(AppIdentifier appIdentifier, String userId) thr public String[] getUsersForRole(TenantIdentifier tenantIdentifier, String role) throws StorageQueryException { try { - return UserRoleQueries.getUsersForRole(this, tenantIdentifier, role); + return UserRolesQueries.getUsersForRole(this, tenantIdentifier, role); } catch (SQLException e) { throw new StorageQueryException(e); } @@ -1913,7 +1922,7 @@ public String[] getUsersForRole(TenantIdentifier tenantIdentifier, String role) public String[] getPermissionsForRole(AppIdentifier appIdentifier, String role) throws StorageQueryException { try { - return UserRoleQueries.getPermissionsForRole(this, appIdentifier, role); + return UserRolesQueries.getPermissionsForRole(this, appIdentifier, role); } catch (SQLException e) { throw new StorageQueryException(e); } @@ -1923,7 +1932,7 @@ public String[] getPermissionsForRole(AppIdentifier appIdentifier, String role) public String[] getRolesThatHavePermission(AppIdentifier appIdentifier, String permission) throws StorageQueryException { try { - return UserRoleQueries.getRolesThatHavePermission(this, appIdentifier, permission); + return UserRolesQueries.getRolesThatHavePermission(this, appIdentifier, permission); } catch (SQLException e) { throw new StorageQueryException(e); } @@ -1932,7 +1941,7 @@ public String[] getRolesThatHavePermission(AppIdentifier appIdentifier, String p @Override public boolean deleteRole(AppIdentifier appIdentifier, String role) throws StorageQueryException { try { - return UserRoleQueries.deleteRole(this, appIdentifier, role); + return UserRolesQueries.deleteRole(this, appIdentifier, role); } catch (SQLException e) { throw new StorageQueryException(e); } @@ -1941,7 +1950,7 @@ public boolean deleteRole(AppIdentifier appIdentifier, String role) throws Stora @Override public String[] getRoles(AppIdentifier appIdentifier) throws StorageQueryException { try { - return UserRoleQueries.getRoles(this, appIdentifier); + return UserRolesQueries.getRoles(this, appIdentifier); } catch (SQLException e) { throw new StorageQueryException(e); } @@ -1950,7 +1959,7 @@ public String[] getRoles(AppIdentifier appIdentifier) throws StorageQueryExcepti @Override public boolean doesRoleExist(AppIdentifier appIdentifier, String role) throws StorageQueryException { try { - return UserRoleQueries.doesRoleExist(this, appIdentifier, role); + return UserRolesQueries.doesRoleExist(this, appIdentifier, role); } catch (SQLException e) { throw new StorageQueryException(e); } @@ -1960,7 +1969,7 @@ public boolean doesRoleExist(AppIdentifier appIdentifier, String role) throws St public int deleteAllRolesForUser(TenantIdentifier tenantIdentifier, String userId) throws StorageQueryException { try { - return UserRoleQueries.deleteAllRolesForUser(this, tenantIdentifier, userId); + return UserRolesQueries.deleteAllRolesForUser(this, tenantIdentifier, userId); } catch (SQLException e) { throw new StorageQueryException(e); } @@ -1972,7 +1981,7 @@ public boolean deleteRoleForUser_Transaction(TenantIdentifier tenantIdentifier, throws StorageQueryException { Connection sqlCon = (Connection) con.getConnection(); try { - return UserRoleQueries.deleteRoleForUser_Transaction(this, sqlCon, tenantIdentifier, userId, + return UserRolesQueries.deleteRoleForUser_Transaction(this, sqlCon, tenantIdentifier, userId, role); } catch (SQLException e) { throw new StorageQueryException(e); @@ -1985,7 +1994,7 @@ public boolean createNewRoleOrDoNothingIfExists_Transaction(AppIdentifier appIde throws StorageQueryException, TenantOrAppNotFoundException { Connection sqlCon = (Connection) con.getConnection(); try { - return UserRoleQueries.createNewRoleOrDoNothingIfExists_Transaction( + return UserRolesQueries.createNewRoleOrDoNothingIfExists_Transaction( this, sqlCon, appIdentifier, role); } catch (SQLException e) { if (e instanceof SQLiteException) { @@ -2011,7 +2020,7 @@ public void addPermissionToRoleOrDoNothingIfExists_Transaction(AppIdentifier app throws StorageQueryException, UnknownRoleException { Connection sqlCon = (Connection) con.getConnection(); try { - UserRoleQueries.addPermissionToRoleOrDoNothingIfExists_Transaction(this, sqlCon, appIdentifier, + UserRolesQueries.addPermissionToRoleOrDoNothingIfExists_Transaction(this, sqlCon, appIdentifier, role, permission); } catch (SQLException e) { if (e instanceof SQLiteException) { @@ -2035,7 +2044,7 @@ public boolean deletePermissionForRole_Transaction(AppIdentifier appIdentifier, throws StorageQueryException { Connection sqlCon = (Connection) con.getConnection(); try { - return UserRoleQueries.deletePermissionForRole_Transaction(this, sqlCon, appIdentifier, role, + return UserRolesQueries.deletePermissionForRole_Transaction(this, sqlCon, appIdentifier, role, permission); } catch (SQLException e) { throw new StorageQueryException(e); @@ -2048,7 +2057,7 @@ public int deleteAllPermissionsForRole_Transaction(AppIdentifier appIdentifier, throws StorageQueryException { Connection sqlCon = (Connection) con.getConnection(); try { - return UserRoleQueries.deleteAllPermissionsForRole_Transaction(this, sqlCon, appIdentifier, + return UserRolesQueries.deleteAllPermissionsForRole_Transaction(this, sqlCon, appIdentifier, role); } catch (SQLException e) { throw new StorageQueryException(e); @@ -2060,7 +2069,7 @@ public boolean doesRoleExist_Transaction(AppIdentifier appIdentifier, Transactio throws StorageQueryException { Connection sqlCon = (Connection) con.getConnection(); try { - return UserRoleQueries.doesRoleExist_transaction(this, sqlCon, appIdentifier, role); + return UserRolesQueries.doesRoleExist_transaction(this, sqlCon, appIdentifier, role); } catch (SQLException e) { throw new StorageQueryException(e); } @@ -2071,7 +2080,7 @@ public void deleteAllRolesForUser_Transaction(TransactionConnection con, AppIden throws StorageQueryException { try { Connection sqlCon = (Connection) con.getConnection(); - UserRoleQueries.deleteAllRolesForUser_Transaction(sqlCon, this, appIdentifier, userId); + UserRolesQueries.deleteAllRolesForUser_Transaction(sqlCon, this, appIdentifier, userId); } catch (SQLException e) { throw new StorageQueryException(e); } @@ -2906,4 +2915,36 @@ public int getUsersCountWithMoreThanOneLoginMethod(AppIdentifier appIdentifier) throw new StorageQueryException(e); } } + + + @Override + public UserIdMapping getUserIdMapping_Transaction(TransactionConnection con, AppIdentifier appIdentifier, String userId, boolean isSuperTokensUserId) + throws StorageQueryException { + try { + Connection sqlCon = (Connection) con.getConnection(); + if (isSuperTokensUserId) { + return UserIdMappingQueries.getuseraIdMappingWithSuperTokensUserId_Transaction(this, sqlCon, appIdentifier, + userId); + } + + return UserIdMappingQueries.getUserIdMappingWithExternalUserId_Transaction(this, sqlCon, appIdentifier, userId); + } catch (SQLException e) { + throw new StorageQueryException(e); + } + } + + @Override + public UserIdMapping[] getUserIdMapping_Transaction(TransactionConnection con, AppIdentifier appIdentifier, String userId) + throws StorageQueryException { + try { + Connection sqlCon = (Connection) con.getConnection(); + return UserIdMappingQueries.getUserIdMappingWithEitherSuperTokensUserIdOrExternalUserId_Transaction(this, + sqlCon, + appIdentifier, + userId); + } catch (SQLException e) { + throw new StorageQueryException(e); + } + } + } diff --git a/src/main/java/io/supertokens/inmemorydb/queries/ActiveUsersQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/ActiveUsersQueries.java index 819b991eb..48aacda21 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/ActiveUsersQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/ActiveUsersQueries.java @@ -23,7 +23,8 @@ static String getQueryToCreateUserLastActiveTable(Start start) { + " );"; } - public static int countUsersActiveSince(Start start, AppIdentifier appIdentifier, long sinceTime) throws SQLException, StorageQueryException { + public static int countUsersActiveSince(Start start, AppIdentifier appIdentifier, long sinceTime) + throws SQLException, StorageQueryException { String QUERY = "SELECT COUNT(*) as total FROM " + Config.getConfig(start).getUserLastActiveTable() + " WHERE app_id = ? AND last_active_time >= ?"; @@ -60,7 +61,8 @@ public static int countUsersActiveSinceAndHasMoreThanOneLoginMethod(Start start, }); } - public static int countUsersEnabledTotp(Start start, AppIdentifier appIdentifier) throws SQLException, StorageQueryException { + public static int countUsersEnabledTotp(Start start, AppIdentifier appIdentifier) + throws SQLException, StorageQueryException { String QUERY = "SELECT COUNT(*) as total FROM " + Config.getConfig(start).getTotpUsersTable() + " WHERE app_id = ?"; @@ -74,8 +76,10 @@ public static int countUsersEnabledTotp(Start start, AppIdentifier appIdentifier }); } - public static int countUsersEnabledTotpAndActiveSince(Start start, AppIdentifier appIdentifier, long sinceTime) throws SQLException, StorageQueryException { - String QUERY = "SELECT COUNT(*) as total FROM " + Config.getConfig(start).getTotpUsersTable() + " AS totp_users " + public static int countUsersEnabledTotpAndActiveSince(Start start, AppIdentifier appIdentifier, long sinceTime) + throws SQLException, StorageQueryException { + String QUERY = + "SELECT COUNT(*) as total FROM " + Config.getConfig(start).getTotpUsersTable() + " AS totp_users " + "INNER JOIN " + Config.getConfig(start).getUserLastActiveTable() + " AS user_last_active " + "ON totp_users.user_id = user_last_active.user_id " + "WHERE user_last_active.app_id = ? AND user_last_active.last_active_time >= ?"; @@ -91,9 +95,12 @@ public static int countUsersEnabledTotpAndActiveSince(Start start, AppIdentifier }); } - public static int updateUserLastActive(Start start, AppIdentifier appIdentifier, String userId) throws SQLException, StorageQueryException { + public static int updateUserLastActive(Start start, AppIdentifier appIdentifier, String userId) + throws SQLException, StorageQueryException { String QUERY = "INSERT INTO " + Config.getConfig(start).getUserLastActiveTable() - + "(app_id, user_id, last_active_time) VALUES(?, ?, ?) ON CONFLICT(app_id, user_id) DO UPDATE SET last_active_time = ?"; + + + "(app_id, user_id, last_active_time) VALUES(?, ?, ?) ON CONFLICT(app_id, user_id) DO UPDATE SET " + + "last_active_time = ?"; long now = System.currentTimeMillis(); return update(start, QUERY, pst -> { @@ -124,7 +131,8 @@ public static Long getLastActiveByUserId(Start start, AppIdentifier appIdentifie } } - public static void deleteUserActive_Transaction(Connection con, Start start, AppIdentifier appIdentifier, String userId) + public static void deleteUserActive_Transaction(Connection con, Start start, AppIdentifier appIdentifier, + String userId) throws StorageQueryException, SQLException { String QUERY = "DELETE FROM " + Config.getConfig(start).getUserLastActiveTable() + " WHERE app_id = ? AND user_id = ?"; diff --git a/src/main/java/io/supertokens/inmemorydb/queries/DashboardQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/DashboardQueries.java index 587bfd610..425768e39 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/DashboardQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/DashboardQueries.java @@ -37,9 +37,9 @@ public class DashboardQueries { public static String getQueryToCreateDashboardUsersTable(Start start) { - String tableName = Config.getConfig(start).getDashboardUsersTable(); + String dashboardUsersTable = Config.getConfig(start).getDashboardUsersTable(); // @formatter:off - return "CREATE TABLE IF NOT EXISTS " + tableName + " (" + return "CREATE TABLE IF NOT EXISTS " + dashboardUsersTable + " (" + "app_id VARCHAR(64) DEFAULT 'public'," + "user_id CHAR(36) NOT NULL," + "email VARCHAR(256) NOT NULL," @@ -287,5 +287,4 @@ public DashboardSessionInfo[] extract(ResultSet result) throws SQLException, Sto return temp.toArray(DashboardSessionInfo[]::new); } } - -} \ No newline at end of file +} diff --git a/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java index ceba78bf5..fb27d870f 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/EmailPasswordQueries.java @@ -80,6 +80,7 @@ static String getQueryToCreatePasswordResetTokensTable(Start start) { + "app_id VARCHAR(64) DEFAULT 'public'," + "user_id CHAR(36) NOT NULL," + "token VARCHAR(128) NOT NULL UNIQUE," + + "email VARCHAR(256)," // nullable cause of backwards compatibility. + "token_expiry BIGINT UNSIGNED NOT NULL," + "PRIMARY KEY (app_id, user_id, token)," + "FOREIGN KEY (app_id, user_id) REFERENCES " + Config.getConfig(start).getAppIdToUserIdTable() @@ -181,8 +182,9 @@ public static PasswordResetTokenInfo[] getAllPasswordResetTokenInfoForUser_Trans appIdentifier.getAppId() + "~" + userId + Config.getConfig(start).getPasswordResetTokensTable()); - String QUERY = "SELECT user_id, token, token_expiry FROM " + getConfig(start).getPasswordResetTokensTable() - + " WHERE app_id = ? AND user_id = ?"; + String QUERY = + "SELECT user_id, token, token_expiry, email FROM " + getConfig(start).getPasswordResetTokensTable() + + " WHERE app_id = ? AND user_id = ?"; return execute(con, QUERY, pst -> { pst.setString(1, appIdentifier.getAppId()); @@ -526,7 +528,7 @@ public static boolean addUserIdToTenant_Transaction(Start start, Connection sqlC { // emailpassword_user_to_tenant String QUERY = "INSERT INTO " + getConfig(start).getEmailPasswordUserToTenantTable() + "(app_id, tenant_id, user_id, email)" - + " VALUES(?, ?, ?, ?) " + " ON CONFLICT DO NOTHING"; + + " VALUES(?, ?, ?, ?) " + " ON CONFLICT (app_id, tenant_id, user_id) DO NOTHING"; int numRows = update(sqlCon, QUERY, pst -> { pst.setString(1, tenantIdentifier.getAppId()); diff --git a/src/main/java/io/supertokens/inmemorydb/queries/EmailVerificationQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/EmailVerificationQueries.java index 3cfc1274e..c571427ba 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/EmailVerificationQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/EmailVerificationQueries.java @@ -239,6 +239,9 @@ public UserIdAndEmail(String userId, String email) { public static List isEmailVerified_transaction(Start start, Connection sqlCon, AppIdentifier appIdentifier, List userIdAndEmail) throws SQLException, StorageQueryException { + if (userIdAndEmail.isEmpty()) { + return new ArrayList<>(); + } List emails = new ArrayList<>(); List userIds = new ArrayList<>(); Map userIdToEmailMap = new HashMap<>(); @@ -323,7 +326,8 @@ public static List isEmailVerified(Start start, AppIdentifier appIdentif }); } - public static void deleteUserInfo_Transaction(Connection sqlCon, Start start, AppIdentifier appIdentifier, String userId) + public static void deleteUserInfo_Transaction(Connection sqlCon, Start start, AppIdentifier appIdentifier, + String userId) throws StorageQueryException, SQLException { { String QUERY = "DELETE FROM " + getConfig(start).getEmailVerificationTable() diff --git a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java index 8989563bb..96ccc6773 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/GeneralQueries.java @@ -75,6 +75,7 @@ static String getQueryToCreateUsersTable(Start start) { + "user_id CHAR(36) NOT NULL," + "primary_or_recipe_user_id CHAR(36) NOT NULL," + "is_linked_or_is_a_primary_user BOOLEAN NOT NULL DEFAULT FALSE," + + "primary_or_recipe_user_time_joined BIGINT UNSIGNED NOT NULL," + "recipe_id VARCHAR(128) NOT NULL," + "time_joined BIGINT UNSIGNED NOT NULL," + "PRIMARY KEY (app_id, tenant_id, user_id)," @@ -353,22 +354,22 @@ public static void createTablesIfNotExists(Start start, Main main) throws SQLExc if (!doesTableExists(start, Config.getConfig(start).getRolesTable())) { getInstance(main).addState(CREATING_NEW_TABLE, null); - update(start, UserRoleQueries.getQueryToCreateRolesTable(start), NO_OP_SETTER); + update(start, UserRolesQueries.getQueryToCreateRolesTable(start), NO_OP_SETTER); } if (!doesTableExists(start, Config.getConfig(start).getUserRolesPermissionsTable())) { getInstance(main).addState(CREATING_NEW_TABLE, null); - update(start, UserRoleQueries.getQueryToCreateRolePermissionsTable(start), NO_OP_SETTER); + update(start, UserRolesQueries.getQueryToCreateRolePermissionsTable(start), NO_OP_SETTER); // index - update(start, UserRoleQueries.getQueryToCreateRolePermissionsPermissionIndex(start), NO_OP_SETTER); + update(start, UserRolesQueries.getQueryToCreateRolePermissionsPermissionIndex(start), NO_OP_SETTER); } if (!doesTableExists(start, Config.getConfig(start).getUserRolesTable())) { getInstance(main).addState(CREATING_NEW_TABLE, null); - update(start, UserRoleQueries.getQueryToCreateUserRolesTable(start), NO_OP_SETTER); + update(start, UserRolesQueries.getQueryToCreateUserRolesTable(start), NO_OP_SETTER); // index - update(start, UserRoleQueries.getQueryToCreateUserRolesRoleIndex(start), NO_OP_SETTER); + update(start, UserRolesQueries.getQueryToCreateUserRolesRoleIndex(start), NO_OP_SETTER); } if (!doesTableExists(start, Config.getConfig(start).getUserIdMappingTable())) { @@ -1255,10 +1256,12 @@ private static List getPrimaryUserInfoForUserIds(Start start for (AllAuthRecipeUsersResultHolder authRecipeUsersResultHolder : allAuthUsersResult) { String recipeUserId = authRecipeUsersResultHolder.userId; LoginMethod loginMethod = recipeUserIdToLoginMethodMap.get(recipeUserId); + if (loginMethod == null) { // loginMethod will be null for primaryUserId for which the user has been deleted during unlink continue; } + String primaryUserId = authRecipeUsersResultHolder.primaryOrRecipeUserId; AuthRecipeUserInfo curr = userIdToAuthRecipeUserInfo.get(primaryUserId); if (curr == null) { @@ -1565,6 +1568,21 @@ public static AccountLinkingInfo getAccountLinkingInfo_Transaction(Start start, return accountLinkingInfo; } + public static boolean doesUserIdExist_Transaction(Start start, Connection sqlCon, AppIdentifier appIdentifier, String userId) + throws SQLException, StorageQueryException { + // We query both tables cause there is a case where a primary user ID exists, but its associated + // recipe user ID has been deleted AND there are other recipe user IDs linked to this primary user ID already. + String QUERY = "SELECT 1 FROM " + getConfig(start).getAppIdToUserIdTable() + + " WHERE app_id = ? AND user_id = ? UNION SELECT 1 FROM " + getConfig(start).getUsersTable() + + " WHERE app_id = ? AND primary_or_recipe_user_id = ?"; + return execute(sqlCon, QUERY, pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setString(2, userId); + pst.setString(3, appIdentifier.getAppId()); + pst.setString(4, userId); + }, ResultSet::next); + } + private static class AllAuthRecipeUsersResultHolder { String userId; String tenantId; @@ -1609,4 +1627,4 @@ public AccountLinkingInfo(String primaryUserId, boolean isLinked) { this.isLinked = isLinked; } } -} \ No newline at end of file +} diff --git a/src/main/java/io/supertokens/inmemorydb/queries/MultitenancyQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/MultitenancyQueries.java index da667a45b..82af7379d 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/MultitenancyQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/MultitenancyQueries.java @@ -216,7 +216,7 @@ public static void addTenantIdInTargetStorage(Start start, TenantIdentifier tena { String QUERY = "INSERT INTO " + Config.getConfig(start).getTenantsTable() - + "(app_id, tenant_id, created_at_time)" + " VALUES(?, ?, ?) ON CONFLICT DO NOTHING"; + + "(app_id, tenant_id, created_at_time)" + " VALUES(?, ?, ?)"; update(sqlCon, QUERY, pst -> { pst.setString(1, tenantIdentifier.getAppId()); diff --git a/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java index 1112b90a3..54e5a49f3 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/PasswordlessQueries.java @@ -422,7 +422,6 @@ public static AuthRecipeUserInfo createUser(Start start, TenantIdentifier tenant pst.setString(5, phoneNumber); }); } - UserInfoPartial userInfo = new UserInfoPartial(id, email, phoneNumber, timeJoined); fillUserInfoWithTenantIds_transaction(start, sqlCon, tenantIdentifier.toAppIdentifier(), userInfo); fillUserInfoWithVerified_transaction(start, sqlCon, tenantIdentifier.toAppIdentifier(), userInfo); @@ -463,7 +462,8 @@ private static UserInfoWithTenantId[] getUserInfosWithTenant_Transaction(Start s }); } - public static void deleteUser_Transaction(Connection sqlCon, Start start, AppIdentifier appIdentifier, String userId, boolean deleteUserIdMappingToo) + public static void deleteUser_Transaction(Connection sqlCon, Start start, AppIdentifier appIdentifier, + String userId, boolean deleteUserIdMappingToo) throws StorageQueryException, SQLException { UserInfoWithTenantId[] userInfos = getUserInfosWithTenant_Transaction(start, sqlCon, appIdentifier, userId); @@ -749,13 +749,13 @@ public static List getUsersInfoUsingIdList_Transaction(Start start, return Collections.emptyList(); } - public static UserInfoPartial getUserById_Transaction(Start start, Connection sqlCon, AppIdentifier appIdentifier, - String userId) throws StorageQueryException, SQLException { + private static UserInfoPartial getUserById_Transaction(Start start, Connection sqlCon, AppIdentifier appIdentifier, + String userId) + throws StorageQueryException, SQLException { // we don't need a LOCK here because this is already part of a transaction, and locked on app_id_to_user_id // table String QUERY = "SELECT user_id, email, phone_number, time_joined FROM " - + getConfig(start).getPasswordlessUsersTable() - + " WHERE app_id = ? AND user_id = ?"; + + getConfig(start).getPasswordlessUsersTable() + " WHERE app_id = ? AND user_id = ?"; return execute(sqlCon, QUERY, pst -> { pst.setString(1, appIdentifier.getAppId()); @@ -769,12 +769,11 @@ public static UserInfoPartial getUserById_Transaction(Start start, Connection sq } public static List lockEmail_Transaction(Start start, Connection con, AppIdentifier appIdentifier, - String email) throws SQLException, StorageQueryException { + String email) throws StorageQueryException, SQLException { // normally the query below will use a for update, but sqlite doesn't support it. ((ConnectionWithLocks) con).lock( appIdentifier.getAppId() + "~" + email + Config.getConfig(start).getPasswordlessUsersTable()); - String QUERY = "SELECT user_id FROM " + getConfig(start).getPasswordlessUsersTable() + " WHERE app_id = ? AND email = ?"; return execute(con, QUERY, pst -> { @@ -789,7 +788,7 @@ public static List lockEmail_Transaction(Start start, Connection con, Ap }); } - public static String lockPhone_Transaction(Start start, Connection con, + public static List lockPhone_Transaction(Start start, Connection con, AppIdentifier appIdentifier, String phoneNumber) throws SQLException, StorageQueryException { @@ -804,10 +803,11 @@ public static String lockPhone_Transaction(Start start, Connection con, pst.setString(1, appIdentifier.getAppId()); pst.setString(2, phoneNumber); }, result -> { - if (result.next()) { - return result.getString("user_id"); + List userIds = new ArrayList<>(); + while (result.next()) { + userIds.add(result.getString("user_id")); } - return null; + return userIds; }); } @@ -926,7 +926,7 @@ public static boolean addUserIdToTenant_Transaction(Start start, Connection sqlC { // passwordless_user_to_tenant String QUERY = "INSERT INTO " + getConfig(start).getPasswordlessUserToTenantTable() + "(app_id, tenant_id, user_id, email, phone_number)" - + " VALUES(?, ?, ?, ?, ?)" + " ON CONFLICT DO NOTHING"; + + " VALUES(?, ?, ?, ?, ?)" + " ON CONFLICT (app_id, tenant_id, user_id) DO NOTHING"; int numRows = update(sqlCon, QUERY, pst -> { pst.setString(1, tenantIdentifier.getAppId()); @@ -959,7 +959,17 @@ public static boolean removeUserIdFromTenant_Transaction(Start start, Connection // automatically deleted from passwordless_user_to_tenant because of foreign key constraint } - private static List fillUserInfoWithVerified(Start start, + private static UserInfoPartial fillUserInfoWithVerified_transaction(Start start, + Connection sqlCon, + AppIdentifier appIdentifier, + UserInfoPartial userInfo) + throws SQLException, StorageQueryException { + if (userInfo == null) return null; + return fillUserInfoWithVerified_transaction(start, sqlCon, appIdentifier, List.of(userInfo)).get(0); + } + + private static List fillUserInfoWithVerified_transaction(Start start, + Connection sqlCon, AppIdentifier appIdentifier, List userInfos) throws SQLException, StorageQueryException { @@ -972,7 +982,7 @@ private static List fillUserInfoWithVerified(Start start, userIdsAndEmails.add(new EmailVerificationQueries.UserIdAndEmail(userInfo.id, userInfo.email)); } } - List userIdsThatAreVerified = EmailVerificationQueries.isEmailVerified(start, + List userIdsThatAreVerified = EmailVerificationQueries.isEmailVerified_transaction(start, sqlCon, appIdentifier, userIdsAndEmails); Set verifiedUserIdsSet = new HashSet<>(userIdsThatAreVerified); @@ -991,38 +1001,9 @@ private static List fillUserInfoWithVerified(Start start, return userInfos; } - private static UserInfoPartial fillUserInfoWithVerified_transaction(Start start, - Connection sqlCon, - AppIdentifier appIdentifier, - UserInfoPartial userInfo) - throws SQLException, StorageQueryException { - if (userInfo == null) return null; - return fillUserInfoWithVerified_transaction(start, sqlCon, appIdentifier, List.of(userInfo)).get(0); - } - - private static List fillUserInfoWithTenantIds(Start start, - AppIdentifier appIdentifier, - List userInfos) - throws SQLException, StorageQueryException { - String[] userIds = new String[userInfos.size()]; - for (int i = 0; i < userInfos.size(); i++) { - userIds[i] = userInfos.get(i).id; - } - - Map> tenantIdsForUserIds = GeneralQueries.getTenantIdsForUserIds(start, - appIdentifier, - userIds); - List result = new ArrayList<>(); - for (UserInfoPartial userInfo : userInfos) { - userInfo.tenantIds = tenantIdsForUserIds.get(userInfo.id).toArray(new String[0]); - } - return userInfos; - } - - private static List fillUserInfoWithVerified_transaction(Start start, - Connection sqlCon, - AppIdentifier appIdentifier, - List userInfos) + private static List fillUserInfoWithVerified(Start start, + AppIdentifier appIdentifier, + List userInfos) throws SQLException, StorageQueryException { List userIdsAndEmails = new ArrayList<>(); for (UserInfoPartial userInfo : userInfos) { @@ -1033,7 +1014,7 @@ private static List fillUserInfoWithVerified_transaction(Start userIdsAndEmails.add(new EmailVerificationQueries.UserIdAndEmail(userInfo.id, userInfo.email)); } } - List userIdsThatAreVerified = EmailVerificationQueries.isEmailVerified_transaction(start, sqlCon, + List userIdsThatAreVerified = EmailVerificationQueries.isEmailVerified(start, appIdentifier, userIdsAndEmails); Set verifiedUserIdsSet = new HashSet<>(userIdsThatAreVerified); @@ -1079,6 +1060,25 @@ private static List fillUserInfoWithTenantIds_transaction(Start return userInfos; } + private static List fillUserInfoWithTenantIds(Start start, + AppIdentifier appIdentifier, + List userInfos) + throws SQLException, StorageQueryException { + String[] userIds = new String[userInfos.size()]; + for (int i = 0; i < userInfos.size(); i++) { + userIds[i] = userInfos.get(i).id; + } + + Map> tenantIdsForUserIds = GeneralQueries.getTenantIdsForUserIds(start, + appIdentifier, + userIds); + List result = new ArrayList<>(); + for (UserInfoPartial userInfo : userInfos) { + userInfo.tenantIds = tenantIdsForUserIds.get(userInfo.id).toArray(new String[0]); + } + return userInfos; + } + private static class PasswordlessDeviceRowMapper implements RowMapper { private static final PasswordlessDeviceRowMapper INSTANCE = new PasswordlessDeviceRowMapper(); @@ -1160,7 +1160,6 @@ public UserInfoPartial map(ResultSet result) throws Exception { } } - private static class UserInfoWithTenantId { public final String userId; public final String tenantId; diff --git a/src/main/java/io/supertokens/inmemorydb/queries/SessionQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/SessionQueries.java index dbcb6e215..65fa18c1a 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/SessionQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/SessionQueries.java @@ -203,24 +203,24 @@ public static int deleteSession(Start start, TenantIdentifier tenantIdentifier, }); } - public static void deleteSessionsOfUser_Transaction(Connection sqlCon, Start start, AppIdentifier appIdentifier, - String userId) + public static void deleteSessionsOfUser(Start start, AppIdentifier appIdentifier, String userId) throws SQLException, StorageQueryException { String QUERY = "DELETE FROM " + getConfig(start).getSessionInfoTable() + " WHERE app_id = ? AND user_id = ?"; - update(sqlCon, QUERY.toString(), pst -> { + update(start, QUERY.toString(), pst -> { pst.setString(1, appIdentifier.getAppId()); pst.setString(2, userId); }); } - public static void deleteSessionsOfUser(Start start, AppIdentifier appIdentifier, String userId) + public static void deleteSessionsOfUser_Transaction(Connection sqlCon, Start start, AppIdentifier appIdentifier, + String userId) throws SQLException, StorageQueryException { String QUERY = "DELETE FROM " + getConfig(start).getSessionInfoTable() + " WHERE app_id = ? AND user_id = ?"; - update(start, QUERY.toString(), pst -> { + update(sqlCon, QUERY.toString(), pst -> { pst.setString(1, appIdentifier.getAppId()); pst.setString(2, userId); }); diff --git a/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java index 89d876750..3e3848353 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/ThirdPartyQueries.java @@ -104,8 +104,7 @@ public static AuthRecipeUserInfo signUp(Start start, TenantIdentifier tenantIden { // all_auth_recipe_users String QUERY = "INSERT INTO " + getConfig(start).getUsersTable() - + - "(app_id, tenant_id, user_id, primary_or_recipe_user_id, recipe_id, time_joined, primary_or_recipe_user_time_joined)" + + + "(app_id, tenant_id, user_id, primary_or_recipe_user_id, recipe_id, time_joined, primary_or_recipe_user_time_joined)" + " VALUES(?, ?, ?, ?, ?, ?, ?)"; update(sqlCon, QUERY, pst -> { pst.setString(1, tenantIdentifier.getAppId()); @@ -157,7 +156,8 @@ public static AuthRecipeUserInfo signUp(Start start, TenantIdentifier tenantIden }); } - public static void deleteUser_Transaction(Connection sqlCon, Start start, AppIdentifier appIdentifier, String userId, boolean deleteUserIdMappingToo) + public static void deleteUser_Transaction(Connection sqlCon, Start start, AppIdentifier appIdentifier, + String userId, boolean deleteUserIdMappingToo) throws StorageQueryException, SQLException { if (deleteUserIdMappingToo) { String QUERY = "DELETE FROM " + getConfig(start).getAppIdToUserIdTable() @@ -224,7 +224,7 @@ public static List lockThirdPartyInfo_Transaction(Start start, Connectio // in psql / mysql dbs, this will lock the rows that are in both the tables that meet the ON criteria only. String QUERY = "SELECT user_id " + " FROM " + getConfig(start).getThirdPartyUsersTable() + - " WHERE app_id = ? AND third_party_id = ? AND third_party_user_id = ? FOR UPDATE"; + " WHERE app_id = ? AND third_party_id = ? AND third_party_user_id = ?"; return execute(con, QUERY, pst -> { pst.setString(1, appIdentifier.getAppId()); @@ -301,6 +301,7 @@ public static List getUsersInfoUsingIdList_Transaction(Start start, return Collections.emptyList(); } + public static List listUserIdsByThirdPartyInfo(Start start, AppIdentifier appIdentifier, String thirdPartyId, String thirdPartyUserId) throws SQLException, StorageQueryException { @@ -350,6 +351,7 @@ public static List listUserIdsByThirdPartyInfo_Transaction(Start start, public static String getUserIdByThirdPartyInfo(Start start, TenantIdentifier tenantIdentifier, String thirdPartyId, String thirdPartyUserId) throws SQLException, StorageQueryException { + String QUERY = "SELECT DISTINCT all_users.primary_or_recipe_user_id AS user_id " + "FROM " + getConfig(start).getThirdPartyUserToTenantTable() + " AS tp" + " JOIN " + getConfig(start).getUsersTable() + " AS all_users" + @@ -369,27 +371,6 @@ public static String getUserIdByThirdPartyInfo(Start start, TenantIdentifier ten }); } - public static List getPrimaryUserIdUsingEmail_Transaction(Start start, Connection con, - AppIdentifier appIdentifier, String email) - throws StorageQueryException, SQLException { - String QUERY = "SELECT DISTINCT all_users.primary_or_recipe_user_id AS user_id " - + "FROM " + getConfig(start).getThirdPartyUsersTable() + " AS tp" + - " JOIN " + getConfig(start).getAppIdToUserIdTable() + " AS all_users" + - " ON tp.app_id = all_users.app_id AND tp.user_id = all_users.user_id" + - " WHERE tp.app_id = ? AND tp.email = ?"; - - return execute(con, QUERY, pst -> { - pst.setString(1, appIdentifier.getAppId()); - pst.setString(2, email); - }, result -> { - List finalResult = new ArrayList<>(); - while (result.next()) { - finalResult.add(result.getString("user_id")); - } - return finalResult; - }); - } - public static void updateUserEmail_Transaction(Start start, Connection con, AppIdentifier appIdentifier, String thirdPartyId, String thirdPartyUserId, String newEmail) throws SQLException, StorageQueryException { @@ -448,6 +429,27 @@ public static List getPrimaryUserIdUsingEmail(Start start, }); } + public static List getPrimaryUserIdUsingEmail_Transaction(Start start, Connection con, + AppIdentifier appIdentifier, String email) + throws StorageQueryException, SQLException { + String QUERY = "SELECT DISTINCT all_users.primary_or_recipe_user_id AS user_id " + + "FROM " + getConfig(start).getThirdPartyUsersTable() + " AS tp" + + " JOIN " + getConfig(start).getAppIdToUserIdTable() + " AS all_users" + + " ON tp.app_id = all_users.app_id AND tp.user_id = all_users.user_id" + + " WHERE tp.app_id = ? AND tp.email = ?"; + + return execute(con, QUERY, pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setString(2, email); + }, result -> { + List finalResult = new ArrayList<>(); + while (result.next()) { + finalResult.add(result.getString("user_id")); + } + return finalResult; + }); + } + public static boolean addUserIdToTenant_Transaction(Start start, Connection sqlCon, TenantIdentifier tenantIdentifier, String userId) throws SQLException, StorageQueryException, UnknownUserIdException { @@ -479,7 +481,7 @@ public static boolean addUserIdToTenant_Transaction(Start start, Connection sqlC { // thirdparty_user_to_tenant String QUERY = "INSERT INTO " + getConfig(start).getThirdPartyUserToTenantTable() + "(app_id, tenant_id, user_id, third_party_id, third_party_user_id)" - + " VALUES(?, ?, ?, ?, ?)" + " ON CONFLICT DO NOTHING"; + + " VALUES(?, ?, ?, ?, ?)" + " ON CONFLICT (app_id, tenant_id, user_id) DO NOTHING"; int numRows = update(sqlCon, QUERY, pst -> { pst.setString(1, tenantIdentifier.getAppId()); pst.setString(2, tenantIdentifier.getTenantId()); diff --git a/src/main/java/io/supertokens/inmemorydb/queries/UserIdMappingQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/UserIdMappingQueries.java index fd7831c75..2bfad8270 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/UserIdMappingQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/UserIdMappingQueries.java @@ -24,6 +24,7 @@ import io.supertokens.pluginInterface.useridmapping.UserIdMapping; import javax.annotation.Nullable; +import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; @@ -115,6 +116,25 @@ public static UserIdMapping[] getUserIdMappingWithEitherSuperTokensUserIdOrExter } + public static UserIdMapping[] getUserIdMappingWithEitherSuperTokensUserIdOrExternalUserId_Transaction(Start start, Connection sqlCon, + AppIdentifier appIdentifier, String userId) throws SQLException, StorageQueryException { + String QUERY = "SELECT * FROM " + Config.getConfig(start).getUserIdMappingTable() + + " WHERE app_id = ? AND (supertokens_user_id = ? OR external_user_id = ?)"; + + return execute(sqlCon, QUERY, pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setString(2, userId); + pst.setString(3, userId); + }, result -> { + ArrayList userIdMappingArray = new ArrayList<>(); + while (result.next()) { + userIdMappingArray.add(UserIdMappingRowMapper.getInstance().mapOrThrow(result)); + } + return userIdMappingArray.toArray(UserIdMapping[]::new); + }); + + } + public static HashMap getUserIdMappingWithUserIds(Start start, ArrayList userIds) throws SQLException, StorageQueryException { @@ -205,6 +225,37 @@ public static boolean updateOrDeleteExternalUserIdInfoWithExternalUserId(Start s return rowUpdated > 0; } + public static UserIdMapping getuseraIdMappingWithSuperTokensUserId_Transaction(Start start, Connection sqlCon, AppIdentifier appIdentifier, String userId) + throws SQLException, StorageQueryException { + String QUERY = "SELECT * FROM " + Config.getConfig(start).getUserIdMappingTable() + + " WHERE app_id = ? AND supertokens_user_id = ?"; + return execute(sqlCon, QUERY, pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setString(2, userId); + }, result -> { + if (result.next()) { + return UserIdMappingRowMapper.getInstance().mapOrThrow(result); + } + return null; + }); + } + + public static UserIdMapping getUserIdMappingWithExternalUserId_Transaction(Start start, Connection sqlCon, AppIdentifier appIdentifier, String userId) + throws SQLException, StorageQueryException { + String QUERY = "SELECT * FROM " + Config.getConfig(start).getUserIdMappingTable() + + " WHERE app_id = ? AND external_user_id = ?"; + + return execute(sqlCon, QUERY, pst -> { + pst.setString(1, appIdentifier.getAppId()); + pst.setString(2, userId); + }, result -> { + if (result.next()) { + return UserIdMappingRowMapper.getInstance().mapOrThrow(result); + } + return null; + }); + } + private static class UserIdMappingRowMapper implements RowMapper { private static final UserIdMappingRowMapper INSTANCE = new UserIdMappingRowMapper(); @@ -221,4 +272,5 @@ public UserIdMapping map(ResultSet rs) throws Exception { rs.getString("external_user_id_info")); } } -} \ No newline at end of file + +} diff --git a/src/main/java/io/supertokens/inmemorydb/queries/UserMetadataQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/UserMetadataQueries.java index 4d673562f..bbdb4d9c3 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/UserMetadataQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/UserMetadataQueries.java @@ -48,7 +48,8 @@ public static String getQueryToCreateUserMetadataTable(Start start) { // @formatter:on } - public static int deleteUserMetadata(Start start, AppIdentifier appIdentifier, String userId) throws SQLException, StorageQueryException { + public static int deleteUserMetadata(Start start, AppIdentifier appIdentifier, String userId) + throws SQLException, StorageQueryException { String QUERY = "DELETE FROM " + getConfig(start).getUserMetadataTable() + " WHERE app_id = ? AND user_id = ?"; @@ -70,7 +71,8 @@ public static int deleteUserMetadata_Transaction(Connection sqlCon, Start start, }); } - public static int setUserMetadata_Transaction(Start start, Connection con, AppIdentifier appIdentifier, String userId, JsonObject metadata) + public static int setUserMetadata_Transaction(Start start, Connection con, AppIdentifier appIdentifier, + String userId, JsonObject metadata) throws SQLException, StorageQueryException { String QUERY = "INSERT INTO " + getConfig(start).getUserMetadataTable() @@ -103,7 +105,8 @@ public static JsonObject getUserMetadata_Transaction(Start start, Connection con }); } - public static JsonObject getUserMetadata(Start start, AppIdentifier appIdentifier, String userId) throws SQLException, StorageQueryException { + public static JsonObject getUserMetadata(Start start, AppIdentifier appIdentifier, String userId) + throws SQLException, StorageQueryException { String QUERY = "SELECT user_metadata FROM " + getConfig(start).getUserMetadataTable() + " WHERE app_id = ? AND user_id = ?"; return execute(start, QUERY, pst -> { diff --git a/src/main/java/io/supertokens/inmemorydb/queries/UserRoleQueries.java b/src/main/java/io/supertokens/inmemorydb/queries/UserRolesQueries.java similarity index 88% rename from src/main/java/io/supertokens/inmemorydb/queries/UserRoleQueries.java rename to src/main/java/io/supertokens/inmemorydb/queries/UserRolesQueries.java index 0b7bba5b2..dc7fb1710 100644 --- a/src/main/java/io/supertokens/inmemorydb/queries/UserRoleQueries.java +++ b/src/main/java/io/supertokens/inmemorydb/queries/UserRolesQueries.java @@ -33,7 +33,7 @@ import static io.supertokens.inmemorydb.QueryExecutorTemplate.update; import static io.supertokens.inmemorydb.config.Config.getConfig; -public class UserRoleQueries { +public class UserRolesQueries { public static String getQueryToCreateRolesTable(Start start) { String tableName = Config.getConfig(start).getRolesTable(); // @formatter:off @@ -101,7 +101,8 @@ public static boolean createNewRoleOrDoNothingIfExists_Transaction(Start start, public static void addPermissionToRoleOrDoNothingIfExists_Transaction(Start start, Connection con, AppIdentifier appIdentifier, String role, - String permission) throws SQLException, StorageQueryException { + String permission) + throws SQLException, StorageQueryException { String QUERY = "INSERT INTO " + getConfig(start).getUserRolesPermissionsTable() + " (app_id, role, permission) VALUES(?, ?, ?) ON CONFLICT DO NOTHING"; @@ -112,32 +113,18 @@ public static void addPermissionToRoleOrDoNothingIfExists_Transaction(Start star }); } - 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 - Connection sqlCon = (Connection) con.getConnection(); - ((ConnectionWithLocks) sqlCon).lock(appIdentifier.getAppId() + "~" + role + Config.getConfig(start).getRolesTable()); - - String QUERY = "DELETE FROM " + getConfig(start).getRolesTable() - + " WHERE app_id = ? AND role = ? ;"; - - try { - return update(sqlCon, QUERY, pst -> { - pst.setString(1, appIdentifier.getAppId()); - pst.setString(2, role); - }) == 1; - } catch (SQLException e) { - throw new StorageTransactionLogicException(e); - } - }); - } catch (StorageTransactionLogicException e) { - throw new StorageQueryException(e.actualException); - } + public static boolean deleteRole(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; } - public static boolean doesRoleExist(Start start, AppIdentifier appIdentifier, String role) throws SQLException, StorageQueryException { + public static boolean doesRoleExist(Start start, AppIdentifier appIdentifier, String role) + throws SQLException, StorageQueryException { String QUERY = "SELECT 1 FROM " + getConfig(start).getRolesTable() + " WHERE app_id = ? AND role = ?"; return execute(start, QUERY, pst -> { @@ -146,7 +133,8 @@ public static boolean doesRoleExist(Start start, AppIdentifier appIdentifier, St }, ResultSet::next); } - public static String[] getPermissionsForRole(Start start, AppIdentifier appIdentifier, String role) throws SQLException, StorageQueryException { + public static String[] getPermissionsForRole(Start start, AppIdentifier appIdentifier, String role) + throws SQLException, StorageQueryException { String QUERY = "SELECT permission FROM " + Config.getConfig(start).getUserRolesPermissionsTable() + " WHERE app_id = ? AND role = ?;"; return execute(start, QUERY, pst -> { @@ -161,7 +149,8 @@ public static String[] getPermissionsForRole(Start start, AppIdentifier appIdent }); } - public static String[] getRoles(Start start, AppIdentifier appIdentifier) throws SQLException, StorageQueryException { + public static String[] getRoles(Start start, AppIdentifier appIdentifier) + throws SQLException, StorageQueryException { String QUERY = "SELECT role FROM " + getConfig(start).getRolesTable() + " WHERE app_id = ?"; return execute(start, QUERY, pst -> pst.setString(1, appIdentifier.getAppId()), result -> { ArrayList roles = new ArrayList<>(); @@ -235,9 +224,9 @@ public static boolean deleteRoleForUser_Transaction(Start start, Connection con, return rowUpdatedCount > 0; } - public static boolean doesRoleExist_transaction(Start start, Connection con, AppIdentifier appIdentifier, String role) + public static boolean doesRoleExist_transaction(Start start, Connection con, AppIdentifier appIdentifier, + String role) throws SQLException, StorageQueryException { - ((ConnectionWithLocks) con).lock(appIdentifier.getAppId() + "~" + role + Config.getConfig(start).getRolesTable()); String QUERY = "SELECT 1 FROM " + getConfig(start).getRolesTable() @@ -248,7 +237,8 @@ public static boolean doesRoleExist_transaction(Start start, Connection con, App }, ResultSet::next); } - public static String[] getUsersForRole(Start start, TenantIdentifier tenantIdentifier, String role) throws SQLException, StorageQueryException { + public static String[] getUsersForRole(Start start, TenantIdentifier tenantIdentifier, String role) + throws SQLException, StorageQueryException { String QUERY = "SELECT user_id FROM " + getConfig(start).getUserRolesTable() + " WHERE app_id = ? AND tenant_id = ? AND role = ? "; return execute(start, QUERY, pst -> { @@ -266,7 +256,8 @@ public static String[] getUsersForRole(Start start, TenantIdentifier tenantIdent public static boolean deletePermissionForRole_Transaction(Start start, Connection con, AppIdentifier appIdentifier, String role, - String permission) throws SQLException, StorageQueryException { + String permission) + throws SQLException, StorageQueryException { String QUERY = "DELETE FROM " + getConfig(start).getUserRolesPermissionsTable() + " WHERE app_id = ? AND role = ? AND permission = ? "; @@ -314,7 +305,8 @@ public static String[] getRolesThatHavePermission(Start start, AppIdentifier app }); } - public static int deleteAllRolesForUser(Start start, TenantIdentifier tenantIdentifier, String userId) throws SQLException, StorageQueryException { + public static int deleteAllRolesForUser(Start start, TenantIdentifier tenantIdentifier, String userId) + throws SQLException, StorageQueryException { String QUERY = "DELETE FROM " + getConfig(start).getUserRolesTable() + " WHERE app_id = ? AND tenant_id = ? AND user_id = ?"; return update(start, QUERY, pst -> { @@ -324,7 +316,9 @@ public static int deleteAllRolesForUser(Start start, TenantIdentifier tenantIden }); } - public static int deleteAllRolesForUser_Transaction(Connection con, Start start, AppIdentifier appIdentifier, String userId) throws SQLException, StorageQueryException { + public static int deleteAllRolesForUser_Transaction(Connection con, Start start, + AppIdentifier appIdentifier, String userId) + throws SQLException, StorageQueryException { String QUERY = "DELETE FROM " + getConfig(start).getUserRolesTable() + " WHERE app_id = ? AND user_id = ?"; return update(con, QUERY, pst -> { diff --git a/src/main/java/io/supertokens/useridmapping/UserIdMapping.java b/src/main/java/io/supertokens/useridmapping/UserIdMapping.java index c5c5ddbf7..687e8fea2 100644 --- a/src/main/java/io/supertokens/useridmapping/UserIdMapping.java +++ b/src/main/java/io/supertokens/useridmapping/UserIdMapping.java @@ -25,15 +25,18 @@ import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; import io.supertokens.pluginInterface.emailverification.EmailVerificationStorage; import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; import io.supertokens.pluginInterface.jwt.JWTRecipeStorage; import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.session.SessionStorage; +import io.supertokens.pluginInterface.sqlStorage.TransactionConnection; import io.supertokens.pluginInterface.totp.TOTPStorage; import io.supertokens.pluginInterface.useridmapping.UserIdMappingStorage; import io.supertokens.pluginInterface.useridmapping.exception.UnknownSuperTokensUserIdException; import io.supertokens.pluginInterface.useridmapping.exception.UserIdMappingAlreadyExistsException; +import io.supertokens.pluginInterface.useridmapping.sqlStorage.UserIdMappingSQLStorage; import io.supertokens.pluginInterface.usermetadata.UserMetadataStorage; import io.supertokens.pluginInterface.userroles.UserRolesStorage; import io.supertokens.storageLayer.StorageLayer; @@ -124,17 +127,38 @@ public static io.supertokens.pluginInterface.useridmapping.UserIdMapping getUser AppIdentifierWithStorage appIdentifierWithStorage, String userId, UserIdType userIdType) throws StorageQueryException { - UserIdMappingStorage storage = appIdentifierWithStorage.getUserIdMappingStorage(); + UserIdMappingSQLStorage storage = (UserIdMappingSQLStorage) appIdentifierWithStorage.getUserIdMappingStorage(); + + try { + return storage.startTransaction(con -> { + return getUserIdMapping(con, appIdentifierWithStorage, userId, userIdType); + }); + } catch (StorageTransactionLogicException e) { + if (e.actualException instanceof StorageQueryException) { + throw (StorageQueryException) e.actualException; + } else { + throw new IllegalStateException(e.actualException); + } + } + } + + public static io.supertokens.pluginInterface.useridmapping.UserIdMapping getUserIdMapping( + TransactionConnection con, + AppIdentifierWithStorage appIdentifierWithStorage, String userId, + UserIdType userIdType) + throws StorageQueryException { + UserIdMappingSQLStorage storage = (UserIdMappingSQLStorage) appIdentifierWithStorage.getUserIdMappingStorage(); if (userIdType == UserIdType.SUPERTOKENS) { - return storage.getUserIdMapping(appIdentifierWithStorage, userId, true); + return storage.getUserIdMapping_Transaction(con, appIdentifierWithStorage, userId, true); } + if (userIdType == UserIdType.EXTERNAL) { - return storage.getUserIdMapping(appIdentifierWithStorage, userId, false); + return storage.getUserIdMapping_Transaction(con, appIdentifierWithStorage, userId, false); } - io.supertokens.pluginInterface.useridmapping.UserIdMapping[] userIdMappings = storage.getUserIdMapping( - appIdentifierWithStorage, userId); + io.supertokens.pluginInterface.useridmapping.UserIdMapping[] userIdMappings = storage.getUserIdMapping_Transaction( + con, appIdentifierWithStorage, userId); if (userIdMappings.length == 0) { return null; diff --git a/src/test/java/io/supertokens/test/AuthRecipesParallelTest.java b/src/test/java/io/supertokens/test/AuthRecipesParallelTest.java index fb6cc9af6..a302bc7a0 100644 --- a/src/test/java/io/supertokens/test/AuthRecipesParallelTest.java +++ b/src/test/java/io/supertokens/test/AuthRecipesParallelTest.java @@ -21,6 +21,7 @@ import io.supertokens.emailpassword.exceptions.EmailChangeNotAllowedException; import io.supertokens.emailpassword.exceptions.WrongCredentialsException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.TestingProcessManager; import io.supertokens.test.Utils; import io.supertokens.thirdparty.ThirdParty; @@ -103,6 +104,10 @@ public void timeTakenFor500SignInUpParallel() throws Exception { TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.isInMemDb(process.getProcess())) { + return; + } + ExecutorService ex = Executors.newFixedThreadPool(1000); int numberOfThreads = 500; diff --git a/src/test/java/io/supertokens/test/accountlinking/CreatePrimaryUserTest.java b/src/test/java/io/supertokens/test/accountlinking/CreatePrimaryUserTest.java index f46cd3d3e..73325130e 100644 --- a/src/test/java/io/supertokens/test/accountlinking/CreatePrimaryUserTest.java +++ b/src/test/java/io/supertokens/test/accountlinking/CreatePrimaryUserTest.java @@ -130,6 +130,11 @@ public void testThatCreationOfPrimaryUserRequiresAccountLinkingFeatureToBeEnable TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.isInMemDb(process.getProcess())) { + // Features are enabled by default for inmemory db + return; + } + try { AuthRecipe.createPrimaryUser(process.main, new AppIdentifierWithStorage(null, null, StorageLayer.getStorage(process.main)), ""); diff --git a/src/test/java/io/supertokens/test/accountlinking/LinkAccountsTest.java b/src/test/java/io/supertokens/test/accountlinking/LinkAccountsTest.java index 5298adf7f..aab79a23a 100644 --- a/src/test/java/io/supertokens/test/accountlinking/LinkAccountsTest.java +++ b/src/test/java/io/supertokens/test/accountlinking/LinkAccountsTest.java @@ -167,6 +167,10 @@ public void testThatLinkingAccountsRequiresAccountLinkingFeatureToBeEnabled() th TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.isInMemDb(process.getProcess())) { + return; + } + try { AuthRecipe.linkAccounts(process.main, "", ""); assert (false); diff --git a/src/test/java/io/supertokens/test/accountlinking/api/CreatePrimaryUserAPITest.java b/src/test/java/io/supertokens/test/accountlinking/api/CreatePrimaryUserAPITest.java index eada034c6..1775db634 100644 --- a/src/test/java/io/supertokens/test/accountlinking/api/CreatePrimaryUserAPITest.java +++ b/src/test/java/io/supertokens/test/accountlinking/api/CreatePrimaryUserAPITest.java @@ -512,6 +512,10 @@ public void createReturnsFailsWithoutFeatureEnabled() throws Exception { return; } + if (StorageLayer.isInMemDb(process.getProcess())) { + return; + } + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "abcd1234"); JsonObject userObj; diff --git a/src/test/java/io/supertokens/test/accountlinking/api/LinkAccountsAPITest.java b/src/test/java/io/supertokens/test/accountlinking/api/LinkAccountsAPITest.java index 39a252ed7..5fd6a1943 100644 --- a/src/test/java/io/supertokens/test/accountlinking/api/LinkAccountsAPITest.java +++ b/src/test/java/io/supertokens/test/accountlinking/api/LinkAccountsAPITest.java @@ -489,6 +489,10 @@ public void linkReturnsFailsWithoutFeatureEnabled() throws Exception { return; } + if (StorageLayer.isInMemDb(process.getProcess())) { + return; + } + AuthRecipeUserInfo user = EmailPassword.signUp(process.getProcess(), "test@example.com", "abcd1234"); AuthRecipeUserInfo user2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "abcd1234"); diff --git a/src/test/java/io/supertokens/test/multitenant/api/TestMultitenancyAPIHelper.java b/src/test/java/io/supertokens/test/multitenant/api/TestMultitenancyAPIHelper.java index fd249ac7a..261a0eb27 100644 --- a/src/test/java/io/supertokens/test/multitenant/api/TestMultitenancyAPIHelper.java +++ b/src/test/java/io/supertokens/test/multitenant/api/TestMultitenancyAPIHelper.java @@ -203,7 +203,7 @@ public static JsonObject getTenant(TenantIdentifier tenantIdentifier, Main main) public static JsonObject associateUserToTenant(TenantIdentifier tenantIdentifier, String userId, Main main) throws HttpResponseException, IOException { JsonObject requestBody = new JsonObject(); - requestBody.addProperty("userId", userId); + requestBody.addProperty("recipeUserId", userId); JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(main, "", HttpRequestForTesting.getMultitenantUrl(tenantIdentifier, "/recipe/multitenancy/tenant/user"), @@ -216,7 +216,7 @@ public static JsonObject associateUserToTenant(TenantIdentifier tenantIdentifier public static JsonObject disassociateUserFromTenant(TenantIdentifier tenantIdentifier, String userId, Main main) throws HttpResponseException, IOException { JsonObject requestBody = new JsonObject(); - requestBody.addProperty("userId", userId); + requestBody.addProperty("recipeUserId", userId); JsonObject response = HttpRequestForTesting.sendJsonPOSTRequest(main, "", HttpRequestForTesting.getMultitenantUrl(tenantIdentifier, "/recipe/multitenancy/tenant/user/remove"), From e993e14a5ad12a5ca2560c96c821d8dd6ab62411 Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Tue, 19 Sep 2023 12:46:10 +0530 Subject: [PATCH 128/131] fix: license stats fix (#818) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: add exp and iat to JWT payloads without scientific notation (#765) * adding dev-v6.0.9 tag to this commit to ensure building * fix: fix handling of b64 and b64url encoded access tokens (#767) * adding dev-v6.0.10 tag to this commit to ensure building * Update release.md * Update release.md * fix: ee featureflag cron job (#778) * fix: ee featureflag cron job * fix: test * fix: tests * fix: tests * adding dev-v6.0.11 tag to this commit to ensure building * fix: test (#779) * adding dev-v6.0.11 tag to this commit to ensure building * fix: test (#780) * fix: test * fix: test * adding dev-v6.0.11 tag to this commit to ensure building * fix: test (#781) * adding dev-v6.0.11 tag to this commit to ensure building * Update README.md (#783) Corrected all the grammatical errors in the README file. * fix: session concurrency issue (#785) * adding dev-v6.0.12 tag to this commit to ensure building * fix: fixing ee folder issue when empty database at startup (#786) * fix: fixing ee folder issue when empty database at startup * fix: changelog * adding dev-v6.0.12 tag to this commit to ensure building * fix: test (#787) * fix: fixing ee folder issue when empty database at startup * fix: changelog * fix: test * adding dev-v6.0.12 tag to this commit to ensure building * bug fixes * adding dev-v6.0.12 tag to this commit to ensure building * Update README.md * Update README.md * fix: stats fix (#816) * fix: stats fix * fix: pr comments * fix: disable for in mem * fix: pr comments * adding dev-v6.0.13 tag to this commit to ensure building --------- Co-authored-by: Mihály Lengyel Co-authored-by: rishabhpoddar Co-authored-by: Abhisar Yadav <112550486+abhisar-yadav@users.noreply.github.com> --- CHANGELOG.md | 4 + README.md | 9 +- build.gradle | 2 +- .../java/io/supertokens/ee/EEFeatureFlag.java | 51 +++++-- .../ee/test/TestMultitenancyStats.java | 129 ++++++++++++++++++ .../java/io/supertokens/ee/test/Utils.java | 2 + jar/{core-6.0.12.jar => core-6.0.13.jar} | Bin 718455 -> 658729 bytes .../test/session/AccessTokenTest.java | 1 + 8 files changed, 187 insertions(+), 11 deletions(-) create mode 100644 ee/src/test/java/io/supertokens/ee/test/TestMultitenancyStats.java rename jar/{core-6.0.12.jar => core-6.0.13.jar} (55%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06af34a47..9c0aa2aa3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -97,6 +97,10 @@ ALTER TABLE emailpassword_pswd_reset_tokens ADD CONSTRAINT emailpassword_pswd_re ALTER TABLE emailpassword_pswd_reset_tokens ADD COLUMN email VARCHAR(256); ``` +## [6.0.13] - 2023-09-15 + +- Fixes paid stats reporting for multitenancy + ## [6.0.12] - 2023-09-04 - Fixes randomly occurring `serialization error for concurrent update` in `verifySession` API diff --git a/README.md b/README.md index bf258ea18..45d543f54 100644 --- a/README.md +++ b/README.md @@ -203,8 +203,13 @@ Mihály Lengyel
Nicholas Dudfield

Qdea
-
-Lukas Knuth
+
Lukas Knuth
+
+Melvyn Hills
+ +
Matt Murray
+Melvyn Hills
+ diff --git a/build.gradle b/build.gradle index 9d329dff7..5ae32374e 100644 --- a/build.gradle +++ b/build.gradle @@ -19,7 +19,7 @@ compileTestJava { options.encoding = "UTF-8" } // } //} -version = "6.0.12" +version = "6.0.13" repositories { diff --git a/ee/src/main/java/io/supertokens/ee/EEFeatureFlag.java b/ee/src/main/java/io/supertokens/ee/EEFeatureFlag.java index 40eb83bb7..b68622959 100644 --- a/ee/src/main/java/io/supertokens/ee/EEFeatureFlag.java +++ b/ee/src/main/java/io/supertokens/ee/EEFeatureFlag.java @@ -29,6 +29,7 @@ import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.TenantConfig; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.pluginInterface.multitenancy.ThirdPartyConfig; import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.pluginInterface.session.sqlStorage.SessionSQLStorage; @@ -58,6 +59,8 @@ public class EEFeatureFlag implements io.supertokens.featureflag.EEFeatureFlagIn public static final String FEATURE_FLAG_KEY_IN_DB = "FEATURE_FLAG"; public static final String LICENSE_KEY_IN_DB = "LICENSE_KEY"; + private static List licenseCheckRequests = new ArrayList<>(); + private static final String[] ENTERPRISE_THIRD_PARTY_IDS = new String[] { "google-workspaces", "okta", @@ -150,6 +153,12 @@ public void syncFeatureFlagWithLicenseKey() licenseKey = this.getLicenseKeyFromDb(); this.isLicenseKeyPresent = true; } catch (NoLicenseKeyFoundException ex) { + try { + licenseKey = this.getRootLicenseKeyFromDb(); + verifyLicenseKey(licenseKey); // also sends paid user stats for the app + } catch (NoLicenseKeyFoundException | InvalidLicenseKeyException ex2) { + // follow through below + } this.isLicenseKeyPresent = false; this.setEnabledEEFeaturesInDb(new EE_FEATURES[]{}); return; @@ -440,6 +449,9 @@ private EE_FEATURES[] doServerCall(String licenseKey) json.addProperty("licenseKey", licenseKey); json.addProperty("superTokensVersion", Version.getVersion(main).getCoreVersion()); json.add("paidFeatureUsageStats", this.getPaidFeatureStats()); + if (Main.isTesting) { + licenseCheckRequests.add(json); + } ProcessState.getInstance(main).addState(ProcessState.PROCESS_STATE.LICENSE_KEY_CHECK_NETWORK_CALL, null); JsonObject licenseCheckResponse = HttpRequest.sendJsonPOSTRequest(this.main, REQUEST_ID, "https://api.supertokens.io/0/st/license/check", @@ -522,17 +534,40 @@ private void removeLicenseKeyFromDb() throws StorageQueryException, TenantOrAppN new KeyValueInfo(LICENSE_KEY_IN_DB_NOT_PRESENT_VALUE)); } - @Override - public String getLicenseKeyFromDb() - throws NoLicenseKeyFoundException, StorageQueryException, TenantOrAppNotFoundException { - Logging.debug(main, appIdentifier.getAsPublicTenantIdentifier(), "Attempting to fetch license key from db"); - KeyValueInfo info = StorageLayer.getStorage(this.appIdentifier.getAsPublicTenantIdentifier(), main) - .getKeyValue(this.appIdentifier.getAsPublicTenantIdentifier(), LICENSE_KEY_IN_DB); + private String getLicenseKeyInDb(TenantIdentifier tenantIdentifier) + throws TenantOrAppNotFoundException, StorageQueryException, NoLicenseKeyFoundException { + Logging.debug(main, tenantIdentifier, "Attempting to fetch license key from db"); + KeyValueInfo info = StorageLayer.getStorage(tenantIdentifier, main) + .getKeyValue(tenantIdentifier, LICENSE_KEY_IN_DB); if (info == null || info.value.equals(LICENSE_KEY_IN_DB_NOT_PRESENT_VALUE)) { - Logging.debug(main, appIdentifier.getAsPublicTenantIdentifier(), "No license key found in db"); + Logging.debug(main, tenantIdentifier, "No license key found in db"); throw new NoLicenseKeyFoundException(); } - Logging.debug(main, appIdentifier.getAsPublicTenantIdentifier(), "Fetched license key from db: " + info.value); + Logging.debug(main, tenantIdentifier, "Fetched license key from db: " + info.value); return info.value; } + + @Override + public String getLicenseKeyFromDb() + throws NoLicenseKeyFoundException, StorageQueryException, TenantOrAppNotFoundException { + return getLicenseKeyInDb(appIdentifier.getAsPublicTenantIdentifier()); + } + + private String getRootLicenseKeyFromDb() + throws TenantOrAppNotFoundException, StorageQueryException, NoLicenseKeyFoundException { + return getLicenseKeyInDb(TenantIdentifier.BASE_TENANT); + } + + @TestOnly + public static List getLicenseCheckRequests() { + assert (Main.isTesting); + return licenseCheckRequests; + } + + @TestOnly + public static void resetLisenseCheckRequests() { + licenseCheckRequests = new ArrayList<>(); + } + + } \ No newline at end of file diff --git a/ee/src/test/java/io/supertokens/ee/test/TestMultitenancyStats.java b/ee/src/test/java/io/supertokens/ee/test/TestMultitenancyStats.java new file mode 100644 index 000000000..d9a658e91 --- /dev/null +++ b/ee/src/test/java/io/supertokens/ee/test/TestMultitenancyStats.java @@ -0,0 +1,129 @@ +package io.supertokens.ee.test; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import io.supertokens.ProcessState; +import io.supertokens.cronjobs.CronTaskTest; +import io.supertokens.ee.EEFeatureFlag; +import io.supertokens.ee.cronjobs.EELicenseCheck; +import io.supertokens.ee.test.httpRequest.HttpRequestForTesting; +import io.supertokens.featureflag.FeatureFlag; +import io.supertokens.multitenancy.Multitenancy; +import io.supertokens.pluginInterface.multitenancy.*; +import io.supertokens.storageLayer.StorageLayer; +import io.supertokens.webserver.WebserverAPI; +import org.junit.*; +import org.junit.rules.TestRule; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class TestMultitenancyStats { + @Rule + public TestRule watchman = Utils.getOnFailure(); + + @AfterClass + public static void afterTesting() { + Utils.afterTesting(); + } + + @Before + public void beforeEach() { + Utils.reset(); + FeatureFlag.clearURLClassLoader(); + } + + private final String OPAQUE_KEY_WITH_MULTITENANCY_FEATURE = "ijaleljUd2kU9XXWLiqFYv5br8nutTxbyBqWypQdv2N-" + + "BocoNriPrnYQd0NXPm8rVkeEocN9ayq0B7c3Pv-BTBIhAZSclXMlgyfXtlwAOJk=9BfESEleW6LyTov47dXu"; + + @Test + public void testPaidStatsIsSentForAllAppsInMultitenancy() throws Exception { + String[] args = {"../../"}; + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + CronTaskTest.getInstance(process.main).setIntervalInSeconds(EELicenseCheck.RESOURCE_KEY, 1); + Assert.assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.isInMemDb(process.main)) { + // cause we keep all features enabled in memdb anyway + return; + } + + { + // Add the license + JsonObject requestBody = new JsonObject(); + + requestBody.addProperty("licenseKey", OPAQUE_KEY_WITH_MULTITENANCY_FEATURE); + + HttpRequestForTesting.sendJsonPUTRequest(process.getProcess(), "", + "http://localhost:3567/ee/license", + requestBody, 10000, 10000, null, WebserverAPI.getLatestCDIVersion().get(), ""); + } + + { + // Create tenants and apps + JsonObject config = new JsonObject(); + StorageLayer.getStorage(new TenantIdentifier(null, null, null), process.getProcess()) + .modifyConfigToAddANewUserPoolForTesting(config, 1); + + Multitenancy.addNewOrUpdateAppOrTenant(process.getProcess(), new TenantConfig( + new TenantIdentifier("127.0.0.1", null, null), + new EmailPasswordConfig(true), + new ThirdPartyConfig(true, null), + new PasswordlessConfig(true), + config + ), false); + + Multitenancy.addNewOrUpdateAppOrTenant(process.getProcess(), new TenantConfig( + new TenantIdentifier("127.0.0.1", "a1", null), + new EmailPasswordConfig(true), + new ThirdPartyConfig(true, null), + new PasswordlessConfig(true), + config + ), false); + + Multitenancy.addNewOrUpdateAppOrTenant(process.getProcess(), new TenantConfig( + new TenantIdentifier("127.0.0.1", "a1", "t1"), + new EmailPasswordConfig(true), + new ThirdPartyConfig(true, null), + new PasswordlessConfig(true), + config + ), false); + } + + Thread.sleep(2000); // Let all the cron tasks run + + List requests = EEFeatureFlag.getLicenseCheckRequests(); + Set tenantIdentifiers = new HashSet<>(); + + for (JsonObject request : requests) { + if (request.has("paidFeatureUsageStats")) { + JsonObject paidStats = request.getAsJsonObject("paidFeatureUsageStats"); + if (paidStats.has("multi_tenancy")) { + JsonObject mtStats = paidStats.getAsJsonObject("multi_tenancy"); + String cud = mtStats.get("connectionUriDomain").getAsString(); + String appId = mtStats.get("appId").getAsString(); + + JsonArray tenants = mtStats.get("tenants").getAsJsonArray(); + for (JsonElement tenantElem : tenants) { + JsonObject tenant = tenantElem.getAsJsonObject(); + String tenantId = tenant.get("tenantId").getAsString(); + + tenantIdentifiers.add(new TenantIdentifier(cud, appId, tenantId)); + } + } + } + } + + Assert.assertEquals(tenantIdentifiers.size(), 4); + Assert.assertTrue(tenantIdentifiers.contains(new TenantIdentifier(null, null, null))); + Assert.assertTrue(tenantIdentifiers.contains(new TenantIdentifier("127.0.0.1", null, null))); + Assert.assertTrue(tenantIdentifiers.contains(new TenantIdentifier("127.0.0.1", "a1", null))); + Assert.assertTrue(tenantIdentifiers.contains(new TenantIdentifier("127.0.0.1", "a1", "t1"))); + + process.kill(); + Assert.assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } +} diff --git a/ee/src/test/java/io/supertokens/ee/test/Utils.java b/ee/src/test/java/io/supertokens/ee/test/Utils.java index ae4223ec5..2235c3349 100644 --- a/ee/src/test/java/io/supertokens/ee/test/Utils.java +++ b/ee/src/test/java/io/supertokens/ee/test/Utils.java @@ -1,6 +1,7 @@ package io.supertokens.ee.test; import io.supertokens.Main; +import io.supertokens.ee.EEFeatureFlag; import io.supertokens.pluginInterface.PluginInterfaceTesting; import io.supertokens.storageLayer.StorageLayer; import org.apache.tomcat.util.http.fileupload.FileUtils; @@ -51,6 +52,7 @@ public static void reset() { Main.isTesting = true; PluginInterfaceTesting.isTesting = true; Main.makeConsolePrintSilent = true; + EEFeatureFlag.resetLisenseCheckRequests(); String installDir = "../../"; try { diff --git a/jar/core-6.0.12.jar b/jar/core-6.0.13.jar similarity index 55% rename from jar/core-6.0.12.jar rename to jar/core-6.0.13.jar index 6e249ea878cf4aa80db56f257ee3a200dea808a6..c25370fb2137bc47e156b52f1bfed940e373f1e4 100644 GIT binary patch delta 241888 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≻4>{?%}PJWxM-Wy>+3V3o|+BNr$|AztIc5`-^N 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ℜ-@AHmWeWidG5&Sy#@RV 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 delta 300094 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�Rz z;S}Sd%lxVBBD=%@SY_6$Y0RYo{}?M_UvO6JCU zmf=#DUx&G9LM4BSdKXjl$5cHJAG$97rNs`$EasRWxiJ%=?IlOLzT9H>l0ZA^*8~b+ zlaH7s#53^J+DAOLPQ`gbu_j}({*=>v&s5AkH8~a9NCvzNo5y#WzcDqyvE=w`ps&ij z7(ps=m`}W03o+3f^w|7PUtaat#c*>erwwtB_A4MMk0rkvYp~nLG}5T|k1npg_WrsV zGIeG_V514-M`&ai5&TLoKSOP688kiAH28Mdf zVq^{je)gdnf<{@EYbkP_W2*fdUOi(}DqXC(IAw-ba=oW%?4P6h)IU31@U(Ha5`#H5 z#tiF+c63ftJ55c~OwJ|2IU<#l`T4N(-)e0Y)^P#m6dFR3Kl}YB zC6Ah!=VnMW!NlvkKh8J^(=n&Zf}-(d$2UlLKZQraOE-)i_4pm58TXd9BK#}f+_J;T zuvrAd{AwkBz$A7QCiNTpY_rE#VSyd_bEI1Ri_9+lyE46J;IbQYx=RvwA5J6ggA58csi34W6o$5+*nDILE(ax2^e4f@r6z2y(rTxI!I7 zLMEfaOp>pp%ud=03VOY!vP{cTU^O;&<7k14l5^KzrOL;Put)S!){L@b%T`NcGhRDP4es zFVBX^_o6w;*yxBI8reV4n(k`aU0t7=uJB%GwKcC$87R#U(Co@dQvu-vM4S$#fDdA* z@>BdA*&%mC#^j2EQ>5P3Xt_fi?Xs@#yu&_GcuV+2;Zc$*Mbw*4D=ehknQ^kzoP(Sy^upEALLy)NW1X!+yU`r-A5lnY9PbXsddmn;;^O! z2AH%exx-9G;p8VAZ8qSEQqT%7^)p3`Xc90RVSeYEivi(zcIDjuWd_1tH*Mk5h#54>BBI|B$GrJxIXVr)_ zK_cCT3CJIu+ZGRyVf^78hC-a>w1yJKs4kIB~22_Yp1 zH5Zv61_|(FAn0cj+qEt1tY5CwXS&3Zng&^B^;RI{yW{f?RCAv?-Phf=B-Bpl^>8c8 z4=tlU{6%I$ECi)^I&DYXVe}(kJs9M7cBgYLFo54M_4F9R zr?;(_7gT^Tvu`*-yogd|BHpLvpW? zgE8}0E#7_mFkNYC>QDtak7!HoWx_Ge`3u269x`kgUpa=^6j)&cCv%+1K-(fc)T27T zLf*3}`1iH*r)QUm4fGEkZrXigAj;+ms=vs``SFnv^8uR(@q~-B;kCL#4PqN>)8~s0CJ)XV9At^<0T2cg zp`QJ5eEAXGg|@i&`#lP@{~uNF7#&E|H49HLv2ELSCbpf4Cbq4aj&0kvZQHi(OsqT4 z^WG2dT6NayKmDg`_wMe~yQ=g%0=-?3bdq73`)uS}bb}}DA%$%

u%w0P)Q12;1@T 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 |JRq@oc!hANL#*8Dj}5)S>D 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 z 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

q62tJM!Vq}_W^6eZcaoYjekac=~b5VAk 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 diff --git a/src/test/java/io/supertokens/test/session/AccessTokenTest.java b/src/test/java/io/supertokens/test/session/AccessTokenTest.java index 31e482a7a..30247227a 100644 --- a/src/test/java/io/supertokens/test/session/AccessTokenTest.java +++ b/src/test/java/io/supertokens/test/session/AccessTokenTest.java @@ -301,6 +301,7 @@ public void inputOutputTestStatic() throws Exception { TokenInfo newToken = AccessToken.createNewAccessToken(process.getProcess(), "sessionHandle", "userId", "refreshTokenHash1", "parentRefreshTokenHash1", jsonObj, "antiCsrfToken", expiryTime, AccessToken.getLatestVersion(), true); + System.out.println(newToken.token); AccessTokenInfo info = AccessToken.getInfoFromAccessToken(process.getProcess(), newToken.token, true); assertEquals("sessionHandle", info.sessionHandle); assertEquals("userId", info.recipeUserId); From abdcfbc6353769383e503c107ab02e40e63f446b Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Tue, 19 Sep 2023 16:21:12 +0530 Subject: [PATCH 129/131] fix: fixes for mongo plugin (#817) --- .../java/io/supertokens/session/Session.java | 23 ++++++---- .../test/AuthRecipesParallelTest.java | 9 ++++ .../test/SuperTokensSaaSSecretTest.java | 8 ++++ .../accountlinking/CreatePrimaryUserTest.java | 44 +++++++++++++++++++ .../GetUserByAccountInfoTest.java | 33 ++++++++++++++ .../test/accountlinking/GetUserByIdTest.java | 14 ++++++ .../test/accountlinking/MultitenantTest.java | 21 +++++++++ .../api/CanCreatePrimaryUserAPITest.java | 16 +++++++ .../api/CanLinkAccountsAPITest.java | 16 +++++++ .../api/CreatePrimaryUserAPITest.java | 16 +++++++ .../api/GetUserByAccountInfoTest.java | 37 ++++++++++++++++ .../accountlinking/api/GetUserByIdTest.java | 42 ++++++++++++++++++ .../api/LinkAccountsAPITest.java | 20 +++++++++ .../api/UserPaginationTest.java | 10 +++++ .../api/EmailVerificationTest.java | 4 ++ .../thirdparty/api/EmailVerificationTest.java | 4 ++ 16 files changed, 308 insertions(+), 9 deletions(-) diff --git a/src/main/java/io/supertokens/session/Session.java b/src/main/java/io/supertokens/session/Session.java index 63b2cb6d4..dcbbdd204 100644 --- a/src/main/java/io/supertokens/session/Session.java +++ b/src/main/java/io/supertokens/session/Session.java @@ -136,10 +136,13 @@ public static SessionInformationHolder createNewSession(TenantIdentifierWithStor sessionHandle += "_" + tenantIdentifierWithStorage.getTenantId(); } - String primaryUserId = tenantIdentifierWithStorage.getAuthRecipeStorage() - .getPrimaryUserIdStrForUserId(tenantIdentifierWithStorage.toAppIdentifier(), recipeUserId); - if (primaryUserId == null) { - primaryUserId = recipeUserId; + String primaryUserId = recipeUserId; + if (tenantIdentifierWithStorage.getStorage().getType().equals(STORAGE_TYPE.SQL)) { + tenantIdentifierWithStorage.getAuthRecipeStorage() + .getPrimaryUserIdStrForUserId(tenantIdentifierWithStorage.toAppIdentifier(), recipeUserId); + if (primaryUserId == null) { + primaryUserId = recipeUserId; + } } String antiCsrfToken = enableAntiCsrf ? UUID.randomUUID().toString() : null; @@ -845,11 +848,13 @@ public static String[] getAllNonExpiredSessionHandlesForUser( Set userIds = new HashSet<>(); userIds.add(userId); if (fetchSessionsForAllLinkedAccounts) { - AuthRecipeUserInfo primaryUser = appIdentifierWithStorage.getAuthRecipeStorage() - .getPrimaryUserById(appIdentifierWithStorage, userId); - if (primaryUser != null) { - for (LoginMethod lM : primaryUser.loginMethods) { - userIds.add(lM.getSupertokensUserId()); + if (appIdentifierWithStorage.getStorage().getType().equals(STORAGE_TYPE.SQL)) { + AuthRecipeUserInfo primaryUser = appIdentifierWithStorage.getAuthRecipeStorage() + .getPrimaryUserById(appIdentifierWithStorage, userId); + if (primaryUser != null) { + for (LoginMethod lM : primaryUser.loginMethods) { + userIds.add(lM.getSupertokensUserId()); + } } } } diff --git a/src/test/java/io/supertokens/test/AuthRecipesParallelTest.java b/src/test/java/io/supertokens/test/AuthRecipesParallelTest.java index a302bc7a0..3e65fc432 100644 --- a/src/test/java/io/supertokens/test/AuthRecipesParallelTest.java +++ b/src/test/java/io/supertokens/test/AuthRecipesParallelTest.java @@ -20,6 +20,7 @@ import io.supertokens.emailpassword.EmailPassword; import io.supertokens.emailpassword.exceptions.EmailChangeNotAllowedException; import io.supertokens.emailpassword.exceptions.WrongCredentialsException; +import io.supertokens.pluginInterface.STORAGE_TYPE; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.TestingProcessManager; @@ -60,6 +61,10 @@ public void timeTakenFor500SignInParallel() throws Exception { TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + ExecutorService ex = Executors.newFixedThreadPool(1000); int numberOfThreads = 500; @@ -104,6 +109,10 @@ public void timeTakenFor500SignInUpParallel() throws Exception { TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + if (StorageLayer.isInMemDb(process.getProcess())) { return; } diff --git a/src/test/java/io/supertokens/test/SuperTokensSaaSSecretTest.java b/src/test/java/io/supertokens/test/SuperTokensSaaSSecretTest.java index 0f20483d3..1e565cec5 100644 --- a/src/test/java/io/supertokens/test/SuperTokensSaaSSecretTest.java +++ b/src/test/java/io/supertokens/test/SuperTokensSaaSSecretTest.java @@ -186,6 +186,10 @@ public void testCreatingSessionWithAndWithoutAPIKey() throws Exception { TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + String userId = "userId"; JsonObject userDataInJWT = new JsonObject(); userDataInJWT.addProperty("key", "value"); @@ -272,6 +276,10 @@ public void testCreatingSessionWithAndWithoutAPIKeyWhenSuperTokensSaaSSecretIsAl TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + String userId = "userId"; JsonObject userDataInJWT = new JsonObject(); userDataInJWT.addProperty("key", "value"); diff --git a/src/test/java/io/supertokens/test/accountlinking/CreatePrimaryUserTest.java b/src/test/java/io/supertokens/test/accountlinking/CreatePrimaryUserTest.java index 73325130e..5614de91d 100644 --- a/src/test/java/io/supertokens/test/accountlinking/CreatePrimaryUserTest.java +++ b/src/test/java/io/supertokens/test/accountlinking/CreatePrimaryUserTest.java @@ -159,6 +159,10 @@ public void makeEmailPasswordPrimaryUserSuccess() throws Exception { process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + AuthRecipeUserInfo emailPasswordUser = EmailPassword.signUp(process.getProcess(), "test@example.com", "pass1234"); @@ -191,6 +195,10 @@ public void makeThirdPartyPrimaryUserSuccess() throws Exception { process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + ThirdParty.SignInUpResponse signInUp = ThirdParty.signInUp(process.getProcess(), "google", "user-google", "test@example.com"); @@ -226,6 +234,10 @@ public void makePasswordlessEmailPrimaryUserSuccess() throws Exception { process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + Passwordless.CreateCodeResponse code = Passwordless.createCode(process.getProcess(), "u@e.com", null, null, null); Passwordless.ConsumeCodeResponse pResp = Passwordless.consumeCode(process.getProcess(), code.deviceId, @@ -261,6 +273,10 @@ public void makePasswordlessPhonePrimaryUserSuccess() throws Exception { process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + Passwordless.CreateCodeResponse code = Passwordless.createCode(process.getProcess(), null, "1234", null, null); Passwordless.ConsumeCodeResponse pResp = Passwordless.consumeCode(process.getProcess(), code.deviceId, @@ -296,6 +312,10 @@ public void alreadyPrimaryUsertest() throws Exception { process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + AuthRecipeUserInfo emailPasswordUser = EmailPassword.signUp(process.getProcess(), "test@example.com", "pass1234"); @@ -332,6 +352,10 @@ public void makePrimaryUserFailsCauseAnotherAccountWithSameEmailAlreadyAPrimaryU process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + AuthRecipeUserInfo emailPasswordUser = EmailPassword.signUp(process.getProcess(), "test@example.com", "pass1234"); @@ -363,6 +387,10 @@ public void makePrimarySucceedsEvenIfAnotherAccountWithSameEmailButIsNotAPrimary process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + AuthRecipeUserInfo emailPasswordUser = EmailPassword.signUp(process.getProcess(), "test@example.com", "pass1234"); @@ -387,6 +415,10 @@ public void makePrimaryUserFailsCauseAnotherAccountWithSameEmailAlreadyAPrimaryU process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + Multitenancy.addNewOrUpdateAppOrTenant(process.main, new TenantIdentifier(null, null, null), new TenantConfig(new TenantIdentifier(null, null, "t1"), new EmailPasswordConfig(true), new ThirdPartyConfig(true, new ThirdPartyConfig.Provider[0]), new PasswordlessConfig(true), @@ -429,6 +461,10 @@ public void makePrimarySucceedsEvenIfAnotherAccountWithSameEmailButInADifferentT process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + Multitenancy.addNewOrUpdateAppOrTenant(process.main, new TenantIdentifier(null, null, null), new TenantConfig(new TenantIdentifier(null, null, "t1"), new EmailPasswordConfig(true), new ThirdPartyConfig(true, new ThirdPartyConfig.Provider[0]), new PasswordlessConfig(true), @@ -463,6 +499,10 @@ public void makePrimaryUserFailsCauseOfUnknownUserId() throws Exception { process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + try { AuthRecipe.createPrimaryUser(process.main, "random"); assert (false); @@ -483,6 +523,10 @@ public void makingPrimaryUserFailsCauseAlreadyLinkedToAnotherAccount() throws Ex process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + AuthRecipeUserInfo emailPasswordUser1 = EmailPassword.signUp(process.getProcess(), "test@example.com", "pass1234"); AuthRecipeUserInfo emailPasswordUser2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", diff --git a/src/test/java/io/supertokens/test/accountlinking/GetUserByAccountInfoTest.java b/src/test/java/io/supertokens/test/accountlinking/GetUserByAccountInfoTest.java index 0168677e5..11371bf97 100644 --- a/src/test/java/io/supertokens/test/accountlinking/GetUserByAccountInfoTest.java +++ b/src/test/java/io/supertokens/test/accountlinking/GetUserByAccountInfoTest.java @@ -26,6 +26,7 @@ import io.supertokens.passwordless.Passwordless; import io.supertokens.passwordless.exceptions.*; import io.supertokens.pluginInterface.RECIPE_ID; +import io.supertokens.pluginInterface.STORAGE_TYPE; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; @@ -104,6 +105,10 @@ public void testListUsersByAccountInfoForUnlinkedAccounts() throws Exception { process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + AuthRecipeUserInfo user1 = createEmailPasswordUser(process.getProcess(), "test1@example.com", "password1"); AuthRecipeUserInfo user2 = createThirdPartyUser(process.getProcess(), "google", "userid1", "test2@example.com"); AuthRecipeUserInfo user3 = createPasswordlessUserWithEmail(process.getProcess(), "test3@example.com"); @@ -151,6 +156,10 @@ public void testListUsersByAccountInfoForUnlinkedAccountsWithUnionOption() throw process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + AuthRecipeUserInfo user1 = createEmailPasswordUser(process.getProcess(), "test1@example.com", "password1"); AuthRecipeUserInfo user2 = createThirdPartyUser(process.getProcess(), "google", "userid1", "test2@example.com"); AuthRecipeUserInfo user3 = createPasswordlessUserWithEmail(process.getProcess(), "test3@example.com"); @@ -197,6 +206,10 @@ public void testUnknownAccountInfo() throws Exception { process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + TenantIdentifierWithStorage tenantIdentifierWithStorage = TenantIdentifier.BASE_TENANT.withStorage(StorageLayer.getBaseStorage(process.getProcess())); assertEquals(0, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, "test1@example.com", null, null, null).length); assertEquals(0, AuthRecipe.getUsersByAccountInfo(tenantIdentifierWithStorage, false, null, null, "google", "userid1").length); @@ -217,6 +230,10 @@ public void testListUserByAccountInfoWhenAccountsAreLinked1() throws Exception { process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + AuthRecipeUserInfo user1 = EmailPassword.signUp(process.getProcess(), "test1@example.com", "password1"); Thread.sleep(50); AuthRecipeUserInfo user2 = ThirdParty.signInUp(process.getProcess(), "google", "userid1", "test2@example.com").user; @@ -254,6 +271,10 @@ public void testListUserByAccountInfoWhenAccountsAreLinked2() throws Exception { process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + AuthRecipeUserInfo user1 = EmailPassword.signUp(process.getProcess(), "test1@example.com", "password1"); Thread.sleep(50); AuthRecipeUserInfo user2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "password2"); @@ -285,6 +306,10 @@ public void testListUserByAccountInfoWhenAccountsAreLinked3() throws Exception { process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + AuthRecipeUserInfo user1 = createEmailPasswordUser(process.getProcess(), "test1@example.com", "password1"); Thread.sleep(50); AuthRecipeUserInfo user2 = createPasswordlessUserWithEmail(process.getProcess(), "test2@example.com"); @@ -316,6 +341,10 @@ public void testListUserByAccountInfoWhenAccountsAreLinked4() throws Exception { process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + AuthRecipeUserInfo user1 = createEmailPasswordUser(process.getProcess(), "test1@example.com", "password1"); Thread.sleep(50); AuthRecipeUserInfo user2 = createPasswordlessUserWithPhone(process.getProcess(), "+919876543210"); @@ -349,6 +378,10 @@ public void testListUserByAccountInfoWhenAccountsAreLinked5() throws Exception { process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + AuthRecipeUserInfo user1 = createEmailPasswordUser(process.getProcess(), "test1@example.com", "password1"); Thread.sleep(50); AuthRecipeUserInfo user2 = createThirdPartyUser(process.getProcess(), "google", "userid1", "test2@example.com"); diff --git a/src/test/java/io/supertokens/test/accountlinking/GetUserByIdTest.java b/src/test/java/io/supertokens/test/accountlinking/GetUserByIdTest.java index 306beec64..48bf9ee9a 100644 --- a/src/test/java/io/supertokens/test/accountlinking/GetUserByIdTest.java +++ b/src/test/java/io/supertokens/test/accountlinking/GetUserByIdTest.java @@ -26,11 +26,13 @@ import io.supertokens.passwordless.Passwordless; import io.supertokens.passwordless.exceptions.*; import io.supertokens.pluginInterface.RECIPE_ID; +import io.supertokens.pluginInterface.STORAGE_TYPE; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; import io.supertokens.pluginInterface.passwordless.exception.DuplicateLinkCodeHashException; +import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.TestingProcessManager; import io.supertokens.test.Utils; import io.supertokens.thirdparty.ThirdParty; @@ -102,6 +104,10 @@ public void testAllLoginMethods() throws Exception { process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + AuthRecipeUserInfo user1 = createEmailPasswordUser(process.getProcess(), "test@example.com", "password1"); Thread.sleep(50); AuthRecipeUserInfo user2 = createThirdPartyUser(process.getProcess(), "google", "userid1", "test@example.com"); @@ -162,6 +168,10 @@ public void testUnknownUserIdReturnsNull() throws Exception { process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + assertNull(AuthRecipe.getUserById(process.getProcess(), "unknownid")); process.kill(); @@ -179,6 +189,10 @@ public void testLoginMethodsAreSortedByTime() throws Exception { process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + // Create users AuthRecipeUserInfo user4 = createPasswordlessUserWithPhone(process.getProcess(), "+919876543210"); Thread.sleep(50); diff --git a/src/test/java/io/supertokens/test/accountlinking/MultitenantTest.java b/src/test/java/io/supertokens/test/accountlinking/MultitenantTest.java index 3b63f2e2e..e9f34f4d6 100644 --- a/src/test/java/io/supertokens/test/accountlinking/MultitenantTest.java +++ b/src/test/java/io/supertokens/test/accountlinking/MultitenantTest.java @@ -32,6 +32,7 @@ import io.supertokens.multitenancy.exception.*; import io.supertokens.passwordless.Passwordless; import io.supertokens.passwordless.exceptions.PhoneNumberChangeNotAllowedException; +import io.supertokens.pluginInterface.STORAGE_TYPE; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.authRecipe.LoginMethod; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; @@ -174,6 +175,10 @@ public void testUserAreNotAutomaticallySharedBetweenTenantsOfLinkedAccountsForPl process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + createTenants(process.getProcess()); t1 = new TenantIdentifier(null, "a1", null); @@ -214,6 +219,10 @@ public void testUserAreNotAutomaticallySharedBetweenTenantsOfLinkedAccountsForTP process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + createTenants(process.getProcess()); t1 = new TenantIdentifier(null, "a1", null); @@ -250,6 +259,10 @@ public void testTenantDeletionWithAccountLinking() throws Exception { process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + createTenants(process.getProcess()); t1 = new TenantIdentifier(null, "a1", null); @@ -290,6 +303,10 @@ public void testTenantDeletionWithAccountLinkingWithUserRoles() throws Exception process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + createTenants(process.getProcess()); t1 = new TenantIdentifier(null, "a1", null); @@ -740,6 +757,10 @@ public void execute(Main main) throws Exception { process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + createTenants(process.getProcess()); System.out.println("Executing test case : " + i); diff --git a/src/test/java/io/supertokens/test/accountlinking/api/CanCreatePrimaryUserAPITest.java b/src/test/java/io/supertokens/test/accountlinking/api/CanCreatePrimaryUserAPITest.java index 608a313a2..bf2d42429 100644 --- a/src/test/java/io/supertokens/test/accountlinking/api/CanCreatePrimaryUserAPITest.java +++ b/src/test/java/io/supertokens/test/accountlinking/api/CanCreatePrimaryUserAPITest.java @@ -210,6 +210,10 @@ public void makePrimaryUserFailsCauseAnotherAccountWithSameEmailAlreadyAPrimaryU process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + AuthRecipeUserInfo emailPasswordUser = EmailPassword.signUp(process.getProcess(), "test@example.com", "pass1234"); @@ -248,6 +252,10 @@ public void makingPrimaryUserFailsCauseAlreadyLinkedToAnotherAccount() throws Ex process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + AuthRecipeUserInfo emailPasswordUser1 = EmailPassword.signUp(process.getProcess(), "test@example.com", "pass1234"); AuthRecipeUserInfo emailPasswordUser2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", @@ -286,6 +294,10 @@ public void makePrimaryUserFailsCauseAnotherAccountWithSameEmailAlreadyAPrimaryU process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + AuthRecipeUserInfo emailPasswordUser = EmailPassword.signUp(process.getProcess(), "test@example.com", "pass1234"); UserIdMapping.createUserIdMapping(process.main, emailPasswordUser.getSupertokensUserId(), "r1", null, false); @@ -325,6 +337,10 @@ public void makingPrimaryUserFailsCauseAlreadyLinkedToAnotherAccountWithUserIdMa process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + AuthRecipeUserInfo emailPasswordUser1 = EmailPassword.signUp(process.getProcess(), "test@example.com", "pass1234"); UserIdMapping.createUserIdMapping(process.main, emailPasswordUser1.getSupertokensUserId(), "r1", null, false); diff --git a/src/test/java/io/supertokens/test/accountlinking/api/CanLinkAccountsAPITest.java b/src/test/java/io/supertokens/test/accountlinking/api/CanLinkAccountsAPITest.java index 1613ae749..ab155e481 100644 --- a/src/test/java/io/supertokens/test/accountlinking/api/CanLinkAccountsAPITest.java +++ b/src/test/java/io/supertokens/test/accountlinking/api/CanLinkAccountsAPITest.java @@ -263,6 +263,10 @@ public void linkingUsersFailsCauseAnotherAccountWithSameEmailAlreadyAPrimaryUser process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + AuthRecipeUserInfo emailPasswordUser = EmailPassword.signUp(process.getProcess(), "test@example.com", "pass1234"); @@ -309,6 +313,10 @@ public void linkingUsersFailsCauseAnotherAccountWithSameEmailAlreadyAPrimaryUser process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + AuthRecipeUserInfo emailPasswordUser = EmailPassword.signUp(process.getProcess(), "test@example.com", "pass1234"); UserIdMapping.createUserIdMapping(process.main, emailPasswordUser.getSupertokensUserId(), "e1", null, false); @@ -357,6 +365,10 @@ public void linkingUserFailsCauseAlreadyLinkedToAnotherAccount() throws Exceptio process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + AuthRecipeUserInfo emailPasswordUser1 = EmailPassword.signUp(process.getProcess(), "test@example.com", "pass1234"); AuthRecipeUserInfo emailPasswordUser2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", @@ -400,6 +412,10 @@ public void makingPrimaryUserFailsCauseAlreadyLinkedToAnotherAccountWithUserIdMa process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + AuthRecipeUserInfo emailPasswordUser1 = EmailPassword.signUp(process.getProcess(), "test@example.com", "pass1234"); UserIdMapping.createUserIdMapping(process.main, emailPasswordUser1.getSupertokensUserId(), "r1", null, false); diff --git a/src/test/java/io/supertokens/test/accountlinking/api/CreatePrimaryUserAPITest.java b/src/test/java/io/supertokens/test/accountlinking/api/CreatePrimaryUserAPITest.java index 1775db634..ce4dd6010 100644 --- a/src/test/java/io/supertokens/test/accountlinking/api/CreatePrimaryUserAPITest.java +++ b/src/test/java/io/supertokens/test/accountlinking/api/CreatePrimaryUserAPITest.java @@ -266,6 +266,10 @@ public void makePrimaryUserFailsCauseAnotherAccountWithSameEmailAlreadyAPrimaryU process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + AuthRecipeUserInfo emailPasswordUser = EmailPassword.signUp(process.getProcess(), "test@example.com", "pass1234"); @@ -304,6 +308,10 @@ public void makingPrimaryUserFailsCauseAlreadyLinkedToAnotherAccount() throws Ex process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + AuthRecipeUserInfo emailPasswordUser1 = EmailPassword.signUp(process.getProcess(), "test@example.com", "pass1234"); AuthRecipeUserInfo emailPasswordUser2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", @@ -342,6 +350,10 @@ public void makePrimaryUserFailsCauseAnotherAccountWithSameEmailAlreadyAPrimaryU process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + AuthRecipeUserInfo emailPasswordUser = EmailPassword.signUp(process.getProcess(), "test@example.com", "pass1234"); UserIdMapping.createUserIdMapping(process.main, emailPasswordUser.getSupertokensUserId(), "r1", null, false); @@ -381,6 +393,10 @@ public void makingPrimaryUserFailsCauseAlreadyLinkedToAnotherAccountWithUserIdMa process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + AuthRecipeUserInfo emailPasswordUser1 = EmailPassword.signUp(process.getProcess(), "test@example.com", "pass1234"); UserIdMapping.createUserIdMapping(process.main, emailPasswordUser1.getSupertokensUserId(), "r1", null, false); diff --git a/src/test/java/io/supertokens/test/accountlinking/api/GetUserByAccountInfoTest.java b/src/test/java/io/supertokens/test/accountlinking/api/GetUserByAccountInfoTest.java index 90f14ca2f..b799ecbcf 100644 --- a/src/test/java/io/supertokens/test/accountlinking/api/GetUserByAccountInfoTest.java +++ b/src/test/java/io/supertokens/test/accountlinking/api/GetUserByAccountInfoTest.java @@ -27,6 +27,7 @@ import io.supertokens.featureflag.FeatureFlagTestContent; import io.supertokens.passwordless.Passwordless; import io.supertokens.passwordless.exceptions.*; +import io.supertokens.pluginInterface.STORAGE_TYPE; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; @@ -139,6 +140,10 @@ public void testListUsersByAccountInfoForUnlinkedAccounts() throws Exception { process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + AuthRecipeUserInfo user1 = createEmailPasswordUser(process.getProcess(), "test1@example.com", "password1"); AuthRecipeUserInfo user2 = createThirdPartyUser(process.getProcess(), "google", "userid1", "test2@example.com"); AuthRecipeUserInfo user3 = createPasswordlessUserWithEmail(process.getProcess(), "test3@example.com"); @@ -178,6 +183,10 @@ public void testListUsersByAccountInfoForUnlinkedAccountsWithUnionOption() throw process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + AuthRecipeUserInfo user1 = createEmailPasswordUser(process.getProcess(), "test1@example.com", "password1"); AuthRecipeUserInfo user2 = createThirdPartyUser(process.getProcess(), "google", "userid1", "test2@example.com"); AuthRecipeUserInfo user3 = createPasswordlessUserWithEmail(process.getProcess(), "test3@example.com"); @@ -229,6 +238,10 @@ public void testUnknownAccountInfo() throws Exception { process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + TenantIdentifierWithStorage tenantIdentifierWithStorage = TenantIdentifier.BASE_TENANT.withStorage(StorageLayer.getBaseStorage(process.getProcess())); assertEquals(0, getUsersByAccountInfo(process.getProcess(), false, "test1@example.com", null, null, null).size()); assertEquals(0, getUsersByAccountInfo(process.getProcess(), false, null, null, "google", "userid1").size()); @@ -249,6 +262,10 @@ public void testListUserByAccountInfoWhenAccountsAreLinked1() throws Exception { process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + AuthRecipeUserInfo user1 = EmailPassword.signUp(process.getProcess(), "test1@example.com", "password1"); Thread.sleep(50); AuthRecipeUserInfo user2 = ThirdParty.signInUp(process.getProcess(), "google", "userid1", "test2@example.com").user; @@ -286,6 +303,10 @@ public void testListUserByAccountInfoWhenAccountsAreLinked2() throws Exception { process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + AuthRecipeUserInfo user1 = EmailPassword.signUp(process.getProcess(), "test1@example.com", "password1"); Thread.sleep(50); AuthRecipeUserInfo user2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", "password2"); @@ -314,6 +335,10 @@ public void testListUserByAccountInfoWhenAccountsAreLinked3() throws Exception { process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + AuthRecipeUserInfo user1 = createEmailPasswordUser(process.getProcess(), "test1@example.com", "password1"); Thread.sleep(50); AuthRecipeUserInfo user2 = createPasswordlessUserWithEmail(process.getProcess(), "test2@example.com"); @@ -345,6 +370,10 @@ public void testListUserByAccountInfoWhenAccountsAreLinked4() throws Exception { process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + AuthRecipeUserInfo user1 = createEmailPasswordUser(process.getProcess(), "test1@example.com", "password1"); Thread.sleep(50); AuthRecipeUserInfo user2 = createPasswordlessUserWithPhone(process.getProcess(), "+919876543210"); @@ -378,6 +407,10 @@ public void testListUserByAccountInfoWhenAccountsAreLinked5() throws Exception { process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + AuthRecipeUserInfo user1 = createEmailPasswordUser(process.getProcess(), "test1@example.com", "password1"); Thread.sleep(50); AuthRecipeUserInfo user2 = createThirdPartyUser(process.getProcess(), "google", "userid1", "test2@example.com"); @@ -415,6 +448,10 @@ public void testWithUserIdMapping() throws Exception { process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + AuthRecipeUserInfo user1 = createEmailPasswordUser(process.getProcess(), "test@example.com", "password1"); Thread.sleep(50); AuthRecipeUserInfo user2 = createThirdPartyUser(process.getProcess(), "google", "userid1", "test@example.com"); diff --git a/src/test/java/io/supertokens/test/accountlinking/api/GetUserByIdTest.java b/src/test/java/io/supertokens/test/accountlinking/api/GetUserByIdTest.java index a949f7afb..dc6345086 100644 --- a/src/test/java/io/supertokens/test/accountlinking/api/GetUserByIdTest.java +++ b/src/test/java/io/supertokens/test/accountlinking/api/GetUserByIdTest.java @@ -27,11 +27,13 @@ import io.supertokens.featureflag.FeatureFlagTestContent; import io.supertokens.passwordless.Passwordless; import io.supertokens.passwordless.exceptions.*; +import io.supertokens.pluginInterface.STORAGE_TYPE; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; import io.supertokens.pluginInterface.passwordless.exception.DuplicateLinkCodeHashException; +import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.TestingProcessManager; import io.supertokens.test.Utils; import io.supertokens.test.httpRequest.HttpRequestForTesting; @@ -109,6 +111,10 @@ public void testJsonStructure() throws Exception { process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + AuthRecipeUserInfo user1 = createEmailPasswordUser(process.getProcess(), "test@example.com", "password1"); Thread.sleep(50); AuthRecipeUserInfo user2 = createThirdPartyUser(process.getProcess(), "google", "userid1", "test@example.com"); @@ -169,6 +175,10 @@ public void testThatEmailIsAUnionOfLinkedAccounts1() throws Exception { process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + AuthRecipeUserInfo user1 = createEmailPasswordUser(process.getProcess(), "test1@example.com", "password1"); Thread.sleep(50); AuthRecipeUserInfo user2 = createThirdPartyUser(process.getProcess(), "google", "userid1", "test2@example.com"); @@ -211,6 +221,10 @@ public void testThatEmailIsAUnionOfLinkedAccounts2() throws Exception { process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + AuthRecipeUserInfo user1 = createEmailPasswordUser(process.getProcess(), "test1@example.com", "password1"); Thread.sleep(50); AuthRecipeUserInfo user2 = createThirdPartyUser(process.getProcess(), "google", "userid1", "test2@example.com"); @@ -249,6 +263,10 @@ public void testThatEmailIsAUnionOfLinkedAccounts3() throws Exception { process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + AuthRecipeUserInfo user1 = createEmailPasswordUser(process.getProcess(), "test1@example.com", "password1"); Thread.sleep(50); AuthRecipeUserInfo user2 = createPasswordlessUserWithEmail(process.getProcess(), "test2@example.com"); @@ -287,6 +305,10 @@ public void testThatEmailIsAUnionOfLinkedAccounts4() throws Exception { process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + AuthRecipeUserInfo user1 = createThirdPartyUser(process.getProcess(), "google", "googleid", "test1@example.com"); Thread.sleep(50); AuthRecipeUserInfo user2 = createPasswordlessUserWithEmail(process.getProcess(), "test2@example.com"); @@ -325,6 +347,10 @@ public void testThatPhoneNumberIsUnionOfLinkedAccounts1() throws Exception { process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + AuthRecipeUserInfo user1 = createPasswordlessUserWithPhone(process.getProcess(), "+919876543210"); Thread.sleep(50); AuthRecipeUserInfo user2 = createPasswordlessUserWithPhone(process.getProcess(), "+911234567890"); @@ -363,6 +389,10 @@ public void testThatPhoneNumberIsUnionOfLinkedAccounts2() throws Exception { process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + AuthRecipeUserInfo user1 = createPasswordlessUserWithPhone(process.getProcess(), "+919876543210"); Thread.sleep(50); AuthRecipeUserInfo user2 = createPasswordlessUserWithPhone(process.getProcess(), "+911234567890"); @@ -404,6 +434,10 @@ public void testThatPhoneNumberIsUnionOfLinkedAccounts3() throws Exception { process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + AuthRecipeUserInfo user1 = createPasswordlessUserWithPhone(process.getProcess(), "+919876543210"); Thread.sleep(50); AuthRecipeUserInfo user2 = createPasswordlessUserWithPhone(process.getProcess(), "+911234567890"); @@ -445,6 +479,10 @@ public void testWithUserIdMapping() throws Exception { process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + AuthRecipeUserInfo user1 = createEmailPasswordUser(process.getProcess(), "test@example.com", "password1"); Thread.sleep(50); AuthRecipeUserInfo user2 = createThirdPartyUser(process.getProcess(), "google", "userid1", "test@example.com"); @@ -489,6 +527,10 @@ public void testUnknownUser() throws Exception { process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + Map params = new HashMap<>(); params.put("userId", "unknownid"); JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", diff --git a/src/test/java/io/supertokens/test/accountlinking/api/LinkAccountsAPITest.java b/src/test/java/io/supertokens/test/accountlinking/api/LinkAccountsAPITest.java index 5fd6a1943..1adae1ba1 100644 --- a/src/test/java/io/supertokens/test/accountlinking/api/LinkAccountsAPITest.java +++ b/src/test/java/io/supertokens/test/accountlinking/api/LinkAccountsAPITest.java @@ -268,6 +268,10 @@ public void linkingUsersFailsCauseAnotherAccountWithSameEmailAlreadyAPrimaryUser process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + AuthRecipeUserInfo emailPasswordUser = EmailPassword.signUp(process.getProcess(), "test@example.com", "pass1234"); @@ -314,6 +318,10 @@ public void linkingUsersFailsCauseAnotherAccountWithSameEmailAlreadyAPrimaryUser process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + AuthRecipeUserInfo emailPasswordUser = EmailPassword.signUp(process.getProcess(), "test@example.com", "pass1234"); UserIdMapping.createUserIdMapping(process.main, emailPasswordUser.getSupertokensUserId(), "e1", null, false); @@ -362,6 +370,10 @@ public void linkingUserFailsCauseAlreadyLinkedToAnotherAccount() throws Exceptio process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + AuthRecipeUserInfo emailPasswordUser1 = EmailPassword.signUp(process.getProcess(), "test@example.com", "pass1234"); AuthRecipeUserInfo emailPasswordUser2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", @@ -407,6 +419,10 @@ public void makingPrimaryUserFailsCauseAlreadyLinkedToAnotherAccountWithUserIdMa process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + AuthRecipeUserInfo emailPasswordUser1 = EmailPassword.signUp(process.getProcess(), "test@example.com", "pass1234"); UserIdMapping.createUserIdMapping(process.main, emailPasswordUser1.getSupertokensUserId(), "r1", null, false); @@ -573,6 +589,10 @@ public void linkingUserFailsCauseAlreadyLinkedToAnotherAccountReturnsUserObject( process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + AuthRecipeUserInfo emailPasswordUser1 = EmailPassword.signUp(process.getProcess(), "test@example.com", "pass1234"); AuthRecipeUserInfo emailPasswordUser2 = EmailPassword.signUp(process.getProcess(), "test2@example.com", diff --git a/src/test/java/io/supertokens/test/accountlinking/api/UserPaginationTest.java b/src/test/java/io/supertokens/test/accountlinking/api/UserPaginationTest.java index b5d619959..99cf76376 100644 --- a/src/test/java/io/supertokens/test/accountlinking/api/UserPaginationTest.java +++ b/src/test/java/io/supertokens/test/accountlinking/api/UserPaginationTest.java @@ -27,11 +27,13 @@ import io.supertokens.featureflag.FeatureFlagTestContent; import io.supertokens.passwordless.Passwordless; import io.supertokens.passwordless.exceptions.*; +import io.supertokens.pluginInterface.STORAGE_TYPE; import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo; import io.supertokens.pluginInterface.emailpassword.exceptions.DuplicateEmailException; import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException; import io.supertokens.pluginInterface.passwordless.exception.DuplicateLinkCodeHashException; +import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.TestingProcessManager; import io.supertokens.test.Utils; import io.supertokens.test.httpRequest.HttpRequestForTesting; @@ -142,6 +144,10 @@ public void testUserPaginationResultJson() throws Exception { process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + AuthRecipeUserInfo user1 = createEmailPasswordUser(process.getProcess(), "test1@example.com", "password"); AuthRecipeUserInfo user2 = createEmailPasswordUser(process.getProcess(), "test2@example.com", "password"); AuthRecipeUserInfo user3 = createPasswordlessUserWithEmail(process.getProcess(), "test3@example.com"); @@ -230,6 +236,10 @@ public void testUserPaginationWithManyUsers() throws Exception { process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + Map userInfoMap = new HashMap<>(); Set userIds = new HashSet<>(); Set emailPasswordUsers = new HashSet<>(); diff --git a/src/test/java/io/supertokens/test/passwordless/api/EmailVerificationTest.java b/src/test/java/io/supertokens/test/passwordless/api/EmailVerificationTest.java index 0596536cf..750fe9b98 100644 --- a/src/test/java/io/supertokens/test/passwordless/api/EmailVerificationTest.java +++ b/src/test/java/io/supertokens/test/passwordless/api/EmailVerificationTest.java @@ -171,6 +171,10 @@ public void testWithAccountLinking() throws Exception { process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + AuthRecipeUserInfo user1 = createEmailPasswordUser(process.getProcess(), "test@example.com", "password"); Thread.sleep(50); AuthRecipeUserInfo user2 = createPasswordlessUserWithEmail(process.getProcess(), "test@example.com"); diff --git a/src/test/java/io/supertokens/test/thirdparty/api/EmailVerificationTest.java b/src/test/java/io/supertokens/test/thirdparty/api/EmailVerificationTest.java index 8ca4ab873..b270670bf 100644 --- a/src/test/java/io/supertokens/test/thirdparty/api/EmailVerificationTest.java +++ b/src/test/java/io/supertokens/test/thirdparty/api/EmailVerificationTest.java @@ -232,6 +232,10 @@ public void testWithAccountLinking() throws Exception { process.startProcess(); assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + AuthRecipeUserInfo user1 = createEmailPasswordUser(process.getProcess(), "test@example.com", "password"); Thread.sleep(50); AuthRecipeUserInfo user2 = createThirdPartyUser(process.getProcess(), "google", "google-user", "test@example.com"); From d6638931db3a610ec92aebdef28b0ba76fb90450 Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Tue, 19 Sep 2023 16:32:10 +0530 Subject: [PATCH 130/131] fix: version and changelog --- CHANGELOG.md | 238 ++++++++++++++++-------- build.gradle | 2 +- jar/{core-6.0.13.jar => core-7.0.0.jar} | Bin 658729 -> 726964 bytes pluginInterfaceSupported.json | 2 +- 4 files changed, 160 insertions(+), 82 deletions(-) rename jar/{core-6.0.13.jar => core-7.0.0.jar} (52%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c0aa2aa3..ac1d7fe81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,10 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [unreleased] +## [7.0.0] - 2023-09-19 + - Support for CDI version 4.0 +- Adds Account Linking feature ### Session recipe changes: - New access token version: v5, which contains a required prop: `rsub`. This contains the recipe user ID that belongs to the login method that the user used to login. The `sub` claim in the access token payload is now the primary user ID. @@ -15,87 +18,162 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - Apis that create / modify / refresh a session return the `recipeUserId` in the `session` object in the response. - Token theft detected response returns userId and recipeUserId +### Migration steps for SQL + +1. Ensure that the core is already upgraded to version 6.0.13 (CDI version 3.0) +2. Stop the core instance(s) +3. Run the migration script + +

+ + If using PostgreSQL + + ```sql + ALTER TABLE all_auth_recipe_users + ADD COLUMN primary_or_recipe_user_id CHAR(36) NOT NULL DEFAULT ('0'); + + ALTER TABLE all_auth_recipe_users + ADD COLUMN is_linked_or_is_a_primary_user BOOLEAN NOT NULL DEFAULT FALSE; + + ALTER TABLE all_auth_recipe_users + ADD COLUMN primary_or_recipe_user_time_joined BIGINT NOT NULL DEFAULT 0; + + UPDATE all_auth_recipe_users + SET primary_or_recipe_user_id = user_id + WHERE primary_or_recipe_user_id = '0'; + + UPDATE all_auth_recipe_users + SET primary_or_recipe_user_time_joined = time_joined + WHERE primary_or_recipe_user_time_joined = 0; + + ALTER TABLE all_auth_recipe_users + ADD CONSTRAINT all_auth_recipe_users_primary_or_recipe_user_id_fkey + FOREIGN KEY (app_id, primary_or_recipe_user_id) + REFERENCES app_id_to_user_id (app_id, user_id) ON DELETE CASCADE; + + ALTER TABLE all_auth_recipe_users + ALTER primary_or_recipe_user_id DROP DEFAULT; + + ALTER TABLE app_id_to_user_id + ADD COLUMN primary_or_recipe_user_id CHAR(36) NOT NULL DEFAULT ('0'); + + ALTER TABLE app_id_to_user_id + ADD COLUMN is_linked_or_is_a_primary_user BOOLEAN NOT NULL DEFAULT FALSE; + + UPDATE app_id_to_user_id + SET primary_or_recipe_user_id = user_id + WHERE primary_or_recipe_user_id = '0'; + + ALTER TABLE app_id_to_user_id + ADD CONSTRAINT app_id_to_user_id_primary_or_recipe_user_id_fkey + FOREIGN KEY (app_id, primary_or_recipe_user_id) + REFERENCES app_id_to_user_id (app_id, user_id) ON DELETE CASCADE; + + ALTER TABLE app_id_to_user_id + ALTER primary_or_recipe_user_id DROP DEFAULT; + + DROP INDEX all_auth_recipe_users_pagination_index; + + CREATE INDEX all_auth_recipe_users_pagination_index1 ON all_auth_recipe_users ( + app_id, tenant_id, primary_or_recipe_user_time_joined DESC, primary_or_recipe_user_id DESC); + + CREATE INDEX all_auth_recipe_users_pagination_index2 ON all_auth_recipe_users ( + app_id, tenant_id, primary_or_recipe_user_time_joined ASC, primary_or_recipe_user_id DESC); + + CREATE INDEX all_auth_recipe_users_pagination_index3 ON all_auth_recipe_users ( + recipe_id, app_id, tenant_id, primary_or_recipe_user_time_joined DESC, primary_or_recipe_user_id DESC); + + CREATE INDEX all_auth_recipe_users_pagination_index4 ON all_auth_recipe_users ( + recipe_id, app_id, tenant_id, primary_or_recipe_user_time_joined ASC, primary_or_recipe_user_id DESC); + + CREATE INDEX all_auth_recipe_users_primary_user_id_index ON all_auth_recipe_users (primary_or_recipe_user_id, app_id); + + CREATE INDEX all_auth_recipe_users_recipe_id_index ON all_auth_recipe_users (app_id, recipe_id, tenant_id); + + ALTER TABLE emailpassword_pswd_reset_tokens DROP CONSTRAINT IF EXISTS emailpassword_pswd_reset_tokens_user_id_fkey; + + ALTER TABLE emailpassword_pswd_reset_tokens ADD CONSTRAINT emailpassword_pswd_reset_tokens_user_id_fkey FOREIGN KEY (app_id, user_id) REFERENCES app_id_to_user_id (app_id, user_id) ON DELETE CASCADE; + + ALTER TABLE emailpassword_pswd_reset_tokens ADD COLUMN email VARCHAR(256); + ``` +
+ +
+ + If using MySQL + + ```sql + ALTER TABLE all_auth_recipe_users + ADD primary_or_recipe_user_id CHAR(36) NOT NULL DEFAULT ('0'); + + ALTER TABLE all_auth_recipe_users + ADD is_linked_or_is_a_primary_user BOOLEAN NOT NULL DEFAULT FALSE; + + ALTER TABLE all_auth_recipe_users + ADD primary_or_recipe_user_time_joined BIGINT UNSIGNED NOT NULL DEFAULT 0; + + UPDATE all_auth_recipe_users + SET primary_or_recipe_user_id = user_id + WHERE primary_or_recipe_user_id = '0'; + + UPDATE all_auth_recipe_users + SET primary_or_recipe_user_time_joined = time_joined + WHERE primary_or_recipe_user_time_joined = 0; + + ALTER TABLE all_auth_recipe_users + ADD FOREIGN KEY (app_id, primary_or_recipe_user_id) + REFERENCES app_id_to_user_id (app_id, user_id) ON DELETE CASCADE; + + ALTER TABLE all_auth_recipe_users + ALTER primary_or_recipe_user_id DROP DEFAULT; + + ALTER TABLE app_id_to_user_id + ADD primary_or_recipe_user_id CHAR(36) NOT NULL DEFAULT ('0'); + + ALTER TABLE app_id_to_user_id + ADD is_linked_or_is_a_primary_user BOOLEAN NOT NULL DEFAULT FALSE; + + UPDATE app_id_to_user_id + SET primary_or_recipe_user_id = user_id + WHERE primary_or_recipe_user_id = '0'; + + ALTER TABLE app_id_to_user_id + ADD FOREIGN KEY (app_id, primary_or_recipe_user_id) + REFERENCES app_id_to_user_id (app_id, user_id) ON DELETE CASCADE; + + ALTER TABLE app_id_to_user_id + ALTER primary_or_recipe_user_id DROP DEFAULT; + + DROP INDEX all_auth_recipe_users_pagination_index ON all_auth_recipe_users; + + CREATE INDEX all_auth_recipe_users_pagination_index1 ON all_auth_recipe_users ( + app_id, tenant_id, primary_or_recipe_user_time_joined DESC, primary_or_recipe_user_id DESC); + + CREATE INDEX all_auth_recipe_users_pagination_index2 ON all_auth_recipe_users ( + app_id, tenant_id, primary_or_recipe_user_time_joined ASC, primary_or_recipe_user_id DESC); + + CREATE INDEX all_auth_recipe_users_pagination_index3 ON all_auth_recipe_users ( + recipe_id, app_id, tenant_id, primary_or_recipe_user_time_joined DESC, primary_or_recipe_user_id DESC); + + CREATE INDEX all_auth_recipe_users_pagination_index4 ON all_auth_recipe_users ( + recipe_id, app_id, tenant_id, primary_or_recipe_user_time_joined ASC, primary_or_recipe_user_id DESC); + + CREATE INDEX all_auth_recipe_users_primary_user_id_index ON all_auth_recipe_users (primary_or_recipe_user_id, app_id); + + CREATE INDEX all_auth_recipe_users_recipe_id_index ON all_auth_recipe_users (app_id, recipe_id, tenant_id); + + ALTER TABLE emailpassword_pswd_reset_tokens + DROP FOREIGN KEY emailpassword_pswd_reset_tokens_ibfk_1; + + ALTER TABLE emailpassword_pswd_reset_tokens + ADD FOREIGN KEY (app_id, user_id) REFERENCES app_id_to_user_id (app_id, user_id) ON DELETE CASCADE; + + ALTER TABLE emailpassword_pswd_reset_tokens ADD email VARCHAR(256); + ``` + +
-### Db schema changes: - -- Removed index `all_auth_recipe_users_pagination_index` and added these instead: - - `all_auth_recipe_users_pagination_index1` - - `all_auth_recipe_users_pagination_index2` - - `all_auth_recipe_users_pagination_index3` - - `all_auth_recipe_users_pagination_index4` -- Added new index `all_auth_recipe_users_recipe_id_index` -- Added new index `all_auth_recipe_users_primary_user_id_index`. -- Added a two new columns in `all_auth_recipe_users`: - - `primary_or_recipe_user_id` (default value is equal to `user_id` column) - - `is_linked_or_is_a_primary_user` (default value is false) -- Added a two new columns in `app_id_to_user_id`: - - `primary_or_recipe_user_id` (default value is equal to `user_id` column) - - `is_linked_or_is_a_primary_user` (default value is false) -- Dropped existing fkey constraint on `emailpassword_pswd_reset_tokens`, and added a new fkey constraint on `app_id_to_user_id` table. -- Added new column in `emailpassword_pswd_reset_tokens` to store email - -### DB migration: - -```sql -ALTER TABLE all_auth_recipe_users - ADD COLUMN primary_or_recipe_user_id CHAR(36) NOT NULL DEFAULT ('0'); - -ALTER TABLE all_auth_recipe_users - ADD COLUMN is_linked_or_is_a_primary_user BOOLEAN NOT NULL DEFAULT FALSE; - -ALTER TABLE all_auth_recipe_users - ADD COLUMN primary_or_recipe_user_time_joined BIGINT NOT NULL DEFAULT 0; - -UPDATE all_auth_recipe_users -SET primary_or_recipe_user_id = user_id -WHERE primary_or_recipe_user_id = '0'; - -UPDATE all_auth_recipe_users -SET primary_or_recipe_user_time_joined = time_joined -WHERE primary_or_recipe_user_time_joined = 0; - -ALTER TABLE all_auth_recipe_users - ADD CONSTRAINT all_auth_recipe_users_primary_or_recipe_user_id_fkey - FOREIGN KEY (app_id, primary_or_recipe_user_id) - REFERENCES app_id_to_user_id (app_id, user_id) ON DELETE CASCADE; - -ALTER TABLE all_auth_recipe_users - ALTER primary_or_recipe_user_id DROP DEFAULT; - -ALTER TABLE app_id_to_user_id - ADD COLUMN primary_or_recipe_user_id CHAR(36) NOT NULL DEFAULT ('0'); - -ALTER TABLE app_id_to_user_id - ADD COLUMN is_linked_or_is_a_primary_user BOOLEAN NOT NULL DEFAULT FALSE; - -UPDATE app_id_to_user_id -SET primary_or_recipe_user_id = user_id -WHERE primary_or_recipe_user_id = '0'; - -ALTER TABLE app_id_to_user_id - ADD CONSTRAINT app_id_to_user_id_primary_or_recipe_user_id_fkey - FOREIGN KEY (app_id, primary_or_recipe_user_id) - REFERENCES app_id_to_user_id (app_id, user_id) ON DELETE CASCADE; - -ALTER TABLE app_id_to_user_id - ALTER primary_or_recipe_user_id DROP DEFAULT; - -DROP INDEX all_auth_recipe_users_pagination_index; -CREATE INDEX all_auth_recipe_users_pagination_index1 ON all_auth_recipe_users ( - app_id, tenant_id, primary_or_recipe_user_time_joined DESC, primary_or_recipe_user_id DESC); -CREATE INDEX all_auth_recipe_users_pagination_index2 ON all_auth_recipe_users ( - app_id, tenant_id, primary_or_recipe_user_time_joined ASC, primary_or_recipe_user_id DESC); -CREATE INDEX all_auth_recipe_users_pagination_index3 ON all_auth_recipe_users ( - recipe_id, app_id, tenant_id, primary_or_recipe_user_time_joined DESC, primary_or_recipe_user_id DESC); -CREATE INDEX all_auth_recipe_users_pagination_index4 ON all_auth_recipe_users ( - recipe_id, app_id, tenant_id, primary_or_recipe_user_time_joined ASC, primary_or_recipe_user_id DESC); - -CREATE INDEX all_auth_recipe_users_primary_user_id_index ON all_auth_recipe_users (primary_or_recipe_user_id, app_id); - -ALTER TABLE emailpassword_pswd_reset_tokens DROP CONSTRAINT IF EXISTS emailpassword_pswd_reset_tokens_user_id_fkey; -ALTER TABLE emailpassword_pswd_reset_tokens ADD CONSTRAINT emailpassword_pswd_reset_tokens_user_id_fkey FOREIGN KEY (app_id, user_id) REFERENCES app_id_to_user_id (app_id, user_id) ON DELETE CASCADE; -ALTER TABLE emailpassword_pswd_reset_tokens ADD COLUMN email VARCHAR(256); -``` +4. Start the new instance(s) of the core (version 7.0.0) ## [6.0.13] - 2023-09-15 diff --git a/build.gradle b/build.gradle index 5ae32374e..a2c2d397a 100644 --- a/build.gradle +++ b/build.gradle @@ -19,7 +19,7 @@ compileTestJava { options.encoding = "UTF-8" } // } //} -version = "6.0.13" +version = "7.0.0" repositories { diff --git a/jar/core-6.0.13.jar b/jar/core-7.0.0.jar similarity index 52% rename from jar/core-6.0.13.jar rename to jar/core-7.0.0.jar index c25370fb2137bc47e156b52f1bfed940e373f1e4..723342c67423590f0338e5d3adcdda8df238d69a 100644 GIT binary patch delta 329120 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

7nm5q1?FelQsAM(wHR5UOY8 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 bojza4{MY!c?mX*x)>qeBS!GX0R%rGAneHU^ delta 261994 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

FbBAhuJY_n_t~ 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 i^1rk~gAqv76#W9of2fCT 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

F6c~LByyTO#AWggH$~_VDXw2Dn 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 kuBq_2)e!1DaAyW;+I-~bMw(NUU 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 k2}qhZcq_BG~RsaA1 diff --git a/pluginInterfaceSupported.json b/pluginInterfaceSupported.json index 431c3b083..a5fdc62cd 100644 --- a/pluginInterfaceSupported.json +++ b/pluginInterfaceSupported.json @@ -1,6 +1,6 @@ { "_comment": "contains a list of plugin interfaces branch names that this core supports", "versions": [ - "3.0" + "4.0" ] } \ No newline at end of file From 43d1a6d505a7937cc2c9790388a468d0aaf84505 Mon Sep 17 00:00:00 2001 From: Sattvik Chakravarthy Date: Tue, 19 Sep 2023 16:35:10 +0530 Subject: [PATCH 131/131] fix: schema updates --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac1d7fe81..d656eec54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,11 +13,24 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - Adds Account Linking feature ### Session recipe changes: + - New access token version: v5, which contains a required prop: `rsub`. This contains the recipe user ID that belongs to the login method that the user used to login. The `sub` claim in the access token payload is now the primary user ID. - APIs that return `SessionInformation` (like GET `/recipe/session`) contains userId, recipeUserId in the response. - Apis that create / modify / refresh a session return the `recipeUserId` in the `session` object in the response. - Token theft detected response returns userId and recipeUserId +### Db Schema changes + +- Adds columns `primary_or_recipe_user_id`, `is_linked_or_is_a_primary_user` and `primary_or_recipe_user_time_joined` to `all_auth_recipe_users` table +- Adds columns `primary_or_recipe_user_id` and `is_linked_or_is_a_primary_user` to `app_id_to_user_id` table +- Removes index `all_auth_recipe_users_pagination_index` and addes `all_auth_recipe_users_pagination_index1`, + `all_auth_recipe_users_pagination_index2`, `all_auth_recipe_users_pagination_index3` and + `all_auth_recipe_users_pagination_index4` indexes instead on `all_auth_recipe_users` table +- Adds `all_auth_recipe_users_recipe_id_index` on `all_auth_recipe_users` table +- Adds `all_auth_recipe_users_primary_user_id_index` on `all_auth_recipe_users` table +- Adds `email` column to `emailpassword_pswd_reset_tokens` table +- Changes `user_id` foreign key constraint on `emailpassword_pswd_reset_tokens` to `app_id_to_user_id` table + ### Migration steps for SQL 1. Ensure that the core is already upgraded to version 6.0.13 (CDI version 3.0)