Skip to content

Commit

Permalink
Merge pull request #871 from supertokens/mfa-stats
Browse files Browse the repository at this point in the history
fix: mfa stats
  • Loading branch information
sattvikc authored Nov 1, 2023
2 parents 148c72a + 3e8ae02 commit c10aff7
Show file tree
Hide file tree
Showing 13 changed files with 296 additions and 181 deletions.
72 changes: 17 additions & 55 deletions ee/src/main/java/io/supertokens/ee/EEFeatureFlag.java
Original file line number Diff line number Diff line change
Expand Up @@ -185,37 +185,6 @@ private JsonObject getDashboardLoginStats() throws TenantOrAppNotFoundException,
return stats;
}

private JsonObject getTOTPStats() throws StorageQueryException, TenantOrAppNotFoundException {
JsonObject totpStats = new JsonObject();
JsonArray totpMauArr = new JsonArray();

Storage[] storages = StorageLayer.getStoragesForApp(main, this.appIdentifier);

// TODO Active users are present only on public tenant and TOTP users may be present on different storages
Storage publicTenantStorage = StorageLayer.getStorage(this.appIdentifier.getAsPublicTenantIdentifier(), main);
final long now = System.currentTimeMillis();
for (int i = 0; i < 30; i++) {
long today = now - (now % (24 * 60 * 60 * 1000L));
long timestamp = today - (i * 24 * 60 * 60 * 1000L);

int totpMau = 0;
// TODO Need to figure out a way to combine the data from different storages to get the final stats
// for (Storage storage : storages) {
totpMau += ((ActiveUsersStorage) publicTenantStorage).countUsersEnabledTotpAndActiveSince(this.appIdentifier, timestamp);
// }
totpMauArr.add(new JsonPrimitive(totpMau));
}

totpStats.add("maus", totpMauArr);

int totpTotalUsers = 0;
for (Storage storage : storages) {
totpTotalUsers += ((ActiveUsersStorage) storage).countUsersEnabledTotp(this.appIdentifier);
}
totpStats.addProperty("total_users", totpTotalUsers);
return totpStats;
}

private boolean isEnterpriseThirdPartyId(String thirdPartyId) {
for (String enterpriseThirdPartyId : ENTERPRISE_THIRD_PARTY_IDS) {
if (thirdPartyId.startsWith(enterpriseThirdPartyId)) {
Expand All @@ -225,37 +194,29 @@ private boolean isEnterpriseThirdPartyId(String thirdPartyId) {
return false;
}


private JsonObject getMFAStats() throws StorageQueryException, TenantOrAppNotFoundException{
JsonObject mfaStats = new JsonObject();
JsonArray mfaMauArr = new JsonArray();

Storage[] storages = StorageLayer.getStoragesForApp(main, this.appIdentifier);

// TODO: Active users are present only on public tenant and MFA users may be present on different storages
Storage publicTenantStorage = StorageLayer.getStorage(this.appIdentifier.getAsPublicTenantIdentifier(), main);
final long now = System.currentTimeMillis();
for (int i = 0; i < 30; i++) {
long today = now - (now % (24 * 60 * 60 * 1000L));
long timestamp = today - (i * 24 * 60 * 60 * 1000L);
JsonObject result = new JsonObject();
Storage[] storages = StorageLayer.getStoragesForApp(main, this.appIdentifier);

int mfaMau = 0;
// TODO Need to figure out a way to combine the data from different storages to get the final stats
// for (Storage storage : storages) {
mfaMau += ((ActiveUsersStorage) publicTenantStorage).countUsersEnabledMfaAndActiveSince(this.appIdentifier, timestamp);
// }
mfaMauArr.add(new JsonPrimitive(mfaMau));
}
int totalUserCountWithMoreThanOneLoginMethod = 0;
int[] maus = new int[30];

mfaStats.add("maus", mfaMauArr);
mfaStats.add("totp", getTOTPStats());
long now = System.currentTimeMillis();
long today = now - (now % (24 * 60 * 60 * 1000L));

int mfaTotalUsers = 0;
for (Storage storage : storages) {
mfaTotalUsers += ((ActiveUsersStorage) storage).countUsersEnabledMfa(this.appIdentifier);
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);
}
}
mfaStats.addProperty("total_users", mfaTotalUsers);
return mfaStats;

result.addProperty("totalUserCountWithMoreThanOneLoginMethodOrTOTPEnabled", totalUserCountWithMoreThanOneLoginMethod);
result.add("mauWithMoreThanOneLoginMethodOrTOTPEnabled", new Gson().toJsonTree(maus));
return result;
}

private JsonObject getMultiTenancyStats()
Expand Down Expand Up @@ -306,6 +267,7 @@ private JsonObject getMultiTenancyStats()
}

private JsonObject getAccountLinkingStats() throws StorageQueryException {
// TODO: Active users are present only on public tenant and MFA users may be present on different storages
JsonObject result = new JsonObject();
Storage[] storages = StorageLayer.getStoragesForApp(main, this.appIdentifier);
boolean usesAccountLinking = false;
Expand Down
47 changes: 9 additions & 38 deletions src/main/java/io/supertokens/inmemorydb/Start.java
Original file line number Diff line number Diff line change
Expand Up @@ -1211,44 +1211,6 @@ public int countUsersActiveSince(AppIdentifier appIdentifier, long time) throws
}
}

@Override
public int countUsersEnabledTotp(AppIdentifier appIdentifier) throws StorageQueryException {
try {
return ActiveUsersQueries.countUsersEnabledTotp(this, appIdentifier);
} catch (SQLException e) {
throw new StorageQueryException(e);
}
}

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

@Override
public int countUsersEnabledMfa(AppIdentifier appIdentifier) throws StorageQueryException {
try {
return ActiveUsersQueries.countUsersEnabledMfa(this, appIdentifier);
} catch (SQLException e) {
throw new StorageQueryException(e);
}
}

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

@Override
public void deleteUserActive_Transaction(TransactionConnection con, AppIdentifier appIdentifier, String userId)
throws StorageQueryException {
Expand Down Expand Up @@ -2997,4 +2959,13 @@ public UserIdMapping[] getUserIdMapping_Transaction(TransactionConnection con, A
}
}

@Override
public int getUsersCountWithMoreThanOneLoginMethodOrTOTPEnabled(AppIdentifier appIdentifier) throws StorageQueryException {
return 0; // TODO
}

@Override
public int countUsersThatHaveMoreThanOneLoginMethodOrTOTPEnabledAndActiveSince(AppIdentifier appIdentifier, long timestamp) throws StorageQueryException {
return 0; // TODO
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public static int countUsersActiveSince(Start start, AppIdentifier appIdentifier

public static int countUsersActiveSinceAndHasMoreThanOneLoginMethod(Start start, AppIdentifier appIdentifier, long sinceTime)
throws SQLException, StorageQueryException {
// TODO: Active users are present only on public tenant and MFA users may be present on different storages
String QUERY = "SELECT count(1) as c FROM ("
+ " SELECT count(user_id) as num_login_methods, app_id, primary_or_recipe_user_id"
+ " FROM " + Config.getConfig(start).getUsersTable()
Expand All @@ -61,48 +62,6 @@ public static int countUsersActiveSinceAndHasMoreThanOneLoginMethod(Start start,
});
}

public static int countUsersEnabledTotp(Start start, AppIdentifier appIdentifier)
throws SQLException, StorageQueryException {
String QUERY = "SELECT COUNT(*) as total FROM " + Config.getConfig(start).getTotpUsersTable()
+ " WHERE app_id = ?";

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

public static int countUsersEnabledTotpAndActiveSince(Start start, AppIdentifier appIdentifier, long sinceTime)
throws SQLException, StorageQueryException {
String QUERY =
"SELECT COUNT(*) as total FROM " + Config.getConfig(start).getTotpUsersTable() + " AS totp_users "
+ "INNER JOIN " + Config.getConfig(start).getUserLastActiveTable() + " AS user_last_active "
+ "ON totp_users.user_id = user_last_active.user_id "
+ "WHERE user_last_active.app_id = ? AND user_last_active.last_active_time >= ?";

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

public static int countUsersEnabledMfa(Start start, AppIdentifier appIdentifier) throws SQLException, StorageQueryException {
return 0; // TODO
}

public static int countUsersEnabledMfaAndActiveSince(Start start, AppIdentifier appIdentifier, long sinceTime) throws SQLException, StorageQueryException {
return 0; // TODO
}

public static int updateUserLastActive(Start start, AppIdentifier appIdentifier, String userId)
throws SQLException, StorageQueryException {
String QUERY = "INSERT INTO " + Config.getConfig(start).getUserLastActiveTable()
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/io/supertokens/webserver/WebserverAPI.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,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;
Expand Down Expand Up @@ -340,6 +341,16 @@ 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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,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");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,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");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -140,7 +140,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");
Expand Down
Loading

0 comments on commit c10aff7

Please sign in to comment.