Some checks failed
CI Pipeline / Code Quality & Typecheck (push) Waiting to run
CI Pipeline / Test Suite (macos-latest) (push) Blocked by required conditions
CI Pipeline / Test Suite (ubuntu-latest) (push) Blocked by required conditions
CI Pipeline / Test Suite (windows-latest) (push) Blocked by required conditions
CI Pipeline / Build Validation (admin) (push) Blocked by required conditions
CI Pipeline / Build Validation (desktop) (push) Blocked by required conditions
Deploy Landing Page / deploy (push) Blocked by required conditions
Deploy Landing Page / build (push) Waiting to run
Release & Packaging Pipeline / Build & Publish Admin Docker Image (push) Failing after 8s
Release & Code Signing CA Pipeline / build-and-sign-windows (push) Failing after 1m51s
Build macOS / Build & Package (macOS) (push) Failing after 4s
Build macOS / Build & Package (macOS)-1 (push) Failing after 5s
Release & Code Signing CA Pipeline / build-and-sign-macos (push) Failing after 3s
Release & Packaging Pipeline / Package macOS Desktop App (push) Failing after 4s
Release & Packaging Pipeline / Package Windows Desktop App (push) Failing after 2m28s
Release & Packaging Pipeline / Publish Official GitHub Release (push) Has been skipped
121 lines
4 KiB
C#
121 lines
4 KiB
C#
using System;
|
|
using System.IdentityModel.Tokens.Jwt;
|
|
using System.Security.Claims;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using D3ROVoice.Api.Data;
|
|
using D3ROVoice.Api.Dtos;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.Configuration;
|
|
using Microsoft.IdentityModel.Tokens;
|
|
|
|
namespace D3ROVoice.Api.Services;
|
|
|
|
public interface IAuthService
|
|
{
|
|
Task<AuthResponseDto> RegisterAsync(RegisterDto dto);
|
|
Task<AuthResponseDto> LoginAsync(LoginDto dto);
|
|
Task<UserInfoDto?> GetUserByEmailAsync(string email);
|
|
}
|
|
|
|
public class AuthService : IAuthService
|
|
{
|
|
private readonly AppDbContext _db;
|
|
private readonly IConfiguration _config;
|
|
|
|
public AuthService(AppDbContext db, IConfiguration config)
|
|
{
|
|
_db = db;
|
|
_config = config;
|
|
}
|
|
|
|
public async Task<AuthResponseDto> RegisterAsync(RegisterDto dto)
|
|
{
|
|
var existing = await _db.Users.FirstOrDefaultAsync(u => u.Email.ToLower() == dto.Email.ToLower());
|
|
if (existing != null)
|
|
{
|
|
throw new InvalidOperationException("이미 등록된 이메일 주소입니다.");
|
|
}
|
|
|
|
var isFirstUser = !await _db.Users.AnyAsync();
|
|
var user = new User
|
|
{
|
|
Email = dto.Email.Trim().ToLower(),
|
|
PasswordHash = HashPassword(dto.Password),
|
|
Role = isFirstUser ? "Admin" : "User",
|
|
CreatedAt = DateTime.UtcNow,
|
|
IsActive = true
|
|
};
|
|
|
|
_db.Users.Add(user);
|
|
await _db.SaveChangesAsync();
|
|
|
|
return GenerateToken(user);
|
|
}
|
|
|
|
public async Task<AuthResponseDto> LoginAsync(LoginDto dto)
|
|
{
|
|
var user = await _db.Users.FirstOrDefaultAsync(u => u.Email.ToLower() == dto.Email.ToLower());
|
|
if (user == null || !VerifyPassword(dto.Password, user.PasswordHash))
|
|
{
|
|
throw new UnauthorizedAccessException("이메일 또는 비밀번호가 올바르지 않습니다.");
|
|
}
|
|
|
|
if (!user.IsActive)
|
|
{
|
|
throw new UnauthorizedAccessException("비활성화된 계정입니다. 관리자에게 문의하세요.");
|
|
}
|
|
|
|
user.LastLoginAt = DateTime.UtcNow;
|
|
await _db.SaveChangesAsync();
|
|
|
|
return GenerateToken(user);
|
|
}
|
|
|
|
public async Task<UserInfoDto?> GetUserByEmailAsync(string email)
|
|
{
|
|
var user = await _db.Users.AsNoTracking().FirstOrDefaultAsync(u => u.Email.ToLower() == email.ToLower());
|
|
if (user == null) return null;
|
|
return new UserInfoDto(user.Id, user.Email, user.Role, user.CreatedAt, user.LastLoginAt, user.IsActive);
|
|
}
|
|
|
|
private AuthResponseDto GenerateToken(User user)
|
|
{
|
|
var secretKey = _config["Jwt:SecretKey"] ?? "D3ROVoice_Super_Secure_Secret_Key_2026_Key!";
|
|
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey));
|
|
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
|
|
|
|
var claims = new[]
|
|
{
|
|
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
|
|
new Claim(ClaimTypes.Email, user.Email),
|
|
new Claim(ClaimTypes.Role, user.Role)
|
|
};
|
|
|
|
var expiresAt = DateTime.UtcNow.AddDays(30);
|
|
|
|
var token = new JwtSecurityToken(
|
|
issuer: _config["Jwt:Issuer"] ?? "D3ROVoiceApi",
|
|
audience: _config["Jwt:Audience"] ?? "D3ROVoiceClient",
|
|
claims: claims,
|
|
expires: expiresAt,
|
|
signingCredentials: creds
|
|
);
|
|
|
|
var tokenHandler = new JwtSecurityTokenHandler();
|
|
return new AuthResponseDto(tokenHandler.WriteToken(token), user.Email, user.Role, expiresAt);
|
|
}
|
|
|
|
public static string HashPassword(string password)
|
|
{
|
|
using var sha256 = SHA256.Create();
|
|
var bytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(password + "D3RO_SALT_2026"));
|
|
return Convert.ToBase64String(bytes);
|
|
}
|
|
|
|
private static bool VerifyPassword(string password, string hash)
|
|
{
|
|
return HashPassword(password) == hash;
|
|
}
|
|
}
|