Skip to content

Commit

Permalink
Merge with 9.0
Browse files Browse the repository at this point in the history
  • Loading branch information
prateek3255 committed Mar 14, 2024
2 parents 0267bf2 + 85fd67e commit 6dc4f22
Show file tree
Hide file tree
Showing 144 changed files with 7,808 additions and 1,003 deletions.
4 changes: 4 additions & 0 deletions .github/ISSUE_TEMPLATE/release.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,16 @@ labels:
- [supertokens-core:X.Y](https://github.com/supertokens/supertokens-core/tree/X.Y)
- [ ] core
- [ ] check CDI, plugin interface list
- [ ] Add migration script for psql / mysql
- [ ] Make sure no memory leak
- [ ] plugin-interface
- [ ] check plugin interface list
- [ ] mysql-plugin
- [ ] check plugin interface list
- [ ] Add migration script for mysql
- [ ] postgresql-plugin
- [ ] check plugin interface list
- [ ] Add migration script for psql
- [ ] mongodb-plugin
- [ ] check plugin interface list
- [ ] [supertokens-node:X.Y](https://github.com/supertokens/supertokens-node/tree/X.Y)
Expand Down
30 changes: 30 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,36 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

- Add a new core API for fetching all the core properties

## [9.0.0] - 2024-03-13

### Added

- Supports CDI version `5.0`
- MFA stats in `EEFeatureFlag`
- Adds `ImportTotpDeviceAPI`

### Changes

- `deviceName` in request body of `CreateOrUpdateTotpDeviceAPI` `POST` is now optional
- Adds `firstFactors` and `requiredSecondaryFactors` in request body of create or update CUD, App and
Tenant APIs
- Adds `deviceName` in the response of `CreateOrUpdateTotpDeviceAPI` `POST`
- `VerifyTOTPAPI` changes
- Removes `allowUnverifiedDevices` from request body and unverified devices are not allowed
- Adds `currentNumberOfFailedAttempts` and `maxNumberOfFailedAttempts` in response when status is
`INVALID_TOTP_ERROR` or `LIMIT_REACHED_ERROR`
- Adds status `UNKNOWN_USER_ID_ERROR`
- `VerifyTotpDeviceAPI` changes
- Adds `currentNumberOfFailedAttempts` and `maxNumberOfFailedAttempts` in response when status is
`INVALID_TOTP_ERROR` or `LIMIT_REACHED_ERROR`
- Adds a new required `useDynamicSigningKey` into the request body of `RefreshSessionAPI`
- This enables smooth switching between `useDynamicAccessTokenSigningKey` settings by allowing refresh calls to
change the signing key type of a session

### Migration

- TODO - copy once postgres / mysql changelog is done

## [8.0.1] - 2024-03-11

- Making this version backward compatible. Breaking changes in `8.0.0` can now be ignored.
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ compileTestJava { options.encoding = "UTF-8" }
// }
//}

version = "8.0.1"
version = "9.0.0"


repositories {
Expand Down
Binary file modified cli/jar/cli.jar
Binary file not shown.
5 changes: 3 additions & 2 deletions coreDriverInterfaceSupported.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"2.20",
"2.21",
"3.0",
"4.0"
"4.0",
"5.0"
]
}
}
Binary file modified downloader/jar/downloader.jar
Binary file not shown.
Binary file modified ee/jar/ee.jar
Binary file not shown.
75 changes: 45 additions & 30 deletions ee/src/main/java/io/supertokens/ee/EEFeatureFlag.java
Original file line number Diff line number Diff line change
Expand Up @@ -185,43 +185,43 @@ private JsonObject getDashboardLoginStats() throws TenantOrAppNotFoundException,
return stats;
}

private JsonObject getTOTPStats() throws StorageQueryException, TenantOrAppNotFoundException {
JsonObject totpStats = new JsonObject();
JsonArray totpMauArr = new JsonArray();
private boolean isEnterpriseThirdPartyId(String thirdPartyId) {
for (String enterpriseThirdPartyId : ENTERPRISE_THIRD_PARTY_IDS) {
if (thirdPartyId.startsWith(enterpriseThirdPartyId)) {
return true;
}
}
return false;
}

private JsonObject getMFAStats() throws StorageQueryException, TenantOrAppNotFoundException {
// TODO: Active users are present only on public tenant and MFA users may be
// present on different storages
JsonObject result = new JsonObject();
Storage[] storages = StorageLayer.getStoragesForApp(main, this.appIdentifier);

// TODO Active users are present only on public tenant and TOTP users may be present on different storages
Storage publicTenantStorage = StorageLayer.getStorage(this.appIdentifier.getAsPublicTenantIdentifier(), main);
final long now = System.currentTimeMillis();
for (int i = 1; i <= 31; i++) {
long timestamp = now - (i * 24 * 60 * 60 * 1000L);

int totpMau = 0;
// TODO Need to figure out a way to combine the data from different storages to get the final stats
// for (Storage storage : storages) {
totpMau += ((ActiveUsersStorage) publicTenantStorage).countUsersEnabledTotpAndActiveSince(this.appIdentifier, timestamp);
// }
totpMauArr.add(new JsonPrimitive(totpMau));
}
int totalUserCountWithMoreThanOneLoginMethod = 0;
int[] maus = new int[31];

totpStats.add("maus", totpMauArr);
long now = System.currentTimeMillis();

int totpTotalUsers = 0;
for (Storage storage : storages) {
totpTotalUsers += ((ActiveUsersStorage) storage).countUsersEnabledTotp(this.appIdentifier);
}
totpStats.addProperty("total_users", totpTotalUsers);
return totpStats;
}
totalUserCountWithMoreThanOneLoginMethod += ((AuthRecipeStorage) storage)
.getUsersCountWithMoreThanOneLoginMethodOrTOTPEnabled(this.appIdentifier);

private boolean isEnterpriseThirdPartyId(String thirdPartyId) {
for (String enterpriseThirdPartyId : ENTERPRISE_THIRD_PARTY_IDS) {
if (thirdPartyId.startsWith(enterpriseThirdPartyId)) {
return true;
for (int i = 1; i <= 31; i++) {
long timestamp = now - (i * 24 * 60 * 60 * 1000L);

// `maus[i-1]` since i starts from 1
maus[i - 1] += ((ActiveUsersStorage) storage)
.countUsersThatHaveMoreThanOneLoginMethodOrTOTPEnabledAndActiveSince(appIdentifier, timestamp);
}
}
return false;

result.addProperty("totalUserCountWithMoreThanOneLoginMethodOrTOTPEnabled",
totalUserCountWithMoreThanOneLoginMethod);
result.add("mauWithMoreThanOneLoginMethodOrTOTPEnabled", new Gson().toJsonTree(maus));
return result;
}

private JsonObject getMultiTenancyStats()
Expand All @@ -245,6 +245,21 @@ private JsonObject getMultiTenancyStats()
hasUsersOrSessions = hasUsersOrSessions || ((SessionSQLStorage) storage).getNumberOfSessions(tenantConfig.tenantIdentifier) > 0;
tenantStat.addProperty("usersCount", usersCount);
tenantStat.addProperty("hasUsersOrSessions", hasUsersOrSessions);
if (tenantConfig.firstFactors != null) {
JsonArray firstFactors = new JsonArray();
for (String firstFactor : tenantConfig.firstFactors) {
firstFactors.add(new JsonPrimitive(firstFactor));
}
tenantStat.add("firstFactors", firstFactors);
}

if (tenantConfig.requiredSecondaryFactors != null) {
JsonArray requiredSecondaryFactors = new JsonArray();
for (String requiredSecondaryFactor : tenantConfig.requiredSecondaryFactors) {
requiredSecondaryFactors.add(new JsonPrimitive(requiredSecondaryFactor));
}
tenantStat.add("requiredSecondaryFactors", requiredSecondaryFactors);
}

try {
tenantStat.addProperty("userPoolId", Utils.hashSHA256(storage.getUserPoolId()));
Expand Down Expand Up @@ -355,8 +370,8 @@ public JsonObject getPaidFeatureStats() throws StorageQueryException, TenantOrAp
usageStats.add(EE_FEATURES.DASHBOARD_LOGIN.toString(), getDashboardLoginStats());
}

if (feature == EE_FEATURES.TOTP) {
usageStats.add(EE_FEATURES.TOTP.toString(), getTOTPStats());
if (feature == EE_FEATURES.MFA) {
usageStats.add(EE_FEATURES.MFA.toString(), getMFAStats());
}

if (feature == EE_FEATURES.MULTI_TENANCY) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ public void testPaidStatsIsSentForAllAppsInMultitenancy() throws Exception {
new EmailPasswordConfig(true),
new ThirdPartyConfig(true, null),
new PasswordlessConfig(true),
null, null,
config
), false);

Expand All @@ -86,6 +87,7 @@ public void testPaidStatsIsSentForAllAppsInMultitenancy() throws Exception {
new EmailPasswordConfig(true),
new ThirdPartyConfig(true, null),
new PasswordlessConfig(true),
null, null,
config
), false);

Expand All @@ -94,6 +96,7 @@ public void testPaidStatsIsSentForAllAppsInMultitenancy() throws Exception {
new EmailPasswordConfig(true),
new ThirdPartyConfig(true, null),
new PasswordlessConfig(true),
null, null,
config
), false);
}
Expand Down
Binary file renamed jar/core-8.0.1.jar → jar/core-9.0.0.jar
Binary file not shown.
2 changes: 1 addition & 1 deletion pluginInterfaceSupported.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"_comment": "contains a list of plugin interfaces branch names that this core supports",
"versions": [
"5.0"
"6.0"
]
}
16 changes: 15 additions & 1 deletion src/main/java/io/supertokens/ActiveUsers.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package io.supertokens;

import io.supertokens.pluginInterface.ActiveUsersSQLStorage;
import io.supertokens.pluginInterface.Storage;
import io.supertokens.pluginInterface.StorageUtils;
import io.supertokens.pluginInterface.authRecipe.sqlStorage.AuthRecipeSQLStorage;
import io.supertokens.pluginInterface.exceptions.StorageQueryException;
import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException;
import io.supertokens.pluginInterface.multitenancy.AppIdentifier;
Expand Down Expand Up @@ -37,6 +37,20 @@ public static int countUsersActiveSince(Main main, AppIdentifier appIdentifier,
return StorageUtils.getActiveUsersStorage(storage).countUsersActiveSince(appIdentifier, time);
}

public static void updateLastActiveAfterLinking(Main main, AppIdentifier appIdentifier, String primaryUserId,
String recipeUserId)
throws StorageQueryException, TenantOrAppNotFoundException, StorageTransactionLogicException {
ActiveUsersSQLStorage activeUsersStorage =
(ActiveUsersSQLStorage) StorageUtils.getActiveUsersStorage(StorageLayer.getStorage(appIdentifier.getAsPublicTenantIdentifier(), main));

activeUsersStorage.startTransaction(con -> {
activeUsersStorage.deleteUserActive_Transaction(con, appIdentifier, recipeUserId);
return null;
});

updateLastActive(appIdentifier, main, primaryUserId);
}

@TestOnly
public static int countUsersActiveSince(Main main, long time)
throws StorageQueryException, TenantOrAppNotFoundException {
Expand Down
6 changes: 3 additions & 3 deletions src/main/java/io/supertokens/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,9 @@ private void init() throws IOException, StorageQueryException {
throw new QuitProgramException(e);
}

// loading version file
Version.loadVersion(this, CLIOptions.get(this).getInstallationPath() + "version.yaml");

Logging.info(this, TenantIdentifier.BASE_TENANT, "Completed config.yaml loading.", true);

// loading storage layer
Expand All @@ -167,9 +170,6 @@ private void init() throws IOException, StorageQueryException {
throw new QuitProgramException(e);
}

// loading version file
Version.loadVersion(this, CLIOptions.get(this).getInstallationPath() + "version.yaml");

// init file logging
Logging.initFileLogging(this);

Expand Down
21 changes: 9 additions & 12 deletions src/main/java/io/supertokens/authRecipe/AuthRecipe.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,7 @@
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;
import io.supertokens.pluginInterface.Storage;
import io.supertokens.pluginInterface.StorageUtils;
import io.supertokens.pluginInterface.authRecipe.AuthRecipeStorage;
import io.supertokens.pluginInterface.*;
import io.supertokens.pluginInterface.authRecipe.AuthRecipeUserInfo;
import io.supertokens.pluginInterface.authRecipe.LoginMethod;
import io.supertokens.pluginInterface.authRecipe.sqlStorage.AuthRecipeSQLStorage;
Expand Down Expand Up @@ -126,7 +122,7 @@ public static AuthRecipeUserInfo getUserById(Main main, String userId)

public static AuthRecipeUserInfo getUserById(AppIdentifier appIdentifier, Storage storage, String userId)
throws StorageQueryException {
return StorageUtils.getAuthRecipeStorage(storage).getPrimaryUserById(appIdentifier, userId);
return StorageUtils.getAuthRecipeStorage(storage).getPrimaryUserById(appIdentifier, userId);
}

public static class CreatePrimaryUserResult {
Expand Down Expand Up @@ -325,22 +321,22 @@ public static LinkAccountsResult linkAccounts(Main main, String recipeUserId, St
}

public static LinkAccountsResult linkAccounts(Main main, AppIdentifier appIdentifier,
Storage storage, String _recipeUserId, String _primaryUserId)
Storage storage, String _recipeUserId, String _primaryUserId)
throws StorageQueryException,
AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException,
RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException, InputUserIdIsNotAPrimaryUserException,
UnknownUserIdException, TenantOrAppNotFoundException, FeatureNotEnabledException {

if (Arrays.stream(FeatureFlag.getInstance(main, appIdentifier).getEnabledFeatures())
.noneMatch(t -> t == EE_FEATURES.ACCOUNT_LINKING)) {
.noneMatch(t -> t == EE_FEATURES.ACCOUNT_LINKING || t == EE_FEATURES.MFA)) {
throw new FeatureNotEnabledException(
"Account linking feature is not enabled for this app. Please contact support to enable it.");
}

AuthRecipeSQLStorage authRecipeStorage = StorageUtils.getAuthRecipeStorage(storage);
try {
LinkAccountsResult result = authRecipeStorage.startTransaction(con -> {

LinkAccountsResult result = authRecipeStorage.startTransaction(con -> {
try {
CanLinkAccountsResult canLinkAccounts = canLinkAccountsHelper(con, appIdentifier,
authRecipeStorage, _recipeUserId, _primaryUserId);
Expand Down Expand Up @@ -537,7 +533,7 @@ public static CreatePrimaryUserResult createPrimaryUser(Main main,
FeatureNotEnabledException {

if (Arrays.stream(FeatureFlag.getInstance(main, appIdentifier).getEnabledFeatures())
.noneMatch(t -> t == EE_FEATURES.ACCOUNT_LINKING)) {
.noneMatch(t -> t == EE_FEATURES.ACCOUNT_LINKING || t == EE_FEATURES.MFA)) {
throw new FeatureNotEnabledException(
"Account linking feature is not enabled for this app. Please contact support to enable it.");
}
Expand Down Expand Up @@ -911,7 +907,7 @@ public static void deleteUser(AppIdentifier appIdentifier, Storage storage, Stri
}

private static void deleteNonAuthRecipeUser(TransactionConnection con, AppIdentifier appIdentifier,
Storage storage, String userId)
Storage storage, String userId)
throws StorageQueryException {
StorageUtils.getUserMetadataStorage(storage)
.deleteUserMetadata_Transaction(con, appIdentifier, userId);
Expand All @@ -921,6 +917,7 @@ private static void deleteNonAuthRecipeUser(TransactionConnection con, AppIdenti
.deleteEmailVerificationUserInfo_Transaction(con, appIdentifier, userId);
StorageUtils.getUserRolesStorage(storage)
.deleteAllRolesForUser_Transaction(con, appIdentifier, userId);

StorageUtils.getActiveUsersStorage(storage)
.deleteUserActive_Transaction(con, appIdentifier, userId);
StorageUtils.getTOTPStorage(storage)
Expand Down Expand Up @@ -977,4 +974,4 @@ public UnlinkResult(String userId, boolean wasLinked) {
this.wasLinked = wasLinked;
}
}
}
}
28 changes: 26 additions & 2 deletions src/main/java/io/supertokens/emailpassword/EmailPassword.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +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.emailverification.sqlStorage.EmailVerificationSQLStorage;
import io.supertokens.pluginInterface.exceptions.StorageQueryException;
import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException;
import io.supertokens.pluginInterface.multitenancy.AppIdentifier;
Expand Down Expand Up @@ -110,14 +111,37 @@ public static AuthRecipeUserInfo signUp(TenantIdentifier tenantIdentifier, Stora
.createHashWithSalt(tenantIdentifier.toAppIdentifier(), password);

while (true) {

String userId = Utils.getUUID();
long timeJoined = System.currentTimeMillis();

try {
return StorageUtils.getEmailPasswordStorage(storage)
AuthRecipeUserInfo newUser = StorageUtils.getEmailPasswordStorage(storage)
.signUp(tenantIdentifier, userId, email, hashedPassword, timeJoined);

if (Utils.isFakeEmail(email)) {
try {
EmailVerificationSQLStorage evStorage = StorageUtils.getEmailVerificationStorage(storage);
evStorage.startTransaction(con -> {
try {
evStorage.updateIsEmailVerified_Transaction(tenantIdentifier.toAppIdentifier(), con,
newUser.getSupertokensUserId(), email, true);
evStorage.commitTransaction(con);

return null;
} catch (TenantOrAppNotFoundException e) {
throw new StorageTransactionLogicException(e);
}
});
newUser.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 newUser;
} catch (DuplicateUserIdException ignored) {
// we retry with a new userId (while loop)
}
Expand Down
Loading

0 comments on commit 6dc4f22

Please sign in to comment.