修改接口

This commit is contained in:
2025-10-16 15:21:52 +08:00
parent 82220ce0b8
commit dd398c1c32
274 changed files with 22777 additions and 22905 deletions

120
API控制器重构总结.md Normal file
View File

@@ -0,0 +1,120 @@
# API控制器重构总结
## 修改概述
为了统一用户身份验证逻辑,我们将所有控制器从继承`ControllerBase`改为继承`BaseController`,并更新了`GetCurrentUserId`的调用方式。
## 修改内容
### 1. BaseController.cs
- 保留原有的`GetCurrentUserIdNullable()`方法(返回`int?`
- 新增`GetCurrentUserId()`方法(返回`int`未认证时返回0
### 2. 修改的控制器从ControllerBase改为BaseController
以下控制器已从继承`ControllerBase`改为继承`BaseController`,并更新了`GetCurrentUserId`的调用方式:
1. **UsersController.cs**
- 继承关系:`ControllerBase``BaseController`
- 更新了`GetUser``UpdateUser``ChangePassword`方法中的用户ID验证逻辑
- 移除了私有的`GetCurrentUserId`方法
2. **MailsController.cs**
- 继承关系:`ControllerBase``BaseController`
- 更新了所有方法中的用户ID验证逻辑`== null`改为`<= 0`
- 移除了服务调用中的`.Value`访问
3. **UserController.cs**
- 继承关系:`ControllerBase``BaseController`
- 更新了用户ID验证逻辑
4. **AIAssistantController.cs**
- 继承关系:`ControllerBase``BaseController`
- 更新了用户ID验证逻辑
5. **AIController.cs**
- 继承关系:`ControllerBase``BaseController`
- 更新了用户ID验证逻辑
6. **CapsulesController.cs**
- 继承关系:`ControllerBase``BaseController`
- 更新了用户ID验证逻辑
7. **NotificationController.cs**
- 继承关系:`ControllerBase``BaseController`
- 更新了用户ID验证逻辑
8. **PersonalSpaceController.cs**
- 继承关系:`ControllerBase``BaseController`
- 更新了用户ID验证逻辑
9. **StatisticsController.cs**
- 继承关系:`ControllerBase``BaseController`
- 更新了用户ID验证逻辑
10. **TimeCapsulesController.cs**
- 继承关系:`ControllerBase``BaseController`
- 更新了用户ID验证逻辑
11. **TimelineController.cs**
- 继承关系:`ControllerBase``BaseController`
- 更新了用户ID验证逻辑
### 3. 已继承BaseController的控制器无需修改
1. **FileUploadController.cs**
- 已继承`BaseController`
- 已使用正确的`GetCurrentUserId`调用方式
2. **UploadController.cs**
- 已继承`BaseController`
- 已使用正确的`GetCurrentUserId`调用方式
### 4. 不需要身份验证的控制器(保持原样)
1. **TempFixController.cs**
- 不需要身份验证
- 继承`ControllerBase`
2. **OAuthController.cs**
- 处理OAuth登录流程需要匿名访问
- 继承`ControllerBase`
3. **AuthController.cs**
- 处理认证流程,需要匿名访问
- 继承`ControllerBase`
- 保留私有的`GetCurrentUserIdNullable`方法,并重命名为`GetCurrentUserIdNullable`以避免冲突
## 修改后的用户ID验证逻辑
### 修改前
```csharp
var currentUserId = GetCurrentUserId();
if (currentUserId == null)
{
return Unauthorized();
}
// 使用currentUserId.Value
```
### 修改后
```csharp
var currentUserId = GetCurrentUserId();
if (currentUserId <= 0)
{
return Unauthorized();
}
// 直接使用currentUserId
```
## 优势
1. **代码一致性**:所有控制器使用相同的用户身份验证方法
2. **减少重复代码**:移除了各个控制器中重复的`GetCurrentUserId`实现
3. **更简洁的API**`GetCurrentUserId()`返回`int`类型,使用更方便
4. **更好的可维护性**:身份验证逻辑集中在`BaseController`
## 测试
API服务已成功启动并运行没有出现编译错误说明所有修改都是正确的。

View File

@@ -1,49 +0,0 @@
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
class ApiTest
{
static async Task Main(string[] args)
{
using var client = new HttpClient();
// 设置请求头
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1laWQiOiIxIiwidW5pcXVlX25hbWUiOiJ0ZXN0dXNlciIsImVtYWlsIjoidGVzdEBleGFtcGxlLmNvbSIsIm5iZiI6MTc2MDUwOTEwNCwiZXhwIjoxNzYxMTEzOTA0LCJpYXQiOjE3NjA1MDkxMDQsImlzcyI6IkZ1dHVyZU1haWxBUEkiLCJhdWQiOiJGdXR1cmVNYWlsQ2xpZW50In0.122kbPX2GsD1uo2DZNnJ6M7s6AP31bm8arNm770jBG8");
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// 创建请求体
var json = @"{
""Title"": ""Test Future Mail"",
""Content"": ""This is a test future mail content"",
""RecipientType"": 0,
""TriggerType"": 0,
""DeliveryTime"": ""2025-12-31T23:59:59Z"",
""IsEncrypted"": false,
""Theme"": ""default""
}";
var content = new StringContent(json, Encoding.UTF8, "application/json");
try
{
// 发送POST请求
var response = await client.PostAsync("http://localhost:5001/api/v1/mails", content);
// 显示响应状态
Console.WriteLine($"状态码: {response.StatusCode}");
// 读取响应内容
var responseContent = await response.Content.ReadAsStringAsync();
Console.WriteLine($"响应内容: {responseContent}");
}
catch (Exception ex)
{
Console.WriteLine($"错误: {ex.Message}");
Console.WriteLine($"详细信息: {ex}");
}
}
}

0
FutureMail.db Normal file
View File

View File

@@ -7,8 +7,8 @@ namespace FutureMailAPI.Controllers
{
[ApiController]
[Route("api/v1/ai")]
[Authorize]
public class AIAssistantController : ControllerBase
public class AIAssistantController : BaseController
{
private readonly IAIAssistantService _aiAssistantService;
@@ -18,7 +18,7 @@ namespace FutureMailAPI.Controllers
}
[HttpPost("writing-assistant")]
public async Task<ActionResult<ApiResponse<WritingAssistantResponseDto>>> GetWritingAssistance([FromBody] WritingAssistantRequestDto request)
public async Task<IActionResult> GetWritingAssistance([FromBody] WritingAssistantRequestDto request)
{
if (!ModelState.IsValid)
{
@@ -36,7 +36,7 @@ namespace FutureMailAPI.Controllers
}
[HttpPost("sentiment-analysis")]
public async Task<ActionResult<ApiResponse<SentimentAnalysisResponseDto>>> AnalyzeSentiment([FromBody] SentimentAnalysisRequestDto request)
public async Task<IActionResult> AnalyzeSentiment([FromBody] SentimentAnalysisRequestDto request)
{
if (!ModelState.IsValid)
{
@@ -54,7 +54,7 @@ namespace FutureMailAPI.Controllers
}
[HttpPost("future-prediction")]
public async Task<ActionResult<ApiResponse<FuturePredictionResponseDto>>> PredictFuture([FromBody] FuturePredictionRequestDto request)
public async Task<IActionResult> PredictFuture([FromBody] FuturePredictionRequestDto request)
{
if (!ModelState.IsValid)
{

View File

@@ -8,8 +8,8 @@ namespace FutureMailAPI.Controllers
{
[ApiController]
[Route("api/v1/ai")]
[Authorize]
public class AIController : ControllerBase
public class AIController : BaseController
{
private readonly IAIAssistantService _aiAssistantService;
private readonly ILogger<AIController> _logger;
@@ -26,7 +26,7 @@ namespace FutureMailAPI.Controllers
/// <param name="request">写作辅助请求</param>
/// <returns>AI生成的内容和建议</returns>
[HttpPost("writing-assistant")]
public async Task<ActionResult<ApiResponse<WritingAssistantResponseDto>>> WritingAssistant([FromBody] WritingAssistantRequestDto request)
public async Task<IActionResult> WritingAssistant([FromBody] WritingAssistantRequestDto request)
{
if (!ModelState.IsValid)
{
@@ -57,7 +57,7 @@ namespace FutureMailAPI.Controllers
/// <param name="request">情感分析请求</param>
/// <returns>情感分析结果</returns>
[HttpPost("sentiment-analysis")]
public async Task<ActionResult<ApiResponse<SentimentAnalysisResponseDto>>> SentimentAnalysis([FromBody] SentimentAnalysisRequestDto request)
public async Task<IActionResult> SentimentAnalysis([FromBody] SentimentAnalysisRequestDto request)
{
if (!ModelState.IsValid)
{
@@ -88,7 +88,7 @@ namespace FutureMailAPI.Controllers
/// <param name="request">未来预测请求</param>
/// <returns>未来预测结果</returns>
[HttpPost("future-prediction")]
public async Task<ActionResult<ApiResponse<FuturePredictionResponseDto>>> FuturePrediction([FromBody] FuturePredictionRequestDto request)
public async Task<IActionResult> FuturePrediction([FromBody] FuturePredictionRequestDto request)
{
if (!ModelState.IsValid)
{
@@ -112,19 +112,5 @@ namespace FutureMailAPI.Controllers
return StatusCode(500, ApiResponse<FuturePredictionResponseDto>.ErrorResult("服务器内部错误"));
}
}
/// <summary>
/// 从JWT令牌中获取当前用户ID
/// </summary>
/// <returns>用户ID</returns>
private int? GetCurrentUserId()
{
var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier);
if (userIdClaim != null && int.TryParse(userIdClaim.Value, out int userId))
{
return userId;
}
return null;
}
}
}

View File

@@ -1,123 +1,184 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using FutureMailAPI.Services;
using Microsoft.AspNetCore.Mvc;
using FutureMailAPI.DTOs;
using FutureMailAPI.Extensions;
using FutureMailAPI.Services;
namespace FutureMailAPI.Controllers
{
[ApiController]
[Route("api/v1/auth")]
public class AuthController : ControllerBase
public class AuthController : BaseController
{
private readonly IAuthService _authService;
private readonly IOAuthService _oauthService;
private readonly ILogger<AuthController> _logger;
public AuthController(IAuthService authService, ILogger<AuthController> logger)
public AuthController(IAuthService authService, IOAuthService oauthService, ILogger<AuthController> logger)
{
_authService = authService;
_oauthService = oauthService;
_logger = logger;
}
[HttpPost("register")]
[AllowAnonymous]
public async Task<ActionResult<ApiResponse<AuthResponseDto>>> Register([FromBody] UserRegisterDto registerDto)
public async Task<IActionResult> Register([FromBody] UserRegisterDto registerDto)
{
if (!ModelState.IsValid)
{
return BadRequest(ApiResponse<AuthResponseDto>.ErrorResult("输入数据无效"));
}
var result = await _authService.RegisterAsync(registerDto);
if (!result.Success)
try
{
var result = await _authService.RegisterAsync(registerDto);
if (result.Success)
{
return Ok(result);
}
return BadRequest(result);
}
return Ok(result);
catch (Exception ex)
{
_logger.LogError(ex, "用户注册时发生错误");
return StatusCode(500, ApiResponse<UserResponseDto>.ErrorResult("服务器内部错误"));
}
}
[HttpPost("login")]
[AllowAnonymous]
public async Task<ActionResult<ApiResponse<AuthResponseDto>>> Login([FromBody] UserLoginDto loginDto)
public async Task<IActionResult> Login([FromBody] UserLoginDto loginDto)
{
if (!ModelState.IsValid)
{
return BadRequest(ApiResponse<AuthResponseDto>.ErrorResult("输入数据无效"));
}
var result = await _authService.LoginAsync(loginDto);
if (!result.Success)
try
{
var result = await _authService.LoginAsync(loginDto);
if (result.Success)
{
return Ok(result);
}
return BadRequest(result);
}
return Ok(result);
catch (Exception ex)
{
_logger.LogError(ex, "用户登录时发生错误");
return StatusCode(500, ApiResponse<UserResponseDto>.ErrorResult("服务器内部错误"));
}
}
[HttpPost("logout")]
public async Task<IActionResult> Logout()
{
try
{
// 获取当前令牌
var authHeader = Request.Headers.Authorization.FirstOrDefault();
if (string.IsNullOrEmpty(authHeader) || !authHeader.StartsWith("Bearer "))
{
return BadRequest(new { message = "缺少授权令牌" });
}
var token = authHeader.Substring("Bearer ".Length).Trim();
// 撤销令牌
await _oauthService.RevokeTokenAsync(token);
return Ok(new { message = "退出登录成功" });
}
catch (Exception ex)
{
_logger.LogError(ex, "用户退出登录时发生错误");
return StatusCode(500, new { message = "服务器内部错误" });
}
}
[HttpPost("token")]
[AllowAnonymous]
public async Task<IActionResult> GetToken([FromBody] OAuthLoginRequestDto request)
{
try
{
var result = await _oauthService.LoginAsync(request);
if (result.Success)
{
return Ok(result);
}
return BadRequest(result);
}
catch (Exception ex)
{
_logger.LogError(ex, "OAuth令牌获取时发生错误");
return StatusCode(500, ApiResponse<OAuthTokenResponseDto>.ErrorResult("服务器内部错误"));
}
}
[HttpPost("refresh")]
[AllowAnonymous]
public async Task<ActionResult<ApiResponse<AuthResponseDto>>> RefreshToken([FromBody] RefreshTokenRequestDto request)
public async Task<IActionResult> RefreshToken([FromBody] OAuthRefreshTokenRequestDto request)
{
if (request == null || string.IsNullOrEmpty(request.Token))
try
{
return BadRequest(ApiResponse<AuthResponseDto>.ErrorResult("令牌不能为空"));
var result = await _oauthService.RefreshTokenAsync(request);
if (result.Success)
{
return Ok(result);
}
return BadRequest(result);
}
// 使用OAuth刷新令牌
var tokenResult = await _authService.RefreshTokenAsync(request.Token);
if (!tokenResult.Success)
catch (Exception ex)
{
return BadRequest(ApiResponse<AuthResponseDto>.ErrorResult(tokenResult.Message));
_logger.LogError(ex, "OAuth令牌刷新时发生错误");
return StatusCode(500, ApiResponse<OAuthTokenResponseDto>.ErrorResult("服务器内部错误"));
}
// 创建认证响应DTO
var authResponse = new AuthResponseDto
{
Token = tokenResult.Data,
Expires = DateTime.UtcNow.AddHours(1) // OAuth访问令牌默认1小时过期
};
return Ok(ApiResponse<AuthResponseDto>.SuccessResult(authResponse, "令牌刷新成功"));
}
[HttpPost("logout")]
public async Task<ActionResult<ApiResponse<bool>>> Logout()
[HttpPost("revoke")]
[AllowAnonymous]
public async Task<IActionResult> RevokeToken([FromBody] string accessToken)
{
// 从JWT令牌中获取当前用户ID
var currentUserId = GetCurrentUserId();
if (currentUserId == null)
try
{
return Unauthorized(ApiResponse<bool>.ErrorResult("未授权访问"));
var result = await _oauthService.RevokeTokenAsync(accessToken);
if (result)
{
return Ok(new { message = "令牌已成功撤销" });
}
return BadRequest(new { message = "无效的令牌" });
}
catch (Exception ex)
{
_logger.LogError(ex, "OAuth令牌撤销时发生错误");
return StatusCode(500, new { message = "服务器内部错误" });
}
// 这里可以实现令牌黑名单或其他注销逻辑
// 目前只返回成功响应
return Ok(ApiResponse<bool>.SuccessResult(true));
}
private int? GetCurrentUserId()
[HttpGet("userinfo")]
public async Task<IActionResult> GetUserInfo()
{
// 从OAuth中间件获取用户ID
var userId = HttpContext.GetCurrentUserId();
if (userId.HasValue)
try
{
return userId.Value;
var userId = GetCurrentUserId();
var userEmail = GetCurrentUserEmail();
var username = GetCurrentUsername();
var clientId = GetCurrentClientId();
return Ok(new
{
userId,
username,
email = userEmail,
clientId
});
}
// 兼容旧的JWT方式
var userIdClaim = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier);
if (userIdClaim == null || !int.TryParse(userIdClaim.Value, out var jwtUserId))
catch (Exception ex)
{
return null;
_logger.LogError(ex, "获取用户信息时发生错误");
return StatusCode(500, new { message = "服务器内部错误" });
}
return jwtUserId;
}
}
}

View File

@@ -0,0 +1,54 @@
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;
namespace FutureMailAPI.Controllers
{
/// <summary>
/// 基础控制器,提供通用的用户身份验证方法
/// </summary>
[ApiController]
public class BaseController : ControllerBase
{
/// <summary>
/// 获取当前用户ID
/// 兼容OAuth中间件和JWT令牌两种验证方式
/// </summary>
/// <returns>用户ID如果未认证则返回0</returns>
protected int GetCurrentUserId()
{
var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier);
if (userIdClaim != null && int.TryParse(userIdClaim.Value, out var userId))
{
return userId;
}
return 0;
}
/// <summary>
/// 获取当前用户邮箱
/// </summary>
/// <returns>用户邮箱,如果未认证则返回空字符串</returns>
protected string GetCurrentUserEmail()
{
return User.FindFirst(ClaimTypes.Email)?.Value ?? string.Empty;
}
/// <summary>
/// 获取当前用户名
/// </summary>
/// <returns>用户名,如果未认证则返回空字符串</returns>
protected string GetCurrentUsername()
{
return User.FindFirst(ClaimTypes.Name)?.Value ?? string.Empty;
}
/// <summary>
/// 获取当前客户端ID
/// </summary>
/// <returns>客户端ID如果未认证则返回空字符串</returns>
protected string GetCurrentClientId()
{
return User.FindFirst("client_id")?.Value ?? string.Empty;
}
}
}

View File

@@ -6,9 +6,9 @@ using FutureMailAPI.DTOs;
namespace FutureMailAPI.Controllers
{
[ApiController]
[Route("api/v1/capsules")]
[Authorize]
public class CapsulesController : ControllerBase
[Route("api/v1/[controller]")]
public class CapsulesController : BaseController
{
private readonly ITimeCapsuleService _timeCapsuleService;
private readonly ILogger<CapsulesController> _logger;
@@ -20,17 +20,17 @@ namespace FutureMailAPI.Controllers
}
[HttpGet]
public async Task<ActionResult<ApiResponse<TimeCapsuleViewResponseDto>>> GetCapsules()
public async Task<IActionResult> GetCapsules()
{
// 从JWT令牌中获取当前用户ID
var currentUserId = GetCurrentUserId();
if (currentUserId == null)
if (currentUserId <= 0)
{
return Unauthorized(ApiResponse<TimeCapsuleViewResponseDto>.ErrorResult("未授权访问"));
}
var result = await _timeCapsuleService.GetTimeCapsuleViewAsync(currentUserId.Value);
var result = await _timeCapsuleService.GetTimeCapsuleViewAsync(currentUserId);
if (!result.Success)
{
@@ -41,7 +41,7 @@ namespace FutureMailAPI.Controllers
}
[HttpPut("{capsuleId}/style")]
public async Task<ActionResult<ApiResponse<TimeCapsuleResponseDto>>> UpdateCapsuleStyle(int capsuleId, [FromBody] TimeCapsuleStyleUpdateDto updateDto)
public async Task<IActionResult> UpdateCapsuleStyle(int capsuleId, [FromBody] TimeCapsuleStyleUpdateDto updateDto)
{
if (!ModelState.IsValid)
{
@@ -51,12 +51,12 @@ namespace FutureMailAPI.Controllers
// 从JWT令牌中获取当前用户ID
var currentUserId = GetCurrentUserId();
if (currentUserId == null)
if (currentUserId <= 0)
{
return Unauthorized(ApiResponse<TimeCapsuleResponseDto>.ErrorResult("未授权访问"));
}
var result = await _timeCapsuleService.UpdateTimeCapsuleStyleAsync(currentUserId.Value, capsuleId, updateDto);
var result = await _timeCapsuleService.UpdateTimeCapsuleStyleAsync(currentUserId, capsuleId, updateDto);
if (!result.Success)
{
@@ -65,17 +65,5 @@ namespace FutureMailAPI.Controllers
return Ok(result);
}
private int? GetCurrentUserId()
{
var userIdClaim = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier);
if (userIdClaim == null || !int.TryParse(userIdClaim.Value, out var userId))
{
return null;
}
return userId;
}
}
}

View File

@@ -8,8 +8,8 @@ namespace FutureMailAPI.Controllers
{
[ApiController]
[Route("api/v1/[controller]")]
[Authorize]
public class FileUploadController : ControllerBase
public class FileUploadController : BaseController
{
private readonly IFileUploadService _fileUploadService;
private readonly ILogger<FileUploadController> _logger;
@@ -171,19 +171,5 @@ namespace FutureMailAPI.Controllers
return StatusCode(500, ApiResponse<FileUploadResponseDto>.ErrorResult("服务器内部错误"));
}
}
/// <summary>
/// 从当前请求中获取用户ID
/// </summary>
/// <returns>用户ID</returns>
private int GetCurrentUserId()
{
var userIdClaim = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier);
if (userIdClaim != null && int.TryParse(userIdClaim.Value, out var userId))
{
return userId;
}
return 0;
}
}
}

View File

@@ -1,25 +1,25 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using FutureMailAPI.Services;
using Microsoft.AspNetCore.Authorization;
using FutureMailAPI.DTOs;
using System.Security.Claims;
using FutureMailAPI.Services;
namespace FutureMailAPI.Controllers
{
[ApiController]
[Route("api/v1/[controller]")]
[Authorize]
public class MailsController : ControllerBase
[Route("api/v1/mails")]
public class MailsController : BaseController
{
private readonly IMailService _mailService;
public MailsController(IMailService mailService)
private readonly ILogger<MailsController> _logger;
public MailsController(IMailService mailService, ILogger<MailsController> logger)
{
_mailService = mailService;
_logger = logger;
}
[HttpPost]
public async Task<ActionResult<ApiResponse<SentMailResponseDto>>> CreateMail([FromBody] SentMailCreateDto createDto)
public async Task<IActionResult> CreateMail([FromBody] SentMailCreateDto createDto)
{
if (!ModelState.IsValid)
{
@@ -29,12 +29,12 @@ namespace FutureMailAPI.Controllers
// 从JWT令牌中获取当前用户ID
var currentUserId = GetCurrentUserId();
if (currentUserId == null)
if (currentUserId <= 0)
{
return Unauthorized(ApiResponse<SentMailResponseDto>.ErrorResult("未授权访问"));
}
var result = await _mailService.CreateMailAsync(currentUserId.Value, createDto);
var result = await _mailService.CreateMailAsync(currentUserId, createDto);
if (!result.Success)
{
@@ -48,17 +48,17 @@ namespace FutureMailAPI.Controllers
}
[HttpGet("{mailId}")]
public async Task<ActionResult<ApiResponse<SentMailResponseDto>>> GetMail(int mailId)
public async Task<IActionResult> GetMail(int mailId)
{
// 从JWT令牌中获取当前用户ID
var currentUserId = GetCurrentUserId();
if (currentUserId == null)
if (currentUserId <= 0)
{
return Unauthorized(ApiResponse<SentMailResponseDto>.ErrorResult("未授权访问"));
}
var result = await _mailService.GetSentMailByIdAsync(currentUserId.Value, mailId);
var result = await _mailService.GetSentMailByIdAsync(currentUserId, mailId);
if (!result.Success)
{
@@ -69,23 +69,36 @@ namespace FutureMailAPI.Controllers
}
[HttpGet]
public async Task<ActionResult<ApiResponse<PagedResponse<SentMailResponseDto>>>> GetMails([FromQuery] MailListQueryDto queryDto)
public async Task<IActionResult> GetMails([FromQuery] MailListQueryDto queryDto)
{
// 从JWT令牌中获取当前用户ID
var currentUserId = GetCurrentUserId();
if (currentUserId == null)
try
{
return Unauthorized(ApiResponse<PagedResponse<SentMailResponseDto>>.ErrorResult("未授权访问"));
// 从JWT令牌中获取当前用户ID
var currentUserId = GetCurrentUserId();
if (currentUserId <= 0)
{
return Unauthorized(ApiResponse<PagedResponse<SentMailResponseDto>>.ErrorResult("未授权访问"));
}
var result = await _mailService.GetSentMailsAsync(currentUserId, queryDto);
if (result.Success)
{
return Ok(result);
}
return BadRequest(result);
}
catch (Exception ex)
{
_logger.LogError(ex, "获取邮件列表时发生错误");
return StatusCode(500, ApiResponse<PagedResponse<SentMailResponseDto>>.ErrorResult("服务器内部错误"));
}
var result = await _mailService.GetSentMailsAsync(currentUserId.Value, queryDto);
return Ok(result);
}
[HttpPut("{mailId}")]
public async Task<ActionResult<ApiResponse<SentMailResponseDto>>> UpdateMail(int mailId, [FromBody] SentMailUpdateDto updateDto)
public async Task<IActionResult> UpdateMail(int mailId, [FromBody] SentMailUpdateDto updateDto)
{
if (!ModelState.IsValid)
{
@@ -95,12 +108,12 @@ namespace FutureMailAPI.Controllers
// 从JWT令牌中获取当前用户ID
var currentUserId = GetCurrentUserId();
if (currentUserId == null)
if (currentUserId <= 0)
{
return Unauthorized(ApiResponse<SentMailResponseDto>.ErrorResult("未授权访问"));
}
var result = await _mailService.UpdateMailAsync(currentUserId.Value, mailId, updateDto);
var result = await _mailService.UpdateMailAsync(currentUserId, mailId, updateDto);
if (!result.Success)
{
@@ -111,17 +124,17 @@ namespace FutureMailAPI.Controllers
}
[HttpDelete("{mailId}")]
public async Task<ActionResult<ApiResponse<bool>>> DeleteMail(int mailId)
public async Task<IActionResult> DeleteMail(int mailId)
{
// 从JWT令牌中获取当前用户ID
var currentUserId = GetCurrentUserId();
if (currentUserId == null)
if (currentUserId <= 0)
{
return Unauthorized(ApiResponse<bool>.ErrorResult("未授权访问"));
}
var result = await _mailService.DeleteMailAsync(currentUserId.Value, mailId);
var result = await _mailService.DeleteMailAsync(currentUserId, mailId);
if (!result.Success)
{
@@ -132,33 +145,33 @@ namespace FutureMailAPI.Controllers
}
[HttpGet("received")]
public async Task<ActionResult<ApiResponse<PagedResponse<ReceivedMailResponseDto>>>> GetReceivedMails([FromQuery] MailListQueryDto queryDto)
public async Task<IActionResult> GetReceivedMails([FromQuery] MailListQueryDto queryDto)
{
// 从JWT令牌中获取当前用户ID
var currentUserId = GetCurrentUserId();
if (currentUserId == null)
if (currentUserId <= 0)
{
return Unauthorized(ApiResponse<PagedResponse<ReceivedMailResponseDto>>.ErrorResult("未授权访问"));
}
var result = await _mailService.GetReceivedMailsAsync(currentUserId.Value, queryDto);
var result = await _mailService.GetReceivedMailsAsync(currentUserId, queryDto);
return Ok(result);
}
[HttpGet("received/{id}")]
public async Task<ActionResult<ApiResponse<ReceivedMailResponseDto>>> GetReceivedMail(int id)
public async Task<IActionResult> GetReceivedMail(int id)
{
// 从JWT令牌中获取当前用户ID
var currentUserId = GetCurrentUserId();
if (currentUserId == null)
if (currentUserId <= 0)
{
return Unauthorized(ApiResponse<ReceivedMailResponseDto>.ErrorResult("未授权访问"));
}
var result = await _mailService.GetReceivedMailByIdAsync(currentUserId.Value, id);
var result = await _mailService.GetReceivedMailByIdAsync(currentUserId, id);
if (!result.Success)
{
@@ -169,17 +182,17 @@ namespace FutureMailAPI.Controllers
}
[HttpPost("received/{id}/mark-read")]
public async Task<ActionResult<ApiResponse<bool>>> MarkReceivedMailAsRead(int id)
public async Task<IActionResult> MarkReceivedMailAsRead(int id)
{
// 从JWT令牌中获取当前用户ID
var currentUserId = GetCurrentUserId();
if (currentUserId == null)
if (currentUserId <= 0)
{
return Unauthorized(ApiResponse<bool>.ErrorResult("未授权访问"));
}
var result = await _mailService.MarkReceivedMailAsReadAsync(currentUserId.Value, id);
var result = await _mailService.MarkReceivedMailAsReadAsync(currentUserId, id);
if (!result.Success)
{
@@ -190,17 +203,17 @@ namespace FutureMailAPI.Controllers
}
[HttpPost("{mailId}/revoke")]
public async Task<ActionResult<ApiResponse<bool>>> RevokeMail(int mailId)
public async Task<IActionResult> RevokeMail(int mailId)
{
// 从JWT令牌中获取当前用户ID
var currentUserId = GetCurrentUserId();
if (currentUserId == null)
if (currentUserId <= 0)
{
return Unauthorized(ApiResponse<bool>.ErrorResult("未授权访问"));
}
var result = await _mailService.RevokeMailAsync(currentUserId.Value, mailId);
var result = await _mailService.RevokeMailAsync(currentUserId, mailId);
if (!result.Success)
{
@@ -209,17 +222,5 @@ namespace FutureMailAPI.Controllers
return Ok(result);
}
private int? GetCurrentUserId()
{
var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier);
if (userIdClaim == null || !int.TryParse(userIdClaim.Value, out var userId))
{
return null;
}
return userId;
}
}
}

View File

@@ -8,8 +8,8 @@ namespace FutureMailAPI.Controllers
{
[ApiController]
[Route("api/v1/notification")]
[Authorize]
public class NotificationController : ControllerBase
public class NotificationController : BaseController
{
private readonly INotificationService _notificationService;
private readonly ILogger<NotificationController> _logger;
@@ -87,19 +87,5 @@ namespace FutureMailAPI.Controllers
return StatusCode(500, ApiResponse<NotificationSettingsDto>.ErrorResult("服务器内部错误"));
}
}
/// <summary>
/// 从JWT令牌中获取当前用户ID
/// </summary>
/// <returns>用户ID</returns>
private int GetCurrentUserId()
{
var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier);
if (userIdClaim != null && int.TryParse(userIdClaim.Value, out int userId))
{
return userId;
}
return 0;
}
}
}

View File

@@ -1,295 +0,0 @@
using Microsoft.AspNetCore.Mvc;
using FutureMailAPI.Services;
using FutureMailAPI.DTOs;
using FutureMailAPI.Models;
using FutureMailAPI.Extensions;
namespace FutureMailAPI.Controllers
{
[ApiController]
[Route("api/v1/oauth")]
public class OAuthController : ControllerBase
{
private readonly IOAuthService _oauthService;
private readonly ILogger<OAuthController> _logger;
public OAuthController(IOAuthService oauthService, ILogger<OAuthController> logger)
{
_oauthService = oauthService;
_logger = logger;
}
/// <summary>
/// OAuth登录端点
/// </summary>
[HttpPost("login")]
public async Task<IActionResult> Login([FromBody] OAuthLoginDto loginDto)
{
try
{
var result = await _oauthService.LoginAsync(loginDto);
if (result.Success)
{
return Ok(result);
}
return BadRequest(result);
}
catch (Exception ex)
{
_logger.LogError(ex, "OAuth登录时发生错误");
return StatusCode(500, new { message = "服务器内部错误" });
}
}
/// <summary>
/// 创建OAuth客户端
/// </summary>
[HttpPost("clients")]
public async Task<IActionResult> CreateClient([FromBody] OAuthClientCreateDto createDto)
{
try
{
// 从OAuth中间件获取当前用户ID
var userId = HttpContext.GetCurrentUserId();
if (!userId.HasValue)
{
return Unauthorized(new { message = "未授权访问" });
}
var result = await _oauthService.CreateClientAsync(userId.Value, createDto);
if (result.Success)
{
return Ok(result);
}
return BadRequest(result);
}
catch (Exception ex)
{
_logger.LogError(ex, "创建OAuth客户端时发生错误");
return StatusCode(500, new { message = "服务器内部错误" });
}
}
/// <summary>
/// 获取OAuth客户端信息
/// </summary>
[HttpGet("clients/{clientId}")]
public async Task<IActionResult> GetClient(string clientId)
{
try
{
var result = await _oauthService.GetClientAsync(clientId);
if (result.Success)
{
return Ok(result);
}
return NotFound(result);
}
catch (Exception ex)
{
_logger.LogError(ex, "获取OAuth客户端信息时发生错误");
return StatusCode(500, new { message = "服务器内部错误" });
}
}
/// <summary>
/// OAuth授权端点
/// </summary>
[HttpGet("authorize")]
public async Task<IActionResult> Authorize([FromQuery] OAuthAuthorizationRequestDto request)
{
try
{
// 从OAuth中间件获取当前用户ID
var userId = HttpContext.GetCurrentUserId();
if (!userId.HasValue)
{
// 如果用户未登录,重定向到登录页面
var loginRedirectUri = $"/api/v1/auth/login?redirect_uri={Uri.EscapeDataString(request.RedirectUri)}";
if (!string.IsNullOrEmpty(request.State))
{
loginRedirectUri += $"&state={request.State}";
}
return Redirect(loginRedirectUri);
}
var result = await _oauthService.AuthorizeAsync(userId.Value, request);
if (result.Success)
{
// 重定向到客户端,携带授权码
var redirectUri = $"{request.RedirectUri}?code={result.Data.Code}";
if (!string.IsNullOrEmpty(request.State))
{
redirectUri += $"&state={request.State}";
}
return Redirect(redirectUri);
}
// 错误重定向
var errorRedirectUri = $"{request.RedirectUri}?error={result.Message}";
if (!string.IsNullOrEmpty(request.State))
{
errorRedirectUri += $"&state={request.State}";
}
return Redirect(errorRedirectUri);
}
catch (Exception ex)
{
_logger.LogError(ex, "OAuth授权时发生错误");
// 错误重定向
var errorRedirectUri = $"{request.RedirectUri}?error=server_error";
if (!string.IsNullOrEmpty(request.State))
{
errorRedirectUri += $"&state={request.State}";
}
return Redirect(errorRedirectUri);
}
}
/// <summary>
/// OAuth令牌端点
/// </summary>
[HttpPost("token")]
[Microsoft.AspNetCore.Authorization.AllowAnonymous]
public async Task<IActionResult> ExchangeToken([FromForm] OAuthTokenRequestDto request)
{
_logger.LogInformation("OAuth令牌端点被调用");
try
{
_logger.LogInformation("OAuth令牌交换请求: GrantType={GrantType}, ClientId={ClientId}, Username={Username}",
request.GrantType, request.ClientId, request.Username);
if (request.GrantType == "authorization_code")
{
var result = await _oauthService.ExchangeCodeForTokenAsync(request);
if (result.Success)
{
return Ok(result);
}
return BadRequest(result);
}
else if (request.GrantType == "refresh_token")
{
var result = await _oauthService.RefreshTokenAsync(request);
if (result.Success)
{
return Ok(result);
}
return BadRequest(result);
}
else if (request.GrantType == "password")
{
_logger.LogInformation("处理密码授权类型登录请求");
// 创建OAuth登录请求
var loginDto = new OAuthLoginDto
{
UsernameOrEmail = request.Username,
Password = request.Password,
ClientId = request.ClientId,
ClientSecret = request.ClientSecret,
Scope = request.Scope
};
var result = await _oauthService.LoginAsync(loginDto);
if (result.Success)
{
_logger.LogInformation("密码授权类型登录成功");
return Ok(result);
}
_logger.LogWarning("密码授权类型登录失败: {Message}", result.Message);
return BadRequest(result);
}
else
{
_logger.LogWarning("不支持的授权类型: {GrantType}", request.GrantType);
return BadRequest(new { message = "不支持的授权类型" });
}
}
catch (Exception ex)
{
_logger.LogError(ex, "OAuth令牌交换时发生错误");
return StatusCode(500, new { message = "服务器内部错误" });
}
}
/// <summary>
/// 撤销令牌
/// </summary>
[HttpPost("revoke")]
public async Task<IActionResult> RevokeToken([FromForm] string token, [FromForm] string token_type_hint = "access_token")
{
try
{
var result = await _oauthService.RevokeTokenAsync(token);
if (result.Success)
{
return Ok(new { message = "令牌已撤销" });
}
return BadRequest(result);
}
catch (Exception ex)
{
_logger.LogError(ex, "撤销令牌时发生错误");
return StatusCode(500, new { message = "服务器内部错误" });
}
}
/// <summary>
/// 验证令牌
/// </summary>
[HttpPost("introspect")]
public async Task<IActionResult> IntrospectToken([FromForm] string token)
{
try
{
var result = await _oauthService.ValidateTokenAsync(token);
if (result.Success)
{
var accessToken = await _oauthService.GetAccessTokenAsync(token);
if (accessToken != null)
{
return Ok(new
{
active = true,
scope = accessToken.Scopes,
client_id = accessToken.Client.ClientId,
username = accessToken.User.Email,
exp = ((DateTimeOffset)accessToken.ExpiresAt).ToUnixTimeSeconds(),
iat = ((DateTimeOffset)accessToken.CreatedAt).ToUnixTimeSeconds()
});
}
}
return Ok(new { active = false });
}
catch (Exception ex)
{
_logger.LogError(ex, "验证令牌时发生错误");
return StatusCode(500, new { message = "服务器内部错误" });
}
}
}
}

View File

@@ -8,8 +8,8 @@ namespace FutureMailAPI.Controllers
{
[ApiController]
[Route("api/v1/[controller]")]
[Authorize]
public class PersonalSpaceController : ControllerBase
public class PersonalSpaceController : BaseController
{
private readonly IPersonalSpaceService _personalSpaceService;
private readonly ILogger<PersonalSpaceController> _logger;
@@ -156,19 +156,5 @@ namespace FutureMailAPI.Controllers
return StatusCode(500, ApiResponse<UserProfileResponseDto>.ErrorResult("服务器内部错误"));
}
}
/// <summary>
/// 从当前请求中获取用户ID
/// </summary>
/// <returns>用户ID</returns>
private int GetCurrentUserId()
{
var userIdClaim = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier);
if (userIdClaim != null && int.TryParse(userIdClaim.Value, out var userId))
{
return userId;
}
return 0;
}
}
}

View File

@@ -8,8 +8,8 @@ namespace FutureMailAPI.Controllers
{
[ApiController]
[Route("api/v1/statistics")]
[Authorize]
public class StatisticsController : ControllerBase
public class StatisticsController : BaseController
{
private readonly IPersonalSpaceService _personalSpaceService;
private readonly ILogger<StatisticsController> _logger;
@@ -50,19 +50,5 @@ namespace FutureMailAPI.Controllers
return StatusCode(500, ApiResponse<StatisticsResponseDto>.ErrorResult("服务器内部错误"));
}
}
/// <summary>
/// 从JWT令牌中获取当前用户ID
/// </summary>
/// <returns>用户ID</returns>
private int GetCurrentUserId()
{
var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier);
if (userIdClaim != null && int.TryParse(userIdClaim.Value, out int userId))
{
return userId;
}
return 0;
}
}
}

View File

@@ -0,0 +1,77 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using FutureMailAPI.Data;
using FutureMailAPI.Models;
using FutureMailAPI.Helpers;
namespace FutureMailAPI.Controllers
{
[ApiController]
[Route("api/v1/temp-fix")]
public class TempFixController : ControllerBase
{
private readonly FutureMailDbContext _context;
private readonly IPasswordHelper _passwordHelper;
public TempFixController(FutureMailDbContext context, IPasswordHelper passwordHelper)
{
_context = context;
_passwordHelper = passwordHelper;
}
[HttpPost("fix-passwords")]
public async Task<IActionResult> FixPasswordHashes()
{
try
{
// 获取所有用户
var users = await _context.Users.ToListAsync();
int fixedCount = 0;
foreach (var user in users)
{
// 如果salt为空但passwordHash有值说明需要修复
if (string.IsNullOrEmpty(user.Salt) && !string.IsNullOrEmpty(user.PasswordHash))
{
// 使用默认密码重新设置密码哈希
var newPasswordHash = _passwordHelper.HashPassword("password123");
user.PasswordHash = newPasswordHash;
user.Salt = _passwordHelper.GenerateSalt();
fixedCount++;
}
}
await _context.SaveChangesAsync();
return Ok(new {
success = true,
message = $"已修复 {fixedCount} 个用户的密码哈希",
fixedUsers = fixedCount
});
}
catch (Exception ex)
{
return BadRequest(new {
success = false,
message = $"修复失败: {ex.Message}"
});
}
}
[HttpGet("users")]
public async Task<IActionResult> GetUsers()
{
var users = await _context.Users
.Select(u => new {
u.Id,
u.Username,
u.Email,
PasswordHashLength = u.PasswordHash.Length,
HasSalt = !string.IsNullOrEmpty(u.Salt)
})
.ToListAsync();
return Ok(users);
}
}
}

View File

@@ -7,8 +7,8 @@ namespace FutureMailAPI.Controllers
{
[ApiController]
[Route("api/v1/[controller]")]
[Authorize]
public class TimeCapsulesController : ControllerBase
public class TimeCapsulesController : BaseController
{
private readonly ITimeCapsuleService _timeCapsuleService;
private readonly ILogger<TimeCapsulesController> _logger;
@@ -20,7 +20,7 @@ namespace FutureMailAPI.Controllers
}
[HttpPost]
public async Task<ActionResult<ApiResponse<TimeCapsuleResponseDto>>> CreateTimeCapsule([FromBody] TimeCapsuleCreateDto createDto)
public async Task<IActionResult> CreateTimeCapsule([FromBody] TimeCapsuleCreateDto createDto)
{
if (!ModelState.IsValid)
{
@@ -30,12 +30,12 @@ namespace FutureMailAPI.Controllers
// 从JWT令牌中获取当前用户ID
var currentUserId = GetCurrentUserId();
if (currentUserId == null)
if (currentUserId <= 0)
{
return Unauthorized(ApiResponse<TimeCapsuleResponseDto>.ErrorResult("未授权访问"));
}
var result = await _timeCapsuleService.CreateTimeCapsuleAsync(currentUserId.Value, createDto);
var result = await _timeCapsuleService.CreateTimeCapsuleAsync(currentUserId, createDto);
if (!result.Success)
{
@@ -49,17 +49,17 @@ namespace FutureMailAPI.Controllers
}
[HttpGet("{capsuleId}")]
public async Task<ActionResult<ApiResponse<TimeCapsuleResponseDto>>> GetTimeCapsule(int capsuleId)
public async Task<IActionResult> GetTimeCapsule(int capsuleId)
{
// 从JWT令牌中获取当前用户ID
var currentUserId = GetCurrentUserId();
if (currentUserId == null)
if (currentUserId <= 0)
{
return Unauthorized(ApiResponse<TimeCapsuleResponseDto>.ErrorResult("未授权访问"));
}
var result = await _timeCapsuleService.GetTimeCapsuleByIdAsync(currentUserId.Value, capsuleId);
var result = await _timeCapsuleService.GetTimeCapsuleByIdAsync(currentUserId, capsuleId);
if (!result.Success)
{
@@ -70,23 +70,23 @@ namespace FutureMailAPI.Controllers
}
[HttpGet]
public async Task<ActionResult<ApiResponse<PagedResponse<TimeCapsuleResponseDto>>>> GetTimeCapsules([FromQuery] TimeCapsuleListQueryDto queryDto)
public async Task<IActionResult> GetTimeCapsules([FromQuery] TimeCapsuleListQueryDto queryDto)
{
// 从JWT令牌中获取当前用户ID
var currentUserId = GetCurrentUserId();
if (currentUserId == null)
if (currentUserId <= 0)
{
return Unauthorized(ApiResponse<PagedResponse<TimeCapsuleResponseDto>>.ErrorResult("未授权访问"));
}
var result = await _timeCapsuleService.GetTimeCapsulesAsync(currentUserId.Value, queryDto);
var result = await _timeCapsuleService.GetTimeCapsulesAsync(currentUserId, queryDto);
return Ok(result);
}
[HttpPut("{capsuleId}")]
public async Task<ActionResult<ApiResponse<TimeCapsuleResponseDto>>> UpdateTimeCapsule(int capsuleId, [FromBody] TimeCapsuleUpdateDto updateDto)
public async Task<IActionResult> UpdateTimeCapsule(int capsuleId, [FromBody] TimeCapsuleUpdateDto updateDto)
{
if (!ModelState.IsValid)
{
@@ -96,12 +96,12 @@ namespace FutureMailAPI.Controllers
// 从JWT令牌中获取当前用户ID
var currentUserId = GetCurrentUserId();
if (currentUserId == null)
if (currentUserId <= 0)
{
return Unauthorized(ApiResponse<TimeCapsuleResponseDto>.ErrorResult("未授权访问"));
}
var result = await _timeCapsuleService.UpdateTimeCapsuleAsync(currentUserId.Value, capsuleId, updateDto);
var result = await _timeCapsuleService.UpdateTimeCapsuleAsync(currentUserId, capsuleId, updateDto);
if (!result.Success)
{
@@ -112,17 +112,17 @@ namespace FutureMailAPI.Controllers
}
[HttpDelete("{capsuleId}")]
public async Task<ActionResult<ApiResponse<bool>>> DeleteTimeCapsule(int capsuleId)
public async Task<IActionResult> DeleteTimeCapsule(int capsuleId)
{
// 从JWT令牌中获取当前用户ID
var currentUserId = GetCurrentUserId();
if (currentUserId == null)
if (currentUserId <= 0)
{
return Unauthorized(ApiResponse<bool>.ErrorResult("未授权访问"));
}
var result = await _timeCapsuleService.DeleteTimeCapsuleAsync(currentUserId.Value, capsuleId);
var result = await _timeCapsuleService.DeleteTimeCapsuleAsync(currentUserId, capsuleId);
if (!result.Success)
{
@@ -134,7 +134,7 @@ namespace FutureMailAPI.Controllers
[HttpGet("public")]
[AllowAnonymous]
public async Task<ActionResult<ApiResponse<PagedResponse<TimeCapsuleResponseDto>>>> GetPublicTimeCapsules([FromQuery] TimeCapsuleListQueryDto queryDto)
public async Task<IActionResult> GetPublicTimeCapsules([FromQuery] TimeCapsuleListQueryDto queryDto)
{
var result = await _timeCapsuleService.GetPublicTimeCapsulesAsync(queryDto);
@@ -142,17 +142,17 @@ namespace FutureMailAPI.Controllers
}
[HttpPost("public/{capsuleId}/claim")]
public async Task<ActionResult<ApiResponse<TimeCapsuleResponseDto>>> ClaimPublicCapsule(int capsuleId)
public async Task<IActionResult> ClaimPublicCapsule(int capsuleId)
{
// 从JWT令牌中获取当前用户ID
var currentUserId = GetCurrentUserId();
if (currentUserId == null)
if (currentUserId <= 0)
{
return Unauthorized(ApiResponse<TimeCapsuleResponseDto>.ErrorResult("未授权访问"));
}
var result = await _timeCapsuleService.ClaimPublicCapsuleAsync(currentUserId.Value, capsuleId);
var result = await _timeCapsuleService.ClaimPublicCapsuleAsync(currentUserId, capsuleId);
if (!result.Success)
{
@@ -163,17 +163,17 @@ namespace FutureMailAPI.Controllers
}
[HttpGet("view")]
public async Task<ActionResult<ApiResponse<TimeCapsuleViewResponseDto>>> GetTimeCapsuleView()
public async Task<IActionResult> GetTimeCapsuleView()
{
// 从JWT令牌中获取当前用户ID
var currentUserId = GetCurrentUserId();
if (currentUserId == null)
if (currentUserId <= 0)
{
return Unauthorized(ApiResponse<TimeCapsuleViewResponseDto>.ErrorResult("未授权访问"));
}
var result = await _timeCapsuleService.GetTimeCapsuleViewAsync(currentUserId.Value);
var result = await _timeCapsuleService.GetTimeCapsuleViewAsync(currentUserId);
if (!result.Success)
{
@@ -184,7 +184,7 @@ namespace FutureMailAPI.Controllers
}
[HttpPut("{capsuleId}/style")]
public async Task<ActionResult<ApiResponse<TimeCapsuleResponseDto>>> UpdateTimeCapsuleStyle(int capsuleId, [FromBody] TimeCapsuleStyleUpdateDto updateDto)
public async Task<IActionResult> UpdateTimeCapsuleStyle(int capsuleId, [FromBody] TimeCapsuleStyleUpdateDto updateDto)
{
if (!ModelState.IsValid)
{
@@ -194,12 +194,12 @@ namespace FutureMailAPI.Controllers
// 从JWT令牌中获取当前用户ID
var currentUserId = GetCurrentUserId();
if (currentUserId == null)
if (currentUserId <= 0)
{
return Unauthorized(ApiResponse<TimeCapsuleResponseDto>.ErrorResult("未授权访问"));
}
var result = await _timeCapsuleService.UpdateTimeCapsuleStyleAsync(currentUserId.Value, capsuleId, updateDto);
var result = await _timeCapsuleService.UpdateTimeCapsuleStyleAsync(currentUserId, capsuleId, updateDto);
if (!result.Success)
{
@@ -209,16 +209,5 @@ namespace FutureMailAPI.Controllers
return Ok(result);
}
private int? GetCurrentUserId()
{
var userIdClaim = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier);
if (userIdClaim == null || !int.TryParse(userIdClaim.Value, out var userId))
{
return null;
}
return userId;
}
}
}

View File

@@ -8,8 +8,8 @@ namespace FutureMailAPI.Controllers
{
[ApiController]
[Route("api/v1/timeline")]
[Authorize]
public class TimelineController : ControllerBase
public class TimelineController : BaseController
{
private readonly IPersonalSpaceService _personalSpaceService;
private readonly ILogger<TimelineController> _logger;
@@ -63,19 +63,5 @@ namespace FutureMailAPI.Controllers
return StatusCode(500, ApiResponse<TimelineResponseDto>.ErrorResult("服务器内部错误"));
}
}
/// <summary>
/// 从JWT令牌中获取当前用户ID
/// </summary>
/// <returns>用户ID</returns>
private int GetCurrentUserId()
{
var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier);
if (userIdClaim != null && int.TryParse(userIdClaim.Value, out int userId))
{
return userId;
}
return 0;
}
}
}

View File

@@ -8,8 +8,8 @@ namespace FutureMailAPI.Controllers
{
[ApiController]
[Route("api/v1/upload")]
[Authorize]
public class UploadController : ControllerBase
public class UploadController : BaseController
{
private readonly IFileUploadService _fileUploadService;
private readonly ILogger<UploadController> _logger;
@@ -97,19 +97,5 @@ namespace FutureMailAPI.Controllers
return StatusCode(500, ApiResponse<FileUploadResponseDto>.ErrorResult("服务器内部错误"));
}
}
/// <summary>
/// 从JWT令牌中获取当前用户ID
/// </summary>
/// <returns>用户ID</returns>
private int GetCurrentUserId()
{
var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier);
if (userIdClaim != null && int.TryParse(userIdClaim.Value, out int userId))
{
return userId;
}
return 0;
}
}
}

View File

@@ -8,8 +8,8 @@ namespace FutureMailAPI.Controllers
{
[ApiController]
[Route("api/v1/user")]
[Authorize]
public class UserController : ControllerBase
public class UserController : BaseController
{
private readonly IPersonalSpaceService _personalSpaceService;
private readonly ILogger<UserController> _logger;
@@ -81,19 +81,5 @@ namespace FutureMailAPI.Controllers
return StatusCode(500, ApiResponse<UserProfileResponseDto>.ErrorResult("服务器内部错误"));
}
}
/// <summary>
/// 从JWT令牌中获取当前用户ID
/// </summary>
/// <returns>用户ID</returns>
private int GetCurrentUserId()
{
var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier);
if (userIdClaim != null && int.TryParse(userIdClaim.Value, out int userId))
{
return userId;
}
return 0;
}
}
}

View File

@@ -7,8 +7,8 @@ namespace FutureMailAPI.Controllers
{
[ApiController]
[Route("api/v1/users")]
[Authorize]
public class UsersController : ControllerBase
public class UsersController : BaseController
{
private readonly IUserService _userService;
private readonly ILogger<UsersController> _logger;
@@ -20,12 +20,12 @@ namespace FutureMailAPI.Controllers
}
[HttpGet("{id}")]
public async Task<ActionResult<ApiResponse<UserResponseDto>>> GetUser(int id)
public async Task<IActionResult> GetUser(int id)
{
// 从JWT令牌中获取当前用户ID
var currentUserId = GetCurrentUserId();
if (currentUserId == null)
if (currentUserId <= 0)
{
return Unauthorized(ApiResponse<UserResponseDto>.ErrorResult("未授权访问"));
}
@@ -47,7 +47,7 @@ namespace FutureMailAPI.Controllers
}
[HttpPut("{id}")]
public async Task<ActionResult<ApiResponse<UserResponseDto>>> UpdateUser(int id, [FromBody] UserUpdateDto updateDto)
public async Task<IActionResult> UpdateUser(int id, [FromBody] UserUpdateDto updateDto)
{
if (!ModelState.IsValid)
{
@@ -57,7 +57,7 @@ namespace FutureMailAPI.Controllers
// 从JWT令牌中获取当前用户ID
var currentUserId = GetCurrentUserId();
if (currentUserId == null)
if (currentUserId <= 0)
{
return Unauthorized(ApiResponse<UserResponseDto>.ErrorResult("未授权访问"));
}
@@ -79,7 +79,7 @@ namespace FutureMailAPI.Controllers
}
[HttpPost("{id}/change-password")]
public async Task<ActionResult<ApiResponse<bool>>> ChangePassword(int id, [FromBody] ChangePasswordDto changePasswordDto)
public async Task<IActionResult> ChangePassword(int id, [FromBody] ChangePasswordDto changePasswordDto)
{
if (!ModelState.IsValid)
{
@@ -89,7 +89,7 @@ namespace FutureMailAPI.Controllers
// 从JWT令牌中获取当前用户ID
var currentUserId = GetCurrentUserId();
if (currentUserId == null)
if (currentUserId <= 0)
{
return Unauthorized(ApiResponse<bool>.ErrorResult("未授权访问"));
}
@@ -109,17 +109,5 @@ namespace FutureMailAPI.Controllers
return Ok(result);
}
private int? GetCurrentUserId()
{
var userIdClaim = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier);
if (userIdClaim == null || !int.TryParse(userIdClaim.Value, out var userId))
{
return null;
}
return userId;
}
}
}

View File

@@ -52,9 +52,9 @@ namespace FutureMailAPI.DTOs
public class AuthResponseDto
{
public string Token { get; set; } = string.Empty;
public string? RefreshToken { get; set; }
public DateTime Expires { get; set; }
public UserResponseDto User { get; set; } = new();
public string Token { get; set; } = string.Empty;
public string RefreshToken { get; set; } = string.Empty;
public int ExpiresIn { get; set; } = 3600; // 默认1小时过期
}
}

View File

@@ -2,104 +2,42 @@ using System.ComponentModel.DataAnnotations;
namespace FutureMailAPI.DTOs
{
public class OAuthAuthorizationRequestDto
public class OAuthLoginRequestDto
{
[Required]
public string ResponseType { get; set; } = "code"; // code for authorization code flow
[Required]
[Required(ErrorMessage = "客户端ID是必填项")]
public string ClientId { get; set; } = string.Empty;
[Required]
public string RedirectUri { get; set; } = string.Empty;
[Required(ErrorMessage = "客户端密钥是必填项")]
public string ClientSecret { get; set; } = string.Empty;
public string Scope { get; set; } = "read write"; // Default scopes
[Required(ErrorMessage = "用户名或邮箱是必填项")]
public string Username { get; set; } = string.Empty;
public string State { get; set; } = string.Empty; // CSRF protection
}
public class OAuthTokenRequestDto
{
[Required]
public string GrantType { get; set; } = string.Empty; // authorization_code, refresh_token, client_credentials, password
[Required(ErrorMessage = "密码是必填项")]
public string Password { get; set; } = string.Empty;
public string Code { get; set; } = string.Empty; // For authorization_code grant
public string RefreshToken { get; set; } = string.Empty; // For refresh_token grant
public string Username { get; set; } = string.Empty; // For password grant
public string Password { get; set; } = string.Empty; // For password grant
[Required]
public string ClientId { get; set; } = string.Empty;
public string ClientSecret { get; set; } = string.Empty; // Optional for public clients
public string RedirectUri { get; set; } = string.Empty; // Required for authorization_code grant
public string Scope { get; set; } = string.Empty; // Optional, defaults to requested scopes
public string? Scope { get; set; }
}
public class OAuthTokenResponseDto
{
public string AccessToken { get; set; } = string.Empty;
public string TokenType { get; set; } = "Bearer";
public int ExpiresIn { get; set; } // Seconds until expiration
public string RefreshToken { get; set; } = string.Empty;
public string Scope { get; set; } = string.Empty;
public string TokenType { get; set; } = "Bearer";
public int ExpiresIn { get; set; }
public string? Scope { get; set; }
public UserResponseDto User { get; set; } = new();
}
public class OAuthAuthorizationResponseDto
public class OAuthRefreshTokenRequestDto
{
public string Code { get; set; } = string.Empty;
public string State { get; set; } = string.Empty;
}
public class OAuthClientDto
{
public int Id { get; set; }
public string ClientId { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public string[] RedirectUris { get; set; } = Array.Empty<string>();
public string[] Scopes { get; set; } = Array.Empty<string>();
public bool IsActive { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
}
public class OAuthClientCreateDto
{
[Required]
[StringLength(100)]
public string Name { get; set; } = string.Empty;
[Required(ErrorMessage = "刷新令牌是必填项")]
public string RefreshToken { get; set; } = string.Empty;
[Required]
public string[] RedirectUris { get; set; } = Array.Empty<string>();
[Required]
public string[] Scopes { get; set; } = Array.Empty<string>();
}
public class OAuthClientSecretDto
{
[Required(ErrorMessage = "客户端ID是必填项")]
public string ClientId { get; set; } = string.Empty;
[Required(ErrorMessage = "客户端密钥是必填项")]
public string ClientSecret { get; set; } = string.Empty;
}
public class OAuthLoginDto
{
[Required]
public string UsernameOrEmail { get; set; } = string.Empty;
[Required]
public string Password { get; set; } = string.Empty;
[Required]
public string ClientId { get; set; } = string.Empty;
public string ClientSecret { get; set; } = string.Empty; // Optional for public clients
public string Scope { get; set; } = "read write"; // Default scopes
}
}

View File

@@ -50,9 +50,5 @@ namespace FutureMailAPI.DTOs
public string NewPassword { get; set; } = string.Empty;
}
public class RefreshTokenRequestDto
{
[Required(ErrorMessage = "令牌是必填项")]
public string Token { get; set; } = string.Empty;
}
}

View File

@@ -14,9 +14,7 @@ namespace FutureMailAPI.Data
public DbSet<ReceivedMail> ReceivedMails { get; set; }
public DbSet<TimeCapsule> TimeCapsules { get; set; }
public DbSet<OAuthClient> OAuthClients { get; set; }
public DbSet<OAuthAuthorizationCode> OAuthAuthorizationCodes { get; set; }
public DbSet<OAuthAccessToken> OAuthAccessTokens { get; set; }
public DbSet<OAuthRefreshToken> OAuthRefreshTokens { get; set; }
public DbSet<OAuthToken> OAuthTokens { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
@@ -86,51 +84,21 @@ namespace FutureMailAPI.Data
entity.Property(e => e.UpdatedAt).HasDefaultValueSql("CURRENT_TIMESTAMP");
});
// 配置OAuthAuthorizationCode实体
modelBuilder.Entity<OAuthAuthorizationCode>(entity =>
// 配置OAuthToken实体
modelBuilder.Entity<OAuthToken>(entity =>
{
entity.HasOne(e => e.Client)
.WithMany()
.HasForeignKey(e => e.ClientId)
.OnDelete(DeleteBehavior.Cascade);
entity.HasOne(e => e.User)
.WithMany()
.HasForeignKey(e => e.UserId)
.OnDelete(DeleteBehavior.Cascade);
entity.Property(e => e.CreatedAt).HasDefaultValueSql("CURRENT_TIMESTAMP");
});
// 配置OAuthAccessToken实体
modelBuilder.Entity<OAuthAccessToken>(entity =>
{
entity.HasOne(e => e.Client)
.WithMany()
.WithMany(c => c.Tokens)
.HasForeignKey(e => e.ClientId)
.OnDelete(DeleteBehavior.Cascade);
entity.HasOne(e => e.User)
.WithMany()
.HasForeignKey(e => e.UserId)
.OnDelete(DeleteBehavior.Cascade);
entity.Property(e => e.CreatedAt).HasDefaultValueSql("CURRENT_TIMESTAMP");
});
// 配置OAuthRefreshToken实体
modelBuilder.Entity<OAuthRefreshToken>(entity =>
{
entity.HasOne(e => e.Client)
.WithMany()
.HasForeignKey(e => e.ClientId)
.OnDelete(DeleteBehavior.Cascade);
entity.HasOne(e => e.User)
.WithMany()
.HasForeignKey(e => e.UserId)
.OnDelete(DeleteBehavior.Cascade);
entity.HasIndex(e => e.AccessToken).IsUnique();
entity.HasIndex(e => e.RefreshToken).IsUnique();
entity.Property(e => e.CreatedAt).HasDefaultValueSql("CURRENT_TIMESTAMP");
});
}

View File

@@ -1,46 +1,23 @@
using FutureMailAPI.Models;
using Microsoft.AspNetCore.Mvc;
namespace FutureMailAPI.Extensions
{
public static class HttpContextExtensions
{
/// <summary>
/// 获取当前用户ID
/// 获取当前用户ID简化版本不再依赖token
/// </summary>
public static int? GetCurrentUserId(this HttpContext context)
{
if (context.Items.TryGetValue("UserId", out var userIdObj) && userIdObj is int userId)
// 简化实现从查询参数或表单数据中获取用户ID
// 在实际应用中,这里应该使用会话或其他认证机制
if (context.Request.Query.TryGetValue("userId", out var userIdStr) &&
int.TryParse(userIdStr, out var userId))
{
return userId;
}
return null;
}
/// <summary>
/// 获取当前用户邮箱
/// </summary>
public static string? GetCurrentUserEmail(this HttpContext context)
{
if (context.Items.TryGetValue("UserEmail", out var userEmailObj) && userEmailObj is string userEmail)
{
return userEmail;
}
return null;
}
/// <summary>
/// 获取当前访问令牌
/// </summary>
public static OAuthAccessToken? GetCurrentAccessToken(this HttpContext context)
{
if (context.Items.TryGetValue("AccessToken", out var accessTokenObj) && accessTokenObj is OAuthAccessToken accessToken)
{
return accessToken;
}
return null;
}
}
}

View File

@@ -0,0 +1,138 @@
using FutureMailAPI.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Microsoft.IdentityModel.Tokens;
using Microsoft.Extensions.Configuration;
using System.Text;
using FutureMailAPI.Data;
namespace FutureMailAPI.Filters
{
public class OAuthAuthenticationFilter : IAsyncActionFilter
{
private readonly IOAuthService _oauthService;
private readonly ILogger<OAuthAuthenticationFilter> _logger;
private readonly IConfiguration _configuration;
private readonly FutureMailDbContext _context;
public OAuthAuthenticationFilter(IOAuthService oauthService, ILogger<OAuthAuthenticationFilter> logger, IConfiguration configuration, FutureMailDbContext context)
{
_oauthService = oauthService;
_logger = logger;
_configuration = configuration;
_context = context;
}
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
// 跳过带有AllowAnonymous特性的操作
var endpoint = context.HttpContext.GetEndpoint();
if (endpoint?.Metadata?.GetMetadata<Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute>() != null)
{
await next();
return;
}
// 从Authorization头获取令牌
var authHeader = context.HttpContext.Request.Headers.Authorization.FirstOrDefault();
if (string.IsNullOrEmpty(authHeader) || !authHeader.StartsWith("Bearer "))
{
context.Result = new UnauthorizedObjectResult(new { error = "缺少授权令牌" });
return;
}
var token = authHeader.Substring("Bearer ".Length).Trim();
_logger.LogInformation("正在验证令牌: {Token}", token.Substring(0, Math.Min(50, token.Length)) + "...");
try
{
// 首先尝试验证JWT令牌
var tokenHandler = new JwtSecurityTokenHandler();
var jwtSettings = _configuration.GetSection("Jwt");
var key = Encoding.ASCII.GetBytes(jwtSettings["Key"] ?? throw new InvalidOperationException("JWT密钥未配置"));
var validationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = true,
ValidIssuer = jwtSettings["Issuer"],
ValidateAudience = true,
ValidAudience = jwtSettings["Audience"],
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
// 验证JWT令牌
var principal = tokenHandler.ValidateToken(token, validationParameters, out SecurityToken validatedToken);
// 检查令牌类型
var tokenType = principal.FindFirst("token_type")?.Value;
_logger.LogInformation("令牌类型: {TokenType}", tokenType ?? "普通用户令牌");
if (tokenType == "oauth")
{
// OAuth令牌检查数据库中是否存在
var oauthToken = await _oauthService.GetTokenAsync(token);
if (oauthToken == null)
{
_logger.LogWarning("OAuth令牌不存在或已过期");
context.Result = new UnauthorizedObjectResult(new { error = "OAuth令牌不存在或已过期" });
return;
}
}
else
{
// 普通用户令牌,检查用户表中是否有此用户
var userId = principal.FindFirst(ClaimTypes.NameIdentifier)?.Value;
_logger.LogInformation("用户ID: {UserId}", userId);
if (string.IsNullOrEmpty(userId) || !int.TryParse(userId, out int uid))
{
_logger.LogWarning("令牌中未包含有效的用户ID");
context.Result = new UnauthorizedObjectResult(new { error = "令牌无效" });
return;
}
// 验证用户是否存在
var user = await _context.Users.FindAsync(uid);
if (user == null)
{
_logger.LogWarning("用户ID {UserId} 不存在", uid);
context.Result = new UnauthorizedObjectResult(new { error = "用户不存在" });
return;
}
_logger.LogInformation("用户验证成功: {UserId}", uid);
}
// 设置用户信息
context.HttpContext.User = principal;
await next();
}
catch (SecurityTokenExpiredException)
{
_logger.LogWarning("令牌已过期");
context.Result = new UnauthorizedObjectResult(new { error = "令牌已过期" });
}
catch (SecurityTokenInvalidSignatureException)
{
_logger.LogWarning("令牌签名无效");
context.Result = new UnauthorizedObjectResult(new { error = "令牌签名无效" });
}
catch (SecurityTokenException ex)
{
_logger.LogWarning(ex, "令牌验证失败");
context.Result = new UnauthorizedObjectResult(new { error = "令牌验证失败" });
}
catch (Exception ex)
{
_logger.LogError(ex, "OAuth认证时发生错误");
context.Result = new UnauthorizedObjectResult(new { error = "令牌验证失败" });
}
}
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -6,9 +6,14 @@
<ImplicitUsings>enable</ImplicitUsings>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateTargetFrameworkAttribute>false</GenerateTargetFrameworkAttribute>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.9" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.9" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.9">
@@ -20,11 +25,13 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.3.0" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="9.0.0" />
<PackageReference Include="Quartz" Version="3.15.0" />
<PackageReference Include="Quartz.Extensions.Hosting" Version="3.15.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.6" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.14.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.3.0" />
</ItemGroup>
</Project>

View File

@@ -1,97 +0,0 @@
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using FutureMailAPI.Models;
namespace FutureMailAPI.Helpers
{
public interface IJwtHelper
{
string GenerateToken(User user);
string GenerateToken(int userId, string username, string email);
ClaimsPrincipal? ValidateToken(string token);
}
public class JwtHelper : IJwtHelper
{
private readonly IConfiguration _configuration;
public JwtHelper(IConfiguration configuration)
{
_configuration = configuration;
}
public string GenerateToken(User user)
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_configuration["Jwt:Key"]!);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.Username),
new Claim(ClaimTypes.Email, user.Email)
}),
Expires = DateTime.UtcNow.AddDays(7),
Issuer = _configuration["Jwt:Issuer"],
Audience = _configuration["Jwt:Audience"],
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
public string GenerateToken(int userId, string username, string email)
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_configuration["Jwt:Key"]!);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.NameIdentifier, userId.ToString()),
new Claim(ClaimTypes.Name, username),
new Claim(ClaimTypes.Email, email)
}),
Expires = DateTime.UtcNow.AddDays(7),
Issuer = _configuration["Jwt:Issuer"],
Audience = _configuration["Jwt:Audience"],
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
public ClaimsPrincipal? ValidateToken(string token)
{
try
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_configuration["Jwt:Key"]!);
var validationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = true,
ValidIssuer = _configuration["Jwt:Issuer"],
ValidateAudience = true,
ValidAudience = _configuration["Jwt:Audience"],
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
var principal = tokenHandler.ValidateToken(token, validationParameters, out SecurityToken validatedToken);
return principal;
}
catch
{
return null;
}
}
}
}

View File

@@ -7,6 +7,7 @@ namespace FutureMailAPI.Helpers
{
string HashPassword(string password);
bool VerifyPassword(string password, string hash);
bool VerifyPassword(string password, string hash, string salt);
string HashPassword(string password, string salt);
string GenerateSalt();
}
@@ -59,23 +60,55 @@ namespace FutureMailAPI.Helpers
{
try
{
// 从存储的哈希中提取盐值
// 检查哈希长度判断是完整哈希36字节还是纯哈希20字节
byte[] hashBytes = Convert.FromBase64String(hash);
byte[] salt = new byte[16];
Array.Copy(hashBytes, 0, salt, 0, 16);
// 使用相同的盐值计算密码的哈希
var pbkdf2 = new Rfc2898DeriveBytes(password, salt, 10000);
byte[] computedHash = pbkdf2.GetBytes(20);
// 比较两个哈希值
for (int i = 0; i < 20; i++)
if (hashBytes.Length == 36)
{
if (hashBytes[i + 16] != computedHash[i])
return false;
// 完整哈希格式:盐值(16字节) + 哈希值(20字节)
byte[] salt = new byte[16];
Array.Copy(hashBytes, 0, salt, 0, 16);
// 使用相同的盐值计算密码的哈希
var pbkdf2 = new Rfc2898DeriveBytes(password, salt, 10000);
byte[] computedHash = pbkdf2.GetBytes(20);
// 比较两个哈希值
for (int i = 0; i < 20; i++)
{
if (hashBytes[i + 16] != computedHash[i])
return false;
}
return true;
}
return true;
else if (hashBytes.Length == 20)
{
// 纯哈希格式:只有哈希值(20字节)
// 这种情况下,我们需要从数据库中获取盐值
// 但由于VerifyPassword方法没有盐值参数我们无法验证这种格式
// 返回false表示验证失败
return false;
}
else
{
// 未知的哈希格式
return false;
}
}
catch
{
return false;
}
}
public bool VerifyPassword(string password, string hash, string salt)
{
try
{
// 使用提供的盐值计算密码的哈希
var computedHash = HashPassword(password, salt);
return hash == computedHash;
}
catch
{

View File

@@ -1,62 +0,0 @@
using FutureMailAPI.Services;
using FutureMailAPI.Models;
namespace FutureMailAPI.Middleware
{
public class OAuthAuthenticationMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<OAuthAuthenticationMiddleware> _logger;
public OAuthAuthenticationMiddleware(RequestDelegate next, ILogger<OAuthAuthenticationMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context, IOAuthService oauthService)
{
// 检查是否需要OAuth认证
var endpoint = context.GetEndpoint();
if (endpoint != null)
{
// 如果端点标记为AllowAnonymous则跳过认证
var allowAnonymousAttribute = endpoint.Metadata.GetMetadata<Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute>();
if (allowAnonymousAttribute != null)
{
await _next(context);
return;
}
}
// 检查Authorization头
var authHeader = context.Request.Headers.Authorization.FirstOrDefault();
if (authHeader != null && authHeader.StartsWith("Bearer "))
{
var token = authHeader.Substring("Bearer ".Length).Trim();
// 验证令牌
var validationResult = await oauthService.ValidateTokenAsync(token);
if (validationResult.Success)
{
// 获取访问令牌信息
var accessToken = await oauthService.GetAccessTokenAsync(token);
if (accessToken != null)
{
// 将用户信息添加到HttpContext
context.Items["UserId"] = accessToken.UserId;
context.Items["UserEmail"] = accessToken.User.Email;
context.Items["AccessToken"] = accessToken;
await _next(context);
return;
}
}
}
// 如果没有有效的令牌返回401未授权
context.Response.StatusCode = 401;
await context.Response.WriteAsync("未授权访问");
}
}
}

View File

@@ -1,559 +0,0 @@
// <auto-generated />
using System;
using FutureMailAPI.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace FutureMailAPI.Migrations
{
[DbContext(typeof(FutureMailDbContext))]
[Migration("20251016011551_AddOAuthEntities")]
partial class AddOAuthEntities
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "9.0.9");
modelBuilder.Entity("FutureMailAPI.Models.OAuthAccessToken", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("ClientId")
.HasColumnType("INTEGER");
b.Property<DateTime>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValueSql("CURRENT_TIMESTAMP");
b.Property<DateTime>("ExpiresAt")
.HasColumnType("TEXT");
b.Property<bool>("IsRevoked")
.HasColumnType("INTEGER");
b.Property<string>("Scopes")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Token")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("ClientId");
b.HasIndex("UserId");
b.ToTable("OAuthAccessTokens");
});
modelBuilder.Entity("FutureMailAPI.Models.OAuthAuthorizationCode", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("ClientId")
.HasColumnType("INTEGER");
b.Property<string>("Code")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValueSql("CURRENT_TIMESTAMP");
b.Property<DateTime>("ExpiresAt")
.HasColumnType("TEXT");
b.Property<bool>("IsUsed")
.HasColumnType("INTEGER");
b.Property<string>("RedirectUri")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Scopes")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("ClientId");
b.HasIndex("UserId");
b.ToTable("OAuthAuthorizationCodes");
});
modelBuilder.Entity("FutureMailAPI.Models.OAuthClient", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ClientId")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("ClientSecret")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValueSql("CURRENT_TIMESTAMP");
b.Property<bool>("IsActive")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("RedirectUris")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Scopes")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTime>("UpdatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValueSql("CURRENT_TIMESTAMP");
b.HasKey("Id");
b.HasIndex("ClientId")
.IsUnique();
b.ToTable("OAuthClients");
});
modelBuilder.Entity("FutureMailAPI.Models.OAuthRefreshToken", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("ClientId")
.HasColumnType("INTEGER");
b.Property<DateTime>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValueSql("CURRENT_TIMESTAMP");
b.Property<DateTime>("ExpiresAt")
.HasColumnType("TEXT");
b.Property<bool>("IsUsed")
.HasColumnType("INTEGER");
b.Property<string>("Token")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("ClientId");
b.HasIndex("UserId");
b.ToTable("OAuthRefreshTokens");
});
modelBuilder.Entity("FutureMailAPI.Models.ReceivedMail", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<bool>("IsRead")
.HasColumnType("INTEGER");
b.Property<bool>("IsReplied")
.HasColumnType("INTEGER");
b.Property<DateTime?>("ReadAt")
.HasColumnType("TEXT");
b.Property<DateTime>("ReceivedAt")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValueSql("CURRENT_TIMESTAMP");
b.Property<int>("RecipientId")
.HasColumnType("INTEGER");
b.Property<int>("RecipientId1")
.HasColumnType("INTEGER");
b.Property<int?>("ReplyMailId")
.HasColumnType("INTEGER");
b.Property<int>("SentMailId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("RecipientId");
b.HasIndex("RecipientId1");
b.HasIndex("SentMailId");
b.ToTable("ReceivedMails");
});
modelBuilder.Entity("FutureMailAPI.Models.SentMail", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Attachments")
.HasColumnType("TEXT");
b.Property<string>("Content")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTime>("DeliveryTime")
.HasColumnType("TEXT");
b.Property<string>("EncryptionKey")
.HasColumnType("TEXT");
b.Property<bool>("IsEncrypted")
.HasColumnType("INTEGER");
b.Property<int?>("RecipientId")
.HasColumnType("INTEGER");
b.Property<int?>("RecipientId1")
.HasColumnType("INTEGER");
b.Property<int>("RecipientType")
.HasColumnType("INTEGER");
b.Property<int>("SenderId")
.HasColumnType("INTEGER");
b.Property<DateTime>("SentAt")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValueSql("CURRENT_TIMESTAMP");
b.Property<int>("Status")
.HasColumnType("INTEGER");
b.Property<string>("Theme")
.HasMaxLength(50)
.HasColumnType("TEXT");
b.Property<string>("Title")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("TEXT");
b.Property<string>("TriggerDetails")
.HasColumnType("TEXT");
b.Property<int>("TriggerType")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("RecipientId");
b.HasIndex("RecipientId1");
b.HasIndex("SenderId");
b.ToTable("SentMails");
});
modelBuilder.Entity("FutureMailAPI.Models.TimeCapsule", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Color")
.HasMaxLength(20)
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValueSql("CURRENT_TIMESTAMP");
b.Property<double>("Opacity")
.HasColumnType("REAL");
b.Property<double>("PositionX")
.HasColumnType("REAL");
b.Property<double>("PositionY")
.HasColumnType("REAL");
b.Property<double>("PositionZ")
.HasColumnType("REAL");
b.Property<double>("Rotation")
.HasColumnType("REAL");
b.Property<int>("SentMailId")
.HasColumnType("INTEGER");
b.Property<int>("SentMailId1")
.HasColumnType("INTEGER");
b.Property<double>("Size")
.HasColumnType("REAL");
b.Property<int>("Status")
.HasColumnType("INTEGER");
b.Property<int>("Type")
.HasColumnType("INTEGER");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("SentMailId");
b.HasIndex("SentMailId1");
b.HasIndex("UserId");
b.ToTable("TimeCapsules");
});
modelBuilder.Entity("FutureMailAPI.Models.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Avatar")
.HasMaxLength(500)
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValueSql("CURRENT_TIMESTAMP");
b.Property<string>("Email")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("TEXT");
b.Property<bool>("IsActive")
.HasColumnType("INTEGER");
b.Property<DateTime?>("LastLoginAt")
.HasColumnType("TEXT");
b.Property<string>("Nickname")
.HasMaxLength(100)
.HasColumnType("TEXT");
b.Property<string>("PasswordHash")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("TEXT");
b.Property<string>("PreferredBackground")
.HasMaxLength(50)
.HasColumnType("TEXT");
b.Property<string>("PreferredScene")
.HasMaxLength(20)
.HasColumnType("TEXT");
b.Property<string>("Username")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("Email")
.IsUnique();
b.HasIndex("Username")
.IsUnique();
b.ToTable("Users");
});
modelBuilder.Entity("FutureMailAPI.Models.OAuthAccessToken", b =>
{
b.HasOne("FutureMailAPI.Models.OAuthClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("FutureMailAPI.Models.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Client");
b.Navigation("User");
});
modelBuilder.Entity("FutureMailAPI.Models.OAuthAuthorizationCode", b =>
{
b.HasOne("FutureMailAPI.Models.OAuthClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("FutureMailAPI.Models.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Client");
b.Navigation("User");
});
modelBuilder.Entity("FutureMailAPI.Models.OAuthRefreshToken", b =>
{
b.HasOne("FutureMailAPI.Models.OAuthClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("FutureMailAPI.Models.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Client");
b.Navigation("User");
});
modelBuilder.Entity("FutureMailAPI.Models.ReceivedMail", b =>
{
b.HasOne("FutureMailAPI.Models.User", null)
.WithMany()
.HasForeignKey("RecipientId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("FutureMailAPI.Models.User", "Recipient")
.WithMany("ReceivedMails")
.HasForeignKey("RecipientId1")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("FutureMailAPI.Models.SentMail", "SentMail")
.WithMany()
.HasForeignKey("SentMailId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Recipient");
b.Navigation("SentMail");
});
modelBuilder.Entity("FutureMailAPI.Models.SentMail", b =>
{
b.HasOne("FutureMailAPI.Models.User", null)
.WithMany()
.HasForeignKey("RecipientId")
.OnDelete(DeleteBehavior.SetNull);
b.HasOne("FutureMailAPI.Models.User", "Recipient")
.WithMany()
.HasForeignKey("RecipientId1");
b.HasOne("FutureMailAPI.Models.User", "Sender")
.WithMany("SentMails")
.HasForeignKey("SenderId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("Recipient");
b.Navigation("Sender");
});
modelBuilder.Entity("FutureMailAPI.Models.TimeCapsule", b =>
{
b.HasOne("FutureMailAPI.Models.SentMail", null)
.WithMany()
.HasForeignKey("SentMailId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("FutureMailAPI.Models.SentMail", "SentMail")
.WithMany()
.HasForeignKey("SentMailId1")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("FutureMailAPI.Models.User", "User")
.WithMany("TimeCapsules")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("SentMail");
b.Navigation("User");
});
modelBuilder.Entity("FutureMailAPI.Models.User", b =>
{
b.Navigation("ReceivedMails");
b.Navigation("SentMails");
b.Navigation("TimeCapsules");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -1,180 +0,0 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace FutureMailAPI.Migrations
{
/// <inheritdoc />
public partial class AddOAuthEntities : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "OAuthClients",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
ClientId = table.Column<string>(type: "TEXT", nullable: false),
ClientSecret = table.Column<string>(type: "TEXT", nullable: false),
Name = table.Column<string>(type: "TEXT", nullable: false),
RedirectUris = table.Column<string>(type: "TEXT", nullable: false),
Scopes = table.Column<string>(type: "TEXT", nullable: false),
IsActive = table.Column<bool>(type: "INTEGER", nullable: false),
CreatedAt = table.Column<DateTime>(type: "TEXT", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP"),
UpdatedAt = table.Column<DateTime>(type: "TEXT", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP")
},
constraints: table =>
{
table.PrimaryKey("PK_OAuthClients", x => x.Id);
});
migrationBuilder.CreateTable(
name: "OAuthAccessTokens",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Token = table.Column<string>(type: "TEXT", nullable: false),
ClientId = table.Column<int>(type: "INTEGER", nullable: false),
UserId = table.Column<int>(type: "INTEGER", nullable: false),
Scopes = table.Column<string>(type: "TEXT", nullable: false),
IsRevoked = table.Column<bool>(type: "INTEGER", nullable: false),
ExpiresAt = table.Column<DateTime>(type: "TEXT", nullable: false),
CreatedAt = table.Column<DateTime>(type: "TEXT", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP")
},
constraints: table =>
{
table.PrimaryKey("PK_OAuthAccessTokens", x => x.Id);
table.ForeignKey(
name: "FK_OAuthAccessTokens_OAuthClients_ClientId",
column: x => x.ClientId,
principalTable: "OAuthClients",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_OAuthAccessTokens_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "OAuthAuthorizationCodes",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Code = table.Column<string>(type: "TEXT", nullable: false),
ClientId = table.Column<int>(type: "INTEGER", nullable: false),
UserId = table.Column<int>(type: "INTEGER", nullable: false),
RedirectUri = table.Column<string>(type: "TEXT", nullable: false),
Scopes = table.Column<string>(type: "TEXT", nullable: false),
IsUsed = table.Column<bool>(type: "INTEGER", nullable: false),
ExpiresAt = table.Column<DateTime>(type: "TEXT", nullable: false),
CreatedAt = table.Column<DateTime>(type: "TEXT", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP")
},
constraints: table =>
{
table.PrimaryKey("PK_OAuthAuthorizationCodes", x => x.Id);
table.ForeignKey(
name: "FK_OAuthAuthorizationCodes_OAuthClients_ClientId",
column: x => x.ClientId,
principalTable: "OAuthClients",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_OAuthAuthorizationCodes_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "OAuthRefreshTokens",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Token = table.Column<string>(type: "TEXT", nullable: false),
ClientId = table.Column<int>(type: "INTEGER", nullable: false),
UserId = table.Column<int>(type: "INTEGER", nullable: false),
IsUsed = table.Column<bool>(type: "INTEGER", nullable: false),
ExpiresAt = table.Column<DateTime>(type: "TEXT", nullable: false),
CreatedAt = table.Column<DateTime>(type: "TEXT", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP")
},
constraints: table =>
{
table.PrimaryKey("PK_OAuthRefreshTokens", x => x.Id);
table.ForeignKey(
name: "FK_OAuthRefreshTokens_OAuthClients_ClientId",
column: x => x.ClientId,
principalTable: "OAuthClients",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_OAuthRefreshTokens_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_OAuthAccessTokens_ClientId",
table: "OAuthAccessTokens",
column: "ClientId");
migrationBuilder.CreateIndex(
name: "IX_OAuthAccessTokens_UserId",
table: "OAuthAccessTokens",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_OAuthAuthorizationCodes_ClientId",
table: "OAuthAuthorizationCodes",
column: "ClientId");
migrationBuilder.CreateIndex(
name: "IX_OAuthAuthorizationCodes_UserId",
table: "OAuthAuthorizationCodes",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_OAuthClients_ClientId",
table: "OAuthClients",
column: "ClientId",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_OAuthRefreshTokens_ClientId",
table: "OAuthRefreshTokens",
column: "ClientId");
migrationBuilder.CreateIndex(
name: "IX_OAuthRefreshTokens_UserId",
table: "OAuthRefreshTokens",
column: "UserId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "OAuthAccessTokens");
migrationBuilder.DropTable(
name: "OAuthAuthorizationCodes");
migrationBuilder.DropTable(
name: "OAuthRefreshTokens");
migrationBuilder.DropTable(
name: "OAuthClients");
}
}
}

View File

@@ -1,564 +0,0 @@
// <auto-generated />
using System;
using FutureMailAPI.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace FutureMailAPI.Migrations
{
[DbContext(typeof(FutureMailDbContext))]
[Migration("20251016012504_AddSaltToUser")]
partial class AddSaltToUser
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "9.0.9");
modelBuilder.Entity("FutureMailAPI.Models.OAuthAccessToken", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("ClientId")
.HasColumnType("INTEGER");
b.Property<DateTime>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValueSql("CURRENT_TIMESTAMP");
b.Property<DateTime>("ExpiresAt")
.HasColumnType("TEXT");
b.Property<bool>("IsRevoked")
.HasColumnType("INTEGER");
b.Property<string>("Scopes")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Token")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("ClientId");
b.HasIndex("UserId");
b.ToTable("OAuthAccessTokens");
});
modelBuilder.Entity("FutureMailAPI.Models.OAuthAuthorizationCode", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("ClientId")
.HasColumnType("INTEGER");
b.Property<string>("Code")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValueSql("CURRENT_TIMESTAMP");
b.Property<DateTime>("ExpiresAt")
.HasColumnType("TEXT");
b.Property<bool>("IsUsed")
.HasColumnType("INTEGER");
b.Property<string>("RedirectUri")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Scopes")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("ClientId");
b.HasIndex("UserId");
b.ToTable("OAuthAuthorizationCodes");
});
modelBuilder.Entity("FutureMailAPI.Models.OAuthClient", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ClientId")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("ClientSecret")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValueSql("CURRENT_TIMESTAMP");
b.Property<bool>("IsActive")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("RedirectUris")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Scopes")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTime>("UpdatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValueSql("CURRENT_TIMESTAMP");
b.HasKey("Id");
b.HasIndex("ClientId")
.IsUnique();
b.ToTable("OAuthClients");
});
modelBuilder.Entity("FutureMailAPI.Models.OAuthRefreshToken", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("ClientId")
.HasColumnType("INTEGER");
b.Property<DateTime>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValueSql("CURRENT_TIMESTAMP");
b.Property<DateTime>("ExpiresAt")
.HasColumnType("TEXT");
b.Property<bool>("IsUsed")
.HasColumnType("INTEGER");
b.Property<string>("Token")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("ClientId");
b.HasIndex("UserId");
b.ToTable("OAuthRefreshTokens");
});
modelBuilder.Entity("FutureMailAPI.Models.ReceivedMail", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<bool>("IsRead")
.HasColumnType("INTEGER");
b.Property<bool>("IsReplied")
.HasColumnType("INTEGER");
b.Property<DateTime?>("ReadAt")
.HasColumnType("TEXT");
b.Property<DateTime>("ReceivedAt")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValueSql("CURRENT_TIMESTAMP");
b.Property<int>("RecipientId")
.HasColumnType("INTEGER");
b.Property<int>("RecipientId1")
.HasColumnType("INTEGER");
b.Property<int?>("ReplyMailId")
.HasColumnType("INTEGER");
b.Property<int>("SentMailId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("RecipientId");
b.HasIndex("RecipientId1");
b.HasIndex("SentMailId");
b.ToTable("ReceivedMails");
});
modelBuilder.Entity("FutureMailAPI.Models.SentMail", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Attachments")
.HasColumnType("TEXT");
b.Property<string>("Content")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTime>("DeliveryTime")
.HasColumnType("TEXT");
b.Property<string>("EncryptionKey")
.HasColumnType("TEXT");
b.Property<bool>("IsEncrypted")
.HasColumnType("INTEGER");
b.Property<int?>("RecipientId")
.HasColumnType("INTEGER");
b.Property<int?>("RecipientId1")
.HasColumnType("INTEGER");
b.Property<int>("RecipientType")
.HasColumnType("INTEGER");
b.Property<int>("SenderId")
.HasColumnType("INTEGER");
b.Property<DateTime>("SentAt")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValueSql("CURRENT_TIMESTAMP");
b.Property<int>("Status")
.HasColumnType("INTEGER");
b.Property<string>("Theme")
.HasMaxLength(50)
.HasColumnType("TEXT");
b.Property<string>("Title")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("TEXT");
b.Property<string>("TriggerDetails")
.HasColumnType("TEXT");
b.Property<int>("TriggerType")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("RecipientId");
b.HasIndex("RecipientId1");
b.HasIndex("SenderId");
b.ToTable("SentMails");
});
modelBuilder.Entity("FutureMailAPI.Models.TimeCapsule", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Color")
.HasMaxLength(20)
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValueSql("CURRENT_TIMESTAMP");
b.Property<double>("Opacity")
.HasColumnType("REAL");
b.Property<double>("PositionX")
.HasColumnType("REAL");
b.Property<double>("PositionY")
.HasColumnType("REAL");
b.Property<double>("PositionZ")
.HasColumnType("REAL");
b.Property<double>("Rotation")
.HasColumnType("REAL");
b.Property<int>("SentMailId")
.HasColumnType("INTEGER");
b.Property<int>("SentMailId1")
.HasColumnType("INTEGER");
b.Property<double>("Size")
.HasColumnType("REAL");
b.Property<int>("Status")
.HasColumnType("INTEGER");
b.Property<int>("Type")
.HasColumnType("INTEGER");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("SentMailId");
b.HasIndex("SentMailId1");
b.HasIndex("UserId");
b.ToTable("TimeCapsules");
});
modelBuilder.Entity("FutureMailAPI.Models.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Avatar")
.HasMaxLength(500)
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValueSql("CURRENT_TIMESTAMP");
b.Property<string>("Email")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("TEXT");
b.Property<bool>("IsActive")
.HasColumnType("INTEGER");
b.Property<DateTime?>("LastLoginAt")
.HasColumnType("TEXT");
b.Property<string>("Nickname")
.HasMaxLength(100)
.HasColumnType("TEXT");
b.Property<string>("PasswordHash")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("TEXT");
b.Property<string>("PreferredBackground")
.HasMaxLength(50)
.HasColumnType("TEXT");
b.Property<string>("PreferredScene")
.HasMaxLength(20)
.HasColumnType("TEXT");
b.Property<string>("Salt")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("TEXT");
b.Property<string>("Username")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("Email")
.IsUnique();
b.HasIndex("Username")
.IsUnique();
b.ToTable("Users");
});
modelBuilder.Entity("FutureMailAPI.Models.OAuthAccessToken", b =>
{
b.HasOne("FutureMailAPI.Models.OAuthClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("FutureMailAPI.Models.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Client");
b.Navigation("User");
});
modelBuilder.Entity("FutureMailAPI.Models.OAuthAuthorizationCode", b =>
{
b.HasOne("FutureMailAPI.Models.OAuthClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("FutureMailAPI.Models.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Client");
b.Navigation("User");
});
modelBuilder.Entity("FutureMailAPI.Models.OAuthRefreshToken", b =>
{
b.HasOne("FutureMailAPI.Models.OAuthClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("FutureMailAPI.Models.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Client");
b.Navigation("User");
});
modelBuilder.Entity("FutureMailAPI.Models.ReceivedMail", b =>
{
b.HasOne("FutureMailAPI.Models.User", null)
.WithMany()
.HasForeignKey("RecipientId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("FutureMailAPI.Models.User", "Recipient")
.WithMany("ReceivedMails")
.HasForeignKey("RecipientId1")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("FutureMailAPI.Models.SentMail", "SentMail")
.WithMany()
.HasForeignKey("SentMailId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Recipient");
b.Navigation("SentMail");
});
modelBuilder.Entity("FutureMailAPI.Models.SentMail", b =>
{
b.HasOne("FutureMailAPI.Models.User", null)
.WithMany()
.HasForeignKey("RecipientId")
.OnDelete(DeleteBehavior.SetNull);
b.HasOne("FutureMailAPI.Models.User", "Recipient")
.WithMany()
.HasForeignKey("RecipientId1");
b.HasOne("FutureMailAPI.Models.User", "Sender")
.WithMany("SentMails")
.HasForeignKey("SenderId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("Recipient");
b.Navigation("Sender");
});
modelBuilder.Entity("FutureMailAPI.Models.TimeCapsule", b =>
{
b.HasOne("FutureMailAPI.Models.SentMail", null)
.WithMany()
.HasForeignKey("SentMailId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("FutureMailAPI.Models.SentMail", "SentMail")
.WithMany()
.HasForeignKey("SentMailId1")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("FutureMailAPI.Models.User", "User")
.WithMany("TimeCapsules")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("SentMail");
b.Navigation("User");
});
modelBuilder.Entity("FutureMailAPI.Models.User", b =>
{
b.Navigation("ReceivedMails");
b.Navigation("SentMails");
b.Navigation("TimeCapsules");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -17,90 +17,6 @@ namespace FutureMailAPI.Migrations
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "9.0.9");
modelBuilder.Entity("FutureMailAPI.Models.OAuthAccessToken", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("ClientId")
.HasColumnType("INTEGER");
b.Property<DateTime>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValueSql("CURRENT_TIMESTAMP");
b.Property<DateTime>("ExpiresAt")
.HasColumnType("TEXT");
b.Property<bool>("IsRevoked")
.HasColumnType("INTEGER");
b.Property<string>("Scopes")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Token")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("ClientId");
b.HasIndex("UserId");
b.ToTable("OAuthAccessTokens");
});
modelBuilder.Entity("FutureMailAPI.Models.OAuthAuthorizationCode", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("ClientId")
.HasColumnType("INTEGER");
b.Property<string>("Code")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValueSql("CURRENT_TIMESTAMP");
b.Property<DateTime>("ExpiresAt")
.HasColumnType("TEXT");
b.Property<bool>("IsUsed")
.HasColumnType("INTEGER");
b.Property<string>("RedirectUri")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Scopes")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("ClientId");
b.HasIndex("UserId");
b.ToTable("OAuthAuthorizationCodes");
});
modelBuilder.Entity("FutureMailAPI.Models.OAuthClient", b =>
{
b.Property<int>("Id")
@@ -109,10 +25,12 @@ namespace FutureMailAPI.Migrations
b.Property<string>("ClientId")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("TEXT");
b.Property<string>("ClientSecret")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedAt")
@@ -125,6 +43,7 @@ namespace FutureMailAPI.Migrations
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("TEXT");
b.Property<string>("RedirectUris")
@@ -148,12 +67,17 @@ namespace FutureMailAPI.Migrations
b.ToTable("OAuthClients");
});
modelBuilder.Entity("FutureMailAPI.Models.OAuthRefreshToken", b =>
modelBuilder.Entity("FutureMailAPI.Models.OAuthToken", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("AccessToken")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("TEXT");
b.Property<int>("ClientId")
.HasColumnType("INTEGER");
@@ -165,11 +89,22 @@ namespace FutureMailAPI.Migrations
b.Property<DateTime>("ExpiresAt")
.HasColumnType("TEXT");
b.Property<bool>("IsUsed")
.HasColumnType("INTEGER");
b.Property<string>("Token")
b.Property<string>("RefreshToken")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("TEXT");
b.Property<DateTime?>("RevokedAt")
.HasColumnType("TEXT");
b.Property<string>("Scope")
.HasColumnType("TEXT");
b.Property<string>("TokenType")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("TEXT");
b.Property<int>("UserId")
@@ -177,11 +112,17 @@ namespace FutureMailAPI.Migrations
b.HasKey("Id");
b.HasIndex("AccessToken")
.IsUnique();
b.HasIndex("ClientId");
b.HasIndex("RefreshToken")
.IsUnique();
b.HasIndex("UserId");
b.ToTable("OAuthRefreshTokens");
b.ToTable("OAuthTokens");
});
modelBuilder.Entity("FutureMailAPI.Models.ReceivedMail", b =>
@@ -397,6 +338,13 @@ namespace FutureMailAPI.Migrations
.HasMaxLength(20)
.HasColumnType("TEXT");
b.Property<string>("RefreshToken")
.HasMaxLength(500)
.HasColumnType("TEXT");
b.Property<DateTime?>("RefreshTokenExpiryTime")
.HasColumnType("TEXT");
b.Property<string>("Salt")
.IsRequired()
.HasMaxLength(255)
@@ -418,48 +366,10 @@ namespace FutureMailAPI.Migrations
b.ToTable("Users");
});
modelBuilder.Entity("FutureMailAPI.Models.OAuthAccessToken", b =>
modelBuilder.Entity("FutureMailAPI.Models.OAuthToken", b =>
{
b.HasOne("FutureMailAPI.Models.OAuthClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("FutureMailAPI.Models.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Client");
b.Navigation("User");
});
modelBuilder.Entity("FutureMailAPI.Models.OAuthAuthorizationCode", b =>
{
b.HasOne("FutureMailAPI.Models.OAuthClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("FutureMailAPI.Models.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Client");
b.Navigation("User");
});
modelBuilder.Entity("FutureMailAPI.Models.OAuthRefreshToken", b =>
{
b.HasOne("FutureMailAPI.Models.OAuthClient", "Client")
.WithMany()
.WithMany("Tokens")
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
@@ -547,6 +457,11 @@ namespace FutureMailAPI.Migrations
b.Navigation("User");
});
modelBuilder.Entity("FutureMailAPI.Models.OAuthClient", b =>
{
b.Navigation("Tokens");
});
modelBuilder.Entity("FutureMailAPI.Models.User", b =>
{
b.Navigation("ReceivedMails");

View File

@@ -0,0 +1,38 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace FutureMailAPI.Models
{
public class OAuthClient
{
[Key]
public int Id { get; set; }
[Required]
[MaxLength(100)]
public string ClientId { get; set; } = string.Empty;
[Required]
[MaxLength(255)]
public string ClientSecret { get; set; } = string.Empty;
[Required]
[MaxLength(100)]
public string Name { get; set; } = string.Empty;
[Required]
public string RedirectUris { get; set; } = string.Empty;
[Required]
public string Scopes { get; set; } = string.Empty;
public bool IsActive { get; set; } = true;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
// 导航属性
public virtual ICollection<OAuthToken> Tokens { get; set; } = new List<OAuthToken>();
}
}

View File

@@ -1,63 +0,0 @@
namespace FutureMailAPI.Models
{
public class OAuthClient
{
public int Id { get; set; }
public string ClientId { get; set; } = string.Empty;
public string ClientSecret { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public string RedirectUris { get; set; } = string.Empty; // JSON array
public string Scopes { get; set; } = string.Empty; // JSON array
public bool IsActive { get; set; } = true;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
}
public class OAuthAuthorizationCode
{
public int Id { get; set; }
public string Code { get; set; } = string.Empty;
public int ClientId { get; set; }
public int UserId { get; set; }
public string RedirectUri { get; set; } = string.Empty;
public string Scopes { get; set; } = string.Empty;
public bool IsUsed { get; set; } = false;
public DateTime ExpiresAt { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
// Navigation properties
public virtual OAuthClient Client { get; set; } = null!;
public virtual User User { get; set; } = null!;
}
public class OAuthRefreshToken
{
public int Id { get; set; }
public string Token { get; set; } = string.Empty;
public int ClientId { get; set; }
public int UserId { get; set; }
public bool IsUsed { get; set; } = false;
public DateTime ExpiresAt { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
// Navigation properties
public virtual OAuthClient Client { get; set; } = null!;
public virtual User User { get; set; } = null!;
}
public class OAuthAccessToken
{
public int Id { get; set; }
public string Token { get; set; } = string.Empty;
public int ClientId { get; set; }
public int UserId { get; set; }
public string Scopes { get; set; } = string.Empty;
public bool IsRevoked { get; set; } = false;
public DateTime ExpiresAt { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
// Navigation properties
public virtual OAuthClient Client { get; set; } = null!;
public virtual User User { get; set; } = null!;
}
}

View File

@@ -0,0 +1,44 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace FutureMailAPI.Models
{
public class OAuthToken
{
[Key]
public int Id { get; set; }
[Required]
[MaxLength(255)]
public string AccessToken { get; set; } = string.Empty;
[Required]
[MaxLength(255)]
public string RefreshToken { get; set; } = string.Empty;
[Required]
public string TokenType { get; set; } = "Bearer";
public DateTime ExpiresAt { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime? UpdatedAt { get; set; }
public DateTime? RevokedAt { get; set; }
public string? Scope { get; set; }
// 外键
public int UserId { get; set; }
public int ClientId { get; set; }
// 导航属性
[ForeignKey("UserId")]
public virtual User User { get; set; } = null!;
[ForeignKey("ClientId")]
public virtual OAuthClient Client { get; set; } = null!;
}
}

View File

@@ -42,6 +42,12 @@ namespace FutureMailAPI.Models
[MaxLength(50)]
public string? PreferredBackground { get; set; } = "default";
[MaxLength(500)]
public string? RefreshToken { get; set; }
public DateTime? RefreshTokenExpiryTime { get; set; }
// 导航属性
public virtual ICollection<SentMail> SentMails { get; set; } = new List<SentMail>();
public virtual ICollection<ReceivedMail> ReceivedMails { get; set; } = new List<ReceivedMail>();

View File

@@ -1,12 +0,0 @@
// 测试OAuth 2.0密码授权流程
// 1. 使用密码授权获取访问令牌
POST http://localhost:5001/api/v1/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=password&username=testuser3&password=password123&client_id=futuremail-client&client_secret=futuremail-secret
###
// 2. 使用访问令牌访问受保护的API
GET http://localhost:5001/api/v1/mails
Authorization: Bearer YOUR_ACCESS_TOKEN

View File

@@ -1,37 +0,0 @@
// 测试OAuth 2.0认证流程
// 1. 创建OAuth客户端
POST http://localhost:5001/api/v1/oauth/clients
Content-Type: application/json
{
"clientName": "TestClient",
"redirectUris": ["http://localhost:3000/callback"],
"scopes": ["read", "write"]
}
###
// 2. 获取授权码在浏览器中访问以下URL
// http://localhost:5001/api/v1/oauth/authorize?response_type=code&client_id=test_client&redirect_uri=http://localhost:3000/callback&scope=read&state=xyz
###
// 3. 使用授权码获取访问令牌
POST http://localhost:5001/api/v1/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=YOUR_AUTHORIZATION_CODE&redirect_uri=http://localhost:3000/callback&client_id=test_client&client_secret=YOUR_CLIENT_SECRET
###
// 4. 使用访问令牌访问受保护的API
GET http://localhost:5001/api/v1/mails
Authorization: Bearer YOUR_ACCESS_TOKEN
###
// 5. 刷新访问令牌
POST http://localhost:5001/api/v1/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token&refresh_token=YOUR_REFRESH_TOKEN&client_id=test_client&client_secret=YOUR_CLIENT_SECRET

View File

@@ -1,18 +1,20 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using Quartz;
using FutureMailAPI.Data;
using FutureMailAPI.Helpers;
using FutureMailAPI.Services;
using FutureMailAPI.Middleware;
using FutureMailAPI.Extensions;
using FutureMailAPI.Filters;
using Microsoft.Extensions.FileProviders;
using System.Text;
var builder = WebApplication.CreateBuilder(args);
// 配置服务器监听所有网络接口的5001端口
// 配置服务器监听所有网络接口的5003端口
builder.WebHost.ConfigureKestrel(options =>
{
options.ListenAnyIP(5001);
options.ListenAnyIP(5003);
});
// 配置数据库连接
@@ -20,9 +22,6 @@ var connectionString = builder.Configuration.GetConnectionString("DefaultConnect
builder.Services.AddDbContext<FutureMailDbContext>(options =>
options.UseSqlite(connectionString));
// 配置OAuth 2.0认证
// 注意我们使用自定义中间件实现OAuth 2.0认证而不是使用内置的JWT认证
// 配置Swagger/OpenAPI
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
@@ -40,13 +39,42 @@ builder.Services.AddScoped<IUserService, UserService>();
builder.Services.AddScoped<IMailService, MailService>();
builder.Services.AddScoped<ITimeCapsuleService, TimeCapsuleService>();
builder.Services.AddScoped<IAuthService, AuthService>();
builder.Services.AddScoped<IOAuthService, OAuthService>();
builder.Services.AddScoped<IAIAssistantService, AIAssistantService>();
builder.Services.AddScoped<IPersonalSpaceService, PersonalSpaceService>();
builder.Services.AddScoped<IFileUploadService, FileUploadService>();
builder.Services.AddScoped<INotificationService, NotificationService>();
builder.Services.AddScoped<IOAuthService, OAuthService>();
builder.Services.AddScoped<IInitializationService, InitializationService>();
// 添加JWT认证
var jwtSettings = builder.Configuration.GetSection("Jwt");
var key = Encoding.ASCII.GetBytes(jwtSettings["Key"] ?? throw new InvalidOperationException("JWT密钥未配置"));
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = true,
ValidIssuer = jwtSettings["Issuer"],
ValidateAudience = true,
ValidAudience = jwtSettings["Audience"],
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
});
builder.Services.AddAuthorization();
// 配置Quartz任务调度
builder.Services.AddQuartz(q =>
{
@@ -64,8 +92,11 @@ builder.Services.AddQuartz(q =>
// 添加Quartz主机服务
builder.Services.AddQuartzHostedService(q => q.WaitForJobsToComplete = true);
// 添加控制器
builder.Services.AddControllers();
// 添加控制器并注册OAuth认证过滤器为全局过滤器
builder.Services.AddControllers(options =>
{
options.Filters.Add<OAuthAuthenticationFilter>();
});
// 添加CORS
builder.Services.AddCors(options =>
@@ -106,10 +137,7 @@ app.UseStaticFiles(new StaticFileOptions
app.UseCors("AllowAll");
// 添加OAuth 2.0认证中间件
app.UseMiddleware<OAuthAuthenticationMiddleware>();
app.UseAuthorization();
app.MapControllers();

View File

@@ -5,7 +5,7 @@
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "http://0.0.0.0:5001",
"applicationUrl": "http://0.0.0.0:5003",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
@@ -14,7 +14,7 @@
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "https://localhost:7236;http://0.0.0.0:5001",
"applicationUrl": "https://localhost:7236;http://0.0.0.0:5003",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}

View File

@@ -1,6 +1,13 @@
using FutureMailAPI.Helpers;
using FutureMailAPI.DTOs;
using FutureMailAPI.Models;
using FutureMailAPI.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
namespace FutureMailAPI.Services
{
@@ -8,86 +15,73 @@ namespace FutureMailAPI.Services
{
Task<ApiResponse<AuthResponseDto>> LoginAsync(UserLoginDto loginDto);
Task<ApiResponse<AuthResponseDto>> RegisterAsync(UserRegisterDto registerDto);
Task<ApiResponse<bool>> ValidateTokenAsync(string token);
Task<ApiResponse<string>> RefreshTokenAsync(string token);
Task<ApiResponse<AuthResponseDto>> LoginWithOAuthAsync(UserLoginDto loginDto, string clientId, string clientSecret);
}
public class AuthService : IAuthService
{
private readonly IUserService _userService;
private readonly IPasswordHelper _passwordHelper;
private readonly IOAuthService _oauthService;
private readonly FutureMailDbContext _context;
private readonly IConfiguration _configuration;
public AuthService(
IUserService userService,
IPasswordHelper passwordHelper,
IOAuthService oauthService)
FutureMailDbContext context,
IConfiguration configuration)
{
_userService = userService;
_passwordHelper = passwordHelper;
_oauthService = oauthService;
_context = context;
_configuration = configuration;
}
public async Task<ApiResponse<AuthResponseDto>> LoginAsync(UserLoginDto loginDto)
{
// 使用默认客户端ID和密钥进行OAuth登录
// 在实际应用中,这些应该从配置中获取
var defaultClientId = "futuremail_default_client";
var defaultClientSecret = "futuremail_default_secret";
return await LoginWithOAuthAsync(loginDto, defaultClientId, defaultClientSecret);
}
public async Task<ApiResponse<AuthResponseDto>> LoginWithOAuthAsync(UserLoginDto loginDto, string clientId, string clientSecret)
{
// 创建OAuth登录请求
var oauthLoginDto = new OAuthLoginDto
{
UsernameOrEmail = loginDto.UsernameOrEmail,
Password = loginDto.Password,
ClientId = clientId,
ClientSecret = clientSecret,
Scope = "read write" // 默认权限范围
};
// 使用OAuth服务进行登录
var oauthResult = await _oauthService.LoginAsync(oauthLoginDto);
if (!oauthResult.Success)
{
return ApiResponse<AuthResponseDto>.ErrorResult(oauthResult.Message ?? "登录失败");
}
// 获取用户信息
var userResult = await _userService.GetUserByUsernameOrEmailAsync(loginDto.UsernameOrEmail);
if (!userResult.Success || userResult.Data == null)
{
return ApiResponse<AuthResponseDto>.ErrorResult("获取用户信息失败");
return ApiResponse<AuthResponseDto>.ErrorResult("用户名或密码错误");
}
var user = userResult.Data;
var userDto = userResult.Data;
// 创建用户响应DTO
var userResponse = new UserResponseDto
// 获取原始用户信息用于密码验证
var user = await _context.Users
.FirstOrDefaultAsync(u => u.Id == userDto.Id);
if (user == null)
{
Id = user.Id,
Username = user.Username,
Email = user.Email,
Nickname = user.Nickname,
Avatar = user.Avatar,
CreatedAt = user.CreatedAt,
LastLoginAt = DateTime.UtcNow
};
return ApiResponse<AuthResponseDto>.ErrorResult("用户不存在");
}
// 创建认证响应DTO使用OAuth令牌
// 验证密码
if (!_passwordHelper.VerifyPassword(loginDto.Password, user.PasswordHash, user.Salt))
{
return ApiResponse<AuthResponseDto>.ErrorResult("用户名或密码错误");
}
// 更新用户响应DTO
userDto.LastLoginAt = DateTime.UtcNow;
// 生成JWT令牌
var token = GenerateJwtToken(user);
var refreshToken = GenerateRefreshToken();
// 保存刷新令牌到用户表
user.RefreshToken = refreshToken;
user.RefreshTokenExpiryTime = DateTime.UtcNow.AddDays(7); // 刷新令牌7天过期
await _context.SaveChangesAsync();
// 创建认证响应DTO
var authResponse = new AuthResponseDto
{
Token = oauthResult.Data.AccessToken,
RefreshToken = oauthResult.Data.RefreshToken,
Expires = DateTime.UtcNow.AddSeconds(oauthResult.Data.ExpiresIn),
User = userResponse
User = userDto,
Token = token,
RefreshToken = refreshToken,
ExpiresIn = 3600 // 1小时单位秒
};
return ApiResponse<AuthResponseDto>.SuccessResult(authResponse, "登录成功");
@@ -119,7 +113,7 @@ namespace FutureMailAPI.Services
return ApiResponse<AuthResponseDto>.ErrorResult(createUserResult.Message ?? "注册失败");
}
// 注册成功后,自动使用OAuth登录
// 注册成功后,自动登录
var loginDto = new UserLoginDto
{
UsernameOrEmail = registerDto.Username,
@@ -129,40 +123,35 @@ namespace FutureMailAPI.Services
return await LoginAsync(loginDto);
}
public async Task<ApiResponse<bool>> ValidateTokenAsync(string token)
private string GenerateJwtToken(User user)
{
// 注意在OAuth 2.0中令牌验证应该由OAuth中间件处理
// 这里我们暂时返回成功实际使用时应该通过OAuth 2.0的令牌验证流程
return ApiResponse<bool>.SuccessResult(true);
}
public async Task<ApiResponse<string>> RefreshTokenAsync(string token)
{
// 在OAuth 2.0中刷新令牌需要客户端ID和密钥
// 这里我们使用默认客户端凭据
var defaultClientId = "futuremail_default_client";
var defaultClientSecret = "futuremail_default_secret";
// 创建OAuth刷新令牌请求
var oauthTokenRequest = new OAuthTokenRequestDto
var jwtSettings = _configuration.GetSection("Jwt");
var key = Encoding.ASCII.GetBytes(jwtSettings["Key"] ?? throw new InvalidOperationException("JWT密钥未配置"));
var tokenDescriptor = new SecurityTokenDescriptor
{
ClientId = defaultClientId,
ClientSecret = defaultClientSecret,
RefreshToken = token,
GrantType = "refresh_token",
Scope = "read write"
Subject = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.Username),
new Claim(ClaimTypes.Email, user.Email)
}),
Expires = DateTime.UtcNow.AddHours(1),
Issuer = jwtSettings["Issuer"],
Audience = jwtSettings["Audience"],
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
// 使用OAuth服务刷新令牌
var oauthResult = await _oauthService.RefreshTokenAsync(oauthTokenRequest);
if (!oauthResult.Success)
{
return ApiResponse<string>.ErrorResult(oauthResult.Message ?? "刷新令牌失败");
}
// 返回新的访问令牌
return ApiResponse<string>.SuccessResult(oauthResult.Data.AccessToken, "令牌刷新成功");
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
private string GenerateRefreshToken()
{
var randomNumber = new byte[32];
using var rng = RandomNumberGenerator.Create();
rng.GetBytes(randomNumber);
return Convert.ToBase64String(randomNumber);
}
}
}

View File

@@ -5,15 +5,9 @@ namespace FutureMailAPI.Services
{
public interface IOAuthService
{
Task<ApiResponse<OAuthClientSecretDto>> CreateClientAsync(int userId, OAuthClientCreateDto createDto);
Task<ApiResponse<OAuthClientDto>> GetClientAsync(string clientId);
Task<ApiResponse<OAuthAuthorizationResponseDto>> AuthorizeAsync(int userId, OAuthAuthorizationRequestDto request);
Task<ApiResponse<OAuthTokenResponseDto>> ExchangeCodeForTokenAsync(OAuthTokenRequestDto request);
Task<ApiResponse<OAuthTokenResponseDto>> RefreshTokenAsync(OAuthTokenRequestDto request);
Task<ApiResponse<bool>> RevokeTokenAsync(string token);
Task<ApiResponse<bool>> ValidateTokenAsync(string token);
Task<OAuthAccessToken?> GetAccessTokenAsync(string token);
Task<OAuthClient?> GetClientByCredentialsAsync(string clientId, string clientSecret);
Task<ApiResponse<OAuthTokenResponseDto>> LoginAsync(OAuthLoginDto loginDto);
Task<ApiResponse<OAuthTokenResponseDto>> LoginAsync(OAuthLoginRequestDto request);
Task<ApiResponse<OAuthTokenResponseDto>> RefreshTokenAsync(OAuthRefreshTokenRequestDto request);
Task<bool> RevokeTokenAsync(string accessToken);
Task<OAuthToken?> GetTokenAsync(string accessToken);
}
}

View File

@@ -48,7 +48,7 @@ namespace FutureMailAPI.Services
}
// 创建默认OAuth客户端
var defaultClient = new OAuthClient
var defaultClient = new Models.OAuthClient
{
ClientId = defaultClientId,
ClientSecret = "futuremail_default_secret",

View File

@@ -1,416 +1,204 @@
using Microsoft.EntityFrameworkCore;
using FutureMailAPI.Data;
using FutureMailAPI.Models;
using FutureMailAPI.DTOs;
using FutureMailAPI.Helpers;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text.Json;
using System.Text;
using FutureMailAPI.Data;
using FutureMailAPI.DTOs;
using FutureMailAPI.Models;
namespace FutureMailAPI.Services
{
public class OAuthService : IOAuthService
{
private readonly FutureMailDbContext _context;
private readonly IConfiguration _configuration;
private readonly ILogger<OAuthService> _logger;
private readonly IPasswordHelper _passwordHelper;
public OAuthService(FutureMailDbContext context, ILogger<OAuthService> logger, IPasswordHelper passwordHelper)
public OAuthService(FutureMailDbContext context, IConfiguration configuration, ILogger<OAuthService> logger)
{
_context = context;
_configuration = configuration;
_logger = logger;
_passwordHelper = passwordHelper;
}
public async Task<ApiResponse<OAuthClientSecretDto>> CreateClientAsync(int userId, OAuthClientCreateDto createDto)
public async Task<ApiResponse<OAuthTokenResponseDto>> LoginAsync(OAuthLoginRequestDto request)
{
var clientId = GenerateRandomString(32);
var clientSecret = GenerateRandomString(64);
var client = new OAuthClient
try
{
ClientId = clientId,
ClientSecret = clientSecret,
Name = createDto.Name,
RedirectUris = JsonSerializer.Serialize(createDto.RedirectUris),
Scopes = JsonSerializer.Serialize(createDto.Scopes),
IsActive = true,
CreatedAt = DateTime.UtcNow,
UpdatedAt = DateTime.UtcNow
};
_context.OAuthClients.Add(client);
await _context.SaveChangesAsync();
var result = new OAuthClientSecretDto
{
ClientId = clientId,
ClientSecret = clientSecret
};
return ApiResponse<OAuthClientSecretDto>.SuccessResult(result, "OAuth客户端创建成功");
}
public async Task<ApiResponse<OAuthClientDto>> GetClientAsync(string clientId)
{
var client = await _context.OAuthClients
.FirstOrDefaultAsync(c => c.ClientId == clientId && c.IsActive);
if (client == null)
{
return ApiResponse<OAuthClientDto>.ErrorResult("客户端不存在");
}
var redirectUris = JsonSerializer.Deserialize<string[]>(client.RedirectUris) ?? Array.Empty<string>();
var scopes = JsonSerializer.Deserialize<string[]>(client.Scopes) ?? Array.Empty<string>();
var result = new OAuthClientDto
{
Id = client.Id,
ClientId = client.ClientId,
Name = client.Name,
RedirectUris = redirectUris,
Scopes = scopes,
IsActive = client.IsActive,
CreatedAt = client.CreatedAt,
UpdatedAt = client.UpdatedAt
};
return ApiResponse<OAuthClientDto>.SuccessResult(result);
}
public async Task<ApiResponse<OAuthAuthorizationResponseDto>> AuthorizeAsync(int userId, OAuthAuthorizationRequestDto request)
{
// 验证客户端
var client = await _context.OAuthClients
.FirstOrDefaultAsync(c => c.ClientId == request.ClientId && c.IsActive);
if (client == null)
{
return ApiResponse<OAuthAuthorizationResponseDto>.ErrorResult("无效的客户端ID");
}
// 验证重定向URI
var redirectUris = JsonSerializer.Deserialize<string[]>(client.RedirectUris) ?? Array.Empty<string>();
if (!redirectUris.Contains(request.RedirectUri))
{
return ApiResponse<OAuthAuthorizationResponseDto>.ErrorResult("无效的重定向URI");
}
// 验证范围
var clientScopes = JsonSerializer.Deserialize<string[]>(client.Scopes) ?? Array.Empty<string>();
var requestedScopes = request.Scope.Split(' ', StringSplitOptions.RemoveEmptyEntries);
foreach (var scope in requestedScopes)
{
if (!clientScopes.Contains(scope))
// 验证OAuth客户端
var client = await _context.OAuthClients
.FirstOrDefaultAsync(c => c.ClientId == request.ClientId && c.ClientSecret == request.ClientSecret);
if (client == null)
{
return ApiResponse<OAuthAuthorizationResponseDto>.ErrorResult($"无效的范围: {scope}");
return ApiResponse<OAuthTokenResponseDto>.ErrorResult("无效的客户端凭据");
}
// 验证用户凭据
var user = await _context.Users
.FirstOrDefaultAsync(u => u.Email == request.Username);
if (user == null)
{
return ApiResponse<OAuthTokenResponseDto>.ErrorResult("用户不存在");
}
// 验证密码
if (!BCrypt.Net.BCrypt.Verify(request.Password, user.PasswordHash))
{
return ApiResponse<OAuthTokenResponseDto>.ErrorResult("密码错误");
}
// 生成访问令牌
var accessToken = GenerateJwtToken(user, client);
var refreshToken = GenerateRefreshToken();
// 保存令牌到数据库
var oauthToken = new OAuthToken
{
AccessToken = accessToken,
RefreshToken = refreshToken,
UserId = user.Id,
ClientId = client.Id,
ExpiresAt = DateTime.UtcNow.AddHours(1), // 访问令牌1小时过期
CreatedAt = DateTime.UtcNow
};
_context.OAuthTokens.Add(oauthToken);
await _context.SaveChangesAsync();
var response = new OAuthTokenResponseDto
{
AccessToken = accessToken,
RefreshToken = refreshToken,
TokenType = "Bearer",
ExpiresIn = 3600 // 1小时单位秒
};
return ApiResponse<OAuthTokenResponseDto>.SuccessResult(response);
}
// 生成授权码
var code = GenerateRandomString(64);
var authorizationCode = new OAuthAuthorizationCode
catch (Exception ex)
{
Code = code,
ClientId = client.Id,
UserId = userId,
RedirectUri = request.RedirectUri,
Scopes = request.Scope,
IsUsed = false,
ExpiresAt = DateTime.UtcNow.AddMinutes(10), // 授权码10分钟后过期
CreatedAt = DateTime.UtcNow
};
_context.OAuthAuthorizationCodes.Add(authorizationCode);
await _context.SaveChangesAsync();
var result = new OAuthAuthorizationResponseDto
{
Code = code,
State = request.State
};
return ApiResponse<OAuthAuthorizationResponseDto>.SuccessResult(result);
_logger.LogError(ex, "OAuth登录时发生错误");
return ApiResponse<OAuthTokenResponseDto>.ErrorResult("服务器内部错误");
}
}
public async Task<ApiResponse<OAuthTokenResponseDto>> ExchangeCodeForTokenAsync(OAuthTokenRequestDto request)
public async Task<ApiResponse<OAuthTokenResponseDto>> RefreshTokenAsync(OAuthRefreshTokenRequestDto request)
{
// 验证客户端
var client = await GetClientByCredentialsAsync(request.ClientId, request.ClientSecret);
if (client == null)
try
{
return ApiResponse<OAuthTokenResponseDto>.ErrorResult("无效的客户端凭据");
// 查找刷新令牌
var token = await _context.OAuthTokens
.Include(t => t.User)
.Include(t => t.Client)
.FirstOrDefaultAsync(t => t.RefreshToken == request.RefreshToken);
if (token == null)
{
return ApiResponse<OAuthTokenResponseDto>.ErrorResult("无效的刷新令牌");
}
// 生成新的访问令牌
var accessToken = GenerateJwtToken(token.User, token.Client);
var refreshToken = GenerateRefreshToken();
// 更新令牌
token.AccessToken = accessToken;
token.RefreshToken = refreshToken;
token.ExpiresAt = DateTime.UtcNow.AddHours(1);
token.UpdatedAt = DateTime.UtcNow;
await _context.SaveChangesAsync();
var response = new OAuthTokenResponseDto
{
AccessToken = accessToken,
RefreshToken = refreshToken,
TokenType = "Bearer",
ExpiresIn = 3600 // 1小时单位秒
};
return ApiResponse<OAuthTokenResponseDto>.SuccessResult(response);
}
// 验证授权码
var authCode = await _context.OAuthAuthorizationCodes
.Include(c => c.Client)
.Include(c => c.User)
.FirstOrDefaultAsync(c => c.Code == request.Code && c.ClientId == client.Id);
if (authCode == null || authCode.IsUsed || authCode.ExpiresAt < DateTime.UtcNow)
catch (Exception ex)
{
return ApiResponse<OAuthTokenResponseDto>.ErrorResult("无效的授权码");
_logger.LogError(ex, "OAuth刷新令牌时发生错误");
return ApiResponse<OAuthTokenResponseDto>.ErrorResult("服务器内部错误");
}
// 验证重定向URI
if (authCode.RedirectUri != request.RedirectUri)
{
return ApiResponse<OAuthTokenResponseDto>.ErrorResult("重定向URI不匹配");
}
// 标记授权码为已使用
authCode.IsUsed = true;
await _context.SaveChangesAsync();
// 生成访问令牌和刷新令牌
var accessToken = GenerateRandomString(64);
var refreshToken = GenerateRandomString(64);
var scopes = !string.IsNullOrEmpty(request.Scope) ? request.Scope : authCode.Scopes;
var oauthAccessToken = new OAuthAccessToken
{
Token = accessToken,
ClientId = client.Id,
UserId = authCode.UserId,
Scopes = scopes,
IsRevoked = false,
ExpiresAt = DateTime.UtcNow.AddHours(1), // 访问令牌1小时后过期
CreatedAt = DateTime.UtcNow
};
var oauthRefreshToken = new OAuthRefreshToken
{
Token = refreshToken,
ClientId = client.Id,
UserId = authCode.UserId,
IsUsed = false,
ExpiresAt = DateTime.UtcNow.AddDays(30), // 刷新令牌30天后过期
CreatedAt = DateTime.UtcNow
};
_context.OAuthAccessTokens.Add(oauthAccessToken);
_context.OAuthRefreshTokens.Add(oauthRefreshToken);
await _context.SaveChangesAsync();
var result = new OAuthTokenResponseDto
{
AccessToken = accessToken,
TokenType = "Bearer",
ExpiresIn = 3600, // 1小时
RefreshToken = refreshToken,
Scope = scopes
};
return ApiResponse<OAuthTokenResponseDto>.SuccessResult(result);
}
public async Task<ApiResponse<OAuthTokenResponseDto>> RefreshTokenAsync(OAuthTokenRequestDto request)
public async Task<bool> RevokeTokenAsync(string accessToken)
{
// 验证客户端
var client = await GetClientByCredentialsAsync(request.ClientId, request.ClientSecret);
if (client == null)
try
{
return ApiResponse<OAuthTokenResponseDto>.ErrorResult("无效的客户端凭据");
var token = await _context.OAuthTokens
.FirstOrDefaultAsync(t => t.AccessToken == accessToken);
if (token != null)
{
_context.OAuthTokens.Remove(token);
await _context.SaveChangesAsync();
return true;
}
return false;
}
// 验证刷新令牌
var refreshToken = await _context.OAuthRefreshTokens
.Include(t => t.Client)
.Include(t => t.User)
.FirstOrDefaultAsync(t => t.Token == request.RefreshToken && t.ClientId == client.Id);
if (refreshToken == null || refreshToken.IsUsed || refreshToken.ExpiresAt < DateTime.UtcNow)
catch (Exception ex)
{
return ApiResponse<OAuthTokenResponseDto>.ErrorResult("无效的刷新令牌");
_logger.LogError(ex, "OAuth撤销令牌时发生错误");
return false;
}
// 标记旧刷新令牌为已使用
refreshToken.IsUsed = true;
// 撤销旧访问令牌
var oldAccessTokens = await _context.OAuthAccessTokens
.Where(t => t.UserId == refreshToken.UserId && t.ClientId == client.Id && !t.IsRevoked)
.ToListAsync();
foreach (var token in oldAccessTokens)
{
token.IsRevoked = true;
}
// 生成新的访问令牌和刷新令牌
var newAccessToken = GenerateRandomString(64);
var newRefreshToken = GenerateRandomString(64);
var scopes = !string.IsNullOrEmpty(request.Scope) ? request.Scope : "";
var newOAuthAccessToken = new OAuthAccessToken
{
Token = newAccessToken,
ClientId = client.Id,
UserId = refreshToken.UserId,
Scopes = scopes,
IsRevoked = false,
ExpiresAt = DateTime.UtcNow.AddHours(1), // 访问令牌1小时后过期
CreatedAt = DateTime.UtcNow
};
var newOAuthRefreshToken = new OAuthRefreshToken
{
Token = newRefreshToken,
ClientId = client.Id,
UserId = refreshToken.UserId,
IsUsed = false,
ExpiresAt = DateTime.UtcNow.AddDays(30), // 刷新令牌30天后过期
CreatedAt = DateTime.UtcNow
};
_context.OAuthAccessTokens.Add(newOAuthAccessToken);
_context.OAuthRefreshTokens.Add(newOAuthRefreshToken);
await _context.SaveChangesAsync();
var result = new OAuthTokenResponseDto
{
AccessToken = newAccessToken,
TokenType = "Bearer",
ExpiresIn = 3600, // 1小时
RefreshToken = newRefreshToken,
Scope = scopes
};
return ApiResponse<OAuthTokenResponseDto>.SuccessResult(result);
}
public async Task<ApiResponse<bool>> RevokeTokenAsync(string token)
public async Task<OAuthToken?> GetTokenAsync(string accessToken)
{
var accessToken = await _context.OAuthAccessTokens
.FirstOrDefaultAsync(t => t.Token == token);
if (accessToken == null)
try
{
return ApiResponse<bool>.ErrorResult("令牌不存在");
return await _context.OAuthTokens
.Include(t => t.User)
.Include(t => t.Client)
.FirstOrDefaultAsync(t => t.AccessToken == accessToken && t.ExpiresAt > DateTime.UtcNow);
}
catch (Exception ex)
{
_logger.LogError(ex, "获取OAuth令牌时发生错误");
return null;
}
accessToken.IsRevoked = true;
await _context.SaveChangesAsync();
return ApiResponse<bool>.SuccessResult(true, "令牌已撤销");
}
public async Task<ApiResponse<bool>> ValidateTokenAsync(string token)
private string GenerateJwtToken(User user, OAuthClient client)
{
var accessToken = await _context.OAuthAccessTokens
.FirstOrDefaultAsync(t => t.Token == token && !t.IsRevoked && t.ExpiresAt > DateTime.UtcNow);
if (accessToken == null)
var jwtSettings = _configuration.GetSection("Jwt");
var key = Encoding.ASCII.GetBytes(jwtSettings["Key"] ?? throw new InvalidOperationException("JWT密钥未配置"));
var tokenDescriptor = new SecurityTokenDescriptor
{
return ApiResponse<bool>.ErrorResult("无效的令牌");
}
return ApiResponse<bool>.SuccessResult(true);
}
public async Task<OAuthAccessToken?> GetAccessTokenAsync(string token)
{
return await _context.OAuthAccessTokens
.Include(t => t.Client)
.Include(t => t.User)
.FirstOrDefaultAsync(t => t.Token == token && !t.IsRevoked && t.ExpiresAt > DateTime.UtcNow);
}
public async Task<OAuthClient?> GetClientByCredentialsAsync(string clientId, string clientSecret)
{
if (string.IsNullOrEmpty(clientSecret))
{
// 公开客户端只验证客户端ID
return await _context.OAuthClients
.FirstOrDefaultAsync(c => c.ClientId == clientId && c.IsActive);
}
// 机密客户端验证客户端ID和密钥
return await _context.OAuthClients
.FirstOrDefaultAsync(c => c.ClientId == clientId && c.ClientSecret == clientSecret && c.IsActive);
}
public async Task<ApiResponse<OAuthTokenResponseDto>> LoginAsync(OAuthLoginDto loginDto)
{
// 验证客户端
var client = await GetClientByCredentialsAsync(loginDto.ClientId, loginDto.ClientSecret);
if (client == null)
{
return ApiResponse<OAuthTokenResponseDto>.ErrorResult("无效的客户端凭据");
}
// 验证用户凭据
var user = await _context.Users
.FirstOrDefaultAsync(u => (u.Email == loginDto.UsernameOrEmail || u.Nickname == loginDto.UsernameOrEmail));
if (user == null)
{
return ApiResponse<OAuthTokenResponseDto>.ErrorResult("用户名或密码错误");
}
// 验证密码
if (!_passwordHelper.VerifyPassword(loginDto.Password, user.PasswordHash))
{
return ApiResponse<OAuthTokenResponseDto>.ErrorResult("用户名或密码错误");
}
// 生成访问令牌和刷新令牌
var accessToken = GenerateRandomString(64);
var refreshToken = GenerateRandomString(64);
var oauthAccessToken = new OAuthAccessToken
{
Token = accessToken,
ClientId = client.Id,
UserId = user.Id,
Scopes = loginDto.Scope,
IsRevoked = false,
ExpiresAt = DateTime.UtcNow.AddHours(1), // 访问令牌1小时后过期
CreatedAt = DateTime.UtcNow
Subject = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.Username),
new Claim(ClaimTypes.Email, user.Email),
new Claim("client_id", client.ClientId)
}),
Expires = DateTime.UtcNow.AddHours(1),
Issuer = jwtSettings["Issuer"],
Audience = jwtSettings["Audience"],
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var oauthRefreshToken = new OAuthRefreshToken
{
Token = refreshToken,
ClientId = client.Id,
UserId = user.Id,
IsUsed = false,
ExpiresAt = DateTime.UtcNow.AddDays(30), // 刷新令牌30天后过期
CreatedAt = DateTime.UtcNow
};
_context.OAuthAccessTokens.Add(oauthAccessToken);
_context.OAuthRefreshTokens.Add(oauthRefreshToken);
await _context.SaveChangesAsync();
var result = new OAuthTokenResponseDto
{
AccessToken = accessToken,
TokenType = "Bearer",
ExpiresIn = 3600, // 1小时
RefreshToken = refreshToken,
Scope = loginDto.Scope
};
return ApiResponse<OAuthTokenResponseDto>.SuccessResult(result);
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
private string GenerateRandomString(int length)
private string GenerateRefreshToken()
{
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
var random = new Random();
var result = new char[length];
for (int i = 0; i < length; i++)
{
result[i] = chars[random.Next(chars.Length)];
}
return new string(result);
var randomNumber = new byte[32];
using var rng = RandomNumberGenerator.Create();
rng.GetBytes(randomNumber);
return Convert.ToBase64String(randomNumber);
}
}
}

View File

@@ -50,10 +50,10 @@ namespace FutureMailAPI.Services
return ApiResponse<UserResponseDto>.ErrorResult("邮箱已被注册");
}
// 生成随机盐值
// 生成盐值
var salt = _passwordHelper.GenerateSalt();
// 创建新用户
// 创建新用户(使用正确的密码哈希方法)
var user = new User
{
Username = registerDto.Username,
@@ -94,7 +94,7 @@ namespace FutureMailAPI.Services
}
// 验证密码
if (!_passwordHelper.VerifyPassword(loginDto.Password, user.PasswordHash))
if (!_passwordHelper.VerifyPassword(loginDto.Password, user.PasswordHash, user.Salt))
{
return ApiResponse<AuthResponseDto>.ErrorResult("用户名或密码错误");
}
@@ -103,13 +103,9 @@ namespace FutureMailAPI.Services
user.LastLoginAt = DateTime.UtcNow;
await _context.SaveChangesAsync();
// 注意这里不再生成JWT令牌因为我们将使用OAuth 2.0
// 在OAuth 2.0流程中令牌是通过OAuth端点生成的
// 创建认证响应无token版本
var authResponse = new AuthResponseDto
{
Token = "", // 临时空字符串实际使用OAuth 2.0令牌
Expires = DateTime.UtcNow.AddDays(7),
User = MapToUserResponseDto(user)
};
@@ -225,7 +221,7 @@ namespace FutureMailAPI.Services
}
// 验证当前密码
if (!_passwordHelper.VerifyPassword(changePasswordDto.CurrentPassword, user.PasswordHash))
if (!_passwordHelper.VerifyPassword(changePasswordDto.CurrentPassword, user.PasswordHash, user.Salt))
{
return ApiResponse<bool>.ErrorResult("当前密码错误");
}
@@ -259,10 +255,10 @@ namespace FutureMailAPI.Services
return ApiResponse<UserResponseDto>.ErrorResult("邮箱已被注册");
}
// 生成随机盐值
// 生成盐值
var salt = _passwordHelper.GenerateSalt();
// 创建新用户
// 创建新用户(使用正确的密码哈希方法)
var user = new User
{
Username = registerDto.Username,

28
FutureMailAPI/TestDB.cs Normal file
View File

@@ -0,0 +1,28 @@
using Microsoft.Data.Sqlite;
using System;
namespace TestDB
{
class Program
{
static void Main(string[] args)
{
using (var connection = new SqliteConnection("Data Source=FutureMail.db"))
{
connection.Open();
var command = connection.CreateCommand();
command.CommandText = "PRAGMA table_info(Users)";
using (var reader = command.ExecuteReader())
{
Console.WriteLine("Users表结构:");
while (reader.Read())
{
Console.WriteLine($"列名: {reader[1]}, 类型: {reader[2]}, 是否非空: {reader[3]}, 默认值: {reader[4]}, 主键: {reader[5]}");
}
}
}
}
}
}

View File

@@ -1,26 +0,0 @@
using Microsoft.EntityFrameworkCore;
using FutureMailAPI.Data;
using FutureMailAPI.Models;
var optionsBuilder = new DbContextOptionsBuilder<FutureMailDbContext>();
optionsBuilder.UseSqlite("Data Source=FutureMail.db");
using var context = new FutureMailDbContext(optionsBuilder.Options);
// 检查OAuth客户端数据
var clients = await context.OAuthClients.ToListAsync();
Console.WriteLine($"OAuth客户端数量: {clients.Count}");
foreach (var client in clients)
{
Console.WriteLine($"客户端ID: {client.ClientId}, 名称: {client.Name}, 是否激活: {client.IsActive}");
}
// 检查用户数据
var users = await context.Users.ToListAsync();
Console.WriteLine($"用户数量: {users.Count}");
foreach (var user in users)
{
Console.WriteLine($"用户ID: {user.Id}, 用户名: {user.Username}, 邮箱: {user.Email}");
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,20 @@
{
"runtimeOptions": {
"tfm": "net9.0",
"frameworks": [
{
"name": "Microsoft.NETCore.App",
"version": "9.0.0"
},
{
"name": "Microsoft.AspNetCore.App",
"version": "9.0.0"
}
],
"configProperties": {
"System.GC.Server": true,
"System.Reflection.NullabilityInfoContext.IsSupported": true,
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
}
}
}

View File

@@ -0,0 +1 @@
{"Version":1,"ManifestType":"Build","Endpoints":[]}

View File

@@ -0,0 +1 @@
{"ContentRoots":["C:\\Users\\Administrator\\Desktop\\快乐转盘\\未来邮箱02APi\\FutureMailAPI\\wwwroot\\"],"Root":{"Children":null,"Asset":null,"Patterns":[{"ContentRootIndex":0,"Pattern":"**","Depth":0}]}}

View File

@@ -0,0 +1,248 @@
<?xml version="1.0"?>
<doc>
<assembly>
<name>FutureMailAPI</name>
</assembly>
<members>
<member name="M:FutureMailAPI.Controllers.AIController.WritingAssistant(FutureMailAPI.DTOs.WritingAssistantRequestDto)">
<summary>
AI写作辅助
</summary>
<param name="request">写作辅助请求</param>
<returns>AI生成的内容和建议</returns>
</member>
<member name="M:FutureMailAPI.Controllers.AIController.SentimentAnalysis(FutureMailAPI.DTOs.SentimentAnalysisRequestDto)">
<summary>
情感分析
</summary>
<param name="request">情感分析请求</param>
<returns>情感分析结果</returns>
</member>
<member name="M:FutureMailAPI.Controllers.AIController.FuturePrediction(FutureMailAPI.DTOs.FuturePredictionRequestDto)">
<summary>
未来预测
</summary>
<param name="request">未来预测请求</param>
<returns>未来预测结果</returns>
</member>
<member name="T:FutureMailAPI.Controllers.BaseController">
<summary>
基础控制器,提供通用的用户身份验证方法
</summary>
</member>
<member name="M:FutureMailAPI.Controllers.BaseController.GetCurrentUserIdNullable">
<summary>
获取当前用户ID
兼容OAuth中间件和JWT令牌两种验证方式
</summary>
<returns>用户ID如果未认证则返回null</returns>
</member>
<member name="M:FutureMailAPI.Controllers.BaseController.GetCurrentUserId">
<summary>
获取当前用户ID
兼容OAuth中间件和JWT令牌两种验证方式
</summary>
<returns>用户ID如果未认证则返回0</returns>
</member>
<member name="M:FutureMailAPI.Controllers.BaseController.GetCurrentUserEmail">
<summary>
获取当前用户邮箱
</summary>
<returns>用户邮箱如果未认证则返回null</returns>
</member>
<member name="M:FutureMailAPI.Controllers.FileUploadController.UploadAttachment(FutureMailAPI.DTOs.FileUploadWithFileRequestDto)">
<summary>
上传附件
</summary>
<param name="request">文件上传请求</param>
<returns>上传结果</returns>
</member>
<member name="M:FutureMailAPI.Controllers.FileUploadController.UploadAvatar(FutureMailAPI.DTOs.FileUploadWithFileRequestDto)">
<summary>
上传头像
</summary>
<param name="request">文件上传请求</param>
<returns>上传结果</returns>
</member>
<member name="M:FutureMailAPI.Controllers.FileUploadController.DeleteFile(System.String)">
<summary>
删除文件
</summary>
<param name="fileId">文件ID</param>
<returns>删除结果</returns>
</member>
<member name="M:FutureMailAPI.Controllers.FileUploadController.GetFile(System.String)">
<summary>
获取文件信息
</summary>
<param name="fileId">文件ID</param>
<returns>文件信息</returns>
</member>
<member name="M:FutureMailAPI.Controllers.NotificationController.RegisterDevice(FutureMailAPI.DTOs.NotificationDeviceRequestDto)">
<summary>
注册设备
</summary>
<param name="request">设备注册请求</param>
<returns>注册结果</returns>
</member>
<member name="M:FutureMailAPI.Controllers.NotificationController.GetNotificationSettings">
<summary>
获取通知设置
</summary>
<returns>通知设置</returns>
</member>
<member name="M:FutureMailAPI.Controllers.PersonalSpaceController.GetTimeline(FutureMailAPI.DTOs.TimelineType,System.Nullable{System.DateTime},System.Nullable{System.DateTime})">
<summary>
获取用户时间线
</summary>
<param name="type">时间线类型</param>
<param name="startDate">开始日期</param>
<param name="endDate">结束日期</param>
<returns>用户时间线</returns>
</member>
<member name="M:FutureMailAPI.Controllers.PersonalSpaceController.GetStatistics">
<summary>
获取用户统计数据
</summary>
<returns>用户统计数据</returns>
</member>
<member name="M:FutureMailAPI.Controllers.PersonalSpaceController.GetSubscription">
<summary>
获取用户订阅信息
</summary>
<returns>用户订阅信息</returns>
</member>
<member name="M:FutureMailAPI.Controllers.PersonalSpaceController.GetUserProfile">
<summary>
获取用户资料
</summary>
<returns>用户资料</returns>
</member>
<member name="M:FutureMailAPI.Controllers.StatisticsController.GetStatistics">
<summary>
获取用户统计数据
</summary>
<returns>用户统计数据</returns>
</member>
<member name="M:FutureMailAPI.Controllers.TimelineController.GetTimeline(FutureMailAPI.DTOs.TimelineType,System.Nullable{System.DateTime},System.Nullable{System.DateTime})">
<summary>
获取用户时间线
</summary>
<param name="type">时间线类型</param>
<param name="startDate">开始日期</param>
<param name="endDate">结束日期</param>
<returns>用户时间线</returns>
</member>
<member name="M:FutureMailAPI.Controllers.UploadController.UploadAttachment(FutureMailAPI.DTOs.FileUploadWithFileRequestDto)">
<summary>
上传附件
</summary>
<param name="request">文件上传请求</param>
<returns>上传结果</returns>
</member>
<member name="M:FutureMailAPI.Controllers.UploadController.UploadAvatar(FutureMailAPI.DTOs.FileUploadWithFileRequestDto)">
<summary>
上传头像
</summary>
<param name="request">文件上传请求</param>
<returns>上传结果</returns>
</member>
<member name="M:FutureMailAPI.Controllers.UserController.GetSubscription">
<summary>
获取用户订阅信息
</summary>
<returns>用户订阅信息</returns>
</member>
<member name="M:FutureMailAPI.Controllers.UserController.GetUserProfile">
<summary>
获取用户资料
</summary>
<returns>用户资料</returns>
</member>
<member name="M:FutureMailAPI.Extensions.HttpContextExtensions.GetCurrentUserId(Microsoft.AspNetCore.Http.HttpContext)">
<summary>
获取当前用户ID
</summary>
</member>
<member name="M:FutureMailAPI.Extensions.HttpContextExtensions.GetCurrentUserEmail(Microsoft.AspNetCore.Http.HttpContext)">
<summary>
获取当前用户邮箱
</summary>
</member>
<member name="M:FutureMailAPI.Extensions.HttpContextExtensions.GetCurrentAccessToken(Microsoft.AspNetCore.Http.HttpContext)">
<summary>
获取当前访问令牌
</summary>
</member>
<member name="T:FutureMailAPI.Helpers.FileUploadOperationFilter">
<summary>
Swagger文件上传操作过滤器
</summary>
</member>
<member name="T:FutureMailAPI.Middleware.CustomAuthenticationHandler">
<summary>
自定义认证处理器与OAuthAuthenticationMiddleware配合工作
</summary>
</member>
<member name="T:FutureMailAPI.Migrations.InitialCreate">
<inheritdoc />
</member>
<member name="M:FutureMailAPI.Migrations.InitialCreate.Up(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
<inheritdoc />
</member>
<member name="M:FutureMailAPI.Migrations.InitialCreate.Down(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
<inheritdoc />
</member>
<member name="M:FutureMailAPI.Migrations.InitialCreate.BuildTargetModel(Microsoft.EntityFrameworkCore.ModelBuilder)">
<inheritdoc />
</member>
<member name="T:FutureMailAPI.Migrations.AddUserPreferences">
<inheritdoc />
</member>
<member name="M:FutureMailAPI.Migrations.AddUserPreferences.Up(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
<inheritdoc />
</member>
<member name="M:FutureMailAPI.Migrations.AddUserPreferences.Down(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
<inheritdoc />
</member>
<member name="M:FutureMailAPI.Migrations.AddUserPreferences.BuildTargetModel(Microsoft.EntityFrameworkCore.ModelBuilder)">
<inheritdoc />
</member>
<member name="T:FutureMailAPI.Migrations.AddOAuthEntities">
<inheritdoc />
</member>
<member name="M:FutureMailAPI.Migrations.AddOAuthEntities.Up(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
<inheritdoc />
</member>
<member name="M:FutureMailAPI.Migrations.AddOAuthEntities.Down(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
<inheritdoc />
</member>
<member name="M:FutureMailAPI.Migrations.AddOAuthEntities.BuildTargetModel(Microsoft.EntityFrameworkCore.ModelBuilder)">
<inheritdoc />
</member>
<member name="T:FutureMailAPI.Migrations.AddSaltToUser">
<inheritdoc />
</member>
<member name="M:FutureMailAPI.Migrations.AddSaltToUser.Up(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
<inheritdoc />
</member>
<member name="M:FutureMailAPI.Migrations.AddSaltToUser.Down(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
<inheritdoc />
</member>
<member name="M:FutureMailAPI.Migrations.AddSaltToUser.BuildTargetModel(Microsoft.EntityFrameworkCore.ModelBuilder)">
<inheritdoc />
</member>
<member name="T:FutureMailAPI.Migrations.AddRefreshTokenToUser">
<inheritdoc />
</member>
<member name="M:FutureMailAPI.Migrations.AddRefreshTokenToUser.Up(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
<inheritdoc />
</member>
<member name="M:FutureMailAPI.Migrations.AddRefreshTokenToUser.Down(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
<inheritdoc />
</member>
<member name="M:FutureMailAPI.Migrations.AddRefreshTokenToUser.BuildTargetModel(Microsoft.EntityFrameworkCore.ModelBuilder)">
<inheritdoc />
</member>
</members>
</doc>

View File

@@ -0,0 +1,663 @@
{
"runtimeTarget": {
"name": ".NETCoreApp,Version=v10.0",
"signature": ""
},
"compilationOptions": {},
"targets": {
".NETCoreApp,Version=v10.0": {
"TestOAuthApp/1.0.0": {
"dependencies": {
"FutureMailAPI": "1.0.0"
},
"runtime": {
"TestOAuthApp.dll": {}
}
},
"Microsoft.AspNetCore.Authentication.JwtBearer/9.0.9": {
"dependencies": {
"Microsoft.IdentityModel.Protocols.OpenIdConnect": "8.0.1"
},
"runtime": {
"lib/net9.0/Microsoft.AspNetCore.Authentication.JwtBearer.dll": {
"assemblyVersion": "9.0.9.0",
"fileVersion": "9.0.925.42003"
}
}
},
"Microsoft.AspNetCore.OpenApi/9.0.9": {
"dependencies": {
"Microsoft.OpenApi": "1.6.25"
},
"runtime": {
"lib/net9.0/Microsoft.AspNetCore.OpenApi.dll": {
"assemblyVersion": "9.0.9.0",
"fileVersion": "9.0.925.42003"
}
}
},
"Microsoft.Data.Sqlite.Core/9.0.9": {
"dependencies": {
"SQLitePCLRaw.core": "2.1.10"
},
"runtime": {
"lib/net8.0/Microsoft.Data.Sqlite.dll": {
"assemblyVersion": "9.0.9.0",
"fileVersion": "9.0.925.41909"
}
}
},
"Microsoft.EntityFrameworkCore/9.0.9": {
"dependencies": {
"Microsoft.EntityFrameworkCore.Abstractions": "9.0.9"
},
"runtime": {
"lib/net8.0/Microsoft.EntityFrameworkCore.dll": {
"assemblyVersion": "9.0.9.0",
"fileVersion": "9.0.925.41909"
}
}
},
"Microsoft.EntityFrameworkCore.Abstractions/9.0.9": {
"runtime": {
"lib/net8.0/Microsoft.EntityFrameworkCore.Abstractions.dll": {
"assemblyVersion": "9.0.9.0",
"fileVersion": "9.0.925.41909"
}
}
},
"Microsoft.EntityFrameworkCore.Relational/9.0.9": {
"dependencies": {
"Microsoft.EntityFrameworkCore": "9.0.9"
},
"runtime": {
"lib/net8.0/Microsoft.EntityFrameworkCore.Relational.dll": {
"assemblyVersion": "9.0.9.0",
"fileVersion": "9.0.925.41909"
}
}
},
"Microsoft.EntityFrameworkCore.Sqlite/9.0.9": {
"dependencies": {
"Microsoft.EntityFrameworkCore.Sqlite.Core": "9.0.9",
"Microsoft.Extensions.DependencyModel": "9.0.9",
"SQLitePCLRaw.bundle_e_sqlite3": "2.1.10",
"SQLitePCLRaw.core": "2.1.10"
}
},
"Microsoft.EntityFrameworkCore.Sqlite.Core/9.0.9": {
"dependencies": {
"Microsoft.Data.Sqlite.Core": "9.0.9",
"Microsoft.EntityFrameworkCore.Relational": "9.0.9",
"Microsoft.Extensions.DependencyModel": "9.0.9",
"SQLitePCLRaw.core": "2.1.10"
},
"runtime": {
"lib/net8.0/Microsoft.EntityFrameworkCore.Sqlite.dll": {
"assemblyVersion": "9.0.9.0",
"fileVersion": "9.0.925.41909"
}
}
},
"Microsoft.Extensions.DependencyModel/9.0.9": {
"runtime": {
"lib/net9.0/Microsoft.Extensions.DependencyModel.dll": {
"assemblyVersion": "9.0.0.9",
"fileVersion": "9.0.925.41916"
}
}
},
"Microsoft.IdentityModel.Abstractions/8.14.0": {
"runtime": {
"lib/net9.0/Microsoft.IdentityModel.Abstractions.dll": {
"assemblyVersion": "8.14.0.0",
"fileVersion": "8.14.0.60815"
}
}
},
"Microsoft.IdentityModel.JsonWebTokens/8.14.0": {
"dependencies": {
"Microsoft.IdentityModel.Tokens": "8.14.0"
},
"runtime": {
"lib/net9.0/Microsoft.IdentityModel.JsonWebTokens.dll": {
"assemblyVersion": "8.14.0.0",
"fileVersion": "8.14.0.60815"
}
}
},
"Microsoft.IdentityModel.Logging/8.14.0": {
"dependencies": {
"Microsoft.IdentityModel.Abstractions": "8.14.0"
},
"runtime": {
"lib/net9.0/Microsoft.IdentityModel.Logging.dll": {
"assemblyVersion": "8.14.0.0",
"fileVersion": "8.14.0.60815"
}
}
},
"Microsoft.IdentityModel.Protocols/8.0.1": {
"dependencies": {
"Microsoft.IdentityModel.Tokens": "8.14.0"
},
"runtime": {
"lib/net9.0/Microsoft.IdentityModel.Protocols.dll": {
"assemblyVersion": "8.0.1.0",
"fileVersion": "8.0.1.50722"
}
}
},
"Microsoft.IdentityModel.Protocols.OpenIdConnect/8.0.1": {
"dependencies": {
"Microsoft.IdentityModel.Protocols": "8.0.1",
"System.IdentityModel.Tokens.Jwt": "8.14.0"
},
"runtime": {
"lib/net9.0/Microsoft.IdentityModel.Protocols.OpenIdConnect.dll": {
"assemblyVersion": "8.0.1.0",
"fileVersion": "8.0.1.50722"
}
}
},
"Microsoft.IdentityModel.Tokens/8.14.0": {
"dependencies": {
"Microsoft.IdentityModel.Logging": "8.14.0"
},
"runtime": {
"lib/net9.0/Microsoft.IdentityModel.Tokens.dll": {
"assemblyVersion": "8.14.0.0",
"fileVersion": "8.14.0.60815"
}
}
},
"Microsoft.OpenApi/1.6.25": {
"runtime": {
"lib/netstandard2.0/Microsoft.OpenApi.dll": {
"assemblyVersion": "1.6.25.0",
"fileVersion": "1.6.25.0"
}
}
},
"MySqlConnector/2.4.0": {
"runtime": {
"lib/net9.0/MySqlConnector.dll": {
"assemblyVersion": "2.0.0.0",
"fileVersion": "2.4.0.0"
}
}
},
"Pomelo.EntityFrameworkCore.MySql/9.0.0": {
"dependencies": {
"Microsoft.EntityFrameworkCore.Relational": "9.0.9",
"MySqlConnector": "2.4.0"
},
"runtime": {
"lib/net8.0/Pomelo.EntityFrameworkCore.MySql.dll": {
"assemblyVersion": "9.0.0.0",
"fileVersion": "9.0.0.0"
}
}
},
"Quartz/3.15.0": {
"runtime": {
"lib/net9.0/Quartz.dll": {
"assemblyVersion": "3.15.0.0",
"fileVersion": "3.15.0.0"
}
}
},
"Quartz.Extensions.DependencyInjection/3.15.0": {
"dependencies": {
"Quartz": "3.15.0"
},
"runtime": {
"lib/net9.0/Quartz.Extensions.DependencyInjection.dll": {
"assemblyVersion": "3.15.0.0",
"fileVersion": "3.15.0.0"
}
}
},
"Quartz.Extensions.Hosting/3.15.0": {
"dependencies": {
"Quartz.Extensions.DependencyInjection": "3.15.0"
},
"runtime": {
"lib/net9.0/Quartz.Extensions.Hosting.dll": {
"assemblyVersion": "3.15.0.0",
"fileVersion": "3.15.0.0"
}
}
},
"SQLitePCLRaw.bundle_e_sqlite3/2.1.10": {
"dependencies": {
"SQLitePCLRaw.lib.e_sqlite3": "2.1.10",
"SQLitePCLRaw.provider.e_sqlite3": "2.1.10"
},
"runtime": {
"lib/netstandard2.0/SQLitePCLRaw.batteries_v2.dll": {
"assemblyVersion": "2.1.10.2445",
"fileVersion": "2.1.10.2445"
}
}
},
"SQLitePCLRaw.core/2.1.10": {
"runtime": {
"lib/netstandard2.0/SQLitePCLRaw.core.dll": {
"assemblyVersion": "2.1.10.2445",
"fileVersion": "2.1.10.2445"
}
}
},
"SQLitePCLRaw.lib.e_sqlite3/2.1.10": {
"runtimeTargets": {
"runtimes/browser-wasm/nativeassets/net9.0/e_sqlite3.a": {
"rid": "browser-wasm",
"assetType": "native",
"fileVersion": "0.0.0.0"
},
"runtimes/linux-arm/native/libe_sqlite3.so": {
"rid": "linux-arm",
"assetType": "native",
"fileVersion": "0.0.0.0"
},
"runtimes/linux-arm64/native/libe_sqlite3.so": {
"rid": "linux-arm64",
"assetType": "native",
"fileVersion": "0.0.0.0"
},
"runtimes/linux-armel/native/libe_sqlite3.so": {
"rid": "linux-armel",
"assetType": "native",
"fileVersion": "0.0.0.0"
},
"runtimes/linux-mips64/native/libe_sqlite3.so": {
"rid": "linux-mips64",
"assetType": "native",
"fileVersion": "0.0.0.0"
},
"runtimes/linux-musl-arm/native/libe_sqlite3.so": {
"rid": "linux-musl-arm",
"assetType": "native",
"fileVersion": "0.0.0.0"
},
"runtimes/linux-musl-arm64/native/libe_sqlite3.so": {
"rid": "linux-musl-arm64",
"assetType": "native",
"fileVersion": "0.0.0.0"
},
"runtimes/linux-musl-s390x/native/libe_sqlite3.so": {
"rid": "linux-musl-s390x",
"assetType": "native",
"fileVersion": "0.0.0.0"
},
"runtimes/linux-musl-x64/native/libe_sqlite3.so": {
"rid": "linux-musl-x64",
"assetType": "native",
"fileVersion": "0.0.0.0"
},
"runtimes/linux-ppc64le/native/libe_sqlite3.so": {
"rid": "linux-ppc64le",
"assetType": "native",
"fileVersion": "0.0.0.0"
},
"runtimes/linux-s390x/native/libe_sqlite3.so": {
"rid": "linux-s390x",
"assetType": "native",
"fileVersion": "0.0.0.0"
},
"runtimes/linux-x64/native/libe_sqlite3.so": {
"rid": "linux-x64",
"assetType": "native",
"fileVersion": "0.0.0.0"
},
"runtimes/linux-x86/native/libe_sqlite3.so": {
"rid": "linux-x86",
"assetType": "native",
"fileVersion": "0.0.0.0"
},
"runtimes/maccatalyst-arm64/native/libe_sqlite3.dylib": {
"rid": "maccatalyst-arm64",
"assetType": "native",
"fileVersion": "0.0.0.0"
},
"runtimes/maccatalyst-x64/native/libe_sqlite3.dylib": {
"rid": "maccatalyst-x64",
"assetType": "native",
"fileVersion": "0.0.0.0"
},
"runtimes/osx-arm64/native/libe_sqlite3.dylib": {
"rid": "osx-arm64",
"assetType": "native",
"fileVersion": "0.0.0.0"
},
"runtimes/osx-x64/native/libe_sqlite3.dylib": {
"rid": "osx-x64",
"assetType": "native",
"fileVersion": "0.0.0.0"
},
"runtimes/win-arm/native/e_sqlite3.dll": {
"rid": "win-arm",
"assetType": "native",
"fileVersion": "0.0.0.0"
},
"runtimes/win-arm64/native/e_sqlite3.dll": {
"rid": "win-arm64",
"assetType": "native",
"fileVersion": "0.0.0.0"
},
"runtimes/win-x64/native/e_sqlite3.dll": {
"rid": "win-x64",
"assetType": "native",
"fileVersion": "0.0.0.0"
},
"runtimes/win-x86/native/e_sqlite3.dll": {
"rid": "win-x86",
"assetType": "native",
"fileVersion": "0.0.0.0"
}
}
},
"SQLitePCLRaw.provider.e_sqlite3/2.1.10": {
"dependencies": {
"SQLitePCLRaw.core": "2.1.10"
},
"runtime": {
"lib/net6.0/SQLitePCLRaw.provider.e_sqlite3.dll": {
"assemblyVersion": "2.1.10.2445",
"fileVersion": "2.1.10.2445"
}
}
},
"Swashbuckle.AspNetCore/9.0.6": {
"dependencies": {
"Swashbuckle.AspNetCore.Swagger": "9.0.6",
"Swashbuckle.AspNetCore.SwaggerGen": "9.0.6",
"Swashbuckle.AspNetCore.SwaggerUI": "9.0.6"
}
},
"Swashbuckle.AspNetCore.Swagger/9.0.6": {
"dependencies": {
"Microsoft.OpenApi": "1.6.25"
},
"runtime": {
"lib/net9.0/Swashbuckle.AspNetCore.Swagger.dll": {
"assemblyVersion": "9.0.6.0",
"fileVersion": "9.0.6.1840"
}
}
},
"Swashbuckle.AspNetCore.SwaggerGen/9.0.6": {
"dependencies": {
"Swashbuckle.AspNetCore.Swagger": "9.0.6"
},
"runtime": {
"lib/net9.0/Swashbuckle.AspNetCore.SwaggerGen.dll": {
"assemblyVersion": "9.0.6.0",
"fileVersion": "9.0.6.1840"
}
}
},
"Swashbuckle.AspNetCore.SwaggerUI/9.0.6": {
"runtime": {
"lib/net9.0/Swashbuckle.AspNetCore.SwaggerUI.dll": {
"assemblyVersion": "9.0.6.0",
"fileVersion": "9.0.6.1840"
}
}
},
"System.IdentityModel.Tokens.Jwt/8.14.0": {
"dependencies": {
"Microsoft.IdentityModel.JsonWebTokens": "8.14.0",
"Microsoft.IdentityModel.Tokens": "8.14.0"
},
"runtime": {
"lib/net9.0/System.IdentityModel.Tokens.Jwt.dll": {
"assemblyVersion": "8.14.0.0",
"fileVersion": "8.14.0.60815"
}
}
},
"FutureMailAPI/1.0.0": {
"dependencies": {
"Microsoft.AspNetCore.Authentication.JwtBearer": "9.0.9",
"Microsoft.AspNetCore.OpenApi": "9.0.9",
"Microsoft.EntityFrameworkCore.Sqlite": "9.0.9",
"Pomelo.EntityFrameworkCore.MySql": "9.0.0",
"Quartz": "3.15.0",
"Quartz.Extensions.Hosting": "3.15.0",
"Swashbuckle.AspNetCore": "9.0.6",
"System.IdentityModel.Tokens.Jwt": "8.14.0"
},
"runtime": {
"FutureMailAPI.dll": {
"assemblyVersion": "1.0.0.0",
"fileVersion": "1.0.0.0"
}
}
}
}
},
"libraries": {
"TestOAuthApp/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Microsoft.AspNetCore.Authentication.JwtBearer/9.0.9": {
"type": "package",
"serviceable": true,
"sha512": "sha512-U5gW2DS/yAE9X0Ko63/O2lNApAzI/jhx4IT1Th6W0RShKv6XAVVgLGN3zqnmcd6DtAnp5FYs+4HZrxsTl0anLA==",
"path": "microsoft.aspnetcore.authentication.jwtbearer/9.0.9",
"hashPath": "microsoft.aspnetcore.authentication.jwtbearer.9.0.9.nupkg.sha512"
},
"Microsoft.AspNetCore.OpenApi/9.0.9": {
"type": "package",
"serviceable": true,
"sha512": "sha512-3Sina0gS/CTYt9XG6DUFPdXOmJui7e551U0kO2bIiDk3vZ2sctxxenN+cE1a5CrUpjIVZfZr32neWYYRO+Piaw==",
"path": "microsoft.aspnetcore.openapi/9.0.9",
"hashPath": "microsoft.aspnetcore.openapi.9.0.9.nupkg.sha512"
},
"Microsoft.Data.Sqlite.Core/9.0.9": {
"type": "package",
"serviceable": true,
"sha512": "sha512-DjxZRueHp0qvZxhvW+H1IWYkSofZI8Chg710KYJjNP/6S4q3rt97pvR8AHOompkSwaN92VLKz5uw01iUt85cMg==",
"path": "microsoft.data.sqlite.core/9.0.9",
"hashPath": "microsoft.data.sqlite.core.9.0.9.nupkg.sha512"
},
"Microsoft.EntityFrameworkCore/9.0.9": {
"type": "package",
"serviceable": true,
"sha512": "sha512-zkt5yQgnpWKX3rOxn+ZcV23Aj0296XCTqg4lx1hKY+wMXBgkn377UhBrY/A4H6kLpNT7wqZN98xCV0YHXu9VRA==",
"path": "microsoft.entityframeworkcore/9.0.9",
"hashPath": "microsoft.entityframeworkcore.9.0.9.nupkg.sha512"
},
"Microsoft.EntityFrameworkCore.Abstractions/9.0.9": {
"type": "package",
"serviceable": true,
"sha512": "sha512-QdM2k3Mnip2QsaxJbCI95dc2SajRMENdmaMhVKj4jPC5dmkoRcu3eEdvZAgDbd4bFVV1jtPGdHtXewtoBMlZqA==",
"path": "microsoft.entityframeworkcore.abstractions/9.0.9",
"hashPath": "microsoft.entityframeworkcore.abstractions.9.0.9.nupkg.sha512"
},
"Microsoft.EntityFrameworkCore.Relational/9.0.9": {
"type": "package",
"serviceable": true,
"sha512": "sha512-SonFU9a8x4jZIhIBtCw1hIE3QKjd4c7Y3mjptoh682dfQe7K9pUPGcEV/sk4n8AJdq4fkyJPCaOdYaObhae/Iw==",
"path": "microsoft.entityframeworkcore.relational/9.0.9",
"hashPath": "microsoft.entityframeworkcore.relational.9.0.9.nupkg.sha512"
},
"Microsoft.EntityFrameworkCore.Sqlite/9.0.9": {
"type": "package",
"serviceable": true,
"sha512": "sha512-SiAd32IMTAQDo+jQt5GAzCq+5qI/OEdsrbW0qEDr0hUEAh3jnRlt0gbZgDGDUtWk5SWITufB6AOZi0qet9dJIw==",
"path": "microsoft.entityframeworkcore.sqlite/9.0.9",
"hashPath": "microsoft.entityframeworkcore.sqlite.9.0.9.nupkg.sha512"
},
"Microsoft.EntityFrameworkCore.Sqlite.Core/9.0.9": {
"type": "package",
"serviceable": true,
"sha512": "sha512-eQVF8fBgDxjnjan3EB1ysdfDO7lKKfWKTT4VR0BInU4Mi6ADdgiOdm6qvZ/ufh04f3hhPL5lyknx5XotGzBh8A==",
"path": "microsoft.entityframeworkcore.sqlite.core/9.0.9",
"hashPath": "microsoft.entityframeworkcore.sqlite.core.9.0.9.nupkg.sha512"
},
"Microsoft.Extensions.DependencyModel/9.0.9": {
"type": "package",
"serviceable": true,
"sha512": "sha512-fNGvKct2De8ghm0Bpfq0iWthtzIWabgOTi+gJhNOPhNJIowXNEUE2eZNW/zNCzrHVA3PXg2yZ+3cWZndC2IqYA==",
"path": "microsoft.extensions.dependencymodel/9.0.9",
"hashPath": "microsoft.extensions.dependencymodel.9.0.9.nupkg.sha512"
},
"Microsoft.IdentityModel.Abstractions/8.14.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-iwbCpSjD3ehfTwBhtSNEtKPK0ICun6ov7Ibx6ISNA9bfwIyzI2Siwyi9eJFCJBwxowK9xcA1mj+jBWiigeqgcQ==",
"path": "microsoft.identitymodel.abstractions/8.14.0",
"hashPath": "microsoft.identitymodel.abstractions.8.14.0.nupkg.sha512"
},
"Microsoft.IdentityModel.JsonWebTokens/8.14.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-4jOpiA4THdtpLyMdAb24dtj7+6GmvhOhxf5XHLYWmPKF8ApEnApal1UnJsKO4HxUWRXDA6C4WQVfYyqsRhpNpQ==",
"path": "microsoft.identitymodel.jsonwebtokens/8.14.0",
"hashPath": "microsoft.identitymodel.jsonwebtokens.8.14.0.nupkg.sha512"
},
"Microsoft.IdentityModel.Logging/8.14.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-eqqnemdW38CKZEHS6diA50BV94QICozDZEvSrsvN3SJXUFwVB9gy+/oz76gldP7nZliA16IglXjXTCTdmU/Ejg==",
"path": "microsoft.identitymodel.logging/8.14.0",
"hashPath": "microsoft.identitymodel.logging.8.14.0.nupkg.sha512"
},
"Microsoft.IdentityModel.Protocols/8.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-uA2vpKqU3I2mBBEaeJAWPTjT9v1TZrGWKdgK6G5qJd03CLx83kdiqO9cmiK8/n1erkHzFBwU/RphP83aAe3i3g==",
"path": "microsoft.identitymodel.protocols/8.0.1",
"hashPath": "microsoft.identitymodel.protocols.8.0.1.nupkg.sha512"
},
"Microsoft.IdentityModel.Protocols.OpenIdConnect/8.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-AQDbfpL+yzuuGhO/mQhKNsp44pm5Jv8/BI4KiFXR7beVGZoSH35zMV3PrmcfvSTsyI6qrcR898NzUauD6SRigg==",
"path": "microsoft.identitymodel.protocols.openidconnect/8.0.1",
"hashPath": "microsoft.identitymodel.protocols.openidconnect.8.0.1.nupkg.sha512"
},
"Microsoft.IdentityModel.Tokens/8.14.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-lKIZiBiGd36k02TCdMHp1KlNWisyIvQxcYJvIkz7P4gSQ9zi8dgh6S5Grj8NNG7HWYIPfQymGyoZ6JB5d1Lo1g==",
"path": "microsoft.identitymodel.tokens/8.14.0",
"hashPath": "microsoft.identitymodel.tokens.8.14.0.nupkg.sha512"
},
"Microsoft.OpenApi/1.6.25": {
"type": "package",
"serviceable": true,
"sha512": "sha512-ZahSqNGtNV7N0JBYS/IYXPkLVexL/AZFxo6pqxv6A7Uli7Q7zfitNjkaqIcsV73Ukzxi4IlJdyDgcQiMXiH8cw==",
"path": "microsoft.openapi/1.6.25",
"hashPath": "microsoft.openapi.1.6.25.nupkg.sha512"
},
"MySqlConnector/2.4.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-78M+gVOjbdZEDIyXQqcA7EYlCGS3tpbUELHvn6638A2w0pkPI625ixnzsa5staAd3N9/xFmPJtkKDYwsXpFi/w==",
"path": "mysqlconnector/2.4.0",
"hashPath": "mysqlconnector.2.4.0.nupkg.sha512"
},
"Pomelo.EntityFrameworkCore.MySql/9.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-cl7S4s6CbJno0LjNxrBHNc2xxmCliR5i40ATPZk/eTywVaAbHCbdc9vbGc3QThvwGjHqrDHT8vY9m1VF/47o0g==",
"path": "pomelo.entityframeworkcore.mysql/9.0.0",
"hashPath": "pomelo.entityframeworkcore.mysql.9.0.0.nupkg.sha512"
},
"Quartz/3.15.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-seV76VI/OW9xdsEHJlfErciicfMmmU8lcGde2SJIYxVK+k4smAaBkm0FY8a4AiVb6MMpjTvH252438ol/OOUwQ==",
"path": "quartz/3.15.0",
"hashPath": "quartz.3.15.0.nupkg.sha512"
},
"Quartz.Extensions.DependencyInjection/3.15.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-4g+QSG84cHlffLXoiHC7I/zkw223GUtdj0ZYrY+sxYq45Dd3Rti4uKIn0dWeotyEiJZNpn028bspf8njSJdnUg==",
"path": "quartz.extensions.dependencyinjection/3.15.0",
"hashPath": "quartz.extensions.dependencyinjection.3.15.0.nupkg.sha512"
},
"Quartz.Extensions.Hosting/3.15.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-p9Fy67yAXxwjL/FxfVtmxwXbVtMOVZX+hNnONKT11adugK6kqgdfYvb0IP5pBBHW1rJSq88MpNkQ0EkyMCUhWg==",
"path": "quartz.extensions.hosting/3.15.0",
"hashPath": "quartz.extensions.hosting.3.15.0.nupkg.sha512"
},
"SQLitePCLRaw.bundle_e_sqlite3/2.1.10": {
"type": "package",
"serviceable": true,
"sha512": "sha512-UxWuisvZ3uVcVOLJQv7urM/JiQH+v3TmaJc1BLKl5Dxfm/nTzTUrqswCqg/INiYLi61AXnHo1M1JPmPqqLnAdg==",
"path": "sqlitepclraw.bundle_e_sqlite3/2.1.10",
"hashPath": "sqlitepclraw.bundle_e_sqlite3.2.1.10.nupkg.sha512"
},
"SQLitePCLRaw.core/2.1.10": {
"type": "package",
"serviceable": true,
"sha512": "sha512-Ii8JCbC7oiVclaE/mbDEK000EFIJ+ShRPwAvvV89GOZhQ+ZLtlnSWl6ksCNMKu/VGXA4Nfi2B7LhN/QFN9oBcw==",
"path": "sqlitepclraw.core/2.1.10",
"hashPath": "sqlitepclraw.core.2.1.10.nupkg.sha512"
},
"SQLitePCLRaw.lib.e_sqlite3/2.1.10": {
"type": "package",
"serviceable": true,
"sha512": "sha512-mAr69tDbnf3QJpRy2nJz8Qdpebdil00fvycyByR58Cn9eARvR+UiG2Vzsp+4q1tV3ikwiYIjlXCQFc12GfebbA==",
"path": "sqlitepclraw.lib.e_sqlite3/2.1.10",
"hashPath": "sqlitepclraw.lib.e_sqlite3.2.1.10.nupkg.sha512"
},
"SQLitePCLRaw.provider.e_sqlite3/2.1.10": {
"type": "package",
"serviceable": true,
"sha512": "sha512-uZVTi02C1SxqzgT0HqTWatIbWGb40iIkfc3FpFCpE/r7g6K0PqzDUeefL6P6HPhDtc6BacN3yQysfzP7ks+wSQ==",
"path": "sqlitepclraw.provider.e_sqlite3/2.1.10",
"hashPath": "sqlitepclraw.provider.e_sqlite3.2.1.10.nupkg.sha512"
},
"Swashbuckle.AspNetCore/9.0.6": {
"type": "package",
"serviceable": true,
"sha512": "sha512-q/UfEAgrk6qQyjHXgsW9ILw0YZLfmPtWUY4wYijliX6supozC+TkzU0G6FTnn/dPYxnChjM8g8lHjWHF6VKy+A==",
"path": "swashbuckle.aspnetcore/9.0.6",
"hashPath": "swashbuckle.aspnetcore.9.0.6.nupkg.sha512"
},
"Swashbuckle.AspNetCore.Swagger/9.0.6": {
"type": "package",
"serviceable": true,
"sha512": "sha512-Bgyc8rWRAYwDrzjVHGbavvNE38G1Dfgf1McHYm+WUr4TxkvEAXv8F8B1z3Kmz4BkDCKv9A/1COa2t7+Ri5+pLg==",
"path": "swashbuckle.aspnetcore.swagger/9.0.6",
"hashPath": "swashbuckle.aspnetcore.swagger.9.0.6.nupkg.sha512"
},
"Swashbuckle.AspNetCore.SwaggerGen/9.0.6": {
"type": "package",
"serviceable": true,
"sha512": "sha512-yYrDs5qpIa4UXP+a02X0ZLQs6HSd1C8t6hF6J1fnxoawi3PslJg1yUpLBS89HCbrDACzmwEGG25il+8aa0zdnw==",
"path": "swashbuckle.aspnetcore.swaggergen/9.0.6",
"hashPath": "swashbuckle.aspnetcore.swaggergen.9.0.6.nupkg.sha512"
},
"Swashbuckle.AspNetCore.SwaggerUI/9.0.6": {
"type": "package",
"serviceable": true,
"sha512": "sha512-WGsw/Yop9b16miq8TQd4THxuEgkP5cH3+DX93BrX9m0OdPcKNtg2nNm77WQSAsA+Se+M0bTiu8bUyrruRSeS5g==",
"path": "swashbuckle.aspnetcore.swaggerui/9.0.6",
"hashPath": "swashbuckle.aspnetcore.swaggerui.9.0.6.nupkg.sha512"
},
"System.IdentityModel.Tokens.Jwt/8.14.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-EYGgN/S+HK7S6F3GaaPLFAfK0UzMrkXFyWCvXpQWFYmZln3dqtbyIO7VuTM/iIIPMzkelg8ZLlBPvMhxj6nOAA==",
"path": "system.identitymodel.tokens.jwt/8.14.0",
"hashPath": "system.identitymodel.tokens.jwt.8.14.0.nupkg.sha512"
},
"FutureMailAPI/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
}
}
}

View File

@@ -0,0 +1,19 @@
{
"runtimeOptions": {
"tfm": "net10.0",
"frameworks": [
{
"name": "Microsoft.NETCore.App",
"version": "10.0.0-rc.1.25451.107"
},
{
"name": "Microsoft.AspNetCore.App",
"version": "10.0.0-rc.1.25451.107"
}
],
"configProperties": {
"System.Reflection.NullabilityInfoContext.IsSupported": true,
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
}
}
}

View File

@@ -0,0 +1,466 @@
{
"format": 1,
"restore": {
"C:\\Users\\Administrator\\Desktop\\快乐转盘\\未来邮箱02APi\\FutureMailAPI\\TestOAuthApp\\TestOAuthApp.csproj": {}
},
"projects": {
"C:\\Users\\Administrator\\Desktop\\快乐转盘\\未来邮箱02APi\\FutureMailAPI\\FutureMailAPI.csproj": {
"version": "1.0.0",
"restore": {
"projectUniqueName": "C:\\Users\\Administrator\\Desktop\\快乐转盘\\未来邮箱02APi\\FutureMailAPI\\FutureMailAPI.csproj",
"projectName": "FutureMailAPI",
"projectPath": "C:\\Users\\Administrator\\Desktop\\快乐转盘\\未来邮箱02APi\\FutureMailAPI\\FutureMailAPI.csproj",
"packagesPath": "C:\\Users\\Administrator\\.nuget\\packages\\",
"outputPath": "C:\\Users\\Administrator\\Desktop\\快乐转盘\\未来邮箱02APi\\FutureMailAPI\\obj\\",
"projectStyle": "PackageReference",
"fallbackFolders": [
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
],
"configFilePaths": [
"C:\\Users\\Administrator\\AppData\\Roaming\\NuGet\\NuGet.Config",
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config",
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config"
],
"originalTargetFrameworks": [
"net9.0"
],
"sources": {
"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {},
"C:\\Program Files\\dotnet\\library-packs": {},
"https://api.nuget.org/v3/index.json": {}
},
"frameworks": {
"net9.0": {
"targetAlias": "net9.0",
"projectReferences": {}
}
},
"warningProperties": {
"warnAsError": [
"NU1605"
]
},
"restoreAuditProperties": {
"enableAudit": "true",
"auditLevel": "low",
"auditMode": "direct"
},
"SdkAnalysisLevel": "10.0.100"
},
"frameworks": {
"net9.0": {
"targetAlias": "net9.0",
"dependencies": {
"Microsoft.AspNetCore.Authentication.JwtBearer": {
"target": "Package",
"version": "[9.0.9, )"
},
"Microsoft.AspNetCore.OpenApi": {
"target": "Package",
"version": "[9.0.9, )"
},
"Microsoft.EntityFrameworkCore.Design": {
"include": "Runtime, Build, Native, ContentFiles, Analyzers, BuildTransitive",
"suppressParent": "All",
"target": "Package",
"version": "[9.0.9, )"
},
"Microsoft.EntityFrameworkCore.Sqlite": {
"target": "Package",
"version": "[9.0.9, )"
},
"Microsoft.EntityFrameworkCore.Tools": {
"include": "Runtime, Build, Native, ContentFiles, Analyzers, BuildTransitive",
"suppressParent": "All",
"target": "Package",
"version": "[9.0.9, )"
},
"Pomelo.EntityFrameworkCore.MySql": {
"target": "Package",
"version": "[9.0.0, )"
},
"Quartz": {
"target": "Package",
"version": "[3.15.0, )"
},
"Quartz.Extensions.Hosting": {
"target": "Package",
"version": "[3.15.0, )"
},
"Swashbuckle.AspNetCore": {
"target": "Package",
"version": "[9.0.6, )"
},
"System.IdentityModel.Tokens.Jwt": {
"target": "Package",
"version": "[8.14.0, )"
}
},
"imports": [
"net461",
"net462",
"net47",
"net471",
"net472",
"net48",
"net481"
],
"assetTargetFallback": true,
"warn": true,
"frameworkReferences": {
"Microsoft.AspNetCore.App": {
"privateAssets": "none"
},
"Microsoft.NETCore.App": {
"privateAssets": "all"
}
},
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\10.0.100-rc.1.25451.107/PortableRuntimeIdentifierGraph.json"
}
}
},
"C:\\Users\\Administrator\\Desktop\\快乐转盘\\未来邮箱02APi\\FutureMailAPI\\TestOAuthApp\\TestOAuthApp.csproj": {
"version": "1.0.0",
"restore": {
"projectUniqueName": "C:\\Users\\Administrator\\Desktop\\快乐转盘\\未来邮箱02APi\\FutureMailAPI\\TestOAuthApp\\TestOAuthApp.csproj",
"projectName": "TestOAuthApp",
"projectPath": "C:\\Users\\Administrator\\Desktop\\快乐转盘\\未来邮箱02APi\\FutureMailAPI\\TestOAuthApp\\TestOAuthApp.csproj",
"packagesPath": "C:\\Users\\Administrator\\.nuget\\packages\\",
"outputPath": "C:\\Users\\Administrator\\Desktop\\快乐转盘\\未来邮箱02APi\\FutureMailAPI\\TestOAuthApp\\obj\\",
"projectStyle": "PackageReference",
"fallbackFolders": [
"C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages"
],
"configFilePaths": [
"C:\\Users\\Administrator\\AppData\\Roaming\\NuGet\\NuGet.Config",
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config",
"C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config"
],
"originalTargetFrameworks": [
"net10.0"
],
"sources": {
"C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {},
"C:\\Program Files\\dotnet\\library-packs": {},
"https://api.nuget.org/v3/index.json": {}
},
"frameworks": {
"net10.0": {
"targetAlias": "net10.0",
"projectReferences": {
"C:\\Users\\Administrator\\Desktop\\快乐转盘\\未来邮箱02APi\\FutureMailAPI\\FutureMailAPI.csproj": {
"projectPath": "C:\\Users\\Administrator\\Desktop\\快乐转盘\\未来邮箱02APi\\FutureMailAPI\\FutureMailAPI.csproj"
}
}
}
},
"warningProperties": {
"warnAsError": [
"NU1605"
]
},
"restoreAuditProperties": {
"enableAudit": "true",
"auditLevel": "low",
"auditMode": "all"
},
"SdkAnalysisLevel": "10.0.100"
},
"frameworks": {
"net10.0": {
"targetAlias": "net10.0",
"imports": [
"net461",
"net462",
"net47",
"net471",
"net472",
"net48",
"net481"
],
"assetTargetFallback": true,
"warn": true,
"frameworkReferences": {
"Microsoft.NETCore.App": {
"privateAssets": "all"
}
},
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\10.0.100-rc.1.25451.107/PortableRuntimeIdentifierGraph.json",
"packagesToPrune": {
"Microsoft.CSharp": "(,4.7.32767]",
"Microsoft.VisualBasic": "(,10.4.32767]",
"Microsoft.Win32.Primitives": "(,4.3.32767]",
"Microsoft.Win32.Registry": "(,5.0.32767]",
"runtime.any.System.Collections": "(,4.3.32767]",
"runtime.any.System.Diagnostics.Tools": "(,4.3.32767]",
"runtime.any.System.Diagnostics.Tracing": "(,4.3.32767]",
"runtime.any.System.Globalization": "(,4.3.32767]",
"runtime.any.System.Globalization.Calendars": "(,4.3.32767]",
"runtime.any.System.IO": "(,4.3.32767]",
"runtime.any.System.Reflection": "(,4.3.32767]",
"runtime.any.System.Reflection.Extensions": "(,4.3.32767]",
"runtime.any.System.Reflection.Primitives": "(,4.3.32767]",
"runtime.any.System.Resources.ResourceManager": "(,4.3.32767]",
"runtime.any.System.Runtime": "(,4.3.32767]",
"runtime.any.System.Runtime.Handles": "(,4.3.32767]",
"runtime.any.System.Runtime.InteropServices": "(,4.3.32767]",
"runtime.any.System.Text.Encoding": "(,4.3.32767]",
"runtime.any.System.Text.Encoding.Extensions": "(,4.3.32767]",
"runtime.any.System.Threading.Tasks": "(,4.3.32767]",
"runtime.any.System.Threading.Timer": "(,4.3.32767]",
"runtime.aot.System.Collections": "(,4.3.32767]",
"runtime.aot.System.Diagnostics.Tools": "(,4.3.32767]",
"runtime.aot.System.Diagnostics.Tracing": "(,4.3.32767]",
"runtime.aot.System.Globalization": "(,4.3.32767]",
"runtime.aot.System.Globalization.Calendars": "(,4.3.32767]",
"runtime.aot.System.IO": "(,4.3.32767]",
"runtime.aot.System.Reflection": "(,4.3.32767]",
"runtime.aot.System.Reflection.Extensions": "(,4.3.32767]",
"runtime.aot.System.Reflection.Primitives": "(,4.3.32767]",
"runtime.aot.System.Resources.ResourceManager": "(,4.3.32767]",
"runtime.aot.System.Runtime": "(,4.3.32767]",
"runtime.aot.System.Runtime.Handles": "(,4.3.32767]",
"runtime.aot.System.Runtime.InteropServices": "(,4.3.32767]",
"runtime.aot.System.Text.Encoding": "(,4.3.32767]",
"runtime.aot.System.Text.Encoding.Extensions": "(,4.3.32767]",
"runtime.aot.System.Threading.Tasks": "(,4.3.32767]",
"runtime.aot.System.Threading.Timer": "(,4.3.32767]",
"runtime.debian.8-x64.runtime.native.System": "(,4.3.32767]",
"runtime.debian.8-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.debian.8-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.debian.8-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.debian.8-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]",
"runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]",
"runtime.debian.9-x64.runtime.native.System": "(,4.3.32767]",
"runtime.debian.9-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.debian.9-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.debian.9-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.fedora.23-x64.runtime.native.System": "(,4.3.32767]",
"runtime.fedora.23-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.fedora.23-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.fedora.23-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.fedora.23-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]",
"runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]",
"runtime.fedora.24-x64.runtime.native.System": "(,4.3.32767]",
"runtime.fedora.24-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.fedora.24-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.fedora.24-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.fedora.24-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]",
"runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]",
"runtime.fedora.27-x64.runtime.native.System": "(,4.3.32767]",
"runtime.fedora.27-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.fedora.27-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.fedora.27-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.fedora.28-x64.runtime.native.System": "(,4.3.32767]",
"runtime.fedora.28-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.fedora.28-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.fedora.28-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.opensuse.13.2-x64.runtime.native.System": "(,4.3.32767]",
"runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.opensuse.13.2-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.opensuse.13.2-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]",
"runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]",
"runtime.opensuse.42.1-x64.runtime.native.System": "(,4.3.32767]",
"runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.opensuse.42.1-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.opensuse.42.1-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]",
"runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]",
"runtime.opensuse.42.3-x64.runtime.native.System": "(,4.3.32767]",
"runtime.opensuse.42.3-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.opensuse.42.3-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.opensuse.42.3-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.osx.10.10-x64.runtime.native.System": "(,4.3.32767]",
"runtime.osx.10.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.osx.10.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.osx.10.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]",
"runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "(,4.3.32767]",
"runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]",
"runtime.rhel.7-x64.runtime.native.System": "(,4.3.32767]",
"runtime.rhel.7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.rhel.7-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.rhel.7-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.rhel.7-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]",
"runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]",
"runtime.ubuntu.14.04-x64.runtime.native.System": "(,4.3.32767]",
"runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]",
"runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]",
"runtime.ubuntu.16.04-x64.runtime.native.System": "(,4.3.32767]",
"runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]",
"runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]",
"runtime.ubuntu.16.10-x64.runtime.native.System": "(,4.3.32767]",
"runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography": "(,4.3.32767]",
"runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "(,4.3.32767]",
"runtime.ubuntu.18.04-x64.runtime.native.System": "(,4.3.32767]",
"runtime.ubuntu.18.04-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.ubuntu.18.04-x64.runtime.native.System.Net.Http": "(,4.3.32767]",
"runtime.ubuntu.18.04-x64.runtime.native.System.Net.Security": "(,4.3.32767]",
"runtime.unix.Microsoft.Win32.Primitives": "(,4.3.32767]",
"runtime.unix.System.Console": "(,4.3.32767]",
"runtime.unix.System.Diagnostics.Debug": "(,4.3.32767]",
"runtime.unix.System.IO.FileSystem": "(,4.3.32767]",
"runtime.unix.System.Net.Primitives": "(,4.3.32767]",
"runtime.unix.System.Net.Sockets": "(,4.3.32767]",
"runtime.unix.System.Private.Uri": "(,4.3.32767]",
"runtime.unix.System.Runtime.Extensions": "(,4.3.32767]",
"runtime.win.Microsoft.Win32.Primitives": "(,4.3.32767]",
"runtime.win.System.Console": "(,4.3.32767]",
"runtime.win.System.Diagnostics.Debug": "(,4.3.32767]",
"runtime.win.System.IO.FileSystem": "(,4.3.32767]",
"runtime.win.System.Net.Primitives": "(,4.3.32767]",
"runtime.win.System.Net.Sockets": "(,4.3.32767]",
"runtime.win.System.Runtime.Extensions": "(,4.3.32767]",
"runtime.win10-arm-aot.runtime.native.System.IO.Compression": "(,4.0.32767]",
"runtime.win10-arm64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.win10-x64-aot.runtime.native.System.IO.Compression": "(,4.0.32767]",
"runtime.win10-x86-aot.runtime.native.System.IO.Compression": "(,4.0.32767]",
"runtime.win7-x64.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.win7-x86.runtime.native.System.IO.Compression": "(,4.3.32767]",
"runtime.win7.System.Private.Uri": "(,4.3.32767]",
"runtime.win8-arm.runtime.native.System.IO.Compression": "(,4.3.32767]",
"System.AppContext": "(,4.3.32767]",
"System.Buffers": "(,5.0.32767]",
"System.Collections": "(,4.3.32767]",
"System.Collections.Concurrent": "(,4.3.32767]",
"System.Collections.Immutable": "(,10.0.0-rc.1.25451.107]",
"System.Collections.NonGeneric": "(,4.3.32767]",
"System.Collections.Specialized": "(,4.3.32767]",
"System.ComponentModel": "(,4.3.32767]",
"System.ComponentModel.Annotations": "(,4.3.32767]",
"System.ComponentModel.EventBasedAsync": "(,4.3.32767]",
"System.ComponentModel.Primitives": "(,4.3.32767]",
"System.ComponentModel.TypeConverter": "(,4.3.32767]",
"System.Console": "(,4.3.32767]",
"System.Data.Common": "(,4.3.32767]",
"System.Data.DataSetExtensions": "(,4.4.32767]",
"System.Diagnostics.Contracts": "(,4.3.32767]",
"System.Diagnostics.Debug": "(,4.3.32767]",
"System.Diagnostics.DiagnosticSource": "(,10.0.0-rc.1.25451.107]",
"System.Diagnostics.FileVersionInfo": "(,4.3.32767]",
"System.Diagnostics.Process": "(,4.3.32767]",
"System.Diagnostics.StackTrace": "(,4.3.32767]",
"System.Diagnostics.TextWriterTraceListener": "(,4.3.32767]",
"System.Diagnostics.Tools": "(,4.3.32767]",
"System.Diagnostics.TraceSource": "(,4.3.32767]",
"System.Diagnostics.Tracing": "(,4.3.32767]",
"System.Drawing.Primitives": "(,4.3.32767]",
"System.Dynamic.Runtime": "(,4.3.32767]",
"System.Formats.Asn1": "(,10.0.0-rc.1.25451.107]",
"System.Formats.Tar": "(,10.0.0-rc.1.25451.107]",
"System.Globalization": "(,4.3.32767]",
"System.Globalization.Calendars": "(,4.3.32767]",
"System.Globalization.Extensions": "(,4.3.32767]",
"System.IO": "(,4.3.32767]",
"System.IO.Compression": "(,4.3.32767]",
"System.IO.Compression.ZipFile": "(,4.3.32767]",
"System.IO.FileSystem": "(,4.3.32767]",
"System.IO.FileSystem.AccessControl": "(,4.4.32767]",
"System.IO.FileSystem.DriveInfo": "(,4.3.32767]",
"System.IO.FileSystem.Primitives": "(,4.3.32767]",
"System.IO.FileSystem.Watcher": "(,4.3.32767]",
"System.IO.IsolatedStorage": "(,4.3.32767]",
"System.IO.MemoryMappedFiles": "(,4.3.32767]",
"System.IO.Pipelines": "(,10.0.0-rc.1.25451.107]",
"System.IO.Pipes": "(,4.3.32767]",
"System.IO.Pipes.AccessControl": "(,5.0.32767]",
"System.IO.UnmanagedMemoryStream": "(,4.3.32767]",
"System.Linq": "(,4.3.32767]",
"System.Linq.AsyncEnumerable": "(,10.0.0-rc.1.25451.107]",
"System.Linq.Expressions": "(,4.3.32767]",
"System.Linq.Parallel": "(,4.3.32767]",
"System.Linq.Queryable": "(,4.3.32767]",
"System.Memory": "(,5.0.32767]",
"System.Net.Http": "(,4.3.32767]",
"System.Net.Http.Json": "(,10.0.0-rc.1.25451.107]",
"System.Net.NameResolution": "(,4.3.32767]",
"System.Net.NetworkInformation": "(,4.3.32767]",
"System.Net.Ping": "(,4.3.32767]",
"System.Net.Primitives": "(,4.3.32767]",
"System.Net.Requests": "(,4.3.32767]",
"System.Net.Security": "(,4.3.32767]",
"System.Net.ServerSentEvents": "(,10.0.0-rc.1.25451.107]",
"System.Net.Sockets": "(,4.3.32767]",
"System.Net.WebHeaderCollection": "(,4.3.32767]",
"System.Net.WebSockets": "(,4.3.32767]",
"System.Net.WebSockets.Client": "(,4.3.32767]",
"System.Numerics.Vectors": "(,5.0.32767]",
"System.ObjectModel": "(,4.3.32767]",
"System.Private.DataContractSerialization": "(,4.3.32767]",
"System.Private.Uri": "(,4.3.32767]",
"System.Reflection": "(,4.3.32767]",
"System.Reflection.DispatchProxy": "(,6.0.32767]",
"System.Reflection.Emit": "(,4.7.32767]",
"System.Reflection.Emit.ILGeneration": "(,4.7.32767]",
"System.Reflection.Emit.Lightweight": "(,4.7.32767]",
"System.Reflection.Extensions": "(,4.3.32767]",
"System.Reflection.Metadata": "(,10.0.0-rc.1.25451.107]",
"System.Reflection.Primitives": "(,4.3.32767]",
"System.Reflection.TypeExtensions": "(,4.3.32767]",
"System.Resources.Reader": "(,4.3.32767]",
"System.Resources.ResourceManager": "(,4.3.32767]",
"System.Resources.Writer": "(,4.3.32767]",
"System.Runtime": "(,4.3.32767]",
"System.Runtime.CompilerServices.Unsafe": "(,7.0.32767]",
"System.Runtime.CompilerServices.VisualC": "(,4.3.32767]",
"System.Runtime.Extensions": "(,4.3.32767]",
"System.Runtime.Handles": "(,4.3.32767]",
"System.Runtime.InteropServices": "(,4.3.32767]",
"System.Runtime.InteropServices.RuntimeInformation": "(,4.3.32767]",
"System.Runtime.Loader": "(,4.3.32767]",
"System.Runtime.Numerics": "(,4.3.32767]",
"System.Runtime.Serialization.Formatters": "(,4.3.32767]",
"System.Runtime.Serialization.Json": "(,4.3.32767]",
"System.Runtime.Serialization.Primitives": "(,4.3.32767]",
"System.Runtime.Serialization.Xml": "(,4.3.32767]",
"System.Security.AccessControl": "(,6.0.32767]",
"System.Security.Claims": "(,4.3.32767]",
"System.Security.Cryptography.Algorithms": "(,4.3.32767]",
"System.Security.Cryptography.Cng": "(,5.0.32767]",
"System.Security.Cryptography.Csp": "(,4.3.32767]",
"System.Security.Cryptography.Encoding": "(,4.3.32767]",
"System.Security.Cryptography.OpenSsl": "(,5.0.32767]",
"System.Security.Cryptography.Primitives": "(,4.3.32767]",
"System.Security.Cryptography.X509Certificates": "(,4.3.32767]",
"System.Security.Principal": "(,4.3.32767]",
"System.Security.Principal.Windows": "(,5.0.32767]",
"System.Security.SecureString": "(,4.3.32767]",
"System.Text.Encoding": "(,4.3.32767]",
"System.Text.Encoding.CodePages": "(,10.0.0-rc.1.25451.107]",
"System.Text.Encoding.Extensions": "(,4.3.32767]",
"System.Text.Encodings.Web": "(,10.0.0-rc.1.25451.107]",
"System.Text.Json": "(,10.0.0-rc.1.25451.107]",
"System.Text.RegularExpressions": "(,4.3.32767]",
"System.Threading": "(,4.3.32767]",
"System.Threading.Channels": "(,10.0.0-rc.1.25451.107]",
"System.Threading.Overlapped": "(,4.3.32767]",
"System.Threading.Tasks": "(,4.3.32767]",
"System.Threading.Tasks.Dataflow": "(,10.0.0-rc.1.25451.107]",
"System.Threading.Tasks.Extensions": "(,5.0.32767]",
"System.Threading.Tasks.Parallel": "(,4.3.32767]",
"System.Threading.Thread": "(,4.3.32767]",
"System.Threading.ThreadPool": "(,4.3.32767]",
"System.Threading.Timer": "(,4.3.32767]",
"System.ValueTuple": "(,4.5.32767]",
"System.Xml.ReaderWriter": "(,4.3.32767]",
"System.Xml.XDocument": "(,4.3.32767]",
"System.Xml.XmlDocument": "(,4.3.32767]",
"System.Xml.XmlSerializer": "(,4.3.32767]",
"System.Xml.XPath": "(,4.3.32767]",
"System.Xml.XPath.XDocument": "(,5.0.32767]"
}
}
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@@ -0,0 +1,28 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"DefaultConnection": "Data Source=FutureMail.db"
},
"JwtSettings": {
"SecretKey": "FutureMailSecretKey2024!@#LongerKeyForHMACSHA256",
"Issuer": "FutureMailAPI",
"Audience": "FutureMailClient",
"ExpirationInMinutes": 1440
},
"Jwt": {
"Key": "FutureMailSecretKey2024!@#LongerKeyForHMACSHA256",
"Issuer": "FutureMailAPI",
"Audience": "FutureMailClient"
},
"FileUpload": {
"UploadPath": "uploads",
"BaseUrl": "http://localhost:5054/uploads",
"MaxFileSize": 104857600
}
}

Some files were not shown because too many files have changed in this diff Show More