This commit is contained in:
Caraxi 2025-08-22 18:33:14 +09:30
parent 616b96981d
commit bc1c884562
26 changed files with 209 additions and 498 deletions

View file

@ -28,10 +28,10 @@ services:
image: darkarchon/mare-synchronos-server:latest image: darkarchon/mare-synchronos-server:latest
restart: on-failure restart: on-failure
ports: ports:
- 6000:6000/tcp - 6300:6300/tcp
- 6050:6050/tcp - 6350:6350/tcp
environment: environment:
MareSynchronos__CdnFullUrl: "${DEV_MARE_CDNURL}" MareSynchronos__CdnFullUrl: "http://127.0.0.1:6200/marecache"
MareSynchronos__XIVAPIKey: "${DEV_MARE_XIVAPIKEY}" MareSynchronos__XIVAPIKey: "${DEV_MARE_XIVAPIKEY}"
DOTNET_USE_POLLING_FILE_WATCHER: 1 DOTNET_USE_POLLING_FILE_WATCHER: 1
volumes: volumes:
@ -42,34 +42,34 @@ services:
postgres: postgres:
condition: service_healthy condition: service_healthy
healthcheck: healthcheck:
test: ["CMD-SHELL", "curl --fail http://localhost:6000/health || exit 1"] test: ["CMD-SHELL", "curl --fail http://localhost:6300/health || exit 1"]
retries: 60 retries: 60
start_period: 10s start_period: 10s
timeout: 1s timeout: 1s
#
mare-auth: # mare-auth:
image: darkarchon/mare-synchronos-authservice:latest # image: darkarchon/mare-synchronos-authservice:latest
restart: on-failure # restart: on-failure
environment: # environment:
DOTNET_USE_POLLING_FILE_WATCHER: 1 # DOTNET_USE_POLLING_FILE_WATCHER: 1
volumes: # volumes:
- ../config/standalone/authservice-standalone.json:/opt/MareSynchronosAuthService/appsettings.json # - ../config/standalone/authservice-standalone.json:/opt/MareSynchronosAuthService/appsettings.json
- ../log/authservice-standalone/:/opt/MareSynchronosAuthService/logs/:rw # - ../log/authservice-standalone/:/opt/MareSynchronosAuthService/logs/:rw
- postgres_socket:/var/run/postgresql/:rw # - postgres_socket:/var/run/postgresql/:rw
depends_on: # depends_on:
mare-server: # mare-server:
condition: service_healthy # condition: service_healthy
postgres: # postgres:
condition: service_healthy # condition: service_healthy
mare-services: mare-services:
image: darkarchon/mare-synchronos-services:latest image: darkarchon/mare-synchronos-services:latest
restart: on-failure restart: on-failure
environment: environment:
MareSynchronos__DiscordBotToken: "${DEV_MARE_DISCORDTOKEN}" MareSynchronos__DiscordBotToken: "${DEV_MARE_DISCORDTOKEN}"
MareSynchronos__DiscordChannelForMessages: "${DEV_MARE_DISCORDCHANNEL}" MareSynchronos__DiscordChannelForMessages: "926997505848254507"
MareSynchronos__DiscordChannelForReports: "${DEV_MARE_DISCORDCHANNEL}" MareSynchronos__DiscordChannelForReports: "926997505848254507"
MareSynchronos__DiscordChannelForCommands: "${DEV_MARE_DISCORDCHANNEL}" MareSynchronos__DiscordChannelForCommands: "926997505848254507"
DOTNET_USE_POLLING_FILE_WATCHER: 1 DOTNET_USE_POLLING_FILE_WATCHER: 1
volumes: volumes:
- ../config/standalone/services-standalone.json:/opt/MareSynchronosServices/appsettings.json - ../config/standalone/services-standalone.json:/opt/MareSynchronosServices/appsettings.json
@ -87,13 +87,13 @@ services:
- 6200:6200/tcp - 6200:6200/tcp
restart: on-failure restart: on-failure
environment: environment:
MareSynchronos__CdnFullUrl: "${DEV_MARE_CDNURL}" MareSynchronos__CdnFullUrl: "http://127.0.0.1:6200/marecache"
DOTNET_USE_POLLING_FILE_WATCHER: 1 DOTNET_USE_POLLING_FILE_WATCHER: 1
volumes: volumes:
- ../config/standalone/files-standalone.json:/opt/MareSynchronosStaticFilesServer/appsettings.json - ../config/standalone/files-standalone.json:/opt/MareSynchronosStaticFilesServer/appsettings.json
- ../log/files-standalone/:/opt/MareSynchronosStaticFilesServer/logs/:rw - ../log/files-standalone/:/opt/MareSynchronosStaticFilesServer/logs/:rw
- postgres_socket:/var/run/postgresql/:rw - postgres_socket:/var/run/postgresql/:rw
- ../data/files-standalone/:/marecache/:rw - D:\mareserverfiles\:/marecache/:rw
depends_on: depends_on:
postgres: postgres:
condition: service_healthy condition: service_healthy

View file

@ -7,8 +7,8 @@
"Default": "Warning", "Default": "Warning",
"Microsoft": "Warning", "Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information", "Microsoft.Hosting.Lifetime": "Information",
"MareSynchronosStaticFilesServer": "Debug", "MareSynchronosStaticFilesServer": "Trace",
"MareSynchronosShared": "Debug", "MareSynchronosShared": "Trace",
"System.IO": "Information", "System.IO": "Information",
}, },
"File": { "File": {

View file

@ -8,7 +8,7 @@
"Microsoft": "Warning", "Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information", "Microsoft.Hosting.Lifetime": "Information",
"Microsoft.EntityFramework": "Warning", "Microsoft.EntityFramework": "Warning",
"MareSynchronosServer": "Debug", "MareSynchronosServer": "Trace",
"MareSynchronosShared": "Debug", "MareSynchronosShared": "Debug",
"System.IO": "Information", "System.IO": "Information",
"MareSynchronosServer.Services.SystemInfoService": "Warning" "MareSynchronosServer.Services.SystemInfoService": "Warning"
@ -29,7 +29,7 @@
"MareSynchronos": { "MareSynchronos": {
"DbContextPoolSize": 512, "DbContextPoolSize": 512,
"ShardName": "Main", "ShardName": "Main",
"MetricsPort": 6050, "MetricsPort": 6350,
"MainServerAddress": "", "MainServerAddress": "",
"FailedAuthForTempBan": 5, "FailedAuthForTempBan": 5,
"TempBanDurationInMinutes": 5, "TempBanDurationInMinutes": 5,
@ -38,20 +38,20 @@
"" ""
], ],
"RedisConnectionString": "redis,password=secretredispassword", "RedisConnectionString": "redis,password=secretredispassword",
"CdnFullUrl": "http://localhost:6200", "CdnFullUrl": "http://localhost:6200/marecache",
"MaxExistingGroupsByUser": 6, "MaxExistingGroupsByUser": 6,
"MaxJoinedGroupsByUser": 10, "MaxJoinedGroupsByUser": 10,
"MaxGroupUserCount": 100, "MaxGroupUserCount": 100,
"PurgeUnusedAccounts": false, "PurgeUnusedAccounts": false,
"PurgeUnusedAccountsPeriodInDays": 14, "PurgeUnusedAccountsPeriodInDays": 14,
"ExpectedClientVersion": "0.9.0", "ExpectedClientVersion": "0.11.22",
"XIVAPIKey": "" "XIVAPIKey": ""
}, },
"AllowedHosts": "*", "AllowedHosts": "*",
"Kestrel": { "Kestrel": {
"Endpoints": { "Endpoints": {
"Http": { "Http": {
"Url": "http://+:6000" "Url": "http://+:6300"
} }
} }
}, },

View file

@ -29,14 +29,14 @@
"ShardName": "Services", "ShardName": "Services",
"MetricsPort": 6150, "MetricsPort": 6150,
"CdnFullUrl": "http://localhost:6200/", "CdnFullUrl": "http://localhost:6200/",
"MainServerAddress": "http://mare-server:6000/", "MainServerAddress": "http://mare-server:6300/",
"MainServerGrpcAddress": "http://mare-server:6005/", "MainServerGrpcAddress": "http://mare-server:6005/",
"DiscordBotToken": "", "DiscordBotToken": "",
"DiscordChannelForMessages": "", "DiscordChannelForMessages": "",
"DiscordChannelForCommands": "", "DiscordChannelForCommands": "",
"Jwt": "teststringteststringteststringteststringteststringteststringteststringteststringteststringteststring", "Jwt": "teststringteststringteststringteststringteststringteststringteststringteststringteststringteststring",
"RedisConnectionString": "redis,password=secretredispassword", "RedisConnectionString": "redis,password=secretredispassword",
"VanityRoles": {"984484868241096724":"Main Developer", "1012008556259725398":"Patreon Subscriber"} "VanityRoles": {"1097028541666836500":"Main Developer", "1012008556259725398":"Patreon Subscriber"}
}, },
"AllowedHosts": "*", "AllowedHosts": "*",
"Kestrel": { "Kestrel": {

View file

@ -1,2 +1,2 @@
@echo off @echo off
docker compose -f compose\mare-standalone.yml -p standalone up docker compose -f compose\mare-standalone.yml -p standalone up -d

View file

@ -0,0 +1,10 @@
# Default ignored files
/shelf/
/workspace.xml
# Rider ignored files
/modules.xml
/.idea.MareSynchronosServer.iml
/projectSettingsUpdater.xml
/contentModel.xml
# Editor-based HTTP Client requests
/httpRequests/

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
</project>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="UserContentModel">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
</component>
</project>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
<mapping directory="$PROJECT_DIR$/../MareAPI" vcs="Git" />
</component>
</project>

View file

@ -19,4 +19,9 @@
<ProjectReference Include="..\MareSynchronosShared\MareSynchronosShared.csproj" /> <ProjectReference Include="..\MareSynchronosShared\MareSynchronosShared.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Controllers\" />
<Folder Include="Services\" />
</ItemGroup>
</Project> </Project>

View file

@ -1,143 +0,0 @@
using MareSynchronosShared;
using MareSynchronosShared.Services;
using MareSynchronosShared.Utils.Configuration;
using MaxMind.GeoIP2;
namespace MareSynchronosAuthService.Services;
public class GeoIPService : IHostedService
{
private readonly ILogger<GeoIPService> _logger;
private readonly IConfigurationService<AuthServiceConfiguration> _mareConfiguration;
private bool _useGeoIP = false;
private string _cityFile = string.Empty;
private DatabaseReader? _dbReader;
private DateTime _dbLastWriteTime = DateTime.MinValue;
private CancellationTokenSource _fileWriteTimeCheckCts = new();
private bool _processingReload = false;
public GeoIPService(ILogger<GeoIPService> logger,
IConfigurationService<AuthServiceConfiguration> mareConfiguration)
{
_logger = logger;
_mareConfiguration = mareConfiguration;
}
public async Task<string> GetCountryFromIP(IHttpContextAccessor httpContextAccessor)
{
if (!_useGeoIP)
{
return "*";
}
try
{
var ip = httpContextAccessor.GetIpAddress();
using CancellationTokenSource waitCts = new();
waitCts.CancelAfter(TimeSpan.FromSeconds(5));
while (_processingReload) await Task.Delay(100, waitCts.Token).ConfigureAwait(false);
if (_dbReader!.TryCity(ip, out var response))
{
string? continent = response?.Continent.Code;
if (!string.IsNullOrEmpty(continent) &&
string.Equals(continent, "NA", StringComparison.Ordinal)
&& response?.Location.Longitude != null)
{
if (response.Location.Longitude < -102)
{
continent = "NA-W";
}
else
{
continent = "NA-E";
}
}
return continent ?? "*";
}
return "*";
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Error handling Geo IP country in request");
return "*";
}
}
public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("GeoIP module starting update task");
var token = _fileWriteTimeCheckCts.Token;
_ = PeriodicReloadTask(token);
return Task.CompletedTask;
}
private async Task PeriodicReloadTask(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
try
{
_processingReload = true;
var useGeoIP = _mareConfiguration.GetValueOrDefault(nameof(AuthServiceConfiguration.UseGeoIP), false);
var cityFile = _mareConfiguration.GetValueOrDefault(nameof(AuthServiceConfiguration.GeoIPDbCityFile), string.Empty);
DateTime lastWriteTime = DateTime.MinValue;
if (File.Exists(cityFile))
{
lastWriteTime = new FileInfo(cityFile).LastWriteTimeUtc;
}
if (useGeoIP && (!string.Equals(cityFile, _cityFile, StringComparison.OrdinalIgnoreCase) || lastWriteTime > _dbLastWriteTime))
{
_cityFile = cityFile;
if (!File.Exists(_cityFile)) throw new FileNotFoundException($"Could not open GeoIP City Database, path does not exist: {_cityFile}");
_dbReader?.Dispose();
_dbReader = null;
_dbReader = new DatabaseReader(_cityFile);
_dbLastWriteTime = lastWriteTime;
_ = _dbReader.City("8.8.8.8").Continent;
_logger.LogInformation($"Loaded GeoIP city file from {_cityFile}");
if (_useGeoIP != useGeoIP)
{
_logger.LogInformation("GeoIP module is now enabled");
_useGeoIP = useGeoIP;
}
}
if (_useGeoIP != useGeoIP && !useGeoIP)
{
_logger.LogInformation("GeoIP module is now disabled");
_useGeoIP = useGeoIP;
}
}
catch (Exception e)
{
_logger.LogWarning(e, "Error during periodic GeoIP module reload task, disabling GeoIP");
_useGeoIP = false;
}
finally
{
_processingReload = false;
}
await Task.Delay(TimeSpan.FromMinutes(1)).ConfigureAwait(false);
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
_fileWriteTimeCheckCts.Cancel();
_fileWriteTimeCheckCts.Dispose();
_dbReader?.Dispose();
return Task.CompletedTask;
}
}

View file

@ -7,8 +7,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MareSynchronosServer", "Mar
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MareSynchronos.API", "..\MareAPI\MareSynchronosAPI\MareSynchronos.API.csproj", "{326BFB1B-5571-47A6-8513-1FFDB32D53B0}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MareSynchronos.API", "..\MareAPI\MareSynchronosAPI\MareSynchronos.API.csproj", "{326BFB1B-5571-47A6-8513-1FFDB32D53B0}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MareSynchronosServerTest", "MareSynchronosServerTest\MareSynchronosServerTest.csproj", "{25A82A2A-35C2-4EE0-A0E8-DFDD77978DDA}"
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}"
@ -36,10 +34,6 @@ Global
{326BFB1B-5571-47A6-8513-1FFDB32D53B0}.Debug|Any CPU.Build.0 = Debug|Any CPU {326BFB1B-5571-47A6-8513-1FFDB32D53B0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{326BFB1B-5571-47A6-8513-1FFDB32D53B0}.Release|Any CPU.ActiveCfg = Release|Any CPU {326BFB1B-5571-47A6-8513-1FFDB32D53B0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{326BFB1B-5571-47A6-8513-1FFDB32D53B0}.Release|Any CPU.Build.0 = Release|Any CPU {326BFB1B-5571-47A6-8513-1FFDB32D53B0}.Release|Any CPU.Build.0 = Release|Any CPU
{25A82A2A-35C2-4EE0-A0E8-DFDD77978DDA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{25A82A2A-35C2-4EE0-A0E8-DFDD77978DDA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{25A82A2A-35C2-4EE0-A0E8-DFDD77978DDA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{25A82A2A-35C2-4EE0-A0E8-DFDD77978DDA}.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}.Release|Any CPU.ActiveCfg = Release|Any CPU {67B1461D-E215-4BA8-A64D-E1836724D5E6}.Release|Any CPU.ActiveCfg = Release|Any CPU

View file

@ -1,3 +1,3 @@
namespace MareSynchronosAuthService.Authentication; namespace MareSynchronosServer.Authentication;
public record SecretKeyAuthReply(bool Success, string? Uid, string? PrimaryUid, string? Alias, bool TempBan, bool Permaban, bool MarkedForBan); public record SecretKeyAuthReply(bool Success, string? Uid, string? PrimaryUid, string? Alias, bool TempBan, bool Permaban, bool MarkedForBan);

View file

@ -1,4 +1,4 @@
namespace MareSynchronosAuthService.Authentication; namespace MareSynchronosServer.Authentication;
internal record SecretKeyFailedAuthorization internal record SecretKeyFailedAuthorization
{ {

View file

@ -1,5 +1,4 @@
using MareSynchronosAuthService.Authentication; using MareSynchronosAuthService.Services;
using MareSynchronosAuthService.Services;
using MareSynchronosShared.Data; using MareSynchronosShared.Data;
using MareSynchronosShared.Models; using MareSynchronosShared.Models;
using MareSynchronosShared.Services; using MareSynchronosShared.Services;
@ -13,6 +12,7 @@ using System.Globalization;
using System.IdentityModel.Tokens.Jwt; using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims; using System.Security.Claims;
using System.Text; using System.Text;
using MareSynchronosServer.Authentication;
namespace MareSynchronosAuthService.Controllers; namespace MareSynchronosAuthService.Controllers;
@ -24,18 +24,16 @@ public abstract class AuthControllerBase : Controller
protected readonly IDbContextFactory<MareDbContext> MareDbContextFactory; protected readonly IDbContextFactory<MareDbContext> MareDbContextFactory;
protected readonly SecretKeyAuthenticatorService SecretKeyAuthenticatorService; protected readonly SecretKeyAuthenticatorService SecretKeyAuthenticatorService;
private readonly IDatabase _redis; private readonly IDatabase _redis;
private readonly GeoIPService _geoIPProvider;
protected AuthControllerBase(ILogger logger, protected AuthControllerBase(ILogger logger,
IHttpContextAccessor accessor, IDbContextFactory<MareDbContext> mareDbContextFactory, IHttpContextAccessor accessor, IDbContextFactory<MareDbContext> mareDbContextFactory,
SecretKeyAuthenticatorService secretKeyAuthenticatorService, SecretKeyAuthenticatorService secretKeyAuthenticatorService,
IConfigurationService<AuthServiceConfiguration> configuration, IConfigurationService<AuthServiceConfiguration> configuration,
IDatabase redisDb, GeoIPService geoIPProvider) IDatabase redisDb)
{ {
Logger = logger; Logger = logger;
HttpAccessor = accessor; HttpAccessor = accessor;
_redis = redisDb; _redis = redisDb;
_geoIPProvider = geoIPProvider;
MareDbContextFactory = mareDbContextFactory; MareDbContextFactory = mareDbContextFactory;
SecretKeyAuthenticatorService = secretKeyAuthenticatorService; SecretKeyAuthenticatorService = secretKeyAuthenticatorService;
Configuration = configuration; Configuration = configuration;
@ -106,7 +104,6 @@ public abstract class AuthControllerBase : Controller
new Claim(MareClaimTypes.CharaIdent, charaIdent), new Claim(MareClaimTypes.CharaIdent, charaIdent),
new Claim(MareClaimTypes.Alias, alias), new Claim(MareClaimTypes.Alias, alias),
new Claim(MareClaimTypes.Expires, DateTime.UtcNow.AddHours(6).Ticks.ToString(CultureInfo.InvariantCulture)), new Claim(MareClaimTypes.Expires, DateTime.UtcNow.AddHours(6).Ticks.ToString(CultureInfo.InvariantCulture)),
new Claim(MareClaimTypes.Continent, await _geoIPProvider.GetCountryFromIP(HttpAccessor))
}); });
return Content(token.RawData); return Content(token.RawData);

View file

@ -20,6 +20,8 @@ public class ClientMessageController : Controller
_hubContext = hubContext; _hubContext = hubContext;
} }
[Route("sendMessage")] [Route("sendMessage")]
[HttpPost] [HttpPost]
public async Task<IActionResult> SendMessage(ClientMessage msg) public async Task<IActionResult> SendMessage(ClientMessage msg)

View file

@ -19,9 +19,9 @@ public class JwtController : AuthControllerBase
IHttpContextAccessor accessor, IDbContextFactory<MareDbContext> mareDbContextFactory, IHttpContextAccessor accessor, IDbContextFactory<MareDbContext> mareDbContextFactory,
SecretKeyAuthenticatorService secretKeyAuthenticatorService, SecretKeyAuthenticatorService secretKeyAuthenticatorService,
IConfigurationService<AuthServiceConfiguration> configuration, IConfigurationService<AuthServiceConfiguration> configuration,
IDatabase redisDb, GeoIPService geoIPProvider) IDatabase redisDb)
: base(logger, accessor, mareDbContextFactory, secretKeyAuthenticatorService, : base(logger, accessor, mareDbContextFactory, secretKeyAuthenticatorService,
configuration, redisDb, geoIPProvider) configuration, redisDb)
{ {
} }

View file

@ -29,9 +29,9 @@ public class OAuthController : AuthControllerBase
IHttpContextAccessor accessor, IDbContextFactory<MareDbContext> mareDbContext, IHttpContextAccessor accessor, IDbContextFactory<MareDbContext> mareDbContext,
SecretKeyAuthenticatorService secretKeyAuthenticatorService, SecretKeyAuthenticatorService secretKeyAuthenticatorService,
IConfigurationService<AuthServiceConfiguration> configuration, IConfigurationService<AuthServiceConfiguration> configuration,
IDatabase redisDb, GeoIPService geoIPProvider) IDatabase redisDb)
: base(logger, accessor, mareDbContext, secretKeyAuthenticatorService, : base(logger, accessor, mareDbContext, secretKeyAuthenticatorService,
configuration, redisDb, geoIPProvider) configuration, redisDb)
{ {
} }

View file

@ -25,6 +25,7 @@
<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="MaxMind.GeoIP2" Version="5.2.0" />
<PackageReference Include="Meziantou.Analyzer" Version="2.0.184"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.184">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

View file

@ -1,5 +1,5 @@
using System.Collections.Concurrent; using System.Collections.Concurrent;
using MareSynchronosAuthService.Authentication; using MareSynchronosServer.Authentication;
using MareSynchronosShared.Data; using MareSynchronosShared.Data;
using MareSynchronosShared.Metrics; using MareSynchronosShared.Metrics;
using MareSynchronosShared.Models; using MareSynchronosShared.Models;

View file

@ -18,6 +18,8 @@ using StackExchange.Redis.Extensions.Core.Configuration;
using System.Net; using System.Net;
using StackExchange.Redis.Extensions.System.Text.Json; using StackExchange.Redis.Extensions.System.Text.Json;
using MareSynchronos.API.SignalR; using MareSynchronos.API.SignalR;
using MareSynchronosAuthService.Controllers;
using MareSynchronosAuthService.Services;
using MessagePack; using MessagePack;
using MessagePack.Resolvers; using MessagePack.Resolvers;
using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Controllers;
@ -44,8 +46,16 @@ public class Startup
services.AddHttpContextAccessor(); services.AddHttpContextAccessor();
services.AddTransient(_ => Configuration); services.AddTransient(_ => Configuration);
services.AddSingleton<IConfigurationService<AuthServiceConfiguration>, MareConfigurationServiceServer<AuthServiceConfiguration>>();
var mareConfig = Configuration.GetRequiredSection("MareSynchronos"); var mareConfig = Configuration.GetRequiredSection("MareSynchronos");
ConfigureRedis(services, mareConfig);
services.AddSingleton<SecretKeyAuthenticatorService>();
// services.AddSingleton<GeoIPService>();
// services.AddHostedService(provider => provider.GetRequiredService<GeoIPService>());
services.AddSingleton<ServerTokenGenerator>();
services.Configure<AuthServiceConfiguration>(Configuration.GetRequiredSection("MareSynchronos"));
// configure metrics // configure metrics
ConfigureMetrics(services); ConfigureMetrics(services);
@ -71,7 +81,8 @@ 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))); a.FeatureProviders.Add(new AllowedControllersFeatureProvider(typeof(MareServerConfigurationController), typeof(MareBaseConfigurationController), typeof(ClientMessageController), typeof(JwtController), typeof(OAuthController)));
} }
else else
{ {
@ -80,6 +91,50 @@ public class Startup
}); });
} }
private void ConfigureRedis(IServiceCollection services, IConfigurationSection mareConfig)
{
// configure redis for SignalR
var redisConnection = mareConfig.GetValue(nameof(ServerConfiguration.RedisConnectionString), string.Empty);
var options = ConfigurationOptions.Parse(redisConnection);
var endpoint = options.EndPoints[0];
string address = "";
int port = 0;
if (endpoint is DnsEndPoint dnsEndPoint) { address = dnsEndPoint.Host; port = dnsEndPoint.Port; }
if (endpoint is IPEndPoint ipEndPoint) { address = ipEndPoint.Address.ToString(); port = ipEndPoint.Port; }
/*
var redisConfiguration = new RedisConfiguration()
{
AbortOnConnectFail = true,
KeyPrefix = "",
Hosts = new RedisHost[]
{
new RedisHost(){ Host = address, Port = port },
},
AllowAdmin = true,
ConnectTimeout = options.ConnectTimeout,
Database = 0,
Ssl = false,
Password = options.Password,
ServerEnumerationStrategy = new ServerEnumerationStrategy()
{
Mode = ServerEnumerationStrategy.ModeOptions.All,
TargetRole = ServerEnumerationStrategy.TargetRoleOptions.Any,
UnreachableServerAction = ServerEnumerationStrategy.UnreachableServerActionOptions.Throw,
},
MaxValueLength = 1024,
PoolSize = mareConfig.GetValue(nameof(ServerConfiguration.RedisPool), 50),
SyncTimeout = options.SyncTimeout,
};*/
var muxer = ConnectionMultiplexer.Connect(options);
var db = muxer.GetDatabase();
services.AddSingleton<IDatabase>(db);
_logger.LogInformation("Setting up Redis to connect to {host}:{port}", address, port);
}
private void ConfigureMareServices(IServiceCollection services, IConfigurationSection mareConfig) private void ConfigureMareServices(IServiceCollection services, IConfigurationSection mareConfig)
{ {
bool isMainServer = mareConfig.GetValue<Uri>(nameof(ServerConfiguration.MainServerAddress), defaultValue: null) == null; bool isMainServer = mareConfig.GetValue<Uri>(nameof(ServerConfiguration.MainServerAddress), defaultValue: null) == null;
@ -90,8 +145,14 @@ public class Startup
services.AddSingleton<ServerTokenGenerator>(); services.AddSingleton<ServerTokenGenerator>();
services.AddSingleton<SystemInfoService>(); services.AddSingleton<SystemInfoService>();
services.AddSingleton<OnlineSyncedPairCacheService>(); services.AddSingleton<OnlineSyncedPairCacheService>();
services.Configure<AuthServiceConfiguration>(Configuration.GetRequiredSection("MareSynchronos"));
services.AddHostedService(provider => provider.GetService<SystemInfoService>()); services.AddHostedService(provider => provider.GetService<SystemInfoService>());
services.AddHttpLogging(o => { });
// configure services based on main server status // configure services based on main server status
_logger.LogWarning($"IsMainServer = {isMainServer}");
ConfigureServicesBasedOnShardType(services, mareConfig, isMainServer); ConfigureServicesBasedOnShardType(services, mareConfig, isMainServer);
services.AddSingleton(s => new MareCensus(s.GetRequiredService<ILogger<MareCensus>>())); services.AddSingleton(s => new MareCensus(s.GetRequiredService<ILogger<MareCensus>>()));
@ -193,6 +254,8 @@ public class Startup
private static void ConfigureAuthorization(IServiceCollection services) private static void ConfigureAuthorization(IServiceCollection services)
{ {
services.AddTransient<IAuthorizationHandler, RedisDbUserRequirementHandler>();
services.AddTransient<IAuthorizationHandler, ExistingUserRequirementHandler>();
services.AddTransient<IAuthorizationHandler, UserRequirementHandler>(); services.AddTransient<IAuthorizationHandler, UserRequirementHandler>();
services.AddTransient<IAuthorizationHandler, ValidTokenRequirementHandler>(); services.AddTransient<IAuthorizationHandler, ValidTokenRequirementHandler>();
services.AddTransient<IAuthorizationHandler, ValidTokenHubRequirementHandler>(); services.AddTransient<IAuthorizationHandler, ValidTokenHubRequirementHandler>();
@ -210,6 +273,8 @@ public class Startup
}; };
}); });
services.AddLogging();
services.AddAuthentication(o => services.AddAuthentication(o =>
{ {
o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
@ -222,6 +287,13 @@ public class Startup
options.DefaultPolicy = new AuthorizationPolicyBuilder() options.DefaultPolicy = new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme) .AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser().Build(); .RequireAuthenticatedUser().Build();
options.AddPolicy("OAuthToken", policy =>
{
policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme);
policy.AddRequirements(new ValidTokenRequirement());
policy.AddRequirements(new ExistingUserRequirement());
policy.RequireClaim(MareClaimTypes.OAuthLoginToken, "True");
});
options.AddPolicy("Authenticated", policy => options.AddPolicy("Authenticated", policy =>
{ {
policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme); policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme);
@ -253,6 +325,8 @@ public class Startup
{ {
services.AddDbContextPool<MareDbContext>(options => services.AddDbContextPool<MareDbContext>(options =>
{ {
var defaultConnection = Configuration.GetConnectionString("DefaultConnection");
_logger.LogDebug("Connecting to DB using: '{0}'", defaultConnection);
options.UseNpgsql(Configuration.GetConnectionString("DefaultConnection"), builder => options.UseNpgsql(Configuration.GetConnectionString("DefaultConnection"), builder =>
{ {
builder.MigrationsHistoryTable("_efmigrationshistory", "public"); builder.MigrationsHistoryTable("_efmigrationshistory", "public");
@ -337,12 +411,16 @@ public class Startup
app.UseWebSockets(); app.UseWebSockets();
app.UseHttpMetrics(); app.UseHttpMetrics();
app.UseExceptionHandler("/Error");
var metricServer = new KestrelMetricServer(config.GetValueOrDefault<int>(nameof(MareConfigurationBase.MetricsPort), 4980)); var metricServer = new KestrelMetricServer(config.GetValueOrDefault<int>(nameof(MareConfigurationBase.MetricsPort), 4980));
metricServer.Start(); metricServer.Start();
app.UseAuthentication(); app.UseAuthentication();
app.UseAuthorization(); app.UseAuthorization();
app.UseHttpLogging();
app.UseEndpoints(endpoints => app.UseEndpoints(endpoints =>
{ {
@ -356,10 +434,11 @@ public class Startup
endpoints.MapHealthChecks("/health").AllowAnonymous(); endpoints.MapHealthChecks("/health").AllowAnonymous();
endpoints.MapControllers(); endpoints.MapControllers();
foreach (var source in endpoints.DataSources.SelectMany(e => e.Endpoints).Cast<RouteEndpoint>()) foreach (var source in endpoints.DataSources.SelectMany(e => e.Endpoints).Cast<RouteEndpoint>())
{ {
if (source == null) continue; if (source == null) continue;
_logger.LogInformation("Endpoint: {url} ", source.RoutePattern.RawText); _logger.LogWarning("Endpoint: {url} ", source.RoutePattern.RawText);
} }
}); });

View file

@ -117,8 +117,8 @@ internal class DiscordBot : IHostedService
_botServices.UpdateGuild(guild); _botServices.UpdateGuild(guild);
await _botServices.LogToChannel("Bot startup complete.").ConfigureAwait(false); await _botServices.LogToChannel("Bot startup complete.").ConfigureAwait(false);
_ = UpdateVanityRoles(guild, _clientConnectedCts.Token); _ = UpdateVanityRoles(guild, _clientConnectedCts.Token);
_ = RemoveUsersNotInVanityRole(_clientConnectedCts.Token); // _ = RemoveUsersNotInVanityRole(_clientConnectedCts.Token);
_ = RemoveUnregisteredUsers(_clientConnectedCts.Token); // 1_ = RemoveUnregisteredUsers(_clientConnectedCts.Token);
} }
private async Task UpdateVanityRoles(RestGuild guild, CancellationToken token) private async Task UpdateVanityRoles(RestGuild guild, CancellationToken token)

View file

@ -1,215 +0,0 @@
using Discord;
using Discord.Interactions;
using MareSynchronosShared.Utils.Configuration;
using System.Text.Json;
namespace MareSynchronosServices.Discord;
public partial class MareWizardModule : InteractionModuleBase
{
private const int _totalAprilFoolsRoles = 200;
private const string _persistentFileName = "april2024.json";
private static readonly SemaphoreSlim _fileSemaphore = new(1, 1);
[ComponentInteraction("wizard-fools")]
public async Task ComponentFools()
{
if (!(await ValidateInteraction().ConfigureAwait(false))) return;
_logger.LogInformation("{method}:{userId}", nameof(ComponentFools), Context.Interaction.User.Id);
EmbedBuilder eb = new();
eb.WithTitle("WorryCoin™ and MareToken© Balance");
eb.WithColor(Color.Gold);
eb.WithDescription("You currently have" + Environment.NewLine + Environment.NewLine
+ "**200000** MaTE©" + Environment.NewLine
+ "**0** WorryCoin™" + Environment.NewLine + Environment.NewLine
+ "You have no payment method set up. Press the button below to add a payment method.");
ComponentBuilder cb = new();
AddHome(cb);
cb.WithButton("Add Payment Method", "wizard-fools-start", ButtonStyle.Primary, emote: new Emoji("💲"));
await ModifyInteraction(eb, cb).ConfigureAwait(false);
}
[ComponentInteraction("wizard-fools-start")]
public async Task ComponentFoolsStart()
{
if (!(await ValidateInteraction().ConfigureAwait(false))) return;
_logger.LogInformation("{method}:{userId}", nameof(ComponentFoolsStart), Context.Interaction.User.Id);
EmbedBuilder eb = new();
var user = await Context.Guild.GetUserAsync(Context.User.Id).ConfigureAwait(false);
bool userIsInPermanentVanityRole = _botServices.VanityRoles.Where(v => !v.Value.Contains('$', StringComparison.Ordinal))
.Select(v => v.Key).Any(u => user.RoleIds.Contains(u.Id)) || !_botServices.VanityRoles.Any();
ComponentBuilder cb = new();
AddHome(cb);
var participatedUsers = await GetParticipants().ConfigureAwait(false);
var remainingRoles = _totalAprilFoolsRoles - participatedUsers.Count(c => c.Value == true);
if (userIsInPermanentVanityRole)
{
eb.WithColor(Color.Green);
eb.WithTitle("Happy April Fools!");
eb.WithDescription("Thank you for participating in Mares 2024 April Fools event."
+ Environment.NewLine + Environment.NewLine
+ "As you might have already guessed from the post, nothing that was written there had any truth behind it."
+ Environment.NewLine + Environment.NewLine
+ "This entire thing was a jab at the ridiculousness of cryptocurrency, microtransactions and games featuring multiple currencies. I hope you enjoyed the announcement post!"
+ Environment.NewLine + Environment.NewLine
+ "__As you already have a role that gives you a permanent Vanity ID, you cannot win another one here. "
+ "However, tell your friends as this bot will give them a chance to win one of " + _totalAprilFoolsRoles + " lifetime vanity roles.__"
+ Environment.NewLine + Environment.NewLine
+ "The giveaway is active until <t:" + (new DateTime(2024, 04, 01, 23, 59, 59, DateTimeKind.Utc).Subtract(DateTime.UnixEpoch).TotalSeconds) + ":f>.");
}
else if (participatedUsers.ContainsKey(Context.User.Id))
{
eb.WithColor(Color.Orange);
eb.WithTitle("Happy April Fools!");
eb.WithDescription("Thank you for participating in Mares 2024 April Fools event."
+ Environment.NewLine + Environment.NewLine
+ "As you might have already guessed from the post, nothing that was written there had any truth behind it."
+ Environment.NewLine + Environment.NewLine
+ "This entire thing was a jab at the ridiculousness of cryptocurrency, microtransactions and games featuring multiple currencies. I hope you enjoyed the announcement post!"
+ Environment.NewLine + Environment.NewLine
+ "__You already participated in the giveaway of the permanent Vanity roles and therefore cannot participate again. Better luck next time!__");
}
else if (remainingRoles > 0)
{
eb.WithColor(Color.Green);
eb.WithTitle("Happy April Fools!");
eb.WithDescription("Thank you for participating in Mares 2024 April Fools event."
+ Environment.NewLine + Environment.NewLine
+ "As you might have already guessed from the post, nothing that was written there had any truth behind it."
+ Environment.NewLine + Environment.NewLine
+ "This entire thing was a jab at the ridiculousness of cryptocurrency, microtransactions and games featuring multiple currencies. I hope you enjoyed the announcement post!"
+ Environment.NewLine + Environment.NewLine
+ "You have currently no permanent role that allows you to set a Vanity ID, however I am giving away a total of " + _totalAprilFoolsRoles + " permanent vanity roles "
+ "(" + remainingRoles + " still remain) and you can win one using this bot!"
+ Environment.NewLine + Environment.NewLine
+ "To win you simply have to pick one of the buttons labeled \"Win\" below this post. Which button will win is random. "
+ "There is a 1 in 5 chance that you can win the role. __You can only participate once.__"
+ Environment.NewLine + Environment.NewLine
+ "The giveaway is active until <t:" + (new DateTime(2024, 04, 01, 23, 59, 59, DateTimeKind.Utc).Subtract(DateTime.UnixEpoch).TotalSeconds) + ":f>.");
cb.WithButton("Win", "wizard-fools-win:1", ButtonStyle.Primary, new Emoji("1⃣"));
cb.WithButton("Win", "wizard-fools-win:2", ButtonStyle.Primary, new Emoji("2⃣"));
cb.WithButton("Win", "wizard-fools-win:3", ButtonStyle.Primary, new Emoji("3⃣"));
cb.WithButton("Win", "wizard-fools-win:4", ButtonStyle.Primary, new Emoji("4⃣"));
cb.WithButton("Win", "wizard-fools-win:5", ButtonStyle.Primary, new Emoji("5⃣"));
}
else
{
eb.WithColor(Color.Orange);
eb.WithTitle("Happy April Fools!");
eb.WithDescription("Thank you for participating in Mares 2024 April Fools event."
+ Environment.NewLine + Environment.NewLine
+ "As you might have already guessed from the post, nothing that was written there had any truth behind it."
+ Environment.NewLine + Environment.NewLine
+ "This entire thing was a jab at the ridiculousness of cryptocurrency, microtransactions and games featuring multiple currencies. I hope you enjoyed the announcement post!"
+ Environment.NewLine + Environment.NewLine
+ "__I have been giving away " + _totalAprilFoolsRoles + " permanent Vanity ID roles for this server, however you are sadly too late as they ran out by now. "
+ "Better luck next year with whatever I will come up with!__");
}
await ModifyInteraction(eb, cb).ConfigureAwait(false);
}
[ComponentInteraction("wizard-fools-win:*")]
public async Task ComponentFoolsWin(int number)
{
if (!(await ValidateInteraction().ConfigureAwait(false))) return;
_logger.LogInformation("{method}:{userId}", nameof(ComponentFoolsWin), Context.Interaction.User.Id);
var winningNumber = new Random().Next(1, 6);
EmbedBuilder eb = new();
ComponentBuilder cb = new();
AddHome(cb);
bool hasWon = winningNumber == number;
await WriteParticipants(Context.Interaction.User.Id, hasWon).ConfigureAwait(false);
if (hasWon)
{
eb.WithColor(Color.Gold);
eb.WithTitle("Congratulations you are winner!");
eb.WithDescription("You, by pure accident and sheer luck, picked the right number and have won yourself a lifetime Vanity ID role on this server!"
+ Environment.NewLine + Environment.NewLine
+ "The role will remain as long as you remain on this server, if you happen to leave it you will not get the role back."
+ Environment.NewLine + Environment.NewLine
+ "Head over to Home and to the Vanity IDs section to set it up for your account!"
+ Environment.NewLine + Environment.NewLine
+ "Once again, thank you for participating and have a great day.");
var user = await Context.Guild.GetUserAsync(Context.User.Id).ConfigureAwait(false);
await user.AddRoleAsync(_mareServicesConfiguration.GetValue<ulong?>(nameof(ServicesConfiguration.DiscordRoleAprilFools2024)).Value).ConfigureAwait(false);
}
else
{
eb.WithColor(Color.Red);
eb.WithTitle("Fortune did not bless you");
eb.WithDescription("You, through sheer misfortune, sadly did not pick the right number. (The winning number was " + winningNumber + ")"
+ Environment.NewLine + Environment.NewLine
+ "Better luck next time!"
+ Environment.NewLine + Environment.NewLine
+ "Once again, thank you for participating and regardless, have a great day.");
}
await ModifyInteraction(eb, cb).ConfigureAwait(false);
}
private async Task<Dictionary<ulong, bool>> GetParticipants()
{
await _fileSemaphore.WaitAsync().ConfigureAwait(false);
try
{
if (!File.Exists(_persistentFileName))
{
return new();
}
var json = await File.ReadAllTextAsync(_persistentFileName).ConfigureAwait(false);
return JsonSerializer.Deserialize<Dictionary<ulong, bool>>(json);
}
catch
{
return new();
}
finally
{
_fileSemaphore.Release();
}
}
private async Task WriteParticipants(ulong participant, bool win)
{
await _fileSemaphore.WaitAsync().ConfigureAwait(false);
try
{
Dictionary<ulong, bool> participants = new();
if (File.Exists(_persistentFileName))
{
try
{
var json = await File.ReadAllTextAsync(_persistentFileName).ConfigureAwait(false);
participants = JsonSerializer.Deserialize<Dictionary<ulong, bool>>(json);
}
catch
{
// probably empty file just deal with it
}
}
participants[participant] = win;
await File.WriteAllTextAsync(_persistentFileName, JsonSerializer.Serialize(participants)).ConfigureAwait(false);
}
finally
{
_fileSemaphore.Release();
}
}
}

View file

@ -24,13 +24,14 @@ public partial class MareWizardModule
eb.WithColor(Color.Blue); eb.WithColor(Color.Blue);
eb.WithTitle("Start Registration"); eb.WithTitle("Start Registration");
eb.WithDescription("Here you can start the registration process with the Mare Synchronos server of this Discord." + Environment.NewLine + Environment.NewLine eb.WithDescription("Here you can start the registration process with the Mare Synchronos server of this Discord." + Environment.NewLine + Environment.NewLine
+ "- Have your Lodestone URL ready (i.e. https://eu.finalfantasyxiv.com/lodestone/character/XXXXXXXXX)" + Environment.NewLine /*+ "- Have your Lodestone URL ready (i.e. https://eu.finalfantasyxiv.com/lodestone/character/XXXXXXXXX)" + Environment.NewLine
+ " - The registration requires you to modify your Lodestone profile with a generated code for verification" + Environment.NewLine + " - The registration requires you to modify your Lodestone profile with a generated code for verification" + Environment.NewLine*/
+ "- Do not use this on mobile because you will need to be able to copy the generated secret key" + Environment.NewLine + "- Do not use this on mobile because you will need to be able to copy the generated secret key" + Environment.NewLine
+ "# Follow the bot instructions precisely. Slow down and read."); + "# Follow the bot instructions precisely. Slow down and read.");
ComponentBuilder cb = new(); ComponentBuilder cb = new();
AddHome(cb); AddHome(cb);
cb.WithButton("Start Registration", "wizard-register-start", ButtonStyle.Primary, emote: new Emoji("🌒")); cb.WithButton("Register", "wizard-register-verify-check:OK", ButtonStyle.Primary, emote: new Emoji("❓"));
// cb.WithButton("Start Registration", "wizard-register-start", ButtonStyle.Primary, emote: new Emoji("🌒"));
await ModifyInteraction(eb, cb).ConfigureAwait(false); await ModifyInteraction(eb, cb).ConfigureAwait(false);
} }
@ -102,74 +103,29 @@ public partial class MareWizardModule
EmbedBuilder eb = new(); EmbedBuilder eb = new();
ComponentBuilder cb = new(); ComponentBuilder cb = new();
bool stillEnqueued = _botServices.VerificationQueue.Any(k => k.Key == Context.User.Id);
bool verificationRan = _botServices.DiscordVerifiedUsers.TryGetValue(Context.User.Id, out bool verified);
bool registerSuccess = false;
if (!verificationRan)
{
if (stillEnqueued)
{
eb.WithColor(Color.Gold);
eb.WithTitle("Your verification is still pending");
eb.WithDescription("Please try again and click Check in a few seconds");
cb.WithButton("Cancel", "wizard-register", ButtonStyle.Secondary, emote: new Emoji("❌"));
cb.WithButton("Check", "wizard-register-verify-check:" + verificationCode, ButtonStyle.Primary, emote: new Emoji("❓"));
}
else
{
eb.WithColor(Color.Red);
eb.WithTitle("Something went wrong");
eb.WithDescription("Your verification was processed but did not arrive properly. Please try to start the registration from the start.");
cb.WithButton("Restart", "wizard-register", ButtonStyle.Primary, emote: new Emoji("🔁"));
}
}
else
{
if (verified)
{
eb.WithColor(Color.Green); eb.WithColor(Color.Green);
using var db = await GetDbContext().ConfigureAwait(false); using var db = await GetDbContext().ConfigureAwait(false);
var (uid, key) = await HandleAddUser(db).ConfigureAwait(false); var (uid, key) = await HandleAddUser(db).ConfigureAwait(false);
eb.WithTitle($"Registration successful, your UID: {uid}"); eb.WithTitle($"Registration successful, your UID: {uid}");
eb.WithDescription("This is your private secret key. Do not share this private secret key with anyone. **If you lose it, it is irrevocably lost.**" eb.WithDescription("This is your private secret key. Do not share this private secret key with anyone. **If you lose it, it is irrevocably lost.**"
/*+ Environment.NewLine + Environment.NewLine
+ "**__NOTE: Secret keys are considered legacy. Using the suggested OAuth2 authentication in Mare, you do not need to use this Secret Key.__**"*/
+ Environment.NewLine + Environment.NewLine + Environment.NewLine + Environment.NewLine
+ "**__NOTE: Secret keys are considered legacy. Using the suggested OAuth2 authentication in Mare, you do not need to use this Secret Key.__**" + $"**`{key}`**"
+ Environment.NewLine + Environment.NewLine + Environment.NewLine + Environment.NewLine
+ $"||**`{key}`**||" /*+ "If you want to continue using legacy authentication, enter this key in Mare Synchronos and hit save to connect to the service."
+ Environment.NewLine + Environment.NewLine + Environment.NewLine*/
+ "If you want to continue using legacy authentication, enter this key in Mare Synchronos and hit save to connect to the service."
+ Environment.NewLine
+ "__NOTE: The Secret Key only contains the letters ABCDEF and numbers 0 - 9.__" + "__NOTE: The Secret Key only contains the letters ABCDEF and numbers 0 - 9.__"
+ Environment.NewLine + Environment.NewLine
+ "You should connect as soon as possible to not get caught by the automatic cleanup process." //+ "You should connect as soon as possible to not get caught by the automatic cleanup process."
+ Environment.NewLine //+ Environment.NewLine
+ "Have fun."); + "Have fun.");
AddHome(cb); AddHome(cb);
registerSuccess = true;
}
else
{
eb.WithColor(Color.Gold);
eb.WithTitle("Failed to verify registration");
eb.WithDescription("The bot was not able to find the required verification code on your Lodestone profile."
+ Environment.NewLine + Environment.NewLine
+ "Please restart your verification process, make sure to save your profile _twice_ for it to be properly saved."
+ Environment.NewLine + Environment.NewLine
+ "If this link does not lead to your profile edit page, you __need__ to configure the privacy settings first: https://na.finalfantasyxiv.com/lodestone/my/setting/profile/"
+ Environment.NewLine + Environment.NewLine
+ "**Make sure your profile is set to public (All Users) for your character. The bot cannot read profiles with privacy settings set to \"logged in\" or \"private\".**"
+ Environment.NewLine + Environment.NewLine
+ "## You __need__ to enter following the code this bot provided onto your Lodestone in the character profile:"
+ Environment.NewLine + Environment.NewLine
+ "**`" + verificationCode + "`**");
cb.WithButton("Cancel", "wizard-register", emote: new Emoji("❌"));
cb.WithButton("Retry", "wizard-register-verify:" + verificationCode, ButtonStyle.Primary, emote: new Emoji("🔁"));
}
}
await ModifyInteraction(eb, cb).ConfigureAwait(false); await ModifyInteraction(eb, cb).ConfigureAwait(false);
if (registerSuccess)
await _botServices.AddRegisteredRoleAsync(Context.Interaction.User).ConfigureAwait(false); // if (registerSuccess)await _botServices.AddRegisteredRoleAsync(Context.Interaction.User).ConfigureAwait(false);
} }
private async Task<(bool, string)> HandleRegisterModalAsync(EmbedBuilder embed, LodestoneModal arg) private async Task<(bool, string)> HandleRegisterModalAsync(EmbedBuilder embed, LodestoneModal arg)
@ -262,7 +218,13 @@ public partial class MareWizardModule
private async Task<(string, string)> HandleAddUser(MareDbContext db) private async Task<(string, string)> HandleAddUser(MareDbContext db)
{ {
_logger.LogWarning($"Adding User: {Context.User.Username}");
var lodestoneAuth = db.LodeStoneAuth.SingleOrDefault(u => u.DiscordId == Context.User.Id); var lodestoneAuth = db.LodeStoneAuth.SingleOrDefault(u => u.DiscordId == Context.User.Id);
if (lodestoneAuth == null) {
lodestoneAuth = new LodeStoneAuth() { DiscordId = Context.User.Id, HashedLodestoneId = $"{Context.User.Id}", User = null, LodestoneAuthString = string.Empty };
await db.LodeStoneAuth.AddAsync(lodestoneAuth).ConfigureAwait(false);
}
var user = new User(); var user = new User();
@ -294,10 +256,12 @@ public partial class MareWizardModule
await db.Users.AddAsync(user).ConfigureAwait(false); await db.Users.AddAsync(user).ConfigureAwait(false);
await db.Auth.AddAsync(auth).ConfigureAwait(false); await db.Auth.AddAsync(auth).ConfigureAwait(false);
lodestoneAuth.StartedAt = null; lodestoneAuth.StartedAt = null;
lodestoneAuth.User = user; lodestoneAuth.User = user;
lodestoneAuth.LodestoneAuthString = null; lodestoneAuth.LodestoneAuthString = null;
await db.SaveChangesAsync().ConfigureAwait(false); await db.SaveChangesAsync().ConfigureAwait(false);
_botServices.Logger.LogInformation("User registered: {userUID}:{hashedKey}", user.UID, hashedKey); _botServices.Logger.LogInformation("User registered: {userUID}:{hashedKey}", user.UID, hashedKey);

View file

@ -160,18 +160,17 @@ public partial class MareWizardModule : InteractionModuleBase
+ (!hasAccount ? string.Empty : ("- Check your account status press \" User Info\"" + Environment.NewLine)) + (!hasAccount ? string.Empty : ("- Check your account status press \" User Info\"" + Environment.NewLine))
+ (hasAccount ? string.Empty : ("- Register a new Mare Account press \"🌒 Register\"" + Environment.NewLine)) + (hasAccount ? string.Empty : ("- Register a new Mare Account press \"🌒 Register\"" + Environment.NewLine))
+ (!hasAccount ? string.Empty : ("- You lost your secret key press \"🏥 Recover\"" + Environment.NewLine)) + (!hasAccount ? string.Empty : ("- You lost your secret key press \"🏥 Recover\"" + Environment.NewLine))
+ (hasAccount ? string.Empty : ("- If you have changed your Discord account press \"🔗 Relink\"" + Environment.NewLine)) // + (hasAccount ? string.Empty : ("- If you have changed your Discord account press \"🔗 Relink\"" + Environment.NewLine))
+ (!hasAccount ? string.Empty : ("- Create a secondary UIDs press \"2⃣ Secondary UID\"" + Environment.NewLine)) + (!hasAccount ? string.Empty : ("- Create a secondary UIDs press \"2⃣ Secondary UID\"" + Environment.NewLine))
+ (!hasAccount ? string.Empty : ("- Set a Vanity UID press \"💅 Vanity IDs\"" + Environment.NewLine)) + (!hasAccount ? string.Empty : ("- Set a Vanity UID press \"💅 Vanity IDs\"" + Environment.NewLine))
+ (!hasAccount ? string.Empty : (!isInAprilFoolsMode ? string.Empty : ("- Check your WorryCoin™ and MareToken© balance and add payment options" + Environment.NewLine)))
+ (!hasAccount ? string.Empty : ("- Delete your primary or secondary accounts with \"⚠️ Delete\"")) + (!hasAccount ? string.Empty : ("- Delete your primary or secondary accounts with \"⚠️ Delete\""))
); );
eb.WithColor(Color.Blue); eb.WithColor(Color.Blue);
ComponentBuilder cb = new(); ComponentBuilder cb = new();
if (!hasAccount) if (!hasAccount)
{ {
cb.WithButton("Register", "wizard-register", ButtonStyle.Primary, new Emoji("🌒")); cb.WithButton("Register", "wizard-register-verify-check:OK", ButtonStyle.Primary, new Emoji("🌒"));
cb.WithButton("Relink", "wizard-relink", ButtonStyle.Secondary, new Emoji("🔗")); // cb.WithButton("Relink", "wizard-relink", ButtonStyle.Secondary, new Emoji("🔗"));
} }
else else
{ {
@ -179,10 +178,6 @@ public partial class MareWizardModule : InteractionModuleBase
cb.WithButton("Recover", "wizard-recover", ButtonStyle.Secondary, new Emoji("🏥")); cb.WithButton("Recover", "wizard-recover", ButtonStyle.Secondary, new Emoji("🏥"));
cb.WithButton("Secondary UID", "wizard-secondary", ButtonStyle.Secondary, new Emoji("2⃣")); cb.WithButton("Secondary UID", "wizard-secondary", ButtonStyle.Secondary, new Emoji("2⃣"));
cb.WithButton("Vanity IDs", "wizard-vanity", ButtonStyle.Secondary, new Emoji("💅")); cb.WithButton("Vanity IDs", "wizard-vanity", ButtonStyle.Secondary, new Emoji("💅"));
if (isInAprilFoolsMode)
{
cb.WithButton("WorryCoin™ and MareToken© management", "wizard-fools", ButtonStyle.Primary, new Emoji("💲"));
}
cb.WithButton("Delete", "wizard-delete", ButtonStyle.Danger, new Emoji("⚠️")); cb.WithButton("Delete", "wizard-delete", ButtonStyle.Danger, new Emoji("⚠️"));
} }

3
build_run.bat Normal file
View file

@ -0,0 +1,3 @@
docker build -t darkarchon/mare-synchronos-server:latest . -f Docker\build\Dockerfile-MareSynchronosServer --no-cache --pull --force-rm
docker build -t darkarchon/mare-synchronos-services:latest . -f Docker\build\Dockerfile-MareSynchronosServices --no-cache --pull --force-rm
docker compose -f Docker\run\compose\mare-standalone.yml -p standalone up -d