Skip to content

Commit

Permalink
Mfa multitenancy (#841)
Browse files Browse the repository at this point in the history
* fix: multitenancy changes

* fix: mfa cleanup

* fix: mfa cleanup

* fix: test

* fix: api

* fix: mfa multitenancy updates

* fix: tests

* fix: mfa

* fix: tests

* fix: tests

* fix: pr comments

* fix: pr comments

* fix: pr comments

* fix: tests

* fix: tests

* fix: pr comments
  • Loading branch information
sattvikc authored Oct 27, 2023
1 parent e7725f5 commit da5dfa2
Show file tree
Hide file tree
Showing 55 changed files with 1,842 additions and 260 deletions.
3 changes: 2 additions & 1 deletion 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",
"4.1"
]
}
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),
new TotpConfig(false), 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),
new TotpConfig(false), 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),
new TotpConfig(false), null, null,
config
), false);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ public TenantConfig map(ResultSet result) throws StorageQueryException {
new EmailPasswordConfig(result.getBoolean("email_password_enabled")),
new ThirdPartyConfig(result.getBoolean("third_party_enabled"), this.providers),
new PasswordlessConfig(result.getBoolean("passwordless_enabled")),
JsonUtils.stringToJsonObject(result.getString("core_config"))
new TotpConfig(false), // TODO
null, null, JsonUtils.stringToJsonObject(result.getString("core_config"))
// TODO
);
} catch (Exception e) {
throw new StorageQueryException(e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ 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, false, false);
new PasswordlessConfig(true), new TotpConfig(true),
null, null, 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
Expand All @@ -95,7 +96,8 @@ private TenantConfig[] getAllTenantsFromDb() throws StorageQueryException {
new EmailPasswordConfig(true),
new ThirdPartyConfig(true, null),
new PasswordlessConfig(true),
new JsonObject()
new TotpConfig(true),
null, null, new JsonObject()
)
};
}
Expand Down
1 change: 1 addition & 0 deletions src/main/java/io/supertokens/utils/SemVer.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public class SemVer implements Comparable<SemVer> {
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");
public static final SemVer v4_1 = new SemVer("4.1");

final private String version;

Expand Down
3 changes: 2 additions & 1 deletion src/main/java/io/supertokens/webserver/WebserverAPI.java
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,11 @@ public abstract class WebserverAPI extends HttpServlet {
supportedVersions.add(SemVer.v2_21);
supportedVersions.add(SemVer.v3_0);
supportedVersions.add(SemVer.v4_0);
supportedVersions.add(SemVer.v4_1);
}

public static SemVer getLatestCDIVersion() {
return SemVer.v4_0;
return SemVer.v4_1;
}

public SemVer getLatestCDIVersionForRequest(HttpServletRequest req)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,10 @@ public BaseCreateOrUpdate(Main main) {

protected void handle(HttpServletRequest req, TenantIdentifier sourceTenantIdentifier,
TenantIdentifier targetTenantIdentifier, Boolean emailPasswordEnabled,
Boolean thirdPartyEnabled, Boolean passwordlessEnabled, JsonObject coreConfig,
HttpServletResponse resp)
Boolean thirdPartyEnabled, Boolean passwordlessEnabled, Boolean totpEnabled,
boolean hasFirstFactors, String[] firstFactors,
boolean hasDefaultRequiredFactorIds, String[] defaultRequiredFactorIds,
JsonObject coreConfig, HttpServletResponse resp)
throws ServletException, IOException {

TenantConfig tenantConfig = Multitenancy.getTenantInfo(main,
Expand All @@ -63,7 +65,8 @@ protected void handle(HttpServletRequest req, TenantIdentifier sourceTenantIdent
new EmailPasswordConfig(true),
new ThirdPartyConfig(true, null),
new PasswordlessConfig(true),
new JsonObject()
new TotpConfig(true),
null, null, new JsonObject()
);
} else {
// We disable all recipes by default while creating tenant
Expand All @@ -72,7 +75,8 @@ protected void handle(HttpServletRequest req, TenantIdentifier sourceTenantIdent
new EmailPasswordConfig(false),
new ThirdPartyConfig(false, null),
new PasswordlessConfig(false),
new JsonObject()
new TotpConfig(false),
null, null, new JsonObject()
);
}
createdNew = true;
Expand All @@ -84,7 +88,8 @@ protected void handle(HttpServletRequest req, TenantIdentifier sourceTenantIdent
new EmailPasswordConfig(emailPasswordEnabled),
tenantConfig.thirdPartyConfig,
tenantConfig.passwordlessConfig,
tenantConfig.coreConfig
tenantConfig.totpConfig,
tenantConfig.firstFactors, tenantConfig.defaultRequiredFactorIds, tenantConfig.coreConfig
);
}

Expand All @@ -94,7 +99,8 @@ protected void handle(HttpServletRequest req, TenantIdentifier sourceTenantIdent
tenantConfig.emailPasswordConfig,
new ThirdPartyConfig(thirdPartyEnabled, tenantConfig.thirdPartyConfig.providers),
tenantConfig.passwordlessConfig,
tenantConfig.coreConfig
tenantConfig.totpConfig,
tenantConfig.firstFactors, tenantConfig.defaultRequiredFactorIds, tenantConfig.coreConfig
);
}

Expand All @@ -104,7 +110,41 @@ protected void handle(HttpServletRequest req, TenantIdentifier sourceTenantIdent
tenantConfig.emailPasswordConfig,
tenantConfig.thirdPartyConfig,
new PasswordlessConfig(passwordlessEnabled),
tenantConfig.coreConfig
tenantConfig.totpConfig,
tenantConfig.firstFactors, tenantConfig.defaultRequiredFactorIds, tenantConfig.coreConfig
);
}

if (totpEnabled != null) {
tenantConfig = new TenantConfig(
tenantConfig.tenantIdentifier,
tenantConfig.emailPasswordConfig,
tenantConfig.thirdPartyConfig,
tenantConfig.passwordlessConfig,
new TotpConfig(totpEnabled),
tenantConfig.firstFactors, tenantConfig.defaultRequiredFactorIds, tenantConfig.coreConfig
);
}

if (hasFirstFactors) {
tenantConfig = new TenantConfig(
tenantConfig.tenantIdentifier,
tenantConfig.emailPasswordConfig,
tenantConfig.thirdPartyConfig,
tenantConfig.passwordlessConfig,
tenantConfig.totpConfig,
firstFactors, tenantConfig.defaultRequiredFactorIds, tenantConfig.coreConfig
);
}

if (hasDefaultRequiredFactorIds) {
tenantConfig = new TenantConfig(
tenantConfig.tenantIdentifier,
tenantConfig.emailPasswordConfig,
tenantConfig.thirdPartyConfig,
tenantConfig.passwordlessConfig,
tenantConfig.totpConfig,
tenantConfig.firstFactors, defaultRequiredFactorIds, tenantConfig.coreConfig
);
}

Expand All @@ -115,7 +155,8 @@ protected void handle(HttpServletRequest req, TenantIdentifier sourceTenantIdent
tenantConfig.emailPasswordConfig,
tenantConfig.thirdPartyConfig,
tenantConfig.passwordlessConfig,
coreConfig
tenantConfig.totpConfig,
tenantConfig.firstFactors, tenantConfig.defaultRequiredFactorIds, coreConfig
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,22 @@

package io.supertokens.webserver.api.multitenancy;

import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import io.supertokens.Main;
import io.supertokens.multitenancy.exception.BadPermissionException;
import io.supertokens.pluginInterface.multitenancy.TenantIdentifier;
import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException;
import io.supertokens.utils.SemVer;
import io.supertokens.webserver.InputParser;
import io.supertokens.webserver.Utils;
import io.supertokens.webserver.api.multitenancy.BaseCreateOrUpdate;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

public class CreateOrUpdateAppAPI extends BaseCreateOrUpdate {

Expand Down Expand Up @@ -56,6 +59,38 @@ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IO
Boolean passwordlessEnabled = InputParser.parseBooleanOrThrowError(input, "passwordlessEnabled", true);
JsonObject coreConfig = InputParser.parseJsonObjectOrThrowError(input, "coreConfig", true);

Boolean totpEnabled = null;
String[] firstFactors = null;
boolean hasFirstFactors = false;
String[] defaultRequiredFactorIds = null;
boolean hasDefaultRequiredFactorIds = false;

if (getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v4_1)) {
totpEnabled = InputParser.parseBooleanOrThrowError(input, "totpEnabled", true);
hasFirstFactors = input.has("firstFactors");
if (hasFirstFactors && !input.get("firstFactors").isJsonNull()) {
JsonArray firstFactorsArr = InputParser.parseArrayOrThrowError(input, "firstFactors", true);
firstFactors = new String[firstFactorsArr.size()];
for (int i = 0; i < firstFactors.length; i++) {
firstFactors[i] = InputParser.parseStringFromElementOrThrowError(firstFactorsArr.get(i), "firstFactors", false);
}
if (firstFactors.length != new HashSet<>(Arrays.asList(firstFactors)).size()) {
throw new ServletException(new BadRequestException("firstFactors input should not contain duplicate values"));
}
}
hasDefaultRequiredFactorIds = input.has("defaultRequiredFactorIds");
if (hasDefaultRequiredFactorIds && !input.get("defaultRequiredFactorIds").isJsonNull()) {
JsonArray defaultRequiredFactorIdsArr = InputParser.parseArrayOrThrowError(input, "defaultRequiredFactorIds", true);
defaultRequiredFactorIds = new String[defaultRequiredFactorIdsArr.size()];
for (int i = 0; i < defaultRequiredFactorIds.length; i++) {
defaultRequiredFactorIds[i] = InputParser.parseStringFromElementOrThrowError(defaultRequiredFactorIdsArr.get(i), "defaultRequiredFactorIds", false);
}
if (defaultRequiredFactorIds.length != new HashSet<>(Arrays.asList(defaultRequiredFactorIds)).size()) {
throw new ServletException(new BadRequestException("defaultRequiredFactorIds input should not contain duplicate values"));
}
}
}

TenantIdentifier sourceTenantIdentifier;
try {
sourceTenantIdentifier = this.getTenantIdentifierWithStorageFromRequest(req);
Expand All @@ -66,7 +101,9 @@ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IO
super.handle(
req, sourceTenantIdentifier,
new TenantIdentifier(sourceTenantIdentifier.getConnectionUriDomain(), appId, null),
emailPasswordEnabled, thirdPartyEnabled, passwordlessEnabled, coreConfig, resp);
emailPasswordEnabled, thirdPartyEnabled, passwordlessEnabled,
totpEnabled, hasFirstFactors, firstFactors, hasDefaultRequiredFactorIds, defaultRequiredFactorIds,
coreConfig, resp);

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,22 @@

package io.supertokens.webserver.api.multitenancy;

import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import io.supertokens.Main;
import io.supertokens.pluginInterface.multitenancy.TenantIdentifier;
import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException;
import io.supertokens.utils.SemVer;
import io.supertokens.webserver.InputParser;
import io.supertokens.webserver.Utils;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

public class CreateOrUpdateConnectionUriDomainAPI extends BaseCreateOrUpdate {

Expand Down Expand Up @@ -54,6 +59,38 @@ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IO
Boolean passwordlessEnabled = InputParser.parseBooleanOrThrowError(input, "passwordlessEnabled", true);
JsonObject coreConfig = InputParser.parseJsonObjectOrThrowError(input, "coreConfig", true);

Boolean totpEnabled = null;
String[] firstFactors = null;
boolean hasFirstFactors = false;
String[] defaultRequiredFactorIds = null;
boolean hasDefaultRequiredFactorIds = false;

if (getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v4_1)) {
totpEnabled = InputParser.parseBooleanOrThrowError(input, "totpEnabled", true);
hasFirstFactors = input.has("firstFactors");
if (hasFirstFactors && !input.get("firstFactors").isJsonNull()) {
JsonArray firstFactorsArr = InputParser.parseArrayOrThrowError(input, "firstFactors", true);
firstFactors = new String[firstFactorsArr.size()];
for (int i = 0; i < firstFactors.length; i++) {
firstFactors[i] = InputParser.parseStringFromElementOrThrowError(firstFactorsArr.get(i), "firstFactors", false);
}
if (firstFactors.length != new HashSet<>(Arrays.asList(firstFactors)).size()) {
throw new ServletException(new BadRequestException("firstFactors input should not contain duplicate values"));
}
}
hasDefaultRequiredFactorIds = input.has("defaultRequiredFactorIds");
if (hasDefaultRequiredFactorIds && !input.get("defaultRequiredFactorIds").isJsonNull()) {
JsonArray defaultRequiredFactorIdsArr = InputParser.parseArrayOrThrowError(input, "defaultRequiredFactorIds", true);
defaultRequiredFactorIds = new String[defaultRequiredFactorIdsArr.size()];
for (int i = 0; i < defaultRequiredFactorIds.length; i++) {
defaultRequiredFactorIds[i] = InputParser.parseStringFromElementOrThrowError(defaultRequiredFactorIdsArr.get(i), "defaultRequiredFactorIds", false);
}
if (defaultRequiredFactorIds.length != new HashSet<>(Arrays.asList(defaultRequiredFactorIds)).size()) {
throw new ServletException(new BadRequestException("defaultRequiredFactorIds input should not contain duplicate values"));
}
}
}

TenantIdentifier sourceTenantIdentifier;
try {
sourceTenantIdentifier = this.getTenantIdentifierWithStorageFromRequest(req);
Expand All @@ -64,7 +101,9 @@ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IO
super.handle(
req, sourceTenantIdentifier,
new TenantIdentifier(connectionUriDomain, null, null),
emailPasswordEnabled, thirdPartyEnabled, passwordlessEnabled, coreConfig, resp);
emailPasswordEnabled, thirdPartyEnabled, passwordlessEnabled,
totpEnabled, hasFirstFactors, firstFactors, hasDefaultRequiredFactorIds, defaultRequiredFactorIds,
coreConfig, resp);

}
}
Loading

0 comments on commit da5dfa2

Please sign in to comment.