Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adds new config #639

Merged
merged 1 commit into from
Apr 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -125,3 +125,8 @@ core_config_version: 0
# (OPTIONAL | Default: null). Regex for denying requests from IP addresses that match with the value. Comment this
# value to deny no IP address.
# ip_deny_regex:

# (OPTIONAL | Default: null). This is used when deploying the core in SuperTokens SaaS infrastructure. If set, limits
# what database information is shown to / modifiable by the dev when they query the core to get the information about
# their tenants. It only exposes that information when this key is used instead of the regular api_keys config.
# supertokens_saas_secret:
5 changes: 5 additions & 0 deletions devConfig.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,8 @@ disable_telemetry: true
# (OPTIONAL | Default: null). Regex for denying requests from IP addresses that match with the value. Comment this
# value to deny no IP address.
# ip_deny_regex:

# (OPTIONAL | Default: null). This is used when deploying the core in SuperTokens SaaS infrastructure. If set, limits
# what database information is shown to / modifiable by the dev when they query the core to get the information about
# their tenants. It only exposes that information when this key is used instead of the regular api_keys config.
# supertokens_saas_secret:
36 changes: 36 additions & 0 deletions src/main/java/io/supertokens/config/CoreConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,9 @@ public class CoreConfig {
@JsonProperty
private String ip_deny_regex = null;

@JsonProperty
private String supertokens_saas_secret = null;

private Set<LOG_LEVEL> allowedLogLevels = null;

public String getIpAllowRegex() {
Expand Down Expand Up @@ -330,6 +333,13 @@ public String[] getAPIKeys() {
return api_keys.trim().replaceAll("\\s", "").split(",");
}

public String getSuperTokensSaaSSecret() {
if (supertokens_saas_secret != null) {
return supertokens_saas_secret.trim();
}
return null;
}

public int getPort(Main main) {
Integer cliPort = CLIOptions.get(main).getPort();
if (cliPort != null) {
Expand Down Expand Up @@ -440,6 +450,27 @@ void validate(Main main) throws InvalidConfigException {
}
}

if (supertokens_saas_secret != null) {
if (api_keys == null) {
throw new InvalidConfigException(
"supertokens_saas_secret can only be used when api_key is also defined");
}
if (supertokens_saas_secret.length() < 40) {
throw new InvalidConfigException(
"supertokens_saas_secret is too short. Please use at least 40 characters");
}
for (int y = 0; y < supertokens_saas_secret.length(); y++) {
char currChar = supertokens_saas_secret.charAt(y);
if (!(currChar == '=' || currChar == '-' || (currChar >= '0' && currChar <= '9')
|| (currChar >= 'a' && currChar <= 'z') || (currChar >= 'A' && currChar <= 'Z'))) {
throw new InvalidConfigException(
"Invalid characters in supertokens_saas_secret key. Please only use '=', '-' and " +
"alpha-numeric (including"
+ " capitals)");
}
}
}

if (!password_hashing_alg.equalsIgnoreCase("ARGON2") && !password_hashing_alg.equalsIgnoreCase("BCRYPT")) {
throw new InvalidConfigException("'password_hashing_alg' must be one of 'ARGON2' or 'BCRYPT'");
}
Expand Down Expand Up @@ -556,6 +587,11 @@ static void assertThatCertainConfigIsNotSetForAppOrTenants(JsonObject config) th
throw new InvalidConfigException(
"webserver_https_enabled can only be set via the core's base config setting");
}

if (config.has("supertokens_saas_secret")) {
throw new InvalidConfigException(
"supertokens_saas_secret can only be set via the core's base config setting");
}
}

void assertThatConfigFromSameAppIdAreNotConflicting(CoreConfig other) throws InvalidConfigException {
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/io/supertokens/inmemorydb/Start.java
Original file line number Diff line number Diff line change
Expand Up @@ -2245,6 +2245,11 @@ public void modifyConfigToAddANewUserPoolForTesting(JsonObject config,
// do nothing cause we have only one in mem db.
}

@Override
public String[] getProtectedConfigsFromSuperTokensSaaSUsers() {
return new String[0];
}

@Override
public void createTenant(TenantConfig config) throws
DuplicateTenantException {
Expand Down
23 changes: 21 additions & 2 deletions src/main/java/io/supertokens/multitenancy/Multitenancy.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,26 @@
import io.supertokens.storageLayer.StorageLayer;
import io.supertokens.thirdparty.InvalidProviderConfigException;
import io.supertokens.thirdparty.ThirdParty;
import org.jetbrains.annotations.TestOnly;

import java.io.IOException;
import java.util.*;

public class Multitenancy extends ResourceDistributor.SingletonResource {

@TestOnly
public static boolean addNewOrUpdateAppOrTenant(Main main, TenantIdentifier sourceTenant, TenantConfig newTenant)
throws DeletionInProgressException, CannotModifyBaseConfigException, BadPermissionException,
StorageQueryException, FeatureNotEnabledException, IOException, InvalidConfigException,
InvalidProviderConfigException, TenantOrAppNotFoundException {
return addNewOrUpdateAppOrTenant(main, sourceTenant, newTenant, false);
}

public static boolean addNewOrUpdateAppOrTenant(Main main, TenantIdentifier sourceTenant, TenantConfig newTenant,
boolean shouldPreventDbConfigUpdate)
throws DeletionInProgressException, CannotModifyBaseConfigException, BadPermissionException,
StorageQueryException, FeatureNotEnabledException, IOException, InvalidConfigException,
InvalidProviderConfigException, TenantOrAppNotFoundException {

// TODO: adding a new tenant is not thread safe here - for example, one can add a new connectionuridomain
// such that they both point to the same user pool ID by trying to add them in parallel. This is not such
Expand Down Expand Up @@ -104,6 +114,15 @@ public static boolean addNewOrUpdateAppOrTenant(Main main, TenantIdentifier sour

// we check if the core config provided is correct
{
if (shouldPreventDbConfigUpdate) {
for (String s : StorageLayer.getStorage(new TenantIdentifier(null, null, null), main)
.getProtectedConfigsFromSuperTokensSaaSUsers()) {
if (newTenant.coreConfig.has(s)) {
throw new BadPermissionException("Not allowed to modify DB related configs.");
}
}
}

TenantConfig[] existingTenants = getAllTenants(new TenantIdentifier(null, null, null), main);
boolean updated = false;
for (int i = 0; i < existingTenants.length; i++) {
Expand Down Expand Up @@ -147,7 +166,7 @@ public static boolean addNewOrUpdateAppOrTenant(Main main, TenantIdentifier sour
.addTenantIdInUserPool(newTenant.tenantIdentifier);
} catch (TenantOrAppNotFoundException e) {
// it should never come here, since we just added the tenant above.. but just in case.
return addNewOrUpdateAppOrTenant(main, sourceTenant, newTenant);
return addNewOrUpdateAppOrTenant(main, sourceTenant, newTenant, shouldPreventDbConfigUpdate);
}
return true;
} catch (DuplicateTenantException e) {
Expand All @@ -166,7 +185,7 @@ public static boolean addNewOrUpdateAppOrTenant(Main main, TenantIdentifier sour
} 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, sourceTenant, newTenant);
return addNewOrUpdateAppOrTenant(main, sourceTenant, newTenant, shouldPreventDbConfigUpdate);
} catch (DuplicateTenantException ex) {
// we treat this as a success
return false;
Expand Down
52 changes: 40 additions & 12 deletions src/main/java/io/supertokens/webserver/WebserverAPI.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,14 @@
package io.supertokens.webserver;

import com.google.gson.JsonElement;
import io.supertokens.AppIdentifierWithStorageAndUserIdMapping;
import io.supertokens.Main;
import io.supertokens.TenantIdentifierWithStorageAndUserIdMapping;
import io.supertokens.config.Config;
import io.supertokens.exceptions.QuitProgramException;
import io.supertokens.featureflag.exceptions.FeatureNotEnabledException;
import io.supertokens.multitenancy.exception.BadPermissionException;
import io.supertokens.output.Logging;
import io.supertokens.utils.SemVer;
import io.supertokens.pluginInterface.Storage;
import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException;
import io.supertokens.pluginInterface.exceptions.StorageQueryException;
Expand All @@ -32,9 +33,8 @@
import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage;
import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException;
import io.supertokens.storageLayer.StorageLayer;
import io.supertokens.AppIdentifierWithStorageAndUserIdMapping;
import io.supertokens.TenantIdentifierWithStorageAndUserIdMapping;
import io.supertokens.useridmapping.UserIdType;
import io.supertokens.utils.SemVer;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
Expand Down Expand Up @@ -149,6 +149,8 @@ protected boolean versionNeeded(HttpServletRequest req) {
private void assertThatAPIKeyCheckPasses(HttpServletRequest req) throws ServletException,
TenantOrAppNotFoundException {
String apiKey = req.getHeader("api-key");

// first we try the normal API key
String[] keys = Config.getConfig(
new TenantIdentifier(getConnectionUriDomain(req), getAppId(req), getTenantId(req)),
this.main).getAPIKeys();
Expand All @@ -161,9 +163,27 @@ private void assertThatAPIKeyCheckPasses(HttpServletRequest req) throws ServletE
for (String key : keys) {
isAuthorised = isAuthorised || key.equals(apiKey);
}
if (!isAuthorised) {
if (isAuthorised) {
return;
}
}

// if the normal API key did not exist, or did not match the api key from the header, we try the
// supertokens_saas_secret
String superTokensSaaSSecret = Config.getConfig(new TenantIdentifier(null, null, null), this.main)
.getSuperTokensSaaSSecret();
if (superTokensSaaSSecret != null) {
if (apiKey == null) {
throw new ServletException(new APIKeyUnauthorisedException());
}
if (apiKey.equals(superTokensSaaSSecret)) {
return;
}
}

// if either were defined, and both failed, we throw an exception
if (superTokensSaaSSecret != null || keys != null) {
throw new ServletException(new APIKeyUnauthorisedException());
}
}

Expand Down Expand Up @@ -247,14 +267,16 @@ protected TenantIdentifier getTenantIdentifierFromRequest(HttpServletRequest req

protected TenantIdentifierWithStorage getTenantIdentifierWithStorageFromRequest(HttpServletRequest req)
throws TenantOrAppNotFoundException {
TenantIdentifier tenantIdentifier = new TenantIdentifier(this.getConnectionUriDomain(req), this.getAppId(req), this.getTenantId(req));
TenantIdentifier tenantIdentifier = new TenantIdentifier(this.getConnectionUriDomain(req), this.getAppId(req),
this.getTenantId(req));
Storage storage = StorageLayer.getStorage(tenantIdentifier, main);
return tenantIdentifier.withStorage(storage);
}

protected AppIdentifierWithStorage getAppIdentifierWithStorage(HttpServletRequest req)
throws TenantOrAppNotFoundException {
TenantIdentifier tenantIdentifier = new TenantIdentifier(this.getConnectionUriDomain(req), this.getAppId(req), this.getTenantId(req));
TenantIdentifier tenantIdentifier = new TenantIdentifier(this.getConnectionUriDomain(req), this.getAppId(req),
this.getTenantId(req));

Storage storage = StorageLayer.getStorage(tenantIdentifier, main);
Storage[] storages = StorageLayer.getStoragesForApp(main, tenantIdentifier.toAppIdentifier());
Expand All @@ -263,7 +285,8 @@ protected AppIdentifierWithStorage getAppIdentifierWithStorage(HttpServletReques
storage, storages);
}

protected AppIdentifierWithStorage getAppIdentifierWithStorageFromRequestAndEnforcePublicTenant(HttpServletRequest req)
protected AppIdentifierWithStorage getAppIdentifierWithStorageFromRequestAndEnforcePublicTenant(
HttpServletRequest req)
throws TenantOrAppNotFoundException, BadPermissionException {
TenantIdentifier tenantIdentifier = new TenantIdentifier(this.getConnectionUriDomain(req), this.getAppId(req),
this.getTenantId(req));
Expand All @@ -278,18 +301,23 @@ protected AppIdentifierWithStorage getAppIdentifierWithStorageFromRequestAndEnfo
storage, storages);
}

protected TenantIdentifierWithStorageAndUserIdMapping getTenantIdentifierWithStorageAndUserIdMappingFromRequest(HttpServletRequest req, String userId, UserIdType userIdType)
protected TenantIdentifierWithStorageAndUserIdMapping getTenantIdentifierWithStorageAndUserIdMappingFromRequest(
HttpServletRequest req, String userId, UserIdType userIdType)
throws StorageQueryException, TenantOrAppNotFoundException, UnknownUserIdException {
TenantIdentifier tenantIdentifier = new TenantIdentifier(this.getConnectionUriDomain(req), this.getAppId(req), this.getTenantId(req));
return StorageLayer.getTenantIdentifierWithStorageAndUserIdMappingForUser(main, tenantIdentifier, userId, userIdType);
TenantIdentifier tenantIdentifier = new TenantIdentifier(this.getConnectionUriDomain(req), this.getAppId(req),
this.getTenantId(req));
return StorageLayer.getTenantIdentifierWithStorageAndUserIdMappingForUser(main, tenantIdentifier, userId,
userIdType);
}

protected AppIdentifierWithStorageAndUserIdMapping getAppIdentifierWithStorageAndUserIdMappingFromRequest(HttpServletRequest req, String userId, UserIdType userIdType)
protected AppIdentifierWithStorageAndUserIdMapping getAppIdentifierWithStorageAndUserIdMappingFromRequest(
HttpServletRequest req, String userId, UserIdType userIdType)
throws StorageQueryException, TenantOrAppNotFoundException, UnknownUserIdException {
// This function uses storage of the tenent from which the request came from as a priorityStorage
// while searching for the user across all storages for the app
AppIdentifierWithStorage appIdentifierWithStorage = getAppIdentifierWithStorage(req);
return StorageLayer.getAppIdentifierWithStorageAndUserIdMappingForUserWithPriorityForTenantStorage(main, appIdentifierWithStorage, appIdentifierWithStorage.getStorage(), userId, userIdType);
return StorageLayer.getAppIdentifierWithStorageAndUserIdMappingForUserWithPriorityForTenantStorage(main,
appIdentifierWithStorage, appIdentifierWithStorage.getStorage(), userId, userIdType);
}

@Override
Expand Down
Loading