diff --git a/CHANGELOG.md b/CHANGELOG.md index 1701e98ec..c19515d0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [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. diff --git a/build.gradle b/build.gradle index e43503f26..d182ca9b6 100644 --- a/build.gradle +++ b/build.gradle @@ -19,7 +19,7 @@ compileTestJava { options.encoding = "UTF-8" } // } //} -version = "7.0.17" +version = "7.0.18" repositories { @@ -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.14.2' // 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..2b61a0da1 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.14.2' // 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/ee/build.gradle b/ee/build.gradle index 94aeed97c..2a32806f6 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.14.2' // 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/implementationDependencies.json b/implementationDependencies.json index e0ab94e68..5d5c4f0dc 100644 --- a/implementationDependencies.json +++ b/implementationDependencies.json @@ -12,34 +12,34 @@ "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/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.0/snakeyaml-2.0.jar", + "name": "SnakeYAML 2.0", + "src": "https://repo1.maven.org/maven2/org/yaml/snakeyaml/2.0/snakeyaml-2.0-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", @@ -52,9 +52,9 @@ "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/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/src/main/java/io/supertokens/cronjobs/telemetry/Telemetry.java b/src/main/java/io/supertokens/cronjobs/telemetry/Telemetry.java index 727d2f5ec..123c95db7 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(); + + 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); + 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/webserver/WebserverAPI.java b/src/main/java/io/supertokens/webserver/WebserverAPI.java index a87130a75..188776eb4 100644 --- a/src/main/java/io/supertokens/webserver/WebserverAPI.java +++ b/src/main/java/io/supertokens/webserver/WebserverAPI.java @@ -30,6 +30,7 @@ import io.supertokens.pluginInterface.Storage; import io.supertokens.pluginInterface.emailpassword.exceptions.UnknownUserIdException; 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.TenantIdentifierWithStorage; @@ -343,6 +344,15 @@ protected AppIdentifierWithStorage getAppIdentifierWithStorageFromRequestAndEnfo storage, storages); } + protected AppIdentifierWithStorage getPublicTenantStorage(HttpServletRequest req) + throws ServletException, TenantOrAppNotFoundException { + AppIdentifier appIdentifier = new AppIdentifier(this.getConnectionUriDomain(req), this.getAppId(req)); + + Storage storage = StorageLayer.getStorage(appIdentifier.getAsPublicTenantIdentifier(), main); + + return appIdentifier.withStorage(storage); + } + protected TenantIdentifierWithStorageAndUserIdMapping getTenantIdentifierWithStorageAndUserIdMappingFromRequest( HttpServletRequest req, String userId, UserIdType userIdType) throws StorageQueryException, TenantOrAppNotFoundException, UnknownUserIdException, ServletException { diff --git a/src/main/java/io/supertokens/webserver/api/emailpassword/SignInAPI.java b/src/main/java/io/supertokens/webserver/api/emailpassword/SignInAPI.java index cf57898cd..720869e95 100644 --- a/src/main/java/io/supertokens/webserver/api/emailpassword/SignInAPI.java +++ b/src/main/java/io/supertokens/webserver/api/emailpassword/SignInAPI.java @@ -79,7 +79,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I password); io.supertokens.useridmapping.UserIdMapping.populateExternalUserIdForUsers(tenantIdentifierWithStorage, new AuthRecipeUserInfo[]{user}); - ActiveUsers.updateLastActive(tenantIdentifierWithStorage.toAppIdentifierWithStorage(), main, + ActiveUsers.updateLastActive(this.getPublicTenantStorage(req), main, user.getSupertokensUserId()); // use the internal user id JsonObject result = new JsonObject(); diff --git a/src/main/java/io/supertokens/webserver/api/emailpassword/SignUpAPI.java b/src/main/java/io/supertokens/webserver/api/emailpassword/SignUpAPI.java index bb915fadb..11d510dad 100644 --- a/src/main/java/io/supertokens/webserver/api/emailpassword/SignUpAPI.java +++ b/src/main/java/io/supertokens/webserver/api/emailpassword/SignUpAPI.java @@ -80,7 +80,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I TenantIdentifierWithStorage tenant = this.getTenantIdentifierWithStorageFromRequest(req); AuthRecipeUserInfo user = EmailPassword.signUp(tenant, super.main, normalisedEmail, password); - ActiveUsers.updateLastActive(this.getAppIdentifierWithStorage(req), main, user.getSupertokensUserId()); + ActiveUsers.updateLastActive(this.getPublicTenantStorage(req), main, user.getSupertokensUserId()); JsonObject result = new JsonObject(); result.addProperty("status", "OK"); 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 6329a81d2..4b622328b 100644 --- a/src/main/java/io/supertokens/webserver/api/passwordless/ConsumeCodeAPI.java +++ b/src/main/java/io/supertokens/webserver/api/passwordless/ConsumeCodeAPI.java @@ -91,7 +91,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I getVersionFromRequest(req).greaterThanOrEqualTo(SemVer.v4_0)); io.supertokens.useridmapping.UserIdMapping.populateExternalUserIdForUsers(this.getTenantIdentifierWithStorageFromRequest(req), new AuthRecipeUserInfo[]{consumeCodeResponse.user}); - ActiveUsers.updateLastActive(this.getAppIdentifierWithStorage(req), main, consumeCodeResponse.user.getSupertokensUserId()); + ActiveUsers.updateLastActive(this.getPublicTenantStorage(req), main, consumeCodeResponse.user.getSupertokensUserId()); JsonObject result = new JsonObject(); result.addProperty("status", "OK"); diff --git a/src/main/java/io/supertokens/webserver/api/session/RefreshSessionAPI.java b/src/main/java/io/supertokens/webserver/api/session/RefreshSessionAPI.java index ee92da0bd..1749e154b 100644 --- a/src/main/java/io/supertokens/webserver/api/session/RefreshSessionAPI.java +++ b/src/main/java/io/supertokens/webserver/api/session/RefreshSessionAPI.java @@ -90,10 +90,10 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I this.getAppIdentifierWithStorage(req), sessionInfo.session.userId, UserIdType.ANY); if (userIdMapping != null) { - ActiveUsers.updateLastActive(appIdentifierWithStorage, main, + ActiveUsers.updateLastActive(this.getPublicTenantStorage(req), main, userIdMapping.superTokensUserId); } else { - ActiveUsers.updateLastActive(appIdentifierWithStorage, main, + ActiveUsers.updateLastActive(this.getPublicTenantStorage(req), main, sessionInfo.session.userId); } } catch (StorageQueryException ignored) { diff --git a/src/main/java/io/supertokens/webserver/api/session/SessionAPI.java b/src/main/java/io/supertokens/webserver/api/session/SessionAPI.java index 4263285c2..7691ee81e 100644 --- a/src/main/java/io/supertokens/webserver/api/session/SessionAPI.java +++ b/src/main/java/io/supertokens/webserver/api/session/SessionAPI.java @@ -110,10 +110,10 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I this.getAppIdentifierWithStorage(req), sessionInfo.session.userId, UserIdType.ANY); if (userIdMapping != null) { - ActiveUsers.updateLastActive(this.getAppIdentifierWithStorage(req), main, + ActiveUsers.updateLastActive(this.getPublicTenantStorage(req), main, userIdMapping.superTokensUserId); } else { - ActiveUsers.updateLastActive(this.getAppIdentifierWithStorage(req), main, + ActiveUsers.updateLastActive(this.getPublicTenantStorage(req), main, sessionInfo.session.userId); } } catch (StorageQueryException ignored) { diff --git a/src/main/java/io/supertokens/webserver/api/session/SessionRemoveAPI.java b/src/main/java/io/supertokens/webserver/api/session/SessionRemoveAPI.java index 127e0b5fc..22fba74cf 100644 --- a/src/main/java/io/supertokens/webserver/api/session/SessionRemoveAPI.java +++ b/src/main/java/io/supertokens/webserver/api/session/SessionRemoveAPI.java @@ -116,10 +116,10 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I this.getAppIdentifierWithStorage(req), userId, UserIdType.ANY); if (userIdMapping != null) { - ActiveUsers.updateLastActive(this.getAppIdentifierWithStorage(req), main, + ActiveUsers.updateLastActive(this.getPublicTenantStorage(req), main, userIdMapping.superTokensUserId); } else { - ActiveUsers.updateLastActive(this.getAppIdentifierWithStorage(req), main, userId); + ActiveUsers.updateLastActive(this.getPublicTenantStorage(req), main, userId); } } catch (StorageQueryException ignored) { } diff --git a/src/main/java/io/supertokens/webserver/api/thirdparty/SignInUpAPI.java b/src/main/java/io/supertokens/webserver/api/thirdparty/SignInUpAPI.java index 06042eb23..34a70df28 100644 --- a/src/main/java/io/supertokens/webserver/api/thirdparty/SignInUpAPI.java +++ b/src/main/java/io/supertokens/webserver/api/thirdparty/SignInUpAPI.java @@ -81,7 +81,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I thirdPartyUserId, email, isEmailVerified); UserIdMapping.populateExternalUserIdForUsers(this.getTenantIdentifierWithStorageFromRequest(req), new AuthRecipeUserInfo[]{response.user}); - ActiveUsers.updateLastActive(this.getAppIdentifierWithStorage(req), main, response.user.getSupertokensUserId()); + ActiveUsers.updateLastActive(this.getPublicTenantStorage(req), main, response.user.getSupertokensUserId()); JsonObject result = new JsonObject(); result.addProperty("status", "OK"); @@ -139,7 +139,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I email, isEmailVerified); UserIdMapping.populateExternalUserIdForUsers(this.getTenantIdentifierWithStorageFromRequest(req), new AuthRecipeUserInfo[]{response.user}); - ActiveUsers.updateLastActive(this.getAppIdentifierWithStorage(req), main, response.user.getSupertokensUserId()); + ActiveUsers.updateLastActive(this.getPublicTenantStorage(req), main, response.user.getSupertokensUserId()); JsonObject result = new JsonObject(); result.addProperty("status", "OK"); diff --git a/src/test/java/io/supertokens/test/ActiveUsersTest.java b/src/test/java/io/supertokens/test/ActiveUsersTest.java index c88a5bbef..108bc38ee 100644 --- a/src/test/java/io/supertokens/test/ActiveUsersTest.java +++ b/src/test/java/io/supertokens/test/ActiveUsersTest.java @@ -4,10 +4,15 @@ import io.supertokens.ActiveUsers; import io.supertokens.Main; import io.supertokens.ProcessState; +import io.supertokens.featureflag.EE_FEATURES; +import io.supertokens.featureflag.FeatureFlagTestContent; import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.httpRequest.HttpRequestForTesting; import io.supertokens.test.httpRequest.HttpResponseException; +import io.supertokens.test.multitenant.api.TestMultitenancyAPIHelper; +import io.supertokens.utils.SemVer; import org.junit.AfterClass; import org.junit.Before; import org.junit.Rule; @@ -212,4 +217,80 @@ public void activeUserCountAPITest() throws Exception { assert res.get("count").getAsInt() == 2; } + @Test + public void testThatActiveUserDataIsSavedInPublicTenantStorage() throws Exception { + String[] args = {"../"}; + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{EE_FEATURES.MULTI_TENANCY}); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + { // Create a tenant + JsonObject coreConfig = new JsonObject(); + + StorageLayer.getStorage(new TenantIdentifier(null, null, null), process.getProcess()) + .modifyConfigToAddANewUserPoolForTesting(coreConfig, 1); + + TestMultitenancyAPIHelper.createTenant( + process.getProcess(), + new TenantIdentifier(null, null, null), + "t1", true, true, true, + coreConfig); + } + + { // no active users yet + HashMap params = new HashMap<>(); + params.put("since", "0"); + JsonObject res = HttpRequestForTesting.sendGETRequest( + process.getProcess(), + "", + "http://localhost:3567/users/count/active", + params, + 1000, + 1000, + null, + Utils.getCdiVersionStringLatestForTests(), + ""); + + assert res.get("status").getAsString().equals("OK"); + assert res.get("count").getAsInt() == 0; + } + + { // Sign up, which updates active users + JsonObject responseBody = new JsonObject(); + responseBody.addProperty("email", "random@gmail.com"); + responseBody.addProperty("password", "validPass123"); + + JsonObject signInResponse = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/t1/recipe/signup", responseBody, 1000, 1000, null, SemVer.v4_0.get(), + "emailpassword"); + } + + { // 1 active user in the public tenant + HashMap params = new HashMap<>(); + params.put("since", "0"); + JsonObject res = HttpRequestForTesting.sendGETRequest( + process.getProcess(), + "", + "http://localhost:3567/users/count/active", + params, + 1000, + 1000, + null, + Utils.getCdiVersionStringLatestForTests(), + ""); + + assert res.get("status").getAsString().equals("OK"); + assert res.get("count").getAsInt() == 1; + } + + process.kill(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } } diff --git a/src/test/java/io/supertokens/test/TelemetryTest.java b/src/test/java/io/supertokens/test/TelemetryTest.java index 95e312ac7..d04e447c0 100644 --- a/src/test/java/io/supertokens/test/TelemetryTest.java +++ b/src/test/java/io/supertokens/test/TelemetryTest.java @@ -21,6 +21,7 @@ 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.test.TestingProcessManager.TestingProcess; import io.supertokens.version.Version; @@ -111,6 +112,14 @@ public void testThatTelemetryWorks() throws Exception { String[] args = { "../" }; TestingProcess process = TestingProcessManager.start(args, false); + process.startProcess(); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + 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 +158,18 @@ 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")); + assertEquals(1, telemetryData.get("dashboardUserEmails").getAsJsonArray().size()); + assertEquals("test@example.com", telemetryData.get("dashboardUserEmails").getAsJsonArray().get(0).getAsString()); + assertEquals(30, telemetryData.get("maus").getAsJsonArray().size()); process.kill(); assertNotNull(process.checkOrWaitForEvent(PROCESS_STATE.STOPPED));