修改接口
This commit is contained in:
120
API控制器重构总结.md
Normal file
120
API控制器重构总结.md
Normal 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服务已成功启动并运行,没有出现编译错误,说明所有修改都是正确的。
|
||||
49
ApiTest.cs
49
ApiTest.cs
@@ -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
0
FutureMail.db
Normal 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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
54
FutureMailAPI/Controllers/BaseController.cs
Normal file
54
FutureMailAPI/Controllers/BaseController.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 = "服务器内部错误" });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
77
FutureMailAPI/Controllers/TempFixController.cs
Normal file
77
FutureMailAPI/Controllers/TempFixController.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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小时过期
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
138
FutureMailAPI/Filters/OAuthAuthenticationFilter.cs
Normal file
138
FutureMailAPI/Filters/OAuthAuthenticationFilter.cs
Normal 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.
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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("未授权访问");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
38
FutureMailAPI/Models/OAuthClient.cs
Normal file
38
FutureMailAPI/Models/OAuthClient.cs
Normal 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>();
|
||||
}
|
||||
}
|
||||
@@ -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!;
|
||||
}
|
||||
}
|
||||
44
FutureMailAPI/Models/OAuthToken.cs
Normal file
44
FutureMailAPI/Models/OAuthToken.cs
Normal 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!;
|
||||
}
|
||||
}
|
||||
@@ -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>();
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -48,7 +48,7 @@ namespace FutureMailAPI.Services
|
||||
}
|
||||
|
||||
// 创建默认OAuth客户端
|
||||
var defaultClient = new OAuthClient
|
||||
var defaultClient = new Models.OAuthClient
|
||||
{
|
||||
ClientId = defaultClientId,
|
||||
ClientSecret = "futuremail_default_secret",
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
28
FutureMailAPI/TestDB.cs
Normal 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]}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}");
|
||||
}
|
||||
1343
FutureMailAPI/TestOAuthApp/bin/Debug/net10.0/FutureMailAPI.deps.json
Normal file
1343
FutureMailAPI/TestOAuthApp/bin/Debug/net10.0/FutureMailAPI.deps.json
Normal file
File diff suppressed because it is too large
Load Diff
BIN
FutureMailAPI/TestOAuthApp/bin/Debug/net10.0/FutureMailAPI.dll
Normal file
BIN
FutureMailAPI/TestOAuthApp/bin/Debug/net10.0/FutureMailAPI.dll
Normal file
Binary file not shown.
BIN
FutureMailAPI/TestOAuthApp/bin/Debug/net10.0/FutureMailAPI.exe
Normal file
BIN
FutureMailAPI/TestOAuthApp/bin/Debug/net10.0/FutureMailAPI.exe
Normal file
Binary file not shown.
BIN
FutureMailAPI/TestOAuthApp/bin/Debug/net10.0/FutureMailAPI.pdb
Normal file
BIN
FutureMailAPI/TestOAuthApp/bin/Debug/net10.0/FutureMailAPI.pdb
Normal file
Binary file not shown.
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"Version":1,"ManifestType":"Build","Endpoints":[]}
|
||||
@@ -0,0 +1 @@
|
||||
{"ContentRoots":["C:\\Users\\Administrator\\Desktop\\快乐转盘\\未来邮箱02APi\\FutureMailAPI\\wwwroot\\"],"Root":{"Children":null,"Asset":null,"Patterns":[{"ContentRootIndex":0,"Pattern":"**","Depth":0}]}}
|
||||
248
FutureMailAPI/TestOAuthApp/bin/Debug/net10.0/FutureMailAPI.xml
Normal file
248
FutureMailAPI/TestOAuthApp/bin/Debug/net10.0/FutureMailAPI.xml
Normal 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>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
FutureMailAPI/TestOAuthApp/bin/Debug/net10.0/MySqlConnector.dll
Normal file
BIN
FutureMailAPI/TestOAuthApp/bin/Debug/net10.0/MySqlConnector.dll
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
FutureMailAPI/TestOAuthApp/bin/Debug/net10.0/Quartz.dll
Normal file
BIN
FutureMailAPI/TestOAuthApp/bin/Debug/net10.0/Quartz.dll
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
FutureMailAPI/TestOAuthApp/bin/Debug/net10.0/TestOAuthApp.dll
Normal file
BIN
FutureMailAPI/TestOAuthApp/bin/Debug/net10.0/TestOAuthApp.dll
Normal file
Binary file not shown.
BIN
FutureMailAPI/TestOAuthApp/bin/Debug/net10.0/TestOAuthApp.exe
Normal file
BIN
FutureMailAPI/TestOAuthApp/bin/Debug/net10.0/TestOAuthApp.exe
Normal file
Binary file not shown.
BIN
FutureMailAPI/TestOAuthApp/bin/Debug/net10.0/TestOAuthApp.pdb
Normal file
BIN
FutureMailAPI/TestOAuthApp/bin/Debug/net10.0/TestOAuthApp.pdb
Normal file
Binary file not shown.
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user