Newer
Older
KeeperJerry_Launcher / LaunchServer / source / auth / handler / CachedAuthHandler.java
package launchserver.auth.handler;

import java.io.IOException;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;

import launcher.LauncherAPI;
import launcher.helper.IOHelper;
import launcher.helper.SecurityHelper;
import launcher.request.auth.JoinServerRequest;
import launcher.serialize.config.entry.BlockConfigEntry;

public abstract class CachedAuthHandler extends AuthHandler {
	private final Map<UUID, Entry> entryCache = new HashMap<>(IOHelper.BUFFER_SIZE);
	private final Map<String, UUID> usernamesCache = new HashMap<>(IOHelper.BUFFER_SIZE);

	@LauncherAPI
	protected CachedAuthHandler(BlockConfigEntry block) {
		super(block);
	}

	@Override
	public final synchronized UUID auth(String username, String accessToken) throws IOException {
		Entry entry = getEntry(username);
		if (entry == null || !updateAccessToken(entry.uuid, accessToken)) {
			return null; // Account doesn't exist
		}

		// Update cached access token (and username case)
		entry.username = username;
		entry.accessToken = accessToken;
		return entry.uuid;
	}

	@Override
	public final synchronized UUID checkServer(String username, String serverID) throws IOException {
		Entry entry = getEntry(username);
		return entry != null && username.equals(entry.username) &&
			serverID.equals(entry.serverID) ? entry.uuid : null;
	}

	@Override
	public final synchronized boolean joinServer(String username, String accessToken, String serverID) throws IOException {
		Entry entry = getEntry(username);
		if (entry == null || !username.equals(entry.username) ||
			!accessToken.equals(entry.accessToken) || !updateServerID(entry.uuid, serverID)) {
			return false; // Account doesn't exist or invalid access token
		}

		// Update cached server ID
		entry.serverID = serverID;
		return true;
	}

	@Override
	public final synchronized UUID usernameToUUID(String username) throws IOException {
		Entry entry = getEntry(username);
		return entry == null ? null : entry.uuid;
	}

	@Override
	public final synchronized String uuidToUsername(UUID uuid) throws IOException {
		Entry entry = getEntry(uuid);
		return entry == null ? null : entry.username;
	}

	@LauncherAPI
	protected void addEntry(Entry entry) {
		entryCache.putIfAbsent(entry.uuid, entry);
		usernamesCache.put(low(entry.username), entry.uuid);
	}

	@LauncherAPI
	protected abstract Entry fetchEntry(UUID uuid) throws IOException;

	@LauncherAPI
	protected abstract Entry fetchEntry(String username) throws IOException;

	@LauncherAPI
	protected abstract boolean updateAccessToken(UUID uuid, String accessToken) throws IOException;

	@LauncherAPI
	protected abstract boolean updateServerID(UUID uuid, String serverID) throws IOException;

	private Entry getEntry(UUID uuid) throws IOException {
		Entry entry = entryCache.get(uuid);
		if (entry == null) {
			entry = fetchEntry(uuid);
			if (entry != null) {
				addEntry(entry);
			}
		}
		return entry;
	}

	private Entry getEntry(String username) throws IOException {
		UUID uuid = usernamesCache.get(low(username));
		if (uuid != null) {
			return getEntry(uuid);
		}

		// Fetch entry by username
		Entry entry = fetchEntry(username);
		if (entry != null) {
			addEntry(entry);
		}

		// Return what we got
		return entry;
	}

	private static String low(String username) {
		return username.toLowerCase(Locale.US);
	}

	public final class Entry {
		@LauncherAPI public final UUID uuid;
		private String username;
		private String accessToken;
		private String serverID;

		@LauncherAPI
		public Entry(UUID uuid, String username, String accessToken, String serverID) {
			this.uuid = Objects.requireNonNull(uuid, "uuid");
			this.username = Objects.requireNonNull(username, "username");
			this.accessToken = accessToken == null ?
				null : SecurityHelper.verifyToken(accessToken);
			this.serverID = serverID == null ?
				null : JoinServerRequest.verifyServerID(serverID);
		}
	}
}