♻️ Extract the Storage service to DysonNetwork.Drive microservice

This commit is contained in:
2025-07-06 17:29:26 +08:00
parent 6a3d04af3d
commit 14b79f16f4
71 changed files with 2629 additions and 346 deletions

View File

@ -218,7 +218,7 @@ public class AuthController(
if (session is not null)
return BadRequest("Session already exists for this challenge.");
session = new Session
var session = new AuthSession
{
LastGrantedAt = Instant.FromDateTimeUtc(DateTime.UtcNow),
ExpiredAt = Instant.FromDateTimeUtc(DateTime.UtcNow.AddDays(30)),

View File

@ -3,11 +3,14 @@ using System.Security.Cryptography;
using System.Text.Encodings.Web;
using DysonNetwork.Pass.Features.Account;
using DysonNetwork.Pass.Features.Auth.OidcProvider.Services;
using DysonNetwork.Pass.Storage;
using DysonNetwork.Pass.Storage.Handlers;
using DysonNetwork.Common.Services;
using DysonNetwork.Drive.Handlers;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Options;
using SystemClock = NodaTime.SystemClock;
using NodaTime;
using DysonNetwork.Pass.Data;
using DysonNetwork.Common.Models;
using DysonNetwork.Drive;
namespace DysonNetwork.Pass.Features.Auth.Services;
@ -57,14 +60,14 @@ public class DysonTokenAuthHandler(
try
{
var now = SystemClock.Instance.GetCurrentInstant();
var now = NodaTime.SystemClock.Instance.GetCurrentInstant();
// Validate token and extract session ID
if (!ValidateToken(tokenInfo.Token, out var sessionId))
return AuthenticateResult.Fail("Invalid token.");
// Try to get session from cache first
var session = await cache.GetAsync<Session>($"{AuthCachePrefix}{sessionId}");
var session = await cache.GetAsync<AuthSession>($"{AuthCachePrefix}{sessionId}");
// If not in cache, load from database
if (session is null)
@ -126,7 +129,7 @@ public class DysonTokenAuthHandler(
{
Account = session.Account,
Session = session,
SeenAt = SystemClock.Instance.GetCurrentInstant(),
SeenAt = NodaTime.SystemClock.Instance.GetCurrentInstant(),
};
fbs.Enqueue(lastInfo);

View File

@ -5,6 +5,7 @@ using DysonNetwork.Pass.Storage;
using Microsoft.EntityFrameworkCore;
using NodaTime;
using DysonNetwork.Pass.Data;
using DysonNetwork.Common.Models;
namespace DysonNetwork.Pass.Features.Auth;

View File

@ -1,4 +1,5 @@
using System.Security.Cryptography;
using DysonNetwork.Common.Models;
namespace DysonNetwork.Pass.Features.Auth;
@ -7,7 +8,7 @@ public class CompactTokenService(IConfiguration config)
private readonly string _privateKeyPath = config["AuthToken:PrivateKeyPath"]
?? throw new InvalidOperationException("AuthToken:PrivateKeyPath configuration is missing");
public string CreateToken(Session session)
public string CreateToken(AuthSession session)
{
// Load the private key for signing
var privateKeyPem = File.ReadAllText(_privateKeyPath);

View File

@ -5,6 +5,8 @@ using System.Text.Json;
using System.Text.Json.Serialization;
using DysonNetwork.Common.Services;
using Microsoft.IdentityModel.Tokens;
using DysonNetwork.Pass.Data;
using DysonNetwork.Sphere;
namespace DysonNetwork.Pass.Features.Auth.OpenId;
@ -14,11 +16,12 @@ namespace DysonNetwork.Pass.Features.Auth.OpenId;
public class AppleOidcService(
IConfiguration configuration,
IHttpClientFactory httpClientFactory,
PassDatabase db,
PassDatabase passDb,
AppDatabase sphereDb,
AuthService auth,
ICacheService cache
)
: OidcService(configuration, httpClientFactory, db, auth, cache)
: OidcService(configuration, httpClientFactory, passDb, sphereDb, auth, cache)
{
private readonly IConfiguration _configuration = configuration;
private readonly IHttpClientFactory _httpClientFactory = httpClientFactory;

View File

@ -1,17 +1,20 @@
using System.Net.Http.Json;
using System.Text.Json;
using DysonNetwork.Pass.Storage;
using DysonNetwork.Common.Services;
using DysonNetwork.Pass.Data;
using DysonNetwork.Sphere;
namespace DysonNetwork.Pass.Features.Auth.OpenId;
public class DiscordOidcService(
IConfiguration configuration,
IHttpClientFactory httpClientFactory,
PassDatabase db,
PassDatabase passDb,
AppDatabase sphereDb,
AuthService auth,
ICacheService cache
)
: OidcService(configuration, httpClientFactory, db, auth, cache)
: OidcService(configuration, httpClientFactory, passDb, sphereDb, auth, cache)
{
public override string ProviderName => "Discord";
protected override string DiscoveryEndpoint => ""; // Discord doesn't have a standard OIDC discovery endpoint

View File

@ -1,17 +1,20 @@
using System.Net.Http.Json;
using System.Text.Json;
using DysonNetwork.Pass.Storage;
using DysonNetwork.Common.Services;
using DysonNetwork.Pass.Data;
using DysonNetwork.Sphere;
namespace DysonNetwork.Pass.Features.Auth.OpenId;
public class GitHubOidcService(
IConfiguration configuration,
IHttpClientFactory httpClientFactory,
PassDatabase db,
PassDatabase passDb,
AppDatabase sphereDb,
AuthService auth,
ICacheService cache
)
: OidcService(configuration, httpClientFactory, db, auth, cache)
: OidcService(configuration, httpClientFactory, passDb, sphereDb, auth, cache)
{
public override string ProviderName => "GitHub";
protected override string DiscoveryEndpoint => ""; // GitHub doesn't have a standard OIDC discovery endpoint
@ -77,7 +80,7 @@ public class GitHubOidcService(
var client = HttpClientFactory.CreateClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://api.github.com/user");
request.Headers.Add("Authorization", $"Bearer {accessToken}");
request.Headers.Add("User-Agent", "DysonNetwork.Sphere");
request.Headers.Add("User-Agent", "DysonNetwork.Drive");
var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
@ -109,7 +112,7 @@ public class GitHubOidcService(
var client = HttpClientFactory.CreateClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://api.github.com/user/emails");
request.Headers.Add("Authorization", $"Bearer {accessToken}");
request.Headers.Add("User-Agent", "DysonNetwork.Sphere");
request.Headers.Add("User-Agent", "DysonNetwork.Drive");
var response = await client.SendAsync(request);
if (!response.IsSuccessStatusCode) return null;

View File

@ -4,17 +4,20 @@ using System.Security.Cryptography;
using System.Text;
using DysonNetwork.Common.Services;
using Microsoft.IdentityModel.Tokens;
using DysonNetwork.Pass.Data;
using DysonNetwork.Sphere;
namespace DysonNetwork.Pass.Features.Auth.OpenId;
public class GoogleOidcService(
IConfiguration configuration,
IHttpClientFactory httpClientFactory,
PassDatabase db,
PassDatabase passDb,
AppDatabase sphereDb,
AuthService auth,
ICacheService cache
)
: OidcService(configuration, httpClientFactory, db, auth, cache)
: OidcService(configuration, httpClientFactory, passDb, sphereDb, auth, cache)
{
private readonly IHttpClientFactory _httpClientFactory = httpClientFactory;

View File

@ -1,17 +1,20 @@
using System.Net.Http.Json;
using System.Text.Json;
using DysonNetwork.Pass.Storage;
using DysonNetwork.Common.Services;
using DysonNetwork.Pass.Data;
using DysonNetwork.Sphere;
namespace DysonNetwork.Pass.Features.Auth.OpenId;
public class MicrosoftOidcService(
IConfiguration configuration,
IHttpClientFactory httpClientFactory,
PassDatabase db,
PassDatabase passDb,
AppDatabase sphereDb,
AuthService auth,
ICacheService cache
)
: OidcService(configuration, httpClientFactory, db, auth, cache)
: OidcService(configuration, httpClientFactory, passDb, sphereDb, auth, cache)
{
public override string ProviderName => "Microsoft";

View File

@ -1,8 +1,10 @@
using DysonNetwork.Common.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using NodaTime;
using DysonNetwork.Common.Models;
using DysonNetwork.Pass.Data;
using DysonNetwork.Sphere;
namespace DysonNetwork.Pass.Features.Auth.OpenId;
@ -10,7 +12,8 @@ namespace DysonNetwork.Pass.Features.Auth.OpenId;
[Route("/auth/login")]
public class OidcController(
IServiceProvider serviceProvider,
PassDatabase db,
PassDatabase passDb,
AppDatabase sphereDb,
AccountService accounts,
ICacheService cache
)
@ -31,7 +34,7 @@ public class OidcController(
var oidcService = GetOidcService(provider);
// If the user is already authenticated, treat as an account connection request
if (HttpContext.Items["CurrentUser"] is Models.Account currentUser)
if (HttpContext.Items["CurrentUser"] is Account currentUser)
{
var state = Guid.NewGuid().ToString();
var nonce = Guid.NewGuid().ToString();
@ -67,7 +70,7 @@ public class OidcController(
/// Handles Apple authentication directly from mobile apps
/// </summary>
[HttpPost("apple/mobile")]
public async Task<ActionResult<Challenge>> AppleMobileLogin(
public async Task<ActionResult<AuthChallenge>> AppleMobileSignIn(
[FromBody] AppleMobileSignInRequest request)
{
try
@ -124,7 +127,7 @@ public class OidcController(
};
}
private async Task<Models.Account> FindOrCreateAccount(OidcUserInfo userInfo, string provider)
private async Task<Account> FindOrCreateAccount(OidcUserInfo userInfo, string provider)
{
if (string.IsNullOrEmpty(userInfo.Email))
throw new ArgumentException("Email is required for account creation");
@ -134,15 +137,16 @@ public class OidcController(
if (existingAccount != null)
{
// Check if this provider connection already exists
var existingConnection = await db.AccountConnections
.FirstOrDefaultAsync(c => c.AccountId == existingAccount.Id &&
c.Provider == provider &&
c.ProvidedIdentifier == userInfo.UserId);
var existingConnection = await passDb.AccountConnections
.FirstOrDefaultAsync(c => c.Provider == provider &&
c.ProvidedIdentifier == userInfo.UserId &&
c.AccountId == existingAccount.Id
);
// If no connection exists, create one
if (existingConnection != null)
{
await db.AccountConnections
await passDb.AccountConnections
.Where(c => c.AccountId == existingAccount.Id &&
c.Provider == provider &&
c.ProvidedIdentifier == userInfo.UserId)
@ -164,8 +168,8 @@ public class OidcController(
Meta = userInfo.ToMetadata()
};
await db.AccountConnections.AddAsync(connection);
await db.SaveChangesAsync();
await passDb.AccountConnections.AddAsync(connection);
await passDb.SaveChangesAsync();
return existingAccount;
}
@ -185,8 +189,8 @@ public class OidcController(
Meta = userInfo.ToMetadata()
};
db.AccountConnections.Add(newConnection);
await db.SaveChangesAsync();
await passDb.AccountConnections.Add(newConnection);
await passDb.SaveChangesAsync();
return newAccount;
}

View File

@ -5,6 +5,8 @@ using DysonNetwork.Common.Services;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using NodaTime;
using DysonNetwork.Common.Models;
using DysonNetwork.Pass.Data;
namespace DysonNetwork.Pass.Features.Auth.OpenId;
@ -21,7 +23,7 @@ public abstract class OidcService(
{
protected readonly IConfiguration Configuration = configuration;
protected readonly IHttpClientFactory HttpClientFactory = httpClientFactory;
protected readonly AppDatabase Db = db;
protected readonly PassDatabase Db = db;
/// <summary>
/// Gets the unique identifier for this provider