♻️ Refactored grpc part of shared project

This commit is contained in:
2025-07-06 22:58:34 +08:00
parent 65450e8511
commit 4b220e7ed7
5 changed files with 111 additions and 28 deletions

View File

@ -0,0 +1,95 @@
using DysonNetwork.Shared.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.Extensions.Configuration;
using NodaTime;
namespace DysonNetwork.Pass;
public abstract class ModelBase
{
public Instant CreatedAt { get; set; }
public Instant UpdatedAt { get; set; }
public Instant? DeletedAt { get; set; }
}
public class AppDatabase(
DbContextOptions<AppDatabase> options,
IConfiguration configuration
) : DbContext(options)
{
public DbSet<PermissionNode> PermissionNodes { get; set; }
public DbSet<PermissionGroup> PermissionGroups { get; set; }
public DbSet<PermissionGroupMember> PermissionGroupMembers { get; set; }
public DbSet<Account> Accounts { get; set; }
public DbSet<AccountConnection> AccountConnections { get; set; }
public DbSet<Profile> AccountProfiles { get; set; }
public DbSet<AccountContact> AccountContacts { get; set; }
public DbSet<AccountAuthFactor> AccountAuthFactors { get; set; }
public DbSet<Relationship> AccountRelationships { get; set; }
public DbSet<Session> AuthSessions { get; set; }
public DbSet<Challenge> AuthChallenges { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseNpgsql(
configuration.GetConnectionString("App"),
opt => opt
.UseNodaTime()
).UseSnakeCaseNamingConvention();
base.OnConfiguring(optionsBuilder);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<PermissionGroupMember>()
.HasKey(pg => new { pg.GroupId, pg.Actor });
modelBuilder.Entity<Relationship>()
.HasKey(r => new { FromAccountId = r.AccountId, ToAccountId = r.RelatedId });
}
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
var now = SystemClock.Instance.GetCurrentInstant();
foreach (var entry in ChangeTracker.Entries<ModelBase>())
{
if (entry.State == EntityState.Added)
{
entry.Entity.CreatedAt = now;
entry.Entity.UpdatedAt = now;
}
else if (entry.State == EntityState.Modified)
{
entry.Entity.UpdatedAt = now;
}
else if (entry.State == EntityState.Deleted)
{
entry.State = EntityState.Modified;
entry.Entity.DeletedAt = now;
}
}
return await base.SaveChangesAsync(cancellationToken);
}
}
public class AppDatabaseFactory : IDesignTimeDbContextFactory<AppDatabase>
{
public AppDatabase CreateDbContext(string[] args)
{
var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.Build();
var optionsBuilder = new DbContextOptionsBuilder<AppDatabase>();
return new AppDatabase(optionsBuilder.Options, configuration);
}
}

View File

@ -5,7 +5,7 @@ package dyson_network.sphere.account;
import "google/protobuf/empty.proto"; import "google/protobuf/empty.proto";
import "google/protobuf/timestamp.proto"; import "google/protobuf/timestamp.proto";
option csharp_namespace = "DysonNetwork.Sphere.Account.Proto"; option csharp_namespace = "DysonNetwork.Shared.Protos.Account";
service AccountService { service AccountService {
rpc GetAccount(google.protobuf.Empty) returns (AccountResponse); rpc GetAccount(google.protobuf.Empty) returns (AccountResponse);

View File

@ -5,7 +5,7 @@ package dyson_network.sphere.auth;
import "google/protobuf/empty.proto"; import "google/protobuf/empty.proto";
import "google/protobuf/timestamp.proto"; import "google/protobuf/timestamp.proto";
option csharp_namespace = "DysonNetwork.Sphere.Auth.Proto"; option csharp_namespace = "DysonNetwork.Shared.Protos.Auth";
service AuthService { service AuthService {
rpc Login(LoginRequest) returns (LoginResponse); rpc Login(LoginRequest) returns (LoginResponse);

View File

@ -6,33 +6,21 @@ using Microsoft.EntityFrameworkCore;
using NodaTime; using NodaTime;
using System.Text.Json; using System.Text.Json;
using DysonNetwork.Shared.Models; using DysonNetwork.Shared.Models;
using Challenge = DysonNetwork.Sphere.Auth.Proto.Challenge;
using Session = DysonNetwork.Sphere.Auth.Proto.Session;
namespace DysonNetwork.Sphere.Auth; namespace DysonNetwork.Sphere.Auth;
public class AuthGrpcService : DysonNetwork.Sphere.Auth.Proto.AuthService.AuthServiceBase public class AuthGrpcService(AppDatabase db, AccountService accounts, AuthService auth)
: DysonNetwork.Sphere.Auth.Proto.AuthService.AuthServiceBase
{ {
private readonly AppDatabase _db;
private readonly AccountService _accounts;
private readonly AuthService _auth;
public AuthGrpcService(AppDatabase db, AccountService accounts, AuthService auth)
{
_db = db;
_accounts = accounts;
_auth = auth;
}
public override async Task<LoginResponse> Login(LoginRequest request, ServerCallContext context) public override async Task<LoginResponse> Login(LoginRequest request, ServerCallContext context)
{ {
var account = await _accounts.LookupAccount(request.Username); var account = await accounts.LookupAccount(request.Username);
if (account == null) if (account == null)
{ {
throw new RpcException(new Grpc.Core.Status(StatusCode.NotFound, "Account not found.")); throw new RpcException(new Grpc.Core.Status(StatusCode.NotFound, "Account not found."));
} }
var factor = await _db.AccountAuthFactors.FirstOrDefaultAsync(f => f.AccountId == account.Id && f.Type == AccountAuthFactorType.Password); var factor = await db.AccountAuthFactors.FirstOrDefaultAsync(f => f.AccountId == account.Id && f.Type == AccountAuthFactorType.Password);
if (factor == null || !factor.VerifyPassword(request.Password)) if (factor == null || !factor.VerifyPassword(request.Password))
{ {
throw new RpcException(new Grpc.Core.Status(StatusCode.Unauthenticated, "Invalid credentials.")); throw new RpcException(new Grpc.Core.Status(StatusCode.Unauthenticated, "Invalid credentials."));
@ -46,10 +34,10 @@ public class AuthGrpcService : DysonNetwork.Sphere.Auth.Proto.AuthService.AuthSe
Challenge = new Challenge() // Create a dummy challenge Challenge = new Challenge() // Create a dummy challenge
}; };
_db.AuthSessions.Add(session); db.AuthSessions.Add(session);
await _db.SaveChangesAsync(); await db.SaveChangesAsync();
var token = _auth.CreateToken(session); var token = auth.CreateToken(session);
return new LoginResponse return new LoginResponse
{ {
@ -60,9 +48,9 @@ public class AuthGrpcService : DysonNetwork.Sphere.Auth.Proto.AuthService.AuthSe
public override async Task<IntrospectionResponse> IntrospectToken(IntrospectTokenRequest request, ServerCallContext context) public override async Task<IntrospectionResponse> IntrospectToken(IntrospectTokenRequest request, ServerCallContext context)
{ {
if (_auth.ValidateToken(request.Token, out var sessionId)) if (auth.ValidateToken(request.Token, out var sessionId))
{ {
var session = await _db.AuthSessions var session = await db.AuthSessions
.Include(s => s.Account) .Include(s => s.Account)
.Include(s => s.Challenge) .Include(s => s.Challenge)
.FirstOrDefaultAsync(s => s.Id == sessionId); .FirstOrDefaultAsync(s => s.Id == sessionId);
@ -91,13 +79,13 @@ public class AuthGrpcService : DysonNetwork.Sphere.Auth.Proto.AuthService.AuthSe
if (authorizationHeader != null) if (authorizationHeader != null)
{ {
var token = authorizationHeader.Value.Replace("Bearer ", ""); var token = authorizationHeader.Value.Replace("Bearer ", "");
if (_auth.ValidateToken(token, out var sessionId)) if (auth.ValidateToken(token, out var sessionId))
{ {
var session = await _db.AuthSessions.FindAsync(sessionId); var session = await db.AuthSessions.FindAsync(sessionId);
if (session != null) if (session != null)
{ {
_db.AuthSessions.Remove(session); db.AuthSessions.Remove(session);
await _db.SaveChangesAsync(); await db.SaveChangesAsync();
} }
} }
} }