diff --git a/.gitignore b/.gitignore index 0a9fcf5..2400df5 100755 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,14 @@ # 특정 환경에 따라 추가 +/private/ +/publish/ +/bin/ +/obj/ + ./private/ ./privacy/ ./publish/ -publish/ ./bin/ -/private/ + diff --git a/Program/Common/JWTToken/JwtTokenService.cs b/Program/Common/JWTToken/JwtTokenService.cs index 250fc0e..0cfd7b5 100644 --- a/Program/Common/JWTToken/JwtTokenService.cs +++ b/Program/Common/JWTToken/JwtTokenService.cs @@ -64,8 +64,7 @@ public class JwtTokenService { rng.GetBytes(randomNumber); } - - // return Convert.ToBase64String(randomNumber); + return new RefreshToken() { uid = uid, @@ -77,6 +76,9 @@ public class JwtTokenService } + /// + /// 여기는 엑세스 토큰의 확인을 위한 jwt 서비스 내의 인증 메서드 + /// public ClaimsPrincipal ValidateToken(string token) { if (string.IsNullOrWhiteSpace(token)) return null; diff --git a/Program/Common/Model/Status.cs b/Program/Common/Model/Status.cs index 593d759..acbf040 100644 --- a/Program/Common/Model/Status.cs +++ b/Program/Common/Model/Status.cs @@ -2,7 +2,7 @@ using System.Text.Json; namespace AcaMate.Common.Models; -public class APIResponseStatus +public class APIResponseStatus { public Status status { get; set; } public T? data { get; set; } @@ -20,59 +20,103 @@ public class Status } -public static class DefaultResponse +public static class APIResponse { - // private static readonly Lazy _instance = new Lazy(); - // public static ErrorResponse Instace => _instance.Value; + public static APIResponseStatus Send(string code, string message, T data) + { + return new APIResponseStatus + { + status = new Status() + { + code = code, + message = message + }, + data = data + }; + } - // private ErrorResponse() - // { - // // 외부 초기화 방지 - // } + /// + /// 반환값 없는 API 정상 동작시 + /// + public static APIResponseStatus Success (){ + return Send("000", "정상", ""); + } - public static APIResponseStatus Success = new APIResponseStatus + public static APIResponseStatus InvalidInputError() { - status = new Status() - { - code = "000", - message = "정상" - } - }; + return Send("001", "입력 값이 유효하지 않습니다.", ""); + } - public static APIResponseStatus InvalidInputError = new APIResponseStatus + public static APIResponseStatus NotFoundError() { - status = new Status() - { - code = "001", - message = "입력 값이 유효하지 않습니다." - } - }; + return Send("002", "알맞은 값을 찾을 수 없습니다.", ""); + } - public static APIResponseStatus NotFoundError = new APIResponseStatus + public static APIResponseStatus InternalSeverError() { - status = new Status() - { - code = "002", - message = "알맞은 값을 찾을 수 없습니다." - } - }; + return Send("003", "통신에 오류가 발생하였습니다.", ""); + } - public static APIResponseStatus InternalSeverError = new APIResponseStatus + public static APIResponseStatus UnknownError() { - status = new Status - { - code = "003", - message = "통신에 오류가 발생하였습니다." - } - }; - - - public static APIResponseStatus UnknownError = new APIResponseStatus - { - status = new Status() - { - code = "999", - message = "알 수 없는 오류가 발생하였습니다.." - } - }; + return Send("999", "알 수 없는 오류가 발생하였습니다.", ""); + } } +// +// public static class DefaultResponse +// { +// // private static readonly Lazy _instance = new Lazy(); +// // public static ErrorResponse Instace => _instance.Value; +// +// // private ErrorResponse() +// // { +// // // 외부 초기화 방지 +// // } +// +// +// public static APIResponseStatus Success = new APIResponseStatus +// { +// status = new Status() +// { +// code = "000", +// message = "정상" +// } +// }; +// +// public static APIResponseStatus InvalidInputError = new APIResponseStatus +// { +// status = new Status() +// { +// code = "001", +// message = "입력 값이 유효하지 않습니다." +// } +// }; +// +// public static APIResponseStatus NotFoundError = new APIResponseStatus +// { +// status = new Status() +// { +// code = "002", +// message = "알맞은 값을 찾을 수 없습니다." +// } +// }; +// +// public static APIResponseStatus InternalSeverError = new APIResponseStatus +// { +// status = new Status +// { +// code = "003", +// message = "통신에 오류가 발생하였습니다." +// } +// }; +// +// +// public static APIResponseStatus UnknownError = new APIResponseStatus +// { +// status = new Status() +// { +// code = "999", +// message = "알 수 없는 오류가 발생하였습니다." +// } +// }; +// } diff --git a/Program/V1/Controllers/AppController.cs b/Program/V1/Controllers/AppController.cs index f202268..c4bd71e 100644 --- a/Program/V1/Controllers/AppController.cs +++ b/Program/V1/Controllers/AppController.cs @@ -25,7 +25,7 @@ public class AppController : ControllerBase { if (string.IsNullOrEmpty(type)) { - return BadRequest(DefaultResponse.InvalidInputError); + return BadRequest(APIResponse.InvalidInputError); } try @@ -34,7 +34,7 @@ public class AppController : ControllerBase if (version == null) { - return NotFound(DefaultResponse.NotFoundError); + return NotFound(APIResponse.NotFoundError); } var response = new APIResponseStatus @@ -62,7 +62,7 @@ public class AppController : ControllerBase catch (Exception ex) { Console.WriteLine($"{ex.Message}\n{ex.StackTrace}"); - return StatusCode(500, DefaultResponse.UnknownError); + return StatusCode(500, APIResponse.UnknownError); } } } \ No newline at end of file diff --git a/Program/V1/Controllers/PushController.cs b/Program/V1/Controllers/PushController.cs index 7cc0d10..a6c7afe 100644 --- a/Program/V1/Controllers/PushController.cs +++ b/Program/V1/Controllers/PushController.cs @@ -25,6 +25,7 @@ public class PushController : ControllerBase public IActionResult GetPushData() { return Ok("SEND"); + } @@ -39,66 +40,74 @@ public class PushController : ControllerBase /// Internal server error occurred. /// Service unavailable. [HttpPost("send")] - [CustomOperation("푸시전송", "저장된 양식으로, 사용자에게 푸시를 전송한다.", "푸시")] + [CustomOperation("푸시전송", "저장된 양식으로, 사용자에게 푸시를 전송한다.(로컬 테스트 불가)", "푸시")] [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(APIResponseStatus))] public async Task SendPush(string deviceToken, [FromBody] Payload? payload) { - - if (string.IsNullOrWhiteSpace(deviceToken)) - { - var inputError = DefaultResponse.InvalidInputError; - inputError.status.message = "Deviece Toekn 오류"; - return StatusCode(500,inputError); - } - - if (payload == null) + if (string.IsNullOrWhiteSpace(deviceToken) || payload == null) + return BadRequest(APIResponse.InvalidInputError()); + + // string uri = ""; + // string p12Path = ""; + // + // string p12PWPath = "/src/private/appleKeys.json"; + // + // // 앱 번들 ID 입력 부분 + // string apnsTopic = "me.myds.ipstein.acamate.AcaMate"; + + Func pushService = (isDevelopment,pw,topic) => { - var inputError = DefaultResponse.InvalidInputError; - inputError.status.message = "payload 입력 오류"; - return StatusCode(500,inputError); - } - - string uri = ""; - string p12Path = ""; - string p12PWPath = "/src/private/appleKeys.json"; - // string p12PWPath = "private/appleKeys.json"; - string apnsTopic = "me.myds.ipstein.acamate.AcaMate"; - - - if (_env.IsDevelopment()) - { - uri = "https://api.sandbox.push.apple.com/"; - p12Path = "/src/private/AM_Push_Sandbox.p12"; - // p12Path = "private/AM_Push_Sandbox.p12"; - } - else - { - uri = "https://api.push.apple.com/"; - p12Path = "/src/private/AM_Push.p12"; - // p12Path = "private/AM_Push.p12"; - } - + if (isDevelopment) + { + return new ApnsPushService( + "https://api.sandbox.push.apple.com/", + "/src/private/AM_Push_Sandbox.p12", + pw, topic + ); + } + else + { + return new ApnsPushService( + "https://api.push.apple.com/", + "/src/private/AM_Push.p12", + pw, topic + ); + } + + }; + // + // if (_env.IsDevelopment()) + // { + // uri = "https://api.sandbox.push.apple.com/"; + // p12Path = "/src/private/AM_Push_Sandbox.p12"; + // } + // else + // { + // uri = "https://api.push.apple.com/"; + // p12Path = "/src/private/AM_Push.p12"; + // } + // // ApnsPushService 인스턴스 생성 - var pushService = new ApnsPushService(uri, p12Path, p12PWPath, apnsTopic); + // var pushService = new ApnsPushService(uri, p12Path, p12PWPath, apnsTopic); + // // 푸시 알림 전송 - var result = await pushService.SendPushNotificationAsync(deviceToken, payload); + // var result = await pushService.SendPushNotificationAsync(deviceToken, payload); + + var result = await pushService( + _env.IsDevelopment(), + "/src/private/appleKeys.json", + "me.myds.ipstein.acamate.AcaMate" + ).SendPushNotificationAsync(deviceToken, payload); + if (result.Success) { - return Ok(DefaultResponse.Success.JsonToString()); + return Ok(APIResponse.Success()); } else { - - var apnsError = DefaultResponse.InternalSeverError; - apnsError.status.message = $"{result.Message}"; - return result.Code switch - { - "002" => StatusCode(002, apnsError), - "003" => StatusCode(003, apnsError), - "999" => StatusCode(999, apnsError) - }; + return BadRequest(APIResponse.InternalSeverError()); } } diff --git a/Program/V1/Controllers/UserController.cs b/Program/V1/Controllers/UserController.cs index a95bd46..00b68a6 100644 --- a/Program/V1/Controllers/UserController.cs +++ b/Program/V1/Controllers/UserController.cs @@ -38,40 +38,47 @@ public class UserController : ControllerBase [HttpGet] [CustomOperation("회원 정보 조회", "회원 정보 조회", "사용자")] - public IActionResult GetUserData(string uid) + public async Task GetUserData(string token, string refresh) { - if (string.IsNullOrEmpty(uid)) return BadRequest(DefaultResponse.InvalidInputError); + if(string.IsNullOrEmpty(token) || string.IsNullOrEmpty(refresh)) + return BadRequest(APIResponse.InvalidInputError()); + try { - var user = _dbContext.User - .Where(u => u.uid == uid) - .Select(u => new User - { - uid = u.uid, - name = u.name, - auto_login_yn = u.auto_login_yn, - birth = u.birth, - device_id = u.device_id, - login_date = u.login_date, - type = u.type - }) - .FirstOrDefault(); - - var response = new APIResponseStatus + var validateToken = await _repositoryService.ValidateToken(token, refresh); + + var user = await _dbContext.User + .Where(u => u.uid == validateToken.uid) + .Select(u => new User { - status = new Status - { - code = "000", - message = "정상" - }, - data = user - }; - return Ok(response.JsonToString()); + uid = u.uid, + name = u.name, + auto_login_yn = u.auto_login_yn, + birth = u.birth, + device_id = u.device_id, + login_date = u.login_date, + type = u.type + }) + .FirstOrDefaultAsync(); + + // _logger.LogInformation($"CHECK!! {user.}"); + + return Ok(APIResponse.Send("000", "정상", user)); + } + catch (TokenException tokenEx) + { + _logger.LogInformation($"[로그인] : {tokenEx}"); + return Ok(APIResponse.Send("001", "로그인 진행: 토큰에 문제가 있음",Empty)); + } + catch (RefreshRevokeException refreshEx) + { + _logger.LogInformation($"[로그인] : {refreshEx}"); + return Ok(APIResponse.Send("001", "로그인 진행: 리프레시 토큰 폐기",Empty)); } catch (Exception ex) { - return StatusCode(500, DefaultResponse.UnknownError); + return StatusCode(500, APIResponse.UnknownError()); } } @@ -81,7 +88,7 @@ public class UserController : ControllerBase { // API 동작 파라미터 입력 값 확인 if (string.IsNullOrEmpty(acctype) && string.IsNullOrEmpty(sns_id)) - return BadRequest(DefaultResponse.InvalidInputError); + return BadRequest(APIResponse.InvalidInputError()); try { @@ -109,45 +116,23 @@ public class UserController : ControllerBase await _repositoryService.SaveData(refreshToken, rt => rt.uid); - - var response = new APIResponseStatus + return Ok(APIResponse.Send("000","정상", new { - status = new Status - { - code = "000", - message = "정상" - }, - data = new - { - token = accessToken, - refresh = refreshToken.refresh_token - } - }; - return Ok(response.JsonToString()); + token = accessToken, + refresh = refreshToken.refresh_token + })); } } // case 1: Login 테이블에 값이 없다 == 로그인이 처음 // case 2: User 테이블에 값이 없다 == 이건 문제가 있는 상황 -> 해결은 회원가입 재 진행 시도 // Login에는 있는데 User 테이블에 없다? 말이 안되긴 하는데... - return Ok(new APIResponseStatus - { - status = new Status - { - code = "010", - message = "로그인 정보 없음 > 회원 가입 진행" - }, - data = new - { - token = "", - refresh = "" - // bidList = new string[] { } - } - }.JsonToString()); + + return Ok(APIResponse.Send("001", "회원가입 진행: 로그인 정보가 없음",Empty)); } catch (Exception ex) { - _logger.LogInformation($"[로그인] 에러 발생 : {ex}"); - return StatusCode(500, DefaultResponse.UnknownError); + _logger.LogInformation($"[로그인][에러] : {ex}"); + return StatusCode(500, APIResponse.UnknownError()); } } @@ -159,20 +144,19 @@ public class UserController : ControllerBase if (string.IsNullOrEmpty(token) || string.IsNullOrEmpty(refresh)) { - var error = DefaultResponse.InvalidInputError; - return Ok(error); + return BadRequest(APIResponse.InvalidInputError()); } try { var validateToken = await _repositoryService.ValidateToken(token, refresh); var uid = validateToken.uid; - + var userAcademy = await _dbContext.UserAcademy .Where(ua => ua.uid == uid) .Select(ua => ua.bid) .ToListAsync(); - + var academies = await _dbContext.Academy .Where(a => userAcademy.Contains(a.bid)) @@ -183,28 +167,22 @@ public class UserController : ControllerBase }) .ToListAsync(); - var response = new APIResponseStatus> - { - status = new Status - { - code = "000", - message = "정상" - }, - data = academies - }; - - - return Ok(response); + return Ok(APIResponse.Send("000","정상.",academies)); } - catch (SecurityTokenException tokenEx) + catch (TokenException tokenEx) { - _logger.LogInformation($"[로그인][오류] 토큰 검증 : {tokenEx}"); - return StatusCode(500, DefaultResponse.InvalidInputError); + _logger.LogInformation($"[로그인] : {tokenEx}"); + return Ok(APIResponse.Send("001", "로그인 진행: 토큰에 문제가 있음",Empty)); + } + catch (RefreshRevokeException refreshEx) + { + _logger.LogInformation($"[로그인] : {refreshEx}"); + return Ok(APIResponse.Send("001", "로그인 진행: 리프레시 토큰 폐기",Empty)); } catch (Exception ex) { - _logger.LogInformation($"[로그인][오류] 에러 발생 : {ex}"); - return StatusCode(500, DefaultResponse.UnknownError); + _logger.LogInformation($"[로그인][에러] : {ex}"); + return StatusCode(500, APIResponse.UnknownError()); } } @@ -213,11 +191,11 @@ public class UserController : ControllerBase [CustomOperation("회원 가입", "사용자 회원 가입", "사용자")] public async Task UserRegister([FromBody] UserAll request) { - if (!ModelState.IsValid) return BadRequest(DefaultResponse.InvalidInputError); + if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); - var atIndext = request.email.IndexOf('@'); - var localPart_email = request.email.Substring(0, atIndext); - var uid = $"AM{localPart_email}{DateTime.Now:yyyyMMdd}"; + var atIndex = request.email.IndexOf('@'); + var localPartEmail = request.email.Substring(0, atIndex); + var uid = $"AM{localPartEmail}{DateTime.Now:yyyyMMdd}"; var user = new User { @@ -265,42 +243,53 @@ public class UserController : ControllerBase } // TO-DO: jwt 토큰 만들어서 여기서 보내는 작업을 해야 함 - var token = _jwtTokenService.GenerateJwtToken(uid);//, "admin"); + var token = _jwtTokenService.GenerateJwtToken(uid); var refreshToken = _jwtTokenService.GenerateRefreshToken(uid); await _repositoryService.SaveData(refreshToken, rt => rt.uid); - var result = new APIResponseStatus() + return Ok(APIResponse.Send("000","정상",new { - status = new Status() - { - code = "000", - message = "정상" - }, - data = new - { - accessToken = token, - refreshToken = refreshToken.refresh_token - } - }; - - return Ok(result.JsonToString()); + accessToken = token, + refreshToken = refreshToken.refresh_token + })); } [HttpGet("logout")] [CustomOperation("로그아웃", "사용자 로그아웃", "사용자")] - public async Task LogOut(string token, string refresh) //([FromBody] UserAll request) + public async Task Logout(string token, string refresh) //([FromBody] UserAll request) { - // 로그아웃 하면 리프래시 토큰 만료 하는걸 넣어야 함 - /* */ - // var value = await ValidateToken(token, refresh); - // _logger.LogInformation(value.uid); - // _logger.LogInformation(value.refresh); - // _logger.LogInformation(value.token); - /* */ - - - return Ok("로그아웃"); + if (string.IsNullOrEmpty(token) || string.IsNullOrEmpty(refresh)) return BadRequest(APIResponse.InvalidInputError()); + + try + { + var validateToken = await _repositoryService.ValidateToken(token, refresh); + var refreshToken = await _dbContext.RefreshTokens.FirstOrDefaultAsync(r => r.uid == validateToken.uid); + if (refreshToken != null) + { + refreshToken.revoke_Date = DateTime.Now; + await _repositoryService.SaveData(refreshToken, rt => rt.uid); + return Ok(APIResponse.Send("000", "로그아웃 정상", Empty)); + } + else + { + return Ok(APIResponse.Send("000", "로그아웃 정상", Empty)); + } + } + catch (TokenException tokenEx) + { + return Ok(APIResponse.Send("101", "입력 받은 토큰의 문제", Empty)); + } + catch (RefreshRevokeException refreshEx) + { + return Ok(APIResponse.Send("102", "폐기된 리프레시 토큰", Empty)); + } + catch (Exception ex) + { + return StatusCode(500, APIResponse.UnknownError()); + } } + + // [HttpGet("set")] diff --git a/Program/V1/Models/AcaException.cs b/Program/V1/Models/AcaException.cs new file mode 100644 index 0000000..42fac10 --- /dev/null +++ b/Program/V1/Models/AcaException.cs @@ -0,0 +1,24 @@ +using Microsoft.IdentityModel.Tokens; +using System; + +namespace AcaMate.V1.Models; + +/// +/// 입력 받은 토큰들(Access & Refresh) 자체에 문제가 있는 경우 +/// +public class TokenException: Exception +{ + public TokenException(string message) : base(message) + { + } +} + +/// +/// 리프레시 토큰이 만료가 나있는 경우 +/// +public class RefreshRevokeException: Exception +{ + public RefreshRevokeException(string message) : base(message) + { + } +} \ No newline at end of file diff --git a/Program/V1/Services/RepositoryService.cs b/Program/V1/Services/RepositoryService.cs index f53fb60..7b608cb 100644 --- a/Program/V1/Services/RepositoryService.cs +++ b/Program/V1/Services/RepositoryService.cs @@ -7,7 +7,9 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using System.Linq.Expressions; using System.Security.Claims; +using AcaMate.V1.Models; using Microsoft.IdentityModel.Tokens; +using Microsoft.VisualBasic; namespace AcaMate.V1.Services; @@ -82,6 +84,9 @@ public class RepositoryService: IRepositoryService } //토큰 태울때는 인코딩 된 걸로 태워야지 원본꺼 태우면 데이터에 손상옵니다. + /// + /// 실제로 엑세스 토큰과 리프레시 토큰으로 접근 하기 위한 메서드 + /// public async Task ValidateToken(string token, string refresh) { var principalToken = _jwtTokenService.ValidateToken(token); @@ -102,15 +107,18 @@ public class RepositoryService: IRepositoryService var refreshToken = await _dbContext.RefreshTokens .FirstOrDefaultAsync(t => t.refresh_token == refresh); if (refreshToken == null) - { - throw new SecurityTokenException("리프레시 토큰도 잘못되었음"); - } + throw new TokenException("입력 받은 토큰 자체의 문제"); + var uid = refreshToken.uid; - + + if (refreshToken.revoke_Date < DateTime.Now) + throw new RefreshRevokeException("리프레시 토큰 해지"); + if (refreshToken.expire_date > DateTime.Now) { - _logger.LogInformation($"리프레시 : {uid}"); + _logger.LogInformation($"인증 완료 리프레시 : {uid}"); var access = _jwtTokenService.GenerateJwtToken(uid); + return new ValidateToken { token = access,