using Application.Abstractions.Authentication; using Application.Abstractions.Messaging; using SharedKernel; using SharedKernel.Results; using Microsoft.Extensions.Options; namespace Application.Features.Api.Auth.SocialAuthorizeUrl; internal sealed class Handler( IEnumerable socialProviders, IOptions options ) : IQueryHandler> { private readonly AppSettings.OAuthSection _oauth = options.Value.OAuth; public Task> Handle(Query request, CancellationToken ct) { var providerName = request.Provider?.Trim().ToLowerInvariant() switch { "naver" => "Naver", "kakao" => "Kakao", _ => null }; if (providerName is null) { return Task.FromResult(Result.Failure(Error.Problem("Auth.UnsupportedProvider", "지원하지 않는 소셜 로그인입니다."))); } // 네이버는 스펙상 필수, 카카오도 CSRF 방어 위해 항상 요구 (d5 §③) if (string.IsNullOrWhiteSpace(request.State)) { return Task.FromResult(Result.Failure(Error.Problem("Auth.StateRequired", "state 값이 필요합니다."))); } var oauthConfig = providerName == "Naver" ? _oauth.Naver : _oauth.Kakao; if (string.IsNullOrWhiteSpace(oauthConfig.ClientId)) { return Task.FromResult(Result.Failure(Error.Problem("Auth.SocialNotConfigured", $"{providerName} 로그인이 설정되지 않았습니다."))); } var redirectUri = string.IsNullOrWhiteSpace(request.RedirectUri) ? oauthConfig.RedirectUri : request.RedirectUri; if (string.IsNullOrWhiteSpace(redirectUri)) { return Task.FromResult(Result.Failure(Error.Problem("Auth.RedirectUriRequired", "Redirect URI 가 필요합니다."))); } var provider = socialProviders.FirstOrDefault(c => c.Provider == providerName); if (provider is null) { return Task.FromResult(Result.Failure(Error.Problem("Auth.UnsupportedProvider", "지원하지 않는 소셜 로그인입니다."))); } var url = provider.GetAuthorizationUrl(oauthConfig.ClientId, redirectUri, request.State); return Task.FromResult(Result.Success(new Response(providerName, url))); } }