From a9462ca9b54bc38c50d98b14076eaef665d6f6c7 Mon Sep 17 00:00:00 2001 From: "Seonkyu.kim" Date: Mon, 9 Jun 2025 17:46:49 +0900 Subject: [PATCH 1/4] =?UTF-8?q?[=E2=9C=A8]=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=20=EA=B4=80=EB=A0=A8=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Program/Controllers/V1/AppController.cs | 12 +- Program/Controllers/V1/OutController.cs | 37 +- package-lock.json | 1669 +++++++++++++++++++++++ 3 files changed, 1705 insertions(+), 13 deletions(-) create mode 100644 package-lock.json diff --git a/Program/Controllers/V1/AppController.cs b/Program/Controllers/V1/AppController.cs index 09946d1..2760112 100644 --- a/Program/Controllers/V1/AppController.cs +++ b/Program/Controllers/V1/AppController.cs @@ -27,7 +27,7 @@ namespace Back.Program.Controllers.V1 private readonly IAppRepository _appRepository; private readonly ISessionService _sessionService; - public AppController(AppDbContext dbContext, ILogger logger, IRepositoryService repositoryService, JwtTokenService jwtTokenService,IAppService appService, IAppRepository appRepository, ISessionService sessionService) + public AppController(AppDbContext dbContext, ILogger logger, IRepositoryService repositoryService, JwtTokenService jwtTokenService, IAppService appService, IAppRepository appRepository, ISessionService sessionService) { _dbContext = dbContext; _logger = logger; @@ -38,23 +38,23 @@ namespace Back.Program.Controllers.V1 _sessionService = sessionService; } - + // 이 키값의 제한 시간은 24h이다 [HttpGet] [CustomOperation("헤더 정보 생성", "헤더에 접근하기 위한 키 값 받아오기", "시스템")] public async Task GetHeaderValue(string type, string specific, string project) { - if (string.IsNullOrEmpty(specific) || string.IsNullOrEmpty(type) || string.IsNullOrEmpty(project)) + if (string.IsNullOrEmpty(specific) || string.IsNullOrEmpty(type) || string.IsNullOrEmpty(project)) return BadRequest(APIResponse.InvalidInputError()); if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); string summary = _repositoryService.ReadSummary(typeof(AppController), "GetHeaderValue"); - + var result = await _appService.GetHeader(summary, type, specific, project); return Ok(result); } [HttpGet("version")] - [CustomOperation("앱 버전 확인","앱 버전을 확인해서 업데이트 여부 판단", "시스템")] + [CustomOperation("앱 버전 확인", "앱 버전을 확인해서 업데이트 여부 판단", "시스템")] public async Task GetVersionData(string type) { if (string.IsNullOrEmpty(type)) return BadRequest(APIResponse.InvalidInputError()); @@ -62,7 +62,7 @@ namespace Back.Program.Controllers.V1 var result = await _appService.GetVersion(summary, type); return Ok(result); } - + [HttpGet("retryAccess")] [CustomOperation("엑세스 토큰 재발급", "액세스 토큰 재발급 동작 수행", "시스템")] public async Task RetryAccessToken(string refresh) diff --git a/Program/Controllers/V1/OutController.cs b/Program/Controllers/V1/OutController.cs index 781c51c..6801f34 100644 --- a/Program/Controllers/V1/OutController.cs +++ b/Program/Controllers/V1/OutController.cs @@ -30,10 +30,16 @@ public class OutController: ControllerBase _kakaoService = kakaoService; _sessionService = sessionService; } + [HttpGet("kakao/auth")] [CustomOperation("카카오 로그인", "카카오 로그인 동작", "사용자")] - public async Task KakaoLogin([FromQuery] string? scope) + public async Task KakaoLogin([FromQuery] string? scope, [FromQuery] string? redirectPath) { + if (!string.IsNullOrEmpty(redirectPath)) + { + await _sessionService.SetString("redirectPath", redirectPath); + } + var url = await _kakaoService.GetAuthorizationUrl(scope ?? ""); Console.WriteLine($"카카오 로그인 API: {url}"); return Ok(new { url }); @@ -46,7 +52,6 @@ public class OutController: ControllerBase Console.WriteLine($"리다이렉트 : {response}"); if (success) { - // HttpContext.Session.SetString("AccessToken", response); var (idSuccess, idResponse) = await _kakaoService.UserMe(response); if (idSuccess) { @@ -59,7 +64,6 @@ public class OutController: ControllerBase Console.WriteLine($"login = {loginResult.JsonToString()}"); if (loginResult.status.code == "000") { - var data = loginResult.data as LoginAPIResponse ?? new LoginAPIResponse(); if (data != null) { @@ -68,7 +72,19 @@ public class OutController: ControllerBase if (await _sessionService.SetString("token", token) && await _sessionService.SetString("refresh", refresh)) { - return Redirect("/about"); + var (hasPath, redirectPath) = await _sessionService.GetString("redirectPath"); + await _sessionService.Remove("redirectPath"); // 사용 후 세션에서 제거 + + // 로그인 성공 flag 쿠키 저장 + Response.Cookies.Append("IsLogin", "true", new CookieOptions + { + HttpOnly = false, + Secure = true, + SameSite = SameSiteMode.Lax, + Path = "/", + Expires = DateTime.Now.AddDays(1) + }); + return Redirect(hasPath && !string.IsNullOrEmpty(redirectPath) ? redirectPath : "/about"); } } } @@ -83,13 +99,20 @@ public class OutController: ControllerBase { return BadRequest(new { error = "로그인 실패", message = loginResult.status.message }); } - // return Ok(new { id="cc" }); } } Console.WriteLine($"ID_res = {idResponse}"); - } return BadRequest(); - } + + // // 로그아웃 API 예시 (이미 있다면 해당 위치에 추가) + // [HttpGet("logout")] + // public IActionResult Logout() + // { + // // 세션/쿠키 등 로그아웃 처리 + // Response.Cookies.Delete("IsLogin"); + // // 기타 로그아웃 처리 로직... + // return Redirect("/"); + // } } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..6ea31ac --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1669 @@ +{ + "name": "tailwind-blazor-template", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "tailwind-blazor-template", + "version": "1.0.0", + "devDependencies": { + "autoprefixer": "^10.4.14", + "postcss": "^8.4.21", + "tailwindcss": "^3.4.1" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/autoprefixer": { + "version": "10.4.21", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", + "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.24.4", + "caniuse-lite": "^1.0.30001702", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", + "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001715", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001715.tgz", + "integrity": "sha512-7ptkFGMm2OAOgvZpwgA4yjQ5SQbrNVGdRjzH0pBdy1Fasvcr+KAeECmbCAECzTuDuoX0FCY8KzUxjf9+9kfZEw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.142", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.142.tgz", + "integrity": "sha512-Ah2HgkTu/9RhTDNThBtzu2Wirdy4DC9b0sMT1pUhbkZQ5U/iwmE+PHZX1MpjD5IkJCc2wSghgGG/B04szAx07w==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/sucrase": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "^10.3.10", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/sucrase/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/sucrase/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/sucrase/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sucrase/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.17", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", + "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.6", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tailwindcss/node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/tailwindcss/node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/tailwindcss/node_modules/postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/tailwindcss/node_modules/postcss-load-config": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", + "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.0.0", + "yaml": "^2.3.4" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/tailwindcss/node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/tailwindcss/node_modules/yaml": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz", + "integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + } + } +} From 669f22d3652222ec23a6fb1975ca8abd301d92c8 Mon Sep 17 00:00:00 2001 From: SEAN-59 Date: Wed, 11 Jun 2025 15:14:42 +0900 Subject: [PATCH 2/4] =?UTF-8?q?[=E2=9C=A8]=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=20=EB=8F=99=EC=9E=91=20=EB=B0=8F=20Repository=20=EB=8F=99?= =?UTF-8?q?=EC=9E=91=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 로그인 되었을 경우 화면 표기 변경 1.1. 사용자 이름 보내주기 1.2. 서버에서 직접적인 크라이언트 쿠키 저장이 아닌 서버는 뒤의 값으로 간섭하게 변경 --- Program/Common/Auth/JwtTokenService.cs | 3 + Program/Controllers/V1/OutController.cs | 59 +++++++++---- Program/Controllers/V1/UserController.cs | 12 ++- Program/Services/V1/AppService.cs | 6 +- .../Services/V1/Interfaces/IUserService.cs | 2 +- Program/Services/V1/UserService.cs | 86 ++++++++++++++++++- 6 files changed, 144 insertions(+), 24 deletions(-) diff --git a/Program/Common/Auth/JwtTokenService.cs b/Program/Common/Auth/JwtTokenService.cs index caa0e27..cd5e0d3 100644 --- a/Program/Common/Auth/JwtTokenService.cs +++ b/Program/Common/Auth/JwtTokenService.cs @@ -22,6 +22,8 @@ namespace Back.Program.Common.Auth _logger = logger; } + + // JWT 토큰 생성 public string GenerateJwtToken(string jwtKey)//, string role) { // 1. 클레임(Claim) 설정 - 필요에 따라 추가 정보도 포함 @@ -53,6 +55,7 @@ namespace Back.Program.Common.Auth return new JwtSecurityTokenHandler().WriteToken(token); } + // 리프레시 토큰 생성 public RefreshToken GenerateRefreshToken(string uid) { var randomNumber = new byte[32]; // 256비트 diff --git a/Program/Controllers/V1/OutController.cs b/Program/Controllers/V1/OutController.cs index 6801f34..eebdfc5 100644 --- a/Program/Controllers/V1/OutController.cs +++ b/Program/Controllers/V1/OutController.cs @@ -10,6 +10,8 @@ using Back.Program.Models.APIResponses; namespace Back.Program.Controllers; + +// TO-DO: 여기 controller, service, repository 분리 필요 [ApiController] [Route("/api/v1/out/user")] [ApiExplorerSettings(GroupName = "외부 동작(사용자)")] @@ -48,48 +50,63 @@ public class OutController: ControllerBase [HttpGet("kakao/redirect")] public async Task RedirectFromKakao([FromQuery] string code) { + _logger.LogInformation("카카오 리다이렉트 시작"); var (success, response) = await _kakaoService.Redirect(code); - Console.WriteLine($"리다이렉트 : {response}"); + _logger.LogInformation($"리다이렉트 결과: {success}, 응답: {response}"); + if (success) { var (idSuccess, idResponse) = await _kakaoService.UserMe(response); + _logger.LogInformation($"사용자 정보 조회 결과: {idSuccess}, 응답: {idResponse}"); + if (idSuccess) { var json = JsonDocument.Parse(idResponse); if (json.RootElement.TryGetProperty("id", out var idElement)) { var snsId = idElement.ToString(); - Console.WriteLine($"ID = {snsId}"); + _logger.LogInformation($"카카오 ID: {snsId}"); + var loginResult = await _userService.Login("SNS Login", "ST01", snsId); - Console.WriteLine($"login = {loginResult.JsonToString()}"); + _logger.LogInformation($"로그인 결과: {loginResult.JsonToString()}"); + if (loginResult.status.code == "000") { - var data = loginResult.data as LoginAPIResponse ?? new LoginAPIResponse(); + var data = JsonSerializer.Deserialize(JsonSerializer.Serialize(loginResult.data)); + _logger.LogInformation($"로그인 데이터: {JsonSerializer.Serialize(data)}"); + if (data != null) { string token = data.token; string refresh = data.refresh; + _logger.LogInformation($"토큰 저장 시도 - token: {token}, refresh: {refresh}"); + if (await _sessionService.SetString("token", token) && await _sessionService.SetString("refresh", refresh)) { + _logger.LogInformation("세션 저장 성공"); var (hasPath, redirectPath) = await _sessionService.GetString("redirectPath"); await _sessionService.Remove("redirectPath"); // 사용 후 세션에서 제거 - // 로그인 성공 flag 쿠키 저장 - Response.Cookies.Append("IsLogin", "true", new CookieOptions - { - HttpOnly = false, - Secure = true, - SameSite = SameSiteMode.Lax, - Path = "/", - Expires = DateTime.Now.AddDays(1) - }); - return Redirect(hasPath && !string.IsNullOrEmpty(redirectPath) ? redirectPath : "/about"); + var redirectUrl = hasPath && !string.IsNullOrEmpty(redirectPath) + ? $"{redirectPath}?auth=true" + : "/about?auth=true"; + _logger.LogInformation($"리다이렉트 URL: {redirectUrl}"); + return Redirect(redirectUrl); } + else + { + _logger.LogError("세션 저장 실패"); + } + } + else + { + _logger.LogError("로그인 데이터가 null입니다"); } } else if (loginResult.status.code == "001") { + _logger.LogInformation("회원가입 필요"); if (await _sessionService.SetString("snsId", snsId)) { return Redirect("/auth/register"); @@ -97,11 +114,23 @@ public class OutController: ControllerBase } else { + _logger.LogError($"로그인 실패: {loginResult.status.message}"); return BadRequest(new { error = "로그인 실패", message = loginResult.status.message }); } } + else + { + _logger.LogError("카카오 ID를 찾을 수 없습니다"); + } } - Console.WriteLine($"ID_res = {idResponse}"); + else + { + _logger.LogError($"사용자 정보 조회 실패: {idResponse}"); + } + } + else + { + _logger.LogError($"카카오 리다이렉트 실패: {response}"); } return BadRequest(); } diff --git a/Program/Controllers/V1/UserController.cs b/Program/Controllers/V1/UserController.cs index 93fe848..18fc255 100644 --- a/Program/Controllers/V1/UserController.cs +++ b/Program/Controllers/V1/UserController.cs @@ -106,10 +106,14 @@ namespace Back.Program.Controllers.V1 return Ok(result); } - - - - + [HttpGet("auth/session")] + [CustomOperation("세션 정보 확인", "세션 정보 확인", "사용자")] + public async Task GetSessionData() + { + string summary = _repositoryService.ReadSummary(typeof(UserController), "GetSessionData"); + var result = await _userService.GetSessionData(summary); + return Ok(result); + } } } diff --git a/Program/Services/V1/AppService.cs b/Program/Services/V1/AppService.cs index 32aeb03..538c8b6 100644 --- a/Program/Services/V1/AppService.cs +++ b/Program/Services/V1/AppService.cs @@ -158,11 +158,11 @@ public class AppService: IAppService { var refreshToken = await _appRepository.FindRefreshToken(refresh); if (refreshToken == null) return APIResponse.InvalidInputError($"[{summary}] : 리프레시 토큰 문제"); - if (refreshToken.revoke_Date < DateTime.Now) return APIResponse.InvalidInputError($"[{summary}] : 리프레시 토큰 만료"); - if (refreshToken.expire_date < DateTime.Now) return APIResponse.InvalidInputError($"[{summary}] : 리프레시 토큰 폐기"); + if (refreshToken.revoke_Date != null) return APIResponse.InvalidInputError($"[{summary}] : 리프레시 토큰 폐기"); + if (refreshToken.expire_date < DateTime.Now) return APIResponse.InvalidInputError($"[{summary}] : 리프레시 토큰 만료"); string access = _jwtTokenService.GenerateJwtToken(refreshToken.uid); - return APIResponse.Send("000", $"[{summary}], 토큰 생성 완료", new { accsee = access }); + return APIResponse.Send("000", $"[{summary}], 토큰 생성 완료", new { access = access }); } } \ No newline at end of file diff --git a/Program/Services/V1/Interfaces/IUserService.cs b/Program/Services/V1/Interfaces/IUserService.cs index ebe0df7..68df934 100644 --- a/Program/Services/V1/Interfaces/IUserService.cs +++ b/Program/Services/V1/Interfaces/IUserService.cs @@ -11,6 +11,6 @@ namespace Back.Program.Services.V1.Interfaces Task> Logout(string summary, string token); Task> Cancel(string summary, string token); Task> GetAcademy(string summary, string token); - + Task> GetSessionData(string summary); } } \ No newline at end of file diff --git a/Program/Services/V1/UserService.cs b/Program/Services/V1/UserService.cs index 418e560..8e13cca 100644 --- a/Program/Services/V1/UserService.cs +++ b/Program/Services/V1/UserService.cs @@ -4,6 +4,7 @@ using Back.Program.Common.Model; using Back.Program.Models.Entities; using Back.Program.Repositories.V1.Interfaces; using Back.Program.Services.V1.Interfaces; +using System.Text.Json; namespace Back.Program.Services.V1 { @@ -14,16 +15,21 @@ namespace Back.Program.Services.V1 private readonly JwtTokenService _jwtTokenService; private readonly IRepositoryService _repositoryService; private readonly ILogRepository _logRepository; + private readonly ISessionService _sessionService; + private readonly IAppService _appService; public UserService(ILogger logger, IUserRepository userRepository, JwtTokenService jwtTokenService, - IRepositoryService repositoryService, ILogRepository logRepository) + IRepositoryService repositoryService, ILogRepository logRepository, + ISessionService sessionService, IAppService appService) { _logger = logger; _userRepository = userRepository; _jwtTokenService = jwtTokenService; _repositoryService = repositoryService; _logRepository = logRepository; + _sessionService = sessionService; + _appService = appService; } public async Task> GetUser(string summary, string token) @@ -234,5 +240,83 @@ namespace Back.Program.Services.V1 _logger.LogInformation($"[{summary}]: 성공"); return APIResponse.Send("000", $"[{summary}], 정상.", academyList); } + + public async Task> GetSessionData(string summary) + { + try + { + _logger.LogInformation($"[{summary}] 세션 데이터 조회 시작"); + + // 1. 세션에서 토큰 가져오기 + var (result, token) = await _sessionService.GetString("token"); + _logger.LogInformation($"[{summary}] 세션에서 토큰 가져오기 결과: {result}, 토큰: {token}"); + + if (!result || string.IsNullOrEmpty(token)) + { + _logger.LogWarning($"[{summary}] 세션에 토큰이 없습니다"); + return APIResponse.Send("200", "세션에 토큰이 없습니다", new { name = "" }); + } + + // 2. 토큰 검증 + var validToken = await _jwtTokenService.ValidateToken(token); + _logger.LogInformation($"[{summary}] 토큰 검증 결과: {validToken != null}"); + + if (validToken == null) + { + // 3. 토큰이 유효하지 않으면 리프레시 토큰으로 새 토큰 발급 시도 + var (refreshResult, refreshToken) = await _sessionService.GetString("refresh"); + _logger.LogInformation($"[{summary}] 리프레시 토큰 가져오기 결과: {refreshResult}, 토큰: {refreshToken}"); + + if (!refreshResult || string.IsNullOrEmpty(refreshToken)) + { + _logger.LogWarning($"[{summary}] 리프레시 토큰이 없습니다"); + return APIResponse.Send("201", "리프레시 토큰이 없습니다", new { name = "" }); + } + + // 4. 리프레시 토큰으로 새 토큰 발급 + var retryResult = await _appService.RetryAccess(summary, refreshToken); + _logger.LogInformation($"[{summary}] 토큰 재발급 결과: {retryResult.status.code}"); + + if (retryResult.status.code == "000") + { + // 5. 새 토큰을 세션에 저장 + var data = JsonSerializer.Deserialize(JsonSerializer.Serialize(retryResult.data)); + var newToken = data.GetProperty("access").GetString(); + await _sessionService.SetString("token", newToken); + _logger.LogInformation($"[{summary}] 새 토큰 세션 저장 완료"); + + // 6. 새 토큰으로 사용자 정보 조회 + validToken = await _jwtTokenService.ValidateToken(newToken); + } + else + { + _logger.LogWarning($"[{summary}] 토큰 갱신 실패: {retryResult.status.message}"); + return APIResponse.Send("202", "토큰 갱신 실패", new { name = "" }); + } + } + + // 7. 최종적으로 유효한 토큰으로 사용자 정보 조회 + var uid = validToken.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty; + _logger.LogInformation($"[{summary}] 사용자 ID: {uid}"); + + var user = await _userRepository.FindUser(uid); + _logger.LogInformation($"[{summary}] 사용자 정보 조회 결과: {user != null}"); + + if (user == null) + { + _logger.LogWarning($"[{summary}] 사용자 정보를 찾을 수 없습니다"); + return APIResponse.Send("203", "사용자 정보를 찾을 수 없습니다", new { name = "" }); + } + + _logger.LogInformation($"[{summary}] 세션 데이터 조회 성공: {user.name}"); + return APIResponse.Send("000", $"[{summary}], 정상", new { name = user.name }); + } + catch (Exception ex) + { + _logger.LogError($"[{summary}] 세션 데이터 조회 중 오류: {ex.Message}"); + _logger.LogError($"[{summary}] 스택 트레이스: {ex.StackTrace}"); + return APIResponse.InternalSeverError($"[{summary}], 세션 데이터 조회 실패"); + } + } } } \ No newline at end of file From 5baa549695deaee916894925df51b186b315f379 Mon Sep 17 00:00:00 2001 From: SEAN-59 Date: Fri, 13 Jun 2025 17:54:43 +0900 Subject: [PATCH 3/4] =?UTF-8?q?[=E2=9C=A8]=20=EC=9C=A0=EC=A0=80=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=20=EC=A0=84=EC=86=A1=20API=20=EB=B0=98?= =?UTF-8?q?=ED=99=98=20=EC=A0=95=EB=B3=B4=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Program/Services/V1/UserService.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Program/Services/V1/UserService.cs b/Program/Services/V1/UserService.cs index 8e13cca..2fe71c7 100644 --- a/Program/Services/V1/UserService.cs +++ b/Program/Services/V1/UserService.cs @@ -254,7 +254,7 @@ namespace Back.Program.Services.V1 if (!result || string.IsNullOrEmpty(token)) { _logger.LogWarning($"[{summary}] 세션에 토큰이 없습니다"); - return APIResponse.Send("200", "세션에 토큰이 없습니다", new { name = "" }); + return APIResponse.Send("200", "세션에 토큰이 없습니다", new { }); } // 2. 토큰 검증 @@ -270,7 +270,7 @@ namespace Back.Program.Services.V1 if (!refreshResult || string.IsNullOrEmpty(refreshToken)) { _logger.LogWarning($"[{summary}] 리프레시 토큰이 없습니다"); - return APIResponse.Send("201", "리프레시 토큰이 없습니다", new { name = "" }); + return APIResponse.Send("201", "리프레시 토큰이 없습니다", new { }); } // 4. 리프레시 토큰으로 새 토큰 발급 @@ -291,7 +291,7 @@ namespace Back.Program.Services.V1 else { _logger.LogWarning($"[{summary}] 토큰 갱신 실패: {retryResult.status.message}"); - return APIResponse.Send("202", "토큰 갱신 실패", new { name = "" }); + return APIResponse.Send("202", "토큰 갱신 실패", new { }); } } @@ -305,11 +305,11 @@ namespace Back.Program.Services.V1 if (user == null) { _logger.LogWarning($"[{summary}] 사용자 정보를 찾을 수 없습니다"); - return APIResponse.Send("203", "사용자 정보를 찾을 수 없습니다", new { name = "" }); + return APIResponse.Send("203", "사용자 정보를 찾을 수 없습니다", new { }); } _logger.LogInformation($"[{summary}] 세션 데이터 조회 성공: {user.name}"); - return APIResponse.Send("000", $"[{summary}], 정상", new { name = user.name }); + return APIResponse.Send("000", $"[{summary}], 정상", user); } catch (Exception ex) { From 9f6a5b882ce6614ef96e52a7fcd0512b96f83424 Mon Sep 17 00:00:00 2001 From: SEAN-59 Date: Mon, 16 Jun 2025 17:44:42 +0900 Subject: [PATCH 4/4] =?UTF-8?q?[=E2=9C=A8]=20=EC=9A=B4=EC=98=81=EC=B2=B4?= =?UTF-8?q?=EC=A0=9C=EB=B3=84=20=EB=8F=99=EC=9E=91=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=ED=86=B5=EC=9D=BC=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 운영체제가 다르다고 다른 API 나 다른 로직을 타는게 아닌 하나의 로직으로 돌게 만들기 위해서 로직 수정 --- Document/Rule.md | 26 ++++ Program.cs | 6 +- Program/Common/Auth/DedicateWeb.cs | 76 ++++++++++ Program/Common/Data/SessionManager.cs | 53 +++++++ Program/Controllers/V1/AppController.cs | 11 +- Program/Controllers/V1/OutController.cs | 19 +-- Program/Controllers/V1/SessionController.cs | 37 +++++ Program/Controllers/V1/UserController.cs | 62 ++++---- .../Services/V1/Interfaces/ISessionService.cs | 7 +- .../Services/V1/Interfaces/IUserService.cs | 1 - Program/Services/V1/SessionService.cs | 139 +++++++++++++----- Program/Services/V1/UserService.cs | 98 ++---------- 12 files changed, 356 insertions(+), 179 deletions(-) create mode 100644 Document/Rule.md create mode 100644 Program/Common/Auth/DedicateWeb.cs create mode 100644 Program/Common/Data/SessionManager.cs create mode 100644 Program/Controllers/V1/SessionController.cs diff --git a/Document/Rule.md b/Document/Rule.md new file mode 100644 index 0000000..6c1b12f --- /dev/null +++ b/Document/Rule.md @@ -0,0 +1,26 @@ +# AcaMate API 문서 + +## 개요 + +## 프로젝트 구조 +### 각 폴더 간 관계 +#### 역할 +- Controller: API 요청을 처리하고 응답을 반환하는 역할 +- Service: 비즈니스 로직을 처리하는 역할 +- Repository: 데이터베이스와의 상호작용을 처리하는 역할 +- Model: 데이터 구조를 정의하는 역할 +#### 폴더 관계 +- Controller 는 Service 를 참조하고, Service 는 Repository 를 참조한다. +- Controller 는 Service 와 1:N 관계를 가진다. +- Service 는 Repository 와 1:N 관계를 가진다. +- Controller에서 Repository를 직접 참조하지 않는다. +- Repository 와 Service 는 모두 Interface 를 통해 의존성을 주입받는다. +- Common 폴더는 모든 계층에서 공통적으로 사용되는 유틸리티나 헬퍼 클래스를 포함한다. + + +### 오류 코드 +- 0xx : 성공 +- 1xx : 입력 오류 +- 2xx : 출력 오류 +- 3xx : 통신 오류 +- 999 : 알 수 없는 오류 \ No newline at end of file diff --git a/Program.cs b/Program.cs index 91e937d..2a42e29 100644 --- a/Program.cs +++ b/Program.cs @@ -26,7 +26,7 @@ using Back.Program.Services.V1.Interfaces; Boolean isLocal = false; // 로컬 테스트 할 때는 이거 키고 아니면 끄기 -// isLocal = true; +isLocal = true; var builder = WebApplication.CreateBuilder(args); @@ -117,7 +117,9 @@ builder.Services.AddSingleton(); builder.Services.AddHostedService(); // PUSH 설정부 끝 + builder.Services.AddControllers(); + // 세션 설정 // IN-MEMORY 캐시 builder.Services.AddDistributedMemoryCache(); @@ -140,6 +142,8 @@ builder.Services.AddScoped(); // builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); // builder.Services.AddScoped(); // // builder.Services.AddScoped(); diff --git a/Program/Common/Auth/DedicateWeb.cs b/Program/Common/Auth/DedicateWeb.cs new file mode 100644 index 0000000..5f2c03e --- /dev/null +++ b/Program/Common/Auth/DedicateWeb.cs @@ -0,0 +1,76 @@ +using System.Security.Claims; +using System.Text.Json; +using Back.Program.Common.Data; +using Back.Program.Common.Model; +using Back.Program.Services.V1.Interfaces; + +namespace Back.Program.Common.Auth; + +public class DedicateWeb( + ILogger _logger, + SessionManager _sessionManager, + IRepositoryService _repositoryService, + JwtTokenService _jwtTokenService, + IAppService _appService) +{ + public async Task<(string code, string result)> GetAuthToken() + { + var summary = "GetAuthToken"; + try + { + // 1. 세션에서 토큰 가져오기 + var (result, token) = await _sessionManager.GetString("token"); + _logger.LogInformation($"세션에서 토큰 가져오기 결과: {result}, 토큰: {token}"); + + if (!result || string.IsNullOrEmpty(token)) + { + _logger.LogWarning($"세션에 토큰이 없습니다"); + return ("200", "세션에 토큰 없음"); + } + + // 2. 토큰 검증 + var validToken = await _jwtTokenService.ValidateToken(token); + _logger.LogInformation($"토큰 검증 결과: {validToken != null}"); + + if (validToken == null) + { + // 3. 토큰이 유효하지 않으면 리프레시 토큰으로 새 토큰 발급 시도 + var (refreshResult, refreshToken) = await _sessionManager.GetString("refresh"); + _logger.LogInformation($"리프레시 토큰 가져오기 결과: {refreshResult}, 토큰: {refreshToken}"); + + if (!refreshResult || string.IsNullOrEmpty(refreshToken)) + { + _logger.LogWarning($"리프레시 토큰이 없습니다"); + return ("200", "리프레시 토큰 없음"); + } + + // 4. 리프레시 토큰으로 새 토큰 발급 + var retryResult = await _appService.RetryAccess(summary, refreshToken); + _logger.LogInformation($"토큰 재발급 결과: {retryResult.status.code}"); + + if (retryResult.status.code == "000") + { + // 5. 새 토큰을 세션에 저장 + var data = JsonSerializer.Deserialize(JsonSerializer.Serialize(retryResult.data)); + var newToken = data.GetProperty("access").GetString(); + await _sessionManager.SetString("token", newToken); + _logger.LogInformation($"[{summary}] 새 토큰 세션 저장 완료"); + + return ("000", newToken); + } + else + { + _logger.LogWarning($"[{summary}] 토큰 갱신 실패: {retryResult.status.message}"); + return ("102", "토큰 갱신 실패"); + } + } + return ("000", token); + } + catch (Exception ex) + { + _logger.LogError($"[{summary}] 세션 데이터 조회 중 오류: {ex.Message}"); + _logger.LogError($"[{summary}] 스택 트레이스: {ex.StackTrace}"); + return ("100", "세션 데이터 조회 중 오류"); + } + } +} \ No newline at end of file diff --git a/Program/Common/Data/SessionManager.cs b/Program/Common/Data/SessionManager.cs new file mode 100644 index 0000000..6ff090f --- /dev/null +++ b/Program/Common/Data/SessionManager.cs @@ -0,0 +1,53 @@ +namespace Back.Program.Common.Data; + +public class SessionManager +{ + private readonly IHttpContextAccessor _http; + + public SessionManager(IHttpContextAccessor http) + { + _http = http; + } + + public Task SetString(string key, string value) + { + try + { + _http.HttpContext.Session.SetString(key, value); + return Task.FromResult(true); + } + catch + { + return Task.FromResult(false); + } + } + public Task<(bool result, string data)> GetString(string key) + { + try + { + var value = _http.HttpContext.Session.GetString(key); + return Task.FromResult((true, value ?? string.Empty)); + } + catch + { + return Task.FromResult((false, "")); + } + } + public Task Remove(string key) + { + try + { + _http.HttpContext.Session.Remove(key); + return Task.FromResult(true); + } + catch + { + return Task.FromResult(false); + } + } + + + + + +} \ No newline at end of file diff --git a/Program/Controllers/V1/AppController.cs b/Program/Controllers/V1/AppController.cs index 2760112..2e4d6b1 100644 --- a/Program/Controllers/V1/AppController.cs +++ b/Program/Controllers/V1/AppController.cs @@ -25,9 +25,10 @@ namespace Back.Program.Controllers.V1 private readonly JwtTokenService _jwtTokenService; private readonly IAppService _appService; private readonly IAppRepository _appRepository; - private readonly ISessionService _sessionService; + private readonly SessionManager _sessionManager; - public AppController(AppDbContext dbContext, ILogger logger, IRepositoryService repositoryService, JwtTokenService jwtTokenService, IAppService appService, IAppRepository appRepository, ISessionService sessionService) + public AppController(AppDbContext dbContext, ILogger logger, IRepositoryService repositoryService, + JwtTokenService jwtTokenService, IAppService appService, IAppRepository appRepository, SessionManager sessionManager) { _dbContext = dbContext; _logger = logger; @@ -35,7 +36,7 @@ namespace Back.Program.Controllers.V1 _jwtTokenService = jwtTokenService; _appService = appService; _appRepository = appRepository; - _sessionService = sessionService; + _sessionManager = sessionManager; } @@ -82,7 +83,7 @@ namespace Back.Program.Controllers.V1 return BadRequest(APIResponse.InvalidInputError()); } - var (success, value) = await _sessionService.GetString(key); + var (success, value) = await _sessionManager.GetString(key); if (!success) { return BadRequest(APIResponse.InvalidInputError()); @@ -106,7 +107,7 @@ namespace Back.Program.Controllers.V1 foreach(var request in requests) { Console.WriteLine($"세션 저장 시도 - key: {request.key}, value: {request.value}"); - var success = await _sessionService.SetString(request.key, request.value); + var success = await _sessionManager.SetString(request.key, request.value); if (!success) { Console.WriteLine($"세션 저장 실패 - key: {request.key}"); diff --git a/Program/Controllers/V1/OutController.cs b/Program/Controllers/V1/OutController.cs index eebdfc5..10e3e64 100644 --- a/Program/Controllers/V1/OutController.cs +++ b/Program/Controllers/V1/OutController.cs @@ -1,4 +1,5 @@ using System.Text.Json; +using Back.Program.Common.Data; using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.AspNetCore.Mvc; @@ -21,16 +22,16 @@ public class OutController: ControllerBase private readonly IRepositoryService _repositoryService; private readonly IUserService _userService; private readonly IKakaoService _kakaoService; - private readonly ISessionService _sessionService; + private readonly SessionManager _sessionManager; public OutController(ILogger logger, - IRepositoryService repositoryService, IUserService userService, IKakaoService kakaoService, ISessionService sessionService) + IRepositoryService repositoryService, IUserService userService, IKakaoService kakaoService, SessionManager sessionManager) { _logger = logger; _repositoryService = repositoryService; _userService = userService; _kakaoService = kakaoService; - _sessionService = sessionService; + _sessionManager = sessionManager; } [HttpGet("kakao/auth")] @@ -39,7 +40,7 @@ public class OutController: ControllerBase { if (!string.IsNullOrEmpty(redirectPath)) { - await _sessionService.SetString("redirectPath", redirectPath); + await _sessionManager.SetString("redirectPath", redirectPath); } var url = await _kakaoService.GetAuthorizationUrl(scope ?? ""); @@ -81,12 +82,12 @@ public class OutController: ControllerBase string refresh = data.refresh; _logger.LogInformation($"토큰 저장 시도 - token: {token}, refresh: {refresh}"); - if (await _sessionService.SetString("token", token) && - await _sessionService.SetString("refresh", refresh)) + if (await _sessionManager.SetString("token", token) && + await _sessionManager.SetString("refresh", refresh)) { _logger.LogInformation("세션 저장 성공"); - var (hasPath, redirectPath) = await _sessionService.GetString("redirectPath"); - await _sessionService.Remove("redirectPath"); // 사용 후 세션에서 제거 + var (hasPath, redirectPath) = await _sessionManager.GetString("redirectPath"); + await _sessionManager.Remove("redirectPath"); // 사용 후 세션에서 제거 var redirectUrl = hasPath && !string.IsNullOrEmpty(redirectPath) ? $"{redirectPath}?auth=true" @@ -107,7 +108,7 @@ public class OutController: ControllerBase else if (loginResult.status.code == "001") { _logger.LogInformation("회원가입 필요"); - if (await _sessionService.SetString("snsId", snsId)) + if (await _sessionManager.SetString("snsId", snsId)) { return Redirect("/auth/register"); } diff --git a/Program/Controllers/V1/SessionController.cs b/Program/Controllers/V1/SessionController.cs new file mode 100644 index 0000000..11bef2c --- /dev/null +++ b/Program/Controllers/V1/SessionController.cs @@ -0,0 +1,37 @@ +using Back.Program.Common.Data; +using Back.Program.Services.V1.Interfaces; +using Microsoft.AspNetCore.Mvc; + +namespace Back.Program.Controllers.V1; + +/// +/// USER는 사용자가 자신의 데이터를 보거나 만들거나 하는 등 직접 사용하는 경우에 사용 +/// +[ApiController] +[Route("/api/v1/in/user")] +[ApiExplorerSettings(GroupName = "")] +public class SessionController : ControllerBase +{ + + private readonly ILogger _logger; + private readonly IRepositoryService _repositoryService; + private readonly SessionManager _sessionManager; + private readonly ISessionService _sessionService; + + private SessionController(ILogger logger, + IRepositoryService repositoryService, SessionManager sessionManager, ISessionService sessionService) + { + _logger = logger; + _repositoryService = repositoryService; + _sessionManager = sessionManager; + _sessionService = sessionService; + } + [HttpGet("session/user")] + [CustomOperation("세션 정보 확인", "세션 정보 확인", "사용자")] + public async Task GetSessionData() + { + string summary = _repositoryService.ReadSummary(typeof(UserController), "GetSessionData"); + var result = await _sessionService.GetSessionData(summary); + return Ok(result); + } +} \ No newline at end of file diff --git a/Program/Controllers/V1/UserController.cs b/Program/Controllers/V1/UserController.cs index 18fc255..aa993e1 100644 --- a/Program/Controllers/V1/UserController.cs +++ b/Program/Controllers/V1/UserController.cs @@ -17,29 +17,32 @@ namespace Back.Program.Controllers.V1 [ApiController] [Route("/api/v1/in/user")] [ApiExplorerSettings(GroupName = "사용자")] - public class UserController : ControllerBase + public class UserController( + ILogger logger, + SessionManager sessionManager, + DedicateWeb dedicateWeb, + IRepositoryService repositoryService, + IUserService userService) + : ControllerBase { - private readonly ILogger _logger; - private readonly IRepositoryService _repositoryService; - private readonly IUserService _userService; + private readonly ILogger _logger = logger; + private readonly SessionManager _sessionManager = sessionManager; - public UserController(ILogger logger, - IRepositoryService repositoryService, IUserService userService, IKakaoService kakaoService) - { - _logger = logger; - _repositoryService = repositoryService; - _userService = userService; - } - [HttpGet] [CustomOperation("회원 정보 조회", "회원 정보 조회 (자기자신)", "사용자")] public async Task GetUserData(string token) { if (string.IsNullOrEmpty(token)) return BadRequest(APIResponse.InvalidInputError()); if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); - string summary = _repositoryService.ReadSummary(typeof(UserController), "GetUserData"); + string summary = repositoryService.ReadSummary(typeof(UserController), "GetUserData"); + if (token == "VO00") + { + var (code, WebAuthResult) = await dedicateWeb.GetAuthToken(); + if (code != "000") return Ok(APIResponse.Send(code, $"{WebAuthResult}", new { })); + token = WebAuthResult; + } - var result = await _userService.GetUser(summary, token); + var result = await userService.GetUser(summary, token); return Ok(result); } @@ -52,8 +55,8 @@ namespace Back.Program.Controllers.V1 if (string.IsNullOrEmpty(accType) && string.IsNullOrEmpty(snsId)) return BadRequest(APIResponse.InvalidInputError()); if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); - string summary = _repositoryService.ReadSummary(typeof(UserController), "Login"); - var result = await _userService.Login(summary, accType, snsId); + string summary = repositoryService.ReadSummary(typeof(UserController), "Login"); + var result = await userService.Login(summary, accType, snsId); return Ok(result); } @@ -63,9 +66,9 @@ namespace Back.Program.Controllers.V1 public async Task UserRegister([FromBody] UserAll request) { if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); - string summary = _repositoryService.ReadSummary(typeof(UserController), "UserRegister"); + string summary = repositoryService.ReadSummary(typeof(UserController), "UserRegister"); - var result = await _userService.Register(summary, request); + var result = await userService.Register(summary, request); return Ok(result); } @@ -75,9 +78,9 @@ namespace Back.Program.Controllers.V1 { if (string.IsNullOrEmpty(token)) return BadRequest(APIResponse.InvalidInputError()); if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); - string summary = _repositoryService.ReadSummary(typeof(UserController), "Logout"); + string summary = repositoryService.ReadSummary(typeof(UserController), "Logout"); - var result = await _userService.Logout(summary, token); + var result = await userService.Logout(summary, token); return Ok(result); } @@ -88,8 +91,8 @@ namespace Back.Program.Controllers.V1 { if (string.IsNullOrEmpty(token)) return BadRequest(APIResponse.InvalidInputError()); if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); - string summary = _repositoryService.ReadSummary(typeof(UserController), "Cancel"); - var result = await _userService.Cancel(summary, token); + string summary = repositoryService.ReadSummary(typeof(UserController), "Cancel"); + var result = await userService.Cancel(summary, token); return Ok(result); } @@ -100,21 +103,12 @@ namespace Back.Program.Controllers.V1 { if (string.IsNullOrEmpty(token)) return BadRequest(APIResponse.InvalidInputError()); if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); - string summary = _repositoryService.ReadSummary(typeof(UserController), "ReadAcademyInfo"); + string summary = repositoryService.ReadSummary(typeof(UserController), "ReadAcademyInfo"); - var result = await _userService.GetAcademy(summary, token); + var result = await userService.GetAcademy(summary, token); return Ok(result); } - - [HttpGet("auth/session")] - [CustomOperation("세션 정보 확인", "세션 정보 확인", "사용자")] - public async Task GetSessionData() - { - string summary = _repositoryService.ReadSummary(typeof(UserController), "GetSessionData"); - var result = await _userService.GetSessionData(summary); - return Ok(result); - } - + } } diff --git a/Program/Services/V1/Interfaces/ISessionService.cs b/Program/Services/V1/Interfaces/ISessionService.cs index b78b4ba..12fcdb0 100644 --- a/Program/Services/V1/Interfaces/ISessionService.cs +++ b/Program/Services/V1/Interfaces/ISessionService.cs @@ -1,8 +1,9 @@ +using Back.Program.Common.Model; +using Back.Program.Models.Entities; + namespace Back.Program.Services.V1.Interfaces; public interface ISessionService { - Task SetString(string key, string value); - Task<(bool result, string data)> GetString(string key); - Task Remove(string key); + Task> GetSessionData(string summary); } \ No newline at end of file diff --git a/Program/Services/V1/Interfaces/IUserService.cs b/Program/Services/V1/Interfaces/IUserService.cs index 68df934..7ebd2c6 100644 --- a/Program/Services/V1/Interfaces/IUserService.cs +++ b/Program/Services/V1/Interfaces/IUserService.cs @@ -11,6 +11,5 @@ namespace Back.Program.Services.V1.Interfaces Task> Logout(string summary, string token); Task> Cancel(string summary, string token); Task> GetAcademy(string summary, string token); - Task> GetSessionData(string summary); } } \ No newline at end of file diff --git a/Program/Services/V1/SessionService.cs b/Program/Services/V1/SessionService.cs index c876b2c..a36e561 100644 --- a/Program/Services/V1/SessionService.cs +++ b/Program/Services/V1/SessionService.cs @@ -1,50 +1,111 @@ +using System.Security.Claims; +using System.Text.Json; +using Back.Program.Common.Auth; +using Back.Program.Common.Data; +using Back.Program.Common.Model; +using Back.Program.Repositories.V1.Interfaces; using Back.Program.Services.V1.Interfaces; namespace Back.Program.Services.V1; public class SessionService: ISessionService { - private readonly IHttpContextAccessor _http; - public SessionService(IHttpContextAccessor http) + private readonly ILogger _logger; + private readonly IUserRepository _userRepository; + private readonly JwtTokenService _jwtTokenService; + private readonly IRepositoryService _repositoryService; + private readonly ILogRepository _logRepository; + private readonly SessionManager _sessionManager; + private readonly IAppService _appService; + + public SessionService(ILogger logger, IUserRepository userRepository, + JwtTokenService jwtTokenService, + IRepositoryService repositoryService, ILogRepository logRepository, + SessionManager sessionManager, IAppService appService) { - _http = http; + _logger = logger; + _userRepository = userRepository; + _jwtTokenService = jwtTokenService; + _repositoryService = repositoryService; + _logRepository = logRepository; + _sessionManager = sessionManager; + _appService = appService; } - public Task SetString(string key, string value) - { - try + public async Task> GetSessionData(string summary) { - _http.HttpContext.Session.SetString(key, value); - return Task.FromResult(true); + try + { + _logger.LogInformation($"[{summary}] 세션 데이터 조회 시작"); + + // 1. 세션에서 토큰 가져오기 + var (result, token) = await _sessionManager.GetString("token"); + _logger.LogInformation($"[{summary}] 세션에서 토큰 가져오기 결과: {result}, 토큰: {token}"); + + if (!result || string.IsNullOrEmpty(token)) + { + _logger.LogWarning($"[{summary}] 세션에 토큰이 없습니다"); + return APIResponse.Send("200", "세션에 토큰이 없습니다", new { }); + } + + // 2. 토큰 검증 + var validToken = await _jwtTokenService.ValidateToken(token); + _logger.LogInformation($"[{summary}] 토큰 검증 결과: {validToken != null}"); + + if (validToken == null) + { + // 3. 토큰이 유효하지 않으면 리프레시 토큰으로 새 토큰 발급 시도 + var (refreshResult, refreshToken) = await _sessionManager.GetString("refresh"); + _logger.LogInformation($"[{summary}] 리프레시 토큰 가져오기 결과: {refreshResult}, 토큰: {refreshToken}"); + + if (!refreshResult || string.IsNullOrEmpty(refreshToken)) + { + _logger.LogWarning($"[{summary}] 리프레시 토큰이 없습니다"); + return APIResponse.Send("201", "리프레시 토큰이 없습니다", new { }); + } + + // 4. 리프레시 토큰으로 새 토큰 발급 + var retryResult = await _appService.RetryAccess(summary, refreshToken); + _logger.LogInformation($"[{summary}] 토큰 재발급 결과: {retryResult.status.code}"); + + if (retryResult.status.code == "000") + { + // 5. 새 토큰을 세션에 저장 + var data = JsonSerializer.Deserialize(JsonSerializer.Serialize(retryResult.data)); + var newToken = data.GetProperty("access").GetString(); + await _sessionManager.SetString("token", newToken); + _logger.LogInformation($"[{summary}] 새 토큰 세션 저장 완료"); + + // 6. 새 토큰으로 사용자 정보 조회 + validToken = await _jwtTokenService.ValidateToken(newToken); + } + else + { + _logger.LogWarning($"[{summary}] 토큰 갱신 실패: {retryResult.status.message}"); + return APIResponse.Send("202", "토큰 갱신 실패", new { }); + } + } + + // 7. 최종적으로 유효한 토큰으로 사용자 정보 조회 + var uid = validToken.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty; + _logger.LogInformation($"[{summary}] 사용자 ID: {uid}"); + + var user = await _userRepository.FindUser(uid); + _logger.LogInformation($"[{summary}] 사용자 정보 조회 결과: {user != null}"); + + if (user == null) + { + _logger.LogWarning($"[{summary}] 사용자 정보를 찾을 수 없습니다"); + return APIResponse.Send("203", "사용자 정보를 찾을 수 없습니다", new { }); + } + + _logger.LogInformation($"[{summary}] 세션 데이터 조회 성공: {user.name}"); + return APIResponse.Send("000", $"[{summary}], 정상", user); + } + catch (Exception ex) + { + _logger.LogError($"[{summary}] 세션 데이터 조회 중 오류: {ex.Message}"); + _logger.LogError($"[{summary}] 스택 트레이스: {ex.StackTrace}"); + return APIResponse.InternalSeverError($"[{summary}], 세션 데이터 조회 실패"); + } } - catch - { - return Task.FromResult(false); - } - } - public Task<(bool result, string data)> GetString(string key) - { - try - { - var value = _http.HttpContext.Session.GetString(key); - return Task.FromResult((true, value ?? string.Empty)); - } - catch - { - return Task.FromResult((false, "")); - } - } - public Task Remove(string key) - { - try - { - _http.HttpContext.Session.Remove(key); - return Task.FromResult(true); - } - catch - { - return Task.FromResult(false); - } - } - - } \ No newline at end of file diff --git a/Program/Services/V1/UserService.cs b/Program/Services/V1/UserService.cs index 2fe71c7..deca333 100644 --- a/Program/Services/V1/UserService.cs +++ b/Program/Services/V1/UserService.cs @@ -5,6 +5,7 @@ using Back.Program.Models.Entities; using Back.Program.Repositories.V1.Interfaces; using Back.Program.Services.V1.Interfaces; using System.Text.Json; +using Back.Program.Common.Data; namespace Back.Program.Services.V1 { @@ -15,23 +16,24 @@ namespace Back.Program.Services.V1 private readonly JwtTokenService _jwtTokenService; private readonly IRepositoryService _repositoryService; private readonly ILogRepository _logRepository; - private readonly ISessionService _sessionService; private readonly IAppService _appService; + private readonly SessionManager _sessionManager; public UserService(ILogger logger, IUserRepository userRepository, JwtTokenService jwtTokenService, IRepositoryService repositoryService, ILogRepository logRepository, - ISessionService sessionService, IAppService appService) + IAppService appService, SessionManager sessionManager) { _logger = logger; _userRepository = userRepository; _jwtTokenService = jwtTokenService; _repositoryService = repositoryService; _logRepository = logRepository; - _sessionService = sessionService; _appService = appService; + _sessionManager = sessionManager; } + public async Task> GetUser(string summary, string token) { var validToken = await _jwtTokenService.ValidateToken(token); @@ -41,7 +43,7 @@ namespace Back.Program.Services.V1 var user = await _userRepository.FindUser(uid); return APIResponse.Send("000", $"[{summary}], 정상", user); - // user 없는 경우가 없네? 그거도 만들것 + // user 없는 경우가 없네? 그거도 만들것 } public async Task> Login(string summary, string accType, string snsId) @@ -52,7 +54,7 @@ namespace Back.Program.Services.V1 var user = await _userRepository.FindUser(login.uid); if (user == null) - return APIResponse.Send("002", $"[{summary}], 회원 정보 오류", new {}); + return APIResponse.Send("002", $"[{summary}], 회원 정보 오류", new { }); user.login_date = DateTime.Now; var token = _jwtTokenService.GenerateJwtToken(user.uid); @@ -176,7 +178,7 @@ namespace Back.Program.Services.V1 refresh.revoke_Date = DateTime.Now; if (await _repositoryService.SaveData(refresh)) { - return APIResponse.Send("000", $"[{summary}], 로그아웃 정상", new {}); + return APIResponse.Send("000", $"[{summary}], 로그아웃 정상", new { }); } } @@ -190,7 +192,7 @@ namespace Back.Program.Services.V1 var uid = validToken.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty; var user = await _userRepository.FindUser(uid); - if (user == null) return APIResponse.Send("001", $"[{summary}], 회원 정보 확인 오류", new {}); + if (user == null) return APIResponse.Send("001", $"[{summary}], 회원 정보 확인 오류", new { }); if (await _repositoryService.DeleteData(user)) { @@ -206,7 +208,7 @@ namespace Back.Program.Services.V1 _logger.LogInformation($"[{summary}]: 성공 - 로그 저장 실패"); } - return APIResponse.Send("000", $"[{summary}], 정상", new {}); + return APIResponse.Send("000", $"[{summary}], 정상", new { }); } else { @@ -233,90 +235,12 @@ namespace Back.Program.Services.V1 var uid = validToken.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty; var user = await _userRepository.FindUser(uid); - if (user == null) return APIResponse.Send("001", $"[{summary}], 회원 정보 확인 오류", new {}); + if (user == null) return APIResponse.Send("001", $"[{summary}], 회원 정보 확인 오류", new { }); var academyList = await _userRepository.FindAcademies(uid); _logger.LogInformation($"[{summary}]: 성공"); return APIResponse.Send("000", $"[{summary}], 정상.", academyList); } - - public async Task> GetSessionData(string summary) - { - try - { - _logger.LogInformation($"[{summary}] 세션 데이터 조회 시작"); - - // 1. 세션에서 토큰 가져오기 - var (result, token) = await _sessionService.GetString("token"); - _logger.LogInformation($"[{summary}] 세션에서 토큰 가져오기 결과: {result}, 토큰: {token}"); - - if (!result || string.IsNullOrEmpty(token)) - { - _logger.LogWarning($"[{summary}] 세션에 토큰이 없습니다"); - return APIResponse.Send("200", "세션에 토큰이 없습니다", new { }); - } - - // 2. 토큰 검증 - var validToken = await _jwtTokenService.ValidateToken(token); - _logger.LogInformation($"[{summary}] 토큰 검증 결과: {validToken != null}"); - - if (validToken == null) - { - // 3. 토큰이 유효하지 않으면 리프레시 토큰으로 새 토큰 발급 시도 - var (refreshResult, refreshToken) = await _sessionService.GetString("refresh"); - _logger.LogInformation($"[{summary}] 리프레시 토큰 가져오기 결과: {refreshResult}, 토큰: {refreshToken}"); - - if (!refreshResult || string.IsNullOrEmpty(refreshToken)) - { - _logger.LogWarning($"[{summary}] 리프레시 토큰이 없습니다"); - return APIResponse.Send("201", "리프레시 토큰이 없습니다", new { }); - } - - // 4. 리프레시 토큰으로 새 토큰 발급 - var retryResult = await _appService.RetryAccess(summary, refreshToken); - _logger.LogInformation($"[{summary}] 토큰 재발급 결과: {retryResult.status.code}"); - - if (retryResult.status.code == "000") - { - // 5. 새 토큰을 세션에 저장 - var data = JsonSerializer.Deserialize(JsonSerializer.Serialize(retryResult.data)); - var newToken = data.GetProperty("access").GetString(); - await _sessionService.SetString("token", newToken); - _logger.LogInformation($"[{summary}] 새 토큰 세션 저장 완료"); - - // 6. 새 토큰으로 사용자 정보 조회 - validToken = await _jwtTokenService.ValidateToken(newToken); - } - else - { - _logger.LogWarning($"[{summary}] 토큰 갱신 실패: {retryResult.status.message}"); - return APIResponse.Send("202", "토큰 갱신 실패", new { }); - } - } - - // 7. 최종적으로 유효한 토큰으로 사용자 정보 조회 - var uid = validToken.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty; - _logger.LogInformation($"[{summary}] 사용자 ID: {uid}"); - - var user = await _userRepository.FindUser(uid); - _logger.LogInformation($"[{summary}] 사용자 정보 조회 결과: {user != null}"); - - if (user == null) - { - _logger.LogWarning($"[{summary}] 사용자 정보를 찾을 수 없습니다"); - return APIResponse.Send("203", "사용자 정보를 찾을 수 없습니다", new { }); - } - - _logger.LogInformation($"[{summary}] 세션 데이터 조회 성공: {user.name}"); - return APIResponse.Send("000", $"[{summary}], 정상", user); - } - catch (Exception ex) - { - _logger.LogError($"[{summary}] 세션 데이터 조회 중 오류: {ex.Message}"); - _logger.LogError($"[{summary}] 스택 트레이스: {ex.StackTrace}"); - return APIResponse.InternalSeverError($"[{summary}], 세션 데이터 조회 실패"); - } - } } } \ No newline at end of file