✨ Shared auth scheme
This commit is contained in:
@@ -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() };
|
||||
}
|
||||
}
|
||||
}
|
@@ -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>
|
||||
|
96
DysonNetwork.Pass/Permission/PermissionServiceGrpc.cs
Normal file
96
DysonNetwork.Pass/Permission/PermissionServiceGrpc.cs
Normal 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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@@ -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();
|
||||
|
Reference in New Issue
Block a user