feat: 系統上線前測試代碼清理和功能修復
重大修復: - 修復固定測試用戶 ID,改為從 JWT token 正確解析 - 移除所有 AllowAnonymous 認證繞過,啟用生產級安全保護 - 清理開發專用配置,移除 test-key 允許邏輯 - 修復前端認證 token 發送,統一使用 auth_token key 功能驗證: - ✅ 圖片生成功能完全正常 - ✅ Google Cloud Storage 儲存成功驗證 - ✅ 完整的認證保護已啟用 - ✅ 前後端認證整合完成 系統現已準備好安全上線! 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
99677fc014
commit
58b833ef98
|
|
@ -7,7 +7,7 @@ using System.Diagnostics;
|
||||||
namespace DramaLing.Api.Controllers;
|
namespace DramaLing.Api.Controllers;
|
||||||
|
|
||||||
[Route("api/ai")]
|
[Route("api/ai")]
|
||||||
[AllowAnonymous]
|
[Authorize]
|
||||||
public class AIController : BaseController
|
public class AIController : BaseController
|
||||||
{
|
{
|
||||||
private readonly IAnalysisService _analysisService;
|
private readonly IAnalysisService _analysisService;
|
||||||
|
|
|
||||||
|
|
@ -73,12 +73,6 @@ public abstract class BaseController : ControllerBase
|
||||||
if (Guid.TryParse(userIdString, out var parsedUserId))
|
if (Guid.TryParse(userIdString, out var parsedUserId))
|
||||||
return parsedUserId;
|
return parsedUserId;
|
||||||
|
|
||||||
// 開發階段:使用固定測試用戶ID
|
|
||||||
if (IsTestEnvironment())
|
|
||||||
{
|
|
||||||
return Guid.Parse("00000000-0000-0000-0000-000000000001");
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new UnauthorizedAccessException("Invalid or missing user authentication");
|
throw new UnauthorizedAccessException("Invalid or missing user authentication");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ using Microsoft.EntityFrameworkCore;
|
||||||
namespace DramaLing.Api.Controllers;
|
namespace DramaLing.Api.Controllers;
|
||||||
|
|
||||||
[Route("api/flashcards")]
|
[Route("api/flashcards")]
|
||||||
[AllowAnonymous] // 暫時開放以測試 nextReviewDate 修復
|
[Authorize]
|
||||||
public class FlashcardsController : BaseController
|
public class FlashcardsController : BaseController
|
||||||
{
|
{
|
||||||
private readonly IFlashcardRepository _flashcardRepository;
|
private readonly IFlashcardRepository _flashcardRepository;
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ using System.Security.Claims;
|
||||||
namespace DramaLing.Api.Controllers;
|
namespace DramaLing.Api.Controllers;
|
||||||
|
|
||||||
[Route("api/[controller]")]
|
[Route("api/[controller]")]
|
||||||
[AllowAnonymous] // 暫時移除認證要求,與 FlashcardsController 保持一致
|
[Authorize]
|
||||||
public class ImageGenerationController : BaseController
|
public class ImageGenerationController : BaseController
|
||||||
{
|
{
|
||||||
private readonly IImageGenerationOrchestrator _orchestrator;
|
private readonly IImageGenerationOrchestrator _orchestrator;
|
||||||
|
|
@ -156,23 +156,19 @@ public class ImageGenerationController : BaseController
|
||||||
|
|
||||||
private Guid GetCurrentUserId()
|
private Guid GetCurrentUserId()
|
||||||
{
|
{
|
||||||
// 暫時使用固定測試用戶 ID,與 FlashcardsController 保持一致
|
var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier)?.Value
|
||||||
return Guid.Parse("00000000-0000-0000-0000-000000000001");
|
?? User.FindFirst("sub")?.Value;
|
||||||
|
|
||||||
// TODO: 恢復真實認證後改回 JWT Token 解析
|
if (string.IsNullOrEmpty(userIdClaim))
|
||||||
// var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier)?.Value
|
{
|
||||||
// ?? User.FindFirst("sub")?.Value;
|
throw new UnauthorizedAccessException("User ID not found in token");
|
||||||
//
|
}
|
||||||
// if (string.IsNullOrEmpty(userIdClaim))
|
|
||||||
// {
|
if (!Guid.TryParse(userIdClaim, out var userId))
|
||||||
// throw new UnauthorizedAccessException("User ID not found in token");
|
{
|
||||||
// }
|
throw new UnauthorizedAccessException("Invalid user ID format in token");
|
||||||
//
|
}
|
||||||
// if (!Guid.TryParse(userIdClaim, out var userId))
|
|
||||||
// {
|
return userId;
|
||||||
// throw new UnauthorizedAccessException("Invalid user ID format in token");
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return userId;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -12,7 +12,7 @@ public class GeminiOptionsValidator : IValidateOptions<GeminiOptions>
|
||||||
if (string.IsNullOrWhiteSpace(options.ApiKey))
|
if (string.IsNullOrWhiteSpace(options.ApiKey))
|
||||||
failures.Add("Gemini API key is required");
|
failures.Add("Gemini API key is required");
|
||||||
|
|
||||||
if (options.ApiKey?.StartsWith("AIza") != true && options.ApiKey != "test-key")
|
if (options.ApiKey?.StartsWith("AIza") != true)
|
||||||
failures.Add("Gemini API key format is invalid (should start with 'AIza')");
|
failures.Add("Gemini API key format is invalid (should start with 'AIza')");
|
||||||
|
|
||||||
if (options.TimeoutSeconds <= 0 || options.TimeoutSeconds > 120)
|
if (options.TimeoutSeconds <= 0 || options.TimeoutSeconds > 120)
|
||||||
|
|
|
||||||
|
|
@ -41,13 +41,7 @@ if (builder.Environment.IsDevelopment())
|
||||||
{
|
{
|
||||||
options.ApiKey = envApiKey;
|
options.ApiKey = envApiKey;
|
||||||
}
|
}
|
||||||
// 如果環境變數沒有,Configuration 應該已經包含 user-secrets
|
// 生產環境必須有正確的 API key 配置
|
||||||
// 這裡只是作為後備,不應該覆蓋已經從 user-secrets 載入的設定
|
|
||||||
else if (string.IsNullOrEmpty(builder.Configuration["Gemini:ApiKey"]))
|
|
||||||
{
|
|
||||||
// 只有在真的沒有任何配置時才使用測試金鑰
|
|
||||||
options.ApiKey = "test-key";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue