Compare commits
21 Commits
0b337e362e
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
b231d80101
|
|||
|
f5d2a95b97
|
|||
|
97c009aef6
|
|||
|
8527e67af1
|
|||
|
073464e6c8
|
|||
|
b2b9ecad7b
|
|||
|
e484616ecd
|
|||
|
331cb612ad
|
|||
|
f696c1d400
|
|||
|
cd5248e4e6
|
|||
|
79ff8fad7a
|
|||
|
f9df60dc88
|
|||
|
74dac9f506
|
|||
|
54e66b2387
|
|||
|
6852869313
|
|||
|
693d3c6af7
|
|||
|
8d0c3aafb3
|
|||
|
5366cfbc60
|
|||
|
c23a43c84c
|
|||
|
b2352514ee
|
|||
|
e43979da54
|
8
.gitignore
vendored
8
.gitignore
vendored
@@ -9,8 +9,6 @@
|
|||||||
*.user
|
*.user
|
||||||
*.userosscache
|
*.userosscache
|
||||||
*.sln.docstates
|
*.sln.docstates
|
||||||
|
|
||||||
# Mac
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||||
@@ -353,4 +351,8 @@ MigrationBackup/
|
|||||||
.ionide/
|
.ionide/
|
||||||
|
|
||||||
# docker run data
|
# docker run data
|
||||||
Docker/run/data/
|
Docker/run/data/
|
||||||
|
|
||||||
|
**/obj/
|
||||||
|
**/bin/
|
||||||
|
**/.idea/
|
||||||
7
.gitmodules
vendored
7
.gitmodules
vendored
@@ -1,3 +1,4 @@
|
|||||||
[submodule "UmbraAPI"]
|
[submodule "MareAPI"]
|
||||||
path = UmbraAPI
|
path = MareAPI
|
||||||
url = https://git.umbra-sync.net/SirConstance/UmbraAPI.git
|
url = git@git.umbra-sync.net:Keda/UmbraAPI.git
|
||||||
|
branch = main
|
||||||
|
|||||||
1
MareAPI
Submodule
1
MareAPI
Submodule
Submodule MareAPI added at d105d20507
@@ -0,0 +1,174 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using MareSynchronosAuthService.Services;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace MareSynchronosAuthService.Controllers;
|
||||||
|
|
||||||
|
[Authorize]
|
||||||
|
[ApiController]
|
||||||
|
[Route("discovery")]
|
||||||
|
public class DiscoveryController : Controller
|
||||||
|
{
|
||||||
|
private readonly DiscoveryWellKnownProvider _provider;
|
||||||
|
private readonly DiscoveryPresenceService _presence;
|
||||||
|
|
||||||
|
public DiscoveryController(DiscoveryWellKnownProvider provider, DiscoveryPresenceService presence)
|
||||||
|
{
|
||||||
|
_provider = provider;
|
||||||
|
_presence = presence;
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class QueryRequest
|
||||||
|
{
|
||||||
|
[JsonPropertyName("hashes")] public string[] Hashes { get; set; } = Array.Empty<string>();
|
||||||
|
[JsonPropertyName("salt")] public string SaltB64 { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class QueryResponseEntry
|
||||||
|
{
|
||||||
|
[JsonPropertyName("hash")] public string Hash { get; set; } = string.Empty;
|
||||||
|
[JsonPropertyName("token")] public string? Token { get; set; }
|
||||||
|
[JsonPropertyName("uid")] public string Uid { get; set; } = string.Empty;
|
||||||
|
[JsonPropertyName("displayName")] public string? DisplayName { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("query")]
|
||||||
|
public IActionResult Query([FromBody] QueryRequest req)
|
||||||
|
{
|
||||||
|
if (_provider.IsExpired(req.SaltB64))
|
||||||
|
{
|
||||||
|
return BadRequest(new { code = "DISCOVERY_SALT_EXPIRED" });
|
||||||
|
}
|
||||||
|
|
||||||
|
var uid = User?.Claims?.FirstOrDefault(c => c.Type == MareSynchronosShared.Utils.MareClaimTypes.Uid)?.Value ?? string.Empty;
|
||||||
|
if (string.IsNullOrEmpty(uid) || req?.Hashes == null || req.Hashes.Length == 0)
|
||||||
|
return Json(Array.Empty<QueryResponseEntry>());
|
||||||
|
|
||||||
|
List<QueryResponseEntry> matches = new();
|
||||||
|
foreach (var h in req.Hashes.Distinct(StringComparer.Ordinal))
|
||||||
|
{
|
||||||
|
var (found, token, targetUid, displayName) = _presence.TryMatchAndIssueToken(uid, h);
|
||||||
|
if (found)
|
||||||
|
{
|
||||||
|
matches.Add(new QueryResponseEntry { Hash = h, Token = token, Uid = targetUid, DisplayName = displayName });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Json(matches);
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class RequestDto
|
||||||
|
{
|
||||||
|
[JsonPropertyName("token")] public string Token { get; set; } = string.Empty;
|
||||||
|
[JsonPropertyName("displayName")] public string? DisplayName { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("request")]
|
||||||
|
public async Task<IActionResult> RequestPair([FromBody] RequestDto req)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(req.Token)) return BadRequest();
|
||||||
|
if (_presence.ValidateToken(req.Token, out var targetUid))
|
||||||
|
{
|
||||||
|
// Phase 3 (minimal): notify target via mare-server internal controller
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var fromUid = User?.Claims?.FirstOrDefault(c => c.Type == MareSynchronosShared.Utils.MareClaimTypes.Uid)?.Value ?? string.Empty;
|
||||||
|
var fromAlias = string.IsNullOrEmpty(req.DisplayName)
|
||||||
|
? (User?.Claims?.FirstOrDefault(c => c.Type == MareSynchronosShared.Utils.MareClaimTypes.Alias)?.Value ?? string.Empty)
|
||||||
|
: req.DisplayName;
|
||||||
|
|
||||||
|
using var http = new HttpClient();
|
||||||
|
// Use same host as public (goes through nginx)
|
||||||
|
var baseUrl = $"{Request.Scheme}://{Request.Host.Value}";
|
||||||
|
var url = new Uri(new Uri(baseUrl), "/main/discovery/notifyRequest");
|
||||||
|
|
||||||
|
// Generate internal JWT
|
||||||
|
var serverToken = HttpContext.RequestServices.GetRequiredService<MareSynchronosShared.Utils.ServerTokenGenerator>().Token;
|
||||||
|
http.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", serverToken);
|
||||||
|
var payload = System.Text.Json.JsonSerializer.Serialize(new { targetUid, fromUid, fromAlias });
|
||||||
|
var resp = await http.PostAsync(url, new StringContent(payload, System.Text.Encoding.UTF8, "application/json"));
|
||||||
|
if (!resp.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var txt = await resp.Content.ReadAsStringAsync();
|
||||||
|
HttpContext.RequestServices.GetRequiredService<ILogger<DiscoveryController>>()
|
||||||
|
.LogWarning("notifyRequest failed: {code} {reason} {body}", (int)resp.StatusCode, resp.ReasonPhrase, txt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { /* ignore */ }
|
||||||
|
|
||||||
|
return Accepted();
|
||||||
|
}
|
||||||
|
return BadRequest(new { code = "INVALID_TOKEN" });
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class AcceptNotifyDto
|
||||||
|
{
|
||||||
|
[JsonPropertyName("targetUid")] public string TargetUid { get; set; } = string.Empty;
|
||||||
|
[JsonPropertyName("displayName")] public string? DisplayName { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accept notification relay (sender -> auth -> main)
|
||||||
|
[HttpPost("acceptNotify")]
|
||||||
|
public async Task<IActionResult> AcceptNotify([FromBody] AcceptNotifyDto req)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(req.TargetUid)) return BadRequest();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var fromUid = User?.Claims?.FirstOrDefault(c => c.Type == MareSynchronosShared.Utils.MareClaimTypes.Uid)?.Value ?? string.Empty;
|
||||||
|
var fromAlias = string.IsNullOrEmpty(req.DisplayName)
|
||||||
|
? (User?.Claims?.FirstOrDefault(c => c.Type == MareSynchronosShared.Utils.MareClaimTypes.Alias)?.Value ?? string.Empty)
|
||||||
|
: req.DisplayName;
|
||||||
|
|
||||||
|
using var http = new HttpClient();
|
||||||
|
var baseUrl = $"{Request.Scheme}://{Request.Host.Value}";
|
||||||
|
var url = new Uri(new Uri(baseUrl), "/main/discovery/notifyAccept");
|
||||||
|
var serverToken = HttpContext.RequestServices.GetRequiredService<MareSynchronosShared.Utils.ServerTokenGenerator>().Token;
|
||||||
|
http.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", serverToken);
|
||||||
|
var payload = System.Text.Json.JsonSerializer.Serialize(new { targetUid = req.TargetUid, fromUid, fromAlias });
|
||||||
|
var resp = await http.PostAsync(url, new StringContent(payload, System.Text.Encoding.UTF8, "application/json"));
|
||||||
|
if (!resp.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var txt = await resp.Content.ReadAsStringAsync();
|
||||||
|
HttpContext.RequestServices.GetRequiredService<ILogger<DiscoveryController>>()
|
||||||
|
.LogWarning("notifyAccept failed: {code} {reason} {body}", (int)resp.StatusCode, resp.ReasonPhrase, txt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { /* ignore */ }
|
||||||
|
|
||||||
|
return Accepted();
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class PublishRequest
|
||||||
|
{
|
||||||
|
[JsonPropertyName("hashes")] public string[] Hashes { get; set; } = Array.Empty<string>();
|
||||||
|
[JsonPropertyName("displayName")] public string? DisplayName { get; set; }
|
||||||
|
[JsonPropertyName("salt")] public string SaltB64 { get; set; } = string.Empty;
|
||||||
|
[JsonPropertyName("allowRequests")] public bool AllowRequests { get; set; } = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("disable")]
|
||||||
|
public IActionResult Disable()
|
||||||
|
{
|
||||||
|
var uid = User?.Claims?.FirstOrDefault(c => c.Type == MareSynchronosShared.Utils.MareClaimTypes.Uid)?.Value ?? string.Empty;
|
||||||
|
if (string.IsNullOrEmpty(uid)) return Accepted();
|
||||||
|
_presence.Unpublish(uid);
|
||||||
|
return Accepted();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("publish")]
|
||||||
|
public IActionResult Publish([FromBody] PublishRequest req)
|
||||||
|
{
|
||||||
|
if (_provider.IsExpired(req.SaltB64))
|
||||||
|
{
|
||||||
|
return BadRequest(new { code = "DISCOVERY_SALT_EXPIRED" });
|
||||||
|
}
|
||||||
|
var uid = User?.Claims?.FirstOrDefault(c => c.Type == MareSynchronosShared.Utils.MareClaimTypes.Uid)?.Value ?? string.Empty;
|
||||||
|
if (string.IsNullOrEmpty(uid) || req?.Hashes == null || req.Hashes.Length == 0)
|
||||||
|
return Accepted();
|
||||||
|
|
||||||
|
_presence.Publish(uid, req.Hashes, req.DisplayName, req.AllowRequests);
|
||||||
|
return Accepted();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -113,7 +113,8 @@ public class JwtController : Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
var existingIdent = await _redis.GetAsync<string>("UID:" + authResult.Uid);
|
var existingIdent = await _redis.GetAsync<string>("UID:" + authResult.Uid);
|
||||||
if (!string.IsNullOrEmpty(existingIdent)) return Unauthorized("Already logged in to this account. Reconnect in 60 seconds. If you keep seeing this issue, restart your game.");
|
if (!string.IsNullOrEmpty(existingIdent) && !string.Equals(existingIdent, charaIdent, StringComparison.Ordinal))
|
||||||
|
return Unauthorized("Already logged in to this account. Reconnect in 60 seconds. If you keep seeing this issue, restart your game.");
|
||||||
|
|
||||||
var token = CreateToken(new List<Claim>()
|
var token = CreateToken(new List<Claim>()
|
||||||
{
|
{
|
||||||
@@ -134,10 +135,13 @@ public class JwtController : Controller
|
|||||||
var tokenContent = tokenResponse as ContentResult;
|
var tokenContent = tokenResponse as ContentResult;
|
||||||
if (tokenContent == null)
|
if (tokenContent == null)
|
||||||
return tokenResponse;
|
return tokenResponse;
|
||||||
|
var provider = HttpContext.RequestServices.GetService<DiscoveryWellKnownProvider>();
|
||||||
|
var wk = provider?.GetWellKnownJson(Request.Scheme, Request.Host.Value)
|
||||||
|
?? _configuration.GetValueOrDefault(nameof(AuthServiceConfiguration.WellKnown), string.Empty);
|
||||||
return Json(new AuthReplyDto
|
return Json(new AuthReplyDto
|
||||||
{
|
{
|
||||||
Token = tokenContent.Content,
|
Token = tokenContent.Content,
|
||||||
WellKnown = _configuration.GetValueOrDefault(nameof(AuthServiceConfiguration.WellKnown), string.Empty),
|
WellKnown = wk,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,4 +194,3 @@ public class JwtController : Controller
|
|||||||
return handler.CreateJwtSecurityToken(token);
|
return handler.CreateJwtSecurityToken(token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
using MareSynchronosAuthService.Services;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace MareSynchronosAuthService.Controllers;
|
||||||
|
|
||||||
|
[AllowAnonymous]
|
||||||
|
[ApiController]
|
||||||
|
public class WellKnownController : Controller
|
||||||
|
{
|
||||||
|
private readonly DiscoveryWellKnownProvider _provider;
|
||||||
|
|
||||||
|
public WellKnownController(DiscoveryWellKnownProvider provider)
|
||||||
|
{
|
||||||
|
_provider = provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("/.well-known/Umbra/client")]
|
||||||
|
public IActionResult Get()
|
||||||
|
{
|
||||||
|
var json = _provider.GetWellKnownJson(Request.Scheme, Request.Host.Value);
|
||||||
|
return Content(json, "application/json");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,21 +1,19 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<EnableDefaultContentItems>false</EnableDefaultContentItems>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content Remove="appsettings.Development.json" />
|
<Content Include="appsettings.Development.json">
|
||||||
<Content Remove="appsettings.json" />
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</ItemGroup>
|
</Content>
|
||||||
|
<Content Include="appsettings.json">
|
||||||
<ItemGroup>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
<None Include="appsettings.Development.json" />
|
</Content>
|
||||||
<None Include="appsettings.json">
|
|
||||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -29,6 +27,9 @@
|
|||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="MaxMind.GeoIP2" Version="5.3.0" />
|
<PackageReference Include="MaxMind.GeoIP2" Version="5.3.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting.Systemd" Version="8.0.1" />
|
<PackageReference Include="Microsoft.Extensions.Hosting.Systemd" Version="8.0.1" />
|
||||||
|
<PackageReference Include="StackExchange.Redis" Version="2.9.11" />
|
||||||
|
<PackageReference Include="StackExchange.Redis.Extensions.Core" Version="10.2.0" />
|
||||||
|
<PackageReference Include="StackExchange.Redis.Extensions.System.Text.Json" Version="10.2.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using System.Collections.Concurrent;
|
||||||
|
|
||||||
|
namespace MareSynchronosAuthService.Services.Discovery;
|
||||||
|
|
||||||
|
public interface IDiscoveryPresenceStore : IDisposable
|
||||||
|
{
|
||||||
|
void Publish(string uid, IEnumerable<string> hashes, string? displayName = null, bool allowRequests = true);
|
||||||
|
void Unpublish(string uid);
|
||||||
|
(bool Found, string? Token, string TargetUid, string? DisplayName) TryMatchAndIssueToken(string requesterUid, string hash);
|
||||||
|
bool ValidateToken(string token, out string targetUid);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
using System.Collections.Concurrent;
|
||||||
|
|
||||||
|
namespace MareSynchronosAuthService.Services.Discovery;
|
||||||
|
|
||||||
|
public sealed class InMemoryPresenceStore : IDiscoveryPresenceStore
|
||||||
|
{
|
||||||
|
private readonly ConcurrentDictionary<string, (string Uid, DateTimeOffset ExpiresAt, string? DisplayName, bool AllowRequests)> _presence = new(StringComparer.Ordinal);
|
||||||
|
private readonly ConcurrentDictionary<string, (string TargetUid, DateTimeOffset ExpiresAt)> _tokens = new(StringComparer.Ordinal);
|
||||||
|
private readonly TimeSpan _presenceTtl;
|
||||||
|
private readonly TimeSpan _tokenTtl;
|
||||||
|
private readonly Timer _cleanupTimer;
|
||||||
|
|
||||||
|
public InMemoryPresenceStore(TimeSpan presenceTtl, TimeSpan tokenTtl)
|
||||||
|
{
|
||||||
|
_presenceTtl = presenceTtl;
|
||||||
|
_tokenTtl = tokenTtl;
|
||||||
|
_cleanupTimer = new Timer(_ => Cleanup(), null, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_cleanupTimer.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Cleanup()
|
||||||
|
{
|
||||||
|
var now = DateTimeOffset.UtcNow;
|
||||||
|
foreach (var kv in _presence.ToArray())
|
||||||
|
{
|
||||||
|
if (kv.Value.ExpiresAt <= now) _presence.TryRemove(kv.Key, out _);
|
||||||
|
}
|
||||||
|
foreach (var kv in _tokens.ToArray())
|
||||||
|
{
|
||||||
|
if (kv.Value.ExpiresAt <= now) _tokens.TryRemove(kv.Key, out _);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Publish(string uid, IEnumerable<string> hashes, string? displayName = null, bool allowRequests = true)
|
||||||
|
{
|
||||||
|
var exp = DateTimeOffset.UtcNow.Add(_presenceTtl);
|
||||||
|
foreach (var h in hashes.Distinct(StringComparer.Ordinal))
|
||||||
|
{
|
||||||
|
_presence[h] = (uid, exp, displayName, allowRequests);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Unpublish(string uid)
|
||||||
|
{
|
||||||
|
// Remove all presence hashes owned by this uid
|
||||||
|
foreach (var kv in _presence.ToArray())
|
||||||
|
{
|
||||||
|
if (string.Equals(kv.Value.Uid, uid, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
_presence.TryRemove(kv.Key, out _);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public (bool Found, string? Token, string TargetUid, string? DisplayName) TryMatchAndIssueToken(string requesterUid, string hash)
|
||||||
|
{
|
||||||
|
if (_presence.TryGetValue(hash, out var entry))
|
||||||
|
{
|
||||||
|
// Refresh TTL for this presence whenever it is matched (regardless of AllowRequests)
|
||||||
|
var refreshed = (entry.Uid, DateTimeOffset.UtcNow.Add(_presenceTtl), entry.DisplayName, entry.AllowRequests);
|
||||||
|
_presence[hash] = refreshed;
|
||||||
|
|
||||||
|
if (string.Equals(entry.Uid, requesterUid, StringComparison.Ordinal))
|
||||||
|
return (false, null, string.Empty, null);
|
||||||
|
|
||||||
|
// Visible but requests disabled → no token
|
||||||
|
if (!entry.AllowRequests)
|
||||||
|
return (true, null, entry.Uid, entry.DisplayName);
|
||||||
|
|
||||||
|
var token = Guid.NewGuid().ToString("N");
|
||||||
|
_tokens[token] = (entry.Uid, DateTimeOffset.UtcNow.Add(_tokenTtl));
|
||||||
|
return (true, token, entry.Uid, entry.DisplayName);
|
||||||
|
}
|
||||||
|
return (false, null, string.Empty, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ValidateToken(string token, out string targetUid)
|
||||||
|
{
|
||||||
|
targetUid = string.Empty;
|
||||||
|
if (_tokens.TryGetValue(token, out var info))
|
||||||
|
{
|
||||||
|
if (info.ExpiresAt > DateTimeOffset.UtcNow)
|
||||||
|
{
|
||||||
|
targetUid = info.TargetUid;
|
||||||
|
|
||||||
|
// Optional robustness: refresh TTL for all presence entries of this target
|
||||||
|
var newExp = DateTimeOffset.UtcNow.Add(_presenceTtl);
|
||||||
|
foreach (var kv in _presence.ToArray())
|
||||||
|
{
|
||||||
|
if (string.Equals(kv.Value.Uid, targetUid, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
var v = kv.Value;
|
||||||
|
_presence[kv.Key] = (v.Uid, newExp, v.DisplayName, v.AllowRequests);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
_tokens.TryRemove(token, out _);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using StackExchange.Redis;
|
||||||
|
|
||||||
|
namespace MareSynchronosAuthService.Services.Discovery;
|
||||||
|
|
||||||
|
public sealed class RedisPresenceStore : IDiscoveryPresenceStore
|
||||||
|
{
|
||||||
|
private readonly ILogger<RedisPresenceStore> _logger;
|
||||||
|
private readonly IDatabase _db;
|
||||||
|
private readonly TimeSpan _presenceTtl;
|
||||||
|
private readonly TimeSpan _tokenTtl;
|
||||||
|
private readonly JsonSerializerOptions _jsonOpts = new() { DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull };
|
||||||
|
|
||||||
|
public RedisPresenceStore(ILogger<RedisPresenceStore> logger, IConnectionMultiplexer mux, TimeSpan presenceTtl, TimeSpan tokenTtl)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_db = mux.GetDatabase();
|
||||||
|
_presenceTtl = presenceTtl;
|
||||||
|
_tokenTtl = tokenTtl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose() { }
|
||||||
|
|
||||||
|
private static string KeyForHash(string hash) => $"nd:hash:{hash}";
|
||||||
|
private static string KeyForToken(string token) => $"nd:token:{token}";
|
||||||
|
private static string KeyForUidSet(string uid) => $"nd:uid:{uid}";
|
||||||
|
|
||||||
|
public void Publish(string uid, IEnumerable<string> hashes, string? displayName = null, bool allowRequests = true)
|
||||||
|
{
|
||||||
|
var entries = hashes.Distinct(StringComparer.Ordinal).ToArray();
|
||||||
|
if (entries.Length == 0) return;
|
||||||
|
var batch = _db.CreateBatch();
|
||||||
|
foreach (var h in entries)
|
||||||
|
{
|
||||||
|
var key = KeyForHash(h);
|
||||||
|
var payload = JsonSerializer.Serialize(new Presence(uid, displayName, allowRequests), _jsonOpts);
|
||||||
|
batch.StringSetAsync(key, payload, _presenceTtl);
|
||||||
|
// Index this hash under the publisher uid for fast unpublish
|
||||||
|
batch.SetAddAsync(KeyForUidSet(uid), h);
|
||||||
|
batch.KeyExpireAsync(KeyForUidSet(uid), _presenceTtl);
|
||||||
|
}
|
||||||
|
batch.Execute();
|
||||||
|
_logger.LogDebug("RedisPresenceStore: published {count} hashes", entries.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Unpublish(string uid)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var setKey = KeyForUidSet(uid);
|
||||||
|
var members = _db.SetMembers(setKey);
|
||||||
|
if (members is { Length: > 0 })
|
||||||
|
{
|
||||||
|
var batch = _db.CreateBatch();
|
||||||
|
foreach (var m in members)
|
||||||
|
{
|
||||||
|
var hash = (string)m;
|
||||||
|
var key = KeyForHash(hash);
|
||||||
|
// Defensive: only delete if the hash is still owned by this uid
|
||||||
|
var val = _db.StringGet(key);
|
||||||
|
if (val.HasValue)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var p = JsonSerializer.Deserialize<Presence>(val!);
|
||||||
|
if (p != null && string.Equals(p.Uid, uid, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
batch.KeyDeleteAsync(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { /* ignore corrupted */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Remove the uid index set itself
|
||||||
|
batch.KeyDeleteAsync(setKey);
|
||||||
|
batch.Execute();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// No index set: best-effort, just delete the set key in case it exists
|
||||||
|
_db.KeyDelete(setKey);
|
||||||
|
}
|
||||||
|
_logger.LogDebug("RedisPresenceStore: unpublished all hashes for uid {uid}", uid);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "RedisPresenceStore: Unpublish failed for uid {uid}", uid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public (bool Found, string? Token, string TargetUid, string? DisplayName) TryMatchAndIssueToken(string requesterUid, string hash)
|
||||||
|
{
|
||||||
|
var key = KeyForHash(hash);
|
||||||
|
var val = _db.StringGet(key);
|
||||||
|
if (!val.HasValue) return (false, null, string.Empty, null);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var p = JsonSerializer.Deserialize<Presence>(val!);
|
||||||
|
if (p == null || string.IsNullOrEmpty(p.Uid)) return (false, null, string.Empty, null);
|
||||||
|
if (string.Equals(p.Uid, requesterUid, StringComparison.Ordinal)) return (false, null, string.Empty, null);
|
||||||
|
|
||||||
|
// Refresh TTLs for this presence whenever it is matched
|
||||||
|
_db.KeyExpire(KeyForHash(hash), _presenceTtl);
|
||||||
|
_db.KeyExpire(KeyForUidSet(p.Uid), _presenceTtl);
|
||||||
|
|
||||||
|
// Visible but requests disabled → return without token
|
||||||
|
if (!p.AllowRequests)
|
||||||
|
{
|
||||||
|
return (true, null, p.Uid, p.DisplayName);
|
||||||
|
}
|
||||||
|
|
||||||
|
var token = Guid.NewGuid().ToString("N");
|
||||||
|
_db.StringSet(KeyForToken(token), p.Uid, _tokenTtl);
|
||||||
|
return (true, token, p.Uid, p.DisplayName);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return (false, null, string.Empty, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ValidateToken(string token, out string targetUid)
|
||||||
|
{
|
||||||
|
targetUid = string.Empty;
|
||||||
|
var key = KeyForToken(token);
|
||||||
|
var val = _db.StringGet(key);
|
||||||
|
if (!val.HasValue) return false;
|
||||||
|
targetUid = val!;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var setKey = KeyForUidSet(targetUid);
|
||||||
|
var members = _db.SetMembers(setKey);
|
||||||
|
if (members is { Length: > 0 })
|
||||||
|
{
|
||||||
|
var batch = _db.CreateBatch();
|
||||||
|
foreach (var m in members)
|
||||||
|
{
|
||||||
|
var h = (string)m;
|
||||||
|
batch.KeyExpireAsync(KeyForHash(h), _presenceTtl);
|
||||||
|
}
|
||||||
|
batch.KeyExpireAsync(setKey, _presenceTtl);
|
||||||
|
batch.Execute();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Still try to extend the set TTL even if empty
|
||||||
|
_db.KeyExpire(setKey, _presenceTtl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { /* ignore TTL refresh issues */ }
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed record Presence(string Uid, string? DisplayName, bool AllowRequests);
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using MareSynchronosAuthService.Services.Discovery;
|
||||||
|
|
||||||
|
namespace MareSynchronosAuthService.Services;
|
||||||
|
|
||||||
|
public class DiscoveryPresenceService : IHostedService, IDisposable
|
||||||
|
{
|
||||||
|
private readonly ILogger<DiscoveryPresenceService> _logger;
|
||||||
|
private readonly IDiscoveryPresenceStore _store;
|
||||||
|
private readonly TimeSpan _presenceTtl = TimeSpan.FromMinutes(5);
|
||||||
|
private readonly TimeSpan _tokenTtl = TimeSpan.FromMinutes(2);
|
||||||
|
|
||||||
|
public DiscoveryPresenceService(ILogger<DiscoveryPresenceService> logger, IDiscoveryPresenceStore store)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_store = store;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task StartAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task StopAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Publish(string uid, IEnumerable<string> hashes, string? displayName = null, bool allowRequests = true)
|
||||||
|
{
|
||||||
|
_store.Publish(uid, hashes, displayName, allowRequests);
|
||||||
|
_logger.LogDebug("Discovery presence published for {uid} with {n} hashes", uid, hashes.Count());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Unpublish(string uid)
|
||||||
|
{
|
||||||
|
_store.Unpublish(uid);
|
||||||
|
_logger.LogDebug("Discovery presence unpublished for {uid}", uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public (bool Found, string? Token, string TargetUid, string? DisplayName) TryMatchAndIssueToken(string requesterUid, string hash)
|
||||||
|
{
|
||||||
|
var res = _store.TryMatchAndIssueToken(requesterUid, hash);
|
||||||
|
return (res.Found, res.Token, res.TargetUid, res.DisplayName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ValidateToken(string token, out string targetUid)
|
||||||
|
{
|
||||||
|
return _store.ValidateToken(token, out targetUid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
(_store as IDisposable)?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace MareSynchronosAuthService.Services;
|
||||||
|
|
||||||
|
public class DiscoveryWellKnownProvider : IHostedService
|
||||||
|
{
|
||||||
|
private readonly ILogger<DiscoveryWellKnownProvider> _logger;
|
||||||
|
private readonly object _lock = new();
|
||||||
|
private byte[] _currentSalt = Array.Empty<byte>();
|
||||||
|
private DateTimeOffset _currentSaltExpiresAt;
|
||||||
|
private byte[] _previousSalt = Array.Empty<byte>();
|
||||||
|
private DateTimeOffset _previousSaltExpiresAt;
|
||||||
|
private readonly TimeSpan _gracePeriod = TimeSpan.FromMinutes(5);
|
||||||
|
private Timer? _rotationTimer;
|
||||||
|
private readonly TimeSpan _saltTtl = TimeSpan.FromDays(30 * 6);
|
||||||
|
private readonly int _refreshSec = 86400; // 24h
|
||||||
|
|
||||||
|
public DiscoveryWellKnownProvider(ILogger<DiscoveryWellKnownProvider> logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task StartAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
RotateSalt();
|
||||||
|
var period = _saltTtl;
|
||||||
|
if (period.TotalMilliseconds > uint.MaxValue - 1)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("DiscoveryWellKnownProvider: salt TTL {ttl} exceeds timer limit, skipping rotation timer in beta", period);
|
||||||
|
_rotationTimer = new Timer(_ => { }, null, Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_rotationTimer = new Timer(_ => RotateSalt(), null, period, period);
|
||||||
|
}
|
||||||
|
_logger.LogInformation("DiscoveryWellKnownProvider started. Salt expires at {exp}", _currentSaltExpiresAt);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task StopAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
_rotationTimer?.Dispose();
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RotateSalt()
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
if (_currentSalt.Length > 0)
|
||||||
|
{
|
||||||
|
_previousSalt = _currentSalt;
|
||||||
|
_previousSaltExpiresAt = DateTimeOffset.UtcNow.Add(_gracePeriod);
|
||||||
|
}
|
||||||
|
_currentSalt = RandomNumberGenerator.GetBytes(32);
|
||||||
|
_currentSaltExpiresAt = DateTimeOffset.UtcNow.Add(_saltTtl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsExpired(string providedSaltB64)
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
var now = DateTimeOffset.UtcNow;
|
||||||
|
var provided = Convert.FromBase64String(providedSaltB64);
|
||||||
|
|
||||||
|
if (_currentSalt.SequenceEqual(provided) && now <= _currentSaltExpiresAt)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (_previousSalt.Length > 0 && _previousSalt.SequenceEqual(provided) && now <= _previousSaltExpiresAt)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetWellKnownJson(string scheme, string host)
|
||||||
|
{
|
||||||
|
var isHttps = string.Equals(scheme, "https", StringComparison.OrdinalIgnoreCase);
|
||||||
|
var wsScheme = isHttps ? "wss" : "ws";
|
||||||
|
var httpScheme = isHttps ? "https" : "http";
|
||||||
|
|
||||||
|
byte[] salt;
|
||||||
|
DateTimeOffset exp;
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
salt = _currentSalt.ToArray();
|
||||||
|
exp = _currentSaltExpiresAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
var root = new WellKnownRoot
|
||||||
|
{
|
||||||
|
ApiUrl = $"{wsScheme}://{host}",
|
||||||
|
HubUrl = $"{wsScheme}://{host}/mare",
|
||||||
|
Features = new() { NearbyDiscovery = true },
|
||||||
|
NearbyDiscovery = new()
|
||||||
|
{
|
||||||
|
Enabled = true,
|
||||||
|
HashAlgo = "sha256",
|
||||||
|
SaltB64 = Convert.ToBase64String(salt),
|
||||||
|
SaltExpiresAt = exp,
|
||||||
|
RefreshSec = _refreshSec,
|
||||||
|
GraceSec = (int)_gracePeriod.TotalSeconds,
|
||||||
|
Endpoints = new()
|
||||||
|
{
|
||||||
|
Publish = $"{httpScheme}://{host}/discovery/publish",
|
||||||
|
Query = $"{httpScheme}://{host}/discovery/query",
|
||||||
|
Request = $"{httpScheme}://{host}/discovery/request",
|
||||||
|
Accept = $"{httpScheme}://{host}/discovery/acceptNotify"
|
||||||
|
},
|
||||||
|
Policies = new()
|
||||||
|
{
|
||||||
|
MaxQueryBatch = 100,
|
||||||
|
MinQueryIntervalMs = 2000,
|
||||||
|
RateLimitPerMin = 30,
|
||||||
|
TokenTtlSec = 120
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return JsonSerializer.Serialize(root, new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull });
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class WellKnownRoot
|
||||||
|
{
|
||||||
|
[JsonPropertyName("api_url")] public string ApiUrl { get; set; } = string.Empty;
|
||||||
|
[JsonPropertyName("hub_url")] public string HubUrl { get; set; } = string.Empty;
|
||||||
|
[JsonPropertyName("skip_negotiation")] public bool SkipNegotiation { get; set; } = true;
|
||||||
|
[JsonPropertyName("transports")] public string[] Transports { get; set; } = new[] { "websockets" };
|
||||||
|
[JsonPropertyName("features")] public Features Features { get; set; } = new();
|
||||||
|
[JsonPropertyName("nearby_discovery")] public Nearby NearbyDiscovery { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class Features
|
||||||
|
{
|
||||||
|
[JsonPropertyName("nearby_discovery")] public bool NearbyDiscovery { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class Nearby
|
||||||
|
{
|
||||||
|
[JsonPropertyName("enabled")] public bool Enabled { get; set; }
|
||||||
|
[JsonPropertyName("hash_algo")] public string HashAlgo { get; set; } = "sha256";
|
||||||
|
[JsonPropertyName("salt_b64")] public string SaltB64 { get; set; } = string.Empty;
|
||||||
|
[JsonPropertyName("salt_expires_at")] public DateTimeOffset SaltExpiresAt { get; set; }
|
||||||
|
[JsonPropertyName("refresh_sec")] public int RefreshSec { get; set; }
|
||||||
|
[JsonPropertyName("grace_sec")] public int GraceSec { get; set; }
|
||||||
|
[JsonPropertyName("endpoints")] public Endpoints Endpoints { get; set; } = new();
|
||||||
|
[JsonPropertyName("policies")] public Policies Policies { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class Endpoints
|
||||||
|
{
|
||||||
|
[JsonPropertyName("publish")] public string? Publish { get; set; }
|
||||||
|
[JsonPropertyName("query")] public string? Query { get; set; }
|
||||||
|
[JsonPropertyName("request")] public string? Request { get; set; }
|
||||||
|
[JsonPropertyName("accept")] public string? Accept { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class Policies
|
||||||
|
{
|
||||||
|
[JsonPropertyName("max_query_batch")] public int MaxQueryBatch { get; set; }
|
||||||
|
[JsonPropertyName("min_query_interval_ms")] public int MinQueryIntervalMs { get; set; }
|
||||||
|
[JsonPropertyName("rate_limit_per_min")] public int RateLimitPerMin { get; set; }
|
||||||
|
[JsonPropertyName("token_ttl_sec")] public int TokenTtlSec { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@ using System.Text;
|
|||||||
using MareSynchronosShared.Data;
|
using MareSynchronosShared.Data;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Prometheus;
|
using Prometheus;
|
||||||
|
using Microsoft.AspNetCore.HttpOverrides;
|
||||||
using MareSynchronosShared.Utils.Configuration;
|
using MareSynchronosShared.Utils.Configuration;
|
||||||
|
|
||||||
namespace MareSynchronosAuthService;
|
namespace MareSynchronosAuthService;
|
||||||
@@ -35,6 +36,12 @@ public class Startup
|
|||||||
{
|
{
|
||||||
var config = app.ApplicationServices.GetRequiredService<IConfigurationService<MareConfigurationBase>>();
|
var config = app.ApplicationServices.GetRequiredService<IConfigurationService<MareConfigurationBase>>();
|
||||||
|
|
||||||
|
// Respect X-Forwarded-* headers from the reverse proxy so generated links use the public scheme/host
|
||||||
|
app.UseForwardedHeaders(new ForwardedHeadersOptions
|
||||||
|
{
|
||||||
|
ForwardedHeaders = ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost | ForwardedHeaders.XForwardedFor
|
||||||
|
});
|
||||||
|
|
||||||
app.UseRouting();
|
app.UseRouting();
|
||||||
|
|
||||||
app.UseHttpMetrics();
|
app.UseHttpMetrics();
|
||||||
@@ -48,6 +55,7 @@ public class Startup
|
|||||||
app.UseEndpoints(endpoints =>
|
app.UseEndpoints(endpoints =>
|
||||||
{
|
{
|
||||||
endpoints.MapControllers();
|
endpoints.MapControllers();
|
||||||
|
endpoints.MapHealthChecks("/healthz").WithMetadata(new AllowAnonymousAttribute());
|
||||||
|
|
||||||
foreach (var source in endpoints.DataSources.SelectMany(e => e.Endpoints).Cast<RouteEndpoint>())
|
foreach (var source in endpoints.DataSources.SelectMany(e => e.Endpoints).Cast<RouteEndpoint>())
|
||||||
{
|
{
|
||||||
@@ -65,8 +73,8 @@ public class Startup
|
|||||||
|
|
||||||
ConfigureRedis(services, mareConfig);
|
ConfigureRedis(services, mareConfig);
|
||||||
|
|
||||||
services.AddSingleton<SecretKeyAuthenticatorService>();
|
services.AddScoped<SecretKeyAuthenticatorService>();
|
||||||
services.AddSingleton<AccountRegistrationService>();
|
services.AddScoped<AccountRegistrationService>();
|
||||||
services.AddSingleton<GeoIPService>();
|
services.AddSingleton<GeoIPService>();
|
||||||
|
|
||||||
services.AddHostedService(provider => provider.GetRequiredService<GeoIPService>());
|
services.AddHostedService(provider => provider.GetRequiredService<GeoIPService>());
|
||||||
@@ -75,6 +83,30 @@ public class Startup
|
|||||||
services.Configure<MareConfigurationBase>(_configuration.GetRequiredSection("MareSynchronos"));
|
services.Configure<MareConfigurationBase>(_configuration.GetRequiredSection("MareSynchronos"));
|
||||||
|
|
||||||
services.AddSingleton<ServerTokenGenerator>();
|
services.AddSingleton<ServerTokenGenerator>();
|
||||||
|
// Nearby discovery services (well-known + presence)
|
||||||
|
services.AddSingleton<DiscoveryWellKnownProvider>();
|
||||||
|
services.AddHostedService(p => p.GetRequiredService<DiscoveryWellKnownProvider>());
|
||||||
|
|
||||||
|
// Presence store selection
|
||||||
|
var discoveryStore = _configuration.GetValue<string>("NearbyDiscovery:Store") ?? "memory";
|
||||||
|
TimeSpan presenceTtl = TimeSpan.FromMinutes(_configuration.GetValue<int>("NearbyDiscovery:PresenceTtlMinutes", 5));
|
||||||
|
TimeSpan tokenTtl = TimeSpan.FromSeconds(_configuration.GetValue<int>("NearbyDiscovery:TokenTtlSeconds", 120));
|
||||||
|
if (string.Equals(discoveryStore, "redis", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
services.AddSingleton<MareSynchronosAuthService.Services.Discovery.IDiscoveryPresenceStore>(sp =>
|
||||||
|
{
|
||||||
|
var logger = sp.GetRequiredService<ILogger<MareSynchronosAuthService.Services.Discovery.RedisPresenceStore>>();
|
||||||
|
var mux = sp.GetRequiredService<IConnectionMultiplexer>();
|
||||||
|
return new MareSynchronosAuthService.Services.Discovery.RedisPresenceStore(logger, mux, presenceTtl, tokenTtl);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
services.AddSingleton<MareSynchronosAuthService.Services.Discovery.IDiscoveryPresenceStore>(sp => new MareSynchronosAuthService.Services.Discovery.InMemoryPresenceStore(presenceTtl, tokenTtl));
|
||||||
|
}
|
||||||
|
|
||||||
|
services.AddSingleton<DiscoveryPresenceService>();
|
||||||
|
services.AddHostedService(p => p.GetRequiredService<DiscoveryPresenceService>());
|
||||||
|
|
||||||
ConfigureAuthorization(services);
|
ConfigureAuthorization(services);
|
||||||
|
|
||||||
@@ -88,8 +120,11 @@ public class Startup
|
|||||||
services.AddControllers().ConfigureApplicationPartManager(a =>
|
services.AddControllers().ConfigureApplicationPartManager(a =>
|
||||||
{
|
{
|
||||||
a.FeatureProviders.Remove(a.FeatureProviders.OfType<ControllerFeatureProvider>().First());
|
a.FeatureProviders.Remove(a.FeatureProviders.OfType<ControllerFeatureProvider>().First());
|
||||||
a.FeatureProviders.Add(new AllowedControllersFeatureProvider(typeof(JwtController)));
|
a.FeatureProviders.Add(new AllowedControllersFeatureProvider(typeof(JwtController), typeof(WellKnownController), typeof(DiscoveryController)));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
services.AddSingleton<DiscoveryWellKnownProvider>();
|
||||||
|
services.AddHostedService(p => p.GetRequiredService<DiscoveryWellKnownProvider>());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ConfigureAuthorization(IServiceCollection services)
|
private static void ConfigureAuthorization(IServiceCollection services)
|
||||||
@@ -195,6 +230,8 @@ public class Startup
|
|||||||
};
|
};
|
||||||
|
|
||||||
services.AddStackExchangeRedisExtensions<SystemTextJsonSerializer>(redisConfiguration);
|
services.AddStackExchangeRedisExtensions<SystemTextJsonSerializer>(redisConfiguration);
|
||||||
|
// Also expose raw multiplexer for custom Redis usage (discovery presence)
|
||||||
|
services.AddSingleton<IConnectionMultiplexer>(_ => ConnectionMultiplexer.Connect(options));
|
||||||
}
|
}
|
||||||
private void ConfigureConfigServices(IServiceCollection services)
|
private void ConfigureConfigServices(IServiceCollection services)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,5 +4,15 @@
|
|||||||
"Default": "Information",
|
"Default": "Information",
|
||||||
"Microsoft.AspNetCore": "Warning"
|
"Microsoft.AspNetCore": "Warning"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*",
|
||||||
|
"ConnectionStrings": {
|
||||||
|
"DefaultConnection": "Host=localhost;Port=5432;Username=postgres;Password=postgres;Database=umbra_dev"
|
||||||
|
},
|
||||||
|
"MareSynchronos": {
|
||||||
|
"Jwt": "dev-secret-umbra-abcdefghijklmnopqrstuvwxyz123456",
|
||||||
|
"RedisConnectionString": "localhost:6379,connectTimeout=5000,syncTimeout=5000",
|
||||||
|
"MetricsPort": 4985
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ VisualStudioVersion = 17.2.32602.215
|
|||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MareSynchronosServer", "MareSynchronosServer\MareSynchronosServer.csproj", "{029CA97F-E0BA-4172-A191-EA21FB61AD0F}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MareSynchronosServer", "MareSynchronosServer\MareSynchronosServer.csproj", "{029CA97F-E0BA-4172-A191-EA21FB61AD0F}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MareSynchronos.API", "..\MareAPI\MareSynchronosAPI\MareSynchronos.API.csproj", "{326BFB1B-5571-47A6-8513-1FFDB32D53B0}"
|
||||||
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MareSynchronosShared", "MareSynchronosShared\MareSynchronosShared.csproj", "{67B1461D-E215-4BA8-A64D-E1836724D5E6}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MareSynchronosShared", "MareSynchronosShared\MareSynchronosShared.csproj", "{67B1461D-E215-4BA8-A64D-E1836724D5E6}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MareSynchronosStaticFilesServer", "MareSynchronosStaticFilesServer\MareSynchronosStaticFilesServer.csproj", "{3C7F43BB-FE4C-48BC-BF42-D24E70E8FCB7}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MareSynchronosStaticFilesServer", "MareSynchronosStaticFilesServer\MareSynchronosStaticFilesServer.csproj", "{3C7F43BB-FE4C-48BC-BF42-D24E70E8FCB7}"
|
||||||
@@ -18,90 +20,36 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MareSynchronosAuthService", "MareSynchronosAuthService\MareSynchronosAuthService.csproj", "{D7D4041C-DCD9-4B7A-B423-0F458DFFF3D6}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MareSynchronosAuthService", "MareSynchronosAuthService\MareSynchronosAuthService.csproj", "{D7D4041C-DCD9-4B7A-B423-0F458DFFF3D6}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MareSynchronos.API", "..\UmbraAPI\MareSynchronosAPI\MareSynchronos.API.csproj", "{FA97F4D1-8F15-4002-A2C1-70EF1367B879}"
|
|
||||||
EndProject
|
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
Debug|x64 = Debug|x64
|
|
||||||
Debug|x86 = Debug|x86
|
|
||||||
Release|Any CPU = Release|Any CPU
|
Release|Any CPU = Release|Any CPU
|
||||||
Release|x64 = Release|x64
|
|
||||||
Release|x86 = Release|x86
|
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
{029CA97F-E0BA-4172-A191-EA21FB61AD0F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{029CA97F-E0BA-4172-A191-EA21FB61AD0F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{029CA97F-E0BA-4172-A191-EA21FB61AD0F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{029CA97F-E0BA-4172-A191-EA21FB61AD0F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{029CA97F-E0BA-4172-A191-EA21FB61AD0F}.Debug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{029CA97F-E0BA-4172-A191-EA21FB61AD0F}.Debug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{029CA97F-E0BA-4172-A191-EA21FB61AD0F}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{029CA97F-E0BA-4172-A191-EA21FB61AD0F}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{029CA97F-E0BA-4172-A191-EA21FB61AD0F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{029CA97F-E0BA-4172-A191-EA21FB61AD0F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{029CA97F-E0BA-4172-A191-EA21FB61AD0F}.Release|Any CPU.Build.0 = Release|Any CPU
|
{029CA97F-E0BA-4172-A191-EA21FB61AD0F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{029CA97F-E0BA-4172-A191-EA21FB61AD0F}.Release|x64.ActiveCfg = Release|Any CPU
|
{326BFB1B-5571-47A6-8513-1FFDB32D53B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{029CA97F-E0BA-4172-A191-EA21FB61AD0F}.Release|x64.Build.0 = Release|Any CPU
|
{326BFB1B-5571-47A6-8513-1FFDB32D53B0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{029CA97F-E0BA-4172-A191-EA21FB61AD0F}.Release|x86.ActiveCfg = Release|Any CPU
|
{326BFB1B-5571-47A6-8513-1FFDB32D53B0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{029CA97F-E0BA-4172-A191-EA21FB61AD0F}.Release|x86.Build.0 = Release|Any CPU
|
{326BFB1B-5571-47A6-8513-1FFDB32D53B0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{67B1461D-E215-4BA8-A64D-E1836724D5E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{67B1461D-E215-4BA8-A64D-E1836724D5E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{67B1461D-E215-4BA8-A64D-E1836724D5E6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{67B1461D-E215-4BA8-A64D-E1836724D5E6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{67B1461D-E215-4BA8-A64D-E1836724D5E6}.Debug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{67B1461D-E215-4BA8-A64D-E1836724D5E6}.Debug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{67B1461D-E215-4BA8-A64D-E1836724D5E6}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{67B1461D-E215-4BA8-A64D-E1836724D5E6}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{67B1461D-E215-4BA8-A64D-E1836724D5E6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{67B1461D-E215-4BA8-A64D-E1836724D5E6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{67B1461D-E215-4BA8-A64D-E1836724D5E6}.Release|Any CPU.Build.0 = Release|Any CPU
|
{67B1461D-E215-4BA8-A64D-E1836724D5E6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{67B1461D-E215-4BA8-A64D-E1836724D5E6}.Release|x64.ActiveCfg = Release|Any CPU
|
|
||||||
{67B1461D-E215-4BA8-A64D-E1836724D5E6}.Release|x64.Build.0 = Release|Any CPU
|
|
||||||
{67B1461D-E215-4BA8-A64D-E1836724D5E6}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{67B1461D-E215-4BA8-A64D-E1836724D5E6}.Release|x86.Build.0 = Release|Any CPU
|
|
||||||
{3C7F43BB-FE4C-48BC-BF42-D24E70E8FCB7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{3C7F43BB-FE4C-48BC-BF42-D24E70E8FCB7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{3C7F43BB-FE4C-48BC-BF42-D24E70E8FCB7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{3C7F43BB-FE4C-48BC-BF42-D24E70E8FCB7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{3C7F43BB-FE4C-48BC-BF42-D24E70E8FCB7}.Debug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{3C7F43BB-FE4C-48BC-BF42-D24E70E8FCB7}.Debug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{3C7F43BB-FE4C-48BC-BF42-D24E70E8FCB7}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{3C7F43BB-FE4C-48BC-BF42-D24E70E8FCB7}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{3C7F43BB-FE4C-48BC-BF42-D24E70E8FCB7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{3C7F43BB-FE4C-48BC-BF42-D24E70E8FCB7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{3C7F43BB-FE4C-48BC-BF42-D24E70E8FCB7}.Release|Any CPU.Build.0 = Release|Any CPU
|
{3C7F43BB-FE4C-48BC-BF42-D24E70E8FCB7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{3C7F43BB-FE4C-48BC-BF42-D24E70E8FCB7}.Release|x64.ActiveCfg = Release|Any CPU
|
|
||||||
{3C7F43BB-FE4C-48BC-BF42-D24E70E8FCB7}.Release|x64.Build.0 = Release|Any CPU
|
|
||||||
{3C7F43BB-FE4C-48BC-BF42-D24E70E8FCB7}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{3C7F43BB-FE4C-48BC-BF42-D24E70E8FCB7}.Release|x86.Build.0 = Release|Any CPU
|
|
||||||
{E29C8677-AB44-4950-9EB1-D8E70B710A56}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{E29C8677-AB44-4950-9EB1-D8E70B710A56}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{E29C8677-AB44-4950-9EB1-D8E70B710A56}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{E29C8677-AB44-4950-9EB1-D8E70B710A56}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{E29C8677-AB44-4950-9EB1-D8E70B710A56}.Debug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{E29C8677-AB44-4950-9EB1-D8E70B710A56}.Debug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{E29C8677-AB44-4950-9EB1-D8E70B710A56}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{E29C8677-AB44-4950-9EB1-D8E70B710A56}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{E29C8677-AB44-4950-9EB1-D8E70B710A56}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{E29C8677-AB44-4950-9EB1-D8E70B710A56}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{E29C8677-AB44-4950-9EB1-D8E70B710A56}.Release|Any CPU.Build.0 = Release|Any CPU
|
{E29C8677-AB44-4950-9EB1-D8E70B710A56}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{E29C8677-AB44-4950-9EB1-D8E70B710A56}.Release|x64.ActiveCfg = Release|Any CPU
|
|
||||||
{E29C8677-AB44-4950-9EB1-D8E70B710A56}.Release|x64.Build.0 = Release|Any CPU
|
|
||||||
{E29C8677-AB44-4950-9EB1-D8E70B710A56}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{E29C8677-AB44-4950-9EB1-D8E70B710A56}.Release|x86.Build.0 = Release|Any CPU
|
|
||||||
{D7D4041C-DCD9-4B7A-B423-0F458DFFF3D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{D7D4041C-DCD9-4B7A-B423-0F458DFFF3D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{D7D4041C-DCD9-4B7A-B423-0F458DFFF3D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{D7D4041C-DCD9-4B7A-B423-0F458DFFF3D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{D7D4041C-DCD9-4B7A-B423-0F458DFFF3D6}.Debug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{D7D4041C-DCD9-4B7A-B423-0F458DFFF3D6}.Debug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{D7D4041C-DCD9-4B7A-B423-0F458DFFF3D6}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{D7D4041C-DCD9-4B7A-B423-0F458DFFF3D6}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{D7D4041C-DCD9-4B7A-B423-0F458DFFF3D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{D7D4041C-DCD9-4B7A-B423-0F458DFFF3D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{D7D4041C-DCD9-4B7A-B423-0F458DFFF3D6}.Release|Any CPU.Build.0 = Release|Any CPU
|
{D7D4041C-DCD9-4B7A-B423-0F458DFFF3D6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{D7D4041C-DCD9-4B7A-B423-0F458DFFF3D6}.Release|x64.ActiveCfg = Release|Any CPU
|
|
||||||
{D7D4041C-DCD9-4B7A-B423-0F458DFFF3D6}.Release|x64.Build.0 = Release|Any CPU
|
|
||||||
{D7D4041C-DCD9-4B7A-B423-0F458DFFF3D6}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{D7D4041C-DCD9-4B7A-B423-0F458DFFF3D6}.Release|x86.Build.0 = Release|Any CPU
|
|
||||||
{FA97F4D1-8F15-4002-A2C1-70EF1367B879}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{FA97F4D1-8F15-4002-A2C1-70EF1367B879}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{FA97F4D1-8F15-4002-A2C1-70EF1367B879}.Debug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{FA97F4D1-8F15-4002-A2C1-70EF1367B879}.Debug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{FA97F4D1-8F15-4002-A2C1-70EF1367B879}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{FA97F4D1-8F15-4002-A2C1-70EF1367B879}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{FA97F4D1-8F15-4002-A2C1-70EF1367B879}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{FA97F4D1-8F15-4002-A2C1-70EF1367B879}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{FA97F4D1-8F15-4002-A2C1-70EF1367B879}.Release|x64.ActiveCfg = Release|Any CPU
|
|
||||||
{FA97F4D1-8F15-4002-A2C1-70EF1367B879}.Release|x64.Build.0 = Release|Any CPU
|
|
||||||
{FA97F4D1-8F15-4002-A2C1-70EF1367B879}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{FA97F4D1-8F15-4002-A2C1-70EF1367B879}.Release|x86.Build.0 = Release|Any CPU
|
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
using MareSynchronos.API.SignalR;
|
||||||
|
using MareSynchronosShared.Utils;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.SignalR;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace MareSynchronosServer.Controllers;
|
||||||
|
|
||||||
|
[Route("/main/discovery")]
|
||||||
|
[Authorize(Policy = "Internal")]
|
||||||
|
public class DiscoveryNotifyController : Controller
|
||||||
|
{
|
||||||
|
private readonly ILogger<DiscoveryNotifyController> _logger;
|
||||||
|
private readonly IHubContext<Hubs.MareHub, IMareHub> _hub;
|
||||||
|
|
||||||
|
public DiscoveryNotifyController(ILogger<DiscoveryNotifyController> logger, IHubContext<Hubs.MareHub, IMareHub> hub)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_hub = hub;
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class NotifyRequestDto
|
||||||
|
{
|
||||||
|
[JsonPropertyName("targetUid")] public string TargetUid { get; set; } = string.Empty;
|
||||||
|
[JsonPropertyName("fromUid")] public string FromUid { get; set; } = string.Empty;
|
||||||
|
[JsonPropertyName("fromAlias")] public string? FromAlias { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("notifyRequest")]
|
||||||
|
public async Task<IActionResult> NotifyRequest([FromBody] NotifyRequestDto dto)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(dto.TargetUid)) return BadRequest();
|
||||||
|
var name = string.IsNullOrEmpty(dto.FromAlias) ? dto.FromUid : dto.FromAlias;
|
||||||
|
var msg = $"Nearby Request: {name} [{dto.FromUid}]";
|
||||||
|
_logger.LogInformation("Discovery notify request to {target} from {from}", dto.TargetUid, name);
|
||||||
|
await _hub.Clients.User(dto.TargetUid).Client_ReceiveServerMessage(MareSynchronos.API.Data.Enum.MessageSeverity.Information, msg);
|
||||||
|
return Accepted();
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class NotifyAcceptDto
|
||||||
|
{
|
||||||
|
[JsonPropertyName("targetUid")] public string TargetUid { get; set; } = string.Empty;
|
||||||
|
[JsonPropertyName("fromUid")] public string FromUid { get; set; } = string.Empty;
|
||||||
|
[JsonPropertyName("fromAlias")] public string? FromAlias { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("notifyAccept")]
|
||||||
|
public async Task<IActionResult> NotifyAccept([FromBody] NotifyAcceptDto dto)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(dto.TargetUid)) return BadRequest();
|
||||||
|
var name = string.IsNullOrEmpty(dto.FromAlias) ? dto.FromUid : dto.FromAlias;
|
||||||
|
var msg = $"Nearby Accept: {name} [{dto.FromUid}]";
|
||||||
|
_logger.LogInformation("Discovery notify accept to {target} from {from}", dto.TargetUid, name);
|
||||||
|
await _hub.Clients.User(dto.TargetUid).Client_ReceiveServerMessage(MareSynchronos.API.Data.Enum.MessageSeverity.Information, msg);
|
||||||
|
return Accepted();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -54,6 +54,8 @@ namespace MareSynchronosServer.Hubs
|
|||||||
|
|
||||||
public Task Client_UserUpdateSelfPairPermissions(UserPermissionsDto dto) => throw new PlatformNotSupportedException("Calling clientside method on server not supported");
|
public Task Client_UserUpdateSelfPairPermissions(UserPermissionsDto dto) => throw new PlatformNotSupportedException("Calling clientside method on server not supported");
|
||||||
|
|
||||||
|
public Task Client_UserTypingState(TypingStateDto dto) => throw new PlatformNotSupportedException("Calling clientside method on server not supported");
|
||||||
|
|
||||||
public Task Client_GposeLobbyJoin(UserData userData) => throw new PlatformNotSupportedException("Calling clientside method on server not supported");
|
public Task Client_GposeLobbyJoin(UserData userData) => throw new PlatformNotSupportedException("Calling clientside method on server not supported");
|
||||||
public Task Client_GposeLobbyLeave(UserData userData) => throw new PlatformNotSupportedException("Calling clientside method on server not supported");
|
public Task Client_GposeLobbyLeave(UserData userData) => throw new PlatformNotSupportedException("Calling clientside method on server not supported");
|
||||||
public Task Client_GposeLobbyPushCharacterData(CharaDataDownloadDto charaDownloadDto) => throw new PlatformNotSupportedException("Calling clientside method on server not supported");
|
public Task Client_GposeLobbyPushCharacterData(CharaDataDownloadDto charaDownloadDto) => throw new PlatformNotSupportedException("Calling clientside method on server not supported");
|
||||||
|
|||||||
@@ -259,7 +259,13 @@ public partial class MareHub
|
|||||||
var user = await DbContext.Users.SingleAsync(u => u.UID == groupHasMigrated.Item2).ConfigureAwait(false);
|
var user = await DbContext.Users.SingleAsync(u => u.UID == groupHasMigrated.Item2).ConfigureAwait(false);
|
||||||
|
|
||||||
await Clients.Users(groupPairsWithoutSelf.Select(p => p.GroupUserUID)).Client_GroupSendInfo(new GroupInfoDto(group.ToGroupData(),
|
await Clients.Users(groupPairsWithoutSelf.Select(p => p.GroupUserUID)).Client_GroupSendInfo(new GroupInfoDto(group.ToGroupData(),
|
||||||
user.ToUserData(), group.GetGroupPermissions())).ConfigureAwait(false);
|
user.ToUserData(), group.GetGroupPermissions())
|
||||||
|
{
|
||||||
|
IsTemporary = group.IsTemporary,
|
||||||
|
ExpiresAt = group.ExpiresAt,
|
||||||
|
AutoDetectVisible = group.AutoDetectVisible,
|
||||||
|
PasswordTemporarilyDisabled = group.PasswordTemporarilyDisabled,
|
||||||
|
}).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -292,4 +298,4 @@ public partial class MareHub
|
|||||||
await UserGroupLeave(groupUserPair, allUserPairs, ident, userUid).ConfigureAwait(false);
|
await UserGroupLeave(groupUserPair, allUserPairs, ident, userUid).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -144,7 +144,13 @@ public partial class MareHub
|
|||||||
|
|
||||||
var groupPairs = await DbContext.GroupPairs.Where(p => p.GroupGID == dto.Group.GID).Select(p => p.GroupUserUID).AsNoTracking().ToListAsync().ConfigureAwait(false);
|
var groupPairs = await DbContext.GroupPairs.Where(p => p.GroupGID == dto.Group.GID).Select(p => p.GroupUserUID).AsNoTracking().ToListAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
await Clients.Users(groupPairs).Client_GroupSendInfo(new GroupInfoDto(group.ToGroupData(), newOwnerPair.GroupUser.ToUserData(), group.GetGroupPermissions())).ConfigureAwait(false);
|
await Clients.Users(groupPairs).Client_GroupSendInfo(new GroupInfoDto(group.ToGroupData(), newOwnerPair.GroupUser.ToUserData(), group.GetGroupPermissions())
|
||||||
|
{
|
||||||
|
IsTemporary = group.IsTemporary,
|
||||||
|
ExpiresAt = group.ExpiresAt,
|
||||||
|
AutoDetectVisible = group.AutoDetectVisible,
|
||||||
|
PasswordTemporarilyDisabled = group.PasswordTemporarilyDisabled,
|
||||||
|
}).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize(Policy = "Identified")]
|
[Authorize(Policy = "Identified")]
|
||||||
@@ -202,7 +208,7 @@ public partial class MareHub
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Authorize(Policy = "Identified")]
|
[Authorize(Policy = "Identified")]
|
||||||
public async Task<GroupPasswordDto> GroupCreate()
|
public async Task<GroupPasswordDto> GroupCreate(string? alias)
|
||||||
{
|
{
|
||||||
_logger.LogCallInfo();
|
_logger.LogCallInfo();
|
||||||
var existingGroupsByUser = await DbContext.Groups.CountAsync(u => u.OwnerUID == UserUID).ConfigureAwait(false);
|
var existingGroupsByUser = await DbContext.Groups.CountAsync(u => u.OwnerUID == UserUID).ConfigureAwait(false);
|
||||||
@@ -223,12 +229,114 @@ public partial class MareHub
|
|||||||
var sha = SHA256.Create();
|
var sha = SHA256.Create();
|
||||||
var hashedPw = StringUtils.Sha256String(passwd);
|
var hashedPw = StringUtils.Sha256String(passwd);
|
||||||
|
|
||||||
|
string? sanitizedAlias = null;
|
||||||
|
if (!string.IsNullOrWhiteSpace(alias))
|
||||||
|
{
|
||||||
|
sanitizedAlias = alias.Trim();
|
||||||
|
if (sanitizedAlias.Length > 50)
|
||||||
|
{
|
||||||
|
sanitizedAlias = sanitizedAlias[..50];
|
||||||
|
}
|
||||||
|
|
||||||
|
var aliasExists = await DbContext.Groups
|
||||||
|
.AnyAsync(g => g.Alias != null && EF.Functions.ILike(g.Alias!, sanitizedAlias))
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
if (aliasExists)
|
||||||
|
{
|
||||||
|
throw new System.Exception("Syncshell name is already in use.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Group newGroup = new()
|
||||||
|
{
|
||||||
|
GID = gid,
|
||||||
|
HashedPassword = hashedPw,
|
||||||
|
InvitesEnabled = true,
|
||||||
|
OwnerUID = UserUID,
|
||||||
|
Alias = sanitizedAlias,
|
||||||
|
IsTemporary = false,
|
||||||
|
ExpiresAt = null,
|
||||||
|
};
|
||||||
|
|
||||||
|
GroupPair initialPair = new()
|
||||||
|
{
|
||||||
|
GroupGID = newGroup.GID,
|
||||||
|
GroupUserUID = UserUID,
|
||||||
|
IsPaused = false,
|
||||||
|
IsPinned = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
await DbContext.Groups.AddAsync(newGroup).ConfigureAwait(false);
|
||||||
|
await DbContext.GroupPairs.AddAsync(initialPair).ConfigureAwait(false);
|
||||||
|
await DbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
|
var self = await DbContext.Users.SingleAsync(u => u.UID == UserUID).ConfigureAwait(false);
|
||||||
|
|
||||||
|
await Clients.User(UserUID).Client_GroupSendFullInfo(new GroupFullInfoDto(newGroup.ToGroupData(), self.ToUserData(), GroupPermissions.NoneSet, GroupUserPermissions.NoneSet, GroupUserInfo.None)
|
||||||
|
{
|
||||||
|
IsTemporary = newGroup.IsTemporary,
|
||||||
|
ExpiresAt = newGroup.ExpiresAt,
|
||||||
|
AutoDetectVisible = newGroup.AutoDetectVisible,
|
||||||
|
PasswordTemporarilyDisabled = newGroup.PasswordTemporarilyDisabled,
|
||||||
|
}).ConfigureAwait(false);
|
||||||
|
|
||||||
|
_logger.LogCallInfo(MareHubLogger.Args(gid));
|
||||||
|
|
||||||
|
return new GroupPasswordDto(newGroup.ToGroupData(), passwd)
|
||||||
|
{
|
||||||
|
IsTemporary = newGroup.IsTemporary,
|
||||||
|
ExpiresAt = newGroup.ExpiresAt,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[Authorize(Policy = "Identified")]
|
||||||
|
public async Task<GroupPasswordDto> GroupCreateTemporary(DateTime expiresAtUtc)
|
||||||
|
{
|
||||||
|
_logger.LogCallInfo();
|
||||||
|
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
if (expiresAtUtc.Kind == DateTimeKind.Unspecified)
|
||||||
|
{
|
||||||
|
expiresAtUtc = DateTime.SpecifyKind(expiresAtUtc, DateTimeKind.Utc);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expiresAtUtc <= now)
|
||||||
|
{
|
||||||
|
throw new System.Exception("Expiration must be in the future.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expiresAtUtc > now.AddDays(7))
|
||||||
|
{
|
||||||
|
throw new System.Exception("Temporary syncshells may not last longer than 7 days.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var existingGroupsByUser = await DbContext.Groups.CountAsync(u => u.OwnerUID == UserUID).ConfigureAwait(false);
|
||||||
|
var existingJoinedGroups = await DbContext.GroupPairs.CountAsync(u => u.GroupUserUID == UserUID).ConfigureAwait(false);
|
||||||
|
if (existingGroupsByUser >= _maxExistingGroupsByUser || existingJoinedGroups >= _maxJoinedGroupsByUser)
|
||||||
|
{
|
||||||
|
throw new System.Exception($"Max groups for user is {_maxExistingGroupsByUser}, max joined groups is {_maxJoinedGroupsByUser}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var gid = StringUtils.GenerateRandomString(9);
|
||||||
|
while (await DbContext.Groups.AnyAsync(g => g.GID == "UMB-" + gid).ConfigureAwait(false))
|
||||||
|
{
|
||||||
|
gid = StringUtils.GenerateRandomString(9);
|
||||||
|
}
|
||||||
|
gid = "UMB-" + gid;
|
||||||
|
|
||||||
|
var passwd = StringUtils.GenerateRandomString(16);
|
||||||
|
var sha = SHA256.Create();
|
||||||
|
var hashedPw = StringUtils.Sha256String(passwd);
|
||||||
|
|
||||||
Group newGroup = new()
|
Group newGroup = new()
|
||||||
{
|
{
|
||||||
GID = gid,
|
GID = gid,
|
||||||
HashedPassword = hashedPw,
|
HashedPassword = hashedPw,
|
||||||
InvitesEnabled = true,
|
InvitesEnabled = true,
|
||||||
OwnerUID = UserUID,
|
OwnerUID = UserUID,
|
||||||
|
Alias = null,
|
||||||
|
IsTemporary = true,
|
||||||
|
ExpiresAt = expiresAtUtc,
|
||||||
};
|
};
|
||||||
|
|
||||||
GroupPair initialPair = new()
|
GroupPair initialPair = new()
|
||||||
@@ -245,12 +353,21 @@ public partial class MareHub
|
|||||||
|
|
||||||
var self = await DbContext.Users.SingleAsync(u => u.UID == UserUID).ConfigureAwait(false);
|
var self = await DbContext.Users.SingleAsync(u => u.UID == UserUID).ConfigureAwait(false);
|
||||||
|
|
||||||
await Clients.User(UserUID).Client_GroupSendFullInfo(new GroupFullInfoDto(newGroup.ToGroupData(), self.ToUserData(), GroupPermissions.NoneSet, GroupUserPermissions.NoneSet, GroupUserInfo.None))
|
await Clients.User(UserUID).Client_GroupSendFullInfo(new GroupFullInfoDto(newGroup.ToGroupData(), self.ToUserData(), GroupPermissions.NoneSet, GroupUserPermissions.NoneSet, GroupUserInfo.None)
|
||||||
.ConfigureAwait(false);
|
{
|
||||||
|
IsTemporary = newGroup.IsTemporary,
|
||||||
|
ExpiresAt = newGroup.ExpiresAt,
|
||||||
|
AutoDetectVisible = newGroup.AutoDetectVisible,
|
||||||
|
PasswordTemporarilyDisabled = newGroup.PasswordTemporarilyDisabled,
|
||||||
|
}).ConfigureAwait(false);
|
||||||
|
|
||||||
_logger.LogCallInfo(MareHubLogger.Args(gid));
|
_logger.LogCallInfo(MareHubLogger.Args(gid, "Temporary", expiresAtUtc));
|
||||||
|
|
||||||
return new GroupPasswordDto(newGroup.ToGroupData(), passwd);
|
return new GroupPasswordDto(newGroup.ToGroupData(), passwd)
|
||||||
|
{
|
||||||
|
IsTemporary = newGroup.IsTemporary,
|
||||||
|
ExpiresAt = newGroup.ExpiresAt,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize(Policy = "Identified")]
|
[Authorize(Policy = "Identified")]
|
||||||
@@ -338,22 +455,130 @@ public partial class MareHub
|
|||||||
_logger.LogCallInfo(MareHubLogger.Args(dto.Group));
|
_logger.LogCallInfo(MareHubLogger.Args(dto.Group));
|
||||||
|
|
||||||
var group = await DbContext.Groups.Include(g => g.Owner).AsNoTracking().SingleOrDefaultAsync(g => g.GID == aliasOrGid || g.Alias == aliasOrGid).ConfigureAwait(false);
|
var group = await DbContext.Groups.Include(g => g.Owner).AsNoTracking().SingleOrDefaultAsync(g => g.GID == aliasOrGid || g.Alias == aliasOrGid).ConfigureAwait(false);
|
||||||
var groupGid = group?.GID ?? string.Empty;
|
|
||||||
var existingPair = await DbContext.GroupPairs.AsNoTracking().SingleOrDefaultAsync(g => g.GroupGID == groupGid && g.GroupUserUID == UserUID).ConfigureAwait(false);
|
|
||||||
var hashedPw = StringUtils.Sha256String(dto.Password);
|
var hashedPw = StringUtils.Sha256String(dto.Password);
|
||||||
|
|
||||||
|
return await JoinGroupInternal(group, aliasOrGid, hashedPw, allowPasswordless: false, skipInviteCheck: false).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Authorize(Policy = "Identified")]
|
||||||
|
public async Task<bool> SyncshellDiscoveryJoin(GroupDto dto)
|
||||||
|
{
|
||||||
|
var gid = dto.Group.GID.Trim();
|
||||||
|
|
||||||
|
_logger.LogCallInfo(MareHubLogger.Args(dto.Group));
|
||||||
|
|
||||||
|
var group = await DbContext.Groups.Include(g => g.Owner).AsNoTracking().SingleOrDefaultAsync(g => g.GID == gid).ConfigureAwait(false);
|
||||||
|
|
||||||
|
return await JoinGroupInternal(group, gid, hashedPassword: null, allowPasswordless: true, skipInviteCheck: true).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Authorize(Policy = "Identified")]
|
||||||
|
public async Task<List<SyncshellDiscoveryEntryDto>> SyncshellDiscoveryList()
|
||||||
|
{
|
||||||
|
_logger.LogCallInfo();
|
||||||
|
|
||||||
|
var groups = await DbContext.Groups.AsNoTracking()
|
||||||
|
.Include(g => g.Owner)
|
||||||
|
.Where(g => g.AutoDetectVisible && (!g.IsTemporary || g.ExpiresAt == null || g.ExpiresAt > DateTime.UtcNow))
|
||||||
|
.ToListAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
|
var groupIds = groups.Select(g => g.GID).ToArray();
|
||||||
|
var memberCounts = await DbContext.GroupPairs.AsNoTracking()
|
||||||
|
.Where(p => groupIds.Contains(p.GroupGID))
|
||||||
|
.GroupBy(p => p.GroupGID)
|
||||||
|
.Select(g => new { g.Key, Count = g.Count() })
|
||||||
|
.ToDictionaryAsync(k => k.Key, k => k.Count, StringComparer.OrdinalIgnoreCase)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
return groups.Select(g => new SyncshellDiscoveryEntryDto
|
||||||
|
{
|
||||||
|
GID = g.GID,
|
||||||
|
Alias = g.Alias,
|
||||||
|
OwnerUID = g.OwnerUID,
|
||||||
|
OwnerAlias = g.Owner.Alias,
|
||||||
|
MemberCount = memberCounts.TryGetValue(g.GID, out var count) ? count : 0,
|
||||||
|
AutoAcceptPairs = g.InvitesEnabled,
|
||||||
|
Description = null,
|
||||||
|
}).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Authorize(Policy = "Identified")]
|
||||||
|
public async Task<SyncshellDiscoveryStateDto?> SyncshellDiscoveryGetState(GroupDto dto)
|
||||||
|
{
|
||||||
|
_logger.LogCallInfo(MareHubLogger.Args(dto.Group));
|
||||||
|
|
||||||
|
var (hasRights, group) = await TryValidateGroupModeratorOrOwner(dto.Group.GID).ConfigureAwait(false);
|
||||||
|
if (!hasRights) return null;
|
||||||
|
|
||||||
|
return new SyncshellDiscoveryStateDto
|
||||||
|
{
|
||||||
|
GID = group.GID,
|
||||||
|
AutoDetectVisible = group.AutoDetectVisible,
|
||||||
|
PasswordTemporarilyDisabled = group.PasswordTemporarilyDisabled,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[Authorize(Policy = "Identified")]
|
||||||
|
public async Task<bool> SyncshellDiscoverySetVisibility(SyncshellDiscoveryVisibilityRequestDto dto)
|
||||||
|
{
|
||||||
|
_logger.LogCallInfo(MareHubLogger.Args(dto));
|
||||||
|
|
||||||
|
var (hasRights, group) = await TryValidateGroupModeratorOrOwner(dto.GID).ConfigureAwait(false);
|
||||||
|
if (!hasRights) return false;
|
||||||
|
|
||||||
|
group.AutoDetectVisible = dto.AutoDetectVisible;
|
||||||
|
group.PasswordTemporarilyDisabled = dto.AutoDetectVisible;
|
||||||
|
|
||||||
|
await DbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
|
await DbContext.Entry(group).Reference(g => g.Owner).LoadAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
|
var groupPairs = await DbContext.GroupPairs.AsNoTracking().Where(p => p.GroupGID == group.GID).Select(p => p.GroupUserUID).ToListAsync().ConfigureAwait(false);
|
||||||
|
await Clients.Users(groupPairs).Client_GroupSendInfo(new GroupInfoDto(group.ToGroupData(), group.Owner.ToUserData(), group.GetGroupPermissions())
|
||||||
|
{
|
||||||
|
IsTemporary = group.IsTemporary,
|
||||||
|
ExpiresAt = group.ExpiresAt,
|
||||||
|
AutoDetectVisible = group.AutoDetectVisible,
|
||||||
|
PasswordTemporarilyDisabled = group.PasswordTemporarilyDisabled,
|
||||||
|
}).ConfigureAwait(false);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<bool> JoinGroupInternal(Group? group, string aliasOrGid, string? hashedPassword, bool allowPasswordless, bool skipInviteCheck)
|
||||||
|
{
|
||||||
|
if (group == null) return false;
|
||||||
|
|
||||||
|
var groupGid = group.GID;
|
||||||
|
var existingPair = await DbContext.GroupPairs.AsNoTracking().SingleOrDefaultAsync(g => g.GroupGID == groupGid && g.GroupUserUID == UserUID).ConfigureAwait(false);
|
||||||
var existingUserCount = await DbContext.GroupPairs.AsNoTracking().CountAsync(g => g.GroupGID == groupGid).ConfigureAwait(false);
|
var existingUserCount = await DbContext.GroupPairs.AsNoTracking().CountAsync(g => g.GroupGID == groupGid).ConfigureAwait(false);
|
||||||
var joinedGroups = await DbContext.GroupPairs.CountAsync(g => g.GroupUserUID == UserUID).ConfigureAwait(false);
|
var joinedGroups = await DbContext.GroupPairs.CountAsync(g => g.GroupUserUID == UserUID).ConfigureAwait(false);
|
||||||
var isBanned = await DbContext.GroupBans.AnyAsync(g => g.GroupGID == groupGid && g.BannedUserUID == UserUID).ConfigureAwait(false);
|
var isBanned = await DbContext.GroupBans.AnyAsync(g => g.GroupGID == groupGid && g.BannedUserUID == UserUID).ConfigureAwait(false);
|
||||||
var oneTimeInvite = await DbContext.GroupTempInvites.SingleOrDefaultAsync(g => g.GroupGID == groupGid && g.Invite == hashedPw).ConfigureAwait(false);
|
|
||||||
|
|
||||||
if (group == null
|
GroupTempInvite? oneTimeInvite = null;
|
||||||
|| (!string.Equals(group.HashedPassword, hashedPw, StringComparison.Ordinal) && oneTimeInvite == null)
|
if (!string.IsNullOrEmpty(hashedPassword))
|
||||||
|
{
|
||||||
|
oneTimeInvite = await DbContext.GroupTempInvites.SingleOrDefaultAsync(g => g.GroupGID == groupGid && g.Invite == hashedPassword).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allowPasswordless && !group.AutoDetectVisible)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool passwordBypass = group.PasswordTemporarilyDisabled || allowPasswordless;
|
||||||
|
bool passwordMatches = !string.IsNullOrEmpty(hashedPassword) && string.Equals(group.HashedPassword, hashedPassword, StringComparison.Ordinal);
|
||||||
|
bool hasValidCredential = passwordBypass || passwordMatches || oneTimeInvite != null;
|
||||||
|
|
||||||
|
if (!hasValidCredential
|
||||||
|| existingPair != null
|
|| existingPair != null
|
||||||
|| existingUserCount >= _maxGroupUserCount
|
|| existingUserCount >= _maxGroupUserCount
|
||||||
|| !group.InvitesEnabled
|
|| (!skipInviteCheck && !group.InvitesEnabled)
|
||||||
|| joinedGroups >= _maxJoinedGroupsByUser
|
|| joinedGroups >= _maxJoinedGroupsByUser
|
||||||
|| isBanned)
|
|| isBanned)
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (oneTimeInvite != null)
|
if (oneTimeInvite != null)
|
||||||
{
|
{
|
||||||
@@ -375,9 +600,17 @@ public partial class MareHub
|
|||||||
|
|
||||||
_logger.LogCallInfo(MareHubLogger.Args(aliasOrGid, "Success"));
|
_logger.LogCallInfo(MareHubLogger.Args(aliasOrGid, "Success"));
|
||||||
|
|
||||||
await Clients.User(UserUID).Client_GroupSendFullInfo(new GroupFullInfoDto(group.ToGroupData(), group.Owner.ToUserData(), group.GetGroupPermissions(), newPair.GetGroupPairPermissions(), newPair.GetGroupPairUserInfo())).ConfigureAwait(false);
|
var owner = group.Owner ?? await DbContext.Users.AsNoTracking().SingleAsync(u => u.UID == group.OwnerUID).ConfigureAwait(false);
|
||||||
|
|
||||||
var self = DbContext.Users.Single(u => u.UID == UserUID);
|
await Clients.User(UserUID).Client_GroupSendFullInfo(new GroupFullInfoDto(group.ToGroupData(), owner.ToUserData(), group.GetGroupPermissions(), newPair.GetGroupPairPermissions(), newPair.GetGroupPairUserInfo())
|
||||||
|
{
|
||||||
|
IsTemporary = group.IsTemporary,
|
||||||
|
ExpiresAt = group.ExpiresAt,
|
||||||
|
AutoDetectVisible = group.AutoDetectVisible,
|
||||||
|
PasswordTemporarilyDisabled = group.PasswordTemporarilyDisabled,
|
||||||
|
}).ConfigureAwait(false);
|
||||||
|
|
||||||
|
var self = await DbContext.Users.SingleAsync(u => u.UID == UserUID).ConfigureAwait(false);
|
||||||
|
|
||||||
var groupPairs = await DbContext.GroupPairs.Include(p => p.GroupUser).Where(p => p.GroupGID == group.GID && p.GroupUserUID != UserUID).ToListAsync().ConfigureAwait(false);
|
var groupPairs = await DbContext.GroupPairs.Include(p => p.GroupUser).Where(p => p.GroupGID == group.GID && p.GroupUserUID != UserUID).ToListAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
@@ -527,7 +760,13 @@ public partial class MareHub
|
|||||||
var groups = await DbContext.GroupPairs.Include(g => g.Group).Include(g => g.Group.Owner).Where(g => g.GroupUserUID == UserUID).AsNoTracking().ToListAsync().ConfigureAwait(false);
|
var groups = await DbContext.GroupPairs.Include(g => g.Group).Include(g => g.Group.Owner).Where(g => g.GroupUserUID == UserUID).AsNoTracking().ToListAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
return groups.Select(g => new GroupFullInfoDto(g.Group.ToGroupData(), g.Group.Owner.ToUserData(),
|
return groups.Select(g => new GroupFullInfoDto(g.Group.ToGroupData(), g.Group.Owner.ToUserData(),
|
||||||
g.Group.GetGroupPermissions(), g.GetGroupPairPermissions(), g.GetGroupPairUserInfo())).ToList();
|
g.Group.GetGroupPermissions(), g.GetGroupPairPermissions(), g.GetGroupPairUserInfo())
|
||||||
|
{
|
||||||
|
IsTemporary = g.Group.IsTemporary,
|
||||||
|
ExpiresAt = g.Group.ExpiresAt,
|
||||||
|
AutoDetectVisible = g.Group.AutoDetectVisible,
|
||||||
|
PasswordTemporarilyDisabled = g.Group.PasswordTemporarilyDisabled,
|
||||||
|
}).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize(Policy = "Identified")]
|
[Authorize(Policy = "Identified")]
|
||||||
@@ -559,4 +798,4 @@ public partial class MareHub
|
|||||||
|
|
||||||
_logger.LogCallInfo(MareHubLogger.Args(dto, "Success"));
|
_logger.LogCallInfo(MareHubLogger.Args(dto, "Success"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,264 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MareSynchronos.API.Dto.McdfShare;
|
||||||
|
using MareSynchronosServer.Utils;
|
||||||
|
using MareSynchronosShared.Models;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace MareSynchronosServer.Hubs;
|
||||||
|
|
||||||
|
public partial class MareHub
|
||||||
|
{
|
||||||
|
[Authorize(Policy = "Identified")]
|
||||||
|
public async Task<List<McdfShareEntryDto>> McdfShareGetOwn()
|
||||||
|
{
|
||||||
|
_logger.LogCallInfo();
|
||||||
|
|
||||||
|
var shares = await DbContext.McdfShares.AsNoTracking()
|
||||||
|
.Include(s => s.Owner)
|
||||||
|
.Include(s => s.AllowedIndividuals)
|
||||||
|
.Include(s => s.AllowedSyncshells)
|
||||||
|
.Where(s => s.OwnerUID == UserUID)
|
||||||
|
.OrderByDescending(s => s.CreatedUtc)
|
||||||
|
.ToListAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
|
return shares.Select(s => MapShareEntryDto(s, true)).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Authorize(Policy = "Identified")]
|
||||||
|
public async Task<List<McdfShareEntryDto>> McdfShareGetShared()
|
||||||
|
{
|
||||||
|
_logger.LogCallInfo();
|
||||||
|
|
||||||
|
var userGroups = await DbContext.GroupPairs.AsNoTracking()
|
||||||
|
.Where(p => p.GroupUserUID == UserUID)
|
||||||
|
.Select(p => p.GroupGID.ToUpperInvariant())
|
||||||
|
.ToListAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
|
var shares = await DbContext.McdfShares.AsNoTracking()
|
||||||
|
.Include(s => s.Owner)
|
||||||
|
.Include(s => s.AllowedIndividuals)
|
||||||
|
.Include(s => s.AllowedSyncshells)
|
||||||
|
.Where(s => s.OwnerUID != UserUID)
|
||||||
|
.OrderByDescending(s => s.CreatedUtc)
|
||||||
|
.ToListAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
var accessible = shares.Where(s => ShareAccessibleToUser(s, userGroups) && (!s.ExpiresAtUtc.HasValue || s.ExpiresAtUtc > now)).ToList();
|
||||||
|
|
||||||
|
return accessible.Select(s => MapShareEntryDto(s, false)).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Authorize(Policy = "Identified")]
|
||||||
|
public async Task<bool> McdfShareUpload(McdfShareUploadRequestDto dto)
|
||||||
|
{
|
||||||
|
_logger.LogCallInfo(MareHubLogger.Args(dto.ShareId));
|
||||||
|
|
||||||
|
var normalizedUsers = dto.AllowedIndividuals
|
||||||
|
.Where(s => !string.IsNullOrWhiteSpace(s))
|
||||||
|
.Select(NormalizeUid)
|
||||||
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||||
|
.ToArray();
|
||||||
|
var normalizedGroups = dto.AllowedSyncshells
|
||||||
|
.Where(s => !string.IsNullOrWhiteSpace(s))
|
||||||
|
.Select(NormalizeGroup)
|
||||||
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
var share = await DbContext.McdfShares
|
||||||
|
.Include(s => s.AllowedIndividuals)
|
||||||
|
.Include(s => s.AllowedSyncshells)
|
||||||
|
.SingleOrDefaultAsync(s => s.Id == dto.ShareId)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (share != null && !string.Equals(share.OwnerUID, UserUID, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
if (share == null)
|
||||||
|
{
|
||||||
|
share = new McdfShare
|
||||||
|
{
|
||||||
|
Id = dto.ShareId,
|
||||||
|
OwnerUID = UserUID,
|
||||||
|
CreatedUtc = now,
|
||||||
|
};
|
||||||
|
DbContext.McdfShares.Add(share);
|
||||||
|
}
|
||||||
|
|
||||||
|
share.Description = dto.Description ?? string.Empty;
|
||||||
|
share.CipherData = dto.CipherData ?? Array.Empty<byte>();
|
||||||
|
share.Nonce = dto.Nonce ?? Array.Empty<byte>();
|
||||||
|
share.Salt = dto.Salt ?? Array.Empty<byte>();
|
||||||
|
share.Tag = dto.Tag ?? Array.Empty<byte>();
|
||||||
|
share.ExpiresAtUtc = dto.ExpiresAtUtc;
|
||||||
|
share.UpdatedUtc = now;
|
||||||
|
|
||||||
|
share.AllowedIndividuals.Clear();
|
||||||
|
foreach (var uid in normalizedUsers)
|
||||||
|
{
|
||||||
|
share.AllowedIndividuals.Add(new McdfShareAllowedUser
|
||||||
|
{
|
||||||
|
ShareId = share.Id,
|
||||||
|
AllowedIndividualUid = uid,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
share.AllowedSyncshells.Clear();
|
||||||
|
foreach (var gid in normalizedGroups)
|
||||||
|
{
|
||||||
|
share.AllowedSyncshells.Add(new McdfShareAllowedGroup
|
||||||
|
{
|
||||||
|
ShareId = share.Id,
|
||||||
|
AllowedGroupGid = gid,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await DbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Authorize(Policy = "Identified")]
|
||||||
|
public async Task<McdfShareEntryDto?> McdfShareUpdate(McdfShareUpdateRequestDto dto)
|
||||||
|
{
|
||||||
|
_logger.LogCallInfo(MareHubLogger.Args(dto.ShareId));
|
||||||
|
|
||||||
|
var share = await DbContext.McdfShares
|
||||||
|
.Include(s => s.AllowedIndividuals)
|
||||||
|
.Include(s => s.AllowedSyncshells)
|
||||||
|
.Include(s => s.Owner)
|
||||||
|
.SingleOrDefaultAsync(s => s.Id == dto.ShareId && s.OwnerUID == UserUID)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (share == null) return null;
|
||||||
|
|
||||||
|
share.Description = dto.Description ?? string.Empty;
|
||||||
|
share.ExpiresAtUtc = dto.ExpiresAtUtc;
|
||||||
|
share.UpdatedUtc = DateTime.UtcNow;
|
||||||
|
|
||||||
|
var normalizedUsers = dto.AllowedIndividuals
|
||||||
|
.Where(s => !string.IsNullOrWhiteSpace(s))
|
||||||
|
.Select(NormalizeUid)
|
||||||
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||||
|
.ToArray();
|
||||||
|
var normalizedGroups = dto.AllowedSyncshells
|
||||||
|
.Where(s => !string.IsNullOrWhiteSpace(s))
|
||||||
|
.Select(NormalizeGroup)
|
||||||
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
share.AllowedIndividuals.Clear();
|
||||||
|
foreach (var uid in normalizedUsers)
|
||||||
|
{
|
||||||
|
share.AllowedIndividuals.Add(new McdfShareAllowedUser
|
||||||
|
{
|
||||||
|
ShareId = share.Id,
|
||||||
|
AllowedIndividualUid = uid,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
share.AllowedSyncshells.Clear();
|
||||||
|
foreach (var gid in normalizedGroups)
|
||||||
|
{
|
||||||
|
share.AllowedSyncshells.Add(new McdfShareAllowedGroup
|
||||||
|
{
|
||||||
|
ShareId = share.Id,
|
||||||
|
AllowedGroupGid = gid,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await DbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||||
|
return MapShareEntryDto(share, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Authorize(Policy = "Identified")]
|
||||||
|
public async Task<bool> McdfShareDelete(Guid shareId)
|
||||||
|
{
|
||||||
|
_logger.LogCallInfo(MareHubLogger.Args(shareId));
|
||||||
|
|
||||||
|
var share = await DbContext.McdfShares.SingleOrDefaultAsync(s => s.Id == shareId && s.OwnerUID == UserUID).ConfigureAwait(false);
|
||||||
|
if (share == null) return false;
|
||||||
|
|
||||||
|
DbContext.McdfShares.Remove(share);
|
||||||
|
await DbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Authorize(Policy = "Identified")]
|
||||||
|
public async Task<McdfSharePayloadDto?> McdfShareDownload(Guid shareId)
|
||||||
|
{
|
||||||
|
_logger.LogCallInfo(MareHubLogger.Args(shareId));
|
||||||
|
|
||||||
|
var share = await DbContext.McdfShares
|
||||||
|
.Include(s => s.AllowedIndividuals)
|
||||||
|
.Include(s => s.AllowedSyncshells)
|
||||||
|
.SingleOrDefaultAsync(s => s.Id == shareId)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (share == null) return null;
|
||||||
|
|
||||||
|
var userGroups = await DbContext.GroupPairs.AsNoTracking()
|
||||||
|
.Where(p => p.GroupUserUID == UserUID)
|
||||||
|
.Select(p => p.GroupGID.ToUpperInvariant())
|
||||||
|
.ToListAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
|
bool isOwner = string.Equals(share.OwnerUID, UserUID, StringComparison.Ordinal);
|
||||||
|
if (!isOwner && (!ShareAccessibleToUser(share, userGroups) || (share.ExpiresAtUtc.HasValue && share.ExpiresAtUtc.Value <= DateTime.UtcNow)))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
share.DownloadCount++;
|
||||||
|
await DbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
|
return new McdfSharePayloadDto
|
||||||
|
{
|
||||||
|
ShareId = share.Id,
|
||||||
|
Description = share.Description,
|
||||||
|
CipherData = share.CipherData,
|
||||||
|
Nonce = share.Nonce,
|
||||||
|
Salt = share.Salt,
|
||||||
|
Tag = share.Tag,
|
||||||
|
CreatedUtc = share.CreatedUtc,
|
||||||
|
ExpiresAtUtc = share.ExpiresAtUtc,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string NormalizeUid(string candidate) => candidate.Trim().ToUpperInvariant();
|
||||||
|
private static string NormalizeGroup(string candidate) => candidate.Trim().ToUpperInvariant();
|
||||||
|
|
||||||
|
private static McdfShareEntryDto MapShareEntryDto(McdfShare share, bool isOwner)
|
||||||
|
{
|
||||||
|
return new McdfShareEntryDto
|
||||||
|
{
|
||||||
|
Id = share.Id,
|
||||||
|
Description = share.Description,
|
||||||
|
CreatedUtc = share.CreatedUtc,
|
||||||
|
UpdatedUtc = share.UpdatedUtc,
|
||||||
|
ExpiresAtUtc = share.ExpiresAtUtc,
|
||||||
|
DownloadCount = share.DownloadCount,
|
||||||
|
IsOwner = isOwner,
|
||||||
|
OwnerUid = share.OwnerUID,
|
||||||
|
OwnerAlias = share.Owner?.Alias ?? string.Empty,
|
||||||
|
AllowedIndividuals = share.AllowedIndividuals.Select(i => i.AllowedIndividualUid).OrderBy(s => s, StringComparer.OrdinalIgnoreCase).ToList(),
|
||||||
|
AllowedSyncshells = share.AllowedSyncshells.Select(g => g.AllowedGroupGid).OrderBy(s => s, StringComparer.OrdinalIgnoreCase).ToList(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ShareAccessibleToUser(McdfShare share, IReadOnlyCollection<string> userGroups)
|
||||||
|
{
|
||||||
|
if (string.Equals(share.OwnerUID, UserUID, StringComparison.Ordinal)) return true;
|
||||||
|
|
||||||
|
bool allowedByUser = share.AllowedIndividuals.Any(i => string.Equals(i.AllowedIndividualUid, UserUID, StringComparison.OrdinalIgnoreCase));
|
||||||
|
if (allowedByUser) return true;
|
||||||
|
|
||||||
|
if (share.AllowedSyncshells.Count == 0) return false;
|
||||||
|
var allowedGroups = share.AllowedSyncshells.Select(g => g.AllowedGroupGid).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||||
|
return userGroups.Any(g => allowedGroups.Contains(g));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MareSynchronos.API.Data.Enum;
|
||||||
|
using MareSynchronos.API.Data.Extensions;
|
||||||
|
using MareSynchronos.API.Dto.User;
|
||||||
|
using MareSynchronosServer.Utils;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace MareSynchronosServer.Hubs;
|
||||||
|
|
||||||
|
public partial class MareHub
|
||||||
|
{
|
||||||
|
[Authorize(Policy = "Identified")]
|
||||||
|
public async Task UserSetTypingState(bool isTyping)
|
||||||
|
{
|
||||||
|
_logger.LogCallInfo(MareHubLogger.Args(isTyping));
|
||||||
|
|
||||||
|
var pairedEntries = await GetAllPairedClientsWithPauseState().ConfigureAwait(false);
|
||||||
|
|
||||||
|
var sender = await DbContext.Users.AsNoTracking()
|
||||||
|
.SingleAsync(u => u.UID == UserUID)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
var typingDto = new TypingStateDto(sender.ToUserData(), isTyping, TypingScope.Proximity);
|
||||||
|
|
||||||
|
await Clients.Caller.Client_UserTypingState(typingDto).ConfigureAwait(false);
|
||||||
|
|
||||||
|
var recipients = pairedEntries
|
||||||
|
.Where(p => !p.IsPaused)
|
||||||
|
.Select(p => p.UID)
|
||||||
|
.Distinct(StringComparer.Ordinal)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (recipients.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
await Clients.Users(recipients).Client_UserTypingState(typingDto).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Authorize(Policy = "Identified")]
|
||||||
|
public async Task UserSetTypingState(bool isTyping, TypingScope scope)
|
||||||
|
{
|
||||||
|
_logger.LogCallInfo(MareHubLogger.Args(isTyping, scope));
|
||||||
|
|
||||||
|
var sender = await DbContext.Users.AsNoTracking()
|
||||||
|
.SingleAsync(u => u.UID == UserUID)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
var typingDto = new TypingStateDto(sender.ToUserData(), isTyping, scope);
|
||||||
|
|
||||||
|
await Clients.Caller.Client_UserTypingState(typingDto).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (scope == TypingScope.Party || scope == TypingScope.CrossParty)
|
||||||
|
{
|
||||||
|
_logger.LogCallInfo(MareHubLogger.Args("Typing scope is party-based; server-side party routing not yet implemented, not broadcasting to non-caller."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var pairedEntries = await GetAllPairedClientsWithPauseState().ConfigureAwait(false);
|
||||||
|
var recipients = pairedEntries
|
||||||
|
.Where(p => !p.IsPaused)
|
||||||
|
.Select(p => p.UID)
|
||||||
|
.Distinct(StringComparer.Ordinal)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (recipients.Count == 0) return;
|
||||||
|
|
||||||
|
await Clients.Users(recipients).Client_UserTypingState(typingDto).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -80,7 +80,7 @@ public partial class MareHub : Hub<IMareHub>, IMareHub
|
|||||||
dbUser.LastLoggedIn = DateTime.UtcNow;
|
dbUser.LastLoggedIn = DateTime.UtcNow;
|
||||||
await DbContext.SaveChangesAsync().ConfigureAwait(false);
|
await DbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Information, "Welcome to Umbra! Current Online Users: " + _systemInfoService.SystemInfoDto.OnlineUsers).ConfigureAwait(false);
|
await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Information, "Bienvenue sur UmbraSync ! Utilisateurs en ligne : " + _systemInfoService.SystemInfoDto.OnlineUsers).ConfigureAwait(false);
|
||||||
|
|
||||||
return new ConnectionDto(new UserData(dbUser.UID, string.IsNullOrWhiteSpace(dbUser.Alias) ? null : dbUser.Alias))
|
return new ConnectionDto(new UserData(dbUser.UID, string.IsNullOrWhiteSpace(dbUser.Alias) ? null : dbUser.Alias))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<UserSecretsId>aspnet-MareSynchronosServer-BA82A12A-0B30-463C-801D-B7E81318CD50</UserSecretsId>
|
<UserSecretsId>aspnet-MareSynchronosServer-BA82A12A-0B30-463C-801D-B7E81318CD50</UserSecretsId>
|
||||||
<AssemblyVersion>1.1.0.0</AssemblyVersion>
|
<AssemblyVersion>1.1.0.0</AssemblyVersion>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
@@ -29,13 +29,17 @@
|
|||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.19">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting.Systemd" Version="8.0.1" />
|
<PackageReference Include="Microsoft.Extensions.Hosting.Systemd" Version="8.0.1" />
|
||||||
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="7.7.1" />
|
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="7.7.1" />
|
||||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.11" />
|
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.11" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\UmbraAPI\MareSynchronosAPI\MareSynchronos.API.csproj" />
|
<ProjectReference Include="..\..\MareAPI\MareSynchronosAPI\MareSynchronos.API.csproj" />
|
||||||
<ProjectReference Include="..\MareSynchronosShared\MareSynchronosShared.csproj" />
|
<ProjectReference Include="..\MareSynchronosShared\MareSynchronosShared.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ public class SystemInfoService : IHostedService, IDisposable
|
|||||||
SystemInfoDto = new SystemInfoDto()
|
SystemInfoDto = new SystemInfoDto()
|
||||||
{
|
{
|
||||||
OnlineUsers = onlineUsers,
|
OnlineUsers = onlineUsers,
|
||||||
|
SupportsTypingState = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (_config.IsMain)
|
if (_config.IsMain)
|
||||||
@@ -95,4 +96,4 @@ public class SystemInfoService : IHostedService, IDisposable
|
|||||||
{
|
{
|
||||||
_timer?.Dispose();
|
_timer?.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using MareSynchronosShared.Services;
|
|||||||
using MareSynchronosShared.Utils;
|
using MareSynchronosShared.Utils;
|
||||||
using MareSynchronosShared.Utils.Configuration;
|
using MareSynchronosShared.Utils.Configuration;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace MareSynchronosServer.Services;
|
namespace MareSynchronosServer.Services;
|
||||||
|
|
||||||
@@ -46,20 +47,41 @@ public class UserCleanupService : IHostedService
|
|||||||
await PurgeUnusedAccounts(dbContext).ConfigureAwait(false);
|
await PurgeUnusedAccounts(dbContext).ConfigureAwait(false);
|
||||||
|
|
||||||
await PurgeTempInvites(dbContext).ConfigureAwait(false);
|
await PurgeTempInvites(dbContext).ConfigureAwait(false);
|
||||||
|
await PurgeExpiredTemporaryGroups(dbContext).ConfigureAwait(false);
|
||||||
|
|
||||||
dbContext.SaveChanges();
|
dbContext.SaveChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
var now = DateTime.Now;
|
var span = TimeSpan.FromMinutes(1);
|
||||||
TimeOnly currentTime = new(now.Hour, now.Minute, now.Second);
|
var nextRun = DateTime.Now.Add(span);
|
||||||
TimeOnly futureTime = new(now.Hour, now.Minute - now.Minute % 10, 0);
|
|
||||||
var span = futureTime.AddMinutes(10) - currentTime;
|
|
||||||
|
|
||||||
_logger.LogInformation("User Cleanup Complete, next run at {date}", now.Add(span));
|
_logger.LogInformation("User Cleanup Complete, next run at {date}", nextRun);
|
||||||
await Task.Delay(span, ct).ConfigureAwait(false);
|
await Task.Delay(span, ct).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task PurgeExpiredTemporaryGroups(MareDbContext dbContext)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
var expiredGroups = await dbContext.Groups
|
||||||
|
.Where(g => g.IsTemporary && g.ExpiresAt != null && g.ExpiresAt <= now)
|
||||||
|
.ToListAsync()
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (expiredGroups.Count == 0) return;
|
||||||
|
|
||||||
|
_logger.LogInformation("Cleaning up {count} expired temporary syncshells", expiredGroups.Count);
|
||||||
|
|
||||||
|
dbContext.Groups.RemoveRange(expiredGroups);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "Error during temporary syncshell purge");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task PurgeTempInvites(MareDbContext dbContext)
|
private async Task PurgeTempInvites(MareDbContext dbContext)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ using Prometheus;
|
|||||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using Microsoft.AspNetCore.HttpOverrides;
|
||||||
using StackExchange.Redis;
|
using StackExchange.Redis;
|
||||||
using StackExchange.Redis.Extensions.Core.Configuration;
|
using StackExchange.Redis.Extensions.Core.Configuration;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
@@ -71,7 +72,7 @@ public class Startup
|
|||||||
a.FeatureProviders.Remove(a.FeatureProviders.OfType<ControllerFeatureProvider>().First());
|
a.FeatureProviders.Remove(a.FeatureProviders.OfType<ControllerFeatureProvider>().First());
|
||||||
if (mareConfig.GetValue<Uri>(nameof(ServerConfiguration.MainServerAddress), defaultValue: null) == null)
|
if (mareConfig.GetValue<Uri>(nameof(ServerConfiguration.MainServerAddress), defaultValue: null) == null)
|
||||||
{
|
{
|
||||||
a.FeatureProviders.Add(new AllowedControllersFeatureProvider(typeof(MareServerConfigurationController), typeof(MareBaseConfigurationController), typeof(ClientMessageController), typeof(MainController)));
|
a.FeatureProviders.Add(new AllowedControllersFeatureProvider(typeof(MareServerConfigurationController), typeof(MareBaseConfigurationController), typeof(ClientMessageController), typeof(MainController), typeof(DiscoveryNotifyController)));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -198,6 +199,21 @@ public class Startup
|
|||||||
ValidateIssuerSigningKey = true,
|
ValidateIssuerSigningKey = true,
|
||||||
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(config.GetValue<string>(nameof(MareConfigurationBase.Jwt)))),
|
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(config.GetValue<string>(nameof(MareConfigurationBase.Jwt)))),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Allow SignalR WebSocket connections to authenticate via access_token query on the hub path
|
||||||
|
options.Events = new JwtBearerEvents
|
||||||
|
{
|
||||||
|
OnMessageReceived = context =>
|
||||||
|
{
|
||||||
|
var accessToken = context.Request.Query["access_token"].ToString();
|
||||||
|
var path = context.HttpContext.Request.Path;
|
||||||
|
if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments(IMareHub.Path))
|
||||||
|
{
|
||||||
|
context.Token = accessToken;
|
||||||
|
}
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
services.AddAuthentication(o =>
|
services.AddAuthentication(o =>
|
||||||
@@ -307,6 +323,12 @@ public class Startup
|
|||||||
|
|
||||||
var config = app.ApplicationServices.GetRequiredService<IConfigurationService<MareConfigurationBase>>();
|
var config = app.ApplicationServices.GetRequiredService<IConfigurationService<MareConfigurationBase>>();
|
||||||
|
|
||||||
|
// Respect X-Forwarded-* headers from reverse proxies (required for correct scheme/host)
|
||||||
|
app.UseForwardedHeaders(new ForwardedHeadersOptions
|
||||||
|
{
|
||||||
|
ForwardedHeaders = ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost | ForwardedHeaders.XForwardedFor
|
||||||
|
});
|
||||||
|
|
||||||
app.UseIpRateLimiting();
|
app.UseIpRateLimiting();
|
||||||
|
|
||||||
app.UseRouting();
|
app.UseRouting();
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\UmbraAPI\MareSynchronosAPI\MareSynchronos.API.csproj" />
|
<ProjectReference Include="..\..\MareAPI\MareSynchronosAPI\MareSynchronos.API.csproj" />
|
||||||
<ProjectReference Include="..\MareSynchronosShared\MareSynchronosShared.csproj" />
|
<ProjectReference Include="..\MareSynchronosShared\MareSynchronosShared.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -51,6 +51,9 @@ public class MareDbContext : DbContext
|
|||||||
public DbSet<CharaDataOriginalFile> CharaDataOriginalFiles { get; set; }
|
public DbSet<CharaDataOriginalFile> CharaDataOriginalFiles { get; set; }
|
||||||
public DbSet<CharaDataPose> CharaDataPoses { get; set; }
|
public DbSet<CharaDataPose> CharaDataPoses { get; set; }
|
||||||
public DbSet<CharaDataAllowance> CharaDataAllowances { get; set; }
|
public DbSet<CharaDataAllowance> CharaDataAllowances { get; set; }
|
||||||
|
public DbSet<McdfShare> McdfShares { get; set; }
|
||||||
|
public DbSet<McdfShareAllowedUser> McdfShareAllowedUsers { get; set; }
|
||||||
|
public DbSet<McdfShareAllowedGroup> McdfShareAllowedGroups { get; set; }
|
||||||
|
|
||||||
protected override void OnModelCreating(ModelBuilder mb)
|
protected override void OnModelCreating(ModelBuilder mb)
|
||||||
{
|
{
|
||||||
@@ -127,5 +130,28 @@ public class MareDbContext : DbContext
|
|||||||
mb.Entity<CharaDataAllowance>().HasIndex(c => c.ParentId);
|
mb.Entity<CharaDataAllowance>().HasIndex(c => c.ParentId);
|
||||||
mb.Entity<CharaDataAllowance>().HasOne(u => u.AllowedGroup).WithMany().HasForeignKey(u => u.AllowedGroupGID).OnDelete(DeleteBehavior.Cascade);
|
mb.Entity<CharaDataAllowance>().HasOne(u => u.AllowedGroup).WithMany().HasForeignKey(u => u.AllowedGroupGID).OnDelete(DeleteBehavior.Cascade);
|
||||||
mb.Entity<CharaDataAllowance>().HasOne(u => u.AllowedUser).WithMany().HasForeignKey(u => u.AllowedUserUID).OnDelete(DeleteBehavior.Cascade);
|
mb.Entity<CharaDataAllowance>().HasOne(u => u.AllowedUser).WithMany().HasForeignKey(u => u.AllowedUserUID).OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
mb.Entity<McdfShare>().ToTable("mcdf_shares");
|
||||||
|
mb.Entity<McdfShare>().HasIndex(s => s.OwnerUID);
|
||||||
|
mb.Entity<McdfShare>().HasOne(s => s.Owner).WithMany().HasForeignKey(s => s.OwnerUID).OnDelete(DeleteBehavior.Cascade);
|
||||||
|
mb.Entity<McdfShare>().Property(s => s.Description).HasColumnType("text");
|
||||||
|
mb.Entity<McdfShare>().Property(s => s.CipherData).HasColumnType("bytea");
|
||||||
|
mb.Entity<McdfShare>().Property(s => s.Nonce).HasColumnType("bytea");
|
||||||
|
mb.Entity<McdfShare>().Property(s => s.Salt).HasColumnType("bytea");
|
||||||
|
mb.Entity<McdfShare>().Property(s => s.Tag).HasColumnType("bytea");
|
||||||
|
mb.Entity<McdfShare>().Property(s => s.CreatedUtc).HasColumnType("timestamp with time zone");
|
||||||
|
mb.Entity<McdfShare>().Property(s => s.UpdatedUtc).HasColumnType("timestamp with time zone");
|
||||||
|
mb.Entity<McdfShare>().Property(s => s.ExpiresAtUtc).HasColumnType("timestamp with time zone");
|
||||||
|
mb.Entity<McdfShare>().Property(s => s.DownloadCount).HasColumnType("integer");
|
||||||
|
mb.Entity<McdfShare>().HasMany(s => s.AllowedIndividuals).WithOne(a => a.Share).HasForeignKey(a => a.ShareId).OnDelete(DeleteBehavior.Cascade);
|
||||||
|
mb.Entity<McdfShare>().HasMany(s => s.AllowedSyncshells).WithOne(a => a.Share).HasForeignKey(a => a.ShareId).OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
mb.Entity<McdfShareAllowedUser>().ToTable("mcdf_share_allowed_users");
|
||||||
|
mb.Entity<McdfShareAllowedUser>().HasKey(u => new { u.ShareId, u.AllowedIndividualUid });
|
||||||
|
mb.Entity<McdfShareAllowedUser>().HasIndex(u => u.AllowedIndividualUid);
|
||||||
|
|
||||||
|
mb.Entity<McdfShareAllowedGroup>().ToTable("mcdf_share_allowed_groups");
|
||||||
|
mb.Entity<McdfShareAllowedGroup>().HasKey(g => new { g.ShareId, g.AllowedGroupGid });
|
||||||
|
mb.Entity<McdfShareAllowedGroup>().HasIndex(g => g.AllowedGroupGid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\UmbraAPI\MareSynchronosAPI\MareSynchronos.API.csproj" />
|
<ProjectReference Include="..\..\MareAPI\MareSynchronosAPI\MareSynchronos.API.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace MareSynchronosServer.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class GroupTemporary : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<DateTime>(
|
||||||
|
name: "expires_at",
|
||||||
|
table: "groups",
|
||||||
|
type: "timestamp with time zone",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<bool>(
|
||||||
|
name: "is_temporary",
|
||||||
|
table: "groups",
|
||||||
|
type: "boolean",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "expires_at",
|
||||||
|
table: "groups");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "is_temporary",
|
||||||
|
table: "groups");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1150
MareSynchronosServer/MareSynchronosShared/Migrations/20251102085631_SyncshellDiscovery.Designer.cs
generated
Normal file
1150
MareSynchronosServer/MareSynchronosShared/Migrations/20251102085631_SyncshellDiscovery.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,40 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace MareSynchronosServer.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class SyncshellDiscovery : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<bool>(
|
||||||
|
name: "auto_detect_visible",
|
||||||
|
table: "groups",
|
||||||
|
type: "boolean",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: false);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<bool>(
|
||||||
|
name: "password_temporarily_disabled",
|
||||||
|
table: "groups",
|
||||||
|
type: "boolean",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "auto_detect_visible",
|
||||||
|
table: "groups");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "password_temporarily_disabled",
|
||||||
|
table: "groups");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1150
MareSynchronosServer/MareSynchronosShared/Migrations/20251102085639_McdfShare.Designer.cs
generated
Normal file
1150
MareSynchronosServer/MareSynchronosShared/Migrations/20251102085639_McdfShare.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,106 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace MareSynchronosServer.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class McdfShare : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "mcdf_shares",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
owner_uid = table.Column<string>(type: "character varying(10)", nullable: false),
|
||||||
|
description = table.Column<string>(type: "text", nullable: false),
|
||||||
|
cipher_data = table.Column<byte[]>(type: "bytea", nullable: false),
|
||||||
|
nonce = table.Column<byte[]>(type: "bytea", nullable: false),
|
||||||
|
salt = table.Column<byte[]>(type: "bytea", nullable: false),
|
||||||
|
tag = table.Column<byte[]>(type: "bytea", nullable: false),
|
||||||
|
created_utc = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||||
|
updated_utc = table.Column<DateTime?>(type: "timestamp with time zone", nullable: true),
|
||||||
|
expires_at_utc = table.Column<DateTime?>(type: "timestamp with time zone", nullable: true),
|
||||||
|
download_count = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("pk_mcdf_shares", x => x.id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "fk_mcdf_shares_users_owner_uid",
|
||||||
|
column: x => x.owner_uid,
|
||||||
|
principalTable: "users",
|
||||||
|
principalColumn: "uid",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "mcdf_share_allowed_groups",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
share_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
allowed_group_gid = table.Column<string>(type: "character varying(20)", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("pk_mcdf_share_allowed_groups", x => new { x.share_id, x.allowed_group_gid });
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "fk_mcdf_share_allowed_groups_mcdf_shares_share_id",
|
||||||
|
column: x => x.share_id,
|
||||||
|
principalTable: "mcdf_shares",
|
||||||
|
principalColumn: "id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "mcdf_share_allowed_users",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
share_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
allowed_individual_uid = table.Column<string>(type: "character varying(10)", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("pk_mcdf_share_allowed_users", x => new { x.share_id, x.allowed_individual_uid });
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "fk_mcdf_share_allowed_users_mcdf_shares_share_id",
|
||||||
|
column: x => x.share_id,
|
||||||
|
principalTable: "mcdf_shares",
|
||||||
|
principalColumn: "id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_mcdf_share_allowed_groups_allowed_group_gid",
|
||||||
|
table: "mcdf_share_allowed_groups",
|
||||||
|
column: "allowed_group_gid");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_mcdf_share_allowed_users_allowed_individual_uid",
|
||||||
|
table: "mcdf_share_allowed_users",
|
||||||
|
column: "allowed_individual_uid");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_mcdf_shares_owner_uid",
|
||||||
|
table: "mcdf_shares",
|
||||||
|
column: "owner_uid");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "mcdf_share_allowed_groups");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "mcdf_share_allowed_users");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "mcdf_shares");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,7 +17,7 @@ namespace MareSynchronosServer.Migrations
|
|||||||
{
|
{
|
||||||
#pragma warning disable 612, 618
|
#pragma warning disable 612, 618
|
||||||
modelBuilder
|
modelBuilder
|
||||||
.HasAnnotation("ProductVersion", "8.0.4")
|
.HasAnnotation("ProductVersion", "8.0.19")
|
||||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||||
|
|
||||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
@@ -438,6 +438,10 @@ namespace MareSynchronosServer.Migrations
|
|||||||
.HasColumnType("character varying(50)")
|
.HasColumnType("character varying(50)")
|
||||||
.HasColumnName("alias");
|
.HasColumnName("alias");
|
||||||
|
|
||||||
|
b.Property<bool>("AutoDetectVisible")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("auto_detect_visible");
|
||||||
|
|
||||||
b.Property<bool>("DisableAnimations")
|
b.Property<bool>("DisableAnimations")
|
||||||
.HasColumnType("boolean")
|
.HasColumnType("boolean")
|
||||||
.HasColumnName("disable_animations");
|
.HasColumnName("disable_animations");
|
||||||
@@ -450,6 +454,10 @@ namespace MareSynchronosServer.Migrations
|
|||||||
.HasColumnType("boolean")
|
.HasColumnType("boolean")
|
||||||
.HasColumnName("disable_vfx");
|
.HasColumnName("disable_vfx");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("ExpiresAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("expires_at");
|
||||||
|
|
||||||
b.Property<string>("HashedPassword")
|
b.Property<string>("HashedPassword")
|
||||||
.HasColumnType("text")
|
.HasColumnType("text")
|
||||||
.HasColumnName("hashed_password");
|
.HasColumnName("hashed_password");
|
||||||
@@ -458,10 +466,18 @@ namespace MareSynchronosServer.Migrations
|
|||||||
.HasColumnType("boolean")
|
.HasColumnType("boolean")
|
||||||
.HasColumnName("invites_enabled");
|
.HasColumnName("invites_enabled");
|
||||||
|
|
||||||
|
b.Property<bool>("IsTemporary")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("is_temporary");
|
||||||
|
|
||||||
b.Property<string>("OwnerUID")
|
b.Property<string>("OwnerUID")
|
||||||
.HasColumnType("character varying(10)")
|
.HasColumnType("character varying(10)")
|
||||||
.HasColumnName("owner_uid");
|
.HasColumnName("owner_uid");
|
||||||
|
|
||||||
|
b.Property<bool>("PasswordTemporarilyDisabled")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("password_temporarily_disabled");
|
||||||
|
|
||||||
b.HasKey("GID")
|
b.HasKey("GID")
|
||||||
.HasName("pk_groups");
|
.HasName("pk_groups");
|
||||||
|
|
||||||
@@ -615,6 +631,103 @@ namespace MareSynchronosServer.Migrations
|
|||||||
b.ToTable("lodestone_auth", (string)null);
|
b.ToTable("lodestone_auth", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MareSynchronosShared.Models.McdfShare", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<byte[]>("CipherData")
|
||||||
|
.HasColumnType("bytea")
|
||||||
|
.HasColumnName("cipher_data");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("created_utc");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasColumnType("text")
|
||||||
|
.HasColumnName("description");
|
||||||
|
|
||||||
|
b.Property<int>("DownloadCount")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("download_count");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("ExpiresAtUtc")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("expires_at_utc");
|
||||||
|
|
||||||
|
b.Property<byte[]>("Nonce")
|
||||||
|
.HasColumnType("bytea")
|
||||||
|
.HasColumnName("nonce");
|
||||||
|
|
||||||
|
b.Property<string>("OwnerUID")
|
||||||
|
.HasMaxLength(10)
|
||||||
|
.HasColumnType("character varying(10)")
|
||||||
|
.HasColumnName("owner_uid");
|
||||||
|
|
||||||
|
b.Property<byte[]>("Salt")
|
||||||
|
.HasColumnType("bytea")
|
||||||
|
.HasColumnName("salt");
|
||||||
|
|
||||||
|
b.Property<byte[]>("Tag")
|
||||||
|
.HasColumnType("bytea")
|
||||||
|
.HasColumnName("tag");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedUtc")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("updated_utc");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk_mcdf_shares");
|
||||||
|
|
||||||
|
b.HasIndex("OwnerUID")
|
||||||
|
.HasDatabaseName("ix_mcdf_shares_owner_uid");
|
||||||
|
|
||||||
|
b.ToTable("mcdf_shares", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MareSynchronosShared.Models.McdfShareAllowedGroup", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("ShareId")
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("share_id");
|
||||||
|
|
||||||
|
b.Property<string>("AllowedGroupGid")
|
||||||
|
.HasMaxLength(20)
|
||||||
|
.HasColumnType("character varying(20)")
|
||||||
|
.HasColumnName("allowed_group_gid");
|
||||||
|
|
||||||
|
b.HasKey("ShareId", "AllowedGroupGid")
|
||||||
|
.HasName("pk_mcdf_share_allowed_groups");
|
||||||
|
|
||||||
|
b.HasIndex("AllowedGroupGid")
|
||||||
|
.HasDatabaseName("ix_mcdf_share_allowed_groups_allowed_group_gid");
|
||||||
|
|
||||||
|
b.ToTable("mcdf_share_allowed_groups", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MareSynchronosShared.Models.McdfShareAllowedUser", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("ShareId")
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("share_id");
|
||||||
|
|
||||||
|
b.Property<string>("AllowedIndividualUid")
|
||||||
|
.HasMaxLength(10)
|
||||||
|
.HasColumnType("character varying(10)")
|
||||||
|
.HasColumnName("allowed_individual_uid");
|
||||||
|
|
||||||
|
b.HasKey("ShareId", "AllowedIndividualUid")
|
||||||
|
.HasName("pk_mcdf_share_allowed_users");
|
||||||
|
|
||||||
|
b.HasIndex("AllowedIndividualUid")
|
||||||
|
.HasDatabaseName("ix_mcdf_share_allowed_users_allowed_individual_uid");
|
||||||
|
|
||||||
|
b.ToTable("mcdf_share_allowed_users", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("MareSynchronosShared.Models.User", b =>
|
modelBuilder.Entity("MareSynchronosShared.Models.User", b =>
|
||||||
{
|
{
|
||||||
b.Property<string>("UID")
|
b.Property<string>("UID")
|
||||||
@@ -945,6 +1058,41 @@ namespace MareSynchronosServer.Migrations
|
|||||||
b.Navigation("User");
|
b.Navigation("User");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MareSynchronosShared.Models.McdfShare", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("MareSynchronosShared.Models.User", "Owner")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("OwnerUID")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.HasConstraintName("fk_mcdf_shares_users_owner_uid");
|
||||||
|
|
||||||
|
b.Navigation("Owner");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MareSynchronosShared.Models.McdfShareAllowedGroup", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("MareSynchronosShared.Models.McdfShare", "Share")
|
||||||
|
.WithMany("AllowedSyncshells")
|
||||||
|
.HasForeignKey("ShareId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired()
|
||||||
|
.HasConstraintName("fk_mcdf_share_allowed_groups_mcdf_shares_share_id");
|
||||||
|
|
||||||
|
b.Navigation("Share");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MareSynchronosShared.Models.McdfShareAllowedUser", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("MareSynchronosShared.Models.McdfShare", "Share")
|
||||||
|
.WithMany("AllowedIndividuals")
|
||||||
|
.HasForeignKey("ShareId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired()
|
||||||
|
.HasConstraintName("fk_mcdf_share_allowed_users_mcdf_shares_share_id");
|
||||||
|
|
||||||
|
b.Navigation("Share");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("MareSynchronosShared.Models.UserProfileData", b =>
|
modelBuilder.Entity("MareSynchronosShared.Models.UserProfileData", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("MareSynchronosShared.Models.User", "User")
|
b.HasOne("MareSynchronosShared.Models.User", "User")
|
||||||
@@ -986,6 +1134,13 @@ namespace MareSynchronosServer.Migrations
|
|||||||
|
|
||||||
b.Navigation("Poses");
|
b.Navigation("Poses");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("MareSynchronosShared.Models.McdfShare", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("AllowedIndividuals");
|
||||||
|
|
||||||
|
b.Navigation("AllowedSyncshells");
|
||||||
|
});
|
||||||
#pragma warning restore 612, 618
|
#pragma warning restore 612, 618
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
namespace MareSynchronosShared.Models;
|
namespace MareSynchronosShared.Models;
|
||||||
|
|
||||||
@@ -16,4 +17,8 @@ public class Group
|
|||||||
public bool DisableSounds { get; set; }
|
public bool DisableSounds { get; set; }
|
||||||
public bool DisableAnimations { get; set; }
|
public bool DisableAnimations { get; set; }
|
||||||
public bool DisableVFX { get; set; }
|
public bool DisableVFX { get; set; }
|
||||||
|
public bool IsTemporary { get; set; }
|
||||||
|
public DateTime? ExpiresAt { get; set; }
|
||||||
|
public bool AutoDetectVisible { get; set; }
|
||||||
|
public bool PasswordTemporarilyDisabled { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace MareSynchronosShared.Models;
|
||||||
|
|
||||||
|
public class McdfShare
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
[MaxLength(10)]
|
||||||
|
public string OwnerUID { get; set; } = string.Empty;
|
||||||
|
public User Owner { get; set; } = null!;
|
||||||
|
public string Description { get; set; } = string.Empty;
|
||||||
|
public byte[] CipherData { get; set; } = Array.Empty<byte>();
|
||||||
|
public byte[] Nonce { get; set; } = Array.Empty<byte>();
|
||||||
|
public byte[] Salt { get; set; } = Array.Empty<byte>();
|
||||||
|
public byte[] Tag { get; set; } = Array.Empty<byte>();
|
||||||
|
public DateTime CreatedUtc { get; set; }
|
||||||
|
public DateTime? UpdatedUtc { get; set; }
|
||||||
|
public DateTime? ExpiresAtUtc { get; set; }
|
||||||
|
public int DownloadCount { get; set; }
|
||||||
|
public ICollection<McdfShareAllowedUser> AllowedIndividuals { get; set; } = new HashSet<McdfShareAllowedUser>();
|
||||||
|
public ICollection<McdfShareAllowedGroup> AllowedSyncshells { get; set; } = new HashSet<McdfShareAllowedGroup>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class McdfShareAllowedUser
|
||||||
|
{
|
||||||
|
public Guid ShareId { get; set; }
|
||||||
|
public McdfShare Share { get; set; } = null!;
|
||||||
|
[MaxLength(10)]
|
||||||
|
public string AllowedIndividualUid { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class McdfShareAllowedGroup
|
||||||
|
{
|
||||||
|
public Guid ShareId { get; set; }
|
||||||
|
public McdfShare Share { get; set; } = null!;
|
||||||
|
[MaxLength(20)]
|
||||||
|
public string AllowedGroupGid { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\UmbraAPI\MareSynchronosAPI\MareSynchronos.API.csproj" />
|
<ProjectReference Include="..\..\MareAPI\MareSynchronosAPI\MareSynchronos.API.csproj" />
|
||||||
<ProjectReference Include="..\MareSynchronosShared\MareSynchronosShared.csproj" />
|
<ProjectReference Include="..\MareSynchronosShared\MareSynchronosShared.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,9 @@
|
|||||||
# UmbraServer
|
# UmbraServer
|
||||||
|
|
||||||
|
🇫🇷
|
||||||
|
Ce projet est basé sur Mare Synchronos de DarkArchon. Le code original est sous licence MIT ; voir le fichier
|
||||||
|
LICENSE_MIT pour plus de détails. À partir de ce commit, le code est sous licence AGPL.
|
||||||
|
|
||||||
|
🇬🇧
|
||||||
|
This project is based on Mare Synchronos by DarkArchon. Original code is licensed under the MIT License; see the
|
||||||
|
LICENSE_MIT file for details. From this commit onwards, the code is licensed under AGPL.
|
||||||
350
UmbraAPI/.gitignore
vendored
350
UmbraAPI/.gitignore
vendored
@@ -1,350 +0,0 @@
|
|||||||
## Ignore Visual Studio temporary files, build results, and
|
|
||||||
## files generated by popular Visual Studio add-ons.
|
|
||||||
##
|
|
||||||
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
|
||||||
|
|
||||||
# User-specific files
|
|
||||||
*.rsuser
|
|
||||||
*.suo
|
|
||||||
*.user
|
|
||||||
*.userosscache
|
|
||||||
*.sln.docstates
|
|
||||||
|
|
||||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
|
||||||
*.userprefs
|
|
||||||
|
|
||||||
# Mono auto generated files
|
|
||||||
mono_crash.*
|
|
||||||
|
|
||||||
# Build results
|
|
||||||
[Dd]ebug/
|
|
||||||
[Dd]ebugPublic/
|
|
||||||
[Rr]elease/
|
|
||||||
[Rr]eleases/
|
|
||||||
x64/
|
|
||||||
x86/
|
|
||||||
[Aa][Rr][Mm]/
|
|
||||||
[Aa][Rr][Mm]64/
|
|
||||||
bld/
|
|
||||||
[Bb]in/
|
|
||||||
[Oo]bj/
|
|
||||||
[Ll]og/
|
|
||||||
[Ll]ogs/
|
|
||||||
|
|
||||||
# Visual Studio 2015/2017 cache/options directory
|
|
||||||
.vs/
|
|
||||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
|
||||||
#wwwroot/
|
|
||||||
|
|
||||||
# Visual Studio 2017 auto generated files
|
|
||||||
Generated\ Files/
|
|
||||||
|
|
||||||
# MSTest test Results
|
|
||||||
[Tt]est[Rr]esult*/
|
|
||||||
[Bb]uild[Ll]og.*
|
|
||||||
|
|
||||||
# NUnit
|
|
||||||
*.VisualState.xml
|
|
||||||
TestResult.xml
|
|
||||||
nunit-*.xml
|
|
||||||
|
|
||||||
# Build Results of an ATL Project
|
|
||||||
[Dd]ebugPS/
|
|
||||||
[Rr]eleasePS/
|
|
||||||
dlldata.c
|
|
||||||
|
|
||||||
# Benchmark Results
|
|
||||||
BenchmarkDotNet.Artifacts/
|
|
||||||
|
|
||||||
# .NET Core
|
|
||||||
project.lock.json
|
|
||||||
project.fragment.lock.json
|
|
||||||
artifacts/
|
|
||||||
|
|
||||||
# StyleCop
|
|
||||||
StyleCopReport.xml
|
|
||||||
|
|
||||||
# Files built by Visual Studio
|
|
||||||
*_i.c
|
|
||||||
*_p.c
|
|
||||||
*_h.h
|
|
||||||
*.ilk
|
|
||||||
*.meta
|
|
||||||
*.obj
|
|
||||||
*.iobj
|
|
||||||
*.pch
|
|
||||||
*.pdb
|
|
||||||
*.ipdb
|
|
||||||
*.pgc
|
|
||||||
*.pgd
|
|
||||||
*.rsp
|
|
||||||
*.sbr
|
|
||||||
*.tlb
|
|
||||||
*.tli
|
|
||||||
*.tlh
|
|
||||||
*.tmp
|
|
||||||
*.tmp_proj
|
|
||||||
*_wpftmp.csproj
|
|
||||||
*.log
|
|
||||||
*.vspscc
|
|
||||||
*.vssscc
|
|
||||||
.builds
|
|
||||||
*.pidb
|
|
||||||
*.svclog
|
|
||||||
*.scc
|
|
||||||
|
|
||||||
# Chutzpah Test files
|
|
||||||
_Chutzpah*
|
|
||||||
|
|
||||||
# Visual C++ cache files
|
|
||||||
ipch/
|
|
||||||
*.aps
|
|
||||||
*.ncb
|
|
||||||
*.opendb
|
|
||||||
*.opensdf
|
|
||||||
*.sdf
|
|
||||||
*.cachefile
|
|
||||||
*.VC.db
|
|
||||||
*.VC.VC.opendb
|
|
||||||
|
|
||||||
# Visual Studio profiler
|
|
||||||
*.psess
|
|
||||||
*.vsp
|
|
||||||
*.vspx
|
|
||||||
*.sap
|
|
||||||
|
|
||||||
# Visual Studio Trace Files
|
|
||||||
*.e2e
|
|
||||||
|
|
||||||
# TFS 2012 Local Workspace
|
|
||||||
$tf/
|
|
||||||
|
|
||||||
# Guidance Automation Toolkit
|
|
||||||
*.gpState
|
|
||||||
|
|
||||||
# ReSharper is a .NET coding add-in
|
|
||||||
_ReSharper*/
|
|
||||||
*.[Rr]e[Ss]harper
|
|
||||||
*.DotSettings.user
|
|
||||||
|
|
||||||
# TeamCity is a build add-in
|
|
||||||
_TeamCity*
|
|
||||||
|
|
||||||
# DotCover is a Code Coverage Tool
|
|
||||||
*.dotCover
|
|
||||||
|
|
||||||
# AxoCover is a Code Coverage Tool
|
|
||||||
.axoCover/*
|
|
||||||
!.axoCover/settings.json
|
|
||||||
|
|
||||||
# Visual Studio code coverage results
|
|
||||||
*.coverage
|
|
||||||
*.coveragexml
|
|
||||||
|
|
||||||
# NCrunch
|
|
||||||
_NCrunch_*
|
|
||||||
.*crunch*.local.xml
|
|
||||||
nCrunchTemp_*
|
|
||||||
|
|
||||||
# MightyMoose
|
|
||||||
*.mm.*
|
|
||||||
AutoTest.Net/
|
|
||||||
|
|
||||||
# Web workbench (sass)
|
|
||||||
.sass-cache/
|
|
||||||
|
|
||||||
# Installshield output folder
|
|
||||||
[Ee]xpress/
|
|
||||||
|
|
||||||
# DocProject is a documentation generator add-in
|
|
||||||
DocProject/buildhelp/
|
|
||||||
DocProject/Help/*.HxT
|
|
||||||
DocProject/Help/*.HxC
|
|
||||||
DocProject/Help/*.hhc
|
|
||||||
DocProject/Help/*.hhk
|
|
||||||
DocProject/Help/*.hhp
|
|
||||||
DocProject/Help/Html2
|
|
||||||
DocProject/Help/html
|
|
||||||
|
|
||||||
# Click-Once directory
|
|
||||||
publish/
|
|
||||||
|
|
||||||
# Publish Web Output
|
|
||||||
*.[Pp]ublish.xml
|
|
||||||
*.azurePubxml
|
|
||||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
|
||||||
# but database connection strings (with potential passwords) will be unencrypted
|
|
||||||
*.pubxml
|
|
||||||
*.publishproj
|
|
||||||
|
|
||||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
|
||||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
|
||||||
# in these scripts will be unencrypted
|
|
||||||
PublishScripts/
|
|
||||||
|
|
||||||
# NuGet Packages
|
|
||||||
*.nupkg
|
|
||||||
# NuGet Symbol Packages
|
|
||||||
*.snupkg
|
|
||||||
# The packages folder can be ignored because of Package Restore
|
|
||||||
**/[Pp]ackages/*
|
|
||||||
# except build/, which is used as an MSBuild target.
|
|
||||||
!**/[Pp]ackages/build/
|
|
||||||
# Uncomment if necessary however generally it will be regenerated when needed
|
|
||||||
#!**/[Pp]ackages/repositories.config
|
|
||||||
# NuGet v3's project.json files produces more ignorable files
|
|
||||||
*.nuget.props
|
|
||||||
*.nuget.targets
|
|
||||||
|
|
||||||
# Microsoft Azure Build Output
|
|
||||||
csx/
|
|
||||||
*.build.csdef
|
|
||||||
|
|
||||||
# Microsoft Azure Emulator
|
|
||||||
ecf/
|
|
||||||
rcf/
|
|
||||||
|
|
||||||
# Windows Store app package directories and files
|
|
||||||
AppPackages/
|
|
||||||
BundleArtifacts/
|
|
||||||
Package.StoreAssociation.xml
|
|
||||||
_pkginfo.txt
|
|
||||||
*.appx
|
|
||||||
*.appxbundle
|
|
||||||
*.appxupload
|
|
||||||
|
|
||||||
# Visual Studio cache files
|
|
||||||
# files ending in .cache can be ignored
|
|
||||||
*.[Cc]ache
|
|
||||||
# but keep track of directories ending in .cache
|
|
||||||
!?*.[Cc]ache/
|
|
||||||
|
|
||||||
# Others
|
|
||||||
ClientBin/
|
|
||||||
~$*
|
|
||||||
*~
|
|
||||||
*.dbmdl
|
|
||||||
*.dbproj.schemaview
|
|
||||||
*.jfm
|
|
||||||
*.pfx
|
|
||||||
*.publishsettings
|
|
||||||
orleans.codegen.cs
|
|
||||||
|
|
||||||
# Including strong name files can present a security risk
|
|
||||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
|
||||||
#*.snk
|
|
||||||
|
|
||||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
|
||||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
|
||||||
#bower_components/
|
|
||||||
|
|
||||||
# RIA/Silverlight projects
|
|
||||||
Generated_Code/
|
|
||||||
|
|
||||||
# Backup & report files from converting an old project file
|
|
||||||
# to a newer Visual Studio version. Backup files are not needed,
|
|
||||||
# because we have git ;-)
|
|
||||||
_UpgradeReport_Files/
|
|
||||||
Backup*/
|
|
||||||
UpgradeLog*.XML
|
|
||||||
UpgradeLog*.htm
|
|
||||||
ServiceFabricBackup/
|
|
||||||
*.rptproj.bak
|
|
||||||
|
|
||||||
# SQL Server files
|
|
||||||
*.mdf
|
|
||||||
*.ldf
|
|
||||||
*.ndf
|
|
||||||
|
|
||||||
# Business Intelligence projects
|
|
||||||
*.rdl.data
|
|
||||||
*.bim.layout
|
|
||||||
*.bim_*.settings
|
|
||||||
*.rptproj.rsuser
|
|
||||||
*- [Bb]ackup.rdl
|
|
||||||
*- [Bb]ackup ([0-9]).rdl
|
|
||||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
|
||||||
|
|
||||||
# Microsoft Fakes
|
|
||||||
FakesAssemblies/
|
|
||||||
|
|
||||||
# GhostDoc plugin setting file
|
|
||||||
*.GhostDoc.xml
|
|
||||||
|
|
||||||
# Node.js Tools for Visual Studio
|
|
||||||
.ntvs_analysis.dat
|
|
||||||
node_modules/
|
|
||||||
|
|
||||||
# Visual Studio 6 build log
|
|
||||||
*.plg
|
|
||||||
|
|
||||||
# Visual Studio 6 workspace options file
|
|
||||||
*.opt
|
|
||||||
|
|
||||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
|
||||||
*.vbw
|
|
||||||
|
|
||||||
# Visual Studio LightSwitch build output
|
|
||||||
**/*.HTMLClient/GeneratedArtifacts
|
|
||||||
**/*.DesktopClient/GeneratedArtifacts
|
|
||||||
**/*.DesktopClient/ModelManifest.xml
|
|
||||||
**/*.Server/GeneratedArtifacts
|
|
||||||
**/*.Server/ModelManifest.xml
|
|
||||||
_Pvt_Extensions
|
|
||||||
|
|
||||||
# Paket dependency manager
|
|
||||||
.paket/paket.exe
|
|
||||||
paket-files/
|
|
||||||
|
|
||||||
# FAKE - F# Make
|
|
||||||
.fake/
|
|
||||||
|
|
||||||
# CodeRush personal settings
|
|
||||||
.cr/personal
|
|
||||||
|
|
||||||
# Python Tools for Visual Studio (PTVS)
|
|
||||||
__pycache__/
|
|
||||||
*.pyc
|
|
||||||
|
|
||||||
# Cake - Uncomment if you are using it
|
|
||||||
# tools/**
|
|
||||||
# !tools/packages.config
|
|
||||||
|
|
||||||
# Tabs Studio
|
|
||||||
*.tss
|
|
||||||
|
|
||||||
# Telerik's JustMock configuration file
|
|
||||||
*.jmconfig
|
|
||||||
|
|
||||||
# BizTalk build output
|
|
||||||
*.btp.cs
|
|
||||||
*.btm.cs
|
|
||||||
*.odx.cs
|
|
||||||
*.xsd.cs
|
|
||||||
|
|
||||||
# OpenCover UI analysis results
|
|
||||||
OpenCover/
|
|
||||||
|
|
||||||
# Azure Stream Analytics local run output
|
|
||||||
ASALocalRun/
|
|
||||||
|
|
||||||
# MSBuild Binary and Structured Log
|
|
||||||
*.binlog
|
|
||||||
|
|
||||||
# NVidia Nsight GPU debugger configuration file
|
|
||||||
*.nvuser
|
|
||||||
|
|
||||||
# MFractors (Xamarin productivity tool) working folder
|
|
||||||
.mfractor/
|
|
||||||
|
|
||||||
# Local History for Visual Studio
|
|
||||||
.localhistory/
|
|
||||||
|
|
||||||
# BeatPulse healthcheck temp database
|
|
||||||
healthchecksdb
|
|
||||||
|
|
||||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
|
||||||
MigrationBackup/
|
|
||||||
|
|
||||||
# Ionide (cross platform F# VS Code tools) working folder
|
|
||||||
.ionide/
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
using MareSynchronos.API.Data.Enum;
|
|
||||||
using MessagePack;
|
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Text;
|
|
||||||
using System.Security.Cryptography;
|
|
||||||
|
|
||||||
namespace MareSynchronos.API.Data;
|
|
||||||
|
|
||||||
[MessagePackObject(keyAsPropertyName: true)]
|
|
||||||
public class CharacterData
|
|
||||||
{
|
|
||||||
public CharacterData()
|
|
||||||
{
|
|
||||||
DataHash = new(() =>
|
|
||||||
{
|
|
||||||
var json = JsonSerializer.Serialize(this);
|
|
||||||
#pragma warning disable SYSLIB0021 // Type or member is obsolete
|
|
||||||
using SHA256CryptoServiceProvider cryptoProvider = new();
|
|
||||||
#pragma warning restore SYSLIB0021 // Type or member is obsolete
|
|
||||||
return BitConverter.ToString(cryptoProvider.ComputeHash(Encoding.UTF8.GetBytes(json))).Replace("-", "", StringComparison.Ordinal);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public Dictionary<ObjectKind, string> CustomizePlusData { get; set; } = new();
|
|
||||||
[JsonIgnore]
|
|
||||||
public Lazy<string> DataHash { get; }
|
|
||||||
|
|
||||||
public Dictionary<ObjectKind, List<FileReplacementData>> FileReplacements { get; set; } = new();
|
|
||||||
public Dictionary<ObjectKind, string> GlamourerData { get; set; } = new();
|
|
||||||
public string HeelsData { get; set; } = string.Empty;
|
|
||||||
public string HonorificData { get; set; } = string.Empty;
|
|
||||||
public string ManipulationData { get; set; } = string.Empty;
|
|
||||||
public string MoodlesData { get; set; } = string.Empty;
|
|
||||||
public string PetNamesData { get; set; } = string.Empty;
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
using MessagePack;
|
|
||||||
|
|
||||||
namespace MareSynchronos.API.Data;
|
|
||||||
|
|
||||||
[MessagePackObject(keyAsPropertyName: true)]
|
|
||||||
public record ChatMessage
|
|
||||||
{
|
|
||||||
public string SenderName { get; set; } = string.Empty;
|
|
||||||
public uint SenderHomeWorldId { get; set; } = 0;
|
|
||||||
public byte[] PayloadContent { get; set; } = [];
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
namespace MareSynchronos.API.Data.Comparer;
|
|
||||||
|
|
||||||
public class GroupDataComparer : IEqualityComparer<GroupData>
|
|
||||||
{
|
|
||||||
public static GroupDataComparer Instance => _instance;
|
|
||||||
private static GroupDataComparer _instance = new GroupDataComparer();
|
|
||||||
|
|
||||||
private GroupDataComparer() { }
|
|
||||||
public bool Equals(GroupData? x, GroupData? y)
|
|
||||||
{
|
|
||||||
if (x == null || y == null) return false;
|
|
||||||
return x.GID.Equals(y.GID, StringComparison.Ordinal);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int GetHashCode(GroupData obj)
|
|
||||||
{
|
|
||||||
return obj.GID.GetHashCode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
using MareSynchronos.API.Dto.Group;
|
|
||||||
|
|
||||||
namespace MareSynchronos.API.Data.Comparer;
|
|
||||||
|
|
||||||
|
|
||||||
public class GroupDtoComparer : IEqualityComparer<GroupDto>
|
|
||||||
{
|
|
||||||
public static GroupDtoComparer Instance => _instance;
|
|
||||||
private static GroupDtoComparer _instance = new GroupDtoComparer();
|
|
||||||
|
|
||||||
private GroupDtoComparer() { }
|
|
||||||
|
|
||||||
public bool Equals(GroupDto? x, GroupDto? y)
|
|
||||||
{
|
|
||||||
if (x == null || y == null) return false;
|
|
||||||
return x.GID.Equals(y.GID, StringComparison.Ordinal);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int GetHashCode(GroupDto obj)
|
|
||||||
{
|
|
||||||
return obj.Group.GID.GetHashCode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
using MareSynchronos.API.Dto.Group;
|
|
||||||
|
|
||||||
namespace MareSynchronos.API.Data.Comparer;
|
|
||||||
|
|
||||||
public class GroupPairDtoComparer : IEqualityComparer<GroupPairDto>
|
|
||||||
{
|
|
||||||
public static GroupPairDtoComparer Instance => _instance;
|
|
||||||
private static GroupPairDtoComparer _instance = new();
|
|
||||||
private GroupPairDtoComparer() { }
|
|
||||||
public bool Equals(GroupPairDto? x, GroupPairDto? y)
|
|
||||||
{
|
|
||||||
if (x == null || y == null) return false;
|
|
||||||
return x.GID.Equals(y.GID, StringComparison.Ordinal) && x.UID.Equals(y.UID, StringComparison.Ordinal);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int GetHashCode(GroupPairDto obj)
|
|
||||||
{
|
|
||||||
return HashCode.Combine(obj.Group.GID.GetHashCode(), obj.User.UID.GetHashCode());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
namespace MareSynchronos.API.Data.Comparer;
|
|
||||||
|
|
||||||
public class UserDataComparer : IEqualityComparer<UserData>
|
|
||||||
{
|
|
||||||
public static UserDataComparer Instance => _instance;
|
|
||||||
private static UserDataComparer _instance = new();
|
|
||||||
|
|
||||||
private UserDataComparer() { }
|
|
||||||
|
|
||||||
public bool Equals(UserData? x, UserData? y)
|
|
||||||
{
|
|
||||||
if (x == null || y == null) return false;
|
|
||||||
return x.UID.Equals(y.UID, StringComparison.Ordinal);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int GetHashCode(UserData obj)
|
|
||||||
{
|
|
||||||
return obj.UID.GetHashCode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
using MareSynchronos.API.Dto.User;
|
|
||||||
|
|
||||||
namespace MareSynchronos.API.Data.Comparer;
|
|
||||||
|
|
||||||
public class UserDtoComparer : IEqualityComparer<UserDto>
|
|
||||||
{
|
|
||||||
public static UserDtoComparer Instance => _instance;
|
|
||||||
private static UserDtoComparer _instance = new();
|
|
||||||
private UserDtoComparer() { }
|
|
||||||
public bool Equals(UserDto? x, UserDto? y)
|
|
||||||
{
|
|
||||||
if (x == null || y == null) return false;
|
|
||||||
return x.User.UID.Equals(y.User.UID, StringComparison.Ordinal);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int GetHashCode(UserDto obj)
|
|
||||||
{
|
|
||||||
return obj.User.UID.GetHashCode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
namespace MareSynchronos.API.Data.Enum;
|
|
||||||
|
|
||||||
[Flags]
|
|
||||||
public enum GroupPermissions
|
|
||||||
{
|
|
||||||
NoneSet = 0x0,
|
|
||||||
DisableAnimations = 0x1,
|
|
||||||
DisableSounds = 0x2,
|
|
||||||
DisableInvites = 0x4,
|
|
||||||
DisableVFX = 0x8,
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
namespace MareSynchronos.API.Data.Enum;
|
|
||||||
|
|
||||||
[Flags]
|
|
||||||
public enum GroupUserInfo
|
|
||||||
{
|
|
||||||
None = 0x0,
|
|
||||||
IsModerator = 0x2,
|
|
||||||
IsPinned = 0x4
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
namespace MareSynchronos.API.Data.Enum;
|
|
||||||
|
|
||||||
[Flags]
|
|
||||||
public enum GroupUserPermissions
|
|
||||||
{
|
|
||||||
NoneSet = 0x0,
|
|
||||||
Paused = 0x1,
|
|
||||||
DisableAnimations = 0x2,
|
|
||||||
DisableSounds = 0x4,
|
|
||||||
DisableVFX = 0x8,
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
namespace MareSynchronos.API.Data.Enum;
|
|
||||||
|
|
||||||
public enum MessageSeverity
|
|
||||||
{
|
|
||||||
Information,
|
|
||||||
Warning,
|
|
||||||
Error
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
namespace MareSynchronos.API.Data.Enum;
|
|
||||||
|
|
||||||
public enum ObjectKind
|
|
||||||
{
|
|
||||||
Player = 0,
|
|
||||||
MinionOrMount = 1,
|
|
||||||
Companion = 2,
|
|
||||||
Pet = 3,
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
namespace MareSynchronos.API.Data.Enum;
|
|
||||||
|
|
||||||
[Flags]
|
|
||||||
public enum UserPermissions
|
|
||||||
{
|
|
||||||
NoneSet = 0,
|
|
||||||
Paired = 1,
|
|
||||||
Paused = 2,
|
|
||||||
DisableAnimations = 4,
|
|
||||||
DisableSounds = 8,
|
|
||||||
DisableVFX = 16,
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
using MareSynchronos.API.Data.Enum;
|
|
||||||
|
|
||||||
namespace MareSynchronos.API.Data.Extensions;
|
|
||||||
|
|
||||||
public static class GroupPermissionsExtensions
|
|
||||||
{
|
|
||||||
public static bool IsDisableAnimations(this GroupPermissions perm)
|
|
||||||
{
|
|
||||||
return perm.HasFlag(GroupPermissions.DisableAnimations);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsDisableSounds(this GroupPermissions perm)
|
|
||||||
{
|
|
||||||
return perm.HasFlag(GroupPermissions.DisableSounds);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsDisableInvites(this GroupPermissions perm)
|
|
||||||
{
|
|
||||||
return perm.HasFlag(GroupPermissions.DisableInvites);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsDisableVFX(this GroupPermissions perm)
|
|
||||||
{
|
|
||||||
return perm.HasFlag(GroupPermissions.DisableVFX);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void SetDisableAnimations(this ref GroupPermissions perm, bool set)
|
|
||||||
{
|
|
||||||
if (set) perm |= GroupPermissions.DisableAnimations;
|
|
||||||
else perm &= ~GroupPermissions.DisableAnimations;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void SetDisableSounds(this ref GroupPermissions perm, bool set)
|
|
||||||
{
|
|
||||||
if (set) perm |= GroupPermissions.DisableSounds;
|
|
||||||
else perm &= ~GroupPermissions.DisableSounds;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void SetDisableInvites(this ref GroupPermissions perm, bool set)
|
|
||||||
{
|
|
||||||
if (set) perm |= GroupPermissions.DisableInvites;
|
|
||||||
else perm &= ~GroupPermissions.DisableInvites;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void SetDisableVFX(this ref GroupPermissions perm, bool set)
|
|
||||||
{
|
|
||||||
if (set) perm |= GroupPermissions.DisableVFX;
|
|
||||||
else perm &= ~GroupPermissions.DisableVFX;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
using MareSynchronos.API.Data.Enum;
|
|
||||||
|
|
||||||
namespace MareSynchronos.API.Data.Extensions;
|
|
||||||
|
|
||||||
public static class GroupUserInfoExtensions
|
|
||||||
{
|
|
||||||
public static bool IsModerator(this GroupUserInfo info)
|
|
||||||
{
|
|
||||||
return info.HasFlag(GroupUserInfo.IsModerator);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsPinned(this GroupUserInfo info)
|
|
||||||
{
|
|
||||||
return info.HasFlag(GroupUserInfo.IsPinned);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void SetModerator(this ref GroupUserInfo info, bool isModerator)
|
|
||||||
{
|
|
||||||
if (isModerator) info |= GroupUserInfo.IsModerator;
|
|
||||||
else info &= ~GroupUserInfo.IsModerator;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void SetPinned(this ref GroupUserInfo info, bool isPinned)
|
|
||||||
{
|
|
||||||
if (isPinned) info |= GroupUserInfo.IsPinned;
|
|
||||||
else info &= ~GroupUserInfo.IsPinned;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
using MareSynchronos.API.Data.Enum;
|
|
||||||
|
|
||||||
namespace MareSynchronos.API.Data.Extensions;
|
|
||||||
|
|
||||||
public static class GroupUserPermissionsExtensions
|
|
||||||
{
|
|
||||||
public static bool IsDisableAnimations(this GroupUserPermissions perm)
|
|
||||||
{
|
|
||||||
return perm.HasFlag(GroupUserPermissions.DisableAnimations);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsDisableSounds(this GroupUserPermissions perm)
|
|
||||||
{
|
|
||||||
return perm.HasFlag(GroupUserPermissions.DisableSounds);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsPaused(this GroupUserPermissions perm)
|
|
||||||
{
|
|
||||||
return perm.HasFlag(GroupUserPermissions.Paused);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsDisableVFX(this GroupUserPermissions perm)
|
|
||||||
{
|
|
||||||
return perm.HasFlag(GroupUserPermissions.DisableVFX);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void SetDisableAnimations(this ref GroupUserPermissions perm, bool set)
|
|
||||||
{
|
|
||||||
if (set) perm |= GroupUserPermissions.DisableAnimations;
|
|
||||||
else perm &= ~GroupUserPermissions.DisableAnimations;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void SetDisableSounds(this ref GroupUserPermissions perm, bool set)
|
|
||||||
{
|
|
||||||
if (set) perm |= GroupUserPermissions.DisableSounds;
|
|
||||||
else perm &= ~GroupUserPermissions.DisableSounds;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void SetPaused(this ref GroupUserPermissions perm, bool set)
|
|
||||||
{
|
|
||||||
if (set) perm |= GroupUserPermissions.Paused;
|
|
||||||
else perm &= ~GroupUserPermissions.Paused;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void SetDisableVFX(this ref GroupUserPermissions perm, bool set)
|
|
||||||
{
|
|
||||||
if (set) perm |= GroupUserPermissions.DisableVFX;
|
|
||||||
else perm &= ~GroupUserPermissions.DisableVFX;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
using MareSynchronos.API.Data.Enum;
|
|
||||||
|
|
||||||
namespace MareSynchronos.API.Data.Extensions;
|
|
||||||
|
|
||||||
public static class UserPermissionsExtensions
|
|
||||||
{
|
|
||||||
public static bool IsPaired(this UserPermissions perm)
|
|
||||||
{
|
|
||||||
return perm.HasFlag(UserPermissions.Paired);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsPaused(this UserPermissions perm)
|
|
||||||
{
|
|
||||||
return perm.HasFlag(UserPermissions.Paused);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsDisableAnimations(this UserPermissions perm)
|
|
||||||
{
|
|
||||||
return perm.HasFlag(UserPermissions.DisableAnimations);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsDisableSounds(this UserPermissions perm)
|
|
||||||
{
|
|
||||||
return perm.HasFlag(UserPermissions.DisableSounds);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsDisableVFX(this UserPermissions perm)
|
|
||||||
{
|
|
||||||
return perm.HasFlag(UserPermissions.DisableVFX);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void SetPaired(this ref UserPermissions perm, bool paired)
|
|
||||||
{
|
|
||||||
if (paired) perm |= UserPermissions.Paired;
|
|
||||||
else perm &= ~UserPermissions.Paired;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void SetPaused(this ref UserPermissions perm, bool paused)
|
|
||||||
{
|
|
||||||
if (paused) perm |= UserPermissions.Paused;
|
|
||||||
else perm &= ~UserPermissions.Paused;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void SetDisableAnimations(this ref UserPermissions perm, bool set)
|
|
||||||
{
|
|
||||||
if (set) perm |= UserPermissions.DisableAnimations;
|
|
||||||
else perm &= ~UserPermissions.DisableAnimations;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void SetDisableSounds(this ref UserPermissions perm, bool set)
|
|
||||||
{
|
|
||||||
if (set) perm |= UserPermissions.DisableSounds;
|
|
||||||
else perm &= ~UserPermissions.DisableSounds;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void SetDisableVFX(this ref UserPermissions perm, bool set)
|
|
||||||
{
|
|
||||||
if (set) perm |= UserPermissions.DisableVFX;
|
|
||||||
else perm &= ~UserPermissions.DisableVFX;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
using MessagePack;
|
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Text;
|
|
||||||
using System.Security.Cryptography;
|
|
||||||
|
|
||||||
|
|
||||||
namespace MareSynchronos.API.Data;
|
|
||||||
|
|
||||||
[MessagePackObject(keyAsPropertyName: true)]
|
|
||||||
public class FileReplacementData
|
|
||||||
{
|
|
||||||
public FileReplacementData()
|
|
||||||
{
|
|
||||||
DataHash = new(() =>
|
|
||||||
{
|
|
||||||
var json = JsonSerializer.Serialize(this);
|
|
||||||
#pragma warning disable SYSLIB0021 // Type or member is obsolete
|
|
||||||
using SHA256CryptoServiceProvider cryptoProvider = new();
|
|
||||||
#pragma warning restore SYSLIB0021 // Type or member is obsolete
|
|
||||||
return BitConverter.ToString(cryptoProvider.ComputeHash(Encoding.UTF8.GetBytes(json))).Replace("-", "", StringComparison.Ordinal);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
[JsonIgnore]
|
|
||||||
public Lazy<string> DataHash { get; }
|
|
||||||
public string[] GamePaths { get; set; } = Array.Empty<string>();
|
|
||||||
public string Hash { get; set; } = string.Empty;
|
|
||||||
public string FileSwapPath { get; set; } = string.Empty;
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
using MessagePack;
|
|
||||||
|
|
||||||
namespace MareSynchronos.API.Data;
|
|
||||||
|
|
||||||
[MessagePackObject(keyAsPropertyName: true)]
|
|
||||||
public record GroupData(string GID, string? Alias = null)
|
|
||||||
{
|
|
||||||
[IgnoreMember]
|
|
||||||
public string AliasOrGID => string.IsNullOrWhiteSpace(Alias) ? GID : Alias;
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
using MessagePack;
|
|
||||||
|
|
||||||
namespace MareSynchronos.API.Data;
|
|
||||||
|
|
||||||
[MessagePackObject(keyAsPropertyName: true)]
|
|
||||||
public record SignedChatMessage(ChatMessage Message, UserData Sender) : ChatMessage(Message)
|
|
||||||
{
|
|
||||||
// Sender and timestamp are set by the server
|
|
||||||
public UserData Sender { get; set; } = Sender;
|
|
||||||
public long Timestamp { get; set; } = 0;
|
|
||||||
// Signature is generated by the server as SHA256(Sender.UID | Timestamp | Destination | Message)
|
|
||||||
// Where Destination is either the receiver's UID, or the group GID
|
|
||||||
public string Signature { get; set; } = string.Empty;
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
using MessagePack;
|
|
||||||
|
|
||||||
namespace MareSynchronos.API.Data;
|
|
||||||
|
|
||||||
[MessagePackObject(keyAsPropertyName: true)]
|
|
||||||
public record UserData(string UID, string? Alias = null)
|
|
||||||
{
|
|
||||||
[IgnoreMember]
|
|
||||||
public string AliasOrUID => string.IsNullOrWhiteSpace(Alias) ? UID : Alias;
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
using MessagePack;
|
|
||||||
|
|
||||||
namespace MareSynchronos.API.Dto.Account;
|
|
||||||
|
|
||||||
[MessagePackObject(keyAsPropertyName: true)]
|
|
||||||
public record RegisterReplyDto
|
|
||||||
{
|
|
||||||
public bool Success { get; set; } = false;
|
|
||||||
public string ErrorMessage { get; set; } = string.Empty;
|
|
||||||
public string UID { get; set; } = string.Empty;
|
|
||||||
public string SecretKey { get; set; } = string.Empty;
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
using MessagePack;
|
|
||||||
|
|
||||||
namespace MareSynchronos.API.Dto.Account;
|
|
||||||
|
|
||||||
[MessagePackObject(keyAsPropertyName: true)]
|
|
||||||
public record RegisterReplyV2Dto
|
|
||||||
{
|
|
||||||
public bool Success { get; set; } = false;
|
|
||||||
public string ErrorMessage { get; set; } = string.Empty;
|
|
||||||
public string UID { get; set; } = string.Empty;
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
using MareSynchronos.API.Data;
|
|
||||||
using MessagePack;
|
|
||||||
|
|
||||||
namespace MareSynchronos.API.Dto;
|
|
||||||
|
|
||||||
[MessagePackObject(keyAsPropertyName: true)]
|
|
||||||
public record AuthReplyDto
|
|
||||||
{
|
|
||||||
public string Token { get; set; } = string.Empty;
|
|
||||||
public string? WellKnown { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
namespace MareSynchronos.API.Dto.CharaData;
|
|
||||||
|
|
||||||
public enum AccessTypeDto
|
|
||||||
{
|
|
||||||
Individuals,
|
|
||||||
ClosePairs,
|
|
||||||
AllPairs,
|
|
||||||
Public
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
using MareSynchronos.API.Data;
|
|
||||||
using MessagePack;
|
|
||||||
|
|
||||||
namespace MareSynchronos.API.Dto.CharaData;
|
|
||||||
|
|
||||||
[MessagePackObject(keyAsPropertyName: true)]
|
|
||||||
public record CharaDataDownloadDto(string Id, UserData Uploader) : CharaDataDto(Id, Uploader)
|
|
||||||
{
|
|
||||||
public string GlamourerData { get; init; } = string.Empty;
|
|
||||||
public string CustomizeData { get; init; } = string.Empty;
|
|
||||||
public string ManipulationData { get; set; } = string.Empty;
|
|
||||||
public List<GamePathEntry> FileGamePaths { get; init; } = [];
|
|
||||||
public List<GamePathEntry> FileSwaps { get; init; } = [];
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
using MareSynchronos.API.Data;
|
|
||||||
|
|
||||||
namespace MareSynchronos.API.Dto.CharaData;
|
|
||||||
|
|
||||||
public record CharaDataDto(string Id, UserData Uploader)
|
|
||||||
{
|
|
||||||
public string Description { get; init; } = string.Empty;
|
|
||||||
public DateTime UpdatedDate { get; init; }
|
|
||||||
}
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
using MareSynchronos.API.Data;
|
|
||||||
using MessagePack;
|
|
||||||
|
|
||||||
namespace MareSynchronos.API.Dto.CharaData;
|
|
||||||
|
|
||||||
[MessagePackObject(keyAsPropertyName: true)]
|
|
||||||
public record CharaDataFullDto(string Id, UserData Uploader) : CharaDataDto(Id, Uploader)
|
|
||||||
{
|
|
||||||
public DateTime CreatedDate { get; init; }
|
|
||||||
public DateTime ExpiryDate { get; set; }
|
|
||||||
public string GlamourerData { get; set; } = string.Empty;
|
|
||||||
public string CustomizeData { get; set; } = string.Empty;
|
|
||||||
public string ManipulationData { get; set; } = string.Empty;
|
|
||||||
public int DownloadCount { get; set; } = 0;
|
|
||||||
public List<UserData> AllowedUsers { get; set; } = [];
|
|
||||||
public List<GroupData> AllowedGroups { get; set; } = [];
|
|
||||||
public List<GamePathEntry> FileGamePaths { get; set; } = [];
|
|
||||||
public List<GamePathEntry> FileSwaps { get; set; } = [];
|
|
||||||
public List<GamePathEntry> OriginalFiles { get; set; } = [];
|
|
||||||
public AccessTypeDto AccessType { get; set; }
|
|
||||||
public ShareTypeDto ShareType { get; set; }
|
|
||||||
public List<PoseEntry> PoseData { get; set; } = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
[MessagePackObject(keyAsPropertyName: true)]
|
|
||||||
public record GamePathEntry(string HashOrFileSwap, string GamePath);
|
|
||||||
|
|
||||||
[MessagePackObject(keyAsPropertyName: true)]
|
|
||||||
public record PoseEntry(long? Id)
|
|
||||||
{
|
|
||||||
public string? Description { get; set; } = string.Empty;
|
|
||||||
public string? PoseData { get; set; } = string.Empty;
|
|
||||||
public WorldData? WorldData { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[MessagePackObject]
|
|
||||||
public record struct WorldData
|
|
||||||
{
|
|
||||||
[Key(0)] public LocationInfo LocationInfo { get; set; }
|
|
||||||
[Key(1)] public float PositionX { get; set; }
|
|
||||||
[Key(2)] public float PositionY { get; set; }
|
|
||||||
[Key(3)] public float PositionZ { get; set; }
|
|
||||||
[Key(4)] public float RotationX { get; set; }
|
|
||||||
[Key(5)] public float RotationY { get; set; }
|
|
||||||
[Key(6)] public float RotationZ { get; set; }
|
|
||||||
[Key(7)] public float RotationW { get; set; }
|
|
||||||
[Key(8)] public float ScaleX { get; set; }
|
|
||||||
[Key(9)] public float ScaleY { get; set; }
|
|
||||||
[Key(10)] public float ScaleZ { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[MessagePackObject]
|
|
||||||
public record struct LocationInfo
|
|
||||||
{
|
|
||||||
[Key(0)] public uint ServerId { get; set; }
|
|
||||||
[Key(1)] public uint MapId { get; set; }
|
|
||||||
[Key(2)] public uint TerritoryId { get; set; }
|
|
||||||
[Key(3)] public uint DivisionId { get; set; }
|
|
||||||
[Key(4)] public uint WardId { get; set; }
|
|
||||||
[Key(5)] public uint HouseId { get; set; }
|
|
||||||
[Key(6)] public uint RoomId { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[MessagePackObject]
|
|
||||||
public record struct PoseData
|
|
||||||
{
|
|
||||||
[Key(0)] public bool IsDelta { get; set; }
|
|
||||||
[Key(1)] public Dictionary<string, BoneData> Bones { get; set; }
|
|
||||||
[Key(2)] public Dictionary<string, BoneData> MainHand { get; set; }
|
|
||||||
[Key(3)] public Dictionary<string, BoneData> OffHand { get; set; }
|
|
||||||
[Key(4)] public BoneData ModelDifference { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[MessagePackObject]
|
|
||||||
public record struct BoneData
|
|
||||||
{
|
|
||||||
[Key(0)] public bool Exists { get; set; }
|
|
||||||
[Key(1)] public float PositionX { get; set; }
|
|
||||||
[Key(2)] public float PositionY { get; set; }
|
|
||||||
[Key(3)] public float PositionZ { get; set; }
|
|
||||||
[Key(4)] public float RotationX { get; set; }
|
|
||||||
[Key(5)] public float RotationY { get; set; }
|
|
||||||
[Key(6)] public float RotationZ { get; set; }
|
|
||||||
[Key(7)] public float RotationW { get; set; }
|
|
||||||
[Key(8)] public float ScaleX { get; set; }
|
|
||||||
[Key(9)] public float ScaleY { get; set; }
|
|
||||||
[Key(10)] public float ScaleZ { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
using MareSynchronos.API.Data;
|
|
||||||
using MessagePack;
|
|
||||||
|
|
||||||
namespace MareSynchronos.API.Dto.CharaData;
|
|
||||||
|
|
||||||
[MessagePackObject(keyAsPropertyName: true)]
|
|
||||||
public record CharaDataMetaInfoDto(string Id, UserData Uploader) : CharaDataDto(Id, Uploader)
|
|
||||||
{
|
|
||||||
public bool CanBeDownloaded { get; init; }
|
|
||||||
public List<PoseEntry> PoseData { get; set; } = [];
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
using MessagePack;
|
|
||||||
|
|
||||||
namespace MareSynchronos.API.Dto.CharaData;
|
|
||||||
|
|
||||||
[MessagePackObject(keyAsPropertyName: true)]
|
|
||||||
public record CharaDataUpdateDto(string Id)
|
|
||||||
{
|
|
||||||
public string? Description { get; set; }
|
|
||||||
public DateTime? ExpiryDate { get; set; }
|
|
||||||
public string? GlamourerData { get; set; }
|
|
||||||
public string? CustomizeData { get; set; }
|
|
||||||
public string? ManipulationData { get; set; }
|
|
||||||
public List<string>? AllowedUsers { get; set; }
|
|
||||||
public List<string>? AllowedGroups { get; set; }
|
|
||||||
public List<GamePathEntry>? FileGamePaths { get; set; }
|
|
||||||
public List<GamePathEntry>? FileSwaps { get; set; }
|
|
||||||
public AccessTypeDto? AccessType { get; set; }
|
|
||||||
public ShareTypeDto? ShareType { get; set; }
|
|
||||||
public List<PoseEntry>? Poses { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
namespace MareSynchronos.API.Dto.CharaData;
|
|
||||||
|
|
||||||
public enum ShareTypeDto
|
|
||||||
{
|
|
||||||
Private,
|
|
||||||
Shared
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
using MareSynchronos.API.Data;
|
|
||||||
using MareSynchronos.API.Dto.Group;
|
|
||||||
using MareSynchronos.API.Dto.User;
|
|
||||||
using MessagePack;
|
|
||||||
|
|
||||||
namespace MareSynchronos.API.Dto.Chat;
|
|
||||||
|
|
||||||
[MessagePackObject(keyAsPropertyName: true)]
|
|
||||||
public record GroupChatMsgDto(GroupDto Group, SignedChatMessage Message)
|
|
||||||
{
|
|
||||||
public GroupDto Group = Group;
|
|
||||||
public SignedChatMessage Message = Message;
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
using MareSynchronos.API.Data;
|
|
||||||
using MareSynchronos.API.Dto.User;
|
|
||||||
using MessagePack;
|
|
||||||
|
|
||||||
namespace MareSynchronos.API.Dto.Chat;
|
|
||||||
|
|
||||||
[MessagePackObject(keyAsPropertyName: true)]
|
|
||||||
public record UserChatMsgDto(SignedChatMessage Message)
|
|
||||||
{
|
|
||||||
public SignedChatMessage Message = Message;
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
using MareSynchronos.API.Data;
|
|
||||||
using MessagePack;
|
|
||||||
|
|
||||||
namespace MareSynchronos.API.Dto;
|
|
||||||
|
|
||||||
[MessagePackObject(keyAsPropertyName: true)]
|
|
||||||
public record ConnectionDto(UserData User)
|
|
||||||
{
|
|
||||||
public Version CurrentClientVersion { get; set; } = new(0, 0, 0);
|
|
||||||
public int ServerVersion { get; set; }
|
|
||||||
public bool IsAdmin { get; set; }
|
|
||||||
public bool IsModerator { get; set; }
|
|
||||||
public ServerInfo ServerInfo { get; set; } = new();
|
|
||||||
}
|
|
||||||
|
|
||||||
[MessagePackObject(keyAsPropertyName: true)]
|
|
||||||
public record ServerInfo
|
|
||||||
{
|
|
||||||
public string ShardName { get; set; } = string.Empty;
|
|
||||||
public int MaxGroupUserCount { get; set; }
|
|
||||||
public int MaxGroupsCreatedByUser { get; set; }
|
|
||||||
public int MaxGroupsJoinedByUser { get; set; }
|
|
||||||
public Uri FileServerAddress { get; set; } = new Uri("http://nonemptyuri");
|
|
||||||
public int MaxCharaData { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
using MessagePack;
|
|
||||||
|
|
||||||
namespace MareSynchronos.API.Dto.Files;
|
|
||||||
|
|
||||||
[MessagePackObject(keyAsPropertyName: true)]
|
|
||||||
public record DownloadFileDto : ITransferFileDto
|
|
||||||
{
|
|
||||||
public bool FileExists { get; set; } = true;
|
|
||||||
public string Hash { get; set; } = string.Empty;
|
|
||||||
public string Url { get; set; } = string.Empty;
|
|
||||||
public long Size { get; set; } = 0;
|
|
||||||
public bool IsForbidden { get; set; } = false;
|
|
||||||
public string ForbiddenBy { get; set; } = string.Empty;
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace MareSynchronos.API.Dto.Files;
|
|
||||||
|
|
||||||
public class FilesSendDto
|
|
||||||
{
|
|
||||||
public List<string> FileHashes { get; set; } = new();
|
|
||||||
public List<string> UIDs { get; set; } = new();
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
namespace MareSynchronos.API.Dto.Files;
|
|
||||||
|
|
||||||
public interface ITransferFileDto
|
|
||||||
{
|
|
||||||
string Hash { get; set; }
|
|
||||||
bool IsForbidden { get; set; }
|
|
||||||
string ForbiddenBy { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
using MessagePack;
|
|
||||||
|
|
||||||
namespace MareSynchronos.API.Dto.Files;
|
|
||||||
|
|
||||||
[MessagePackObject(keyAsPropertyName: true)]
|
|
||||||
public record UploadFileDto : ITransferFileDto
|
|
||||||
{
|
|
||||||
public string Hash { get; set; } = string.Empty;
|
|
||||||
public bool IsForbidden { get; set; } = false;
|
|
||||||
public string ForbiddenBy { get; set; } = string.Empty;
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
using MareSynchronos.API.Data;
|
|
||||||
using MessagePack;
|
|
||||||
|
|
||||||
namespace MareSynchronos.API.Dto.Group;
|
|
||||||
|
|
||||||
[MessagePackObject(keyAsPropertyName: true)]
|
|
||||||
public record BannedGroupUserDto : GroupPairDto
|
|
||||||
{
|
|
||||||
public BannedGroupUserDto(GroupData group, UserData user, string reason, DateTime bannedOn, string bannedBy) : base(group, user)
|
|
||||||
{
|
|
||||||
Reason = reason;
|
|
||||||
BannedOn = bannedOn;
|
|
||||||
BannedBy = bannedBy;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Reason { get; set; }
|
|
||||||
public DateTime BannedOn { get; set; }
|
|
||||||
public string BannedBy { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
using MareSynchronos.API.Data;
|
|
||||||
using MessagePack;
|
|
||||||
|
|
||||||
namespace MareSynchronos.API.Dto.Group;
|
|
||||||
|
|
||||||
[MessagePackObject(keyAsPropertyName: true)]
|
|
||||||
public record GroupDto(GroupData Group)
|
|
||||||
{
|
|
||||||
public GroupData Group { get; set; } = Group;
|
|
||||||
public string GID => Group.GID;
|
|
||||||
public string? GroupAlias => Group.Alias;
|
|
||||||
public string GroupAliasOrGID => Group.AliasOrGID;
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
using MareSynchronos.API.Data;
|
|
||||||
using MareSynchronos.API.Data.Enum;
|
|
||||||
using MessagePack;
|
|
||||||
|
|
||||||
namespace MareSynchronos.API.Dto.Group;
|
|
||||||
|
|
||||||
[MessagePackObject(keyAsPropertyName: true)]
|
|
||||||
public record GroupFullInfoDto(GroupData Group, UserData Owner, GroupPermissions GroupPermissions, GroupUserPermissions GroupUserPermissions, GroupUserInfo GroupUserInfo) : GroupInfoDto(Group, Owner, GroupPermissions)
|
|
||||||
{
|
|
||||||
public GroupUserPermissions GroupUserPermissions { get; set; } = GroupUserPermissions;
|
|
||||||
public GroupUserInfo GroupUserInfo { get; set; } = GroupUserInfo;
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
using MareSynchronos.API.Data;
|
|
||||||
using MareSynchronos.API.Data.Enum;
|
|
||||||
using MessagePack;
|
|
||||||
|
|
||||||
namespace MareSynchronos.API.Dto.Group;
|
|
||||||
|
|
||||||
[MessagePackObject(keyAsPropertyName: true)]
|
|
||||||
public record GroupInfoDto(GroupData Group, UserData Owner, GroupPermissions GroupPermissions) : GroupDto(Group)
|
|
||||||
{
|
|
||||||
public GroupPermissions GroupPermissions { get; set; } = GroupPermissions;
|
|
||||||
public UserData Owner { get; set; } = Owner;
|
|
||||||
|
|
||||||
public string OwnerUID => Owner.UID;
|
|
||||||
public string? OwnerAlias => Owner.Alias;
|
|
||||||
public string OwnerAliasOrUID => Owner.AliasOrUID;
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
using MareSynchronos.API.Data;
|
|
||||||
using MessagePack;
|
|
||||||
|
|
||||||
namespace MareSynchronos.API.Dto.Group;
|
|
||||||
|
|
||||||
[MessagePackObject(keyAsPropertyName: true)]
|
|
||||||
public record GroupPairDto(GroupData Group, UserData User) : GroupDto(Group)
|
|
||||||
{
|
|
||||||
public string UID => User.UID;
|
|
||||||
public string? UserAlias => User.Alias;
|
|
||||||
public string UserAliasOrUID => User.AliasOrUID;
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
using MareSynchronos.API.Data;
|
|
||||||
using MareSynchronos.API.Data.Enum;
|
|
||||||
using MessagePack;
|
|
||||||
|
|
||||||
namespace MareSynchronos.API.Dto.Group;
|
|
||||||
|
|
||||||
[MessagePackObject(keyAsPropertyName: true)]
|
|
||||||
public record GroupPairFullInfoDto(GroupData Group, UserData User, GroupUserInfo GroupPairStatusInfo, GroupUserPermissions GroupUserPermissions) : GroupPairDto(Group, User)
|
|
||||||
{
|
|
||||||
public GroupUserInfo GroupPairStatusInfo { get; set; } = GroupPairStatusInfo;
|
|
||||||
public GroupUserPermissions GroupUserPermissions { get; set; } = GroupUserPermissions;
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
using MareSynchronos.API.Data;
|
|
||||||
using MareSynchronos.API.Data.Enum;
|
|
||||||
using MessagePack;
|
|
||||||
|
|
||||||
namespace MareSynchronos.API.Dto.Group;
|
|
||||||
|
|
||||||
[MessagePackObject(keyAsPropertyName: true)]
|
|
||||||
public record GroupPairUserInfoDto(GroupData Group, UserData User, GroupUserInfo GroupUserInfo) : GroupPairDto(Group, User);
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
using MareSynchronos.API.Data;
|
|
||||||
using MareSynchronos.API.Data.Enum;
|
|
||||||
using MessagePack;
|
|
||||||
|
|
||||||
namespace MareSynchronos.API.Dto.Group;
|
|
||||||
|
|
||||||
[MessagePackObject(keyAsPropertyName: true)]
|
|
||||||
public record GroupPairUserPermissionDto(GroupData Group, UserData User, GroupUserPermissions GroupPairPermissions) : GroupPairDto(Group, User);
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
using MareSynchronos.API.Data;
|
|
||||||
using MessagePack;
|
|
||||||
|
|
||||||
namespace MareSynchronos.API.Dto.Group;
|
|
||||||
|
|
||||||
[MessagePackObject(keyAsPropertyName: true)]
|
|
||||||
public record GroupPasswordDto(GroupData Group, string Password) : GroupDto(Group);
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
using MareSynchronos.API.Data;
|
|
||||||
using MareSynchronos.API.Data.Enum;
|
|
||||||
using MessagePack;
|
|
||||||
|
|
||||||
namespace MareSynchronos.API.Dto.Group;
|
|
||||||
|
|
||||||
[MessagePackObject(keyAsPropertyName: true)]
|
|
||||||
public record GroupPermissionDto(GroupData Group, GroupPermissions Permissions) : GroupDto(Group);
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
using MessagePack;
|
|
||||||
|
|
||||||
namespace MareSynchronos.API.Dto;
|
|
||||||
|
|
||||||
[MessagePackObject(keyAsPropertyName: true)]
|
|
||||||
public record SystemInfoDto
|
|
||||||
{
|
|
||||||
public int OnlineUsers { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
using MareSynchronos.API.Data;
|
|
||||||
using MessagePack;
|
|
||||||
|
|
||||||
namespace MareSynchronos.API.Dto.User;
|
|
||||||
|
|
||||||
[MessagePackObject(keyAsPropertyName: true)]
|
|
||||||
public record OnlineUserCharaDataDto(UserData User, CharacterData CharaData) : UserDto(User);
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
using MareSynchronos.API.Data;
|
|
||||||
using MessagePack;
|
|
||||||
|
|
||||||
namespace MareSynchronos.API.Dto.User;
|
|
||||||
|
|
||||||
[MessagePackObject(keyAsPropertyName: true)]
|
|
||||||
public record OnlineUserIdentDto(UserData User, string Ident) : UserDto(User);
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
using MareSynchronos.API.Data;
|
|
||||||
using MessagePack;
|
|
||||||
|
|
||||||
namespace MareSynchronos.API.Dto.User;
|
|
||||||
|
|
||||||
[MessagePackObject(keyAsPropertyName: true)]
|
|
||||||
public record UserCharaDataMessageDto(List<UserData> Recipients, CharacterData CharaData);
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
using MareSynchronos.API.Data;
|
|
||||||
using MessagePack;
|
|
||||||
|
|
||||||
namespace MareSynchronos.API.Dto.User;
|
|
||||||
|
|
||||||
[MessagePackObject(keyAsPropertyName: true)]
|
|
||||||
public record UserDto(UserData User);
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
using MareSynchronos.API.Data;
|
|
||||||
using MareSynchronos.API.Data.Enum;
|
|
||||||
using MessagePack;
|
|
||||||
|
|
||||||
namespace MareSynchronos.API.Dto.User;
|
|
||||||
|
|
||||||
[MessagePackObject(keyAsPropertyName: true)]
|
|
||||||
public record UserPairDto(UserData User, UserPermissions OwnPermissions, UserPermissions OtherPermissions) : UserDto(User)
|
|
||||||
{
|
|
||||||
public UserPermissions OwnPermissions { get; set; } = OwnPermissions;
|
|
||||||
public UserPermissions OtherPermissions { get; set; } = OtherPermissions;
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
using MareSynchronos.API.Data;
|
|
||||||
using MareSynchronos.API.Data.Enum;
|
|
||||||
using MessagePack;
|
|
||||||
|
|
||||||
namespace MareSynchronos.API.Dto.User;
|
|
||||||
|
|
||||||
[MessagePackObject(keyAsPropertyName: true)]
|
|
||||||
public record UserPermissionsDto(UserData User, UserPermissions Permissions) : UserDto(User);
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
using MareSynchronos.API.Data;
|
|
||||||
using MessagePack;
|
|
||||||
|
|
||||||
namespace MareSynchronos.API.Dto.User;
|
|
||||||
|
|
||||||
[MessagePackObject(keyAsPropertyName: true)]
|
|
||||||
public record UserProfileDto(UserData User, bool Disabled, bool? IsNSFW, string? ProfilePictureBase64, string? Description) : UserDto(User);
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
using MareSynchronos.API.Data;
|
|
||||||
using MessagePack;
|
|
||||||
|
|
||||||
namespace MareSynchronos.API.Dto.User;
|
|
||||||
|
|
||||||
[MessagePackObject(keyAsPropertyName: true)]
|
|
||||||
public record UserProfileReportDto(UserData User, string ProfileReport) : UserDto(User);
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="MessagePack.Annotations" Version="2.5.198" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
|
||||||
# Visual Studio Version 17
|
|
||||||
VisualStudioVersion = 17.2.32602.215
|
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MareSynchronos.API", "MareSynchronos.API.csproj", "{CD05EE19-802F-4490-AAD8-CAD4BF1D630D}"
|
|
||||||
EndProject
|
|
||||||
Global
|
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
|
||||||
Debug|Any CPU = Debug|Any CPU
|
|
||||||
Release|Any CPU = Release|Any CPU
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
|
||||||
{CD05EE19-802F-4490-AAD8-CAD4BF1D630D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{CD05EE19-802F-4490-AAD8-CAD4BF1D630D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{CD05EE19-802F-4490-AAD8-CAD4BF1D630D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{CD05EE19-802F-4490-AAD8-CAD4BF1D630D}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
|
||||||
HideSolutionNode = FALSE
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
|
||||||
SolutionGuid = {DFB70C71-AB27-468D-A08B-218CA79BF69D}
|
|
||||||
EndGlobalSection
|
|
||||||
EndGlobal
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user