mirror of
https://github.com/SKCraft/Launcher.git
synced 2025-01-05 19:09:03 +01:00
Start refactoring auth framework
Featuring: * New account selection dialog * Upgrade to Java 8! * Questionable transitional class names * Lots of swing code!
This commit is contained in:
parent
25a433ab5b
commit
b6252863da
@ -29,8 +29,8 @@ subprojects {
|
||||
group = 'com.skcraft'
|
||||
version = '4.4-SNAPSHOT'
|
||||
|
||||
sourceCompatibility = 1.6
|
||||
targetCompatibility = 1.6
|
||||
sourceCompatibility = 1.8
|
||||
targetCompatibility = 1.8
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
|
@ -12,8 +12,9 @@ import com.google.common.base.Strings;
|
||||
import com.google.common.base.Supplier;
|
||||
import com.google.common.util.concurrent.ListeningExecutorService;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
import com.skcraft.launcher.auth.AccountList;
|
||||
import com.skcraft.launcher.auth.LoginService;
|
||||
import com.skcraft.launcher.auth.NewAccountList;
|
||||
import com.skcraft.launcher.auth.UserType;
|
||||
import com.skcraft.launcher.auth.YggdrasilLoginService;
|
||||
import com.skcraft.launcher.launch.LaunchSupervisor;
|
||||
import com.skcraft.launcher.model.minecraft.Library;
|
||||
@ -63,7 +64,7 @@ public final class Launcher {
|
||||
@Getter private final Properties properties;
|
||||
@Getter private final InstanceList instances;
|
||||
@Getter private final Configuration config;
|
||||
@Getter private final AccountList accounts;
|
||||
@Getter private final NewAccountList accounts;
|
||||
@Getter private final AssetsRoot assets;
|
||||
@Getter private final LaunchSupervisor launchSupervisor = new LaunchSupervisor(this);
|
||||
@Getter private final UpdateManager updateManager = new UpdateManager(this);
|
||||
@ -96,14 +97,10 @@ public final class Launcher {
|
||||
this.instances = new InstanceList(this);
|
||||
this.assets = new AssetsRoot(new File(baseDir, "assets"));
|
||||
this.config = Persistence.load(new File(configDir, "config.json"), Configuration.class);
|
||||
this.accounts = Persistence.load(new File(configDir, "accounts.dat"), AccountList.class);
|
||||
this.accounts = Persistence.load(new File(configDir, "accounts.dat"), NewAccountList.class);
|
||||
|
||||
setDefaultConfig();
|
||||
|
||||
if (accounts.getSize() > 0) {
|
||||
accounts.setSelectedItem(accounts.getElementAt(0));
|
||||
}
|
||||
|
||||
executor.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@ -161,12 +158,20 @@ public final class Launcher {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a login service.
|
||||
* Get the Yggdrasil login service.
|
||||
*
|
||||
* @return a login service
|
||||
* @return the Yggdrasil (legacy) login service
|
||||
*/
|
||||
public LoginService getLoginService() {
|
||||
return new YggdrasilLoginService(HttpRequest.url(getProperties().getProperty("yggdrasilAuthUrl")));
|
||||
public YggdrasilLoginService getYggdrasil() {
|
||||
return new YggdrasilLoginService(HttpRequest.url(getProperties().getProperty("yggdrasilAuthUrl")), accounts.getClientId());
|
||||
}
|
||||
|
||||
public LoginService getLoginService(UserType type) {
|
||||
if (type == UserType.MICROSOFT) {
|
||||
return null; // TODO: Microsoft login service
|
||||
} else {
|
||||
return getYggdrasil();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -29,7 +29,7 @@ import java.util.List;
|
||||
getterVisibility = JsonAutoDetect.Visibility.NONE,
|
||||
setterVisibility = JsonAutoDetect.Visibility.NONE,
|
||||
fieldVisibility = JsonAutoDetect.Visibility.NONE)
|
||||
public class AccountList extends AbstractListModel implements ComboBoxModel {
|
||||
public class AccountList extends AbstractListModel<Account> implements ComboBoxModel<Account> {
|
||||
|
||||
@JsonProperty
|
||||
@Getter
|
||||
|
@ -7,7 +7,6 @@
|
||||
package com.skcraft.launcher.auth;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A service for creating authenticated sessions.
|
||||
@ -15,17 +14,15 @@ import java.util.List;
|
||||
public interface LoginService {
|
||||
|
||||
/**
|
||||
* Attempt to login with the given details.
|
||||
* Attempt to restore a saved session into an active session.
|
||||
*
|
||||
* @param agent the game to authenticate for, such as "Minecraft"
|
||||
* @param id the login ID
|
||||
* @param password the password
|
||||
* @return a list of authenticated sessions, which corresponds to identities
|
||||
* @param savedSession Session to restore
|
||||
* @return An authenticated session, which corresponds to a Minecraft account
|
||||
* @throws IOException thrown on I/O error
|
||||
* @throws InterruptedException thrown if interrupted
|
||||
* @throws AuthenticationException thrown on an authentication error
|
||||
*/
|
||||
List<? extends Session> login(String agent, String id, String password)
|
||||
Session restore(SavedSession savedSession)
|
||||
throws IOException, InterruptedException, AuthenticationException;
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,74 @@
|
||||
package com.skcraft.launcher.auth;
|
||||
|
||||
import com.beust.jcommander.internal.Lists;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.skcraft.launcher.dialog.component.ListListenerReducer;
|
||||
import com.skcraft.launcher.persistence.Scrambled;
|
||||
import lombok.Data;
|
||||
import org.apache.commons.lang.RandomStringUtils;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.ListDataEvent;
|
||||
import javax.swing.event.ListDataListener;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Persisted account list
|
||||
*/
|
||||
@Scrambled("ACCOUNT_LIST_NOT_SECURITY!")
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class NewAccountList implements ListModel<SavedSession> {
|
||||
private List<SavedSession> accounts = Lists.newArrayList();
|
||||
private String clientId = RandomStringUtils.randomAlphanumeric(24);
|
||||
|
||||
@JsonIgnore private final ListListenerReducer listeners = new ListListenerReducer();
|
||||
|
||||
public synchronized void add(SavedSession session) {
|
||||
accounts.add(session);
|
||||
|
||||
int index = accounts.size() - 1;
|
||||
listeners.intervalAdded(new ListDataEvent(this, ListDataEvent.INTERVAL_ADDED, index, index));
|
||||
}
|
||||
|
||||
public synchronized void remove(SavedSession session) {
|
||||
int index = accounts.indexOf(session);
|
||||
|
||||
if (index > -1) {
|
||||
accounts.remove(index);
|
||||
listeners.intervalRemoved(new ListDataEvent(this, ListDataEvent.INTERVAL_REMOVED, index, index));
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void update(SavedSession newSavedSession) {
|
||||
int index = accounts.indexOf(newSavedSession);
|
||||
|
||||
if (index > -1) {
|
||||
accounts.set(index, newSavedSession);
|
||||
listeners.contentsChanged(new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, index, index));
|
||||
} else {
|
||||
this.add(newSavedSession);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
return accounts.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SavedSession getElementAt(int index) {
|
||||
return accounts.get(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addListDataListener(ListDataListener l) {
|
||||
listeners.addListDataListener(l);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListDataListener(ListDataListener l) {
|
||||
listeners.removeListDataListener(l);
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package com.skcraft.launcher.auth;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import lombok.Data;
|
||||
import org.apache.commons.lang.builder.HashCodeBuilder;
|
||||
|
||||
/**
|
||||
* Represents a session saved to disk.
|
||||
*/
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class SavedSession {
|
||||
private UserType type;
|
||||
private String uuid;
|
||||
private String username;
|
||||
private String accessToken;
|
||||
private String refreshToken;
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
SavedSession that = (SavedSession) o;
|
||||
|
||||
return getUuid().equals(that.getUuid());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return new HashCodeBuilder(17, 37)
|
||||
.append(uuid)
|
||||
.toHashCode();
|
||||
}
|
||||
}
|
@ -71,4 +71,19 @@ public interface Session {
|
||||
*/
|
||||
boolean isOnline();
|
||||
|
||||
/**
|
||||
* Convert this session to a saved session
|
||||
* @return Saved session that represents this active session
|
||||
*/
|
||||
default SavedSession toSavedSession() {
|
||||
SavedSession savedSession = new SavedSession();
|
||||
|
||||
savedSession.setType(getUserType());
|
||||
savedSession.setUsername(getName());
|
||||
savedSession.setUuid(getUuid());
|
||||
savedSession.setAccessToken(getAccessToken());
|
||||
|
||||
return savedSession;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -18,7 +18,11 @@ public enum UserType {
|
||||
/**
|
||||
* Mojang accounts login with an email address.
|
||||
*/
|
||||
MOJANG;
|
||||
MOJANG,
|
||||
/**
|
||||
* Microsoft accounts login via OAuth.
|
||||
*/
|
||||
MICROSOFT;
|
||||
|
||||
/**
|
||||
* Return a lowercase version of the enum type.
|
||||
|
@ -11,45 +11,63 @@ import com.skcraft.launcher.util.HttpRequest;
|
||||
import lombok.Data;
|
||||
import lombok.NonNull;
|
||||
import lombok.ToString;
|
||||
import lombok.extern.java.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Creates authenticated sessions using the Mojang Yggdrasil login protocol.
|
||||
*/
|
||||
@Log
|
||||
public class YggdrasilLoginService implements LoginService {
|
||||
|
||||
private final URL authUrl;
|
||||
private final String clientId;
|
||||
|
||||
/**
|
||||
* Create a new login service with the given authentication URL.
|
||||
*
|
||||
* @param authUrl the authentication URL
|
||||
* @param clientId
|
||||
*/
|
||||
public YggdrasilLoginService(@NonNull URL authUrl) {
|
||||
public YggdrasilLoginService(@NonNull URL authUrl, String clientId) {
|
||||
this.authUrl = authUrl;
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
public Session login(String agent, String id, String password)
|
||||
throws IOException, InterruptedException, AuthenticationException {
|
||||
AuthenticatePayload payload = new AuthenticatePayload(new Agent(agent), id, password, clientId);
|
||||
|
||||
return call(this.authUrl, payload);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends Session> login(String agent, String id, String password)
|
||||
public Session restore(SavedSession savedSession)
|
||||
throws IOException, InterruptedException, AuthenticationException {
|
||||
Object payload = new AuthenticatePayload(new Agent(agent), id, password);
|
||||
RefreshPayload payload = new RefreshPayload(savedSession.getAccessToken(), clientId);
|
||||
|
||||
HttpRequest request = HttpRequest
|
||||
.post(authUrl)
|
||||
return call(new URL(this.authUrl, "/refresh"), payload);
|
||||
}
|
||||
|
||||
private Session call(URL url, Object payload)
|
||||
throws IOException, InterruptedException, AuthenticationException {
|
||||
HttpRequest req = HttpRequest
|
||||
.post(url)
|
||||
.bodyJson(payload)
|
||||
.execute();
|
||||
|
||||
if (request.getResponseCode() != 200) {
|
||||
ErrorResponse error = request.returnContent().asJson(ErrorResponse.class);
|
||||
if (req.getResponseCode() != 200) {
|
||||
ErrorResponse error = req.returnContent().asJson(ErrorResponse.class);
|
||||
log.warning(error.toString());
|
||||
throw new AuthenticationException(error.getErrorMessage(), error.getErrorMessage());
|
||||
} else {
|
||||
AuthenticateResponse response = request.returnContent().asJson(AuthenticateResponse.class);
|
||||
return response.getAvailableProfiles();
|
||||
AuthenticateResponse response = req.returnContent().asJson(AuthenticateResponse.class);
|
||||
|
||||
return response.getSelectedProfile();
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,6 +82,14 @@ public class YggdrasilLoginService implements LoginService {
|
||||
private final Agent agent;
|
||||
private final String username;
|
||||
private final String password;
|
||||
private final String clientToken;
|
||||
}
|
||||
|
||||
@Data
|
||||
private static class RefreshPayload {
|
||||
private final String accessToken;
|
||||
private final String clientToken;
|
||||
private boolean requestUser = true;
|
||||
}
|
||||
|
||||
@Data
|
||||
@ -71,8 +97,7 @@ public class YggdrasilLoginService implements LoginService {
|
||||
private static class AuthenticateResponse {
|
||||
private String accessToken;
|
||||
private String clientToken;
|
||||
@JsonManagedReference private List<Profile> availableProfiles;
|
||||
private Profile selectedProfile;
|
||||
@JsonManagedReference private Profile selectedProfile;
|
||||
}
|
||||
|
||||
@Data
|
||||
|
@ -0,0 +1,178 @@
|
||||
package com.skcraft.launcher.dialog;
|
||||
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.skcraft.concurrency.ObservableFuture;
|
||||
import com.skcraft.concurrency.ProgressObservable;
|
||||
import com.skcraft.launcher.Launcher;
|
||||
import com.skcraft.launcher.auth.LoginService;
|
||||
import com.skcraft.launcher.auth.SavedSession;
|
||||
import com.skcraft.launcher.auth.Session;
|
||||
import com.skcraft.launcher.persistence.Persistence;
|
||||
import com.skcraft.launcher.swing.LinedBoxPanel;
|
||||
import com.skcraft.launcher.swing.SwingHelper;
|
||||
import com.skcraft.launcher.util.SharedLocale;
|
||||
import com.skcraft.launcher.util.SwingExecutor;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.net.URL;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import static com.skcraft.launcher.util.HttpRequest.url;
|
||||
|
||||
public class AccountSelectDialog extends JDialog {
|
||||
private final JList<SavedSession> accountList;
|
||||
private final JButton loginButton = new JButton(SharedLocale.tr("login.login"));
|
||||
private final JButton cancelButton = new JButton(SharedLocale.tr("button.cancel"));
|
||||
private final JButton addAccountButton = new JButton(SharedLocale.tr("accounts.addNew"));
|
||||
private final LinedBoxPanel buttonsPanel = new LinedBoxPanel(true);
|
||||
|
||||
private final Launcher launcher;
|
||||
private Session selected;
|
||||
|
||||
public AccountSelectDialog(Window owner, Launcher launcher) {
|
||||
super(owner, ModalityType.DOCUMENT_MODAL);
|
||||
|
||||
this.launcher = launcher;
|
||||
this.accountList = new JList<>(launcher.getAccounts());
|
||||
|
||||
setTitle(SharedLocale.tr("accounts.title"));
|
||||
initComponents();
|
||||
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
|
||||
setMinimumSize(new Dimension(420, 50));
|
||||
setResizable(false);
|
||||
pack();
|
||||
setLocationRelativeTo(owner);
|
||||
}
|
||||
|
||||
private void initComponents() {
|
||||
accountList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||
accountList.setLayoutOrientation(JList.HORIZONTAL_WRAP);
|
||||
accountList.setVisibleRowCount(0);
|
||||
accountList.setCellRenderer(new AccountRenderer());
|
||||
|
||||
JScrollPane accountPane = new JScrollPane(accountList);
|
||||
accountPane.setPreferredSize(new Dimension(250, 100));
|
||||
accountPane.setAlignmentX(LEFT_ALIGNMENT);
|
||||
|
||||
loginButton.setFont(loginButton.getFont().deriveFont(Font.BOLD));
|
||||
|
||||
buttonsPanel.setBorder(BorderFactory.createEmptyBorder(26, 13, 13, 13));
|
||||
buttonsPanel.addGlue();
|
||||
buttonsPanel.addElement(loginButton);
|
||||
buttonsPanel.addElement(cancelButton);
|
||||
|
||||
JPanel listPane = new JPanel();
|
||||
listPane.setLayout(new BoxLayout(listPane, BoxLayout.PAGE_AXIS));
|
||||
listPane.add(accountPane);
|
||||
listPane.add(Box.createVerticalStrut(5));
|
||||
listPane.add(addAccountButton);
|
||||
listPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
||||
|
||||
add(listPane, BorderLayout.CENTER);
|
||||
add(buttonsPanel, BorderLayout.SOUTH);
|
||||
|
||||
loginButton.addActionListener(ev -> attemptLogin(accountList.getSelectedValue()));
|
||||
cancelButton.addActionListener(ev -> dispose());
|
||||
|
||||
addAccountButton.addActionListener(ev -> {
|
||||
Session newSession = LoginDialog.showLoginRequest(this, launcher);
|
||||
|
||||
if (newSession != null) {
|
||||
launcher.getAccounts().add(newSession.toSavedSession());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
accountList.setModel(new DefaultListModel<>());
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
public static Session showAccountRequest(Window owner, Launcher launcher) {
|
||||
AccountSelectDialog dialog = new AccountSelectDialog(owner, launcher);
|
||||
dialog.setVisible(true);
|
||||
|
||||
if (dialog.selected != null) {
|
||||
launcher.getAccounts().update(dialog.selected.toSavedSession());
|
||||
Persistence.commitAndForget(launcher.getAccounts());
|
||||
}
|
||||
|
||||
return dialog.selected;
|
||||
}
|
||||
|
||||
private void setResult(Session result) {
|
||||
this.selected = result;
|
||||
dispose();
|
||||
}
|
||||
|
||||
private void attemptLogin(SavedSession session) {
|
||||
LoginService loginService = launcher.getLoginService(session.getType());
|
||||
RestoreSessionCallable callable = new RestoreSessionCallable(loginService, session);
|
||||
|
||||
ObservableFuture<Session> future = new ObservableFuture<>(launcher.getExecutor().submit(callable), callable);
|
||||
Futures.addCallback(future, new FutureCallback<Session>() {
|
||||
@Override
|
||||
public void onSuccess(Session result) {
|
||||
// session.setAccessToken(result.getAccessToken());
|
||||
setResult(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable t) {
|
||||
t.printStackTrace();
|
||||
}
|
||||
}, SwingExecutor.INSTANCE);
|
||||
|
||||
ProgressDialog.showProgress(this, future, SharedLocale.tr("login.loggingInTitle"),
|
||||
SharedLocale.tr("login.loggingInStatus"));
|
||||
SwingHelper.addErrorDialogCallback(this, future);
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
private static class RestoreSessionCallable implements Callable<Session>, ProgressObservable {
|
||||
private final LoginService service;
|
||||
private final SavedSession session;
|
||||
|
||||
@Override
|
||||
public Session call() throws Exception {
|
||||
return service.restore(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getStatus() {
|
||||
return SharedLocale.tr("accounts.refreshingStatus");
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getProgress() {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
private static class AccountRenderer extends JLabel implements ListCellRenderer<SavedSession> {
|
||||
public AccountRenderer() {
|
||||
setHorizontalAlignment(CENTER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getListCellRendererComponent(JList<? extends SavedSession> list, SavedSession value, int index, boolean isSelected, boolean cellHasFocus) {
|
||||
setText(value.getUsername());
|
||||
|
||||
URL avatarUrl = url("https://visage.surgeplay.com/face/24/" + value.getUuid() + ".png");
|
||||
setIcon(new ImageIcon(avatarUrl));
|
||||
|
||||
if (isSelected) {
|
||||
setOpaque(true);
|
||||
setBackground(new Color(0x397BBF));
|
||||
} else {
|
||||
setOpaque(false);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
@ -6,16 +6,18 @@
|
||||
|
||||
package com.skcraft.launcher.dialog;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.skcraft.concurrency.ObservableFuture;
|
||||
import com.skcraft.concurrency.ProgressObservable;
|
||||
import com.skcraft.launcher.Configuration;
|
||||
import com.skcraft.launcher.Launcher;
|
||||
import com.skcraft.launcher.auth.*;
|
||||
import com.skcraft.launcher.swing.*;
|
||||
import com.skcraft.launcher.auth.Account;
|
||||
import com.skcraft.launcher.auth.AuthenticationException;
|
||||
import com.skcraft.launcher.auth.Session;
|
||||
import com.skcraft.launcher.auth.YggdrasilLoginService;
|
||||
import com.skcraft.launcher.persistence.Persistence;
|
||||
import com.skcraft.launcher.swing.*;
|
||||
import com.skcraft.launcher.util.SharedLocale;
|
||||
import com.skcraft.launcher.util.SwingExecutor;
|
||||
import lombok.Getter;
|
||||
@ -23,10 +25,10 @@ import lombok.NonNull;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.awt.event.WindowAdapter;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
/**
|
||||
@ -35,16 +37,12 @@ import java.util.concurrent.Callable;
|
||||
public class LoginDialog extends JDialog {
|
||||
|
||||
private final Launcher launcher;
|
||||
@Getter private final AccountList accounts;
|
||||
@Getter private Session session;
|
||||
|
||||
private final JComboBox idCombo = new JComboBox();
|
||||
private final JTextField usernameText = new JTextField();
|
||||
private final JPasswordField passwordText = new JPasswordField();
|
||||
private final JCheckBox rememberIdCheck = new JCheckBox(SharedLocale.tr("login.rememberId"));
|
||||
private final JCheckBox rememberPassCheck = new JCheckBox(SharedLocale.tr("login.rememberPassword"));
|
||||
private final JButton loginButton = new JButton(SharedLocale.tr("login.login"));
|
||||
private final LinkButton recoverButton = new LinkButton(SharedLocale.tr("login.recoverAccount"));
|
||||
private final JButton offlineButton = new JButton(SharedLocale.tr("login.playOffline"));
|
||||
private final JButton cancelButton = new JButton(SharedLocale.tr("button.cancel"));
|
||||
private final FormPanel formPanel = new FormPanel();
|
||||
private final LinedBoxPanel buttonsPanel = new LinedBoxPanel(true);
|
||||
@ -59,7 +57,6 @@ public class LoginDialog extends JDialog {
|
||||
super(owner, ModalityType.DOCUMENT_MODAL);
|
||||
|
||||
this.launcher = launcher;
|
||||
this.accounts = launcher.getAccounts();
|
||||
|
||||
setTitle(SharedLocale.tr("login.title"));
|
||||
initComponents();
|
||||
@ -73,39 +70,21 @@ public class LoginDialog extends JDialog {
|
||||
addWindowListener(new WindowAdapter() {
|
||||
@Override
|
||||
public void windowClosing(WindowEvent event) {
|
||||
removeListeners();
|
||||
dispose();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void removeListeners() {
|
||||
idCombo.setModel(new DefaultComboBoxModel());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void initComponents() {
|
||||
idCombo.setModel(getAccounts());
|
||||
updateSelection();
|
||||
|
||||
rememberIdCheck.setBorder(BorderFactory.createEmptyBorder());
|
||||
rememberPassCheck.setBorder(BorderFactory.createEmptyBorder());
|
||||
idCombo.setEditable(true);
|
||||
idCombo.getEditor().selectAll();
|
||||
usernameText.setEditable(true);
|
||||
|
||||
loginButton.setFont(loginButton.getFont().deriveFont(Font.BOLD));
|
||||
|
||||
formPanel.addRow(new JLabel(SharedLocale.tr("login.idEmail")), idCombo);
|
||||
formPanel.addRow(new JLabel(SharedLocale.tr("login.idEmail")), usernameText);
|
||||
formPanel.addRow(new JLabel(SharedLocale.tr("login.password")), passwordText);
|
||||
formPanel.addRow(new JLabel(), rememberIdCheck);
|
||||
formPanel.addRow(new JLabel(), rememberPassCheck);
|
||||
buttonsPanel.setBorder(BorderFactory.createEmptyBorder(26, 13, 13, 13));
|
||||
|
||||
if (launcher.getConfig().isOfflineEnabled()) {
|
||||
buttonsPanel.addElement(offlineButton);
|
||||
buttonsPanel.addElement(Box.createHorizontalStrut(2));
|
||||
}
|
||||
buttonsPanel.addElement(recoverButton);
|
||||
buttonsPanel.addGlue();
|
||||
buttonsPanel.addElement(loginButton);
|
||||
@ -118,162 +97,24 @@ public class LoginDialog extends JDialog {
|
||||
|
||||
passwordText.setComponentPopupMenu(TextFieldPopupMenu.INSTANCE);
|
||||
|
||||
idCombo.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
updateSelection();
|
||||
}
|
||||
});
|
||||
|
||||
idCombo.getEditor().getEditorComponent().addMouseListener(new PopupMouseAdapter() {
|
||||
@Override
|
||||
protected void showPopup(MouseEvent e) {
|
||||
popupManageMenu(e.getComponent(), e.getX(), e.getY());
|
||||
}
|
||||
});
|
||||
|
||||
recoverButton.addActionListener(
|
||||
ActionListeners.openURL(recoverButton, launcher.getProperties().getProperty("resetPasswordUrl")));
|
||||
|
||||
loginButton.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
prepareLogin();
|
||||
}
|
||||
});
|
||||
|
||||
offlineButton.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
setResult(new OfflineSession(launcher.getProperties().getProperty("offlinePlayerName")));
|
||||
removeListeners();
|
||||
dispose();
|
||||
}
|
||||
});
|
||||
|
||||
cancelButton.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
removeListeners();
|
||||
dispose();
|
||||
}
|
||||
});
|
||||
|
||||
rememberPassCheck.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
if (rememberPassCheck.isSelected()) {
|
||||
rememberIdCheck.setSelected(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
rememberIdCheck.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
if (!rememberIdCheck.isSelected()) {
|
||||
rememberPassCheck.setSelected(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void popupManageMenu(Component component, int x, int y) {
|
||||
Object selected = idCombo.getSelectedItem();
|
||||
JPopupMenu popup = new JPopupMenu();
|
||||
JMenuItem menuItem;
|
||||
|
||||
if (selected != null && selected instanceof Account) {
|
||||
final Account account = (Account) selected;
|
||||
|
||||
menuItem = new JMenuItem(SharedLocale.tr("login.forgetUser"));
|
||||
menuItem.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
accounts.remove(account);
|
||||
Persistence.commitAndForget(accounts);
|
||||
}
|
||||
});
|
||||
popup.add(menuItem);
|
||||
|
||||
if (!Strings.isNullOrEmpty(account.getPassword())) {
|
||||
menuItem = new JMenuItem(SharedLocale.tr("login.forgetPassword"));
|
||||
menuItem.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
account.setPassword(null);
|
||||
Persistence.commitAndForget(accounts);
|
||||
}
|
||||
});
|
||||
popup.add(menuItem);
|
||||
}
|
||||
}
|
||||
|
||||
menuItem = new JMenuItem(SharedLocale.tr("login.forgetAllPasswords"));
|
||||
menuItem.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
if (SwingHelper.confirmDialog(LoginDialog.this,
|
||||
SharedLocale.tr("login.confirmForgetAllPasswords"),
|
||||
SharedLocale.tr("login.forgetAllPasswordsTitle"))) {
|
||||
accounts.forgetPasswords();
|
||||
Persistence.commitAndForget(accounts);
|
||||
}
|
||||
}
|
||||
});
|
||||
popup.add(menuItem);
|
||||
|
||||
popup.show(component, x, y);
|
||||
}
|
||||
|
||||
private void updateSelection() {
|
||||
Object selected = idCombo.getSelectedItem();
|
||||
|
||||
if (selected != null && selected instanceof Account) {
|
||||
Account account = (Account) selected;
|
||||
String password = account.getPassword();
|
||||
|
||||
rememberIdCheck.setSelected(true);
|
||||
if (!Strings.isNullOrEmpty(password)) {
|
||||
rememberPassCheck.setSelected(true);
|
||||
passwordText.setText(password);
|
||||
} else {
|
||||
rememberPassCheck.setSelected(false);
|
||||
}
|
||||
} else {
|
||||
passwordText.setText("");
|
||||
rememberIdCheck.setSelected(true);
|
||||
rememberPassCheck.setSelected(false);
|
||||
}
|
||||
loginButton.addActionListener(e -> prepareLogin());
|
||||
cancelButton.addActionListener(e -> dispose());
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private void prepareLogin() {
|
||||
Object selected = idCombo.getSelectedItem();
|
||||
|
||||
if (selected != null && selected instanceof Account) {
|
||||
Account account = (Account) selected;
|
||||
if (!usernameText.getText().isEmpty()) {
|
||||
String password = passwordText.getText();
|
||||
|
||||
if (password == null || password.isEmpty()) {
|
||||
SwingHelper.showErrorDialog(this, SharedLocale.tr("login.noPasswordError"), SharedLocale.tr("login.noPasswordTitle"));
|
||||
} else {
|
||||
if (rememberPassCheck.isSelected()) {
|
||||
account.setPassword(password);
|
||||
} else {
|
||||
account.setPassword(null);
|
||||
}
|
||||
|
||||
if (rememberIdCheck.isSelected()) {
|
||||
accounts.add(account);
|
||||
} else {
|
||||
accounts.remove(account);
|
||||
}
|
||||
|
||||
Account account = new Account(usernameText.getText());
|
||||
account.setLastUsed(new Date());
|
||||
|
||||
Persistence.commitAndForget(accounts);
|
||||
|
||||
attemptLogin(account, password);
|
||||
}
|
||||
} else {
|
||||
@ -303,7 +144,6 @@ public class LoginDialog extends JDialog {
|
||||
|
||||
private void setResult(Session session) {
|
||||
this.session = session;
|
||||
removeListeners();
|
||||
dispose();
|
||||
}
|
||||
|
||||
@ -324,12 +164,12 @@ public class LoginDialog extends JDialog {
|
||||
|
||||
@Override
|
||||
public Session call() throws AuthenticationException, IOException, InterruptedException {
|
||||
LoginService service = launcher.getLoginService();
|
||||
List<? extends Session> identities = service.login(launcher.getProperties().getProperty("agentName"), account.getId(), password);
|
||||
YggdrasilLoginService service = launcher.getYggdrasil();
|
||||
Session identity = service.login(launcher.getProperties().getProperty("agentName"), account.getId(), password);
|
||||
|
||||
// The list of identities (profiles in Mojang terms) corresponds to whether the account
|
||||
// The presence of the identity (profile in Mojang terms) corresponds to whether the account
|
||||
// owns the game, so we need to check that
|
||||
if (identities.size() > 0) {
|
||||
if (identity != null) {
|
||||
// Set offline enabled flag to true
|
||||
Configuration config = launcher.getConfig();
|
||||
if (!config.isOfflineEnabled()) {
|
||||
@ -337,8 +177,7 @@ public class LoginDialog extends JDialog {
|
||||
Persistence.commitAndForget(config);
|
||||
}
|
||||
|
||||
Persistence.commitAndForget(getAccounts());
|
||||
return identities.get(0);
|
||||
return identity;
|
||||
} else {
|
||||
throw new AuthenticationException("Minecraft not owned", SharedLocale.tr("login.minecraftNotOwnedError"));
|
||||
}
|
||||
|
@ -0,0 +1,34 @@
|
||||
package com.skcraft.launcher.dialog.component;
|
||||
|
||||
import com.beust.jcommander.internal.Lists;
|
||||
|
||||
import javax.swing.event.ListDataEvent;
|
||||
import javax.swing.event.ListDataListener;
|
||||
import java.util.List;
|
||||
|
||||
public class ListListenerReducer implements ListDataListener {
|
||||
private final List<ListDataListener> listeners = Lists.newArrayList();
|
||||
|
||||
@Override
|
||||
public void intervalAdded(ListDataEvent e) {
|
||||
listeners.forEach(it -> it.intervalAdded(e));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void intervalRemoved(ListDataEvent e) {
|
||||
listeners.forEach(it -> it.intervalRemoved(e));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void contentsChanged(ListDataEvent e) {
|
||||
listeners.forEach(it -> it.contentsChanged(e));
|
||||
}
|
||||
|
||||
public void addListDataListener(ListDataListener l) {
|
||||
listeners.add(l);
|
||||
}
|
||||
|
||||
public void removeListDataListener(ListDataListener l) {
|
||||
listeners.remove(l);
|
||||
}
|
||||
}
|
@ -13,7 +13,7 @@ import com.skcraft.concurrency.ObservableFuture;
|
||||
import com.skcraft.launcher.Instance;
|
||||
import com.skcraft.launcher.Launcher;
|
||||
import com.skcraft.launcher.auth.Session;
|
||||
import com.skcraft.launcher.dialog.LoginDialog;
|
||||
import com.skcraft.launcher.dialog.AccountSelectDialog;
|
||||
import com.skcraft.launcher.dialog.ProgressDialog;
|
||||
import com.skcraft.launcher.launch.LaunchOptions.UpdatePolicy;
|
||||
import com.skcraft.launcher.persistence.Persistence;
|
||||
@ -61,7 +61,7 @@ public class LaunchSupervisor {
|
||||
if (options.getSession() != null) {
|
||||
session = options.getSession();
|
||||
} else {
|
||||
session = LoginDialog.showLoginRequest(window, launcher);
|
||||
session = AccountSelectDialog.showAccountRequest(window, launcher);
|
||||
if (session == null) {
|
||||
return;
|
||||
}
|
||||
|
@ -87,6 +87,10 @@ launcher.notInstalledHint=(not installed)
|
||||
launcher.requiresUpdateHint=(requires update)
|
||||
launcher.updatePendingHint=(pending update)
|
||||
|
||||
accounts.title=Select the account to play with
|
||||
accounts.refreshingStatus=Refreshing login session...
|
||||
accounts.addNew=Add a new account...
|
||||
|
||||
login.rememberId=Remember my account in the list
|
||||
login.rememberPassword=Remember my password
|
||||
login.login=Login...
|
||||
|
Loading…
Reference in New Issue
Block a user