2929import com .fasterxml .jackson .core .type .TypeReference ;
3030import com .fasterxml .jackson .databind .DeserializationFeature ;
3131import com .fasterxml .jackson .databind .ObjectMapper ;
32+ import com .google .gson .Gson ;
3233import io .netty .channel .epoll .Epoll ;
3334import io .netty .util .NettyRuntime ;
3435import io .netty .util .concurrent .DefaultThreadFactory ;
3839import lombok .Setter ;
3940import net .kyori .adventure .text .Component ;
4041import net .kyori .adventure .text .format .NamedTextColor ;
42+ import net .raphimc .minecraftauth .step .java .session .StepFullJavaSession ;
43+ import net .raphimc .minecraftauth .step .msa .StepMsaToken ;
4144import org .checkerframework .checker .nullness .qual .MonotonicNonNull ;
4245import org .checkerframework .checker .nullness .qual .NonNull ;
4346import org .checkerframework .checker .nullness .qual .Nullable ;
9396import org .geysermc .geyser .util .CooldownUtils ;
9497import org .geysermc .geyser .util .DimensionUtils ;
9598import org .geysermc .geyser .util .Metrics ;
99+ import org .geysermc .geyser .util .MinecraftAuthLogger ;
96100import org .geysermc .geyser .util .NewsHandler ;
97101import org .geysermc .geyser .util .VersionCheckUtils ;
98102import org .geysermc .geyser .util .WebUtils ;
@@ -179,7 +183,7 @@ public class GeyserImpl implements GeyserApi {
179183
180184 private PendingMicrosoftAuthentication pendingMicrosoftAuthentication ;
181185 @ Getter (AccessLevel .NONE )
182- private Map <String , String > savedRefreshTokens ;
186+ private Map <String , String > savedAuthChains ;
183187
184188 @ Getter
185189 private static GeyserImpl instance ;
@@ -552,37 +556,84 @@ private void startInstance() {
552556
553557 if (config .getRemote ().authType () == AuthType .ONLINE ) {
554558 // May be written/read to on multiple threads from each GeyserSession as well as writing the config
555- savedRefreshTokens = new ConcurrentHashMap <>();
559+ savedAuthChains = new ConcurrentHashMap <>();
556560
557- File tokensFile = bootstrap .getSavedUserLoginsFolder ().resolve (Constants .SAVED_REFRESH_TOKEN_FILE ).toFile ();
558- if (tokensFile .exists ()) {
561+ // TODO Remove after a while - just a migration help
562+ //noinspection deprecation
563+ File refreshTokensFile = bootstrap .getSavedUserLoginsFolder ().resolve (Constants .SAVED_REFRESH_TOKEN_FILE ).toFile ();
564+ if (refreshTokensFile .exists ()) {
565+ logger .info ("Migrating refresh tokens to auth chains..." );
566+ TypeReference <Map <String , String >> type = new TypeReference <>() { };
567+ Map <String , String > refreshTokens = null ;
568+ try {
569+ refreshTokens = JSON_MAPPER .readValue (refreshTokensFile , type );
570+ } catch (IOException e ) {
571+ // ignored - we'll just delete this file :))
572+ }
573+
574+ if (refreshTokens != null ) {
575+ List <String > validUsers = config .getSavedUserLogins ();
576+ final Gson gson = new Gson ();
577+ for (Map .Entry <String , String > entry : refreshTokens .entrySet ()) {
578+ String user = entry .getKey ();
579+ if (!validUsers .contains (user )) {
580+ continue ;
581+ }
582+
583+ // Migrate refresh tokens to auth chains
584+ try {
585+ StepFullJavaSession javaSession = PendingMicrosoftAuthentication .AUTH_FLOW .apply (false , 10 );
586+ StepFullJavaSession .FullJavaSession fullJavaSession = javaSession .getFromInput (
587+ MinecraftAuthLogger .INSTANCE ,
588+ PendingMicrosoftAuthentication .AUTH_CLIENT ,
589+ new StepMsaToken .RefreshToken (entry .getValue ())
590+ );
591+
592+ String authChain = gson .toJson (javaSession .toJson (fullJavaSession ));
593+ savedAuthChains .put (user , authChain );
594+ } catch (Exception e ) {
595+ GeyserImpl .getInstance ().getLogger ().warning ("Could not migrate " + entry .getKey () + " to an auth chain! " +
596+ "They will need to sign in the next time they join Geyser." );
597+ }
598+
599+ // Ensure the new additions are written to the file
600+ scheduleAuthChainsWrite ();
601+ }
602+ }
603+
604+ // Finally: Delete it. Goodbye!
605+ refreshTokensFile .delete ();
606+ }
607+
608+ File authChainsFile = bootstrap .getSavedUserLoginsFolder ().resolve (Constants .SAVED_AUTH_CHAINS_FILE ).toFile ();
609+ if (authChainsFile .exists ()) {
559610 TypeReference <Map <String , String >> type = new TypeReference <>() { };
560611
561- Map <String , String > refreshTokenFile = null ;
612+ Map <String , String > authChainFile = null ;
562613 try {
563- refreshTokenFile = JSON_MAPPER .readValue (tokensFile , type );
614+ authChainFile = JSON_MAPPER .readValue (authChainsFile , type );
564615 } catch (IOException e ) {
565616 logger .error ("Cannot load saved user tokens!" , e );
566617 }
567- if (refreshTokenFile != null ) {
618+ if (authChainFile != null ) {
568619 List <String > validUsers = config .getSavedUserLogins ();
569620 boolean doWrite = false ;
570- for (Map .Entry <String , String > entry : refreshTokenFile .entrySet ()) {
621+ for (Map .Entry <String , String > entry : authChainFile .entrySet ()) {
571622 String user = entry .getKey ();
572623 if (!validUsers .contains (user )) {
573624 // Perform a write to this file to purge the now-unused name
574625 doWrite = true ;
575626 continue ;
576627 }
577- savedRefreshTokens .put (user , entry .getValue ());
628+ savedAuthChains .put (user , entry .getValue ());
578629 }
579630 if (doWrite ) {
580- scheduleRefreshTokensWrite ();
631+ scheduleAuthChainsWrite ();
581632 }
582633 }
583634 }
584635 } else {
585- savedRefreshTokens = null ;
636+ savedAuthChains = null ;
586637 }
587638
588639 newsHandler .handleNews (null , NewsItemAction .ON_SERVER_STARTED );
@@ -829,20 +880,20 @@ public WorldManager getWorldManager() {
829880 }
830881
831882 @ Nullable
832- public String refreshTokenFor (@ NonNull String bedrockName ) {
833- return savedRefreshTokens .get (bedrockName );
883+ public String authChainFor (@ NonNull String bedrockName ) {
884+ return savedAuthChains .get (bedrockName );
834885 }
835886
836- public void saveRefreshToken (@ NonNull String bedrockName , @ NonNull String refreshToken ) {
887+ public void saveAuthChain (@ NonNull String bedrockName , @ NonNull String authChain ) {
837888 if (!getConfig ().getSavedUserLogins ().contains (bedrockName )) {
838889 // Do not save this login
839890 return ;
840891 }
841892
842893 // We can safely overwrite old instances because MsaAuthenticationService#getLoginResponseFromRefreshToken
843894 // refreshes the token for us
844- if (!Objects .equals (refreshToken , savedRefreshTokens .put (bedrockName , refreshToken ))) {
845- scheduleRefreshTokensWrite ();
895+ if (!Objects .equals (authChain , savedAuthChains .put (bedrockName , authChain ))) {
896+ scheduleAuthChainsWrite ();
846897 }
847898 }
848899
@@ -852,15 +903,15 @@ private <T> void runIfNonNull(T nullable, Consumer<T> consumer) {
852903 }
853904 }
854905
855- private void scheduleRefreshTokensWrite () {
906+ private void scheduleAuthChainsWrite () {
856907 scheduledThread .execute (() -> {
857908 // Ensure all writes are handled on the same thread
858- File savedTokens = getBootstrap ().getSavedUserLoginsFolder ().resolve (Constants .SAVED_REFRESH_TOKEN_FILE ).toFile ();
909+ File savedAuthChains = getBootstrap ().getSavedUserLoginsFolder ().resolve (Constants .SAVED_AUTH_CHAINS_FILE ).toFile ();
859910 TypeReference <Map <String , String >> type = new TypeReference <>() { };
860- try (FileWriter writer = new FileWriter (savedTokens )) {
911+ try (FileWriter writer = new FileWriter (savedAuthChains )) {
861912 JSON_MAPPER .writerFor (type )
862913 .withDefaultPrettyPrinter ()
863- .writeValue (writer , savedRefreshTokens );
914+ .writeValue (writer , this . savedAuthChains );
864915 } catch (IOException e ) {
865916 getLogger ().error ("Unable to write saved refresh tokens!" , e );
866917 }
0 commit comments