Shared auth scheme

This commit is contained in:
2025-07-13 18:36:51 +08:00
parent e66abe2e0c
commit afdbde951c
34 changed files with 2704 additions and 179 deletions

View File

@@ -1,27 +1,49 @@
using DysonNetwork.Shared.Cache;
using DysonNetwork.Shared.Proto;
using Grpc.Core;
using Microsoft.EntityFrameworkCore;
using NodaTime;
namespace DysonNetwork.Pass.Auth;
public class AuthServiceGrpc(AuthService authService, AppDatabase db) : Shared.Proto.AuthService.AuthServiceBase
public class AuthServiceGrpc(
AuthService authService,
ICacheService cache,
AppDatabase db
)
: Shared.Proto.AuthService.AuthServiceBase
{
public override async Task<Shared.Proto.AuthSession> Authenticate(AuthenticateRequest request, ServerCallContext context)
public override async Task<AuthenticateResponse> Authenticate(
AuthenticateRequest request,
ServerCallContext context
)
{
if (!authService.ValidateToken(request.Token, out var sessionId))
{
throw new RpcException(new Status(StatusCode.Unauthenticated, "Invalid token."));
}
return new AuthenticateResponse { Valid = false, Message = "Invalid token." };
var session = await cache.GetAsync<AuthSession>($"{DysonTokenAuthHandler.AuthCachePrefix}{sessionId}");
if (session is not null)
return new AuthenticateResponse { Valid = true, Session = session.ToProtoValue() };
var session = await db.AuthSessions
session = await db.AuthSessions
.AsNoTracking()
.Include(e => e.Challenge)
.Include(e => e.Account)
.ThenInclude(e => e.Profile)
.FirstOrDefaultAsync(s => s.Id == sessionId);
if (session == null)
{
throw new RpcException(new Status(StatusCode.NotFound, "Session not found."));
}
return new AuthenticateResponse { Valid = false, Message = "Session was not found." };
var now = SystemClock.Instance.GetCurrentInstant();
if (session.ExpiredAt.HasValue && session.ExpiredAt < now)
return new AuthenticateResponse { Valid = false, Message = "Session has been expired." };
await cache.SetWithGroupsAsync(
$"auth:{sessionId}",
session,
[$"{Account.AccountService.AccountCachePrefix}{session.Account.Id}"],
TimeSpan.FromHours(1)
);
return session.ToProtoValue();
return new AuthenticateResponse { Valid = true, Session = session.ToProtoValue() };
}
}
}

View File

@@ -8,33 +8,33 @@
<ItemGroup>
<PackageReference Include="Grpc.AspNetCore.Server" Version="2.71.0" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.7"/>
<PackageReference Include="NodaTime" Version="3.2.2"/>
<PackageReference Include="NodaTime.Serialization.JsonNet" Version="3.2.0"/>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.7" />
<PackageReference Include="NodaTime" Version="3.2.2" />
<PackageReference Include="NodaTime.Serialization.JsonNet" Version="3.2.0" />
<PackageReference Include="NodaTime.Serialization.Protobuf" Version="2.0.2" />
<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"/>
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.12.0"/>
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.12.0"/>
<PackageReference Include="Otp.NET" Version="1.4.0"/>
<PackageReference Include="prometheus-net.AspNetCore" Version="8.2.1"/>
<PackageReference Include="prometheus-net.AspNetCore.HealthChecks" Version="8.2.1"/>
<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="BCrypt.Net-Next" Version="4.0.3"/>
<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="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" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.12.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.12.0" />
<PackageReference Include="Otp.NET" Version="1.4.0" />
<PackageReference Include="prometheus-net.AspNetCore" Version="8.2.1" />
<PackageReference Include="prometheus-net.AspNetCore.HealthChecks" Version="8.2.1" />
<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="BCrypt.Net-Next" Version="4.0.3" />
<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="Swashbuckle.AspNetCore" Version="9.0.3" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="9.0.3" />
</ItemGroup>

View File

@@ -0,0 +1,96 @@
using Grpc.Core;
using Microsoft.EntityFrameworkCore;
using DysonNetwork.Shared.Proto;
using Google.Protobuf.WellKnownTypes;
using System.Text.Json;
using NodaTime.Serialization.Protobuf;
namespace DysonNetwork.Pass.Permission;
public class PermissionServiceGrpc(
PermissionService permissionService,
AppDatabase db
) : DysonNetwork.Shared.Proto.PermissionService.PermissionServiceBase
{
public override async Task<HasPermissionResponse> HasPermission(HasPermissionRequest request, ServerCallContext context)
{
var hasPermission = await permissionService.HasPermissionAsync(request.Actor, request.Area, request.Key);
return new HasPermissionResponse { HasPermission = hasPermission };
}
public override async Task<GetPermissionResponse> GetPermission(GetPermissionRequest request, ServerCallContext context)
{
var permissionValue = await permissionService.GetPermissionAsync<JsonDocument>(request.Actor, request.Area, request.Key);
return new GetPermissionResponse { Value = permissionValue != null ? Value.Parser.ParseJson(permissionValue.RootElement.GetRawText()) : null };
}
public override async Task<AddPermissionNodeResponse> AddPermissionNode(AddPermissionNodeRequest request, ServerCallContext context)
{
var node = await permissionService.AddPermissionNode(
request.Actor,
request.Area,
request.Key,
JsonDocument.Parse(request.Value.ToString()), // Convert Value to JsonDocument
request.ExpiredAt?.ToInstant(),
request.AffectedAt?.ToInstant()
);
return new AddPermissionNodeResponse { Node = node.ToProtoValue() };
}
public override async Task<AddPermissionNodeToGroupResponse> AddPermissionNodeToGroup(AddPermissionNodeToGroupRequest request, ServerCallContext context)
{
var group = await db.PermissionGroups.FirstOrDefaultAsync(g => g.Id == Guid.Parse(request.Group.Id));
if (group == null)
{
throw new RpcException(new Status(StatusCode.NotFound, "Permission group not found."));
}
var node = await permissionService.AddPermissionNodeToGroup(
group,
request.Actor,
request.Area,
request.Key,
JsonDocument.Parse(request.Value.ToString()), // Convert Value to JsonDocument
request.ExpiredAt?.ToInstant(),
request.AffectedAt?.ToInstant()
);
return new AddPermissionNodeToGroupResponse { Node = node.ToProtoValue() };
}
public override async Task<RemovePermissionNodeResponse> RemovePermissionNode(RemovePermissionNodeRequest request, ServerCallContext context)
{
await permissionService.RemovePermissionNode(request.Actor, request.Area, request.Key);
return new RemovePermissionNodeResponse { Success = true };
}
public override async Task<RemovePermissionNodeFromGroupResponse> RemovePermissionNodeFromGroup(RemovePermissionNodeFromGroupRequest request, ServerCallContext context)
{
var group = await db.PermissionGroups.FirstOrDefaultAsync(g => g.Id == Guid.Parse(request.Group.Id));
if (group == null)
{
throw new RpcException(new Status(StatusCode.NotFound, "Permission group not found."));
}
await permissionService.RemovePermissionNodeFromGroup<JsonDocument>(group, request.Actor, request.Area, request.Key);
return new RemovePermissionNodeFromGroupResponse { Success = true };
}
}
public static class PermissionExtensions
{
public static DysonNetwork.Shared.Proto.PermissionNode ToProtoValue(this PermissionNode node)
{
return new DysonNetwork.Shared.Proto.PermissionNode
{
Id = node.Id.ToString(),
Actor = node.Actor,
Area = node.Area,
Key = node.Key,
Value = Value.Parser.ParseJson(node.Value.RootElement.GetRawText()),
ExpiredAt = node.ExpiredAt?.ToTimestamp(),
AffectedAt = node.AffectedAt?.ToTimestamp(),
GroupId = node.GroupId?.ToString() ?? string.Empty
};
}
}

View File

@@ -13,7 +13,7 @@ builder.ConfigureAppKestrel();
builder.Services.AddAppMetrics();
// Add application services
builder.Services.AddEtcdService(builder.Configuration);
builder.Services.AddRegistryService(builder.Configuration);
builder.Services.AddAppServices(builder.Configuration);
builder.Services.AddAppRateLimiting();
builder.Services.AddAppAuthentication();