Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/feat/mfa' into feat/bulk-import-api
Browse files Browse the repository at this point in the history
  • Loading branch information
anku255 committed Feb 27, 2024
2 parents 969b945 + 15a4351 commit 9ed97ab
Show file tree
Hide file tree
Showing 19 changed files with 466 additions and 128 deletions.
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

- Replace `TotpNotEnabledError` with `UnknownUserIdTotpError`.
- Support for MFA recipe
- Adds a new `useStaticKey` param to `updateSessionInfo_Transaction`
- This enables smooth switching between `useDynamicAccessTokenSigningKey` settings by allowing refresh calls to
change the signing key type of a session

## [5.0.8] - 2024-02-19

- Fixes vulnerabilities in dependencies
Expand Down Expand Up @@ -140,7 +146,6 @@ CREATE INDEX IF NOT EXISTS app_id_to_user_id_primary_user_id_index ON app_id_to_
```
4. Run the new instance(s) of the core (version 7.0.0)


## [4.0.2]

- Fixes null pointer issue when user belongs to no tenant.
Expand Down
117 changes: 75 additions & 42 deletions src/main/java/io/supertokens/storage/postgresql/Start.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,7 @@
import io.supertokens.pluginInterface.jwt.JWTSigningKeyInfo;
import io.supertokens.pluginInterface.jwt.exceptions.DuplicateKeyIdException;
import io.supertokens.pluginInterface.jwt.sqlstorage.JWTRecipeSQLStorage;
import io.supertokens.pluginInterface.multitenancy.AppIdentifier;
import io.supertokens.pluginInterface.multitenancy.MultitenancyStorage;
import io.supertokens.pluginInterface.multitenancy.TenantConfig;
import io.supertokens.pluginInterface.multitenancy.TenantIdentifier;
import io.supertokens.pluginInterface.multitenancy.*;
import io.supertokens.pluginInterface.multitenancy.exceptions.DuplicateClientTypeException;
import io.supertokens.pluginInterface.multitenancy.exceptions.DuplicateTenantException;
import io.supertokens.pluginInterface.multitenancy.exceptions.DuplicateThirdPartyIdException;
Expand All @@ -71,8 +68,8 @@
import io.supertokens.pluginInterface.totp.TOTPStorage;
import io.supertokens.pluginInterface.totp.TOTPUsedCode;
import io.supertokens.pluginInterface.totp.exception.DeviceAlreadyExistsException;
import io.supertokens.pluginInterface.totp.exception.TotpNotEnabledException;
import io.supertokens.pluginInterface.totp.exception.UnknownDeviceException;
import io.supertokens.pluginInterface.totp.exception.UnknownTotpUserIdException;
import io.supertokens.pluginInterface.totp.exception.UsedCodeAlreadyExistsException;
import io.supertokens.pluginInterface.totp.sqlStorage.TOTPSQLStorage;
import io.supertokens.pluginInterface.useridmapping.UserIdMapping;
Expand Down Expand Up @@ -111,7 +108,8 @@
public class Start
implements SessionSQLStorage, EmailPasswordSQLStorage, EmailVerificationSQLStorage, ThirdPartySQLStorage,
JWTRecipeSQLStorage, PasswordlessSQLStorage, UserMetadataSQLStorage, UserRolesSQLStorage, UserIdMappingStorage,
UserIdMappingSQLStorage, MultitenancyStorage, MultitenancySQLStorage, DashboardSQLStorage, TOTPSQLStorage, ActiveUsersStorage, AuthRecipeSQLStorage {
UserIdMappingSQLStorage, MultitenancyStorage, MultitenancySQLStorage, DashboardSQLStorage, TOTPSQLStorage,
ActiveUsersStorage, ActiveUsersSQLStorage, AuthRecipeSQLStorage {

// these configs are protected from being modified / viewed by the dev using the SuperTokens
// SaaS. If the core is not running in SuperTokens SaaS, this array has no effect.
Expand Down Expand Up @@ -649,11 +647,11 @@ public SessionInfo getSessionInfo_Transaction(TenantIdentifier tenantIdentifier,
@Override
public void updateSessionInfo_Transaction(TenantIdentifier tenantIdentifier, TransactionConnection con,
String sessionHandle, String refreshTokenHash2,
long expiry) throws StorageQueryException {
long expiry, boolean useStaticKey) throws StorageQueryException {
Connection sqlCon = (Connection) con.getConnection();
try {
SessionQueries.updateSessionInfo_Transaction(this, sqlCon, tenantIdentifier, sessionHandle,
refreshTokenHash2, expiry);
refreshTokenHash2, expiry, useStaticKey);
} catch (SQLException e) {
throw new StorageQueryException(e);
}
Expand Down Expand Up @@ -834,13 +832,14 @@ public void addInfoToNonAuthRecipesBasedOnUserId(TenantIdentifier tenantIdentifi
}
} else if (className.equals(TOTPStorage.class.getName())) {
try {
TOTPDevice device = new TOTPDevice(userId, "testDevice", "secret", 0, 30, false);
TOTPQueries.createDevice(this, tenantIdentifier.toAppIdentifier(), device);
TOTPDevice device = new TOTPDevice(userId, "testDevice", "secret", 0, 30, false, System.currentTimeMillis());
this.startTransaction(con -> {
try {
long now = System.currentTimeMillis();
Connection sqlCon = (Connection) con.getConnection();
TOTPQueries.createDevice_Transaction(this, sqlCon, tenantIdentifier.toAppIdentifier(), device);
TOTPQueries.insertUsedCode_Transaction(this,
(Connection) con.getConnection(), tenantIdentifier,
sqlCon, tenantIdentifier,
new TOTPUsedCode(userId, "123456", true, 1000 + now, now));
} catch (SQLException e) {
throw new StorageTransactionLogicException(e);
Expand Down Expand Up @@ -1328,25 +1327,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 void deleteUserActive_Transaction(TransactionConnection con, AppIdentifier appIdentifier, String userId)
throws StorageQueryException {
Expand Down Expand Up @@ -2621,26 +2601,60 @@ public void revokeExpiredSessions() throws StorageQueryException {
}

// TOTP recipe:
@TestOnly
@Override
public void createDevice(AppIdentifier appIdentifier, TOTPDevice device)
throws StorageQueryException, DeviceAlreadyExistsException, TenantOrAppNotFoundException {
throws DeviceAlreadyExistsException, TenantOrAppNotFoundException, StorageQueryException {
try {
TOTPQueries.createDevice(this, appIdentifier, device);
startTransaction(con -> {
try {
createDevice_Transaction(con, new AppIdentifier(null, null), device);
} catch (DeviceAlreadyExistsException | TenantOrAppNotFoundException e) {
throw new StorageTransactionLogicException(e);
}
return null;
});
} catch (StorageTransactionLogicException e) {
Exception actualException = e.actualException;
if (e.actualException instanceof DeviceAlreadyExistsException) {
throw (DeviceAlreadyExistsException) e.actualException;
} else if (e.actualException instanceof TenantOrAppNotFoundException) {
throw (TenantOrAppNotFoundException) e.actualException;
} else if (e.actualException instanceof StorageQueryException) {
throw (StorageQueryException) e.actualException;
}
}
}

@Override
public TOTPDevice createDevice_Transaction(TransactionConnection con, AppIdentifier appIdentifier, TOTPDevice device)
throws StorageQueryException, DeviceAlreadyExistsException, TenantOrAppNotFoundException {
Connection sqlCon = (Connection) con.getConnection();
try {
TOTPQueries.createDevice_Transaction(this, sqlCon, appIdentifier, device);
return device;
} catch (SQLException e) {
Exception actualException = e;

if (actualException instanceof PSQLException) {
ServerErrorMessage errMsg = ((PSQLException) actualException).getServerErrorMessage();

if (isPrimaryKeyError(errMsg, Config.getConfig(this).getTotpUserDevicesTable())) {
throw new DeviceAlreadyExistsException();
throw new DeviceAlreadyExistsException();
} else if (isForeignKeyConstraintError(errMsg, Config.getConfig(this).getTotpUsersTable(), "app_id")) {
throw new TenantOrAppNotFoundException(appIdentifier);
throw new TenantOrAppNotFoundException(appIdentifier);
}

}
throw new StorageQueryException(e);
}
}

throw new StorageQueryException(e.actualException);
@Override
public TOTPDevice getDeviceByName_Transaction(TransactionConnection con, AppIdentifier appIdentifier, String userId, String deviceName) throws StorageQueryException {
Connection sqlCon = (Connection) con.getConnection();
try {
return TOTPQueries.getDeviceByName_Transaction(this, sqlCon, appIdentifier, userId, deviceName);
} catch (SQLException e) {
throw new StorageQueryException(e);
}
}

Expand Down Expand Up @@ -2694,8 +2708,8 @@ public boolean removeUser(TenantIdentifier tenantIdentifier, String userId)

@Override
public void updateDeviceName(AppIdentifier appIdentifier, String userId, String oldDeviceName, String newDeviceName)
throws StorageQueryException, DeviceAlreadyExistsException,
UnknownDeviceException {
throws StorageQueryException,
UnknownDeviceException, DeviceAlreadyExistsException {
try {
int updatedCount = TOTPQueries.updateDeviceName(this, appIdentifier, userId, oldDeviceName, newDeviceName);
if (updatedCount == 0) {
Expand All @@ -2705,7 +2719,7 @@ public void updateDeviceName(AppIdentifier appIdentifier, String userId, String
if (e instanceof PSQLException) {
ServerErrorMessage errMsg = ((PSQLException) e).getServerErrorMessage();
if (isPrimaryKeyError(errMsg, Config.getConfig(this).getTotpUserDevicesTable())) {
throw new DeviceAlreadyExistsException();
throw new DeviceAlreadyExistsException();
}
}
throw new StorageQueryException(e);
Expand Down Expand Up @@ -2736,7 +2750,7 @@ public TOTPDevice[] getDevices_Transaction(TransactionConnection con, AppIdentif
@Override
public void insertUsedCode_Transaction(TransactionConnection con, TenantIdentifier tenantIdentifier,
TOTPUsedCode usedCodeObj)
throws StorageQueryException, TotpNotEnabledException, UsedCodeAlreadyExistsException,
throws StorageQueryException, UnknownTotpUserIdException, UsedCodeAlreadyExistsException,
TenantOrAppNotFoundException {
Connection sqlCon = (Connection) con.getConnection();
try {
Expand All @@ -2748,7 +2762,7 @@ public void insertUsedCode_Transaction(TransactionConnection con, TenantIdentifi
throw new UsedCodeAlreadyExistsException();
} else if (isForeignKeyConstraintError(err, Config.getConfig(this).getTotpUsedCodesTable(),
"user_id")) {
throw new TotpNotEnabledException();
throw new UnknownTotpUserIdException();
} else if (isForeignKeyConstraintError(err, Config.getConfig(this).getTotpUsedCodesTable(), "tenant_id")) {
throw new TenantOrAppNotFoundException(tenantIdentifier);
}
Expand Down Expand Up @@ -2994,6 +3008,25 @@ public UserIdMapping[] getUserIdMapping_Transaction(TransactionConnection con, A
}
}

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

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

@TestOnly
public int getDbActivityCount(String dbname) throws SQLException, StorageQueryException {
String QUERY = "SELECT COUNT(*) as c FROM pg_stat_activity WHERE datname = ?;";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,14 @@ public String getTenantConfigsTable() {
return addSchemaAndPrefixToTableName("tenant_configs");
}

public String getTenantFirstFactorsTable() {
return addSchemaAndPrefixToTableName("tenant_first_factors");
}

public String getTenantRequiredSecondaryFactorsTable() {
return addSchemaAndPrefixToTableName("tenant_required_secondary_factors");
}

public String getTenantThirdPartyProvidersTable() {
return addSchemaAndPrefixToTableName("tenant_thirdparty_providers");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import static io.supertokens.storage.postgresql.QueryExecutorTemplate.execute;
import static io.supertokens.storage.postgresql.QueryExecutorTemplate.update;
import static io.supertokens.storage.postgresql.config.Config.getConfig;

public class ActiveUsersQueries {
static String getQueryToCreateUserLastActiveTable(Start start) {
Expand Down Expand Up @@ -51,9 +52,10 @@ 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()
+ " FROM " + Config.getConfig(start).getAppIdToUserIdTable()
+ " WHERE primary_or_recipe_user_id IN ("
+ " SELECT user_id FROM " + Config.getConfig(start).getUserLastActiveTable()
+ " WHERE app_id = ? AND last_active_time >= ?"
Expand All @@ -71,40 +73,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 updateUserLastActive(Start start, AppIdentifier appIdentifier, String userId)
throws SQLException, StorageQueryException {
String QUERY = "INSERT INTO " + Config.getConfig(start).getUserLastActiveTable()
Expand Down Expand Up @@ -152,4 +120,41 @@ public static void deleteUserActive_Transaction(Connection con, Start start, App
pst.setString(2, userId);
});
}

public static int countUsersThatHaveMoreThanOneLoginMethodOrTOTPEnabledAndActiveSince(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 (DISTINCT user_id) as c FROM ("
+ " (" // users with more than one login method
+ " SELECT primary_or_recipe_user_id AS user_id FROM ("
+ " SELECT COUNT(user_id) as num_login_methods, app_id, primary_or_recipe_user_id"
+ " FROM " + getConfig(start).getAppIdToUserIdTable()
+ " WHERE app_id = ? AND primary_or_recipe_user_id IN ("
+ " SELECT user_id FROM " + getConfig(start).getUserLastActiveTable()
+ " WHERE app_id = ? AND last_active_time >= ?"
+ " )"
+ " GROUP BY (app_id, primary_or_recipe_user_id)"
+ " ) AS nloginmethods"
+ " WHERE num_login_methods > 1"
+ " ) UNION (" // TOTP users
+ " SELECT user_id FROM " + getConfig(start).getTotpUsersTable()
+ " WHERE app_id = ? AND user_id IN ("
+ " SELECT user_id FROM " + getConfig(start).getUserLastActiveTable()
+ " WHERE app_id = ? AND last_active_time >= ?"
+ " )"
+ " )"
+ ") AS all_users";

return execute(start, QUERY, pst -> {
pst.setString(1, appIdentifier.getAppId());
pst.setString(2, appIdentifier.getAppId());
pst.setLong(3, sinceTime);
pst.setString(4, appIdentifier.getAppId());
pst.setString(5, appIdentifier.getAppId());
pst.setLong(6, sinceTime);
}, result -> {
return result.next() ? result.getInt("c") : 0;
});
}
}
Loading

0 comments on commit 9ed97ab

Please sign in to comment.