diff --git a/.circleci/Dockerfile-ubuntu22 b/.circleci/Dockerfile-ubuntu22
new file mode 100644
index 000000000..1f60f940d
--- /dev/null
+++ b/.circleci/Dockerfile-ubuntu22
@@ -0,0 +1,57 @@
+FROM ubuntu:22.04
+
+RUN apt-get update && apt-get upgrade -y
+
+RUN apt-get install build-essential -y
+
+RUN echo "mysql-server mysql-server/root_password password root" | debconf-set-selections
+
+RUN echo "mysql-server mysql-server/root_password_again password root" | debconf-set-selections
+
+RUN apt install mysql-server -y
+
+RUN usermod -d /var/lib/mysql/ mysql
+
+RUN [ -d /var/run/mysqld ] || mkdir -p /var/run/mysqld
+
+ADD ./runMySQL.sh /runMySQL.sh
+
+RUN chmod +x /runMySQL.sh
+
+RUN apt-get install -y git-core
+
+RUN apt-get install -y wget
+
+# Install OpenJDK 12
+RUN wget https://download.java.net/java/GA/jdk12.0.2/e482c34c86bd4bf8b56c0b35558996b9/10/GPL/openjdk-12.0.2_linux-x64_bin.tar.gz
+
+RUN mkdir /usr/java
+
+RUN mv openjdk-12.0.2_linux-x64_bin.tar.gz /usr/java
+
+RUN cd /usr/java && tar -xzvf openjdk-12.0.2_linux-x64_bin.tar.gz
+
+RUN echo 'JAVA_HOME=/usr/java/jdk-12.0.2' >> /etc/profile
+RUN echo 'PATH=$PATH:$HOME/bin:$JAVA_HOME/bin' >> /etc/profile
+
+RUN apt-get install jq -y
+
+RUN apt-get install curl -y
+
+RUN apt-get install unzip -y
+
+# Install OpenJDK 15.0.1
+RUN wget https://download.java.net/java/GA/jdk15.0.1/51f4f36ad4ef43e39d0dfdbaf6549e32/9/GPL/openjdk-15.0.1_linux-x64_bin.tar.gz
+
+RUN mv openjdk-15.0.1_linux-x64_bin.tar.gz /usr/java
+
+RUN cd /usr/java && tar -xzvf openjdk-15.0.1_linux-x64_bin.tar.gz
+
+RUN echo 'JAVA_HOME=/usr/java/jdk-15.0.1' >> /etc/profile
+RUN echo 'PATH=$PATH:$HOME/bin:$JAVA_HOME/bin' >> /etc/profile
+RUN echo 'export JAVA_HOME' >> /etc/profile
+RUN echo 'export JRE_HOME' >> /etc/profile
+RUN echo 'export PATH' >> /etc/profile
+
+RUN update-alternatives --install "/usr/bin/java" "java" "/usr/java/jdk-12.0.2/bin/java" 1
+RUN update-alternatives --install "/usr/bin/javac" "javac" "/usr/java/jdk-12.0.2/bin/javac" 1
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/release.md b/.github/ISSUE_TEMPLATE/release.md
index 36e7a663b..2afb2131f 100644
--- a/.github/ISSUE_TEMPLATE/release.md
+++ b/.github/ISSUE_TEMPLATE/release.md
@@ -69,6 +69,7 @@ labels:
- [ ] SuperTokens Jackson SAML example update
- [ ] Supabase docs
- [ ] Capacitor template app: https://github.com/RobSchilderr/capacitor-supertokens-nextjs-turborepo
+ - [ ] T4 App: https://github.com/timothymiller/t4-app
### 📚 Documentation (test site)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c8a84e87b..71b6d5c90 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -35,6 +35,26 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- TODO - copy once postgres / mysql changelog is done
+## [7.0.18] - 2024-02-19
+
+- Fixes vulnerabilities in dependencies
+- Updates telemetry payload
+- Fixes Active User tracking to use the right storage
+
+## [7.0.17] - 2024-02-06
+
+- Fixes issue where error logs were printed to StdOut instead of StdErr.
+- Adds new config `supertokens_saas_load_only_cud` that makes the core instance load a particular CUD only, irrespective of the CUDs present in the db.
+- Fixes connection pool handling when connection pool size changes for a tenant.
+
+## [7.0.16] - 2023-12-04
+
+- Returns 400, instead of 500, for badly typed core config while creating CUD, App or Tenant
+
+## [7.0.15] - 2023-11-28
+
+- Adds test for user pagination from old version
+
## [7.0.14] - 2023-11-21
- Updates test user query speed
diff --git a/README.md b/README.md
index 4ad80fde7..4a96a5bb2 100644
--- a/README.md
+++ b/README.md
@@ -230,6 +230,7 @@ Melvyn Hills
Melvyn Hills
Cléo Rebert |
Daniil Borovoy |
+ Krzysztof Witkowski |
diff --git a/build.gradle b/build.gradle
index 3722d4d2d..806add897 100644
--- a/build.gradle
+++ b/build.gradle
@@ -33,22 +33,22 @@ dependencies {
implementation group: 'com.google.code.gson', name: 'gson', version: '2.3.1'
// https://mvnrepository.com/artifact/com.fasterxml.jackson.dataformat/jackson-dataformat-yaml
- implementation group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: '2.14.0'
+ implementation group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: '2.16.1'
// https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core
- implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.14.0'
+ implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.16.1'
// https://mvnrepository.com/artifact/ch.qos.logback/logback-classic
- implementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.3'
+ implementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.4.14'
// https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-core
- implementation group: 'org.apache.tomcat.embed', name: 'tomcat-embed-core', version: '10.1.1'
+ implementation group: 'org.apache.tomcat.embed', name: 'tomcat-embed-core', version: '10.1.18'
// https://mvnrepository.com/artifact/com.google.code.findbugs/jsr305
implementation group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.2'
// https://mvnrepository.com/artifact/org.xerial/sqlite-jdbc
- implementation group: 'org.xerial', name: 'sqlite-jdbc', version: '3.30.1'
+ implementation group: 'org.xerial', name: 'sqlite-jdbc', version: '3.45.1.0'
// https://mvnrepository.com/artifact/org.mindrot/jbcrypt
implementation group: 'org.mindrot', name: 'jbcrypt', version: '0.4'
diff --git a/cli/build.gradle b/cli/build.gradle
index 904dc0065..52e2ab2d5 100644
--- a/cli/build.gradle
+++ b/cli/build.gradle
@@ -19,10 +19,10 @@ dependencies {
implementation group: 'com.google.code.gson', name: 'gson', version: '2.3.1'
// https://mvnrepository.com/artifact/com.fasterxml.jackson.dataformat/jackson-dataformat-yaml
- implementation group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: '2.10.0'
+ implementation group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: '2.16.1'
// https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core
- implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.10.0'
+ implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.16.1'
// https://mvnrepository.com/artifact/de.mkammerer/argon2-jvm
implementation group: 'de.mkammerer', name: 'argon2-jvm', version: '2.11'
diff --git a/cli/implementationDependencies.json b/cli/implementationDependencies.json
index 645cacaf4..665c92fff 100644
--- a/cli/implementationDependencies.json
+++ b/cli/implementationDependencies.json
@@ -7,29 +7,29 @@
"src": "https://repo1.maven.org/maven2/com/google/code/gson/gson/2.3.1/gson-2.3.1-sources.jar"
},
{
- "jar": "https://repo1.maven.org/maven2/com/fasterxml/jackson/dataformat/jackson-dataformat-yaml/2.10.0/jackson-dataformat-yaml-2.10.0.jar",
- "name": "Jackson Dataformat 2.10.0",
- "src": "https://repo1.maven.org/maven2/com/fasterxml/jackson/dataformat/jackson-dataformat-yaml/2.10.0/jackson-dataformat-yaml-2.10.0-sources.jar"
+ "jar": "https://repo1.maven.org/maven2/com/fasterxml/jackson/dataformat/jackson-dataformat-yaml/2.16.1/jackson-dataformat-yaml-2.16.1.jar",
+ "name": "Jackson Dataformat 2.16.1",
+ "src": "https://repo1.maven.org/maven2/com/fasterxml/jackson/dataformat/jackson-dataformat-yaml/2.16.1/jackson-dataformat-yaml-2.16.1-sources.jar"
},
{
- "jar": "https://repo1.maven.org/maven2/org/yaml/snakeyaml/1.24/snakeyaml-1.24.jar",
- "name": "SnakeYAML 1.24",
- "src": "https://repo1.maven.org/maven2/org/yaml/snakeyaml/1.24/snakeyaml-1.24-sources.jar"
+ "jar": "https://repo1.maven.org/maven2/org/yaml/snakeyaml/2.2/snakeyaml-2.2.jar",
+ "name": "SnakeYAML 2.2",
+ "src": "https://repo1.maven.org/maven2/org/yaml/snakeyaml/2.2/snakeyaml-2.2-sources.jar"
},
{
- "jar": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-core/2.10.0/jackson-core-2.10.0.jar",
- "name": "Jackson core 2.10.0",
- "src": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-core/2.10.0/jackson-core-2.10.0-sources.jar"
+ "jar": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-core/2.16.1/jackson-core-2.16.1.jar",
+ "name": "Jackson core 2.16.1",
+ "src": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-core/2.16.1/jackson-core-2.16.1-sources.jar"
},
{
- "jar": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.10.0/jackson-databind-2.10.0.jar",
- "name": "Jackson databind 2.10.0",
- "src": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.10.0/jackson-databind-2.10.0-sources.jar"
+ "jar": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.16.1/jackson-databind-2.16.1.jar",
+ "name": "Jackson databind 2.16.1",
+ "src": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.16.1/jackson-databind-2.16.1-sources.jar"
},
{
- "jar": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-annotations/2.10.0/jackson-annotations-2.10.0.jar",
- "name": "Jackson annotation 2.10.0",
- "src": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-annotations/2.10.0/jackson-annotations-2.10.0-sources.jar"
+ "jar": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-annotations/2.16.1/jackson-annotations-2.16.1.jar",
+ "name": "Jackson annotation 2.16.1",
+ "src": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-annotations/2.16.1/jackson-annotations-2.16.1-sources.jar"
},
{
"jar": "https://repo1.maven.org/maven2/de/mkammerer/argon2-jvm/2.11/argon2-jvm-2.11.jar",
diff --git a/cli/jar/cli.jar b/cli/jar/cli.jar
index a74087247..679236a42 100644
Binary files a/cli/jar/cli.jar and b/cli/jar/cli.jar differ
diff --git a/config.yaml b/config.yaml
index bc6e7cbce..fdb96d4ba 100644
--- a/config.yaml
+++ b/config.yaml
@@ -146,3 +146,8 @@ core_config_version: 0
# when CDI version is not specified in the request. When set to null, the core will assume the latest version of the
# CDI.
# supertokens_max_cdi_version:
+
+
+# (OPTIONAL | Default: null) string value. If specified, the supertokens service will only load the specified CUD even
+# if there are more CUDs in the database and block all other CUDs from being used from this instance.
+# supertokens_saas_load_only_cud:
diff --git a/devConfig.yaml b/devConfig.yaml
index 73ccf220d..276b35d42 100644
--- a/devConfig.yaml
+++ b/devConfig.yaml
@@ -147,3 +147,7 @@ disable_telemetry: true
# when CDI version is not specified in the request. When set to null, the core will assume the latest version of the
# CDI.
# supertokens_max_cdi_version:
+
+# (OPTIONAL | Default: null) string value. If specified, the supertokens service will only load the specified CUD even
+# if there are more CUDs in the database and block all other CUDs from being used from this instance.
+# supertokens_saas_load_only_cud:
diff --git a/downloader/jar/downloader.jar b/downloader/jar/downloader.jar
index 8b94c5ca7..4c8bac4ca 100644
Binary files a/downloader/jar/downloader.jar and b/downloader/jar/downloader.jar differ
diff --git a/ee/build.gradle b/ee/build.gradle
index 94aeed97c..9e91d7a57 100644
--- a/ee/build.gradle
+++ b/ee/build.gradle
@@ -35,10 +35,10 @@ dependencies {
testImplementation group: 'org.mockito', name: 'mockito-core', version: '3.1.0'
// https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-core
- testImplementation group: 'org.apache.tomcat.embed', name: 'tomcat-embed-core', version: '10.1.1'
+ testImplementation group: 'org.apache.tomcat.embed', name: 'tomcat-embed-core', version: '10.1.18'
// https://mvnrepository.com/artifact/ch.qos.logback/logback-classic
- testImplementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.3'
+ testImplementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.4.14'
// https://mvnrepository.com/artifact/com.google.code.gson/gson
testImplementation group: 'com.google.code.gson', name: 'gson', version: '2.3.1'
@@ -46,10 +46,10 @@ dependencies {
testImplementation 'com.tngtech.archunit:archunit-junit4:0.22.0'
// https://mvnrepository.com/artifact/com.fasterxml.jackson.dataformat/jackson-dataformat-yaml
- testImplementation group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: '2.14.0'
+ testImplementation group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: '2.16.1'
// https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core
- testImplementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.14.0'
+ testImplementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.16.1'
testImplementation group: 'org.jetbrains', name: 'annotations', version: '13.0'
}
diff --git a/ee/jar/ee.jar b/ee/jar/ee.jar
index f12f374dc..1795fb9d7 100644
Binary files a/ee/jar/ee.jar and b/ee/jar/ee.jar differ
diff --git a/ee/src/main/java/io/supertokens/ee/EEFeatureFlag.java b/ee/src/main/java/io/supertokens/ee/EEFeatureFlag.java
index 79af8d666..cac2c5cb9 100644
--- a/ee/src/main/java/io/supertokens/ee/EEFeatureFlag.java
+++ b/ee/src/main/java/io/supertokens/ee/EEFeatureFlag.java
@@ -200,17 +200,18 @@ private JsonObject getMFAStats() throws StorageQueryException, TenantOrAppNotFou
Storage[] storages = StorageLayer.getStoragesForApp(main, this.appIdentifier);
int totalUserCountWithMoreThanOneLoginMethod = 0;
- int[] maus = new int[30];
+ int[] maus = new int[31];
long now = System.currentTimeMillis();
- long today = now - (now % (24 * 60 * 60 * 1000L));
for (Storage storage : storages) {
totalUserCountWithMoreThanOneLoginMethod += ((AuthRecipeStorage)storage).getUsersCountWithMoreThanOneLoginMethodOrTOTPEnabled(this.appIdentifier);
- for (int i = 0; i < 30; i++) {
- long timestamp = today - (i * 24 * 60 * 60 * 1000L);
- maus[i] += ((ActiveUsersStorage)storage).countUsersThatHaveMoreThanOneLoginMethodOrTOTPEnabledAndActiveSince(appIdentifier, timestamp);
+ 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);
}
}
@@ -283,7 +284,7 @@ private JsonObject getAccountLinkingStats() throws StorageQueryException {
if (!usesAccountLinking) {
result.addProperty("totalUserCountWithMoreThanOneLoginMethod", 0);
JsonArray mauArray = new JsonArray();
- for (int i = 0; i < 30; i++) {
+ for (int i = 0; i < 31; i++) {
mauArray.add(new JsonPrimitive(0));
}
result.add("mauWithMoreThanOneLoginMethod", mauArray);
@@ -291,17 +292,18 @@ private JsonObject getAccountLinkingStats() throws StorageQueryException {
}
int totalUserCountWithMoreThanOneLoginMethod = 0;
- int[] maus = new int[30];
+ int[] maus = new int[31];
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);
+ for (int i = 1; i <= 31; i++) {
+ long timestamp = now - (i * 24 * 60 * 60 * 1000L);
+
+ // `maus[i-1]` because i starts from 1
+ maus[i-1] += ((ActiveUsersStorage)storage).countUsersThatHaveMoreThanOneLoginMethodAndActiveSince(appIdentifier, timestamp);
}
}
@@ -312,10 +314,10 @@ private JsonObject getAccountLinkingStats() throws StorageQueryException {
private JsonArray getMAUs() throws StorageQueryException, TenantOrAppNotFoundException {
JsonArray mauArr = new JsonArray();
- for (int i = 0; i < 30; i++) {
- long now = System.currentTimeMillis();
- long today = now - (now % (24 * 60 * 60 * 1000L));
- long timestamp = today - (i * 24 * 60 * 60 * 1000L);
+ long now = System.currentTimeMillis();
+
+ for (int i = 1; i <= 31; i++) {
+ long timestamp = now - (i * 24 * 60 * 60 * 1000L);
ActiveUsersStorage activeUsersStorage = (ActiveUsersStorage) StorageLayer.getStorage(
this.appIdentifier.getAsPublicTenantIdentifier(), main);
int mau = activeUsersStorage.countUsersActiveSince(this.appIdentifier, timestamp);
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 3a896b253..7418b24ec 100644
--- a/ee/src/test/java/io/supertokens/ee/test/EETest.java
+++ b/ee/src/test/java/io/supertokens/ee/test/EETest.java
@@ -1326,7 +1326,7 @@ protected URLConnection openConnection(URL u) {
JsonObject paidFeatureUsageStats = j.getAsJsonObject("paidFeatureUsageStats");
JsonArray mauArr = paidFeatureUsageStats.get("maus").getAsJsonArray();
assertEquals(paidFeatureUsageStats.entrySet().size(), 1);
- assertEquals(mauArr.size(), 30);
+ assertEquals(mauArr.size(), 31);
assertEquals(mauArr.get(0).getAsInt(), 0);
assertEquals(mauArr.get(29).getAsInt(), 0);
}
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 d2932f619..efd133a86 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
@@ -54,7 +54,7 @@ public void testRetrievingFeatureFlagInfoWhenNoLicenseKeyIsSet() throws Exceptio
if (StorageLayer.getStorage(process.getProcess()).getType() == STORAGE_TYPE.SQL) {
JsonArray mauArr = usageStats.get("maus").getAsJsonArray();
assertEquals(1, usageStats.entrySet().size());
- assertEquals(30, mauArr.size());
+ assertEquals(31, mauArr.size());
assertEquals(0, mauArr.get(0).getAsInt());
assertEquals(0, mauArr.get(29).getAsInt());
} else {
@@ -92,7 +92,7 @@ public void testRetrievingFeatureFlagInfoWhenLicenseKeyIsSet() throws Exception
if (StorageLayer.getStorage(process.getProcess()).getType() == STORAGE_TYPE.SQL) {
JsonArray mauArr = usageStats.get("maus").getAsJsonArray();
assertEquals(1, usageStats.entrySet().size());
- assertEquals(30, mauArr.size());
+ assertEquals(31, mauArr.size());
assertEquals(0, mauArr.get(0).getAsInt());
assertEquals(0, mauArr.get(29).getAsInt());
} else {
diff --git a/implementationDependencies.json b/implementationDependencies.json
index e0ab94e68..ec4da266a 100644
--- a/implementationDependencies.json
+++ b/implementationDependencies.json
@@ -7,54 +7,54 @@
"src": "https://repo1.maven.org/maven2/com/google/code/gson/gson/2.3.1/gson-2.3.1-sources.jar"
},
{
- "jar": "https://repo1.maven.org/maven2/com/fasterxml/jackson/dataformat/jackson-dataformat-yaml/2.14.2/jackson-dataformat-yaml-2.14.2.jar",
- "name": "Jackson Dataformat 2.14.2",
- "src": "https://repo1.maven.org/maven2/com/fasterxml/jackson/dataformat/jackson-dataformat-yaml/2.14.2/jackson-dataformat-yaml-2.14.2-sources.jar"
+ "jar": "https://repo1.maven.org/maven2/com/fasterxml/jackson/dataformat/jackson-dataformat-yaml/2.16.1/jackson-dataformat-yaml-2.16.1.jar",
+ "name": "Jackson Dataformat 2.16.1",
+ "src": "https://repo1.maven.org/maven2/com/fasterxml/jackson/dataformat/jackson-dataformat-yaml/2.16.1/jackson-dataformat-yaml-2.16.1-sources.jar"
},
{
- "jar": "https://repo1.maven.org/maven2/org/yaml/snakeyaml/1.33/snakeyaml-1.33.jar",
- "name": "SnakeYAML 1.33",
- "src": "https://repo1.maven.org/maven2/org/yaml/snakeyaml/1.33/snakeyaml-1.33-sources.jar"
+ "jar": "https://repo1.maven.org/maven2/org/yaml/snakeyaml/2.2/snakeyaml-2.2.jar",
+ "name": "SnakeYAML 2.2",
+ "src": "https://repo1.maven.org/maven2/org/yaml/snakeyaml/2.2/snakeyaml-2.2-sources.jar"
},
{
- "jar": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-core/2.14.2/jackson-core-2.14.2.jar",
- "name": "Jackson core 2.14.2",
- "src": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-core/2.14.2/jackson-core-2.14.2-sources.jar"
+ "jar": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-core/2.16.1/jackson-core-2.16.1.jar",
+ "name": "Jackson core 2.16.1",
+ "src": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-core/2.16.1/jackson-core-2.16.1-sources.jar"
},
{
- "jar": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.14.2/jackson-databind-2.14.2.jar",
- "name": "Jackson databind 2.14.2",
- "src": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.14.2/jackson-databind-2.14.2-sources.jar"
+ "jar": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.16.1/jackson-databind-2.16.1.jar",
+ "name": "Jackson databind 2.16.1",
+ "src": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.16.1/jackson-databind-2.16.1-sources.jar"
},
{
- "jar": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-annotations/2.14.2/jackson-annotations-2.14.2.jar",
- "name": "Jackson annotation 2.14.2",
- "src": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-annotations/2.14.2/jackson-annotations-2.14.2-sources.jar"
+ "jar": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-annotations/2.16.1/jackson-annotations-2.16.1.jar",
+ "name": "Jackson annotation 2.16.1",
+ "src": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-annotations/2.16.1/jackson-annotations-2.16.1-sources.jar"
},
{
- "jar": "https://repo1.maven.org/maven2/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar",
- "name": "Logback classic 1.2.3",
- "src": "https://repo1.maven.org/maven2/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3-sources.jar"
+ "jar": "https://repo1.maven.org/maven2/ch/qos/logback/logback-classic/1.4.14/logback-classic-1.4.14.jar",
+ "name": "Logback classic 1.4.14",
+ "src": "https://repo1.maven.org/maven2/ch/qos/logback/logback-classic/1.4.14/logback-classic-1.4.14-sources.jar"
},
{
- "jar": "https://repo1.maven.org/maven2/ch/qos/logback/logback-core/1.2.3/logback-core-1.2.3.jar",
- "name": "Logback core 1.2.3",
- "src": "https://repo1.maven.org/maven2/ch/qos/logback/logback-core/1.2.3/logback-core-1.2.3-sources.jar"
+ "jar": "https://repo1.maven.org/maven2/ch/qos/logback/logback-core/1.4.14/logback-core-1.4.14.jar",
+ "name": "Logback core 1.4.14",
+ "src": "https://repo1.maven.org/maven2/ch/qos/logback/logback-core/1.4.14/logback-core-1.4.14-sources.jar"
},
{
- "jar": "https://repo1.maven.org/maven2/org/slf4j/slf4j-api/1.7.25/slf4j-api-1.7.25.jar",
- "name": "SLF4j API 1.7.25",
- "src": "https://repo1.maven.org/maven2/org/slf4j/slf4j-api/1.7.25/slf4j-api-1.7.25-sources.jar"
+ "jar": "https://repo1.maven.org/maven2/org/slf4j/slf4j-api/2.0.7/slf4j-api-2.0.7.jar",
+ "name": "SLF4j API 2.0.7",
+ "src": "https://repo1.maven.org/maven2/org/slf4j/slf4j-api/2.0.7/slf4j-api-2.0.7-sources.jar"
},
{
- "jar": "https://repo1.maven.org/maven2/org/apache/tomcat/tomcat-annotations-api/10.1.1/tomcat-annotations-api-10.1.1.jar",
- "name": "Tomcat annotations API 10.1.1",
- "src": "https://repo1.maven.org/maven2/org/apache/tomcat/tomcat-annotations-api/10.1.1/tomcat-annotations-api-10.1.1-sources.jar"
+ "jar": "https://repo1.maven.org/maven2/org/apache/tomcat/tomcat-annotations-api/10.1.18/tomcat-annotations-api-10.1.18.jar",
+ "name": "Tomcat annotations API 10.1.18",
+ "src": "https://repo1.maven.org/maven2/org/apache/tomcat/tomcat-annotations-api/10.1.18/tomcat-annotations-api-10.1.18-sources.jar"
},
{
- "jar": "https://repo1.maven.org/maven2/org/apache/tomcat/embed/tomcat-embed-core/10.1.1/tomcat-embed-core-10.1.1.jar",
+ "jar": "https://repo1.maven.org/maven2/org/apache/tomcat/embed/tomcat-embed-core/10.1.18/tomcat-embed-core-10.1.18.jar",
"name": "Tomcat embed core API 10.1.1",
- "src": "https://repo1.maven.org/maven2/org/apache/tomcat/embed/tomcat-embed-core/10.1.1/tomcat-embed-core-10.1.1-sources.jar"
+ "src": "https://repo1.maven.org/maven2/org/apache/tomcat/embed/tomcat-embed-core/10.1.18/tomcat-embed-core-10.1.18-sources.jar"
},
{
"jar": "https://repo1.maven.org/maven2/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2.jar",
@@ -67,13 +67,13 @@
"src": "https://repo1.maven.org/maven2/org/jetbrains/annotations/13.0/annotations-13.0-sources.jar"
},
{
- "jar": "https://repo1.maven.org/maven2/org/xerial/sqlite-jdbc/3.30.1/sqlite-jdbc-3.30.1.jar",
- "name": "SQLite JDBC Driver 3.30.1",
- "src": "https://repo1.maven.org/maven2/org/xerial/sqlite-jdbc/3.30.1/sqlite-jdbc-3.30.1-sources.jar"
+ "jar": "https://repo1.maven.org/maven2/org/xerial/sqlite-jdbc/3.45.1.0/sqlite-jdbc-3.45.1.0.jar",
+ "name": "SQLite JDBC Driver 3.45.1.0",
+ "src": "https://repo1.maven.org/maven2/org/xerial/sqlite-jdbc/3.45.1.0/sqlite-jdbc-3.45.1.0-sources.jar"
},
{
"jar": "https://repo1.maven.org/maven2/org/mindrot/jbcrypt/0.4/jbcrypt-0.4.jar",
- "name": "SQLite JDBC Driver 3.30.1",
+ "name": "JBCrypt 0.4",
"src": "https://repo1.maven.org/maven2/org/mindrot/jbcrypt/0.4/jbcrypt-0.4-sources.jar"
},
{
diff --git a/jar/core-7.0.14.jar b/jar/core-7.0.18.jar
similarity index 84%
rename from jar/core-7.0.14.jar
rename to jar/core-7.0.18.jar
index ee63b514b..4d0695612 100644
Binary files a/jar/core-7.0.14.jar and b/jar/core-7.0.18.jar differ
diff --git a/src/main/java/io/supertokens/config/Config.java b/src/main/java/io/supertokens/config/Config.java
index 042af8c9f..a51b8bd1d 100644
--- a/src/main/java/io/supertokens/config/Config.java
+++ b/src/main/java/io/supertokens/config/Config.java
@@ -19,6 +19,7 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import io.supertokens.Main;
import io.supertokens.ProcessState;
@@ -31,6 +32,7 @@
import io.supertokens.pluginInterface.multitenancy.TenantIdentifier;
import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException;
import io.supertokens.storageLayer.StorageLayer;
+import io.supertokens.utils.ConfigMapper;
import org.jetbrains.annotations.TestOnly;
import java.io.File;
@@ -49,16 +51,17 @@ public class Config extends ResourceDistributor.SingletonResource {
private Config(Main main, String configFilePath) throws InvalidConfigException, IOException {
this.main = main;
final ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
- CoreConfig config = mapper.readValue(new File(configFilePath), CoreConfig.class);
- config.normalizeAndValidate(main);
+ Object configObj = mapper.readValue(new File(configFilePath), Object.class);
+ JsonObject jsonConfig = new GsonBuilder().serializeNulls().create().toJsonTree(configObj).getAsJsonObject();
+ CoreConfig config = ConfigMapper.mapConfig(jsonConfig, CoreConfig.class);
+ config.normalizeAndValidate(main, true);
this.core = config;
}
private Config(Main main, JsonObject jsonConfig) throws IOException, InvalidConfigException {
this.main = main;
- final ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
- CoreConfig config = mapper.readValue(jsonConfig.toString(), CoreConfig.class);
- config.normalizeAndValidate(main);
+ CoreConfig config = ConfigMapper.mapConfig(jsonConfig, CoreConfig.class);
+ config.normalizeAndValidate(main, false);
this.core = config;
}
@@ -89,7 +92,7 @@ public static JsonObject getBaseConfigAsJsonObject(Main main) throws IOException
// omit them from the output json.
ObjectMapper yamlReader = new ObjectMapper(new YAMLFactory());
Object obj = yamlReader.readValue(new File(getConfigFilePath(main)), Object.class);
- return new Gson().toJsonTree(obj).getAsJsonObject();
+ return new GsonBuilder().serializeNulls().create().toJsonTree(obj).getAsJsonObject();
}
private static String getConfigFilePath(Main main) {
diff --git a/src/main/java/io/supertokens/config/CoreConfig.java b/src/main/java/io/supertokens/config/CoreConfig.java
index e02fadb4d..3de06caa7 100644
--- a/src/main/java/io/supertokens/config/CoreConfig.java
+++ b/src/main/java/io/supertokens/config/CoreConfig.java
@@ -30,7 +30,9 @@
import io.supertokens.pluginInterface.LOG_LEVEL;
import io.supertokens.pluginInterface.exceptions.InvalidConfigException;
import io.supertokens.utils.SemVer;
+import io.supertokens.webserver.Utils;
import io.supertokens.webserver.WebserverAPI;
+import jakarta.servlet.ServletException;
import org.apache.catalina.filters.RemoteAddrFilter;
import org.jetbrains.annotations.TestOnly;
@@ -197,6 +199,10 @@ public class CoreConfig {
@JsonProperty
private String supertokens_max_cdi_version = null;
+ @ConfigYamlOnly
+ @JsonProperty
+ private String supertokens_saas_load_only_cud = null;
+
@IgnoreForAnnotationCheck
private Set allowedLogLevels = null;
@@ -254,6 +260,10 @@ public String getBasePath() {
return base_path;
}
+ public String getSuperTokensLoadOnlyCUD() {
+ return supertokens_saas_load_only_cud;
+ }
+
public enum PASSWORD_HASHING_ALG {
ARGON2, BCRYPT, FIREBASE_SCRYPT
}
@@ -394,7 +404,7 @@ private String getConfigFileLocation(Main main) {
: CLIOptions.get(main).getConfigFilePath()).getAbsolutePath();
}
- void normalizeAndValidate(Main main) throws InvalidConfigException {
+ void normalizeAndValidate(Main main, boolean includeConfigFilePath) throws InvalidConfigException {
if (isNormalizedAndValid) {
return;
}
@@ -407,8 +417,9 @@ void normalizeAndValidate(Main main) throws InvalidConfigException {
}
if (access_token_validity < 1 || access_token_validity > 86400000) {
throw new InvalidConfigException(
- "'access_token_validity' must be between 1 and 86400000 seconds inclusive. The config file can be"
- + " found here: " + getConfigFileLocation(main));
+ "'access_token_validity' must be between 1 and 86400000 seconds inclusive." +
+ (includeConfigFilePath ? " The config file can be"
+ + " found here: " + getConfigFileLocation(main) : ""));
}
Boolean validityTesting = CoreConfigTestContent.getInstance(main)
.getValue(CoreConfigTestContent.VALIDITY_TESTING);
@@ -417,16 +428,18 @@ void normalizeAndValidate(Main main) throws InvalidConfigException {
if ((refresh_token_validity * 60) <= access_token_validity) {
if (!Main.isTesting || validityTesting) {
throw new InvalidConfigException(
- "'refresh_token_validity' must be strictly greater than 'access_token_validity'. The config "
- + "file can be found here: " + getConfigFileLocation(main));
+ "'refresh_token_validity' must be strictly greater than 'access_token_validity'." +
+ (includeConfigFilePath ? " The config file can be"
+ + " found here: " + getConfigFileLocation(main) : ""));
}
}
if (!Main.isTesting || validityTesting) { // since in testing we make this really small
if (access_token_dynamic_signing_key_update_interval < 1) {
throw new InvalidConfigException(
- "'access_token_dynamic_signing_key_update_interval' must be greater than, equal to 1 hour. The "
- + "config file can be found here: " + getConfigFileLocation(main));
+ "'access_token_dynamic_signing_key_update_interval' must be greater than, equal to 1 hour." +
+ (includeConfigFilePath ? " The config file can be"
+ + " found here: " + getConfigFileLocation(main) : ""));
}
}
@@ -456,8 +469,9 @@ void normalizeAndValidate(Main main) throws InvalidConfigException {
if (max_server_pool_size <= 0) {
throw new InvalidConfigException(
- "'max_server_pool_size' must be >= 1. The config file can be found here: "
- + getConfigFileLocation(main));
+ "'max_server_pool_size' must be >= 1." +
+ (includeConfigFilePath ? " The config file can be"
+ + " found here: " + getConfigFileLocation(main) : ""));
}
if (api_keys != null) {
@@ -663,6 +677,15 @@ void normalizeAndValidate(Main main) throws InvalidConfigException {
host = cliHost;
}
+ if (supertokens_saas_load_only_cud != null) {
+ try {
+ supertokens_saas_load_only_cud =
+ Utils.normalizeAndValidateConnectionUriDomain(supertokens_saas_load_only_cud, true);
+ } catch (ServletException e) {
+ throw new InvalidConfigException("supertokens_saas_load_only_cud is invalid");
+ }
+ }
+
access_token_validity = access_token_validity * 1000;
access_token_dynamic_signing_key_update_interval = access_token_dynamic_signing_key_update_interval * 3600 * 1000;
refresh_token_validity = refresh_token_validity * 60 * 1000;
diff --git a/src/main/java/io/supertokens/cronjobs/telemetry/Telemetry.java b/src/main/java/io/supertokens/cronjobs/telemetry/Telemetry.java
index 727d2f5ec..215024858 100644
--- a/src/main/java/io/supertokens/cronjobs/telemetry/Telemetry.java
+++ b/src/main/java/io/supertokens/cronjobs/telemetry/Telemetry.java
@@ -16,20 +16,26 @@
package io.supertokens.cronjobs.telemetry;
+import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
+import com.google.gson.JsonPrimitive;
import io.supertokens.Main;
import io.supertokens.ProcessState;
+import io.supertokens.authRecipe.AuthRecipe;
import io.supertokens.config.Config;
import io.supertokens.cronjobs.CronTask;
import io.supertokens.cronjobs.CronTaskTest;
+import io.supertokens.dashboard.Dashboard;
import io.supertokens.httpRequest.HttpRequest;
import io.supertokens.httpRequest.HttpRequestMocking;
import io.supertokens.pluginInterface.ActiveUsersStorage;
import io.supertokens.pluginInterface.KeyValueInfo;
import io.supertokens.pluginInterface.STORAGE_TYPE;
import io.supertokens.pluginInterface.Storage;
+import io.supertokens.pluginInterface.dashboard.DashboardUser;
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 io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException;
import io.supertokens.storageLayer.StorageLayer;
@@ -90,14 +96,55 @@ protected void doTaskPerApp(AppIdentifier app) throws Exception {
json.addProperty("telemetryId", telemetryId.value);
json.addProperty("superTokensVersion", coreVersion);
+ json.addProperty("appId", app.getAppId());
+ json.addProperty("connectionUriDomain", app.getConnectionUriDomain());
+
if (StorageLayer.getBaseStorage(main).getType() == STORAGE_TYPE.SQL) {
- ActiveUsersStorage activeUsersStorage = (ActiveUsersStorage) StorageLayer.getStorage(app.getAsPublicTenantIdentifier(), main);
- json.addProperty("mau", activeUsersStorage.countUsersActiveSince(app, System.currentTimeMillis() - 30 * 24 * 3600 * 1000L));
+ { // Users count across all tenants
+ Storage[] storages = StorageLayer.getStoragesForApp(main, app);
+ AppIdentifierWithStorage appIdentifierWithAllTenantStorages = new AppIdentifierWithStorage(
+ app.getConnectionUriDomain(), app.getAppId(),
+ StorageLayer.getStorage(app.getAsPublicTenantIdentifier(), main), storages
+ );
+
+ json.addProperty("usersCount",
+ AuthRecipe.getUsersCountAcrossAllTenants(appIdentifierWithAllTenantStorages, null));
+ }
+
+ { // Dashboard user emails
+ // Dashboard APIs are app specific and are always stored on the public tenant
+ DashboardUser[] dashboardUsers = Dashboard.getAllDashboardUsers(
+ app.withStorage(StorageLayer.getStorage(app.getAsPublicTenantIdentifier(), main)), main);
+ JsonArray dashboardUserEmails = new JsonArray();
+ for (DashboardUser user : dashboardUsers) {
+ dashboardUserEmails.add(new JsonPrimitive(user.email));
+ }
+
+ json.add("dashboardUserEmails", dashboardUserEmails);
+ }
+
+ { // MAUs
+ // Active users are always tracked on the public tenant, so we use the public tenant's storage
+ ActiveUsersStorage activeUsersStorage = (ActiveUsersStorage) StorageLayer.getStorage(
+ app.getAsPublicTenantIdentifier(), main);
+
+ JsonArray mauArr = new JsonArray();
+
+ long now = System.currentTimeMillis();
+
+ for (int i = 1; i <= 31; i++) {
+ long timestamp = now - (i * 24 * 60 * 60 * 1000L);
+ int mau = activeUsersStorage.countUsersActiveSince(app, timestamp);
+ mauArr.add(new JsonPrimitive(mau));
+ }
+
+ json.add("maus", mauArr);
+ }
} else {
- json.addProperty("mau", -1);
+ json.addProperty("usersCount", -1);
+ json.add("dashboardUserEmails", new JsonArray());
+ json.add("maus", new JsonArray());
}
- json.addProperty("appId", app.getAppId());
- json.addProperty("connectionUriDomain", app.getConnectionUriDomain());
String url = "https://api.supertokens.io/0/st/telemetry";
@@ -105,7 +152,7 @@ protected void doTaskPerApp(AppIdentifier app) throws Exception {
// wants
// to use this)
if (!Main.isTesting || HttpRequestMocking.getInstance(main).getMockURL(REQUEST_ID, url) != null) {
- HttpRequest.sendJsonPOSTRequest(main, REQUEST_ID, url, json, 10000, 10000, 4);
+ HttpRequest.sendJsonPOSTRequest(main, REQUEST_ID, url, json, 10000, 10000, 5);
ProcessState.getInstance(main).addState(ProcessState.PROCESS_STATE.SENT_TELEMETRY, null);
}
}
diff --git a/src/main/java/io/supertokens/multitenancy/MultitenancyHelper.java b/src/main/java/io/supertokens/multitenancy/MultitenancyHelper.java
index 24f1f0e7c..63a5cd7b3 100644
--- a/src/main/java/io/supertokens/multitenancy/MultitenancyHelper.java
+++ b/src/main/java/io/supertokens/multitenancy/MultitenancyHelper.java
@@ -51,9 +51,20 @@ public class MultitenancyHelper extends ResourceDistributor.SingletonResource {
private Main main;
private TenantConfig[] tenantConfigs;
+ // when the core has `supertokens_saas_load_only_cud` set, the tenantConfigs array will be filtered
+ // based on the config value. However, we need to keep all the list of CUDs from the db to be able
+ // to check if the CUD is present in the DB or not, while processing the requests.
+ private final Set dangerous_allCUDsFromDb = new HashSet<>();
+
private MultitenancyHelper(Main main) throws StorageQueryException {
this.main = main;
- this.tenantConfigs = getAllTenantsFromDb();
+ TenantConfig[] allTenantsFromDb = getAllTenantsFromDb();
+ this.tenantConfigs = this.getFilteredTenantConfigs(allTenantsFromDb);
+ this.dangerous_allCUDsFromDb.clear();
+
+ for (TenantConfig config : allTenantsFromDb) {
+ this.dangerous_allCUDsFromDb.add(config.tenantIdentifier.getConnectionUriDomain());
+ }
}
public static MultitenancyHelper getInstance(Main main) {
@@ -109,10 +120,11 @@ public List refreshTenantsInCoreBasedOnChangesInCoreConfigOrIf
return main.getResourceDistributor().withResourceDistributorLock(() -> {
try {
TenantConfig[] tenantsFromDb = getAllTenantsFromDb();
+ TenantConfig[] filteredTenantsFromDb = this.getFilteredTenantConfigs(tenantsFromDb);
Map normalizedTenantsFromDb =
Config.getNormalisedConfigsForAllTenants(
- tenantsFromDb, Config.getBaseConfigAsJsonObject(main));
+ filteredTenantsFromDb, Config.getBaseConfigAsJsonObject(main));
Map normalizedTenantsFromMemory =
Config.getNormalisedConfigsForAllTenants(
@@ -130,9 +142,14 @@ public List refreshTenantsInCoreBasedOnChangesInCoreConfigOrIf
}
}
- boolean sameNumberOfTenants = tenantsFromDb.length == this.tenantConfigs.length;
+ boolean sameNumberOfTenants =
+ filteredTenantsFromDb.length == this.tenantConfigs.length;
- this.tenantConfigs = tenantsFromDb;
+ this.dangerous_allCUDsFromDb.clear();
+ for (TenantConfig tenant : tenantsFromDb) {
+ this.dangerous_allCUDsFromDb.add(tenant.tenantIdentifier.getConnectionUriDomain());
+ }
+ this.tenantConfigs = filteredTenantsFromDb;
if (tenantsThatChanged.size() == 0 && sameNumberOfTenants) {
return tenantsThatChanged;
}
@@ -191,7 +208,7 @@ public void loadStorageLayer() throws IOException, InvalidConfigException {
public void loadFeatureFlag(List tenantsThatChanged) {
List apps = new ArrayList<>();
Set appsSet = new HashSet<>();
- for (TenantConfig t : tenantConfigs) {
+ for (TenantConfig t : this.tenantConfigs) {
if (appsSet.contains(t.tenantIdentifier.toAppIdentifier())) {
continue;
}
@@ -205,7 +222,7 @@ public void loadSigningKeys(List tenantsThatChanged)
throws UnsupportedJWTSigningAlgorithmException {
List apps = new ArrayList<>();
Set appsSet = new HashSet<>();
- for (TenantConfig t : tenantConfigs) {
+ for (TenantConfig t : this.tenantConfigs) {
if (appsSet.contains(t.tenantIdentifier.toAppIdentifier())) {
continue;
}
@@ -239,4 +256,21 @@ public TenantConfig[] getAllTenants() {
throw new IllegalStateException(e);
}
}
+
+ private TenantConfig[] getFilteredTenantConfigs(TenantConfig[] inputTenantConfigs) {
+ String loadOnlyCUD = Config.getBaseConfig(main).getSuperTokensLoadOnlyCUD();
+
+ if (loadOnlyCUD == null) {
+ return inputTenantConfigs;
+ }
+
+ return Arrays.stream(inputTenantConfigs)
+ .filter(tenantConfig -> tenantConfig.tenantIdentifier.getConnectionUriDomain().equals(loadOnlyCUD)
+ || tenantConfig.tenantIdentifier.getConnectionUriDomain().equals(TenantIdentifier.DEFAULT_CONNECTION_URI))
+ .toArray(TenantConfig[]::new);
+ }
+
+ public boolean isConnectionUriDomainPresentInDb(String cud) {
+ return this.dangerous_allCUDsFromDb.contains(cud);
+ }
}
diff --git a/src/main/java/io/supertokens/output/Logging.java b/src/main/java/io/supertokens/output/Logging.java
index 58106b7ba..d3c89f1fb 100644
--- a/src/main/java/io/supertokens/output/Logging.java
+++ b/src/main/java/io/supertokens/output/Logging.java
@@ -52,11 +52,11 @@ public class Logging extends ResourceDistributor.SingletonResource {
private Logging(Main main) {
this.infoLogger = Config.getBaseConfig(main).getInfoLogPath(main).equals("null")
- ? createLoggerForConsole(main, "io.supertokens.Info")
+ ? createLoggerForConsole(main, "io.supertokens.Info", LOG_LEVEL.INFO)
: createLoggerForFile(main, Config.getBaseConfig(main).getInfoLogPath(main),
"io.supertokens.Info");
this.errorLogger = Config.getBaseConfig(main).getErrorLogPath(main).equals("null")
- ? createLoggerForConsole(main, "io.supertokens.Error")
+ ? createLoggerForConsole(main, "io.supertokens.Error", LOG_LEVEL.ERROR)
: createLoggerForFile(main, Config.getBaseConfig(main).getErrorLogPath(main),
"io.supertokens.Error");
Storage storage = StorageLayer.getBaseStorage(main);
@@ -251,12 +251,13 @@ private Logger createLoggerForFile(Main main, String file, String name) {
return logger;
}
- private Logger createLoggerForConsole(Main main, String name) {
+ private Logger createLoggerForConsole(Main main, String name, LOG_LEVEL logLevel) {
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
LayoutWrappingEncoder ple = new LayoutWrappingEncoder(main.getProcessId(), Version.getVersion(main).getCoreVersion());
ple.setContext(lc);
ple.start();
ConsoleAppender logConsoleAppender = new ConsoleAppender<>();
+ logConsoleAppender.setTarget(logLevel == LOG_LEVEL.ERROR ? "System.err" : "System.out");
logConsoleAppender.setEncoder(ple);
logConsoleAppender.setContext(lc);
logConsoleAppender.start();
diff --git a/src/main/java/io/supertokens/storageLayer/StorageLayer.java b/src/main/java/io/supertokens/storageLayer/StorageLayer.java
index 3d4c34f83..faeaece0d 100644
--- a/src/main/java/io/supertokens/storageLayer/StorageLayer.java
+++ b/src/main/java/io/supertokens/storageLayer/StorageLayer.java
@@ -242,7 +242,7 @@ public static void loadAllTenantStorage(Main main, TenantConfig[] tenants)
}
main.getResourceDistributor().clearAllResourcesWithResourceKey(RESOURCE_KEY);
- Set userPoolsInUse = new HashSet<>();
+ Set uniquePoolsInUse = new HashSet<>();
for (ResourceDistributor.KeyClass key : resourceKeyToStorageMap.keySet()) {
Storage currStorage = resourceKeyToStorageMap.get(key);
@@ -259,11 +259,16 @@ public static void loadAllTenantStorage(Main main, TenantConfig[] tenants)
main.getResourceDistributor().setResource(key.getTenantIdentifier(), RESOURCE_KEY,
new StorageLayer(resourceKeyToStorageMap.get(key)));
- userPoolsInUse.add(userPoolId);
+ uniquePoolsInUse.add(uniqueId);
}
for (ResourceDistributor.KeyClass key : existingStorageMap.keySet()) {
- if (!userPoolsInUse.contains(((StorageLayer) existingStorageMap.get(key)).storage.getUserPoolId())) {
+ Storage existingStorage = ((StorageLayer) existingStorageMap.get(key)).storage;
+ String userPoolId = existingStorage.getUserPoolId();
+ String connectionPoolId = existingStorage.getConnectionPoolId();
+ String uniqueId = userPoolId + "~" + connectionPoolId;
+
+ if (!uniquePoolsInUse.contains(uniqueId)) {
((StorageLayer) existingStorageMap.get(key)).storage.close();
((StorageLayer) existingStorageMap.get(key)).storage.stopLogging();
}
diff --git a/src/main/java/io/supertokens/utils/ConfigMapper.java b/src/main/java/io/supertokens/utils/ConfigMapper.java
new file mode 100644
index 000000000..4d7484026
--- /dev/null
+++ b/src/main/java/io/supertokens/utils/ConfigMapper.java
@@ -0,0 +1,140 @@
+/*
+ * 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.utils;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonNull;
+import com.google.gson.JsonObject;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonAlias;
+import io.supertokens.pluginInterface.exceptions.InvalidConfigException;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.util.Map;
+
+public class ConfigMapper {
+ public static T mapConfig(JsonObject config, Class clazz) throws InvalidConfigException {
+ try {
+ T result = clazz.newInstance();
+ for (Map.Entry entry : config.entrySet()) {
+ Field field = findField(clazz, entry.getKey());
+ if (field != null) {
+ setValue(result, field, entry.getValue());
+ }
+ }
+ return result;
+ } catch (InstantiationException | IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static Field findField(Class clazz, String key) {
+ Field[] fields = clazz.getDeclaredFields();
+
+ for (Field field : fields) {
+ if (field.getName().equals(key)) {
+ return field;
+ }
+
+ // Check for JsonProperty annotation
+ JsonProperty jsonProperty = field.getAnnotation(JsonProperty.class);
+ if (jsonProperty != null && jsonProperty.value().equals(key)) {
+ return field;
+ }
+
+ // Check for JsonAlias annotation
+ JsonAlias jsonAlias = field.getAnnotation(JsonAlias.class);
+ if (jsonAlias != null) {
+ for (String alias : jsonAlias.value()) {
+ if (alias.equals(key)) {
+ return field;
+ }
+ }
+ }
+ }
+
+ return null; // Field not found
+ }
+
+ private static void setValue(T object, Field field, JsonElement value) throws InvalidConfigException {
+ field.setAccessible(true);
+ Object convertedValue = convertJsonElementToTargetType(value, field.getType(), field.getName());
+ if (convertedValue != null || isNullable(field.getType())) {
+ try {
+ field.set(object, convertedValue);
+ } catch (IllegalAccessException e) {
+ throw new IllegalStateException("should never happen");
+ }
+ }
+ }
+
+ private static boolean isNullable(Class> type) {
+ return !type.isPrimitive();
+ }
+
+ private static Object convertJsonElementToTargetType(JsonElement value, Class> targetType, String fieldName)
+ throws InvalidConfigException {
+ // If the value is JsonNull, return null for any type
+ if (value instanceof JsonNull || value == null) {
+ return null;
+ }
+
+ try {
+ if (targetType == String.class) {
+ return value.getAsString();
+ } else if (targetType == Integer.class || targetType == int.class) {
+ if (value.getAsDouble() == (double) value.getAsInt()) {
+ return value.getAsInt();
+ }
+ } else if (targetType == Long.class || targetType == long.class) {
+ if (value.getAsDouble() == (double) value.getAsLong()) {
+ return value.getAsLong();
+ }
+ } else if (targetType == Double.class || targetType == double.class) {
+ return value.getAsDouble();
+ } else if (targetType == Float.class || targetType == float.class) {
+ return value.getAsFloat();
+ } else if (targetType == Boolean.class || targetType == boolean.class) {
+ // Handle boolean conversion from strings like "true", "false"
+ return handleBooleanConversion(value, fieldName);
+ }
+ } catch (NumberFormatException e) {
+ // do nothing, will fall into InvalidConfigException
+ }
+
+ // Throw an exception for unsupported conversions
+ throw new InvalidConfigException("'" + fieldName + "' must be of type " + targetType.getSimpleName());
+ }
+
+ private static Object handleBooleanConversion(JsonElement value, String fieldName) throws InvalidConfigException {
+ // Handle boolean conversion from strings like "true", "false"
+ if (value.isJsonPrimitive() && value.getAsJsonPrimitive().isString()) {
+ String stringValue = value.getAsString().toLowerCase();
+ if (stringValue.equals("true")) {
+ return true;
+ } else if (stringValue.equals("false")) {
+ return false;
+ }
+ } else if (value.isJsonPrimitive() && value.getAsJsonPrimitive().isBoolean()) {
+ return value.getAsBoolean();
+ }
+
+ // Throw an exception for unsupported conversions
+ throw new InvalidConfigException("'" + fieldName + "' must be of type boolean");
+ }
+}
diff --git a/src/main/java/io/supertokens/webserver/WebserverAPI.java b/src/main/java/io/supertokens/webserver/WebserverAPI.java
index 6b9e23048..03919bc10 100644
--- a/src/main/java/io/supertokens/webserver/WebserverAPI.java
+++ b/src/main/java/io/supertokens/webserver/WebserverAPI.java
@@ -24,6 +24,7 @@
import io.supertokens.config.CoreConfig;
import io.supertokens.exceptions.QuitProgramException;
import io.supertokens.featureflag.exceptions.FeatureNotEnabledException;
+import io.supertokens.multitenancy.MultitenancyHelper;
import io.supertokens.multitenancy.exception.BadPermissionException;
import io.supertokens.output.Logging;
import io.supertokens.pluginInterface.Storage;
@@ -289,15 +290,18 @@ private String getConnectionUriDomain(HttpServletRequest req) throws ServletExce
String connectionUriDomain = req.getServerName();
connectionUriDomain = Utils.normalizeAndValidateConnectionUriDomain(connectionUriDomain, false);
- try {
- if (Config.getConfig(new TenantIdentifier(connectionUriDomain, null, null), main) ==
- Config.getConfig(new TenantIdentifier(null, null, null), main)) {
- return null;
+ if (MultitenancyHelper.getInstance(main).isConnectionUriDomainPresentInDb(connectionUriDomain)) {
+ CoreConfig baseConfig = Config.getBaseConfig(main);
+ if (baseConfig.getSuperTokensLoadOnlyCUD() != null) {
+ if (!connectionUriDomain.equals(baseConfig.getSuperTokensLoadOnlyCUD())) {
+ throw new ServletException(new BadRequestException("Connection URI domain is disallowed"));
+ }
}
- } catch (TenantOrAppNotFoundException e) {
- throw new IllegalStateException(e);
+
+ return connectionUriDomain;
}
- return connectionUriDomain;
+
+ return null;
}
@TestOnly
@@ -348,7 +352,6 @@ protected AppIdentifierWithStorage getPublicTenantStorage(HttpServletRequest req
Storage storage = StorageLayer.getStorage(appIdentifier.getAsPublicTenantIdentifier(), main);
return appIdentifier.withStorage(storage);
-
}
protected TenantIdentifierWithStorageAndUserIdMapping getTenantIdentifierWithStorageAndUserIdMappingFromRequest(
@@ -493,10 +496,13 @@ protected void service(HttpServletRequest req, HttpServletResponse resp) throws
}
Logging.info(main, tenantIdentifier, "API ended: " + req.getRequestURI() + ". Method: " + req.getMethod(),
false);
- try {
- RequestStats.getInstance(main, tenantIdentifier.toAppIdentifier()).updateRequestStats();
- } catch (TenantOrAppNotFoundException e) {
- // Ignore the error as we would have already sent the response for tenantNotFound
+
+ if (tenantIdentifier != null) {
+ try {
+ RequestStats.getInstance(main, tenantIdentifier.toAppIdentifier()).updateRequestStats();
+ } catch (TenantOrAppNotFoundException e) {
+ // Ignore the error as we would have already sent the response for tenantNotFound
+ }
}
}
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 940968568..2fbfb5ce4 100644
--- a/src/main/java/io/supertokens/webserver/api/multitenancy/BaseCreateOrUpdate.java
+++ b/src/main/java/io/supertokens/webserver/api/multitenancy/BaseCreateOrUpdate.java
@@ -19,6 +19,8 @@
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import io.supertokens.Main;
+import io.supertokens.config.Config;
+import io.supertokens.config.CoreConfig;
import io.supertokens.featureflag.exceptions.FeatureNotEnabledException;
import io.supertokens.multitenancy.Multitenancy;
import io.supertokens.multitenancy.exception.BadPermissionException;
@@ -59,6 +61,14 @@ protected void handle(HttpServletRequest req, TenantIdentifier sourceTenantIdent
throw new ServletException(new BadRequestException("requiredSecondaryFactors cannot be empty. Set null instead to remove all required secondary factors."));
}
+ CoreConfig baseConfig = Config.getBaseConfig(main);
+ if (baseConfig.getSuperTokensLoadOnlyCUD() != null) {
+ if (!(targetTenantIdentifier.getConnectionUriDomain().equals(TenantIdentifier.DEFAULT_CONNECTION_URI) || targetTenantIdentifier.getConnectionUriDomain().equals(baseConfig.getSuperTokensLoadOnlyCUD()))) {
+ throw new ServletException(new BadRequestException("Creation of connection uri domain or app or " +
+ "tenant is disallowed"));
+ }
+ }
+
TenantConfig tenantConfig = Multitenancy.getTenantInfo(main,
new TenantIdentifier(targetTenantIdentifier.getConnectionUriDomain(), targetTenantIdentifier.getAppId(),
targetTenantIdentifier.getTenantId()));
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 983dd1624..404947121 100644
--- a/src/main/java/io/supertokens/webserver/api/passwordless/ConsumeCodeAPI.java
+++ b/src/main/java/io/supertokens/webserver/api/passwordless/ConsumeCodeAPI.java
@@ -97,6 +97,10 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I
getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v4_0),
createRecipeUserIfNotExists);
+ io.supertokens.useridmapping.UserIdMapping.populateExternalUserIdForUsers(this.getTenantIdentifierWithStorageFromRequest(req), new AuthRecipeUserInfo[]{consumeCodeResponse.user});
+
+ ActiveUsers.updateLastActive(this.getPublicTenantStorage(req), main, consumeCodeResponse.user.getSupertokensUserId());
+
JsonObject result = new JsonObject();
result.addProperty("status", "OK");
diff --git a/src/test/java/io/supertokens/test/AuthRecipesParallelTest.java b/src/test/java/io/supertokens/test/AuthRecipesParallelTest.java
index 3e65fc432..dfd7c78ef 100644
--- a/src/test/java/io/supertokens/test/AuthRecipesParallelTest.java
+++ b/src/test/java/io/supertokens/test/AuthRecipesParallelTest.java
@@ -20,7 +20,11 @@
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.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;
@@ -37,8 +41,7 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.*;
public class AuthRecipesParallelTest {
@Rule
@@ -56,50 +59,61 @@ public void beforeEach() {
@Test
public void timeTakenFor500SignInParallel() throws Exception {
- String[] args = {"../"};
+ for (int t = 0; t < 5; t++) {
+ 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;
+ }
+
+ 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);
+ }
+ }
+ });
+ }
- TestingProcessManager.TestingProcess process = TestingProcessManager.start(args);
- assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED));
+ ex.shutdown();
- if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) {
- return;
- }
-
- ExecutorService ex = Executors.newFixedThreadPool(1000);
- int numberOfThreads = 500;
+ 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);
- EmailPassword.signUp(process.getProcess(), "test@example.com", "password");
- AtomicInteger counter = new AtomicInteger(0);
- AtomicInteger retryCounter = new AtomicInteger(0);
+ if (retryCounter.get() != 0) {
+ process.kill();
+ assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED));
+ continue; // retry
+ }
+ assertEquals(0, retryCounter.get());
- 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);
- }
- }
- });
+ process.kill();
+ assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED));
+ return;
}
- 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));
+ fail(); // tried 5 times
}
@Test
diff --git a/src/test/java/io/supertokens/test/ConfigMapperTest.java b/src/test/java/io/supertokens/test/ConfigMapperTest.java
new file mode 100644
index 000000000..1b048020b
--- /dev/null
+++ b/src/test/java/io/supertokens/test/ConfigMapperTest.java
@@ -0,0 +1,296 @@
+/*
+ * 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 com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.gson.JsonObject;
+import io.supertokens.pluginInterface.exceptions.InvalidConfigException;
+import io.supertokens.utils.ConfigMapper;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+public class ConfigMapperTest {
+
+ public static class DummyConfig {
+ @JsonProperty
+ int int_property = -1;
+
+ @JsonProperty
+ long long_property = -1;
+
+ @JsonProperty
+ float float_property = -1;
+
+ @JsonProperty
+ double double_property = -1;
+
+ @JsonProperty
+ String string_property = "default_string";
+
+ @JsonProperty
+ boolean bool_property;
+
+ @JsonProperty
+ Long nullable_long_property = new Long(-1);
+ }
+
+ @Test
+ public void testAllValidConversions() throws Exception {
+ // Test defaults
+ {
+ JsonObject config = new JsonObject();
+ assertEquals(-1, ConfigMapper.mapConfig(config, DummyConfig.class).int_property);
+ assertEquals(-1, ConfigMapper.mapConfig(config, DummyConfig.class).long_property);
+ assertEquals(-1, ConfigMapper.mapConfig(config, DummyConfig.class).float_property, 0.0001);
+ assertEquals(-1, ConfigMapper.mapConfig(config, DummyConfig.class).double_property, 0.0001);
+ assertEquals("default_string", ConfigMapper.mapConfig(config, DummyConfig.class).string_property);
+ assertEquals(new Long(-1), ConfigMapper.mapConfig(config, DummyConfig.class).nullable_long_property);
+ }
+
+ // valid for int
+ {
+ JsonObject config = new JsonObject();
+ config.addProperty("int_property", "100");
+ assertEquals(100, ConfigMapper.mapConfig(config, DummyConfig.class).int_property);
+ }
+ {
+ JsonObject config = new JsonObject();
+ config.addProperty("int_property", 100);
+ assertEquals(100, ConfigMapper.mapConfig(config, DummyConfig.class).int_property);
+ }
+
+ // valid for long
+ {
+ JsonObject config = new JsonObject();
+ config.addProperty("long_property", "100");
+ assertEquals(100, ConfigMapper.mapConfig(config, DummyConfig.class).long_property);
+ }
+ {
+ JsonObject config = new JsonObject();
+ config.addProperty("long_property", 100);
+ assertEquals(100, ConfigMapper.mapConfig(config, DummyConfig.class).long_property);
+ }
+
+ // valid for float
+ {
+ JsonObject config = new JsonObject();
+ config.addProperty("float_property", 100);
+ System.out.println(ConfigMapper.mapConfig(config, DummyConfig.class).float_property);
+ assertEquals((float) 100, ConfigMapper.mapConfig(config, DummyConfig.class).float_property, 0.001);
+ }
+ {
+ JsonObject config = new JsonObject();
+ config.addProperty("float_property", 3.14);
+ assertEquals((float) 3.14, ConfigMapper.mapConfig(config, DummyConfig.class).float_property, 0.001);
+ }
+ {
+ JsonObject config = new JsonObject();
+ config.addProperty("float_property", "100");
+ assertEquals((float) 100, ConfigMapper.mapConfig(config, DummyConfig.class).float_property, 0.001);
+ }
+ {
+ JsonObject config = new JsonObject();
+ config.addProperty("float_property", "3.14");
+ assertEquals((float) 3.14, ConfigMapper.mapConfig(config, DummyConfig.class).float_property, 0.001);
+ }
+
+ // valid double
+ {
+ JsonObject config = new JsonObject();
+ config.addProperty("double_property", 100);
+ assertEquals((double) 100, ConfigMapper.mapConfig(config, DummyConfig.class).double_property, 0.001);
+ }
+ {
+ JsonObject config = new JsonObject();
+ config.addProperty("double_property", 3.14);
+ assertEquals((double) 3.14, ConfigMapper.mapConfig(config, DummyConfig.class).double_property, 0.001);
+ }
+ {
+ JsonObject config = new JsonObject();
+ config.addProperty("double_property", "100");
+ assertEquals((double) 100, ConfigMapper.mapConfig(config, DummyConfig.class).double_property, 0.001);
+ }
+ {
+ JsonObject config = new JsonObject();
+ config.addProperty("double_property", "3.14");
+ assertEquals((double) 3.14, ConfigMapper.mapConfig(config, DummyConfig.class).double_property, 0.001);
+ }
+
+ // valid for bool
+ {
+ JsonObject config = new JsonObject();
+ config.addProperty("bool_property", "true");
+ assertEquals(true, ConfigMapper.mapConfig(config, DummyConfig.class).bool_property);
+ }
+ {
+ JsonObject config = new JsonObject();
+ config.addProperty("bool_property", "TRUE");
+ assertEquals(true, ConfigMapper.mapConfig(config, DummyConfig.class).bool_property);
+ }
+ {
+ JsonObject config = new JsonObject();
+ config.addProperty("bool_property", "false");
+ assertEquals(false, ConfigMapper.mapConfig(config, DummyConfig.class).bool_property);
+ }
+ {
+ JsonObject config = new JsonObject();
+ config.addProperty("bool_property", true);
+ assertEquals(true, ConfigMapper.mapConfig(config, DummyConfig.class).bool_property);
+ }
+ {
+ JsonObject config = new JsonObject();
+ config.addProperty("bool_property", false);
+ assertEquals(false, ConfigMapper.mapConfig(config, DummyConfig.class).bool_property);
+ }
+
+ // valid for string
+ {
+ JsonObject config = new JsonObject();
+ config.addProperty("string_property", "true");
+ assertEquals("true", ConfigMapper.mapConfig(config, DummyConfig.class).string_property);
+ }
+ {
+ JsonObject config = new JsonObject();
+ config.addProperty("string_property", true);
+ assertEquals("true", ConfigMapper.mapConfig(config, DummyConfig.class).string_property);
+ }
+ {
+ JsonObject config = new JsonObject();
+ config.addProperty("string_property", 100);
+ assertEquals("100", ConfigMapper.mapConfig(config, DummyConfig.class).string_property);
+ }
+ {
+ JsonObject config = new JsonObject();
+ config.addProperty("string_property", 3.14);
+ assertEquals("3.14", ConfigMapper.mapConfig(config, DummyConfig.class).string_property);
+ }
+ {
+ JsonObject config = new JsonObject();
+ config.addProperty("string_property", "hello");
+ assertEquals("hello", ConfigMapper.mapConfig(config, DummyConfig.class).string_property);
+ }
+
+ {
+ JsonObject config = new JsonObject();
+ config.add("string_property", null);
+ assertEquals(null, ConfigMapper.mapConfig(config, DummyConfig.class).string_property);
+ }
+
+ // valid for nullable long
+ {
+ JsonObject config = new JsonObject();
+ config.add("nullable_long_property", null);
+ assertEquals(null, ConfigMapper.mapConfig(config, DummyConfig.class).nullable_long_property);
+ }
+
+ {
+ JsonObject config = new JsonObject();
+ config.addProperty("nullable_long_property", 100);
+ assertEquals(new Long(100), ConfigMapper.mapConfig(config, DummyConfig.class).nullable_long_property);
+ }
+ }
+
+ @Test
+ public void testInvalidConversions() throws Exception {
+ String[] properties = new String[]{
+ "int_property",
+ "int_property",
+ "int_property",
+ "int_property",
+ "int_property",
+
+ "long_property",
+ "long_property",
+ "long_property",
+ "long_property",
+
+ "float_property",
+ "float_property",
+ "float_property",
+
+ "double_property",
+ "double_property",
+ "double_property",
+ };
+ Object[] values = new Object[]{
+ "abcd", // int
+ "", // int
+ true, // int
+ new Double(4.5), // int
+ new Long(1234567892342l), // int
+
+ "abcd", // long
+ "", // long
+ true, // long
+ new Double(4.5), // long
+
+ "abcd", // float
+ "", // float
+ true, // float
+
+ "abcd", // double
+ "", // double
+ true, // double
+ };
+
+ String[] expectedErrorMessages = new String[]{
+ "'int_property' must be of type int", // int
+ "'int_property' must be of type int", // int
+ "'int_property' must be of type int", // int
+ "'int_property' must be of type int", // int
+ "'int_property' must be of type int", // int
+
+ "'long_property' must be of type long", // long
+ "'long_property' must be of type long", // long
+ "'long_property' must be of type long", // long
+ "'long_property' must be of type long", // long
+
+ "'float_property' must be of type float", // float
+ "'float_property' must be of type float", // float
+ "'float_property' must be of type float", // float
+
+ "'double_property' must be of type double", // double
+ "'double_property' must be of type double", // double
+ "'double_property' must be of type double", // double
+ };
+
+ for (int i = 0; i < properties.length; i++) {
+ try {
+ System.out.println("Test case " + i);
+ JsonObject config = new JsonObject();
+ if (values[i] == null) {
+ config.add(properties[i], null);
+ }
+ else if (values[i] instanceof String) {
+ config.addProperty(properties[i], (String) values[i]);
+ } else if (values[i] instanceof Boolean) {
+ config.addProperty(properties[i], (Boolean) values[i]);
+ } else if (values[i] instanceof Number) {
+ config.addProperty(properties[i], (Number) values[i]);
+ } else {
+ throw new RuntimeException("Invalid type");
+ }
+ DummyConfig dc = ConfigMapper.mapConfig(config, DummyConfig.class);
+ fail();
+ } catch (InvalidConfigException e) {
+ assertEquals(expectedErrorMessages[i], e.getMessage());
+ }
+ }
+ }
+}
diff --git a/src/test/java/io/supertokens/test/ConfigTest2_21.java b/src/test/java/io/supertokens/test/ConfigTest2_21.java
index 73b886f6f..a4b28c060 100644
--- a/src/test/java/io/supertokens/test/ConfigTest2_21.java
+++ b/src/test/java/io/supertokens/test/ConfigTest2_21.java
@@ -24,6 +24,7 @@
import org.junit.*;
import org.junit.rules.TestRule;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
public class ConfigTest2_21 {
@@ -84,4 +85,22 @@ public void testThatNewConfigWorks() throws Exception {
EventAndException stopEvent = process.checkOrWaitForEvent(PROCESS_STATE.STOPPED);
assertNotNull(stopEvent);
}
+
+ @Test
+ public void testCoreConfigTypeValidationInConfigYaml() throws Exception {
+ Utils.setValueInConfig("access_token_validity", "abcd");
+
+ String[] args = { "../" };
+
+ TestingProcess process = TestingProcessManager.start(args);
+
+ EventAndException startEvent = process.checkOrWaitForEvent(PROCESS_STATE.INIT_FAILURE);
+ assertNotNull(startEvent);
+
+ assertEquals("io.supertokens.pluginInterface.exceptions.InvalidConfigException: 'access_token_validity' must be of type long", startEvent.exception.getMessage());
+
+ process.kill();
+ EventAndException stopEvent = process.checkOrWaitForEvent(PROCESS_STATE.STOPPED);
+ assertNotNull(stopEvent);
+ }
}
diff --git a/src/test/java/io/supertokens/test/FeatureFlagTest.java b/src/test/java/io/supertokens/test/FeatureFlagTest.java
index 98a39851e..408351098 100644
--- a/src/test/java/io/supertokens/test/FeatureFlagTest.java
+++ b/src/test/java/io/supertokens/test/FeatureFlagTest.java
@@ -94,7 +94,7 @@ public void noLicenseKeyShouldHaveEmptyFeatureFlag()
JsonObject stats = FeatureFlag.getInstance(process.getProcess()).getPaidFeatureStats();
Assert.assertEquals(stats.entrySet().size(), 1);
- Assert.assertEquals(stats.get("maus").getAsJsonArray().size(), 30);
+ Assert.assertEquals(stats.get("maus").getAsJsonArray().size(), 31);
Assert.assertEquals(stats.get("maus").getAsJsonArray().get(0).getAsInt(), 0);
Assert.assertEquals(stats.get("maus").getAsJsonArray().get(29).getAsInt(), 0);
@@ -189,7 +189,7 @@ public void testThatCallingGetFeatureFlagAPIReturnsMfaStats() throws Exception {
assert features.size() == 2; // MFA + MULTITENANCY
}
assert features.contains(new JsonPrimitive("mfa"));
- assert maus.size() == 30;
+ assert maus.size() == 31;
assert maus.get(0).getAsInt() == 0;
assert maus.get(29).getAsInt() == 0;
@@ -197,7 +197,7 @@ public void testThatCallingGetFeatureFlagAPIReturnsMfaStats() throws Exception {
int totalMfaUsers = mfaStats.get("totalUserCountWithMoreThanOneLoginMethodOrTOTPEnabled").getAsInt();
JsonArray mfaMaus = mfaStats.get("mauWithMoreThanOneLoginMethodOrTOTPEnabled").getAsJsonArray();
- assert mfaMaus.size() == 30;
+ assert mfaMaus.size() == 31;
assert mfaMaus.get(0).getAsInt() == 0;
assert mfaMaus.get(29).getAsInt() == 0;
@@ -250,7 +250,7 @@ public void testThatCallingGetFeatureFlagAPIReturnsMfaStats() throws Exception {
}
assert features.contains(new JsonPrimitive("mfa"));
- assert maus.size() == 30;
+ assert maus.size() == 31;
assert maus.get(0).getAsInt() == 2; // 2 users have signed up
assert maus.get(29).getAsInt() == 2;
@@ -258,7 +258,7 @@ public void testThatCallingGetFeatureFlagAPIReturnsMfaStats() throws Exception {
int totalMfaUsers = mfaStats.get("totalUserCountWithMoreThanOneLoginMethodOrTOTPEnabled").getAsInt();
JsonArray mfaMaus = mfaStats.get("mauWithMoreThanOneLoginMethodOrTOTPEnabled").getAsJsonArray();
- assert mfaMaus.size() == 30;
+ assert mfaMaus.size() == 31;
assert mfaMaus.get(0).getAsInt() == 1; // only 1 user has TOTP enabled
assert mfaMaus.get(29).getAsInt() == 1;
@@ -292,7 +292,7 @@ public void testThatCallingGetFeatureFlagAPIReturnsMfaStats() throws Exception {
}
assert features.contains(new JsonPrimitive("mfa"));
- assert maus.size() == 30;
+ assert maus.size() == 31;
assert maus.get(0).getAsInt() == 4; // 2 users have signed up
assert maus.get(29).getAsInt() == 4;
diff --git a/src/test/java/io/supertokens/test/PathRouterTest.java b/src/test/java/io/supertokens/test/PathRouterTest.java
index de865ed16..b58d80664 100644
--- a/src/test/java/io/supertokens/test/PathRouterTest.java
+++ b/src/test/java/io/supertokens/test/PathRouterTest.java
@@ -1533,7 +1533,9 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I
public void tenantNotFoundTest3()
throws InterruptedException, IOException, io.supertokens.httpRequest.HttpResponseException,
InvalidConfigException,
- io.supertokens.test.httpRequest.HttpResponseException, TenantOrAppNotFoundException {
+ io.supertokens.test.httpRequest.HttpResponseException, TenantOrAppNotFoundException,
+ InvalidProviderConfigException, StorageQueryException, FeatureNotEnabledException,
+ CannotModifyBaseConfigException, BadPermissionException {
String[] args = {"../"};
Utils.setValueInConfig("host", "\"0.0.0.0\"");
@@ -1556,15 +1558,26 @@ public void tenantNotFoundTest3()
StorageLayer.getStorage(new TenantIdentifier(null, null, null), process.getProcess())
.modifyConfigToAddANewUserPoolForTesting(tenantConfig, 2);
- Config.loadAllTenantConfig(process.getProcess(), new TenantConfig[]{
- new TenantConfig(new TenantIdentifier("localhost", null, null), new EmailPasswordConfig(false),
+ Multitenancy.addNewOrUpdateAppOrTenant(
+ process.getProcess(),
+ new TenantConfig(
+ new TenantIdentifier("localhost", null, null),
+ new EmailPasswordConfig(false),
new ThirdPartyConfig(false, new ThirdPartyConfig.Provider[0]),
new PasswordlessConfig(false),
null, null, tenantConfig),
- new TenantConfig(new TenantIdentifier("localhost", null, "t1"), new EmailPasswordConfig(false),
+ false
+ );
+ Multitenancy.addNewOrUpdateAppOrTenant(
+ process.getProcess(),
+ new TenantConfig(
+ new TenantIdentifier("localhost", null, "t1"),
+ new EmailPasswordConfig(false),
new ThirdPartyConfig(false, new ThirdPartyConfig.Provider[0]),
new PasswordlessConfig(false),
- null, null, tenantConfig)}, new ArrayList<>());
+ null, null, tenantConfig),
+ false
+ );
Webserver.getInstance(process.getProcess()).addAPI(new WebserverAPI(process.getProcess(), "") {
@@ -2788,7 +2801,9 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I
public void tenantNotFoundWithAppIdTest3()
throws InterruptedException, IOException, io.supertokens.httpRequest.HttpResponseException,
InvalidConfigException,
- io.supertokens.test.httpRequest.HttpResponseException, TenantOrAppNotFoundException {
+ io.supertokens.test.httpRequest.HttpResponseException, TenantOrAppNotFoundException,
+ InvalidProviderConfigException, StorageQueryException, FeatureNotEnabledException,
+ CannotModifyBaseConfigException, BadPermissionException {
String[] args = {"../"};
Utils.setValueInConfig("host", "\"0.0.0.0\"");
@@ -2811,15 +2826,26 @@ public void tenantNotFoundWithAppIdTest3()
StorageLayer.getStorage(new TenantIdentifier(null, null, null), process.getProcess())
.modifyConfigToAddANewUserPoolForTesting(tenantConfig, 2);
- Config.loadAllTenantConfig(process.getProcess(), new TenantConfig[]{
- new TenantConfig(new TenantIdentifier("localhost", null, null), new EmailPasswordConfig(false),
+ Multitenancy.addNewOrUpdateAppOrTenant(
+ process.getProcess(),
+ new TenantConfig(
+ new TenantIdentifier("localhost", null, null),
+ new EmailPasswordConfig(false),
new ThirdPartyConfig(false, new ThirdPartyConfig.Provider[0]),
new PasswordlessConfig(false),
null, null, tenantConfig),
- new TenantConfig(new TenantIdentifier("localhost", "app1", "t1"), new EmailPasswordConfig(false),
+ false
+ );
+ Multitenancy.addNewOrUpdateAppOrTenant(
+ process.getProcess(),
+ new TenantConfig(
+ new TenantIdentifier("localhost", "app1", "t1"),
+ new EmailPasswordConfig(false),
new ThirdPartyConfig(false, new ThirdPartyConfig.Provider[0]),
new PasswordlessConfig(false),
- null, null, tenantConfig)}, new ArrayList<>());
+ null, null, tenantConfig),
+ false
+ );
Webserver.getInstance(process.getProcess()).addAPI(new WebserverAPI(process.getProcess(), "") {
diff --git a/src/test/java/io/supertokens/test/StorageLayerTest.java b/src/test/java/io/supertokens/test/StorageLayerTest.java
index eb88558d8..d649cc7fb 100644
--- a/src/test/java/io/supertokens/test/StorageLayerTest.java
+++ b/src/test/java/io/supertokens/test/StorageLayerTest.java
@@ -97,7 +97,7 @@ public void totpCodeLengthTest() throws Exception {
// This error will be different in Postgres and MySQL
// We added (CHECK (LENGTH(code) <= 8)) to the table definition in SQLite
String totpUsedCodeTable = Config.getConfig(start).getTotpUsedCodesTable();
- assert e.getMessage().contains("CHECK constraint failed: " + totpUsedCodeTable) || e.getMessage().contains("LENGTH(code) <= 8");
+ assert e.getMessage().contains("CHECK constraint failed: ");
}
// Try code with length < 8
diff --git a/src/test/java/io/supertokens/test/TelemetryTest.java b/src/test/java/io/supertokens/test/TelemetryTest.java
index 95e312ac7..968d154f4 100644
--- a/src/test/java/io/supertokens/test/TelemetryTest.java
+++ b/src/test/java/io/supertokens/test/TelemetryTest.java
@@ -21,7 +21,10 @@
import io.supertokens.ProcessState;
import io.supertokens.ProcessState.PROCESS_STATE;
import io.supertokens.cronjobs.telemetry.Telemetry;
+import io.supertokens.dashboard.Dashboard;
import io.supertokens.httpRequest.HttpRequestMocking;
+import io.supertokens.pluginInterface.STORAGE_TYPE;
+import io.supertokens.storageLayer.StorageLayer;
import io.supertokens.test.TestingProcessManager.TestingProcess;
import io.supertokens.version.Version;
import org.junit.AfterClass;
@@ -111,6 +114,16 @@ public void testThatTelemetryWorks() throws Exception {
String[] args = { "../" };
TestingProcess process = TestingProcessManager.start(args, false);
+ process.startProcess();
+ assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED));
+
+ if (StorageLayer.getBaseStorage(process.getProcess()).getType() == STORAGE_TYPE.SQL) {
+ Dashboard.signUpDashboardUser(process.getProcess(), "test@example.com", "password123");
+ }
+
+ // Restarting the process to send telemetry again
+ process.kill(false);
+ process = TestingProcessManager.start(args, false);
ByteArrayOutputStream output = new ByteArrayOutputStream();
final HttpURLConnection mockCon = mock(HttpURLConnection.class);
@@ -149,13 +162,26 @@ protected URLConnection openConnection(URL u) {
assertNotNull(process.checkOrWaitForEvent(PROCESS_STATE.SENT_TELEMETRY));
JsonObject telemetryData = new JsonParser().parse(output.toString()).getAsJsonObject();
+ assertEquals(7, telemetryData.entrySet().size());
assertTrue(telemetryData.has("telemetryId"));
assertEquals(telemetryData.get("superTokensVersion").getAsString(),
Version.getVersion(process.getProcess()).getCoreVersion());
assertEquals(telemetryData.get("appId").getAsString(), "public");
assertEquals(telemetryData.get("connectionUriDomain").getAsString(), "");
- assertTrue(telemetryData.has("mau"));
+ assertTrue(telemetryData.has("maus"));
+ assertTrue(telemetryData.has("dashboardUserEmails"));
+
+ if (StorageLayer.getBaseStorage(process.getProcess()).getType() == STORAGE_TYPE.SQL) {
+ assertEquals(1, telemetryData.get("dashboardUserEmails").getAsJsonArray().size());
+ assertEquals("test@example.com", telemetryData.get("dashboardUserEmails").getAsJsonArray().get(0).getAsString());
+ assertEquals(31, telemetryData.get("maus").getAsJsonArray().size());
+ assertEquals(0, telemetryData.get("usersCount").getAsInt());
+ } else {
+ assertEquals(0, telemetryData.get("dashboardUserEmails").getAsJsonArray().size());
+ assertEquals(0, telemetryData.get("maus").getAsJsonArray().size());
+ assertEquals(-1, telemetryData.get("usersCount").getAsInt());
+ }
process.kill();
assertNotNull(process.checkOrWaitForEvent(PROCESS_STATE.STOPPED));
diff --git a/src/test/java/io/supertokens/test/accountlinking/TestGetUserSpeed.java b/src/test/java/io/supertokens/test/accountlinking/TestGetUserSpeed.java
index de6a8f890..237e26ac1 100644
--- a/src/test/java/io/supertokens/test/accountlinking/TestGetUserSpeed.java
+++ b/src/test/java/io/supertokens/test/accountlinking/TestGetUserSpeed.java
@@ -59,18 +59,8 @@ public void beforeEach() {
Utils.reset();
}
- @Test
- public void testUserCreationLinkingAndGetByIdSpeeds() throws Exception {
- String[] args = {"../"};
- TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false);
- Utils.setValueInConfig("postgresql_connection_pool_size", "100");
- Utils.setValueInConfig("mysql_connection_pool_size", "100");
-
- 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));
+ public void testUserCreationLinkingAndGetByIdSpeedsCommon(TestingProcessManager.TestingProcess process,
+ long createTime, long linkingTime, long getTime) throws Exception {
if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) {
return;
@@ -81,6 +71,7 @@ public void testUserCreationLinkingAndGetByIdSpeeds() throws Exception {
}
int numberOfUsers = 10000;
+
List userIds = new ArrayList<>();
List userIds2 = new ArrayList<>();
Lock lock = new ReentrantLock();
@@ -108,7 +99,7 @@ public void testUserCreationLinkingAndGetByIdSpeeds() throws Exception {
long end = System.currentTimeMillis();
System.out.println("Created users " + numberOfUsers + " in " + (end - start) + "ms");
- assert end - start < 25000; // 25 sec
+ assert end - start < createTime; // 25 sec
}
Thread.sleep(10000); // wait for index
@@ -148,7 +139,7 @@ public void testUserCreationLinkingAndGetByIdSpeeds() throws Exception {
es.awaitTermination(5, TimeUnit.MINUTES);
long end = System.currentTimeMillis();
System.out.println("Accounts linked in " + (end - start) + "ms");
- assert end - start < 50000; // 50 sec
+ assert end - start < linkingTime; // 50 sec
}
Thread.sleep(10000); // wait for index
@@ -169,10 +160,44 @@ public void testUserCreationLinkingAndGetByIdSpeeds() throws Exception {
es.awaitTermination(5, TimeUnit.MINUTES);
long end = System.currentTimeMillis();
System.out.println("Time taken for " + numberOfUsers + " users: " + (end - start) + "ms");
- assert end - start < 20000; // 20 sec
+ assert end - start < getTime; // 20 sec
}
process.kill();
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED));
}
+
+ @Test
+ public void testUserCreationLinkingAndGetByIdSpeedsWithoutMinIdle() throws Exception {
+ String[] args = {"../"};
+ TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false);
+ Utils.setValueInConfig("postgresql_connection_pool_size", "100");
+ Utils.setValueInConfig("mysql_connection_pool_size", "100");
+
+ 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));
+
+ testUserCreationLinkingAndGetByIdSpeedsCommon(process, 25000, 50000, 20000);
+ }
+
+ @Test
+ public void testUserCreationLinkingAndGetByIdSpeedsWithMinIdle() throws Exception {
+ String[] args = {"../"};
+ TestingProcessManager.TestingProcess process = TestingProcessManager.start(args, false);
+ Utils.setValueInConfig("postgresql_connection_pool_size", "100");
+ Utils.setValueInConfig("mysql_connection_pool_size", "100");
+ Utils.setValueInConfig("postgresql_minimum_idle_connections", "1");
+ Utils.setValueInConfig("mysql_minimum_idle_connections", "1");
+
+ 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));
+
+ testUserCreationLinkingAndGetByIdSpeedsCommon(process, 60000, 50000, 20000);
+ }
}
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 99cf76376..bb2bf18be 100644
--- a/src/test/java/io/supertokens/test/accountlinking/api/UserPaginationTest.java
+++ b/src/test/java/io/supertokens/test/accountlinking/api/UserPaginationTest.java
@@ -17,6 +17,7 @@
package io.supertokens.test.accountlinking.api;
import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import io.supertokens.Main;
import io.supertokens.ProcessState;
@@ -50,8 +51,8 @@
import java.security.NoSuchAlgorithmException;
import java.util.*;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.*;
+import static org.junit.Assert.assertTrue;
public class UserPaginationTest {
@Rule
@@ -380,4 +381,43 @@ public void testUserPaginationWithManyUsers() throws Exception {
process.kill();
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED));
}
+
+ @Test
+ public void testUserPaginationFromOldVersion() 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", "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 primaryUser = AuthRecipe.createPrimaryUser(process.getProcess(), user2.getSupertokensUserId()).user;
+ AuthRecipe.linkAccounts(process.getProcess(), user1.getSupertokensUserId(), primaryUser.getSupertokensUserId());
+ AuthRecipe.linkAccounts(process.getProcess(), user3.getSupertokensUserId(), primaryUser.getSupertokensUserId());
+
+ Map params = new HashMap<>();
+ JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "",
+ "http://localhost:3567/users", params, 1000, 1000, null,
+ SemVer.v3_0.get(), "");
+
+ assertEquals(1, response.get("users").getAsJsonArray().size());
+ JsonObject user = response.get("users").getAsJsonArray().get(0).getAsJsonObject().get("user").getAsJsonObject();
+
+ assertEquals(user1.getSupertokensUserId(), user.get("id").getAsString()); // oldest login method
+
+ process.kill();
+ assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED));
+ }
}
diff --git a/src/test/java/io/supertokens/test/dashboard/DashboardTest.java b/src/test/java/io/supertokens/test/dashboard/DashboardTest.java
index 7bb9211a4..eeaa3b447 100644
--- a/src/test/java/io/supertokens/test/dashboard/DashboardTest.java
+++ b/src/test/java/io/supertokens/test/dashboard/DashboardTest.java
@@ -290,7 +290,7 @@ public void testDashboardUsageStats() throws Exception {
JsonObject usageStats = response.get("usageStats").getAsJsonObject();
JsonArray mauArr = usageStats.get("maus").getAsJsonArray();
assertEquals(1, usageStats.entrySet().size());
- assertEquals(30, mauArr.size());
+ assertEquals(31, mauArr.size());
assertEquals(0, mauArr.get(0).getAsInt());
assertEquals(0, mauArr.get(29).getAsInt());
}
@@ -312,7 +312,7 @@ public void testDashboardUsageStats() throws Exception {
JsonObject usageStats = response.get("usageStats").getAsJsonObject();
JsonArray mauArr = usageStats.get("maus").getAsJsonArray();
assertEquals(1, usageStats.entrySet().size());
- assertEquals(30, mauArr.size());
+ assertEquals(31, mauArr.size());
assertEquals(0, mauArr.get(0).getAsInt());
assertEquals(0, mauArr.get(29).getAsInt());
}
@@ -338,7 +338,7 @@ public void testDashboardUsageStats() throws Exception {
JsonObject usageStats = response.get("usageStats").getAsJsonObject();
JsonObject dashboardLoginObject = usageStats.get("dashboard_login").getAsJsonObject();
assertEquals(2, usageStats.entrySet().size());
- assertEquals(30, usageStats.get("maus").getAsJsonArray().size());
+ assertEquals(31, usageStats.get("maus").getAsJsonArray().size());
assertEquals(1, dashboardLoginObject.entrySet().size());
assertEquals(1, dashboardLoginObject.get("user_count").getAsInt());
}
@@ -366,7 +366,7 @@ public void testDashboardUsageStats() throws Exception {
JsonObject usageStats = response.get("usageStats").getAsJsonObject();
JsonObject dashboardLoginObject = usageStats.get("dashboard_login").getAsJsonObject();
assertEquals(2, usageStats.entrySet().size());
- assertEquals(30, usageStats.get("maus").getAsJsonArray().size());
+ assertEquals(31, usageStats.get("maus").getAsJsonArray().size());
assertEquals(1, dashboardLoginObject.entrySet().size());
assertEquals(4, dashboardLoginObject.get("user_count").getAsInt());
}
diff --git a/src/test/java/io/supertokens/test/multitenant/ConfigTest.java b/src/test/java/io/supertokens/test/multitenant/ConfigTest.java
index 421e3b542..23675314c 100644
--- a/src/test/java/io/supertokens/test/multitenant/ConfigTest.java
+++ b/src/test/java/io/supertokens/test/multitenant/ConfigTest.java
@@ -1914,6 +1914,7 @@ public void testAllConflictingConfigs() throws Exception {
"argon2_memory_kb",
"argon2_parallelism",
"bcrypt_log_rounds",
+ "supertokens_saas_load_only_cud"
};
Object[] disallowedValues = new Object[]{
3567, // port
@@ -1930,6 +1931,7 @@ public void testAllConflictingConfigs() throws Exception {
87795, // argon2_memory_kb
2, // argon2_parallelism
11, // bcrypt_log_rounds
+ "mydomain.com", // supertokens_saas_load_only_cud
};
process.kill();
@@ -1995,7 +1997,7 @@ public void testAllConflictingConfigs() throws Exception {
new Object[]{true, false}, // disable_telemetry
new Object[]{"BCRYPT", "ARGON2"}, // password_hashing_alg
new Object[]{"abcd1234abcd1234abcd1234abcd1234", "qwer1234qwer1234qwer1234qwer1234"}, // firebase_password_hashing_signer_key
- new Object[]{"2.21", "3.0"} // supertokens_max_cdi_version
+ new Object[]{"2.21", "3.0"}, // supertokens_max_cdi_version
};
for (int i=0; i> uniqueUserPoolIdsTenants = StorageLayer.getTenantsWithUniqueUserPoolId(process.getProcess());
+ Cronjobs.addCronjob(process.getProcess(), LoadOnlyCUDTest.PerAppCronjob.getInstance(process.getProcess(), uniqueUserPoolIdsTenants));
+
+ Thread.sleep(3000);
+ Set appIdentifiersFromCron = PerAppCronjob.getInstance(process.getProcess(), uniqueUserPoolIdsTenants).appIdentifiers;
+ assertEquals(2, appIdentifiersFromCron.size());
+ for (AppIdentifier app : appIdentifiersFromCron) {
+ assertNotEquals("localhost.org", app.getConnectionUriDomain());
+ }
+
+ process.kill();
+ assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED));
+ }
+
+ static class PerAppCronjob extends CronTask {
+ private static final String RESOURCE_ID = "io.supertokens.test.CronjobTest.NormalCronjob";
+
+ private PerAppCronjob(Main main, List> tenantsInfo) {
+ super("PerTenantCronjob", main, tenantsInfo, true);
+ }
+
+ Set appIdentifiers = new HashSet<>();
+
+ public static LoadOnlyCUDTest.PerAppCronjob getInstance(Main main, List> tenantsInfo) {
+ try {
+ return (LoadOnlyCUDTest.PerAppCronjob) main.getResourceDistributor().getResource(new TenantIdentifier(null, null, null), RESOURCE_ID);
+ } catch (TenantOrAppNotFoundException e) {
+ return (LoadOnlyCUDTest.PerAppCronjob) main.getResourceDistributor()
+ .setResource(new TenantIdentifier(null, null, null), RESOURCE_ID, new LoadOnlyCUDTest.PerAppCronjob(main, tenantsInfo));
+ }
+ }
+
+ @Override
+ public int getIntervalTimeSeconds() {
+ return 1;
+ }
+
+ @Override
+ public int getInitialWaitTimeSeconds() {
+ return 0;
+ }
+
+ @Override
+ protected void doTaskPerApp(AppIdentifier app) throws Exception {
+ appIdentifiers.add(app);
+ }
+ }
+}
\ No newline at end of file