修复Login Token的IP地址获取异常的问题,更新SQL
This commit is contained in:
parent
94642457a8
commit
f1e43edde7
10 changed files with 281 additions and 8 deletions
|
@ -23,6 +23,9 @@ namespace iFileProxy.Config
|
||||||
[JsonPropertyName("Database")]
|
[JsonPropertyName("Database")]
|
||||||
public Database Database { get; set; }
|
public Database Database { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("GithubProxy")]
|
||||||
|
public GithubProxyOptions GithubProxyOptions { get; set; } = new();
|
||||||
|
|
||||||
public static AppConfig GetCurrConfig(string configPath = "iFileProxy.json")
|
public static AppConfig GetCurrConfig(string configPath = "iFileProxy.json")
|
||||||
{
|
{
|
||||||
if (File.Exists(configPath))
|
if (File.Exists(configPath))
|
||||||
|
@ -140,4 +143,22 @@ namespace iFileProxy.Config
|
||||||
[JsonPropertyName("Description")]
|
[JsonPropertyName("Description")]
|
||||||
public string Description { get; set; }
|
public string Description { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class GithubProxyOptions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 文件大小限制(字节)
|
||||||
|
/// </summary>
|
||||||
|
public long SizeLimit { get; set; } = 1024L * 1024L * 1024L; // 默认1GB
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 黑名单列表
|
||||||
|
/// </summary>
|
||||||
|
public List<string> Blacklist { get; set; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否启用 jsDelivr 加速
|
||||||
|
/// </summary>
|
||||||
|
public bool EnableJsDelivr { get; set; } = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
87
src/Controllers/GithubProxyController.cs
Normal file
87
src/Controllers/GithubProxyController.cs
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using iFileProxy.Services;
|
||||||
|
using iFileProxy.Models;
|
||||||
|
|
||||||
|
namespace iFileProxy.Controllers
|
||||||
|
{
|
||||||
|
[Route("[controller]")]
|
||||||
|
[ApiController]
|
||||||
|
public class GithubProxyController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly ILogger<GithubProxyController> _logger;
|
||||||
|
private readonly GithubProxyService _proxyService;
|
||||||
|
|
||||||
|
public GithubProxyController(
|
||||||
|
ILogger<GithubProxyController> logger,
|
||||||
|
GithubProxyService proxyService)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_proxyService = proxyService;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{**url}")]
|
||||||
|
public async Task<IActionResult> ProxyDownload(string url)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 1. URL 格式化
|
||||||
|
url = url.StartsWith("http") ? url : $"https://{url}";
|
||||||
|
|
||||||
|
// 2. 验证 URL
|
||||||
|
var (isValid, author, repo) = _proxyService.ValidateUrl(url);
|
||||||
|
if (!isValid || author == null || repo == null)
|
||||||
|
{
|
||||||
|
return BadRequest(new CommonRsp
|
||||||
|
{
|
||||||
|
Retcode = 1,
|
||||||
|
Message = "Invalid GitHub URL"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 检查黑名单
|
||||||
|
if (_proxyService.IsBlocked(author, repo))
|
||||||
|
{
|
||||||
|
return Forbid();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 处理URL(CDN或Raw)
|
||||||
|
url = _proxyService.ProcessUrl(url);
|
||||||
|
|
||||||
|
// 5. 代理请求
|
||||||
|
var response = await _proxyService.ProxyRequestAsync(url, Request.Headers);
|
||||||
|
if (!response.Success)
|
||||||
|
{
|
||||||
|
return StatusCode(response.StatusCode, new CommonRsp
|
||||||
|
{
|
||||||
|
Retcode = 1,
|
||||||
|
Message = response.Message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. 设置响应头
|
||||||
|
if (response.Headers != null)
|
||||||
|
{
|
||||||
|
foreach (var header in response.Headers)
|
||||||
|
{
|
||||||
|
Response.Headers[header.Key] = header.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. 返回流式响应
|
||||||
|
return new FileStreamResult(
|
||||||
|
response.Stream!,
|
||||||
|
response.ContentType ?? "application/octet-stream"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Proxy download failed");
|
||||||
|
return StatusCode(500, new CommonRsp
|
||||||
|
{
|
||||||
|
Retcode = 1,
|
||||||
|
Message = "Internal server error"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -50,7 +50,7 @@ namespace iFileProxy.Controllers
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var ip = HttpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown";
|
var ip = MasterHelper.GetClientIPAddr(HttpContext);
|
||||||
var userAgent = HttpContext.Request.Headers["User-Agent"].FirstOrDefault() ?? "unknown";
|
var userAgent = HttpContext.Request.Headers["User-Agent"].FirstOrDefault() ?? "unknown";
|
||||||
var fingerprint = HttpContext.Request.Headers["X-Device-Fingerprint"].FirstOrDefault() ?? "unknown";
|
var fingerprint = HttpContext.Request.Headers["X-Device-Fingerprint"].FirstOrDefault() ?? "unknown";
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
using iFileProxy.Helpers;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
using System.IdentityModel.Tokens.Jwt;
|
using System.IdentityModel.Tokens.Jwt;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
|
@ -21,7 +22,7 @@ namespace iFileProxy.Middleware
|
||||||
var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
|
var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
|
||||||
var fingerprint = context.Request.Headers["X-Device-Fingerprint"].FirstOrDefault();
|
var fingerprint = context.Request.Headers["X-Device-Fingerprint"].FirstOrDefault();
|
||||||
var userAgent = context.Request.Headers["User-Agent"].FirstOrDefault();
|
var userAgent = context.Request.Headers["User-Agent"].FirstOrDefault();
|
||||||
var ip = context.Connection.RemoteIpAddress?.ToString();
|
var ip = MasterHelper.GetClientIPAddr(context);
|
||||||
|
|
||||||
if (token != null)
|
if (token != null)
|
||||||
{
|
{
|
||||||
|
|
|
@ -19,7 +19,6 @@ namespace iFileProxy
|
||||||
Console.Write(" "); // 补全日志第一行开头的空白
|
Console.Write(" "); // 补全日志第一行开头的空白
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
// CORS配置
|
// CORS配置
|
||||||
builder.Services.AddCors(options =>
|
builder.Services.AddCors(options =>
|
||||||
{
|
{
|
||||||
|
@ -27,7 +26,7 @@ namespace iFileProxy
|
||||||
builder =>
|
builder =>
|
||||||
{
|
{
|
||||||
builder
|
builder
|
||||||
.WithOrigins("http://localhost:3000", "http://admin.gitdl.cn", "https://admin.gitdl.cn","http://47.243.56.137:50050", "http://localhost:4173")
|
.WithOrigins("http://localhost:3000", "http://admin.gitdl.cn", "https://admin.gitdl.cn", "https://github.linxi.info", "http://github.linxi.info/", "http://localhost:4173" )
|
||||||
.AllowAnyMethod()
|
.AllowAnyMethod()
|
||||||
.AllowAnyHeader()
|
.AllowAnyHeader()
|
||||||
.AllowCredentials();
|
.AllowCredentials();
|
||||||
|
|
|
@ -995,7 +995,7 @@ namespace iFileProxy.Services
|
||||||
{ "@level", log.Level },
|
{ "@level", log.Level },
|
||||||
{ "@message", log.Message },
|
{ "@message", log.Message },
|
||||||
{ "@exception", log.Exception },
|
{ "@exception", log.Exception },
|
||||||
{ "@properties", JsonConvert.SerializeObject(log.Properties) },
|
{ "@properties", log.Properties },
|
||||||
{ "@timestamp", log.Timestamp }
|
{ "@timestamp", log.Timestamp }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
130
src/Services/GithubProxyService.cs
Normal file
130
src/Services/GithubProxyService.cs
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using iFileProxy.Config;
|
||||||
|
using iFileProxy.Models;
|
||||||
|
|
||||||
|
namespace iFileProxy.Services
|
||||||
|
{
|
||||||
|
public class GithubProxyService
|
||||||
|
{
|
||||||
|
private readonly ILogger<GithubProxyService> _logger;
|
||||||
|
private readonly AppConfig _appConfig;
|
||||||
|
private readonly HttpClient _httpClient;
|
||||||
|
|
||||||
|
private static readonly Regex[] URL_PATTERNS =
|
||||||
|
{
|
||||||
|
new(@"^(?:https?://)?github\.com/(?<author>.+?)/(?<repo>.+?)/(?:releases|archive)/.*$"),
|
||||||
|
new(@"^(?:https?://)?github\.com/(?<author>.+?)/(?<repo>.+?)/(?:blob|raw)/.*$"),
|
||||||
|
new(@"^(?:https?://)?raw\.(?:githubusercontent|github)\.com/(?<author>.+?)/(?<repo>.+?)/.+?/.+$"),
|
||||||
|
new(@"^(?:https?://)?gist\.(?:githubusercontent|github)\.com/(?<author>.+?)/.+?/.+$")
|
||||||
|
};
|
||||||
|
|
||||||
|
public GithubProxyService(
|
||||||
|
ILogger<GithubProxyService> logger,
|
||||||
|
AppConfig appConfig,
|
||||||
|
HttpClient httpClient)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_appConfig = appConfig;
|
||||||
|
_httpClient = httpClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
public (bool isValid, string? author, string? repo) ValidateUrl(string url)
|
||||||
|
{
|
||||||
|
foreach (var pattern in URL_PATTERNS)
|
||||||
|
{
|
||||||
|
var match = pattern.Match(url);
|
||||||
|
if (match.Success)
|
||||||
|
{
|
||||||
|
return (true, match.Groups["author"].Value, match.Groups["repo"].Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (false, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsBlocked(string author, string repo)
|
||||||
|
{
|
||||||
|
return _appConfig.GithubProxyOptions.Blacklist.Contains($"{author}/{repo}") ||
|
||||||
|
_appConfig.GithubProxyOptions.Blacklist.Contains(author);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ProcessUrl(string url)
|
||||||
|
{
|
||||||
|
if (_appConfig.GithubProxyOptions.EnableJsDelivr && url.Contains("/blob/"))
|
||||||
|
{
|
||||||
|
return url.Replace("/blob/", "@")
|
||||||
|
.Replace("github.com", "cdn.jsdelivr.net/gh");
|
||||||
|
}
|
||||||
|
else if (url.Contains("/blob/"))
|
||||||
|
{
|
||||||
|
return url.Replace("/blob/", "/raw/");
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ProxyResponse> ProxyRequestAsync(
|
||||||
|
string url,
|
||||||
|
IHeaderDictionary headers,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Get, url);
|
||||||
|
|
||||||
|
// 复制请求头
|
||||||
|
foreach (var header in headers)
|
||||||
|
{
|
||||||
|
if (!header.Key.Equals("Host", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
request.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var response = await _httpClient.SendAsync(
|
||||||
|
request,
|
||||||
|
HttpCompletionOption.ResponseHeadersRead,
|
||||||
|
cancellationToken);
|
||||||
|
|
||||||
|
// 检查文件大小
|
||||||
|
var contentLength = response.Content.Headers.ContentLength;
|
||||||
|
if (contentLength.HasValue && contentLength.Value > _appConfig.GithubProxyOptions.SizeLimit)
|
||||||
|
{
|
||||||
|
return new ProxyResponse
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
StatusCode = StatusCodes.Status413PayloadTooLarge,
|
||||||
|
Message = "File too large"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ProxyResponse
|
||||||
|
{
|
||||||
|
Success = true,
|
||||||
|
StatusCode = (int)response.StatusCode,
|
||||||
|
Headers = response.Headers.ToDictionary(h => h.Key, h => h.Value.ToArray()),
|
||||||
|
Stream = await response.Content.ReadAsStreamAsync(cancellationToken),
|
||||||
|
ContentType = response.Content.Headers.ContentType?.ToString()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Proxy request failed");
|
||||||
|
return new ProxyResponse
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
StatusCode = StatusCodes.Status500InternalServerError,
|
||||||
|
Message = "Internal server error"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ProxyResponse
|
||||||
|
{
|
||||||
|
public bool Success { get; set; }
|
||||||
|
public int StatusCode { get; set; }
|
||||||
|
public string? Message { get; set; }
|
||||||
|
public Dictionary<string, string[]>? Headers { get; set; }
|
||||||
|
public Stream? Stream { get; set; }
|
||||||
|
public string? ContentType { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,5 +10,13 @@
|
||||||
"Key": "iFileProxy-JWT-Secret-Key-2024-Very-Long-Secret-Key-For-Security",
|
"Key": "iFileProxy-JWT-Secret-Key-2024-Very-Long-Secret-Key-For-Security",
|
||||||
"Issuer": "iFileProxy",
|
"Issuer": "iFileProxy",
|
||||||
"Audience": "iFileProxy.Client"
|
"Audience": "iFileProxy.Client"
|
||||||
|
},
|
||||||
|
"GithubProxy": {
|
||||||
|
"SizeLimit": 1073741824, // 1GB in bytes
|
||||||
|
"Blacklist": [
|
||||||
|
"blockedUser1",
|
||||||
|
"blockedUser2/blockedRepo",
|
||||||
|
"blockedOrg/*"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,12 +11,28 @@
|
||||||
Target Server Version : 50743
|
Target Server Version : 50743
|
||||||
File Encoding : 65001
|
File Encoding : 65001
|
||||||
|
|
||||||
Date: 01/12/2024 01:28:13
|
Date: 04/12/2024 00:13:59
|
||||||
*/
|
*/
|
||||||
|
|
||||||
SET NAMES utf8mb4;
|
SET NAMES utf8mb4;
|
||||||
SET FOREIGN_KEY_CHECKS = 0;
|
SET FOREIGN_KEY_CHECKS = 0;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Table structure for t_system_logs
|
||||||
|
-- ----------------------------
|
||||||
|
DROP TABLE IF EXISTS `t_system_logs`;
|
||||||
|
CREATE TABLE `t_system_logs` (
|
||||||
|
`log_id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||||
|
`level` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||||
|
`message` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||||
|
`exception` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL,
|
||||||
|
`properties` json NULL,
|
||||||
|
`timestamp` datetime NOT NULL,
|
||||||
|
PRIMARY KEY (`log_id`) USING BTREE,
|
||||||
|
INDEX `idx_timestamp`(`timestamp`) USING BTREE,
|
||||||
|
INDEX `idx_level`(`level`) USING BTREE
|
||||||
|
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
-- Table structure for t_tasks_info
|
-- Table structure for t_tasks_info
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
|
@ -35,7 +51,7 @@ CREATE TABLE `t_tasks_info` (
|
||||||
`tag` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '标记',
|
`tag` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '标记',
|
||||||
PRIMARY KEY (`id`) USING BTREE,
|
PRIMARY KEY (`id`) USING BTREE,
|
||||||
UNIQUE INDEX `tid`(`tid`) USING BTREE
|
UNIQUE INDEX `tid`(`tid`) USING BTREE
|
||||||
) ENGINE = InnoDB AUTO_INCREMENT = 9130 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
|
) ENGINE = InnoDB AUTO_INCREMENT = 111135 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
-- Table structure for t_user_events
|
-- Table structure for t_user_events
|
||||||
|
@ -60,6 +76,7 @@ DROP TABLE IF EXISTS `t_users`;
|
||||||
CREATE TABLE `t_users` (
|
CREATE TABLE `t_users` (
|
||||||
`user_id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
`user_id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||||
`nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '昵称',
|
`nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '昵称',
|
||||||
|
`email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '电子邮箱',
|
||||||
`username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户名',
|
`username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户名',
|
||||||
`password_hash` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '密码哈希值',
|
`password_hash` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '密码哈希值',
|
||||||
`mask` int(11) NOT NULL DEFAULT 0 COMMENT '权限掩码',
|
`mask` int(11) NOT NULL DEFAULT 0 COMMENT '权限掩码',
|
||||||
|
@ -68,7 +85,8 @@ CREATE TABLE `t_users` (
|
||||||
`last_login_time` datetime NULL DEFAULT NULL COMMENT '上次登录时间',
|
`last_login_time` datetime NULL DEFAULT NULL COMMENT '上次登录时间',
|
||||||
`last_login_ip` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '上次登录IP',
|
`last_login_ip` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '上次登录IP',
|
||||||
PRIMARY KEY (`user_id`) USING BTREE,
|
PRIMARY KEY (`user_id`) USING BTREE,
|
||||||
UNIQUE INDEX `username`(`username`) USING BTREE
|
UNIQUE INDEX `username`(`username`) USING BTREE,
|
||||||
|
UNIQUE INDEX `email`(`email`) USING BTREE
|
||||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
|
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
|
||||||
|
|
||||||
SET FOREIGN_KEY_CHECKS = 1;
|
SET FOREIGN_KEY_CHECKS = 1;
|
||||||
|
|
|
@ -39,5 +39,14 @@
|
||||||
"/AddOfflineTask"
|
"/AddOfflineTask"
|
||||||
],
|
],
|
||||||
"AllowDifferentIPsForDownload": true // 下载者与任务数据提交者IP不同是否允许下载文件
|
"AllowDifferentIPsForDownload": true // 下载者与任务数据提交者IP不同是否允许下载文件
|
||||||
|
},
|
||||||
|
"GithubProxy": {
|
||||||
|
"SizeLimit": 1073741824,
|
||||||
|
"Blacklist": [
|
||||||
|
"blockedUser1",
|
||||||
|
"blockedUser2/blockedRepo",
|
||||||
|
"blockedOrg/*"
|
||||||
|
],
|
||||||
|
"EnableJsDelivr": false
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in a new issue