diff --git a/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/Realm/IRealmController.cs b/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/Realm/IRealmController.cs index 05fef659e39..f2caa725b49 100644 --- a/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/Realm/IRealmController.cs +++ b/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/Realm/IRealmController.cs @@ -15,8 +15,6 @@ public interface IRealmController UniTask IsReachableAsync(URLDomain realm, CancellationToken ct); - UniTask IsUserAuthorisedToAccessWorldAsync(URLDomain realm, CancellationToken ct); - /// /// Dispose everything on application quit /// diff --git a/Explorer/Assets/DCL/Infrastructure/Global/Dynamic/Bootstraper.cs b/Explorer/Assets/DCL/Infrastructure/Global/Dynamic/Bootstraper.cs index 4930963f971..eb853440bde 100644 --- a/Explorer/Assets/DCL/Infrastructure/Global/Dynamic/Bootstraper.cs +++ b/Explorer/Assets/DCL/Infrastructure/Global/Dynamic/Bootstraper.cs @@ -2,7 +2,6 @@ using CommunicationData.URLHelpers; using Cysharp.Threading.Tasks; using DCL.Audio; -using DCL.Chat.History; using DCL.DebugUtilities; using DCL.Diagnostics; using DCL.FeatureFlags; @@ -287,27 +286,6 @@ public async UniTask LoadStartingRealmAsync(DynamicWorldContainer dynamicWorldCo if (startingRealm.HasValue == false) throw new InvalidOperationException("Starting realm is not set"); - if (realmLaunchSettings.initialRealm is InitialRealm.World) - { - bool isAuthorized = await dynamicWorldContainer.RealmController - .IsUserAuthorisedToAccessWorldAsync(startingRealm.Value, ct); - - if (!isAuthorized) - { - ReportHub.LogWarning(ReportCategory.REALM, - $"[Bootstrap] Startup world '{realmLaunchSettings.TargetWorld}' is not authorized for auto-entry, falling back to Genesis."); - - dynamicWorldContainer.ChatHistory.AddMessage( - ChatChannel.NEARBY_CHANNEL_ID, - ChatChannel.ChatChannelType.NEARBY, - ChatMessage.NewFromSystem($"Could not auto-enter '{realmLaunchSettings.TargetWorld}' due to world permissions. You were sent to Genesis Plaza.")); - - await dynamicWorldContainer.RealmController - .SetRealmAsync(URLDomain.FromString(realmUrls.GenesisRealm()), ct); - return; - } - } - await dynamicWorldContainer.RealmController.SetRealmAsync(startingRealm.Value, ct); } diff --git a/Explorer/Assets/DCL/Infrastructure/Global/Dynamic/DynamicWorldContainer.cs b/Explorer/Assets/DCL/Infrastructure/Global/Dynamic/DynamicWorldContainer.cs index feaa2c4368f..2648056ea44 100644 --- a/Explorer/Assets/DCL/Infrastructure/Global/Dynamic/DynamicWorldContainer.cs +++ b/Explorer/Assets/DCL/Infrastructure/Global/Dynamic/DynamicWorldContainer.cs @@ -381,7 +381,6 @@ static IMultiPool MultiPoolFactory() => var realmContainer = RealmContainer.Create( staticContainer, - identityCache, dynamicWorldParams.StaticLoadPositions, debugBuilder, loadingScreenTimeout, @@ -390,8 +389,7 @@ static IMultiPool MultiPoolFactory() => bootstrapContainer.DecentralandUrlsSource, appArgs, teleportController, - bootstrapContainer.Environment, - worldPermissionsService); + bootstrapContainer.Environment); var terrainContainer = TerrainContainer.Create(staticContainer, realmContainer, dynamicWorldParams.EnableLandscape, localSceneDevelopment); @@ -514,7 +512,9 @@ static IMultiPool MultiPoolFactory() => roomHub, localSceneDevelopment, staticContainer.CharacterContainer, - moderationDataProvider); + moderationDataProvider, + worldPermissionsService, + chatHistory); IRealmNavigator realmNavigator = realmNavigatorContainer.RealmNavigator; HomePlaceEventBus homePlaceEventBus = new HomePlaceEventBus(); diff --git a/Explorer/Assets/DCL/Infrastructure/Global/Dynamic/InitializationFlowContainer.cs b/Explorer/Assets/DCL/Infrastructure/Global/Dynamic/InitializationFlowContainer.cs index de7fd5391af..3dca568f9ed 100644 --- a/Explorer/Assets/DCL/Infrastructure/Global/Dynamic/InitializationFlowContainer.cs +++ b/Explorer/Assets/DCL/Infrastructure/Global/Dynamic/InitializationFlowContainer.cs @@ -1,9 +1,11 @@ using DCL.ApplicationBlocklistGuard; using DCL.Audio; using DCL.Character.Plugin; +using DCL.Chat.History; using DCL.Diagnostics; using DCL.Multiplayer.Connections.RoomHubs; using DCL.Multiplayer.HealthChecks; +using DCL.PrivateWorlds; using DCL.Profiles.Self; using DCL.RealmNavigation; using DCL.RealmNavigation.LoadingOperation; @@ -37,7 +39,9 @@ public static InitializationFlowContainer Create( IRoomHub roomHub, bool localSceneDevelopment, CharacterContainer characterContainer, - ModerationDataProvider moderationDataProvider) + ModerationDataProvider moderationDataProvider, + IWorldPermissionsService worldPermissionsService, + IChatHistory chatHistory) { ILoadingStatus? loadingStatus = staticContainer.LoadingStatus; @@ -100,7 +104,9 @@ public static InitializationFlowContainer Create( characterContainer.CharacterObject, characterContainer.Transform, dynamicWorldParams.StartParcel, - localSceneDevelopment), + localSceneDevelopment, + worldPermissionsService, + chatHistory), }; } } diff --git a/Explorer/Assets/DCL/Infrastructure/Global/Dynamic/RealmController.cs b/Explorer/Assets/DCL/Infrastructure/Global/Dynamic/RealmController.cs index 6cf6c3c5f17..4baaaaca25c 100644 --- a/Explorer/Assets/DCL/Infrastructure/Global/Dynamic/RealmController.cs +++ b/Explorer/Assets/DCL/Infrastructure/Global/Dynamic/RealmController.cs @@ -8,7 +8,6 @@ using DCL.Ipfs; using DCL.Multiplayer.Connections.DecentralandUrls; using DCL.Optimization.Pools; -using DCL.PluginSystem.Global; using DCL.Utilities; using DCL.Utilities.Extensions; using DCL.Web3.Identities; @@ -54,11 +53,9 @@ public class RealmController : IGlobalRealmController private readonly List allScenes = new (PoolConstants.SCENES_COUNT); private readonly ServerAbout serverAbout = new (); - private readonly IWeb3IdentityCache web3IdentityCache; private readonly IWebRequestController webRequestController; private readonly IReadOnlyList staticLoadPositions; private readonly RealmData realmData; - private readonly IWorldPermissionsService worldPermissionsService; private readonly RetrieveSceneFromFixedRealm retrieveSceneFromFixedRealm; private readonly RetrieveSceneFromVolatileWorld retrieveSceneFromVolatileWorld; private readonly TeleportController teleportController; @@ -92,7 +89,6 @@ public GlobalWorld GlobalWorld } public RealmController( - IWeb3IdentityCache web3IdentityCache, IWebRequestController webRequestController, TeleportController teleportController, RetrieveSceneFromFixedRealm retrieveSceneFromFixedRealm, @@ -107,14 +103,11 @@ public RealmController( IAppArgs appArgs, IDecentralandUrlsSource decentralandUrlsSource, DecentralandEnvironment environment, - WorldManifestProvider worldManifestProvider, - IWorldPermissionsService worldPermissionsService) + WorldManifestProvider worldManifestProvider) { - this.web3IdentityCache = web3IdentityCache; this.webRequestController = webRequestController; this.staticLoadPositions = staticLoadPositions; this.realmData = realmData; - this.worldPermissionsService = worldPermissionsService; this.teleportController = teleportController; this.retrieveSceneFromFixedRealm = retrieveSceneFromFixedRealm; this.retrieveSceneFromVolatileWorld = retrieveSceneFromVolatileWorld; @@ -203,31 +196,6 @@ public async UniTask SetRealmAsync(URLDomain realm, CancellationToken ct) public async UniTask IsReachableAsync(URLDomain realm, CancellationToken ct) => await webRequestController.IsHeadReachableAsync(ReportCategory.REALM, realm.Append(new URLPath("/about")), ct); - public async UniTask IsUserAuthorisedToAccessWorldAsync(URLDomain realm, CancellationToken ct) - { - if (!TryExtractWorldName(realm, out string worldName)) - { - ReportHub.LogWarning(ReportCategory.REALM, - $"[RealmController] Failed to extract world name from realm '{realm}'."); - return false; - } - - WorldAccessCheckContext context; - try - { - context = await worldPermissionsService.CheckWorldAccessAsync(worldName, ct); - } - catch (OperationCanceledException) { throw; } - catch (Exception e) - { - ReportHub.LogWarning(ReportCategory.REALM, - $"[RealmController] Failed to verify world access for '{worldName}' via world permissions: {e.Message}"); - return false; - } - - return context.Result == WorldAccessCheckResult.Allowed; - } - private static bool TryExtractWorldName(URLDomain realm, out string worldName) { worldName = string.Empty; diff --git a/Explorer/Assets/DCL/RealmNavigation/Container/RealmContainer.cs b/Explorer/Assets/DCL/RealmNavigation/Container/RealmContainer.cs index b7d9ee98786..e6576d69be5 100644 --- a/Explorer/Assets/DCL/RealmNavigation/Container/RealmContainer.cs +++ b/Explorer/Assets/DCL/RealmNavigation/Container/RealmContainer.cs @@ -32,7 +32,6 @@ public class RealmContainer public static RealmContainer Create( StaticContainer staticContainer, - IWeb3IdentityCache identityCache, IReadOnlyList staticLoadPositions, IDebugContainerBuilder debugContainerBuilder, LoadingScreenTimeout loadingScreenTimeout, @@ -41,8 +40,7 @@ public static RealmContainer Create( IDecentralandUrlsSource urlsSource, IAppArgs appArgs, TeleportController teleportController, - DecentralandEnvironment dclEnvironment, - IWorldPermissionsService worldPermissionsService) + DecentralandEnvironment dclEnvironment) { var retrieveSceneFromFixedRealm = new RetrieveSceneFromFixedRealm(); var retrieveSceneFromVolatileWorld = new RetrieveSceneFromVolatileWorld(staticContainer.RealmData, urlsSource); @@ -50,7 +48,6 @@ public static RealmContainer Create( var realmNavigatorDebugView = new RealmNavigatorDebugView(debugContainerBuilder); var realmController = new RealmController( - identityCache, staticContainer.WebRequestsContainer.WebRequestController, teleportController, retrieveSceneFromFixedRealm, @@ -66,8 +63,7 @@ public static RealmContainer Create( appArgs, urlsSource, dclEnvironment, - staticContainer.WorldManifestProvider, - worldPermissionsService + staticContainer.WorldManifestProvider ); BuildDebugWidget(teleportController, debugContainerBuilder, loadingScreen, loadingScreenTimeout); diff --git a/Explorer/Assets/DCL/UserInAppInitializationFlow/DCL.UserInAppInitializationFlow.asmdef b/Explorer/Assets/DCL/UserInAppInitializationFlow/DCL.UserInAppInitializationFlow.asmdef index 29164920e93..306cab37b2e 100644 --- a/Explorer/Assets/DCL/UserInAppInitializationFlow/DCL.UserInAppInitializationFlow.asmdef +++ b/Explorer/Assets/DCL/UserInAppInitializationFlow/DCL.UserInAppInitializationFlow.asmdef @@ -32,7 +32,8 @@ "GUID:c80c82a8f4e04453b85fbab973d6774a", "GUID:54d33bbd50a28174e8ba0110106203c2", "GUID:875a5d5129614170bd769ed012c2eb3d", - "GUID:8614faad1014549c88b57654b0fb41bd" + "GUID:8614faad1014549c88b57654b0fb41bd", + "GUID:f8127c6ac263abf468221dcbccbde182" ], "includePlatforms": [], "excludePlatforms": [], diff --git a/Explorer/Assets/DCL/UserInAppInitializationFlow/RealUserInAppInitializationFlow.cs b/Explorer/Assets/DCL/UserInAppInitializationFlow/RealUserInAppInitializationFlow.cs index 4007361cadf..f3fa04c4db5 100644 --- a/Explorer/Assets/DCL/UserInAppInitializationFlow/RealUserInAppInitializationFlow.cs +++ b/Explorer/Assets/DCL/UserInAppInitializationFlow/RealUserInAppInitializationFlow.cs @@ -6,10 +6,12 @@ using DCL.Audio; using DCL.AuthenticationScreenFlow; using DCL.Character; +using DCL.Chat.History; using DCL.Diagnostics; using DCL.Multiplayer.Connections.DecentralandUrls; using DCL.Multiplayer.Connections.RoomHubs; using DCL.Prefs; +using DCL.PrivateWorlds; using DCL.RealmNavigation; using DCL.RealmNavigation.LoadingOperation; using DCL.SceneLoadingScreens.LoadingScreen; @@ -17,12 +19,14 @@ using DCL.Utilities; using DCL.Utility.Types; using DCL.Web3.Identities; +using ECS; using ECS.SceneLifeCycle.Realm; using Global.AppArgs; using MVC; using PortableExperiences.Controller; using UnityEngine; using Utility; +using ChatMessage = DCL.Chat.History.ChatMessage; namespace DCL.UserInAppInitializationFlow { @@ -50,6 +54,8 @@ public class RealUserInAppInitializationFlow : IUserInAppInitializationFlow private readonly ExposedTransform characterExposedTransform; private readonly StartParcel startParcel; private readonly bool isLocalSceneDevelopment; + private readonly IWorldPermissionsService worldPermissionsService; + private readonly IChatHistory chatHistory; public RealUserInAppInitializationFlow( ILoadingStatus loadingStatus, @@ -69,7 +75,9 @@ public RealUserInAppInitializationFlow( ICharacterObject characterObject, ExposedTransform characterExposedTransform, StartParcel startParcel, - bool isLocalSceneDevelopment) + bool isLocalSceneDevelopment, + IWorldPermissionsService worldPermissionsService, + IChatHistory chatHistory) { this.initOps = initOps; this.reloginOps = reloginOps; @@ -80,6 +88,8 @@ public RealUserInAppInitializationFlow( this.startParcel = startParcel; this.isLocalSceneDevelopment = isLocalSceneDevelopment; this.characterExposedTransform = characterExposedTransform; + this.worldPermissionsService = worldPermissionsService; + this.chatHistory = chatHistory; this.loadingStatus = loadingStatus; this.decentralandUrlsSource = decentralandUrlsSource; @@ -161,6 +171,11 @@ await UniTask.WhenAll( .ShowWhileExecuteTaskAsync( async (parentLoadReport, ct) => { + // After authentication completes, verify the user can actually access the current realm if it's a world. + // The realm was set during bootstrap before the user had a chance to switch accounts, so the identity + // that's now authenticated may differ from the one assumed at startup. + await VerifyWorldAccessAndFallbackIfNeededAsync(ct); + //Set initial position and start async livekit connection characterExposedTransform.Position.Value = characterObject.Controller.transform.position @@ -221,6 +236,82 @@ await UniTask.WhenAll( while (result.Success == false && parameters.ShowAuthentication); } + private async UniTask VerifyWorldAccessAndFallbackIfNeededAsync(CancellationToken ct) + { + if (isLocalSceneDevelopment) return; + if (!realmController.RealmData.IsWorld()) return; + if (realmController.CurrentDomain == null) return; + + if (!TryExtractWorldName(realmController.CurrentDomain.Value, out string worldName)) + { + ReportHub.LogWarning(ReportCategory.REALM, + $"[RealmController] Failed to extract world name from realm '{realmController.CurrentDomain.Value.ToString()}'."); + await GenesisFallbackAsync(); + return; + } + + WorldAccessCheckContext context; + + try + { + context = await worldPermissionsService.CheckWorldAccessAsync(worldName, ct); + } + catch (OperationCanceledException) { throw; } + catch (Exception e) + { + ReportHub.LogWarning(ReportCategory.REALM, + $"[StartUp] Failed to verify world access for '{worldName}' via world permissions: {e.Message}"); + await GenesisFallbackAsync(); + return; + } + + switch (context.Result) + { + case WorldAccessCheckResult.Allowed: + return; + case WorldAccessCheckResult.CheckFailed: + case WorldAccessCheckResult.AccessDenied: + case WorldAccessCheckResult.PasswordRequired: + ReportHub.LogWarning(ReportCategory.REALM, + $"[StartUp] World '{worldName}' is not authorized for auto-entry, falling back to Genesis."); + await GenesisFallbackAsync(); + return; + default: throw new ArgumentOutOfRangeException(); + } + + async UniTask GenesisFallbackAsync() + { + chatHistory.AddMessage( + ChatChannel.NEARBY_CHANNEL_ID, + ChatChannel.ChatChannelType.NEARBY, + ChatMessage.NewFromSystem($"Could not enter '{worldName}' due to world permissions. You were sent to Genesis Plaza.")); + + await realmController.SetRealmAsync( + URLDomain.FromString(decentralandUrlsSource.Url(DecentralandUrl.Genesis)), ct); + } + } + + private static bool TryExtractWorldName(URLDomain realm, out string worldName) + { + worldName = string.Empty; + + if (!Uri.TryCreate(realm.Value, UriKind.Absolute, out Uri? uri)) + return false; + + string path = uri.AbsolutePath.Trim('/'); + + if (string.IsNullOrEmpty(path)) + return false; + + string[] segments = path.Split('/', StringSplitOptions.RemoveEmptyEntries); + + if (segments.Length == 0) + return false; + + worldName = segments[^1]; + return !string.IsNullOrEmpty(worldName); + } + // TODO should be an operation private async UniTask DoLogoutOperationsAsync() {