支持邮件登录注册

This commit is contained in:
root 2024-12-01 17:37:06 +08:00
parent 9f300df47b
commit 871e7d47a3
8 changed files with 106 additions and 54 deletions

View file

@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Mvc;
using Serilog;
using System.Text.Json;
using iFileProxy.Attributes;
namespace iFileProxy.Controllers
{
[Authorize(UserMask.Admin,UserMask.SuperAdmin)]

View file

@ -1,8 +1,8 @@
using iFileProxy.Attributes;
using iFileProxy.Helpers;
using iFileProxy.Models;
using iFileProxy.Services;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
namespace iFileProxy.Controllers
{
@ -27,8 +27,8 @@ namespace iFileProxy.Controllers
{
try
{
var ip = HttpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown";
var (success, message) = await _authService.RegisterAsync(request.Username, request.Password, ip, request.NickName);
var ip = MasterHelper.GetClientIPAddr(HttpContext);
var (success, message) = await _authService.RegisterAsync(request,ip);
return Ok(new CommonRsp
{
@ -106,9 +106,6 @@ namespace iFileProxy.Controllers
});
}
_logger.LogInformation(JsonConvert.SerializeObject(user));
// 不返回敏感信息
return Ok(new CommonRsp
{
@ -592,16 +589,4 @@ namespace iFileProxy.Controllers
}
}
public class RegisterRequest
{
public string Username { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
public string NickName { get; set; } = string.Empty;
}
public class LoginRequest
{
public string Username { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
}
}

View file

@ -4,9 +4,10 @@ namespace iFileProxy.Models
{
public enum UserMask
{
Guest = -1, // 访客
User = 0,
Admin = 1,
SuperAdmin = 2 // 超级管理员
SuperAdmin = 2, // 超级管理员
}
public enum UserState
{
@ -77,6 +78,12 @@ namespace iFileProxy.Models
/// </summary>
[JsonProperty("last_login_ip")]
public string LastLoginIP { get; set; } = string.Empty;
/// <summary>
/// 电子邮箱
/// </summary>
[JsonProperty("email")]
public string Email { get; set; } = string.Empty;
}
public class UserEvent
@ -132,4 +139,29 @@ namespace iFileProxy.Models
[JsonProperty("nickname")]
public string Nickname { get; set; } = string.Empty;
}
/// <summary>
/// 注册请求数据结构
/// </summary>
public class RegisterRequest
{
public string Username { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
public string NickName { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
}
/// <summary>
/// 登录请求数据结构
/// </summary>
public class LoginRequest
{
/// <summary>
/// 用户名或邮箱
/// </summary>
public string Username { get; set; } = string.Empty;
public string Account { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
}
}

View file

@ -16,18 +16,18 @@ namespace iFileProxy
SerilogConfig.CreateLogger();
Serilog.ILogger logger = Log.Logger.ForContext<Program>();
Console.Write(" "); // 强迫症,看着开始的那条日志不对齐不得劲
Console.Write(" "); // ǿ<EFBFBD><EFBFBD>֢<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ſ<EFBFBD>ʼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>־<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>þ<EFBFBD>
var builder = WebApplication.CreateBuilder(args);
// 添加 CORS 服务
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD> CORS <20><><EFBFBD><EFBFBD>
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowFrontend",
builder =>
{
builder
.WithOrigins("http://localhost:3000", "http://admin.gitdl.cn", "https://admin.gitdl.cn","http://47.243.56.137:50050") // 前端地址
.WithOrigins("http://localhost:3000", "http://admin.gitdl.cn", "https://admin.gitdl.cn","http://47.243.56.137:50050") // ǰ<EFBFBD>˵<EFBFBD>ַ
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials();
@ -55,10 +55,10 @@ namespace iFileProxy
builder.Services.AddSingleton<TaskManager>();
// 添加认证服务
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>֤<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
builder.Services.AddScoped<AuthService>();
// 添加JWT认证
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD>JWT<EFBFBD><EFBFBD>֤
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
@ -98,17 +98,17 @@ namespace iFileProxy
app.UseSwaggerUI();
}
// 错误处理中间件
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>м<EFBFBD><EFBFBD>
app.UseMiddleware<ErrorHandlerMiddleware>();
app.UseCors("AllowFrontend");
app.UseHttpsRedirection();
// 配置默认文件选项
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ĭ<EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD>ѡ<EFBFBD><EFBFBD>
var defaultFilesOptions = new DefaultFilesOptions();
defaultFilesOptions.DefaultFileNames.Clear(); // 清除默认列表
defaultFilesOptions.DefaultFileNames.Add("index.html"); // 添加自定义默认文件
defaultFilesOptions.DefaultFileNames.Clear(); // <EFBFBD><EFBFBD><EFBFBD>Ĭ<EFBFBD><EFBFBD><EFBFBD>б<EFBFBD>
defaultFilesOptions.DefaultFileNames.Add("index.html"); // <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Զ<EFBFBD><EFBFBD><EFBFBD>Ĭ<EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD>
app.UseDefaultFiles(defaultFilesOptions);
@ -120,7 +120,7 @@ namespace iFileProxy
app.Services.GetRequiredService<Dictionary<string, Dictionary<string, uint>>>(),
AppConfig.GetCurrConfig().SecurityOptions.DailyRequestLimitPerIP);
// 添加中间件
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>м<EFBFBD><EFBFBD>
app.UseMiddleware<JwtMiddleware>();
app.MapControllers();

View file

@ -20,14 +20,14 @@ namespace iFileProxy.Services
_logger = logger;
}
public async Task<(bool success, string message)> RegisterAsync(string username, string password, string ip, string nickname)
public async Task<(bool success, string message)> RegisterAsync(RegisterRequest request, string ipAddr)
{
try
{
// 检查用户名是否已存在
if (await _dbGateService.UserExistsAsync(username))
if (await _dbGateService.UserExistsAsync(request.Username, request.Email))
{
return (false, "用户名已存在");
return (false, "用户名或电子邮件已存在");
}
// 检查是否是第一个用户
@ -36,12 +36,13 @@ namespace iFileProxy.Services
// 创建新用户
var user = new User
{
Username = username,
PasswordHash = BCrypt.Net.BCrypt.HashPassword(password),
LastLoginIP = ip,
Username = request.Username,
PasswordHash = BCrypt.Net.BCrypt.HashPassword(request.Password),
LastLoginIP = ipAddr,
// 如果是第一个用户,设置为超级管理员
Mask = isFirstUser ? UserMask.SuperAdmin : UserMask.User,
Nickname = nickname
Nickname = request.NickName,
Email = request.Email,
};
// 保存用户
@ -52,12 +53,12 @@ namespace iFileProxy.Services
{
UserId = user.UserId,
EventType = UserEventType.Registry,
EventIP = ip,
EventDetail = isFirstUser ? "管理员注册" : "用户注册"
EventIP = ipAddr,
EventDetail = isFirstUser ? "超级管理员注册" : "用户注册"
};
await _dbGateService.CreateUserEventAsync(userEvent);
return (true, isFirstUser ? "管理员注册成功" : "注册成功");
return (true, isFirstUser ? "超级管理员注册成功" : "注册成功");
}
catch (Exception ex)
{
@ -66,11 +67,11 @@ namespace iFileProxy.Services
}
}
public async Task<(bool success, string token, string message)> LoginAsync(string username, string password, string ip)
public async Task<(bool success, string token, string message)> LoginAsync(string account, string password, string ip)
{
try
{
var user = await _dbGateService.GetUserByUsernameAsync(username);
var user = await _dbGateService.GetUserByAccountAsync(account);
if (user == null)
{
return (false, string.Empty, "用户不存在");
@ -97,7 +98,7 @@ namespace iFileProxy.Services
UserId = user.UserId,
EventType = UserEventType.Login,
EventIP = ip,
EventDetail = "用户登录"
EventDetail = $"用户通过{(account.Contains('@') ? "" : "")}登录"
};
await _dbGateService.CreateUserEventAsync(userEvent);

View file

@ -39,6 +39,7 @@ namespace iFileProxy.Services
`user_id` varchar(36) NOT NULL,
`username` varchar(50) NOT NULL,
`nickname` varchar(50) NOT NULL,
`email` varchar(100) NOT NULL,
`password_hash` varchar(255) NOT NULL,
`mask` int NOT NULL DEFAULT 0,
`state` int NOT NULL DEFAULT 0,
@ -46,7 +47,8 @@ namespace iFileProxy.Services
`last_login_time` datetime NULL,
`last_login_ip` varchar(45) NULL,
PRIMARY KEY (`user_id`),
UNIQUE KEY `username` (`username`)
UNIQUE KEY `username` (`username`),
UNIQUE KEY `email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
""";
@ -196,6 +198,20 @@ namespace iFileProxy.Services
return n;
}
/// <summary>
/// 内部查询数据专用 当此方法暴露给C端可能造成sql注入等安全问题
/// </summary>
/// <param name="sql">SQL语句</param>
/// <param name="dbConfName">配置文件中的Description字段</param>
/// <returns>影响的行数</returns>
public int Query(string sql, MySqlConnection connection)
{
using MySqlCommand sqlCmd = new(sql, connection);
int n = sqlCmd.ExecuteNonQuery();
_logger.Debug($"查询完成, 受影响的行数: {n}");
return n;
}
public List<TaskInfo> CheckCacheDependencies(string taskId,string ipAddr)
{
string sql = $"SELECT * FROM t_tasks_info WHERE `status` = @status AND `tag` = @tag AND `client_ip` <> @ip_addr";
@ -362,10 +378,11 @@ namespace iFileProxy.Services
}
public bool UpdateTaskStatus(TaskInfo taskInfo)
public bool UpdateTaskStatus(TaskInfo taskInfo, MySqlConnection? connection = null)
{
string sql = @"UPDATE t_tasks_info set `status` = @status , update_time = Now() WHERE `tid` = @tid";
MySqlConnection conn = GetAndOpenDBConn("iFileProxy_Db");
MySqlConnection conn = connection ?? GetAndOpenDBConn("iFileProxy_Db");
try
{
using MySqlCommand sqlCmd = new (sql, conn);
@ -623,12 +640,13 @@ namespace iFileProxy.Services
return users.FirstOrDefault();
}
public async Task<bool> UserExistsAsync(string username)
public async Task<bool> UserExistsAsync(string username, string email)
{
var sql = "SELECT COUNT(*) FROM t_users WHERE username = @username";
var sql = "SELECT COUNT(*) FROM t_users WHERE username = @username OR email = @email";
var parameters = new Dictionary<string, object>
{
{ "@username", username }
{ "@username", username },
{ "@email", email }
};
var count = await ExecuteScalarAsync<long>(sql, parameters);
return count > 0;
@ -636,8 +654,8 @@ namespace iFileProxy.Services
public async Task<bool> CreateUserAsync(User user)
{
var sql = @"INSERT INTO t_users (user_id, username, password_hash, mask, state, create_time, last_login_time, last_login_ip, nickname)
VALUES (@userId, @username, @passwordHash, @mask, @state, @createTime, @lastLoginTime, @lastLoginIp, @nickname)";
var sql = @"INSERT INTO t_users (user_id, username, password_hash, mask, state, create_time, last_login_time, last_login_ip, nickname, email)
VALUES (@userId, @username, @passwordHash, @mask, @state, @createTime, @lastLoginTime, @lastLoginIp, @nickname, @email)";
var parameters = new Dictionary<string, object>
{
@ -650,7 +668,7 @@ namespace iFileProxy.Services
{ "@lastLoginTime", user.LastLoginTime },
{ "@lastLoginIp", user.LastLoginIP },
{ "@nickname", user.Nickname },
{ "@email", user.Email }
};
var result = await ExecuteNonQueryAsync(sql, parameters);
@ -870,5 +888,17 @@ namespace iFileProxy.Services
Data = events
};
}
public async Task<User?> GetUserByAccountAsync(string account)
{
var sql = "SELECT * FROM t_users WHERE username = @account OR email = @account";
var parameters = new Dictionary<string, object>
{
{ "@account", account }
};
var users = await ExecuteQueryAsync<User>(sql, parameters);
return users.FirstOrDefault();
}
}
}

View file

@ -38,6 +38,9 @@ namespace iFileProxy.Services
/// </summary>
public void CheckAndCleanCache(object state)
{
// 初始化并打开一个MySQL连接 防止后续数据过多导致程序crush
var dbConn = _dbGateService.GetAndOpenDBConn(DbConfigName.iFileProxy);
// 获取数据库中超出生命周期的缓存数据
string result = _dbGateService.QueryTableData($"SELECT * FROM t_tasks_info WHERE UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(update_time) > {CACHE_LIFETIME} AND (tag <> 'CLEANED' OR tag IS NULL)", DbConfigName.iFileProxy);
List<TaskInfo>? taskInfos = JsonConvert.DeserializeObject<List<TaskInfo>>(result);
@ -59,9 +62,9 @@ namespace iFileProxy.Services
throw;
}
}
_dbGateService.Query($"UPDATE t_tasks_info SET `tag` = \"CLEANED\" WHERE `tid` = '{taskInfo.TaskId}'", DbConfigName.iFileProxy);
_dbGateService.Query($"UPDATE t_tasks_info SET `tag` = \"CLEANED\" WHERE `tid` = '{taskInfo.TaskId}'", dbConn);
taskInfo.Status = TaskState.Cleaned;
_dbGateService.UpdateTaskStatus(taskInfo);
_dbGateService.UpdateTaskStatus(taskInfo,dbConn);
}
}
}

View file

@ -16,7 +16,7 @@
"Download": {
"SavePath": "./download/", //
"ThreadNum": 4, // 线
"MaxAllowedFileSize": 65536, //
"MaxAllowedFileSize": 1000000000, //
"MaxParallelTasks": 4, //
"MaxQueueLength": 60, //
"Aria2cPath": "./lib/aria2c",