using System.Text; using Asp.Versioning; using JwtRoleAuthentication.Services; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Options; using ServerCommon; using ServerCommon.UGQ.Models; using StackExchange.Redis; using Swashbuckle.AspNetCore.Annotations; using UGQApiServer.Auth; using UGQApiServer.Models; using UGQApiServer.Settings; using UGQDataAccess.Service; using UGQDatabase.Models; using ServerCore; using ServerBase; using Microsoft.AspNetCore.Authorization; using UGQDataAccess.Logs; using ServerCommon.BusinessLogDomain; namespace UGQApiServer.Controllers { [ApiVersion("1.0")] [Route("api/v{version:apiVersion}/[controller]")] [ApiController] public class AccountController : ControllerBase { AccountService _accountService; TokenService _tokenService; WebPortalTokenAuth _webPortalTokenAuth; DynamoDbClient _dynamoDbClient; IConnectionMultiplexer _redis; JWTSettings _jwtSettings; public AccountController( AccountService accountService, TokenService tokenService, WebPortalTokenAuth webPortalTokenAuth, DynamoDbClient dynamoDbClient, IConnectionMultiplexer redis, IOptions settings) { _accountService = accountService; _tokenService = tokenService; _webPortalTokenAuth = webPortalTokenAuth; _dynamoDbClient = dynamoDbClient; _redis = redis; _jwtSettings = settings.Value; } string? userGuidFromToken() { return User.Claims.FirstOrDefault(c => c.Type == "Id")?.Value; } // #if DEBUG /// /// 게임DB와 연동된 버전 개발용 로그인 /// /// /// 계정이 없으면 POST /api/v1/Admin/add-gamedb-account 로 테스트 계정을 만드십시오. /// [HttpPost] [Route("dev-login-v2")] [Produces("application/json")] [DevelopmentOnly] [SwaggerResponse(StatusCodes.Status200OK, Type = typeof(LoginResponse))] [SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))] public async Task loginDev([FromBody] DevLoginRequest request) { (Result result, UserByAccountIdResult? gameAccount) = await EntitySearchHelper.findUserByAccountId(_dynamoDbClient, request.LoginAccountId, ""); if (result.isFail()) return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqRequireAccount)); if (gameAccount == null) return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqServerException)); AccountEntity accountEntity = await _accountService.getOrInsertAccount( gameAccount.UserGuid, gameAccount.Nickname, request.LoginAccountId); var accessToken = _tokenService.CreateToken(gameAccount.UserGuid, gameAccount.Nickname); var refreshToken = _tokenService.GenerateRefreshToken(); DateTime refreshTokenExpryTime = DateTime.Now.AddDays(7); if (await _accountService.saveRefreshToken(accountEntity.UserGuid, refreshToken, refreshTokenExpryTime) == null) return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNullEntity)); { var expiry = TimeSpan.FromMinutes(_jwtSettings.TokenValidityInMinutes); await _redis.GetDatabase().StringSetAsync($"ugq_login:{accountEntity.UserGuid}", "online", expiry); } List business_logs = [ new UgqApiLoginBusinessLog(UgqApiLoginType.DevelopmentApi), ]; var log_action = new LogActionEx(LogActionType.UgqApiLogin); UgqApiBusinessLogger.collectLogs(log_action, accountEntity, business_logs); return Results.Ok(new LoginResponse { UserGuid = accountEntity.UserGuid, Nickname = accountEntity.Nickname, AccessToken = accessToken, RefreshToken = refreshToken, }); } // #endif /// /// 로그인 /// [HttpPost] [Route("login")] [Produces("application/json")] [SwaggerResponse(StatusCodes.Status200OK, Type = typeof(LoginResponse))] [SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))] public async Task login([FromBody] LoginRequest request) { WebPortalToken? webPortalToken = null; { Result result2 = AuthHelper.verifySsoAccountAuthJWT(request.WebPortalToken, _webPortalTokenAuth.WebPortalTokenSecret, out var account_id, out var account_type, out var access_token); if (result2.isFail()) return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqInvalidWebPortalToken)); webPortalToken = new WebPortalToken { AccountId = account_id, AccountType = account_type, AccessToken = access_token, }; Log.getLogger().info($"webPortalToken. AccountId: {webPortalToken.AccountId}, AccountType: {webPortalToken.AccountType}"); } if (webPortalToken == null) return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqInvalidWebPortalToken)); var errorCode = await _webPortalTokenAuth.mysqlAuth(webPortalToken); if (errorCode != ServerErrorCode.Success) return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqInvalidWebPortalToken)); (Result result, UserByAccountIdResult? gameAccount) = await EntitySearchHelper.findUserByAccountId(_dynamoDbClient, webPortalToken.AccountId, ""); if (result.isFail()) return Results.BadRequest(ApiResponseFactory.error(result.ErrorCode)); if (gameAccount == null) return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqServerException)); AccountEntity accountEntity = await _accountService.getOrInsertAccount( gameAccount.UserGuid, gameAccount.Nickname, webPortalToken.AccountId); var accessToken = _tokenService.CreateToken(gameAccount.UserGuid, gameAccount.Nickname); var refreshToken = _tokenService.GenerateRefreshToken(); DateTime refreshTokenExpryTime = DateTime.Now.AddDays(7); if (await _accountService.saveRefreshToken(accountEntity.UserGuid, refreshToken, refreshTokenExpryTime) == null) return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNullEntity)); { var expiry = TimeSpan.FromMinutes(_jwtSettings.TokenValidityInMinutes); await _redis.GetDatabase().StringSetAsync($"ugq_login:{accountEntity.UserGuid}", "online", expiry); } List business_logs = [ new UgqApiLoginBusinessLog(UgqApiLoginType.NormalApi), ]; var log_action = new LogActionEx(LogActionType.UgqApiLogin); UgqApiBusinessLogger.collectLogs(log_action, accountEntity, business_logs); return Results.Ok(new LoginResponse { UserGuid = accountEntity.UserGuid, Nickname = accountEntity.Nickname, AccessToken = accessToken, RefreshToken = refreshToken }); } /// /// img 로그인 /// [HttpPost] [Route("login-igm")] [Produces("application/json")] [SwaggerResponse(StatusCodes.Status200OK, Type = typeof(IgmLoginResponse))] [SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))] public async Task login_igm([FromBody] LoginRequest request) { WebPortalToken? webPortalToken = null; { Result result2 = AuthHelper.verifySsoAccountAuthJWT(request.WebPortalToken, _webPortalTokenAuth.WebPortalTokenSecret, out var account_id, out var account_type, out var access_token); if (result2.isFail()) return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqInvalidWebPortalToken)); webPortalToken = new WebPortalToken { AccountId = account_id, AccountType = account_type, AccessToken = access_token, }; Log.getLogger().info($"webPortalToken. igm - AccountId: {webPortalToken.AccountId}, AccountType: {webPortalToken.AccountType}"); } if (webPortalToken == null) return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqInvalidWebPortalToken)); var errorCode = await _webPortalTokenAuth.mysqlAuth_igm(webPortalToken); if (errorCode != ServerErrorCode.Success) return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqInvalidWebPortalToken)); (Result result, UserByAccountIdResult? gameAccount) = await EntitySearchHelper.findUserByAccountId(_dynamoDbClient, webPortalToken.AccountId, ""); if (result.isFail()) return Results.BadRequest(ApiResponseFactory.error(result.ErrorCode)); if (gameAccount == null) return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqServerException)); var accessToken = _tokenService.CreateToken(gameAccount.UserGuid, gameAccount.Nickname); List business_logs = [ new UgqApiLoginBusinessLog(UgqApiLoginType.NormalApi), ]; var log_action = new LogActionEx(LogActionType.igmApiLogin); UgqApiBusinessLogger.collectLogs(log_action, gameAccount.UserGuid, gameAccount.Nickname, business_logs); return Results.Ok(new IgmLoginResponse { UserGuid = gameAccount.UserGuid, Nickname = gameAccount.Nickname, AccessToken = accessToken, }); } /// /// 토큰 리프레쉬 /// [HttpPost] [Route("refresh")] [Produces("application/json")] [SwaggerResponse(StatusCodes.Status200OK, Type = typeof(RefreshTokenResponse))] [SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))] public async Task Refresh([FromBody] RefreshTokenRequest request) { var principal = _tokenService.GetPrincipalFromExpiredToken(request.AccessToken); var userGuid = principal.Claims.FirstOrDefault(c => c.Type == "Id")?.Value; if (userGuid == null) return Results.Unauthorized(); var accountEntity = await _accountService.getAccount(userGuid); if (accountEntity is null) return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNullEntity)); if (accountEntity.RefreshToken != request.RefreshToken || accountEntity.RefreshTokenExpiryTime <= DateTime.Now) return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqInvalidToken)); var accessToken = _tokenService.CreateToken(userGuid, accountEntity.Nickname); var refreshToken = _tokenService.GenerateRefreshToken(); if (await _accountService.saveRefreshToken(accountEntity.UserGuid, refreshToken, null) == null) return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNullEntity)); { var expiry = TimeSpan.FromMinutes(_jwtSettings.TokenValidityInMinutes); await _redis.GetDatabase().StringSetAsync($"ugq_login:{accountEntity.UserGuid}", "online", expiry); } return Results.Ok(new RefreshTokenResponse { AccessToken = accessToken, RefreshToken = refreshToken, }); } [Authorize] [HttpPost] [Route("logout")] [Produces("application/json")] [SwaggerResponse(StatusCodes.Status200OK)] [SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))] public async Task Logout() { var userGuid = userGuidFromToken(); if (userGuid == null) return Results.Unauthorized(); var accountEntity = await _accountService.getAccount(userGuid); if (accountEntity is null) return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNullEntity)); if (await _accountService.deleteRefreshToken(userGuid) == null) return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNullEntity)); List business_logs = [ new UgqApiLogoutBusinessLog(), ]; var log_action = new LogActionEx(LogActionType.UgqApiLogout); UgqApiBusinessLogger.collectLogs(log_action, accountEntity, business_logs); return Results.Ok(); } } }