All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.florianmichael.waybackauthlib.WaybackAuthLib Maven / Gradle / Ivy

/*
 * This file is part of WaybackAuthLib - https://github.com/FlorianMichael/WaybackAuthLib
 * Copyright (C) 2023 FlorianMichael/EnZaXD and contributors
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package de.florianmichael.waybackauthlib;

import com.mojang.authlib.GameProfile;
import com.mojang.authlib.minecraft.client.MinecraftClient;
import com.mojang.authlib.properties.Property;

import java.net.Proxy;
import java.net.URI;
import java.util.*;

public class WaybackAuthLib {
    public final static String YGG_PROD = "https://authserver.mojang.com/";

    private final static String ROUTE_AUTHENTICATE = "authenticate";
    private final static String ROUTE_REFRESH = "refresh";
    private final static String ROUTE_INVALIDATE = "invalidate";
    private final static String ROUTE_VALIDATE = "validate";

    private final URI baseURI;
    private final String clientToken;
    private final MinecraftClient client;

    private String username;
    private String password;

    private String accessToken;

    private String userId;

    private boolean loggedIn;
    private GameProfile currentProfile;

    private List properties = new ArrayList<>();
    private List profiles = new ArrayList<>();

    /**
     * Creates a new instance of the authentication service.
     */
    public WaybackAuthLib() {
        this(YGG_PROD, "");
    }

    /**
     * Creates a new instance of the authentication service.
     *
     * @param authHost The host of the authentication service. (e.g. Mojang AuthServer)
     */
    public WaybackAuthLib(final String authHost) {
        this(authHost, "");
    }

    /**
     * Creates a new instance of the authentication service.
     *
     * @param authHost    The host of the authentication service. (e.g. Mojang AuthServer)
     * @param clientToken The client token. (e.g. "")
     */
    public WaybackAuthLib(final String authHost, final String clientToken) {
        this(authHost, clientToken, Proxy.NO_PROXY);
    }

    /**
     * Creates a new instance of the authentication service.
     *
     * @param authHost    The host of the authentication service. (e.g. Mojang AuthServer)
     * @param clientToken The client token. (e.g. "")
     * @param proxy       The proxy to use for the requests.
     */
    public WaybackAuthLib(String authHost, final String clientToken, final Proxy proxy) {
        if (authHost == null) throw new IllegalArgumentException("Authentication is null");
        if (!authHost.endsWith("/")) authHost += "/";

        this.baseURI = URI.create(authHost);

        if (clientToken == null) throw new IllegalArgumentException("ClientToken is null");
        this.clientToken = clientToken;

        this.client = MinecraftClient.unauthenticated(Objects.requireNonNullElse(proxy, Proxy.NO_PROXY));
    }

    /**
     * Logs in the user with the provided credentials. If the user is already logged in, it will refresh the access token.
     *
     * @throws InvalidCredentialsException If the provided credentials are invalid.
     * @throws Exception                   If the server didn't send a response or the client token doesn't match.
     */
    public void logIn() throws Exception {
        if (this.username == null || this.username.isEmpty()) {
            throw new InvalidCredentialsException("Invalid username.");
        }

        final var refreshAccessToken = this.accessToken != null && !this.accessToken.isEmpty();
        final var newAuthentication = this.password != null && !this.password.isEmpty();

        if (!refreshAccessToken && !newAuthentication) {
            throw new InvalidCredentialsException("Invalid password or access token.");
        }

        AuthenticateRefreshResponse response;
        if (refreshAccessToken) {
            response = client.post(this.baseURI.resolve(ROUTE_REFRESH).toURL(), new RefreshRequest(this.clientToken, this.accessToken, null), AuthenticateRefreshResponse.class);
        } else {
            response = client.post(this.baseURI.resolve(ROUTE_AUTHENTICATE).toURL(), new AuthenticationRequest(Agent.MINECRAFT, this.username, this.password, this.clientToken), AuthenticateRefreshResponse.class);
        }

        if (response == null) {
            throw new InvalidRequestException("Server didn't sent a response.");
        }
        if (!response.clientToken.equals(this.clientToken)) {
            throw new InvalidRequestException("Server token and provided token doesn't match.");
        }

        // AuthLib tracks this field, so we do it too in case someone wants to use this library as a drop-in replacement.
        if (response.user != null && response.user.id != null) {
            userId = response.user.id;
        } else {
            userId = getUsername();
        }

        this.accessToken = response.accessToken;
        this.profiles = response.availableProfiles != null ? Arrays.asList(response.availableProfiles) : Collections.emptyList();
        this.currentProfile = response.selectedProfile;

        this.properties.clear();
        if (response.user != null && response.user.properties != null) {
            this.properties.addAll(response.user.properties);
        }

        this.loggedIn = true;
    }

    public boolean checkTokenValidity() {
        final var request = new ValidateRequest(accessToken, clientToken);

        try {
            client.post(this.baseURI.resolve(ROUTE_VALIDATE).toURL(), request, Response.class);
            return true;
        } catch (final Exception ignored) {
            return false;
        }
    }

    /**
     * Logs out the user and invalidates the access token. If the user is not logged in, it will do nothing.
     *
     * @throws Exception If the server didn't send a response or the client token doesn't match.
     */
    public void logOut() throws Exception {
        final var request = new InvalidateRequest(this.clientToken, this.accessToken);
        final var response = client.post(this.baseURI.resolve(ROUTE_INVALIDATE).toURL(), request, Response.class); // Mojang doesn't send this request, but it seems useful to invalidate the token.

        if (!this.loggedIn) {
            throw new IllegalStateException("Cannot log out while not logged in.");
        }

        if (response != null && response.error != null && !response.error.isEmpty()) {
            throw new InvalidRequestException(response.error, response.errorMessage, response.cause);
        }

        this.accessToken = null;

        this.loggedIn = false;
        this.currentProfile = null;

        this.properties = new ArrayList<>();
        this.profiles = new ArrayList<>();
    }

    public String getUsername() {
        return username;
    }

    /**
     * Sets the username. If the user is logged in, it will throw an exception.
     *
     * @param username The username.
     */
    public void setUsername(String username) {
        if (this.loggedIn && this.currentProfile != null) {
            throw new IllegalStateException("Cannot change username whilst logged in & online");
        }

        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    /**
     * Sets the password. If the user is logged in, it will throw an exception.
     *
     * @param password The password.
     */
    public void setPassword(String password) {
        if (this.loggedIn && this.currentProfile != null) {
            throw new IllegalStateException("Cannot set password whilst logged in & online");
        }

        this.password = password;
    }

    public String getAccessToken() {
        return accessToken;
    }

    /**
     * Sets the access token. If the user is logged in, it will throw an exception.
     *
     * @param accessToken The access token.
     */
    public void setAccessToken(String accessToken) {
        if (this.loggedIn && this.currentProfile != null) {
            throw new IllegalStateException("Cannot set access token whilst logged in & online");
        }

        this.accessToken = accessToken;
    }

    public String getUserId() {
        return userId;
    }

    public boolean isLoggedIn() {
        return loggedIn;
    }

    public GameProfile getCurrentProfile() {
        return currentProfile;
    }

    public List getProperties() {
        return properties;
    }

    public List getProfiles() {
        return profiles;
    }

    @Override
    public String toString() {
        return "WaybackAuthLib{" +
                "baseURI=" + baseURI +
                ", clientToken='" + clientToken + '\'' +
                ", client=" + client +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", accessToken='" + accessToken + '\'' +
                ", userId='" + userId + '\'' +
                ", loggedIn=" + loggedIn +
                ", currentProfile=" + currentProfile +
                ", properties=" + properties +
                ", profiles=" + profiles +
                '}';
    }

    /**
     * These classes are only internal and are being used to serialize the requests and responses.
     */

    private static class Agent {
        public final static Agent MINECRAFT = new Agent("Minecraft", 1);

        public String name;
        public int version;

        protected Agent(String name, int version) {
            this.name = name;
            this.version = version;
        }
    }

    private static class User {

        public String id;
        public List properties;
    }

    private static class AuthenticationRequest {

        public Agent agent;
        public String username;
        public String password;
        public String clientToken;
        private boolean requestUser;

        protected AuthenticationRequest(final Agent agent, final String username, final String password, final String clientToken) {
            this.agent = agent;
            this.username = username;
            this.password = password;
            this.clientToken = clientToken;
            this.requestUser = true; // Only pass this field to fill the authentication header
        }
    }

    private static class RefreshRequest {

        public String clientToken;
        public String accessToken;
        public GameProfile selectedProfile;
        public boolean requestUser;

        protected RefreshRequest(String clientToken, String accessToken, GameProfile selectedProfile) {
            this.clientToken = clientToken;
            this.accessToken = accessToken;
            this.selectedProfile = selectedProfile;
            this.requestUser = true;
        }
    }

    private static class ValidateRequest {

        public String clientToken;
        public String accessToken;

        public ValidateRequest(final String accessToken, final String clientToken) {
            this.clientToken = clientToken;
            this.accessToken = accessToken;
        }
    }

    private static class InvalidateRequest {

        public String clientToken;
        public String accessToken;

        protected InvalidateRequest(String clientToken, String accessToken) {
            this.clientToken = clientToken;
            this.accessToken = accessToken;
        }
    }

    private static class Response {

        public String error;
        public String errorMessage;
        public String cause;
    }

    private static class AuthenticateRefreshResponse extends Response {

        public String accessToken;
        public String clientToken;
        public GameProfile selectedProfile;
        public GameProfile[] availableProfiles;
        public User user;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy