Skip to content

Commit

Permalink
feat: Add support for active users stats (#585)
Browse files Browse the repository at this point in the history
* feat: Add support for active users stats

* feat: Monitor active users for all auth recipes and session recipe
  • Loading branch information
KShivendu authored Mar 21, 2023
1 parent e6844bf commit 4e13470
Show file tree
Hide file tree
Showing 14 changed files with 154 additions and 4 deletions.
14 changes: 14 additions & 0 deletions src/main/java/io/supertokens/ActiveUsers.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.supertokens;

import io.supertokens.pluginInterface.exceptions.StorageQueryException;
import io.supertokens.storageLayer.StorageLayer;

public class ActiveUsers {

public static void updateLastActive(Main main, String userId) {
try {
StorageLayer.getActiveUsersStorage(main).updateLastActive(userId);
} catch (StorageQueryException ignored) {
}
}
}
21 changes: 20 additions & 1 deletion src/main/java/io/supertokens/inmemorydb/Start.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import io.supertokens.emailverification.exception.EmailAlreadyVerifiedException;
import io.supertokens.inmemorydb.config.Config;
import io.supertokens.inmemorydb.queries.*;
import io.supertokens.pluginInterface.ActiveUsersStorage;
import io.supertokens.pluginInterface.KeyValueInfo;
import io.supertokens.pluginInterface.LOG_LEVEL;
import io.supertokens.pluginInterface.RECIPE_ID;
Expand Down Expand Up @@ -99,7 +100,7 @@
public class Start
implements SessionSQLStorage, EmailPasswordSQLStorage, EmailVerificationSQLStorage, ThirdPartySQLStorage,
JWTRecipeSQLStorage, PasswordlessSQLStorage, UserMetadataSQLStorage, UserRolesSQLStorage, UserIdMappingStorage,
DashboardSQLStorage, TOTPSQLStorage {
DashboardSQLStorage, TOTPSQLStorage, ActiveUsersStorage {

private static final Object appenderLock = new Object();
private static final String APP_ID_KEY_NAME = "app_id";
Expand Down Expand Up @@ -441,6 +442,24 @@ public boolean doesUserIdExist(String userId) throws StorageQueryException {
}
}

@Override
public void updateLastActive(String userId) throws StorageQueryException {
try {
ActiveUsersQueries.updateUserLastActive(this, userId);
} catch (SQLException e) {
throw new StorageQueryException(e);
}
}

@Override
public int countUsersActiveSince(long time) throws StorageQueryException {
try {
return ActiveUsersQueries.countUsersActiveSince(this, time);
} catch (SQLException e) {
throw new StorageQueryException(e);
}
}

@Override
public SessionInfo getSessionInfo_Transaction(TransactionConnection con, String sessionHandle)
throws StorageQueryException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ public String getUsersTable() {
return "all_auth_recipe_users";
}

public String getUserLastActiveTable() {
return "user_last_active";
}

public String getAccessTokenSigningKeysTable() {
return "session_access_token_signing_keys";
}
Expand Down Expand Up @@ -102,11 +106,11 @@ public String getTotpUsedCodesTable() {
return "totp_used_codes";
}

public String getDashboardUsersTable(){
public String getDashboardUsersTable() {
return "dashboard_users";
}

public String getDashboardSessionsTable(){
public String getDashboardSessionsTable() {
return "dashboard_user_sessions";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package io.supertokens.inmemorydb.queries;

import java.sql.SQLException;

import io.supertokens.inmemorydb.config.Config;
import io.supertokens.pluginInterface.exceptions.StorageQueryException;
import io.supertokens.inmemorydb.Start;

import static io.supertokens.inmemorydb.QueryExecutorTemplate.execute;
import static io.supertokens.inmemorydb.QueryExecutorTemplate.update;

public class ActiveUsersQueries {
static String getQueryToCreateUserLastActiveTable(Start start) {
return "CREATE TABLE IF NOT EXISTS " + Config.getConfig(start).getUserLastActiveTable() + " ("
+ "user_id VARCHAR(128),"
+ "last_active_time BIGINT UNSIGNED," + "PRIMARY KEY(user_id)" + " );";
}

public static int countUsersActiveSince(Start start, long sinceTime) throws SQLException, StorageQueryException {
String QUERY = "SELECT COUNT(*) as total FROM " + Config.getConfig(start).getUserLastActiveTable()
+ " WHERE last_active_time >= ?";

return execute(start, QUERY, pst -> pst.setLong(1, sinceTime), result -> {
if (result.next()) {
return result.getInt("total");
}
return 0;
});
}

public static int updateUserLastActive(Start start, String userId) throws SQLException, StorageQueryException {
String QUERY = "INSERT INTO " + Config.getConfig(start).getUserLastActiveTable()
+ "(user_id, last_active_time) VALUES(?, ?) ON CONFLICT(user_id) DO UPDATE SET last_active_time = ?";

long now = System.currentTimeMillis();
return update(start, QUERY, pst -> {
pst.setString(1, userId);
pst.setLong(2, now);
pst.setLong(3, now);
});
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ public static void createTablesIfNotExists(Start start, Main main) throws SQLExc
update(start, getQueryToCreateUserPaginationIndex(start), NO_OP_SETTER);
}

if (!doesTableExists(start, Config.getConfig(start).getUserLastActiveTable())) {
getInstance(main).addState(CREATING_NEW_TABLE, null);
update(start, ActiveUsersQueries.getQueryToCreateUserLastActiveTable(start), NO_OP_SETTER);
}

if (!doesTableExists(start, Config.getConfig(start).getAccessTokenSigningKeysTable())) {
getInstance(main).addState(CREATING_NEW_TABLE, null);
update(start, getQueryToCreateAccessTokenSigningKeysTable(start), NO_OP_SETTER);
Expand Down
9 changes: 9 additions & 0 deletions src/main/java/io/supertokens/storageLayer/StorageLayer.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import io.supertokens.exceptions.QuitProgramException;
import io.supertokens.inmemorydb.Start;
import io.supertokens.output.Logging;
import io.supertokens.pluginInterface.ActiveUsersStorage;
import io.supertokens.pluginInterface.STORAGE_TYPE;
import io.supertokens.pluginInterface.Storage;
import io.supertokens.pluginInterface.authRecipe.AuthRecipeStorage;
Expand Down Expand Up @@ -175,6 +176,14 @@ public static AuthRecipeStorage getAuthRecipeStorage(Main main) {
return (AuthRecipeStorage) getInstance(main).storage;
}

public static ActiveUsersStorage getActiveUsersStorage(Main main) {
if (getInstance(main) == null) {
throw new QuitProgramException("please call init() before calling getStorageLayer");
}

return (ActiveUsersStorage) getInstance(main).storage;
}

public static SessionStorage getSessionStorage(Main main) {
if (getInstance(main) == null) {
throw new QuitProgramException("please call init() before calling getStorageLayer");
Expand Down
1 change: 1 addition & 0 deletions src/main/java/io/supertokens/totp/Totp.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import io.supertokens.config.Config;

import com.eatthepath.otp.TimeBasedOneTimePasswordGenerator;

import io.supertokens.pluginInterface.exceptions.StorageQueryException;
import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException;
import io.supertokens.pluginInterface.totp.TOTPDevice;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

import io.supertokens.ActiveUsers;
import io.supertokens.Main;
import io.supertokens.emailpassword.EmailPassword;
import io.supertokens.emailpassword.exceptions.WrongCredentialsException;
Expand Down Expand Up @@ -65,6 +67,8 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I
try {
UserInfo user = EmailPassword.signIn(super.main, normalisedEmail, password);

ActiveUsers.updateLastActive(main, user.id); // use the internal user id

// if a userIdMapping exists, pass the externalUserId to the response
UserIdMapping userIdMapping = io.supertokens.useridmapping.UserIdMapping.getUserIdMapping(super.main,
user.id, UserIdType.ANY);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

import io.supertokens.ActiveUsers;
import io.supertokens.Main;
import io.supertokens.emailpassword.EmailPassword;
import io.supertokens.output.Logging;
Expand Down Expand Up @@ -67,6 +69,8 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I
try {
UserInfo user = EmailPassword.signUp(super.main, normalisedEmail, password);

ActiveUsers.updateLastActive(main, user.id);

JsonObject result = new JsonObject();
result.addProperty("status", "OK");
JsonObject userJson = new JsonParser().parse(new Gson().toJson(user)).getAsJsonObject();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

import io.supertokens.ActiveUsers;
import io.supertokens.Main;
import io.supertokens.passwordless.Passwordless;
import io.supertokens.passwordless.Passwordless.ConsumeCodeResponse;
Expand Down Expand Up @@ -81,6 +83,8 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I
ConsumeCodeResponse consumeCodeResponse = Passwordless.consumeCode(main, deviceId, deviceIdHash,
userInputCode, linkCode);

ActiveUsers.updateLastActive(main, consumeCodeResponse.user.id);

UserIdMapping userIdMapping = io.supertokens.useridmapping.UserIdMapping.getUserIdMapping(main,
consumeCodeResponse.user.id, UserIdType.ANY);
if (userIdMapping != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,17 @@
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

import io.supertokens.ActiveUsers;
import io.supertokens.Main;
import io.supertokens.exceptions.TokenTheftDetectedException;
import io.supertokens.exceptions.UnauthorisedException;
import io.supertokens.output.Logging;
import io.supertokens.pluginInterface.RECIPE_ID;
import io.supertokens.pluginInterface.exceptions.StorageQueryException;
import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException;
import io.supertokens.pluginInterface.useridmapping.UserIdMapping;
import io.supertokens.useridmapping.UserIdType;
import io.supertokens.session.Session;
import io.supertokens.session.info.SessionInformationHolder;
import io.supertokens.utils.Utils;
Expand Down Expand Up @@ -61,6 +65,15 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I
try {
SessionInformationHolder sessionInfo = Session.refreshSession(main, refreshToken, antiCsrfToken,
enableAntiCsrf);

UserIdMapping userIdMapping = io.supertokens.useridmapping.UserIdMapping.getUserIdMapping(super.main,
sessionInfo.session.userId, UserIdType.ANY);
if (userIdMapping != null) {
ActiveUsers.updateLastActive(main, userIdMapping.superTokensUserId);
} else {
ActiveUsers.updateLastActive(main, sessionInfo.session.userId);
}

JsonObject result = sessionInfo.toJsonObject();
result.addProperty("status", "OK");
super.sendJsonResponse(200, result, resp);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,17 @@
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

import io.supertokens.ActiveUsers;
import io.supertokens.Main;
import io.supertokens.exceptions.UnauthorisedException;
import io.supertokens.output.Logging;
import io.supertokens.pluginInterface.RECIPE_ID;
import io.supertokens.pluginInterface.exceptions.StorageQueryException;
import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException;
import io.supertokens.pluginInterface.session.SessionInfo;
import io.supertokens.pluginInterface.useridmapping.UserIdMapping;
import io.supertokens.useridmapping.UserIdType;
import io.supertokens.session.Session;
import io.supertokens.session.accessToken.AccessTokenSigningKey;
import io.supertokens.session.accessToken.AccessTokenSigningKey.KeyInfo;
Expand Down Expand Up @@ -77,6 +80,14 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I
SessionInformationHolder sessionInfo = Session.createNewSession(main, userId, userDataInJWT,
userDataInDatabase, enableAntiCsrf);

UserIdMapping userIdMapping = io.supertokens.useridmapping.UserIdMapping.getUserIdMapping(super.main,
sessionInfo.session.userId, UserIdType.ANY);
if (userIdMapping != null) {
ActiveUsers.updateLastActive(main, userIdMapping.superTokensUserId);
} else {
ActiveUsers.updateLastActive(main, sessionInfo.session.userId);
}

JsonObject result = sessionInfo.toJsonObject();

result.addProperty("status", "OK");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,13 @@
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;

import io.supertokens.ActiveUsers;
import io.supertokens.Main;
import io.supertokens.pluginInterface.RECIPE_ID;
import io.supertokens.pluginInterface.exceptions.StorageQueryException;
import io.supertokens.pluginInterface.useridmapping.UserIdMapping;
import io.supertokens.useridmapping.UserIdType;
import io.supertokens.session.Session;
import io.supertokens.webserver.InputParser;
import io.supertokens.webserver.WebserverAPI;
Expand Down Expand Up @@ -74,6 +78,15 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I
if (userId != null) {
try {
String[] sessionHandlesRevoked = Session.revokeAllSessionsForUser(main, userId);

UserIdMapping userIdMapping = io.supertokens.useridmapping.UserIdMapping.getUserIdMapping(super.main,
userId, UserIdType.ANY);
if (userIdMapping != null) {
ActiveUsers.updateLastActive(main, userIdMapping.superTokensUserId);
} else {
ActiveUsers.updateLastActive(main, userId);
}

JsonObject result = new JsonObject();
result.addProperty("status", "OK");
JsonArray sessionHandlesRevokedJSON = new JsonArray();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

import io.supertokens.ActiveUsers;
import io.supertokens.Main;
import io.supertokens.pluginInterface.RECIPE_ID;
import io.supertokens.pluginInterface.exceptions.StorageQueryException;
Expand Down Expand Up @@ -70,6 +72,8 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I
ThirdParty.SignInUpResponse response = ThirdParty.signInUp2_7(super.main, thirdPartyId,
thirdPartyUserId, email, isEmailVerified);

ActiveUsers.updateLastActive(main, response.user.id);

JsonObject result = new JsonObject();
result.addProperty("status", "OK");
result.addProperty("createdNewUser", response.createdNewUser);
Expand Down Expand Up @@ -100,6 +104,8 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I
ThirdParty.SignInUpResponse response = ThirdParty.signInUp(super.main, thirdPartyId, thirdPartyUserId,
email);

ActiveUsers.updateLastActive(main, response.user.id);

//
io.supertokens.pluginInterface.useridmapping.UserIdMapping userIdMapping = UserIdMapping
.getUserIdMapping(main, response.user.id, UserIdType.ANY);
Expand Down

0 comments on commit 4e13470

Please sign in to comment.