using Blahblah.FlowerStory.Server.Data; using Blahblah.FlowerStory.Server.Data.Model; using Microsoft.AspNetCore.Mvc; using System.Security.Cryptography; using System.Text; namespace Blahblah.FlowerStory.Server.Controller; /// <summary> /// 基础服务抽象类 /// </summary> public abstract class BaseController : ControllerBase { private const string Salt = "Blah blah, o! Flower story, intimately help you record every bit of the garden."; /// <summary> /// 自定义认证头的关键字 /// </summary> protected const string AuthHeader = "Authorization"; /// <summary> /// 禁用用户 /// </summary> protected const int UserDisabled = -1; /// <summary> /// 普通用户 /// </summary> protected const int UserCommon = 0; /// <summary> /// 管理员用户 /// </summary> protected const int UserAdmin = 99; /// <summary> /// 数据库对象 /// </summary> protected readonly FlowerDatabase database; /// <summary> /// 日志对象 /// </summary> protected readonly ILogger<BaseController>? logger; /// <summary> /// 构造基础服务类 /// </summary> /// <param name="database">数据库对象</param> /// <param name="logger">日志对象</param> protected BaseController(FlowerDatabase database, ILogger<BaseController>? logger = null) { this.database = database; this.logger = logger; } /// <summary> /// 计算密码的 hash /// </summary> /// <param name="password">密码原文</param> /// <param name="id">用户 id</param> /// <returns>密码 hash,值为 SHA256(password+id+salt)</returns> /// <exception cref="ArgumentNullException"></exception> protected string HashPassword(string password, string id) { if (string.IsNullOrEmpty(password)) { throw new ArgumentNullException(nameof(password)); } if (string.IsNullOrEmpty(id)) { throw new ArgumentNullException(nameof(id)); } var data = Encoding.UTF8.GetBytes($"{password}{id}{Salt}"); var hash = SHA256.HashData(data); return Convert.ToHexString(hash); } /// <summary> /// 检出当前会话 /// </summary> /// <returns>若检查失败,第一个参数返回异常 ActionResult。第二个参数返回会话令牌对象</returns> protected (ActionResult? Result, TokenItem? Token) CheckToken() { if (!Request.Headers.TryGetValue(AuthHeader, out var h)) { logger?.LogWarning("request with no {auth} header", AuthHeader); return (Unauthorized(), null); } string hash = h.ToString(); var token = database.Tokens.Find(hash); if (token == null) { logger?.LogWarning("token \"{hash}\" not found", hash); return (Unauthorized(), null); } return (null, token); } /// <summary> /// 检查当前会话权限 /// </summary> /// <param name="level">需要大于等于该权限,默认为 0 - UserCommon</param> /// <returns>若检查失败,第一个参数返回异常 ActionResult。第二个参数返回会话对应的用户对象</returns> protected (ActionResult? Result, UserItem? User) CheckPermission(int? level = 0) { var (result, token) = CheckToken(); if (result != null) { return (result, null); } if (token == null) { return (Unauthorized(), null); } if (token.ExpireDate < DateTimeOffset.UtcNow) { logger?.LogWarning("token \"{hash}\" has expired after {date}", token.Id, token.ExpireDate); return (Unauthorized(), null); } var user = database.Users.Find(token.UserId); if (user == null) { logger?.LogWarning("user not found with id {id}", token.UserId); } else if (user.Level < level) { logger?.LogWarning("user \"{id}\" level ({level}) lower than required ({required})", user.UserId, user.Level, level); return (Forbid(), user); } else { var now = DateTimeOffset.UtcNow; token.ActiveDateUnixTime = now.ToUnixTimeMilliseconds(); var expires = now.AddSeconds(token.ExpireSeconds).ToUnixTimeMilliseconds(); if (expires > token.ExpireDateUnixTime) { token.ExpireDateUnixTime = expires; } database.Tokens.Update(token); user.ActiveDateUnixTime = now.ToUnixTimeMilliseconds(); database.Users.Update(user); } return (null, user); } /// <summary> /// 保存数据库变动并输出日志 /// </summary> /// <returns></returns> protected int SaveDatabase() { var count = database.SaveChanges(); if (count > 0) { logger?.LogInformation("{number} of entries written to database.", count); } else { logger?.LogWarning("no data written to database."); } return count; } }