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);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user