From 1a5d0bbfc0d9b15239b55ca4d831c6c73fa148fd Mon Sep 17 00:00:00 2001
From: LittleSheep <littlesheep.code@hotmail.com>
Date: Tue, 17 Jun 2025 00:40:05 +0800
Subject: [PATCH] :bug: Fix bugs

---
 DysonNetwork.Sphere/.gitignore                |  1 +
 .../Auth/OpenId/ConnectionController.cs       | 52 ++++++++++----
 .../Auth/OpenId/OidcController.cs             |  2 +-
 .../DysonNetwork.Sphere.csproj                | 71 ++++++++++---------
 DysonNetwork.Sphere/Program.cs                | 30 ++++++++
 5 files changed, 108 insertions(+), 48 deletions(-)

diff --git a/DysonNetwork.Sphere/.gitignore b/DysonNetwork.Sphere/.gitignore
index dea484d..eccb44f 100644
--- a/DysonNetwork.Sphere/.gitignore
+++ b/DysonNetwork.Sphere/.gitignore
@@ -1,5 +1,6 @@
 Keys
 Uploads
+DataProtection-Keys
 
 node_modules
 bun.lock
diff --git a/DysonNetwork.Sphere/Auth/OpenId/ConnectionController.cs b/DysonNetwork.Sphere/Auth/OpenId/ConnectionController.cs
index ba93e44..ecaad6f 100644
--- a/DysonNetwork.Sphere/Auth/OpenId/ConnectionController.cs
+++ b/DysonNetwork.Sphere/Auth/OpenId/ConnectionController.cs
@@ -192,32 +192,51 @@ public class ConnectionController(
         }
         catch (Exception ex)
         {
-            return BadRequest($"Error processing callback: {ex.Message}");
+            return BadRequest($"Error processing {provider} authentication: {ex.Message}");
         }
 
+        if (string.IsNullOrEmpty(userInfo.UserId))
+        {
+            return BadRequest($"{provider} did not return a valid user identifier.");
+        }
+
+        // Check if this provider account is already connected to any user
         var existingConnection = await db.AccountConnections
             .FirstOrDefaultAsync(c =>
                 c.Provider.Equals(provider, StringComparison.OrdinalIgnoreCase) &&
                 c.ProvidedIdentifier == userInfo.UserId);
 
+        // If it's connected to a different user, return error
         if (existingConnection != null && existingConnection.AccountId != accountId)
         {
             return BadRequest($"This {provider} account is already linked to another user.");
         }
 
-        var userConnection = await db.AccountConnections
-            .FirstOrDefaultAsync(c =>
-                c.AccountId == accountId && c.Provider.Equals(provider, StringComparison.OrdinalIgnoreCase));
+        // Check if the current user already has this provider connected
+        var userHasProvider = await db.AccountConnections
+            .AnyAsync(c => 
+                c.AccountId == accountId && 
+                c.Provider.Equals(provider, StringComparison.OrdinalIgnoreCase));
 
-        var clock = SystemClock.Instance;
-        if (userConnection != null)
+        if (userHasProvider)
         {
-            userConnection.AccessToken = userInfo.AccessToken;
-            userConnection.RefreshToken = userInfo.RefreshToken;
-            userConnection.LastUsedAt = clock.GetCurrentInstant();
+            // Update existing connection with new tokens
+            var connection = await db.AccountConnections
+                .FirstOrDefaultAsync(c =>
+                    c.AccountId == accountId && 
+                    c.Provider.Equals(provider, StringComparison.OrdinalIgnoreCase));
+
+            if (connection != null)
+            {
+                connection.AccessToken = userInfo.AccessToken;
+                connection.RefreshToken = userInfo.RefreshToken;
+                connection.LastUsedAt = SystemClock.Instance.GetCurrentInstant();
+                connection.Meta = userInfo.ToMetadata();
+            }
         }
         else
         {
+            // Create new connection
             db.AccountConnections.Add(new AccountConnection
             {
                 AccountId = accountId,
@@ -225,17 +244,26 @@ public class ConnectionController(
                 ProvidedIdentifier = userInfo.UserId!,
                 AccessToken = userInfo.AccessToken,
                 RefreshToken = userInfo.RefreshToken,
-                LastUsedAt = clock.GetCurrentInstant(),
+                LastUsedAt = SystemClock.Instance.GetCurrentInstant(),
                 Meta = userInfo.ToMetadata(),
             });
         }
 
-        await db.SaveChangesAsync();
+        try
+        {
+            await db.SaveChangesAsync();
+        }
+        catch (DbUpdateException ex)
+        {
+            return StatusCode(500, $"Failed to save {provider} connection. Please try again.");
+        }
 
+        // Clean up and redirect
         var returnUrl = HttpContext.Session.GetString($"oidc_return_url_{callbackData.State}");
         HttpContext.Session.Remove($"oidc_return_url_{callbackData.State}");
+        HttpContext.Session.Remove($"oidc_state_{callbackData.State}");
 
-        return Redirect(string.IsNullOrEmpty(returnUrl) ? "/" : returnUrl);
+        return Redirect(string.IsNullOrEmpty(returnUrl) ? "/settings/connections" : returnUrl);
     }
 
     private async Task<IActionResult> HandleLoginOrRegistration(
diff --git a/DysonNetwork.Sphere/Auth/OpenId/OidcController.cs b/DysonNetwork.Sphere/Auth/OpenId/OidcController.cs
index 4e42927..ada8bc1 100644
--- a/DysonNetwork.Sphere/Auth/OpenId/OidcController.cs
+++ b/DysonNetwork.Sphere/Auth/OpenId/OidcController.cs
@@ -23,7 +23,7 @@ public class OidcController(
         {
             var oidcService = GetOidcService(provider);
 
-            // If user is already authenticated, treat as an account connection request
+            // If the user is already authenticated, treat as an account connection request
             if (HttpContext.Items["CurrentUser"] is Account.Account currentUser)
             {
                 var state = Guid.NewGuid().ToString();
diff --git a/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj b/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj
index 084f9d2..0b42478 100644
--- a/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj
+++ b/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj
@@ -16,38 +16,39 @@
     </PropertyGroup>
 
     <ItemGroup>
-        <PackageReference Include="BCrypt.Net-Next" Version="4.0.3"/>
-        <PackageReference Include="BlurHashSharp.SkiaSharp" Version="1.3.4"/>
-        <PackageReference Include="EFCore.BulkExtensions" Version="9.0.1"/>
-        <PackageReference Include="EFCore.BulkExtensions.PostgreSql" Version="9.0.1"/>
-        <PackageReference Include="EFCore.NamingConventions" Version="9.0.0"/>
-        <PackageReference Include="FFMpegCore" Version="5.2.0"/>
+        <PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
+        <PackageReference Include="BlurHashSharp.SkiaSharp" Version="1.3.4" />
+        <PackageReference Include="EFCore.BulkExtensions" Version="9.0.1" />
+        <PackageReference Include="EFCore.BulkExtensions.PostgreSql" Version="9.0.1" />
+        <PackageReference Include="EFCore.NamingConventions" Version="9.0.0" />
+        <PackageReference Include="FFMpegCore" Version="5.2.0" />
         <PackageReference Include="Livekit.Server.Sdk.Dotnet" Version="1.0.8" />
-        <PackageReference Include="MailKit" Version="4.11.0"/>
-        <PackageReference Include="MaxMind.GeoIP2" Version="5.3.0"/>
-        <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.4"/>
-        <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.2"/>
+        <PackageReference Include="MailKit" Version="4.11.0" />
+        <PackageReference Include="MaxMind.GeoIP2" Version="5.3.0" />
+        <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.4" />
+        <PackageReference Include="Microsoft.AspNetCore.DataProtection.Extensions" Version="8.0.0" />
+        <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.2" />
         <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.3">
             <PrivateAssets>all</PrivateAssets>
             <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
         </PackageReference>
-        <PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.4"/>
+        <PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.4" />
         <PackageReference Include="MimeTypes" Version="2.5.2">
             <PrivateAssets>all</PrivateAssets>
             <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
         </PackageReference>
-        <PackageReference Include="Minio" Version="6.0.4"/>
-        <PackageReference Include="NetVips" Version="3.0.1"/>
-        <PackageReference Include="NetVips.Native.linux-x64" Version="8.16.1"/>
-        <PackageReference Include="NetVips.Native.osx-arm64" Version="8.16.1"/>
+        <PackageReference Include="Minio" Version="6.0.4" />
+        <PackageReference Include="NetVips" Version="3.0.1" />
+        <PackageReference Include="NetVips.Native.linux-x64" Version="8.16.1" />
+        <PackageReference Include="NetVips.Native.osx-arm64" Version="8.16.1" />
         <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
-        <PackageReference Include="NodaTime" Version="3.2.2"/>
+        <PackageReference Include="NodaTime" Version="3.2.2" />
         <PackageReference Include="NodaTime.Serialization.JsonNet" Version="3.2.0" />
-        <PackageReference Include="NodaTime.Serialization.SystemTextJson" Version="1.3.0"/>
-        <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4"/>
-        <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.Design" Version="1.1.0"/>
-        <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.NetTopologySuite" Version="9.0.4"/>
-        <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime" Version="9.0.4"/>
+        <PackageReference Include="NodaTime.Serialization.SystemTextJson" Version="1.3.0" />
+        <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
+        <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.Design" Version="1.1.0" />
+        <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.NetTopologySuite" Version="9.0.4" />
+        <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime" Version="9.0.4" />
         <PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.12.0" />
         <PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.12.0" />
         <PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.12.0" />
@@ -59,18 +60,18 @@
         <PackageReference Include="prometheus-net.DotNetRuntime" Version="4.4.1" />
         <PackageReference Include="prometheus-net.EntityFramework" Version="0.9.5" />
         <PackageReference Include="prometheus-net.SystemMetrics" Version="3.1.0" />
-        <PackageReference Include="Quartz" Version="3.14.0"/>
-        <PackageReference Include="Quartz.AspNetCore" Version="3.14.0"/>
-        <PackageReference Include="Quartz.Extensions.Hosting" Version="3.14.0"/>
+        <PackageReference Include="Quartz" Version="3.14.0" />
+        <PackageReference Include="Quartz.AspNetCore" Version="3.14.0" />
+        <PackageReference Include="Quartz.Extensions.Hosting" Version="3.14.0" />
         <PackageReference Include="SkiaSharp" Version="2.88.9" />
         <PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="2.88.9" />
         <PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="2.88.9" />
         <PackageReference Include="SkiaSharp.NativeAssets.macOS" Version="2.88.9" />
         <PackageReference Include="StackExchange.Redis" Version="2.8.37" />
         <PackageReference Include="StackExchange.Redis.Extensions.AspNetCore" Version="11.0.0" />
-        <PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.0"/>
-        <PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="8.1.0"/>
-        <PackageReference Include="tusdotnet" Version="2.8.1"/>
+        <PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.0" />
+        <PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="8.1.0" />
+        <PackageReference Include="tusdotnet" Version="2.8.1" />
     </ItemGroup>
 
     <ItemGroup>
@@ -80,7 +81,7 @@
     </ItemGroup>
 
     <ItemGroup>
-        <Folder Include="Migrations\"/>
+        <Folder Include="Migrations\" />
     </ItemGroup>
 
     <ItemGroup>
@@ -151,13 +152,13 @@
     </ItemGroup>
 
     <ItemGroup>
-        <_ContentIncludedByDefault Remove="app\publish\appsettings.json"/>
-        <_ContentIncludedByDefault Remove="app\publish\DysonNetwork.Sphere.deps.json"/>
-        <_ContentIncludedByDefault Remove="app\publish\DysonNetwork.Sphere.runtimeconfig.json"/>
-        <_ContentIncludedByDefault Remove="app\publish\DysonNetwork.Sphere.staticwebassets.endpoints.json"/>
-        <_ContentIncludedByDefault Remove="app\publish\Keys\Solian.json"/>
-        <_ContentIncludedByDefault Remove="app\publish\package-lock.json"/>
-        <_ContentIncludedByDefault Remove="app\publish\package.json"/>
+        <_ContentIncludedByDefault Remove="app\publish\appsettings.json" />
+        <_ContentIncludedByDefault Remove="app\publish\DysonNetwork.Sphere.deps.json" />
+        <_ContentIncludedByDefault Remove="app\publish\DysonNetwork.Sphere.runtimeconfig.json" />
+        <_ContentIncludedByDefault Remove="app\publish\DysonNetwork.Sphere.staticwebassets.endpoints.json" />
+        <_ContentIncludedByDefault Remove="app\publish\Keys\Solian.json" />
+        <_ContentIncludedByDefault Remove="app\publish\package-lock.json" />
+        <_ContentIncludedByDefault Remove="app\publish\package.json" />
     </ItemGroup>
 
 </Project>
diff --git a/DysonNetwork.Sphere/Program.cs b/DysonNetwork.Sphere/Program.cs
index e1515d7..4b4272d 100644
--- a/DysonNetwork.Sphere/Program.cs
+++ b/DysonNetwork.Sphere/Program.cs
@@ -35,6 +35,7 @@ using Quartz;
 using StackExchange.Redis;
 using tusdotnet;
 using tusdotnet.Stores;
+using Microsoft.AspNetCore.DataProtection;
 
 var builder = WebApplication.CreateBuilder(args);
 
@@ -90,12 +91,41 @@ builder.Services.AddSingleton<ICacheService, CacheServiceRedis>();
 
 builder.Services.AddHttpClient();
 
+// Configure Data Protection for persistent session keys
+var keysDirectory = Path.Combine(builder.Environment.ContentRootPath, "DataProtection-Keys");
+Directory.CreateDirectory(keysDirectory);
+
+builder.Services.AddDataProtection()
+    .PersistKeysToFileSystem(new DirectoryInfo(keysDirectory))
+    .SetApplicationName("DysonNetwork.Sphere");
+
+// Configure cookie policy to be essential for session
+builder.Services.Configure<CookiePolicyOptions>(options =>
+{
+    options.CheckConsentNeeded = _ => false; // Required for session to work without consent
+    options.MinimumSameSitePolicy = SameSiteMode.Lax;
+});
+
+// Add session with consistent cookie settings
+builder.Services.AddSession(options =>
+{
+    options.Cookie.Name = "_dynses";
+    options.Cookie.HttpOnly = true;
+    options.Cookie.IsEssential = true;
+    options.IdleTimeout = TimeSpan.FromMinutes(30);
+});
+
 // Register OIDC services
 builder.Services.AddScoped<OidcService, GoogleOidcService>();
 builder.Services.AddScoped<OidcService, AppleOidcService>();
 builder.Services.AddScoped<OidcService, GitHubOidcService>();
 builder.Services.AddScoped<OidcService, MicrosoftOidcService>();
 builder.Services.AddScoped<OidcService, DiscordOidcService>();
+builder.Services.AddScoped<GoogleOidcService>();
+builder.Services.AddScoped<AppleOidcService>();
+builder.Services.AddScoped<GitHubOidcService>();
+builder.Services.AddScoped<MicrosoftOidcService>();
+builder.Services.AddScoped<DiscordOidcService>();
 builder.Services.AddControllers().AddJsonOptions(options =>
 {
     options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower;