Merge pull request #295 from Moonlight-Panel/AddIpFiltering
Implemented ip filtering to detect datacenter and vpn ips
This commit is contained in:
@@ -304,6 +304,14 @@ public class ConfigV1
|
||||
public int BlockIpDuration { get; set; } = 15;
|
||||
|
||||
[JsonProperty("ReCaptcha")] public ReCaptchaData ReCaptcha { get; set; } = new();
|
||||
|
||||
[JsonProperty("BlockDatacenterIps")]
|
||||
[Description("If this option is enabled, users with an ip from datacenters will not be able to access the panel")]
|
||||
public bool BlockDatacenterIps { get; set; } = true;
|
||||
|
||||
[JsonProperty("AllowCloudflareIps")]
|
||||
[Description("Allow cloudflare ips to bypass the datacenter ip check")]
|
||||
public bool AllowCloudflareIps { get; set; } = false;
|
||||
}
|
||||
|
||||
public class ReCaptchaData
|
||||
|
||||
@@ -17,7 +17,7 @@ public class StorageService
|
||||
await UpdateResources();
|
||||
|
||||
return;
|
||||
if(IsEmpty(PathBuilder.Dir("storage", "resources")))
|
||||
if (IsEmpty(PathBuilder.Dir("storage", "resources")))
|
||||
{
|
||||
Logger.Info("Default resources not found. Copying default resources");
|
||||
|
||||
@@ -40,64 +40,73 @@ public class StorageService
|
||||
|
||||
private async Task UpdateResources()
|
||||
{
|
||||
Logger.Info("Checking resources");
|
||||
|
||||
var client = new GitHubClient(
|
||||
new ProductHeaderValue("Moonlight-Panel"));
|
||||
|
||||
string user = "Moonlight-Panel";
|
||||
string repo = "Resources";
|
||||
string branch = "main";
|
||||
string resourcesDir = PathBuilder.Dir("storage", "resources");
|
||||
|
||||
async Task CopyDirectory(string dirPath, string localDir)
|
||||
try
|
||||
{
|
||||
IReadOnlyList<RepositoryContent> contents;
|
||||
Logger.Info("Checking resources");
|
||||
|
||||
if(string.IsNullOrEmpty(dirPath))
|
||||
contents = await client.Repository.Content.GetAllContents(user, repo);
|
||||
else
|
||||
contents = await client.Repository.Content.GetAllContents(user, repo, dirPath);
|
||||
var client = new GitHubClient(
|
||||
new ProductHeaderValue("Moonlight-Panel"));
|
||||
|
||||
foreach (var content in contents)
|
||||
var user = "Moonlight-Panel";
|
||||
var repo = "Resources";
|
||||
var resourcesDir = PathBuilder.Dir("storage", "resources");
|
||||
|
||||
async Task CopyDirectory(string dirPath, string localDir)
|
||||
{
|
||||
string localPath = Path.Combine(localDir, content.Name);
|
||||
IReadOnlyList<RepositoryContent> contents;
|
||||
|
||||
if (content.Type == ContentType.File)
|
||||
if (string.IsNullOrEmpty(dirPath))
|
||||
contents = await client.Repository.Content.GetAllContents(user, repo);
|
||||
else
|
||||
contents = await client.Repository.Content.GetAllContents(user, repo, dirPath);
|
||||
|
||||
foreach (var content in contents)
|
||||
{
|
||||
if(content.Name.EndsWith(".gitattributes"))
|
||||
continue;
|
||||
string localPath = Path.Combine(localDir, content.Name);
|
||||
|
||||
if(File.Exists(localPath) && !content.Name.EndsWith(".lang"))
|
||||
continue;
|
||||
|
||||
if (content.Name.EndsWith(".lang") && File.Exists(localPath) &&
|
||||
new FileInfo(localPath).Length == content.Size)
|
||||
if (content.Type == ContentType.File)
|
||||
{
|
||||
Logger.Info($"Skipped language file '{content.Name}'");
|
||||
continue;
|
||||
if (content.Name.EndsWith(".gitattributes"))
|
||||
continue;
|
||||
|
||||
if (File.Exists(localPath) && !content.Name.EndsWith(".lang"))
|
||||
continue;
|
||||
|
||||
if (content.Name.EndsWith(".lang") && File.Exists(localPath) &&
|
||||
new FileInfo(localPath).Length == content.Size)
|
||||
continue;
|
||||
|
||||
var fileContent = await client.Repository.Content.GetRawContent(user, repo, content.Path);
|
||||
Directory.CreateDirectory(localDir); // Ensure the directory exists
|
||||
await File.WriteAllBytesAsync(localPath, fileContent);
|
||||
|
||||
Logger.Debug($"Synced file '{content.Path}'");
|
||||
}
|
||||
else if (content.Type == ContentType.Dir)
|
||||
{
|
||||
await CopyDirectory(content.Path, localPath);
|
||||
}
|
||||
|
||||
var fileContent = await client.Repository.Content.GetRawContent(user, repo, content.Path);
|
||||
Directory.CreateDirectory(localDir); // Ensure the directory exists
|
||||
await File.WriteAllBytesAsync(localPath, fileContent);
|
||||
|
||||
Logger.Debug($"Synced file '{content.Path}'");
|
||||
}
|
||||
else if (content.Type == ContentType.Dir)
|
||||
{
|
||||
await CopyDirectory(content.Path, localPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await CopyDirectory("", resourcesDir);
|
||||
await CopyDirectory("", resourcesDir);
|
||||
}
|
||||
catch (RateLimitExceededException)
|
||||
{
|
||||
Logger.Warn("Unable to sync resources due to your ip being rate-limited by github");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Warn("Unable to sync resources");
|
||||
Logger.Warn(e);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsEmpty(string path)
|
||||
{
|
||||
return !Directory.EnumerateFileSystemEntries(path).Any();
|
||||
}
|
||||
|
||||
private static void CopyFilesRecursively(string sourcePath, string targetPath)
|
||||
{
|
||||
//Now Create all of the directories
|
||||
@@ -107,7 +116,7 @@ public class StorageService
|
||||
}
|
||||
|
||||
//Copy all the files & Replaces any files with the same name
|
||||
foreach (string newPath in Directory.GetFiles(sourcePath, "*.*",SearchOption.AllDirectories))
|
||||
foreach (string newPath in Directory.GetFiles(sourcePath, "*.*", SearchOption.AllDirectories))
|
||||
{
|
||||
File.Copy(newPath, newPath.Replace(sourcePath, targetPath), true);
|
||||
}
|
||||
|
||||
91
Moonlight/App/Services/IpVerificationService.cs
Normal file
91
Moonlight/App/Services/IpVerificationService.cs
Normal file
@@ -0,0 +1,91 @@
|
||||
using Moonlight.App.Helpers;
|
||||
using Whois.NET;
|
||||
|
||||
namespace Moonlight.App.Services;
|
||||
|
||||
public class IpVerificationService
|
||||
{
|
||||
private readonly ConfigService ConfigService;
|
||||
|
||||
public IpVerificationService(ConfigService configService)
|
||||
{
|
||||
ConfigService = configService;
|
||||
}
|
||||
|
||||
public async Task<bool> IsDatacenterOrVpn(string ip)
|
||||
{
|
||||
if (!ConfigService.Get().Moonlight.Security.BlockDatacenterIps)
|
||||
return false;
|
||||
|
||||
if (string.IsNullOrEmpty(ip))
|
||||
return false;
|
||||
|
||||
var datacenterNames = new List<string>()
|
||||
{
|
||||
"amazon",
|
||||
"aws",
|
||||
"microsoft",
|
||||
"azure",
|
||||
"google",
|
||||
"google cloud",
|
||||
"gcp",
|
||||
"digitalocean",
|
||||
"linode",
|
||||
"vultr",
|
||||
"ovh",
|
||||
"ovhcloud",
|
||||
"alibaba",
|
||||
"oracle",
|
||||
"ibm cloud",
|
||||
"bluehost",
|
||||
"godaddy",
|
||||
"rackpace",
|
||||
"hetzner",
|
||||
"tencent",
|
||||
"scaleway",
|
||||
"softlayer",
|
||||
"dreamhost",
|
||||
"a2 hosting",
|
||||
"inmotion hosting",
|
||||
"red hat openstack",
|
||||
"kamatera",
|
||||
"hostgator",
|
||||
"siteground",
|
||||
"greengeeks",
|
||||
"liquidweb",
|
||||
"joyent",
|
||||
"aruba",
|
||||
"interoute",
|
||||
"fastcomet",
|
||||
"rosehosting",
|
||||
"lunarpages",
|
||||
"fatcow",
|
||||
"jelastic",
|
||||
"datacamp"
|
||||
};
|
||||
|
||||
if(!ConfigService.Get().Moonlight.Security.AllowCloudflareIps)
|
||||
datacenterNames.Add("cloudflare");
|
||||
|
||||
try
|
||||
{
|
||||
var response = await WhoisClient.QueryAsync(ip);
|
||||
var responseText = response.Raw.ToLower();
|
||||
|
||||
foreach (var name in datacenterNames)
|
||||
{
|
||||
if (responseText.Contains(name))
|
||||
{
|
||||
Logger.Debug(name);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -55,6 +55,7 @@
|
||||
<PackageReference Include="SSH.NET" Version="2020.0.2" />
|
||||
<PackageReference Include="Stripe.net" Version="41.23.0-beta.1" />
|
||||
<PackageReference Include="UAParser" Version="3.1.47" />
|
||||
<PackageReference Include="WhoisClient.NET" Version="5.0.0" />
|
||||
<PackageReference Include="XtermBlazor" Version="1.8.1" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -220,6 +220,7 @@ namespace Moonlight
|
||||
builder.Services.AddScoped<TicketClientService>();
|
||||
builder.Services.AddScoped<TicketAdminService>();
|
||||
builder.Services.AddScoped<MalwareScanService>();
|
||||
builder.Services.AddSingleton<IpVerificationService>();
|
||||
|
||||
builder.Services.AddScoped<SessionClientService>();
|
||||
builder.Services.AddSingleton<SessionServerService>();
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
@inject DynamicBackgroundService DynamicBackgroundService
|
||||
@inject KeyListenerService KeyListenerService
|
||||
@inject ConfigService ConfigService
|
||||
@inject IpVerificationService IpVerificationService
|
||||
|
||||
@{
|
||||
var uri = new Uri(NavigationManager.Uri);
|
||||
@@ -46,9 +47,35 @@
|
||||
|
||||
<DefaultLayout>
|
||||
<SoftErrorBoundary>
|
||||
@if (!IsIpBanned)
|
||||
@if (UserProcessed)
|
||||
{
|
||||
if (UserProcessed)
|
||||
if (IsIpBanned)
|
||||
{
|
||||
<div class="modal d-block">
|
||||
<div class="modal-dialog modal-dialog-centered mw-900px">
|
||||
<div class="modal-content">
|
||||
<div class="pt-2 modal-body py-lg-10 px-lg-10">
|
||||
<h2>@(SmartTranslateService.Translate("Your ip has been banned"))</h2>
|
||||
<p class="mt-3 fw-normal fs-6">@(SmartTranslateService.Translate("Your ip address has been banned by an admin"))</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else if (IsIpSuspicious)
|
||||
{
|
||||
<div class="modal d-block">
|
||||
<div class="modal-dialog modal-dialog-centered mw-900px">
|
||||
<div class="modal-content">
|
||||
<div class="pt-2 modal-body py-lg-10 px-lg-10">
|
||||
<h2>@(SmartTranslateService.Translate("Your ip his blocked. VPNs and Datacenter IPs are prohibited from accessing this site"))</h2>
|
||||
<p class="mt-3 fw-normal fs-6">@(SmartTranslateService.Translate("Please disable your vpn or proxy and try it again"))</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
if (uri.LocalPath != "/login" &&
|
||||
uri.LocalPath != "/passwordreset" &&
|
||||
@@ -102,19 +129,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="modal d-block">
|
||||
<div class="modal-dialog modal-dialog-centered mw-900px">
|
||||
<div class="modal-content">
|
||||
<div class="pt-2 modal-body py-lg-10 px-lg-10">
|
||||
<h2>@(SmartTranslateService.Translate("Authenticating"))...</h2>
|
||||
<p class="mt-3 fw-normal fs-6">@(SmartTranslateService.Translate("Verifying token, loading user data"))</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -122,8 +136,8 @@
|
||||
<div class="modal-dialog modal-dialog-centered mw-900px">
|
||||
<div class="modal-content">
|
||||
<div class="pt-2 modal-body py-lg-10 px-lg-10">
|
||||
<h2>@(SmartTranslateService.Translate("Your ip has been banned"))</h2>
|
||||
<p class="mt-3 fw-normal fs-6">@(SmartTranslateService.Translate("Your ip address has been banned by an admin"))</p>
|
||||
<h2>@(SmartTranslateService.Translate("Authenticating"))...</h2>
|
||||
<p class="mt-3 fw-normal fs-6">@(SmartTranslateService.Translate("Verifying token, loading user data"))</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -137,6 +151,7 @@
|
||||
{
|
||||
private bool UserProcessed = false;
|
||||
private bool IsIpBanned = false;
|
||||
private bool IsIpSuspicious = false;
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
@@ -146,11 +161,6 @@
|
||||
{
|
||||
DynamicBackgroundService.OnBackgroundImageChanged += async (_, _) => { await InvokeAsync(StateHasChanged); };
|
||||
|
||||
IsIpBanned = await IpBanService.IsBanned();
|
||||
|
||||
if (IsIpBanned)
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
await Event.On<Object>("ipBan.update", this, async _ =>
|
||||
{
|
||||
IsIpBanned = await IpBanService.IsBanned();
|
||||
@@ -158,6 +168,10 @@
|
||||
});
|
||||
|
||||
await IdentityService.Load();
|
||||
|
||||
IsIpBanned = await IpBanService.IsBanned();
|
||||
IsIpSuspicious = await IpVerificationService.IsDatacenterOrVpn(IdentityService.Ip);
|
||||
|
||||
UserProcessed = true;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user