Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

fix: mfa cleanup #837

Merged
merged 4 commits into from
Oct 17, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions ee/src/main/java/io/supertokens/ee/EEFeatureFlag.java
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ private JsonObject getMFAStats() throws StorageQueryException, TenantOrAppNotFou
}

mfaStats.add("maus", mfaMauArr);
mfaStats.add("totp", getTOTPStats());

int mfaTotalUsers = 0;
for (Storage storage : storages) {
Expand Down Expand Up @@ -387,10 +388,6 @@ public JsonObject getPaidFeatureStats() throws StorageQueryException, TenantOrAp
usageStats.add(EE_FEATURES.DASHBOARD_LOGIN.toString(), getDashboardLoginStats());
}

if (feature == EE_FEATURES.TOTP) {
usageStats.add(EE_FEATURES.TOTP.toString(), getTOTPStats());
}

if (feature == EE_FEATURES.MFA) {
usageStats.add(EE_FEATURES.MFA.toString(), getMFAStats());
}
Expand Down
4 changes: 0 additions & 4 deletions src/main/java/io/supertokens/authRecipe/AuthRecipe.java
Original file line number Diff line number Diff line change
Expand Up @@ -934,8 +934,6 @@ private static void deleteNonAuthRecipeUser(TransactionConnection con, AppIdenti
.deleteAllRolesForUser_Transaction(con, appIdentifierWithStorage, userId);
appIdentifierWithStorage.getActiveUsersStorage()
.deleteUserActive_Transaction(con, appIdentifierWithStorage, userId);
appIdentifierWithStorage.getMfaStorage()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where are we deleting totp devices etc?

.deleteMfaInfoForUser_Transaction(con, appIdentifierWithStorage, userId);
}

private static void deleteAuthRecipeUser(TransactionConnection con,
Expand Down Expand Up @@ -976,8 +974,6 @@ public static boolean deleteNonAuthRecipeUser(TenantIdentifierWithStorage
.removeUser(tenantIdentifierWithStorage, userId);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this on a tenant level and not app level?

finalDidExist = finalDidExist || didExist;

didExist = tenantIdentifierWithStorage.getMfaStorage()
.deleteMfaInfoForUser(tenantIdentifierWithStorage, userId);
finalDidExist = finalDidExist || didExist;

return finalDidExist;
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/io/supertokens/featureflag/EE_FEATURES.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

public enum EE_FEATURES {
ACCOUNT_LINKING("account_linking"), MULTI_TENANCY("multi_tenancy"), TEST("test"),
DASHBOARD_LOGIN("dashboard_login"), TOTP("totp"), MFA("mfa");
DASHBOARD_LOGIN("dashboard_login"), MFA("mfa");

private final String name;

Expand Down
80 changes: 2 additions & 78 deletions src/main/java/io/supertokens/inmemorydb/Start.java
Original file line number Diff line number Diff line change
Expand Up @@ -619,12 +619,7 @@ public boolean isUserIdBeingUsedInNonAuthRecipe(AppIdentifier appIdentifier, Str
} else if (className.equals(JWTRecipeStorage.class.getName())) {
return false;
} else if (className.equals(MfaStorage.class.getName())) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove this if

try {
MultitenancyQueries.getAllTenants(this);
return MfaQueries.listFactors(this, appIdentifier, userId).length > 0;
} catch (SQLException e) {
throw new StorageQueryException(e);
}
return false; /* nothing here */
} else {
throw new IllegalStateException("ClassName: " + className + " is not part of NonAuthRecipeStorage");
}
Expand Down Expand Up @@ -724,11 +719,7 @@ public void addInfoToNonAuthRecipesBasedOnUserId(TenantIdentifier tenantIdentifi
throw new StorageQueryException(e);
}
} else if (className.equals(MfaStorage.class.getName())) {
try {
MfaQueries.enableFactor(this, tenantIdentifier, userId, "emailpassword");
} catch (SQLException e) {
throw new StorageQueryException(e);
}
/* nothing here */
} else {
throw new IllegalStateException("ClassName: " + className + " is not part of NonAuthRecipeStorage");
}
Expand Down Expand Up @@ -2813,73 +2804,6 @@ public int removeExpiredCodes(TenantIdentifier tenantIdentifier, long expiredBef
}
}


// MFA recipe:
@Override
public boolean enableFactor(TenantIdentifier tenantIdentifier, String userId, String factor)
throws StorageQueryException {
try {
int insertedCount = MfaQueries.enableFactor(this, tenantIdentifier, userId, factor);
if (insertedCount == 0) {
return false;
}
return true;
} catch (SQLException e) {
throw new StorageQueryException(e);
}
}

@Override
public String[] listFactors(TenantIdentifier tenantIdentifier, String userId)
throws StorageQueryException {
try {
return MfaQueries.listFactors(this, tenantIdentifier, userId);
} catch (SQLException e) {
throw new StorageQueryException(e);
}
}

@Override
public boolean disableFactor(TenantIdentifier tenantIdentifier, String userId, String factor)
throws StorageQueryException {
try {
int deletedCount = MfaQueries.disableFactor(this, tenantIdentifier, userId, factor);
if (deletedCount == 0) {
return false;
}
return true;
} catch (SQLException e) {
throw new StorageQueryException(e);
}
}

@Override
public boolean deleteMfaInfoForUser_Transaction(TransactionConnection con, AppIdentifier appIdentifier, String userId)
throws StorageQueryException {
try {
int deletedCount = MfaQueries.deleteUser_Transaction(this, (Connection) con.getConnection(), appIdentifier, userId);
if (deletedCount == 0) {
return false;
}
return true;
} catch (SQLException e) {
throw new StorageQueryException(e);
}
}

@Override
public boolean deleteMfaInfoForUser(TenantIdentifier tenantIdentifier, String userId) throws StorageQueryException {
try {
int deletedCount = MfaQueries.deleteUserFromTenant(this, tenantIdentifier, userId);
if (deletedCount == 0) {
return false;
}
return true;
} catch (SQLException e) {
throw new StorageQueryException(e);
}
}

@Override
public Set<String> getValidFieldsInConfig() {
return SQLiteConfig.getValidFields();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,10 +141,6 @@ public String getTotpUsersTable() {
return "totp_users";
}

public String getMfaUserFactorsTable() {
return "mfa_user_factors";
}

public String getTotpUserDevicesTable() {
return "totp_user_devices";
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,35 +96,11 @@ public static int countUsersEnabledTotpAndActiveSince(Start start, AppIdentifier
}

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

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

public static int countUsersEnabledMfaAndActiveSince(Start start, AppIdentifier appIdentifier, long sinceTime) throws SQLException, StorageQueryException {
// Find unique users from mfa_user_factors table and join with user_last_active table
String QUERY = "SELECT COUNT(*) as total FROM (SELECT DISTINCT user_id FROM " + Config.getConfig(start).getMfaUserFactorsTable() + ") AS mfa_users "
+ "INNER JOIN " + Config.getConfig(start).getUserLastActiveTable() + " AS user_last_active "
+ "ON mfa_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;
});
return 0; // TODO
}

public static int updateUserLastActive(Start start, AppIdentifier appIdentifier, String userId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -406,11 +406,6 @@ public static void createTablesIfNotExists(Start start, Main main) throws SQLExc
// index:
update(start, TOTPQueries.getQueryToCreateUsedCodesExpiryTimeIndex(start), NO_OP_SETTER);
}

if (!doesTableExists(start, Config.getConfig(start).getMfaUserFactorsTable())) {
getInstance(main).addState(CREATING_NEW_TABLE, null);
update(start, MfaQueries.getQueryToCreateUserFactorsTable(start), NO_OP_SETTER);
}

}

Expand Down
90 changes: 0 additions & 90 deletions src/main/java/io/supertokens/inmemorydb/queries/MfaQueries.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,94 +31,4 @@
import static io.supertokens.inmemorydb.QueryExecutorTemplate.update;

public class MfaQueries {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

delete this whole file

public static String getQueryToCreateUserFactorsTable(Start start) {
return "CREATE TABLE IF NOT EXISTS " + Config.getConfig(start).getMfaUserFactorsTable() + " ("
+ "app_id VARCHAR(64) DEFAULT 'public',"
+ "tenant_id VARCHAR(64) DEFAULT 'public',"
+ "user_id VARCHAR(128) NOT NULL,"
+ "factor_id VARCHAR(64) NOT NULL,"
+ "PRIMARY KEY (app_id, tenant_id, user_id, factor_id),"
+ "FOREIGN KEY (app_id, tenant_id)"
+ "REFERENCES " + Config.getConfig(start).getTenantsTable() + " (app_id, tenant_id) ON DELETE CASCADE);";
}

public static int enableFactor(Start start, TenantIdentifier tenantIdentifier, String userId, String factorId)
throws StorageQueryException, SQLException {
String QUERY = "INSERT INTO " + Config.getConfig(start).getMfaUserFactorsTable() + " (app_id, tenant_id, user_id, factor_id) VALUES (?, ?, ?, ?) ON CONFLICT DO NOTHING";

return update(start, QUERY, pst -> {
pst.setString(1, tenantIdentifier.getAppId());
pst.setString(2, tenantIdentifier.getTenantId());
pst.setString(3, userId);
pst.setString(4, factorId);
});
}


public static String[] listFactors(Start start, TenantIdentifier tenantIdentifier, String userId)
throws StorageQueryException, SQLException {
String QUERY = "SELECT factor_id FROM " + Config.getConfig(start).getMfaUserFactorsTable() + " WHERE app_id = ? AND tenant_id = ? AND user_id = ?";

return execute(start, QUERY, pst -> {
pst.setString(1, tenantIdentifier.getAppId());
pst.setString(2, tenantIdentifier.getTenantId());
pst.setString(3, userId);
}, result -> {
List<String> factors = new ArrayList<>();
while (result.next()) {
factors.add(result.getString("factor_id"));
}

return factors.toArray(String[]::new);
});
}

public static String[] listFactors(Start start, AppIdentifier appIdentifier, String userId)
throws StorageQueryException, SQLException {
String QUERY = "SELECT factor_id FROM " + Config.getConfig(start).getMfaUserFactorsTable() + " WHERE app_id = ? AND user_id = ?";

return execute(start, QUERY, pst -> {
pst.setString(1, appIdentifier.getAppId());
pst.setString(2, userId);
}, result -> {
List<String> factors = new ArrayList<>();
while (result.next()) {
factors.add(result.getString("factor_id"));
}

return factors.toArray(String[]::new);
});
}

public static int disableFactor(Start start, TenantIdentifier tenantIdentifier, String userId, String factorId)
throws StorageQueryException, SQLException {
String QUERY = "DELETE FROM " + Config.getConfig(start).getMfaUserFactorsTable() + " WHERE app_id = ? AND tenant_id = ? AND user_id = ? AND factor_id = ?";

return update(start, QUERY, pst -> {
pst.setString(1, tenantIdentifier.getAppId());
pst.setString(2, tenantIdentifier.getTenantId());
pst.setString(3, userId);
pst.setString(4, factorId);
});
}

public static int deleteUser_Transaction(Start start, Connection sqlCon, AppIdentifier appIdentifier, String userId) throws StorageQueryException, SQLException {
String QUERY = "DELETE FROM " + Config.getConfig(start).getMfaUserFactorsTable() + " WHERE app_id = ? AND user_id = ?";

return update(sqlCon, QUERY, pst -> {
pst.setString(1, appIdentifier.getAppId());
pst.setString(2, userId);
});
}

public static int deleteUserFromTenant(Start start, TenantIdentifier tenantIdentifier, String userId)
throws StorageQueryException, SQLException {
String QUERY = "DELETE FROM " + Config.getConfig(start).getMfaUserFactorsTable() + " WHERE app_id = ? AND tenant_id = ? AND user_id = ?";

return update(start, QUERY, pst -> {
pst.setString(1, tenantIdentifier.getAppId());
pst.setString(2, tenantIdentifier.getTenantId());
pst.setString(3, userId);
});
}
}
52 changes: 6 additions & 46 deletions src/main/java/io/supertokens/mfa/Mfa.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,60 +5,20 @@
import io.supertokens.featureflag.FeatureFlag;
import io.supertokens.featureflag.exceptions.FeatureNotEnabledException;
import io.supertokens.pluginInterface.exceptions.StorageQueryException;
import io.supertokens.pluginInterface.mfa.MfaStorage;
import io.supertokens.pluginInterface.multitenancy.AppIdentifier;
import io.supertokens.pluginInterface.multitenancy.TenantIdentifierWithStorage;
import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException;

public class Mfa {
private static boolean isMfaEnabled(AppIdentifier appIdentifier, Main main)
throws StorageQueryException, TenantOrAppNotFoundException {
public static void checkForMFAFeature(AppIdentifier appIdentifier, Main main)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public static void checkForMFAFeature(AppIdentifier appIdentifier, Main main)
public static void assertMFAFeatureIsEnabled(AppIdentifier appIdentifier, Main main)

throws StorageQueryException, TenantOrAppNotFoundException, FeatureNotEnabledException {
EE_FEATURES[] features = FeatureFlag.getInstance(main, appIdentifier).getEnabledFeatures();
for (EE_FEATURES f : features) {
if (f == EE_FEATURES.MFA) {
return true;
return;
}
}
return false;
}

public static boolean enableFactor(TenantIdentifierWithStorage tenantIdentifierWithStorage, Main main, String userId, String factorId)
throws
StorageQueryException, FeatureNotEnabledException, TenantOrAppNotFoundException {
if (!isMfaEnabled(tenantIdentifierWithStorage.toAppIdentifier(), main)) {
throw new FeatureNotEnabledException(
"MFA feature is not enabled. Please subscribe to a SuperTokens core license key to enable this " +
"feature.");
}

MfaStorage mfaStorage = tenantIdentifierWithStorage.getMfaStorage();
return mfaStorage.enableFactor(tenantIdentifierWithStorage, userId, factorId);
}

public static String[] listFactors(TenantIdentifierWithStorage tenantIdentifierWithStorage, Main main, String userId)
throws
StorageQueryException, TenantOrAppNotFoundException, FeatureNotEnabledException {
if (!isMfaEnabled(tenantIdentifierWithStorage.toAppIdentifier(), main)) {
throw new FeatureNotEnabledException(
"MFA feature is not enabled. Please subscribe to a SuperTokens core license key to enable this " +
"feature.");
}

MfaStorage mfaStorage = tenantIdentifierWithStorage.getMfaStorage();
return mfaStorage.listFactors(tenantIdentifierWithStorage, userId);
}

public static boolean disableFactor(TenantIdentifierWithStorage tenantIdentifierWithStorage, Main main, String userId, String factorId)
throws
StorageQueryException, TenantOrAppNotFoundException, FeatureNotEnabledException {

if (!isMfaEnabled(tenantIdentifierWithStorage.toAppIdentifier(), main)) {
throw new FeatureNotEnabledException(
"MFA feature is not enabled. Please subscribe to a SuperTokens core license key to enable this " +
"feature.");
}

MfaStorage mfaStorage = tenantIdentifierWithStorage.getMfaStorage();
return mfaStorage.disableFactor(tenantIdentifierWithStorage, userId, factorId);
throw new FeatureNotEnabledException(
"MFA feature is not enabled. Please subscribe to a SuperTokens core license key to enable this " +
"feature.");
}
}
Loading