Add token-based authentication (without rejection handling)
This commit is contained in:
@ -35,6 +35,8 @@ public final class ServerConfig extends Config {
|
||||
put("enableIssueReporting", "e-ir", Boolean::parseBoolean);
|
||||
put("enableGroups", "e-g", Boolean::parseBoolean);
|
||||
put("enableAttachments", "e-a", Boolean::parseBoolean);
|
||||
// user authentication
|
||||
put("authTokenExpiration", "tok-exp", Integer::parseInt);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -93,4 +95,10 @@ public final class ServerConfig extends Config {
|
||||
* @since Envoy Server v0.2-beta
|
||||
*/
|
||||
public String getIssueAuthToken() { return (String) items.get("issueAuthToken").get(); }
|
||||
|
||||
/**
|
||||
* @return the amount of days after which user authentication tokens expire
|
||||
* @since Envoy Server v0.2-beta
|
||||
*/
|
||||
public Integer getAuthTokenExpiration() { return (Integer) items.get("authTokenExpiration").get(); }
|
||||
}
|
||||
|
@ -5,26 +5,18 @@ import static envoy.data.User.UserStatus.ONLINE;
|
||||
import static envoy.event.HandshakeRejection.*;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.*;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.persistence.NoResultException;
|
||||
|
||||
import envoy.data.LoginCredentials;
|
||||
import envoy.event.GroupMessageStatusChange;
|
||||
import envoy.event.HandshakeRejection;
|
||||
import envoy.event.MessageStatusChange;
|
||||
import envoy.server.data.GroupMessage;
|
||||
import envoy.server.data.PersistenceManager;
|
||||
import envoy.server.data.User;
|
||||
import envoy.server.net.ConnectionManager;
|
||||
import envoy.server.net.ObjectWriteProxy;
|
||||
import envoy.server.util.PasswordUtil;
|
||||
import envoy.server.util.VersionUtil;
|
||||
import envoy.util.Bounds;
|
||||
import envoy.util.EnvoyLog;
|
||||
import envoy.event.*;
|
||||
import envoy.server.data.*;
|
||||
import envoy.server.net.*;
|
||||
import envoy.server.util.*;
|
||||
import envoy.util.*;
|
||||
|
||||
/**
|
||||
* This {@link ObjectProcessor} handles {@link LoginCredentials}.<br>
|
||||
@ -62,17 +54,31 @@ public final class LoginCredentialProcessor implements ObjectProcessor<LoginCred
|
||||
try {
|
||||
user = persistenceManager.getUserByName(credentials.getIdentifier());
|
||||
|
||||
// Checking if user is already online
|
||||
// Check if the user is already online
|
||||
if (connectionManager.isOnline(user.getID())) {
|
||||
logger.warning(user + " is already online!");
|
||||
writeProxy.write(socketID, new HandshakeRejection(INTERNAL_ERROR));
|
||||
return;
|
||||
}
|
||||
// Evaluating the correctness of the password hash
|
||||
if (!PasswordUtil.validate(credentials.getPassword(), user.getPasswordHash())) {
|
||||
logger.info(user + " has entered the wrong password.");
|
||||
writeProxy.write(socketID, new HandshakeRejection(WRONG_PASSWORD_OR_USER));
|
||||
return;
|
||||
|
||||
// Authenticate with password or token
|
||||
if (credentials.usesToken()) {
|
||||
|
||||
// Check the token
|
||||
if (user.getAuthToken() == null || user.getAuthTokenExpiration().isBefore(Instant.now())
|
||||
|| !user.getAuthToken().equals(credentials.getPassword())) {
|
||||
logger.info(user + " tried to use an invalid token.");
|
||||
writeProxy.write(socketID, new HandshakeRejection(INVALID_TOKEN));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
|
||||
// Check the password hash
|
||||
if (!PasswordUtil.validate(credentials.getPassword(), user.getPasswordHash())) {
|
||||
logger.info(user + " has entered the wrong password.");
|
||||
writeProxy.write(socketID, new HandshakeRejection(WRONG_PASSWORD_OR_USER));
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (NoResultException e) {
|
||||
logger.info("The requested user does not exist.");
|
||||
@ -80,6 +86,7 @@ public final class LoginCredentialProcessor implements ObjectProcessor<LoginCred
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
|
||||
// Validate user name
|
||||
if (!Bounds.isValidContactName(credentials.getIdentifier())) {
|
||||
logger.info("The requested user name is not valid.");
|
||||
@ -87,7 +94,8 @@ public final class LoginCredentialProcessor implements ObjectProcessor<LoginCred
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// Checking that no user already has this identifier
|
||||
|
||||
// Check if the name is taken
|
||||
PersistenceManager.getInstance().getUserByName(credentials.getIdentifier());
|
||||
|
||||
// This code only gets executed if this user already exists
|
||||
@ -114,6 +122,15 @@ public final class LoginCredentialProcessor implements ObjectProcessor<LoginCred
|
||||
user.setStatus(ONLINE);
|
||||
UserStatusChangeProcessor.updateUserStatus(user);
|
||||
|
||||
// Generate a new token if requested
|
||||
if (credentials.requestToken()) {
|
||||
String token = AuthTokenGenerator.nextToken();
|
||||
user.setAuthToken(token);
|
||||
user.setAuthTokenExpiration(Instant.now().plus(ServerConfig.getInstance().getAuthTokenExpiration().longValue(), ChronoUnit.DAYS));
|
||||
persistenceManager.updateContact(user);
|
||||
writeProxy.write(socketID, new NewAuthToken(token));
|
||||
}
|
||||
|
||||
final var pendingMessages = PersistenceManager.getInstance().getPendingMessages(user, credentials.getLastSync());
|
||||
pendingMessages.removeIf(GroupMessage.class::isInstance);
|
||||
logger.fine("Sending " + pendingMessages.size() + " pending messages to " + user + "...");
|
||||
|
@ -0,0 +1,35 @@
|
||||
package envoy.server.util;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
/**
|
||||
* Provides a secure token generation algorithm.
|
||||
* <p>
|
||||
* Project: <strong>envoy-server</strong><br>
|
||||
* File: <strong>AuthTokenGenerator.java</strong><br>
|
||||
* Created: <strong>19.09.2020</strong><br>
|
||||
*
|
||||
* @author Kai S. K. Engelbart
|
||||
* @since Envoy Server v0.2-beta
|
||||
*/
|
||||
public final class AuthTokenGenerator {
|
||||
|
||||
private static final int TOKEN_LENGTH = 128;
|
||||
private static final char[] CHARACTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".toCharArray();
|
||||
private static final char[] BUFF = new char[TOKEN_LENGTH];
|
||||
private static final SecureRandom RANDOM = new SecureRandom();
|
||||
|
||||
private AuthTokenGenerator() {}
|
||||
|
||||
/**
|
||||
* Generates a random authentication token.
|
||||
*
|
||||
* @return a random authentication token
|
||||
* @since Envoy Server v0.2-beta
|
||||
*/
|
||||
public static String nextToken() {
|
||||
for (int i = 0; i < BUFF.length; ++i)
|
||||
BUFF[i] = CHARACTERS[RANDOM.nextInt(CHARACTERS.length)];
|
||||
return new String(BUFF);
|
||||
}
|
||||
}
|
@ -10,3 +10,4 @@ featureLabel=119
|
||||
issueAuthToken=
|
||||
consoleLevelBarrier=FINEST
|
||||
fileLevelBarrier=WARNING
|
||||
authTokenExpiration=90
|
Reference in New Issue
Block a user