| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144 |
- using SharedKernel.Results;
- using Application.Abstractions.Data;
- using Application.Abstractions.Cache;
- using Application.Abstractions.Messaging.Email;
- using Application.Helpers;
- using Domain.Entities.EmailVerification;
- using Domain.Entities.EmailVerification.ValueObject;
- using MediatR;
- using Microsoft.EntityFrameworkCore;
- namespace Application.Features.Api.Auth.Register;
- internal sealed class Handler(
- IAppDbContext db,
- ICacheService cache,
- IMailService mailService
- ) : IRequestHandler<Command, Result<RegisterResponse>> {
- public async Task<Result<RegisterResponse>> Handle(Command request, CancellationToken ct)
- {
- // 유효성 검사
- if (string.IsNullOrWhiteSpace(request.Email))
- {
- return Result.Failure<RegisterResponse>(Error.Problem("Auth.EmailRequired", "이메일은 필수입니다."));
- }
- if (string.IsNullOrWhiteSpace(request.Password))
- {
- return Result.Failure<RegisterResponse>(Error.Problem("Auth.PasswordRequired", "비밀번호는 필수입니다."));
- }
- // Config 로드
- var accountConfig = await AccountConfigLoader.GetAccountConfigAsync(cache, db, ct);
- // 회원가입 차단 확인
- if (accountConfig.IsRegisterBlock)
- {
- return Result.Failure<RegisterResponse>(Error.Problem("Auth.RegisterBlocked", "현재 회원가입이 차단되어 있습니다."));
- }
- // 비밀번호 복잡도 검증
- var passwordResult = PasswordPolicyValidator.Validate(request.Password, accountConfig);
- if (!passwordResult.IsSuccess)
- {
- return Result.Failure<RegisterResponse>(passwordResult.Error);
- }
- // 이메일 중복 체크
- var email = request.Email.Trim().ToLower();
- var exists = await db.Member.AnyAsync(m => m.Email == email, ct);
- if (exists)
- {
- return Result.Failure<RegisterResponse>(Error.Conflict("Auth.EmailExists", "이미 사용 중인 이메일입니다."));
- }
- // 금지 이메일 체크
- if (!string.IsNullOrWhiteSpace(accountConfig.DeniedEmailList))
- {
- var deniedEmails = accountConfig.DeniedEmailList
- .Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
- .Select(e => e.ToLower());
- foreach (var denied in deniedEmails)
- {
- // 도메인 매칭 (@domain.com) 또는 전체 이메일 매칭
- if (denied.StartsWith('@') && email.EndsWith(denied))
- {
- return Result.Failure<RegisterResponse>(Error.Problem("Auth.DeniedEmail", "사용할 수 없는 이메일입니다."));
- }
- else if (email == denied)
- {
- return Result.Failure<RegisterResponse>(Error.Problem("Auth.DeniedEmail", "사용할 수 없는 이메일입니다."));
- }
- }
- }
- // 금지 별명 체크
- if (!string.IsNullOrWhiteSpace(request.Name) && !string.IsNullOrWhiteSpace(accountConfig.DeniedNameList))
- {
- var name = request.Name.Trim().ToLower();
- var deniedNames = accountConfig.DeniedNameList
- .Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
- .Select(n => n.ToLower());
- if (deniedNames.Contains(name))
- {
- return Result.Failure<RegisterResponse>(Error.Problem("Auth.DeniedName", "사용할 수 없는 별명입니다."));
- }
- }
- // Member 생성 (비밀번호 해싱 포함)
- var member = Domain.Entities.Members.Member.Create(email, request.Password);
- await db.Member.AddAsync(member, ct);
- await db.SaveChangesAsync(ct);
- // Member ID 확보 후 연관 엔티티 생성
- await db.MemberApprove.AddAsync(
- Domain.Entities.Members.MemberApprove.Create(member.ID), ct
- );
- await db.MemberStats.AddAsync(
- Domain.Entities.Members.MemberStats.Create(member.ID), ct
- );
- await db.Wallet.AddAsync(
- Domain.Entities.Wallets.Wallet.Create(member.ID), ct
- );
- await db.SaveChangesAsync(ct);
- // 이메일 인증이 필요한 경우 인증번호 발송
- if (accountConfig.IsRegisterEmailAuth)
- {
- // 기존 미인증 코드 삭제
- var existing = await db.EmailVerifyNumber
- .Where(e => e.Email == email && e.Type == VerificationType.Registration && !e.IsVerified)
- .ToListAsync(ct);
- db.EmailVerifyNumber.RemoveRange(existing);
- // 6자리 랜덤 숫자 코드 생성
- var code = Random.Shared.Next(100000, 999999).ToString();
- var verifyNumber = EmailVerifyNumber.Create(
- VerificationType.Registration,
- email,
- code,
- DateTime.UtcNow.AddMinutes(5)
- );
- await db.EmailVerifyNumber.AddAsync(verifyNumber, ct);
- await db.SaveChangesAsync(ct);
- // 인증번호 이메일 발송
- await mailService.SendAsync(new SendData(
- email,
- "[bitforum] 회원가입 이메일 인증번호",
- $"<p>회원가입 이메일 인증번호입니다.</p><p><strong>{code}</strong></p><p>이 코드는 5분간 유효합니다.</p>"
- ), ct);
- }
- return Result.Success(new RegisterResponse(member.ID, accountConfig.IsRegisterEmailAuth));
- }
- }
|