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.Map;
import java.util.Objects;
import java.util.UUID;

import launcher.LauncherAPI;
import launcher.helper.CommonHelper;
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<>();
	private final Map<String, UUID> usernamesCache = new HashMap<>();

	@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 || !updateAuth(entry.uuid, entry.username, accessToken)) {
			return authError(String.format("UUID is null for username '%s'", username));
		}

		// Update cached access token (and username case)
		entry.username = username;
		entry.accessToken = accessToken;
		entry.serverID = null;
		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) {
		Entry previous = entryCache.put(entry.uuid, entry);
		if (previous != null) { // In case of username changing
			usernamesCache.remove(CommonHelper.low(previous.username));
		}
		usernamesCache.put(CommonHelper.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 updateAuth(UUID uuid, String username, 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(CommonHelper.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;
	}

	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);
		}
	}
}