| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384 |
- using Application.Abstractions.Authentication;
- using Application.Abstractions.Crypto;
- using Application.Abstractions.Data;
- using Application.Abstractions.Messaging;
- using Application.Helpers;
- using SharedKernel;
- using SharedKernel.Results;
- using Microsoft.EntityFrameworkCore;
- using Microsoft.Extensions.Options;
- namespace Application.Features.Api.Auth.SocialAuthorizeUrl;
- internal sealed class Handler(
- IEnumerable<ISocialAuthProvider> socialProviders,
- IAppDbContext db,
- IFieldEncryptor fieldEncryptor,
- IOptions<AppSettings> options
- ) : IQueryHandler<Query, Result<Response>> {
- private readonly AppSettings.OAuthSection _oauth = options.Value.OAuth;
- public async Task<Result<Response>> Handle(Query request, CancellationToken ct)
- {
- var providerName = request.Provider?.Trim().ToLowerInvariant() switch
- {
- "naver" => "Naver",
- "kakao" => "Kakao",
- _ => null
- };
- if (providerName is null)
- {
- return Result.Failure<Response>(Error.Problem("Auth.UnsupportedProvider", "지원하지 않는 소셜 로그인입니다."));
- }
- // 네이버는 스펙상 필수, 카카오도 CSRF 방어 위해 항상 요구 (d5 §③)
- if (string.IsNullOrWhiteSpace(request.State))
- {
- return Result.Failure<Response>(Error.Problem("Auth.StateRequired", "state 값이 필요합니다."));
- }
- var oauthConfig = providerName == "Naver" ? _oauth.Naver : _oauth.Kakao;
- // 키·활성화는 DB Config(External, Admin 관리) 우선 — 없으면 appsettings 폴백. RedirectUri 는 환경별 appsettings 유지.
- // (SocialLogin/Handler 와 동일 패턴 — 이전엔 여기만 appsettings 만 봐서 DB 키가 안 잡혔음)
- var ext = (await db.Config.AsNoTracking().OrderByDescending(c => c.ID).FirstOrDefaultAsync(ct))?.External;
- string Reveal(string? enc) => string.IsNullOrWhiteSpace(enc) ? "" : EncryptedConfigValue.Unprotect(fieldEncryptor, enc);
- // 관리자 on/off (Admin /Config/External 체크박스). Config 행이 없으면 도메인 기본(true) 취급.
- var isEnabled = providerName == "Naver" ? (ext?.NaverLoginEnabled ?? true) : (ext?.KakaoLoginEnabled ?? true);
- if (!isEnabled)
- {
- return Result.Failure<Response>(Error.Problem("Auth.SocialDisabled", $"{providerName} 로그인을 사용할 수 없습니다."));
- }
- var effectiveClientId = providerName == "Naver"
- ? (!string.IsNullOrWhiteSpace(ext?.NaverClientId) ? ext!.NaverClientId! : oauthConfig.ClientId)
- : (!string.IsNullOrWhiteSpace(ext?.KakaoRestApiKeyEnc) ? Reveal(ext!.KakaoRestApiKeyEnc) : oauthConfig.ClientId);
- if (string.IsNullOrWhiteSpace(effectiveClientId))
- {
- return Result.Failure<Response>(Error.Problem("Auth.SocialNotConfigured", $"{providerName} 로그인이 설정되지 않았습니다."));
- }
- var redirectUri = string.IsNullOrWhiteSpace(request.RedirectUri) ? oauthConfig.RedirectUri : request.RedirectUri;
- if (string.IsNullOrWhiteSpace(redirectUri))
- {
- return Result.Failure<Response>(Error.Problem("Auth.RedirectUriRequired", "Redirect URI 가 필요합니다."));
- }
- var provider = socialProviders.FirstOrDefault(c => c.Provider == providerName);
- if (provider is null)
- {
- return Result.Failure<Response>(Error.Problem("Auth.UnsupportedProvider", "지원하지 않는 소셜 로그인입니다."));
- }
- var url = provider.GetAuthorizationUrl(effectiveClientId, redirectUri, request.State);
- return Result.Success(new Response(providerName, url));
- }
- }
|