🐛 Serval bug fixes
This commit is contained in:
3
DysonNetwork.Pass/.gitignore
vendored
3
DysonNetwork.Pass/.gitignore
vendored
@@ -1 +1,2 @@
|
|||||||
/wwwroot/dist
|
/wwwroot/dist
|
||||||
|
/Keys
|
@@ -12,7 +12,7 @@ namespace DysonNetwork.Pass.Account;
|
|||||||
[Index(nameof(Name), IsUnique = true)]
|
[Index(nameof(Name), IsUnique = true)]
|
||||||
public class Account : ModelBase
|
public class Account : ModelBase
|
||||||
{
|
{
|
||||||
public Guid Id { get; set; }
|
public Guid Id { get; set; } = Guid.NewGuid();
|
||||||
[MaxLength(256)] public string Name { get; set; } = string.Empty;
|
[MaxLength(256)] public string Name { get; set; } = string.Empty;
|
||||||
[MaxLength(256)] public string Nick { get; set; } = string.Empty;
|
[MaxLength(256)] public string Nick { get; set; } = string.Empty;
|
||||||
[MaxLength(32)] public string Language { get; set; } = string.Empty;
|
[MaxLength(32)] public string Language { get; set; } = string.Empty;
|
||||||
|
@@ -84,79 +84,69 @@ public class AccountService(
|
|||||||
bool isActivated = false
|
bool isActivated = false
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
await using var transaction = await db.Database.BeginTransactionAsync();
|
var dupeNameCount = await db.Accounts.Where(a => a.Name == name).CountAsync();
|
||||||
try
|
if (dupeNameCount > 0)
|
||||||
|
throw new InvalidOperationException("Account name has already been taken.");
|
||||||
|
|
||||||
|
var account = new Account
|
||||||
{
|
{
|
||||||
var dupeNameCount = await db.Accounts.Where(a => a.Name == name).CountAsync();
|
Name = name,
|
||||||
if (dupeNameCount > 0)
|
Nick = nick,
|
||||||
throw new InvalidOperationException("Account name has already been taken.");
|
Language = language,
|
||||||
|
Contacts = new List<AccountContact>
|
||||||
var account = new Account
|
|
||||||
{
|
{
|
||||||
Name = name,
|
new()
|
||||||
Nick = nick,
|
|
||||||
Language = language,
|
|
||||||
Contacts = new List<AccountContact>
|
|
||||||
{
|
{
|
||||||
new()
|
Type = AccountContactType.Email,
|
||||||
{
|
Content = email,
|
||||||
Type = AccountContactType.Email,
|
VerifiedAt = isEmailVerified ? SystemClock.Instance.GetCurrentInstant() : null,
|
||||||
Content = email,
|
IsPrimary = true
|
||||||
VerifiedAt = isEmailVerified ? SystemClock.Instance.GetCurrentInstant() : null,
|
|
||||||
IsPrimary = true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
AuthFactors = password is not null
|
|
||||||
? new List<AccountAuthFactor>
|
|
||||||
{
|
|
||||||
new AccountAuthFactor
|
|
||||||
{
|
|
||||||
Type = AccountAuthFactorType.Password,
|
|
||||||
Secret = password,
|
|
||||||
EnabledAt = SystemClock.Instance.GetCurrentInstant()
|
|
||||||
}.HashSecret()
|
|
||||||
}
|
|
||||||
: [],
|
|
||||||
Profile = new AccountProfile()
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isActivated)
|
|
||||||
{
|
|
||||||
account.ActivatedAt = SystemClock.Instance.GetCurrentInstant();
|
|
||||||
var defaultGroup = await db.PermissionGroups.FirstOrDefaultAsync(g => g.Key == "default");
|
|
||||||
if (defaultGroup is not null)
|
|
||||||
{
|
|
||||||
db.PermissionGroupMembers.Add(new PermissionGroupMember
|
|
||||||
{
|
|
||||||
Actor = $"user:{account.Id}",
|
|
||||||
Group = defaultGroup
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
else
|
AuthFactors = password is not null
|
||||||
{
|
? new List<AccountAuthFactor>
|
||||||
var spell = await spells.CreateMagicSpell(
|
{
|
||||||
account,
|
new AccountAuthFactor
|
||||||
MagicSpellType.AccountActivation,
|
|
||||||
new Dictionary<string, object>
|
|
||||||
{
|
{
|
||||||
{ "contact_method", account.Contacts.First().Content }
|
Type = AccountAuthFactorType.Password,
|
||||||
}
|
Secret = password,
|
||||||
);
|
EnabledAt = SystemClock.Instance.GetCurrentInstant()
|
||||||
await spells.NotifyMagicSpell(spell, true);
|
}.HashSecret()
|
||||||
}
|
}
|
||||||
|
: [],
|
||||||
|
Profile = new AccountProfile()
|
||||||
|
};
|
||||||
|
|
||||||
db.Accounts.Add(account);
|
if (isActivated)
|
||||||
await db.SaveChangesAsync();
|
|
||||||
|
|
||||||
await transaction.CommitAsync();
|
|
||||||
return account;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
{
|
||||||
await transaction.RollbackAsync();
|
account.ActivatedAt = SystemClock.Instance.GetCurrentInstant();
|
||||||
throw;
|
var defaultGroup = await db.PermissionGroups.FirstOrDefaultAsync(g => g.Key == "default");
|
||||||
|
if (defaultGroup is not null)
|
||||||
|
{
|
||||||
|
db.PermissionGroupMembers.Add(new PermissionGroupMember
|
||||||
|
{
|
||||||
|
Actor = $"user:{account.Id}",
|
||||||
|
Group = defaultGroup
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
db.Accounts.Add(account);
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
|
if (isActivated) return account;
|
||||||
|
|
||||||
|
var spell = await spells.CreateMagicSpell(
|
||||||
|
account,
|
||||||
|
MagicSpellType.AccountActivation,
|
||||||
|
new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "contact_method", account.Contacts.First().Content }
|
||||||
|
}
|
||||||
|
);
|
||||||
|
await spells.NotifyMagicSpell(spell, true);
|
||||||
|
|
||||||
|
return account;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Account> CreateAccount(OidcUserInfo userInfo)
|
public async Task<Account> CreateAccount(OidcUserInfo userInfo)
|
||||||
|
@@ -31,6 +31,7 @@ public class AuthSession : ModelBase
|
|||||||
LastGrantedAt = LastGrantedAt?.ToTimestamp(),
|
LastGrantedAt = LastGrantedAt?.ToTimestamp(),
|
||||||
ExpiredAt = ExpiredAt?.ToTimestamp(),
|
ExpiredAt = ExpiredAt?.ToTimestamp(),
|
||||||
AccountId = AccountId.ToString(),
|
AccountId = AccountId.ToString(),
|
||||||
|
Account = Account.ToProtoValue(),
|
||||||
ChallengeId = ChallengeId.ToString(),
|
ChallengeId = ChallengeId.ToString(),
|
||||||
Challenge = Challenge.ToProtoValue(),
|
Challenge = Challenge.ToProtoValue(),
|
||||||
AppId = AppId?.ToString()
|
AppId = AppId?.ToString()
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
using System.Text.Json;
|
|
||||||
using DysonNetwork.Pass;
|
using DysonNetwork.Pass;
|
||||||
using DysonNetwork.Pass.Pages.Data;
|
using DysonNetwork.Pass.Pages.Data;
|
||||||
using DysonNetwork.Pass.Startup;
|
using DysonNetwork.Pass.Startup;
|
||||||
@@ -6,7 +5,6 @@ using DysonNetwork.Shared.Http;
|
|||||||
using DysonNetwork.Shared.PageData;
|
using DysonNetwork.Shared.PageData;
|
||||||
using DysonNetwork.Shared.Registry;
|
using DysonNetwork.Shared.Registry;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.FileProviders;
|
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
@@ -18,11 +16,12 @@ builder.Services.AddAppMetrics();
|
|||||||
|
|
||||||
// Add application services
|
// Add application services
|
||||||
builder.Services.AddRegistryService(builder.Configuration);
|
builder.Services.AddRegistryService(builder.Configuration);
|
||||||
builder.Services.AddPusherService();
|
|
||||||
builder.Services.AddAppServices(builder.Configuration);
|
builder.Services.AddAppServices(builder.Configuration);
|
||||||
builder.Services.AddAppRateLimiting();
|
builder.Services.AddAppRateLimiting();
|
||||||
builder.Services.AddAppAuthentication();
|
builder.Services.AddAppAuthentication();
|
||||||
builder.Services.AddAppSwagger();
|
builder.Services.AddAppSwagger();
|
||||||
|
builder.Services.AddPusherService();
|
||||||
|
builder.Services.AddDriveService();
|
||||||
|
|
||||||
// Add flush handlers and websocket handlers
|
// Add flush handlers and websocket handlers
|
||||||
builder.Services.AddAppFlushHandlers();
|
builder.Services.AddAppFlushHandlers();
|
||||||
|
@@ -75,6 +75,7 @@ public static class ApplicationConfiguration
|
|||||||
app.MapGrpcService<AccountServiceGrpc>();
|
app.MapGrpcService<AccountServiceGrpc>();
|
||||||
app.MapGrpcService<AuthServiceGrpc>();
|
app.MapGrpcService<AuthServiceGrpc>();
|
||||||
app.MapGrpcService<ActionLogServiceGrpc>();
|
app.MapGrpcService<ActionLogServiceGrpc>();
|
||||||
|
app.MapGrpcService<PermissionServiceGrpc>();
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
@@ -16,6 +16,7 @@ builder.Services.AddAppRateLimiting();
|
|||||||
builder.Services.AddAppAuthentication();
|
builder.Services.AddAppAuthentication();
|
||||||
builder.Services.AddAppSwagger();
|
builder.Services.AddAppSwagger();
|
||||||
builder.Services.AddDysonAuth();
|
builder.Services.AddDysonAuth();
|
||||||
|
builder.Services.AddAccountService();
|
||||||
|
|
||||||
// Add flush handlers and websocket handlers
|
// Add flush handlers and websocket handlers
|
||||||
builder.Services.AddAppFlushHandlers();
|
builder.Services.AddAppFlushHandlers();
|
||||||
|
@@ -29,8 +29,6 @@ public class DysonTokenAuthHandler(
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var now = SystemClock.Instance.GetCurrentInstant();
|
|
||||||
|
|
||||||
// Validate token and extract session ID
|
// Validate token and extract session ID
|
||||||
AuthSession session;
|
AuthSession session;
|
||||||
try
|
try
|
||||||
@@ -59,8 +57,20 @@ public class DysonTokenAuthHandler(
|
|||||||
new("token_type", tokenInfo.Type.ToString())
|
new("token_type", tokenInfo.Type.ToString())
|
||||||
};
|
};
|
||||||
|
|
||||||
// return AuthenticateResult.Success(ticket);
|
// Add scopes as claims
|
||||||
return AuthenticateResult.NoResult();
|
session.Challenge.Scopes.ToList().ForEach(scope => claims.Add(new Claim("scope", scope)));
|
||||||
|
|
||||||
|
// Add superuser claim if applicable
|
||||||
|
if (session.Account.IsSuperuser)
|
||||||
|
claims.Add(new Claim("is_superuser", "1"));
|
||||||
|
|
||||||
|
// Create the identity and principal
|
||||||
|
var identity = new ClaimsIdentity(claims, AuthConstants.SchemeName);
|
||||||
|
var principal = new ClaimsPrincipal(identity);
|
||||||
|
|
||||||
|
var ticket = new AuthenticationTicket(principal, AuthConstants.SchemeName);
|
||||||
|
|
||||||
|
return AuthenticateResult.Success(ticket);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@@ -49,6 +49,18 @@ public static class GrpcClientHelper
|
|||||||
return new AccountService.AccountServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath,
|
return new AccountService.AccountServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath,
|
||||||
clientCertPassword));
|
clientCertPassword));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async Task<ActionLogService.ActionLogServiceClient> CreateActionLogServiceClient(
|
||||||
|
IEtcdClient etcdClient,
|
||||||
|
string clientCertPath,
|
||||||
|
string clientKeyPath,
|
||||||
|
string? clientCertPassword = null
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var url = await GetServiceUrlFromEtcd(etcdClient, "DysonNetwork.Pass");
|
||||||
|
return new ActionLogService.ActionLogServiceClient(CreateCallInvoker(url, clientCertPath, clientKeyPath,
|
||||||
|
clientCertPassword));
|
||||||
|
}
|
||||||
|
|
||||||
public static async Task<AuthService.AuthServiceClient> CreateAuthServiceClient(
|
public static async Task<AuthService.AuthServiceClient> CreateAuthServiceClient(
|
||||||
IEtcdClient etcdClient,
|
IEtcdClient etcdClient,
|
||||||
|
@@ -40,7 +40,21 @@ public static class ServiceHelper
|
|||||||
.CreateAccountServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword)
|
.CreateAccountServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword)
|
||||||
.GetAwaiter()
|
.GetAwaiter()
|
||||||
.GetResult();
|
.GetResult();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
services.AddSingleton<ActionLogService.ActionLogServiceClient>(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
|
||||||
|
.CreateActionLogServiceClient(etcdClient, clientCertPath, clientKeyPath, clientCertPassword)
|
||||||
|
.GetAwaiter()
|
||||||
|
.GetResult();
|
||||||
|
});
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
@@ -8,8 +8,12 @@ namespace DysonNetwork.Shared.Registry;
|
|||||||
|
|
||||||
public class ServiceRegistry(IEtcdClient etcd, ILogger<ServiceRegistry> logger)
|
public class ServiceRegistry(IEtcdClient etcd, ILogger<ServiceRegistry> logger)
|
||||||
{
|
{
|
||||||
public async Task RegisterService(string serviceName, string serviceUrl, long leaseTtlSeconds = 60,
|
public async Task RegisterService(
|
||||||
CancellationToken cancellationToken = default)
|
string serviceName,
|
||||||
|
string serviceUrl,
|
||||||
|
long leaseTtlSeconds = 60,
|
||||||
|
CancellationToken cancellationToken = default
|
||||||
|
)
|
||||||
{
|
{
|
||||||
var key = $"/services/{serviceName}";
|
var key = $"/services/{serviceName}";
|
||||||
var leaseResponse = await etcd.LeaseGrantAsync(
|
var leaseResponse = await etcd.LeaseGrantAsync(
|
||||||
|
@@ -177,6 +177,7 @@
|
|||||||
<_ContentIncludedByDefault Remove="Pages\Auth\VerifyFactor.cshtml" />
|
<_ContentIncludedByDefault Remove="Pages\Auth\VerifyFactor.cshtml" />
|
||||||
<_ContentIncludedByDefault Remove="Pages\Checkpoint\CheckpointPage.cshtml" />
|
<_ContentIncludedByDefault Remove="Pages\Checkpoint\CheckpointPage.cshtml" />
|
||||||
<_ContentIncludedByDefault Remove="Pages\Spell\MagicSpellPage.cshtml" />
|
<_ContentIncludedByDefault Remove="Pages\Spell\MagicSpellPage.cshtml" />
|
||||||
|
<_ContentIncludedByDefault Remove="Keys\Solian.json" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
Reference in New Issue
Block a user