Skip to content

Commit 511bf25

Browse files
committed
refactor(auth, character): 사용자 인증 및 캐릭터 관리 개선
- 캐릭터 삭제 엔드포인트에 소유권 검증 추가 - OAuth2 인증 과정의 토큰 처리 안정성 개선 - 사용자 인증 로직 리팩토링 및 불필요한 로깅 제거 - 요청별 HTTP 헤더 관리로 동시성 문제 해결 - 사용자 인증 및 캐릭터 관리의 보안성 강화
1 parent cb84fbb commit 511bf25

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+647
-111
lines changed

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,12 @@ Thumbs.db
114114
.DS_Store
115115
Desktop.ini
116116

117+
# Shell configuration files
118+
.zshrc
119+
.bashrc
120+
.bash_profile
121+
.profile
122+
117123
# Crash dumps
118124
*.dmp
119125

ProjectVG.Api/Controllers/CharacterController.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,16 @@ public async Task<ActionResult<CharacterResponse>> UpdateCharacterToSystemPrompt
8888
}
8989

9090
[HttpDelete("{id}")]
91+
[JwtAuthentication]
9192
public async Task<ActionResult> DeleteCharacter(Guid id)
9293
{
93-
await _characterService.DeleteCharacterAsync(id);
94+
var userId = GetCurrentUserId();
95+
if (!userId.HasValue)
96+
{
97+
return Unauthorized();
98+
}
99+
100+
await _characterService.DeleteCharacterAsync(id, userId.Value);
94101
return NoContent();
95102
}
96103

ProjectVG.Api/Controllers/ChatController.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using Microsoft.AspNetCore.Mvc;
22
using ProjectVG.Application.Models.Chat;
3-
using ProjectVG.Application.Models.API.Request;
3+
using ProjectVG.Api.Models.Chat.Request;
44
using ProjectVG.Application.Services.Chat;
55
using System.Security.Claims;
66

ProjectVG.Api/Controllers/ConversationController.cs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
using ProjectVG.Api.Models.Conversation.Request;
33
using ProjectVG.Api.Models.Conversation.Response;
44
using ProjectVG.Application.Services.Conversation;
5+
using ProjectVG.Api.Filters;
6+
using ProjectVG.Common.Exceptions;
7+
using ProjectVG.Common.Constants;
58
using System.Security.Claims;
69

710
namespace ProjectVG.Api.Controllers
@@ -137,14 +140,8 @@ public async Task<IActionResult> GetConversationBySessionId(string conversationI
137140
throw new ValidationException(ErrorCode.AUTHENTICATION_FAILED);
138141
}
139142

140-
// 대화 세션 조회
141-
var messages = await _conversationService.GetByConversationIdAsync(conversationId);
142-
143-
// 사용자 권한 확인 (첫 번째 메시지의 사용자 ID 확인)
144-
if (messages.Any() && !messages.Any(m => m.UserId == userGuid))
145-
{
146-
return Forbid();
147-
}
143+
// 대화 세션 조회 (DB 레벨에서 userId로 필터링)
144+
var messages = await _conversationService.GetByConversationIdAsync(conversationId, userGuid);
148145

149146
// 응답 매핑
150147
var response = messages.Select(m => new ConversationHistoryResponse

ProjectVG.Api/Controllers/OAuthController.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,14 +96,12 @@ public async Task<IActionResult> GetOAuth2Token([FromQuery] string state)
9696
throw new ValidationException(ErrorCode.REQUIRED_PARAMETER_MISSING);
9797
}
9898

99-
var tokenData = await _oauth2Service.GetTokenDataAsync(state);
99+
var tokenData = await _oauth2Service.ConsumeTokenDataAsync(state);
100100
if (tokenData == null)
101101
{
102102
throw new ValidationException(ErrorCode.OAUTH2_REQUEST_NOT_FOUND);
103103
}
104104

105-
await _oauth2Service.DeleteTokenDataAsync(state);
106-
107105
Response.Headers.Append("X-Access-Credit", tokenData.AccessToken);
108106
Response.Headers.Append("X-Refresh-Credit", tokenData.RefreshToken);
109107
Response.Headers.Append("X-Expires-In", tokenData.ExpiresIn.ToString());

ProjectVG.Api/Models/Character/Request/CreateCharacterWithFieldsRequest.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ public record CreateCharacterWithFieldsRequest
2424
public string ImageUrl { get; init; } = string.Empty;
2525

2626
[JsonPropertyName("voice_id")]
27-
[StringLength(100, ErrorMessage = "음성 ID는 최대 100자까지 입력 가능합니다.")]
27+
[Required(ErrorMessage = "음성 ID는 필수입니다.")]
28+
[StringLength(100, MinimumLength = 1, ErrorMessage = "음성 ID는 1-100자 사이여야 합니다.")]
2829
public string VoiceId { get; init; } = string.Empty;
2930

3031
[JsonPropertyName("individual_config")]

ProjectVG.Api/Models/Chat/Request/ChatRequest.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using System.Text.Json.Serialization;
2-
using ProjectVG.Application.Models.Chat;
32

4-
namespace ProjectVG.Application.Models.API.Request
3+
namespace ProjectVG.Api.Models.Chat.Request
54
{
65
public record ChatRequest
76
{

ProjectVG.Application/Models/Auth/OAuth2Models.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using System.Text.Json.Serialization;
21

32
namespace ProjectVG.Application.Models.Auth
43
{

ProjectVG.Application/Models/Chat/ChatProcessContext.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using Azure.Core;
21
using ProjectVG.Application.Models.Character;
32
using ProjectVG.Domain.Entities.ConversationHistorys;
43

@@ -39,11 +38,13 @@ public ChatProcessContext(
3938
IEnumerable<ConversationHistory> conversationHistory,
4039
IEnumerable<string> memoryContext)
4140
{
41+
RequestId = command.Id;
4242
UserId = command.UserId;
4343
CharacterId = command.CharacterId;
4444
UserMessage = command.UserPrompt;
4545
MemoryStore = command.UserId.ToString();
4646
UseTTS = command.UseTTS;
47+
UserRequestAt = command.UserRequestAt;
4748

4849
Character = character;
4950
ConversationHistory = conversationHistory;

ProjectVG.Application/Services/Auth/AuthService.cs

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,7 @@
1-
using Microsoft.Extensions.Logging;
2-
using ProjectVG.Application.Models.Auth;
31
using ProjectVG.Application.Models.User;
42
using ProjectVG.Application.Services.Credit;
53
using ProjectVG.Application.Services.Users;
6-
using ProjectVG.Common.Constants;
7-
using ProjectVG.Common.Exceptions;
8-
using ProjectVG.Domain.Entities.Users;
94
using ProjectVG.Infrastructure.Auth;
10-
using System;
115

126
namespace ProjectVG.Application.Services.Auth
137
{
@@ -50,9 +44,6 @@ public async Task<AuthResult> GuestLoginAsync(string guestId)
5044
user = await _userService.CreateUserAsync(createCommand);
5145
_logger.LogInformation("새 게스트 사용자 생성됨: UserId={UserId}, GuestId={GuestId}", user.Id, guestId);
5246
}
53-
else {
54-
_logger.LogDebug("기존 게스트 사용자 로그인: UserId={UserId}, GuestId={GuestId}", user.Id, guestId);
55-
}
5647

5748
return await FinalizeLoginAsync(user, "guest");
5849
}
@@ -64,15 +55,10 @@ private async Task<AuthResult> FinalizeLoginAsync(UserDto user, string provider)
6455
if (tokenGranted) {
6556
_logger.LogInformation("사용자 {UserId}에게 최초 크레딧 지급 완료", user.Id);
6657
}
67-
else {
68-
_logger.LogDebug("사용자 {UserId}는 이미 크레딧이 지급되었거나 지급 실패", user.Id);
69-
}
7058

7159
// 최종 JWT 토큰 발급
7260
var tokens = await _tokenService.GenerateTokensAsync(user.Id);
7361

74-
_logger.LogDebug("사용자 {UserId} 로그인 완료 (Provider={Provider})", user.Id, provider);
75-
7662
return new AuthResult {
7763
Tokens = tokens,
7864
User = user
@@ -82,12 +68,12 @@ private async Task<AuthResult> FinalizeLoginAsync(UserDto user, string provider)
8268
public async Task<AuthResult> RefreshAccessTokenAsync(string? refreshToken)
8369
{
8470
if (string.IsNullOrEmpty(refreshToken)) {
85-
throw new ValidationException(ErrorCode.TOKEN_MISSING, "리프레시 토큰이 필요합니다");
71+
throw new ValidationException(ErrorCode.TOKEN_MISSING);
8672
}
8773

8874
var tokens = await _tokenService.RefreshAccessTokenAsync(refreshToken);
8975
if (tokens == null) {
90-
throw new ValidationException(ErrorCode.TOKEN_REFRESH_FAILED, "유효하지 않거나 만료된 리프레시 토큰입니다");
76+
throw new ValidationException(ErrorCode.TOKEN_REFRESH_FAILED);
9177
}
9278

9379
var userId = await _tokenService.GetUserIdFromTokenAsync(refreshToken);
@@ -102,16 +88,12 @@ public async Task<AuthResult> RefreshAccessTokenAsync(string? refreshToken)
10288
public async Task<bool> LogoutAsync(string? refreshToken)
10389
{
10490
if (string.IsNullOrEmpty(refreshToken)) {
105-
throw new ValidationException(ErrorCode.TOKEN_MISSING, "리프레시 토큰이 필요합니다");
91+
throw new ValidationException(ErrorCode.TOKEN_MISSING);
10692
}
10793

10894
var revoked = await _tokenService.RevokeRefreshTokenAsync(refreshToken);
10995
if (revoked) {
11096
var userId = await _tokenService.GetUserIdFromTokenAsync(refreshToken);
111-
_logger.LogInformation("사용자 {UserId} 로그아웃 성공", userId);
112-
}
113-
else {
114-
_logger.LogWarning("리프레시 토큰 만료 또는 무효화 실패: {RefreshToken}", refreshToken);
11597
}
11698
return revoked;
11799
}

0 commit comments

Comments
 (0)