Skip to content

Commit

Permalink
Merge branch 'master' into feat/mfa
Browse files Browse the repository at this point in the history
  • Loading branch information
KShivendu committed Sep 27, 2023
2 parents 6e5f1ec + a378465 commit 2986794
Show file tree
Hide file tree
Showing 32 changed files with 676 additions and 246 deletions.
50 changes: 50 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,56 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [4.0.0] - 2023-09-19

- Adds support for account linking
- Adds `AuthRecipeUserInfo` class and removes `UserInfo` from emailpassword, passwordless and thirdparty.
- ActiveUsersStorage interface changes
- Removes `deleteUserActive`
- Adds `deleteUserActive_Transaction`
- Adds `countUsersThatHaveMoreThanOneLoginMethodAndActiveSince`
- EmailPasswordStorage interfaces changes
- Removes `deleteEmailPasswordUser`, `getUserInfoUsingId`, `getUserInfoUsingEmail`
- Changes return type of `signUp` from `UserInfo` to `AuthRecipeUserInfo`
- Changes `PasswordResetTokenInfo` to accept additional param `email`
- EmailPasswordSQLStorage interface changes
- Removes `getUserInfoUsingId_Transaction`
- Adds `deleteEmailPasswordUser_Transaction`
- EmailVerificationStorage interface changes
- Removes `deleteEmailVerificationUserInfo`
- EmailVerificationSQLStorage interface changes
- Adds `deleteEmailVerificationUserInfo_Transaction`
- MultitenancyStorage interface changes
- Removes `addUserIdToTenant`
- MultitenancySQLStorage interface changes
- Adds `addUserIdToTenant_Transaction`
- PasswordlessStorage interface changes
- Changes return type of `createUser` from `UserInfo` to `AuthRecipeUserInfo`
- Removes `deletePasswordlessUser`, `getUserById`, `getUserByEmail`, `getUserByPhoneNumber`
- PasswordlessSQLStorage interface changes
- Adds `deletePasswordlessUser_Transaction`
- SessionInfo accepts additional parameter `recipeUserId`
- SessionSQLStorage interface changes
- Adds `deleteSessionsOfUser_Transaction`
- ThirdPartyStorage interface changes
- Removes `deleteThirdPartyUser`, `getThirdPartyUserInfoUsingId`, `getThirdPartyUserInfoUsingId`, `getThirdPartyUsersByEmail`
- Changes return type of `signUp` from `UserInfo` to `AuthRecipeUserInfo`
- ThirdPartySQLStorage interface changes
- Adds `deleteThirdPartyUser_Transaction`
- Removes `getUserInfoUsingId_Transaction`
- UserIdMappingSQLStorage interface changes
- Adds `getUserIdMapping_Transaction`, `getUserIdMapping_Transaction`
- UserMetadataSQLStorage interface changes
- Adds `deleteUserMetadata_Transaction`
- UserRolesStorage interface changes
- Removes `deleteAllRolesForUser`
- UserRolesSQLStorage interface changes
- Adds `deleteAllRolesForUser_Transaction`

## [3.0.1] - 2023-07-04

- Updates `TenantConfig` toJson function to protect core config as well.

## [3.0.0] - 2023-06-02

- Adds support for multi-tenancy
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ plugins {
id 'java-library'
}

version = "3.0.0"
version = "4.0.0"

repositories {
mavenCentral()
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import io.supertokens.pluginInterface.exceptions.StorageQueryException;
import io.supertokens.pluginInterface.multitenancy.AppIdentifier;
import io.supertokens.pluginInterface.nonAuthRecipe.NonAuthRecipeStorage;
import io.supertokens.pluginInterface.sqlStorage.TransactionConnection;

public interface ActiveUsersStorage extends NonAuthRecipeStorage {
/* Update the last active time of a user to now */
Expand All @@ -24,5 +25,8 @@ public interface ActiveUsersStorage extends NonAuthRecipeStorage {
int countUsersEnabledMfaAndActiveSince(AppIdentifier appIdentifier, long time) throws StorageQueryException;

/* Delete a user from active users table */
void deleteUserActive(AppIdentifier appIdentifier, String userId) throws StorageQueryException;
void deleteUserActive_Transaction(TransactionConnection con, AppIdentifier appIdentifier, String userId)
throws StorageQueryException;

int countUsersThatHaveMoreThanOneLoginMethodAndActiveSince(AppIdentifier appIdentifier, long sinceTime) throws StorageQueryException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public enum RECIPE_ID {
EMAIL_PASSWORD("emailpassword"), THIRD_PARTY("thirdparty"), SESSION("session"),
EMAIL_VERIFICATION("emailverification"), JWT("jwt"), PASSWORDLESS("passwordless"), USER_METADATA("usermetadata"),
USER_ROLES("userroles"), USER_ID_MAPPING("useridmapping"), DASHBOARD("dashboard"), TOTP("totp"),
MULTITENANCY("multitenancy"), MFA("mfa");
MULTITENANCY("multitenancy"), ACCOUNT_LINKING("accountlinking"), MFA("mfa");

private final String name;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,24 @@ AuthRecipeUserInfo[] getUsers(TenantIdentifier tenantIdentifier, @Nonnull Intege
boolean doesUserIdExist(AppIdentifier appIdentifier, String userId) throws StorageQueryException;

boolean doesUserIdExist(TenantIdentifier tenantIdentifierIdentifier, String userId) throws StorageQueryException;

AuthRecipeUserInfo getPrimaryUserById(AppIdentifier appIdentifier, String userId) throws StorageQueryException;

String getPrimaryUserIdStrForUserId(AppIdentifier appIdentifier, String userId) throws StorageQueryException;

AuthRecipeUserInfo[] listPrimaryUsersByEmail(TenantIdentifier tenantIdentifier, String email)
throws StorageQueryException;

AuthRecipeUserInfo[] listPrimaryUsersByPhoneNumber(TenantIdentifier tenantIdentifier, String phoneNumber)
throws StorageQueryException;

AuthRecipeUserInfo[] listPrimaryUsersByThirdPartyInfo(AppIdentifier appIdentifier, String thirdPartyId, String thirdPartyUserId)
throws StorageQueryException;

AuthRecipeUserInfo getPrimaryUserByThirdPartyInfo(TenantIdentifier tenantIdentifier, String thirdPartyId,
String thirdPartyUserId) throws StorageQueryException;

boolean checkIfUsesAccountLinking(AppIdentifier appIdentifier) throws StorageQueryException;

int getUsersCountWithMoreThanOneLoginMethod(AppIdentifier appIdentifier) throws StorageQueryException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,232 @@

package io.supertokens.pluginInterface.authRecipe;

import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import io.supertokens.pluginInterface.RECIPE_ID;

public abstract class AuthRecipeUserInfo {
import java.util.*;

public String id;
public class AuthRecipeUserInfo {

private final String id;

private String externalUserId = null;

public boolean isPrimaryUser;

public LoginMethod[] loginMethods;

public Set<String> tenantIds;

public long timeJoined;

public final String[] tenantIds;
private boolean didCallSetExternalUserId = false;

public void setExternalUserId(String externalUserId) {
didCallSetExternalUserId = true;
this.externalUserId = externalUserId;
for (LoginMethod loginMethod : this.loginMethods) {
if (loginMethod.getSupertokensUserId().equals(this.id)) {
loginMethod.setExternalUserId(externalUserId);
}
}
}

public AuthRecipeUserInfo(String id, long timeJoined, String[] tenantIds) {
public String getSupertokensOrExternalUserId() {
assert (this.didCallSetExternalUserId);

if (this.externalUserId != null) {
return this.externalUserId;
}
return this.id;
}

public String getSupertokensUserId() {
return this.id;
}

protected AuthRecipeUserInfo(String id, Boolean isPrimaryUser, LoginMethod loginMethods) {
assert (isPrimaryUser != null);
this.id = id;
this.timeJoined = timeJoined;
this.tenantIds = tenantIds;
this.isPrimaryUser = isPrimaryUser;
this.loginMethods = new LoginMethod[]{loginMethods};
this.timeJoined = loginMethods.timeJoined;
this.tenantIds = new HashSet<>();
this.tenantIds.addAll(loginMethods.tenantIds);
}

public static AuthRecipeUserInfo create(String id, Boolean isPrimaryUser, LoginMethod loginMethod) {
assert (isPrimaryUser != null);
return new AuthRecipeUserInfo(id, isPrimaryUser, loginMethod);
}

public void addLoginMethod(LoginMethod loginMethod) {
for (LoginMethod method : this.loginMethods) {
if (method.equals(loginMethod)) {
return;
}
}
LoginMethod[] newLoginMethods = new LoginMethod[this.loginMethods.length + 1];
System.arraycopy(this.loginMethods, 0, newLoginMethods, 0, this.loginMethods.length);
newLoginMethods[this.loginMethods.length] = loginMethod;
this.loginMethods = Arrays.stream(newLoginMethods).sorted((o1, o2) -> {
if (o1.timeJoined < o2.timeJoined) {
return -1;
} else if (o1.timeJoined > o2.timeJoined) {
return 1;
}
return 0;
}).toArray(LoginMethod[]::new);
if (timeJoined > loginMethod.timeJoined) {
this.timeJoined = loginMethod.timeJoined;
}

this.tenantIds.addAll(loginMethod.tenantIds);
}

public abstract RECIPE_ID getRecipeId();
@Override
public boolean equals(Object other) {
if (!(other instanceof AuthRecipeUserInfo)) {
return false;
}
AuthRecipeUserInfo otherUser = (AuthRecipeUserInfo) other;
return this.id.equals(otherUser.id) && this.isPrimaryUser == otherUser.isPrimaryUser
&& this.timeJoined == otherUser.timeJoined && Arrays.equals(this.loginMethods, otherUser.loginMethods)
&& this.tenantIds.equals(otherUser.tenantIds);
}

@Override
public int hashCode() {
// combine hash codes of all fields
// We multiply with 31 because it's a prime number.
int hashCode = this.id.hashCode();
hashCode = 31 * hashCode + Boolean.hashCode(this.isPrimaryUser);
hashCode = 31 * hashCode + Long.hashCode(this.timeJoined);
hashCode = 31 * hashCode + Arrays.hashCode(this.loginMethods);
hashCode = 31 * hashCode + this.tenantIds.hashCode();
return hashCode;
}

public JsonObject toJson() {
if (!didCallSetExternalUserId) {
throw new RuntimeException("Found a bug: Did you forget to call setExternalUserId?");
}
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("id", getSupertokensOrExternalUserId());
jsonObject.addProperty("isPrimaryUser", this.isPrimaryUser);
JsonArray tenantIds = new JsonArray();
for (String tenant : this.tenantIds) {
tenantIds.add(new JsonPrimitive(tenant));
}
jsonObject.add("tenantIds", tenantIds);
jsonObject.addProperty("timeJoined", this.timeJoined);

// now we add unique emails, phone numbers and third party across all login methods
Set<String> emails = new HashSet<>();
Set<String> phoneNumbers = new HashSet<>();
Set<LoginMethod.ThirdParty> thirdParty = new HashSet<>();
for (LoginMethod loginMethod : this.loginMethods) {
if (loginMethod.email != null) {
emails.add(loginMethod.email);
}
if (loginMethod.phoneNumber != null) {
phoneNumbers.add(loginMethod.phoneNumber);
}
if (loginMethod.thirdParty != null) {
thirdParty.add(loginMethod.thirdParty);
}
}
JsonArray emailsJson = new JsonArray();
for (String email : emails) {
emailsJson.add(new JsonPrimitive(email));
}
jsonObject.add("emails", emailsJson);
JsonArray phoneNumbersJson = new JsonArray();
for (String phoneNumber : phoneNumbers) {
phoneNumbersJson.add(new JsonPrimitive(phoneNumber));
}
jsonObject.add("phoneNumbers", phoneNumbersJson);
JsonArray thirdPartyJson = new JsonArray();
for (LoginMethod.ThirdParty tpInfo : thirdParty) {
JsonObject j = new JsonObject();
j.addProperty("id", tpInfo.id);
j.addProperty("userId", tpInfo.userId);
thirdPartyJson.add(j);
}
jsonObject.add("thirdParty", thirdPartyJson);

// now we add login methods..
JsonArray loginMethodsArr = new JsonArray();
for (LoginMethod lM : this.loginMethods) {
JsonObject lMJsonObject = new JsonObject();
JsonArray lMTenantIds = new JsonArray();
for (String tenant : lM.tenantIds) {
lMTenantIds.add(new JsonPrimitive(tenant));
}
lMJsonObject.add("tenantIds", lMTenantIds);
lMJsonObject.addProperty("recipeUserId", lM.getSupertokensOrExternalUserId());
lMJsonObject.addProperty("verified", lM.verified);
lMJsonObject.addProperty("timeJoined", lM.timeJoined);
lMJsonObject.addProperty("recipeId", lM.recipeId.toString());
if (lM.email != null) {
lMJsonObject.addProperty("email", lM.email);
}
if (lM.phoneNumber != null) {
lMJsonObject.addProperty("phoneNumber", lM.phoneNumber);
}
if (lM.thirdParty != null) {
JsonObject thirdPartyJsonObject = new JsonObject();
thirdPartyJsonObject.addProperty("id", lM.thirdParty.id);
thirdPartyJsonObject.addProperty("userId", lM.thirdParty.userId);
lMJsonObject.add("thirdParty", thirdPartyJsonObject);
}
loginMethodsArr.add(lMJsonObject);
}
jsonObject.add("loginMethods", loginMethodsArr);
return jsonObject;
}

public JsonObject toJsonWithoutAccountLinking() {
if (!didCallSetExternalUserId) {
throw new RuntimeException("Found a bug: Did you forget to call setExternalUserId?");
}
// this is for older CDI versions.
if (this.loginMethods.length != 1) {
throw new IllegalStateException(
"Please use a CDI version that is greater than the one in which account linking feature was " +
"enabled.");
}
LoginMethod loginMethod = loginMethods[0];
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("id", loginMethod.getSupertokensOrExternalUserId());
jsonObject.addProperty("timeJoined", loginMethod.timeJoined);
JsonArray tenantIds = new JsonArray();
for (String tenant : loginMethod.tenantIds) {
tenantIds.add(new JsonPrimitive(tenant));
}
jsonObject.add("tenantIds", tenantIds);
if (loginMethod.recipeId == RECIPE_ID.EMAIL_PASSWORD) {
jsonObject.addProperty("email", loginMethod.email);
} else if (loginMethod.recipeId == RECIPE_ID.THIRD_PARTY) {
jsonObject.addProperty("email", loginMethod.email);
JsonObject thirdPartyJson = new JsonObject();
assert loginMethod.thirdParty != null;
thirdPartyJson.addProperty("id", loginMethod.thirdParty.id);
thirdPartyJson.addProperty("userId", loginMethod.thirdParty.userId);
jsonObject.add("thirdParty", thirdPartyJson);
} else if (loginMethod.recipeId == RECIPE_ID.PASSWORDLESS) {
if (loginMethod.email != null) {
jsonObject.addProperty("email", loginMethod.email);
}
if (loginMethod.phoneNumber != null) {
jsonObject.addProperty("phoneNumber", loginMethod.phoneNumber);
}
} else {
throw new UnsupportedOperationException("Please search for bugs");
}

return jsonObject;
}
}
Loading

0 comments on commit 2986794

Please sign in to comment.