♻️ Still don't know what I am doing

This commit is contained in:
2025-07-13 23:38:57 +08:00
parent 03e26ef93c
commit cde55eb237
23 changed files with 300 additions and 170 deletions

View File

@ -16,10 +16,9 @@ public class DysonTokenAuthHandler(
IOptionsMonitor<DysonTokenAuthOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock,
AuthService.AuthServiceClient auth
)
: AuthenticationHandler<DysonTokenAuthOptions>(options, logger, encoder, clock)
: AuthenticationHandler<DysonTokenAuthOptions>(options, logger, encoder)
{
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{

View File

@ -0,0 +1,72 @@
using DysonNetwork.Shared.Proto;
using Grpc.Core;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
namespace DysonNetwork.Shared.Auth
{
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class RequiredPermissionAttribute(string area, string key) : Attribute
{
public string Area { get; set; } = area;
public string Key { get; } = key;
}
public class PermissionMiddleware(RequestDelegate next)
{
public async Task InvokeAsync(HttpContext httpContext, PermissionService.PermissionServiceClient permissionService, ILogger<PermissionMiddleware> logger)
{
var endpoint = httpContext.GetEndpoint();
var attr = endpoint?.Metadata
.OfType<RequiredPermissionAttribute>()
.FirstOrDefault();
if (attr != null)
{
if (httpContext.Items["CurrentUser"] is not Account currentUser)
{
httpContext.Response.StatusCode = StatusCodes.Status403Forbidden;
await httpContext.Response.WriteAsync("Unauthorized");
return;
}
// Assuming Account proto has a bool field 'is_superuser' which is generated as 'IsSuperuser'
if (currentUser.IsSuperuser)
{
// Bypass the permission check for performance
await next(httpContext);
return;
}
var actor = $"user:{currentUser.Id}";
try
{
var permResp = await permissionService.HasPermissionAsync(new HasPermissionRequest
{
Actor = actor,
Area = attr.Area,
Key = attr.Key
});
if (!permResp.HasPermission)
{
httpContext.Response.StatusCode = StatusCodes.Status403Forbidden;
await httpContext.Response.WriteAsync($"Permission {attr.Area}/{attr.Key} was required.");
return;
}
}
catch (RpcException ex)
{
logger.LogError(ex, "gRPC call to PermissionService failed while checking permission {Area}/{Key} for actor {Actor}", attr.Area, attr.Key, actor);
httpContext.Response.StatusCode = StatusCodes.Status500InternalServerError;
await httpContext.Response.WriteAsync("Error checking permissions.");
return;
}
}
await next(httpContext);
}
}
}

View File

@ -16,9 +16,9 @@ public static class DysonAuthStartup
{
var etcdClient = sp.GetRequiredService<IEtcdClient>();
var config = sp.GetRequiredService<IConfiguration>();
var clientCertPath = config["ClientCert:Path"];
var clientKeyPath = config["ClientKey:Path"];
var clientCertPassword = config["ClientCert:Password"];
var clientCertPath = config["Service:ClientCert"];
var clientKeyPath = config["Service:ClientKey"];
var clientCertPassword = config["Service:CertPassword"];
return GrpcClientHelper
.CreateAuthServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword)

View File

@ -19,8 +19,7 @@
</PackageReference>
<PackageReference Include="MaxMind.GeoIP2" Version="5.3.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication" Version="2.3.0" />
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.3.0" />
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.3.0" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.7" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.7" />
<PackageReference Include="NetTopologySuite" Version="2.6.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />

View File

@ -33,17 +33,6 @@ public static class GrpcClientHelper
return response.Kvs[0].Value.ToStringUtf8();
}
public static AccountService.AccountServiceClient CreateAccountServiceClient(
string url,
string clientCertPath,
string clientKeyPath,
string? clientCertPassword = null
)
{
return new AccountService.AccountServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath,
clientCertPassword));
}
public static async Task<AccountService.AccountServiceClient> CreateAccountServiceClient(
IEtcdClient etcdClient,
string clientCertPath,
@ -51,22 +40,11 @@ public static class GrpcClientHelper
string? clientCertPassword = null
)
{
var url = await GetServiceUrlFromEtcd(etcdClient, "AccountService");
var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Pass");
return new AccountService.AccountServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath,
clientCertPassword));
}
public static AuthService.AuthServiceClient CreateAuthServiceClient(
string url,
string clientCertPath,
string clientKeyPath,
string? clientCertPassword = null
)
{
return new AuthService.AuthServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath,
clientCertPassword));
}
public static async Task<AuthService.AuthServiceClient> CreateAuthServiceClient(
IEtcdClient etcdClient,
string clientCertPath,
@ -74,22 +52,11 @@ public static class GrpcClientHelper
string? clientCertPassword = null
)
{
var url = await GetServiceUrlFromEtcd(etcdClient, "AuthService");
var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Pass");
return new AuthService.AuthServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath,
clientCertPassword));
}
public static PusherService.PusherServiceClient CreatePusherServiceClient(
string url,
string clientCertPath,
string clientKeyPath,
string? clientCertPassword = null
)
{
return new PusherService.PusherServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath,
clientCertPassword));
}
public static async Task<PusherService.PusherServiceClient> CreatePusherServiceClient(
IEtcdClient etcdClient,
string clientCertPath,
@ -97,7 +64,7 @@ public static class GrpcClientHelper
string? clientCertPassword = null
)
{
var url = await GetServiceUrlFromEtcd(etcdClient, "PusherService");
var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Pusher");
return new PusherService.PusherServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath,
clientCertPassword));
}

View File

@ -195,6 +195,7 @@ message LevelingInfo {
service AccountService {
// Account Operations
rpc GetAccount(GetAccountRequest) returns (Account) {}
rpc GetAccountBatch(GetAccountBatchRequest) returns (GetAccountBatchResponse) {}
rpc CreateAccount(CreateAccountRequest) returns (Account) {}
rpc UpdateAccount(UpdateAccountRequest) returns (Account) {}
rpc DeleteAccount(DeleteAccountRequest) returns (google.protobuf.Empty) {}
@ -243,6 +244,14 @@ message GetAccountRequest {
string id = 1; // Account ID to retrieve
}
message GetAccountBatchRequest {
repeated string id = 1; // Account ID to retrieve
}
message GetAccountBatchResponse {
repeated Account accounts = 1; // List of accounts
}
message CreateAccountRequest {
string name = 1; // Required: Unique username
string nick = 2; // Optional: Display name

View File

@ -36,6 +36,12 @@ service PusherService {
// Sends a push notification to a list of users.
rpc SendPushNotificationToUsers(SendPushNotificationToUsersRequest) returns (google.protobuf.Empty) {}
// Unsubscribes a device from push notifications.
rpc UnsubscribePushNotifications(UnsubscribePushNotificationsRequest) returns (google.protobuf.Empty) {}
// Gets the WebSocket connection status for a device or user.
rpc GetWebsocketConnectionStatus(GetWebsocketConnectionStatusRequest) returns (GetWebsocketConnectionStatusResponse) {}
}
// Represents an email message.
@ -108,3 +114,18 @@ message SendPushNotificationToUsersRequest {
repeated string user_ids = 1;
PushNotification notification = 2;
}
message UnsubscribePushNotificationsRequest {
string device_id = 1;
}
message GetWebsocketConnectionStatusRequest {
oneof id {
string device_id = 1;
string user_id = 2;
}
}
message GetWebsocketConnectionStatusResponse {
bool is_connected = 1;
}

View File

@ -0,0 +1,47 @@
using dotnet_etcd.interfaces;
using DysonNetwork.Shared.Proto;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace DysonNetwork.Shared.Registry;
public static class ServiceHelper
{
public static IServiceCollection AddPusherService(this IServiceCollection services)
{
services.AddSingleton<PusherService.PusherServiceClient>(sp =>
{
var etcdClient = sp.GetRequiredService<IEtcdClient>();
var config = sp.GetRequiredService<IConfiguration>();
var clientCertPath = config["Service:ClientCert"];
var clientKeyPath = config["Service:ClientKey"];
var clientCertPassword = config["Service:CertPassword"];
return GrpcClientHelper
.CreatePusherServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword)
.GetAwaiter()
.GetResult();
});
return services;
}
public static IServiceCollection AddAccountService(this IServiceCollection services)
{
services.AddSingleton<AccountService.AccountServiceClient>(sp =>
{
var etcdClient = sp.GetRequiredService<IEtcdClient>();
var config = sp.GetRequiredService<IConfiguration>();
var clientCertPath = config["Service:ClientCert"];
var clientKeyPath = config["Service:ClientKey"];
var clientCertPassword = config["Service:CertPassword"];
return GrpcClientHelper
.CreateAccountServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword)
.GetAwaiter()
.GetResult();
});
return services;
}
}

View File

@ -8,16 +8,20 @@ namespace DysonNetwork.Shared.Registry;
public class ServiceRegistry(IEtcdClient etcd, ILogger<ServiceRegistry> logger)
{
public async Task RegisterService(string serviceName, string serviceUrl, long leaseTtlSeconds = 60, CancellationToken cancellationToken = default)
public async Task RegisterService(string serviceName, string serviceUrl, long leaseTtlSeconds = 60,
CancellationToken cancellationToken = default)
{
var key = $"/services/{serviceName}";
var leaseResponse = await etcd.LeaseGrantAsync(new LeaseGrantRequest { TTL = leaseTtlSeconds });
var leaseResponse = await etcd.LeaseGrantAsync(
new LeaseGrantRequest { TTL = leaseTtlSeconds },
cancellationToken: cancellationToken
);
await etcd.PutAsync(new PutRequest
{
Key = ByteString.CopyFrom(key, Encoding.UTF8),
Value = ByteString.CopyFrom(serviceUrl, Encoding.UTF8),
Lease = leaseResponse.ID
});
}, cancellationToken: cancellationToken);
_ = Task.Run(async () =>
{