forked from External/mage
Implement a password reset flow.
This commit is contained in:
parent
23d47be04c
commit
746d034461
14 changed files with 900 additions and 37 deletions
|
|
@ -14,7 +14,7 @@ import org.apache.shiro.crypto.hash.Hash;
|
|||
@DatabaseTable(tableName = "authorized_user")
|
||||
public class AuthorizedUser {
|
||||
|
||||
@DatabaseField(indexName = "name_index")
|
||||
@DatabaseField(indexName = "name_index", unique = true)
|
||||
protected String name;
|
||||
|
||||
@DatabaseField
|
||||
|
|
@ -29,7 +29,7 @@ public class AuthorizedUser {
|
|||
@DatabaseField
|
||||
protected int hashIterations;
|
||||
|
||||
@DatabaseField
|
||||
@DatabaseField(indexName = "email_index", unique = true)
|
||||
protected String email;
|
||||
|
||||
public AuthorizedUser() {
|
||||
|
|
@ -53,4 +53,8 @@ public class AuthorizedUser {
|
|||
ByteSource.Util.bytes(Base64.decode(this.salt)), "");
|
||||
return matcher.doCredentialsMatch(token, info);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package mage.server;
|
|||
import com.j256.ormlite.dao.Dao;
|
||||
import com.j256.ormlite.dao.DaoManager;
|
||||
import com.j256.ormlite.jdbc.JdbcConnectionSource;
|
||||
import com.j256.ormlite.stmt.DeleteBuilder;
|
||||
import com.j256.ormlite.stmt.QueryBuilder;
|
||||
import com.j256.ormlite.stmt.SelectArg;
|
||||
import com.j256.ormlite.support.ConnectionSource;
|
||||
|
|
@ -11,7 +12,6 @@ import com.j256.ormlite.table.TableUtils;
|
|||
import java.io.File;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
import mage.cards.repository.RepositoryUtil;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.apache.shiro.crypto.RandomNumberGenerator;
|
||||
|
|
@ -54,25 +54,25 @@ public enum AuthorizedUserRepository {
|
|||
|
||||
public void add(final String userName, final String password, final String email) {
|
||||
try {
|
||||
dao.callBatchTasks(new Callable<Object>() {
|
||||
@Override
|
||||
public Object call() throws Exception {
|
||||
try {
|
||||
Hash hash = new SimpleHash(Sha256Hash.ALGORITHM_NAME, password, rng.nextBytes(), 1024);
|
||||
AuthorizedUser user = new AuthorizedUser(userName, hash, email);
|
||||
dao.create(user);
|
||||
} catch (SQLException ex) {
|
||||
Logger.getLogger(AuthorizedUserRepository.class).error("Error adding a user to DB - ", ex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
} catch (Exception ex) {
|
||||
Logger.getLogger(AuthorizedUserRepository.class).error("Error adding a authorized_user - ", ex);
|
||||
Hash hash = new SimpleHash(Sha256Hash.ALGORITHM_NAME, password, rng.nextBytes(), 1024);
|
||||
AuthorizedUser user = new AuthorizedUser(userName, hash, email);
|
||||
dao.create(user);
|
||||
} catch (SQLException ex) {
|
||||
Logger.getLogger(AuthorizedUserRepository.class).error("Error adding a user to DB - ", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public AuthorizedUser get(String userName) {
|
||||
public void remove(final String userName) {
|
||||
try {
|
||||
DeleteBuilder<AuthorizedUser, Object> db = dao.deleteBuilder();
|
||||
db.where().eq("name", new SelectArg(userName));
|
||||
db.delete();
|
||||
} catch (SQLException ex) {
|
||||
Logger.getLogger(AuthorizedUserRepository.class).error("Error removing a user from DB - ", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public AuthorizedUser getByName(String userName) {
|
||||
try {
|
||||
QueryBuilder<AuthorizedUser, Object> qb = dao.queryBuilder();
|
||||
qb.where().eq("name", new SelectArg(userName));
|
||||
|
|
@ -87,6 +87,21 @@ public enum AuthorizedUserRepository {
|
|||
return null;
|
||||
}
|
||||
|
||||
public AuthorizedUser getByEmail(String userName) {
|
||||
try {
|
||||
QueryBuilder<AuthorizedUser, Object> qb = dao.queryBuilder();
|
||||
qb.where().eq("email", new SelectArg(userName));
|
||||
List<AuthorizedUser> results = dao.query(qb.prepare());
|
||||
if (results.size() == 1) {
|
||||
return results.get(0);
|
||||
}
|
||||
return null;
|
||||
} catch (SQLException ex) {
|
||||
Logger.getLogger(AuthorizedUserRepository.class).error("Error getting a authorized_user - ", ex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void closeDB() {
|
||||
try {
|
||||
if (dao != null && dao.getConnectionSource() != null) {
|
||||
|
|
|
|||
|
|
@ -27,9 +27,12 @@
|
|||
*/
|
||||
package mage.server;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
|
@ -95,9 +98,17 @@ public class MageServerImpl implements MageServer {
|
|||
|
||||
private static final Logger logger = Logger.getLogger(MageServerImpl.class);
|
||||
private static final ExecutorService callExecutor = ThreadExecutor.getInstance().getCallExecutor();
|
||||
|
||||
private static final SecureRandom RANDOM = new SecureRandom();
|
||||
|
||||
private final String adminPassword;
|
||||
private final boolean testMode;
|
||||
private final LinkedHashMap<String, String> activeAuthTokens = new LinkedHashMap<String, String>() {
|
||||
@Override
|
||||
protected boolean removeEldestEntry(Map.Entry<String, String> eldest) {
|
||||
// Keep the latest 1024 auth tokens in memory.
|
||||
return size() > 1024;
|
||||
}
|
||||
};
|
||||
|
||||
public MageServerImpl(String adminPassword, boolean testMode) {
|
||||
this.adminPassword = adminPassword;
|
||||
|
|
@ -110,6 +121,50 @@ public class MageServerImpl implements MageServer {
|
|||
return SessionManager.getInstance().registerUser(sessionId, userName, password, email);
|
||||
}
|
||||
|
||||
// generateAuthToken returns a uniformly distributed 6-digits string.
|
||||
static private String generateAuthToken() {
|
||||
return String.format("%06d", RANDOM.nextInt(1000000));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean emailAuthToken(String sessionId, String email) throws MageException {
|
||||
AuthorizedUser authorizedUser = AuthorizedUserRepository.instance.getByEmail(email);
|
||||
if (authorizedUser == null) {
|
||||
sendErrorMessageToClient(sessionId, "No user was found with the email address " + email);
|
||||
logger.info("Auth token is requested for " + email + " but there's no such user in DB");
|
||||
return false;
|
||||
}
|
||||
String authToken = generateAuthToken();
|
||||
activeAuthTokens.put(email, authToken);
|
||||
if (!GmailClient.sendMessage(email, "XMage Password Reset Auth Token",
|
||||
"Use this auth token to reset your password: " + authToken + "\n" +
|
||||
"It's valid until the next server restart.")) {
|
||||
sendErrorMessageToClient(sessionId, "There was an error inside the server while emailing an auth token");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean resetPassword(String sessionId, String email, String authToken, String password) throws MageException {
|
||||
String storedAuthToken = activeAuthTokens.get(email);
|
||||
if (storedAuthToken == null || !storedAuthToken.equals(authToken)) {
|
||||
sendErrorMessageToClient(sessionId, "Invalid auth token");
|
||||
logger.info("Invalid auth token " + authToken + " is sent for " + email);
|
||||
return false;
|
||||
}
|
||||
AuthorizedUser authorizedUser = AuthorizedUserRepository.instance.getByEmail(email);
|
||||
if (authorizedUser == null) {
|
||||
sendErrorMessageToClient(sessionId, "The user is no longer in the DB");
|
||||
logger.info("Auth token is valid, but the user with email address " + email + " is no longer in the DB");
|
||||
return false;
|
||||
}
|
||||
AuthorizedUserRepository.instance.remove(authorizedUser.getName());
|
||||
AuthorizedUserRepository.instance.add(authorizedUser.getName(), password, email);
|
||||
activeAuthTokens.remove(email);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean registerClient(String userName, String sessionId, MageVersion version) throws MageException {
|
||||
// This method is deprecated, so just inform the server version.
|
||||
|
|
@ -1045,6 +1100,15 @@ public class MageServerImpl implements MageServer {
|
|||
}
|
||||
}
|
||||
|
||||
private void sendErrorMessageToClient(final String sessionId, final String message) throws MageException {
|
||||
execute("sendErrorMessageToClient", sessionId, new Action() {
|
||||
@Override
|
||||
public void execute() {
|
||||
SessionManager.getInstance().sendErrorMessageToClient(sessionId, message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected void execute(final String actionName, final String sessionId, final Action action, boolean checkAdminRights) throws MageException {
|
||||
if (checkAdminRights) {
|
||||
if (!SessionManager.getInstance().isAdmin(sessionId)) {
|
||||
|
|
|
|||
|
|
@ -95,6 +95,11 @@ public class Session {
|
|||
sendErrorMessageToClient(returnMessage);
|
||||
return returnMessage;
|
||||
}
|
||||
returnMessage = validateEmail(email);
|
||||
if (returnMessage != null) {
|
||||
sendErrorMessageToClient(returnMessage);
|
||||
return returnMessage;
|
||||
}
|
||||
AuthorizedUserRepository.instance.add(userName, password, email);
|
||||
if (GmailClient.sendMessage(email, "XMage Registration Completed",
|
||||
"You are successfully registered as " + userName + ".")) {
|
||||
|
|
@ -121,7 +126,7 @@ public class Session {
|
|||
if (m.find()) {
|
||||
return "User name '" + userName + "' includes not allowed characters: use a-z, A-Z and 0-9";
|
||||
}
|
||||
AuthorizedUser authorizedUser = AuthorizedUserRepository.instance.get(userName);
|
||||
AuthorizedUser authorizedUser = AuthorizedUserRepository.instance.getByName(userName);
|
||||
if (authorizedUser != null) {
|
||||
return "User name '" + userName + "' already in use";
|
||||
}
|
||||
|
|
@ -147,6 +152,14 @@ public class Session {
|
|||
return null;
|
||||
}
|
||||
|
||||
static private String validateEmail(String email) {
|
||||
AuthorizedUser authorizedUser = AuthorizedUserRepository.instance.getByEmail(email);
|
||||
if (authorizedUser != null) {
|
||||
return "Email address '" + email + "' is associated with another user";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public String connectUser(String userName, String password) throws MageException {
|
||||
String returnMessage = connectUserHandling(userName, password);
|
||||
if (returnMessage != null) {
|
||||
|
|
@ -161,9 +174,8 @@ public class Session {
|
|||
|
||||
public String connectUserHandling(String userName, String password) throws MageException {
|
||||
this.isAdmin = false;
|
||||
|
||||
if (ConfigSettings.getInstance().isAuthenticationActivated()) {
|
||||
AuthorizedUser authorizedUser = AuthorizedUserRepository.instance.get(userName);
|
||||
AuthorizedUser authorizedUser = AuthorizedUserRepository.instance.getByName(userName);
|
||||
if (authorizedUser == null || !authorizedUser.doCredentialsMatch(userName, password)) {
|
||||
return "Wrong username or password";
|
||||
}
|
||||
|
|
@ -347,7 +359,7 @@ public class Session {
|
|||
this.host = hostAddress;
|
||||
}
|
||||
|
||||
void sendErrorMessageToClient(String message) {
|
||||
public void sendErrorMessageToClient(String message) {
|
||||
List<String> messageData = new LinkedList<>();
|
||||
messageData.add("Error while connecting to server");
|
||||
messageData.add(message);
|
||||
|
|
|
|||
|
|
@ -237,4 +237,13 @@ public class SessionManager {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void sendErrorMessageToClient(String sessionId, String message) {
|
||||
Session session = sessions.get(sessionId);
|
||||
if (session == null) {
|
||||
logger.error("Following error message is not delivered because session " + sessionId + " is not found: " + message);
|
||||
return;
|
||||
}
|
||||
session.sendErrorMessageToClient(message);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue