Auth.php 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008
  1. <?php
  2. namespace App\Controllers;
  3. require_once __DIR__ . '/../Libraries/autoload.php';
  4. use App\Models\UserListModel;
  5. use CodeIgniter\RESTful\ResourceController;
  6. use Google\Client;
  7. use Google\Service\Oauth2;
  8. use App\Libraries\JwtLib\JWT;
  9. use App\Libraries\JwtLib\Key;
  10. class Auth extends ResourceController
  11. {
  12. private const FRONTEND_BASE_URL = "http://localhost:3000";
  13. //private const FRONTEND_BASE_URL = 'https://shopdeli.mycafe24.com';
  14. protected $userModel;
  15. public function __construct()
  16. {
  17. $this->userModel = new UserListModel();
  18. }
  19. //구글 로그인 콜백(인증)
  20. public function callback()
  21. {
  22. if (isset($_GET['code'])) {
  23. $code = $_GET['code'];
  24. // Google 클라이언트 설정
  25. $client = new Client();
  26. $client->setClientId('373780605211-diojebh7mug45urv9rnqdil6n0b1ogge.apps.googleusercontent.com');
  27. $client->setClientSecret('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');
  28. $client->setRedirectUri('https://shopdeli.mycafe24.com/auth/callback');
  29. $client->setAccessType('offline'); // 인증 요청 전에 지정!
  30. $client->authenticate($code);
  31. $googleAccessToken = $client->getAccessToken();
  32. // 별도의 변수로 분리
  33. $googleOAuthAccessToken = $googleAccessToken['access_token'] ?? null;
  34. $googleOAuthRefreshToken = $googleAccessToken['refresh_token'] ?? null;
  35. // 업데이트: setAccessToken에는 전체 배열을 넣을 수 있지만
  36. // accessToken 만 넣으려면 아래처럼 쓸 수 있습니다.
  37. if ($googleOAuthAccessToken) {
  38. $client->setAccessToken($googleOAuthAccessToken);
  39. }
  40. // 사용자 정보 가져오기
  41. $oauth2 = new Oauth2($client);
  42. $userInfo = $oauth2->userinfo->get();
  43. // 사용자 정보 변수 지정
  44. $id = $userInfo->id;
  45. $authenticatedEmail = $userInfo->email;
  46. $name = $userInfo->name;
  47. // DB Connection (CodeIgniter 4)
  48. $db = \Config\Database::connect();
  49. // 1. USER_LIST에서 이메일로 조회
  50. $builder = $db->table('USER_LIST');
  51. $user = $builder->where('EMAIL', $authenticatedEmail)->get()->getRowArray();
  52. if ($user) {
  53. $jwtSecret = env('JWT_SECRET');
  54. $kid = env('JWT_KID');
  55. if (empty($jwtSecret) || empty($kid)) {
  56. return $this->failServerError('환경변수가 누락되었습니다. 관리자에게 문의하세요.');
  57. }
  58. if (!class_exists('\App\Libraries\JwtLib\JWT')) {
  59. return $this->failServerError('JWT 라이브러리를 찾을 수 없습니다.');
  60. }
  61. $issuedAt = time();
  62. $accessExpire = $issuedAt + 60 * 15; // 15분
  63. $refreshExpire = $issuedAt + 60 * 60 * 24 * 14; // 14일
  64. $accessPayload = [
  65. 'iat' => $issuedAt,
  66. 'exp' => $accessExpire,
  67. 'sub' => $id,
  68. 'name' => $name ?? '',
  69. ];
  70. $refreshPayload = [
  71. 'iat' => $issuedAt,
  72. 'exp' => $refreshExpire,
  73. 'sub' => $id,
  74. ];
  75. try {
  76. $accessToken = JWT::encode($accessPayload, $jwtSecret, 'HS256', $kid);
  77. $refreshToken = JWT::encode($refreshPayload, $jwtSecret, 'HS256', $kid);
  78. // (선택) DB의 리프레시 토큰 값 업데이트
  79. $db = \Config\Database::connect();
  80. $builder = $db->table('USER_LIST');
  81. $builder->where('ID', $id)->update(['REFRESH_TOKEN' => $refreshToken]);
  82. } catch (\Throwable $e) {
  83. return $this->failServerError('JWT 생성 오류: ' . $e->getMessage());
  84. }
  85. unset($user['PASSWORD']); // 혹시 비번이 있다면 노출 방지
  86. $query = http_build_query([
  87. "accessToken" => $accessToken,
  88. "refreshToken" => $refreshToken,
  89. "user" => urlencode(json_encode($user)),
  90. ]);
  91. //return redirect()->to(base_url("auth/popupClose?$query"));
  92. // 개발진행중 풀 URL
  93. return redirect()->to(self::FRONTEND_BASE_URL."/auth/popupClose?$query");
  94. } else {
  95. // 회원이 없으면 회원가입
  96. $type = 1;
  97. // ID는 auto_increment라면 생략
  98. $authData = [
  99. 'ID' => $id,
  100. 'NAME' => $name,
  101. 'EMAIL' => $authenticatedEmail,
  102. 'TYPE' => $type,
  103. 'JOIN' => '1',
  104. 'GOOGLE_REFRESH_TOKEN' => $googleOAuthRefreshToken ?? null
  105. ];
  106. $query = http_build_query([
  107. "user" => urlencode(json_encode($authData)),
  108. ]);
  109. //return redirect()->to(base_url("auth/popupClose?$query"));
  110. // 개발진행중 풀 URL
  111. return redirect()->to(self::FRONTEND_BASE_URL."/auth/popupClose?$query");
  112. }
  113. } else {
  114. echo "인증코드가 없습니다.";
  115. }
  116. }
  117. public function join()
  118. {
  119. $postData = $this->request->getJSON(true);
  120. // 디버깅: 받은 데이터 로그
  121. error_log('Join 요청 데이터: ' . json_encode($postData));
  122. // 필수값 추출
  123. $id = $postData['ID'] ?? null;
  124. $password = $postData['PASSWORD'] ?? null;
  125. $name = $postData['NAME'] ?? null;
  126. $nick_name = $postData['NICK_NAME'] ?? null;
  127. $phone = $postData['PHONE'] ?? null;
  128. $email = $postData['EMAIL'] ?? null;
  129. $sns_type = $postData['SNS_TYPE'] ?? null;
  130. $sns_link_id = $postData['SNS_LINK_ID'] ?? null;
  131. $add_info1 = $postData['ADD_INFO1'] ?? null;
  132. $google_refresh_token = $postData['GOOGLE_REFRESH_TOKEN'] ?? null;
  133. $type = $postData['TYPE'] ?? null;
  134. $privacy_agree = $postData['PRIVACY_AGREE'] ?? 'N';
  135. $thirdparty_agree = $postData['THIRDPARTY_AGREE'] ?? 'N';
  136. // 필수값 검증
  137. if (empty($name) || empty($email)) {
  138. return redirect()->back()->with('error', '필수 정보를 입력해 주세요.');
  139. }
  140. // insert용 데이터 준비 - 동의 필드 강제 설정으로 테스트
  141. $insertData = [
  142. 'ID' => $id,
  143. 'PASSWORD' => $password,
  144. 'NAME' => $name,
  145. 'NICK_NAME' => $nick_name,
  146. 'PHONE' => $phone,
  147. 'EMAIL' => $email,
  148. 'SNS_TYPE' => $sns_type,
  149. 'SNS_LINK_ID' => $sns_link_id,
  150. 'ADD_INFO1' => $add_info1,
  151. 'GOOGLE_REFRESH_TOKEN' => $google_refresh_token,
  152. 'TYPE' => $type,
  153. 'PRIVACY_AGREE' => $privacy_agree,
  154. 'THIRDPARTY_AGREE' => $thirdparty_agree
  155. ];
  156. // 디버깅: INSERT 데이터 로그
  157. error_log('INSERT 데이터: ' . json_encode($insertData));
  158. if (!empty($password)) {
  159. $insertData['PASSWORD'] = password_hash($password, PASSWORD_DEFAULT);
  160. }
  161. // DB 연결 및 INSERT
  162. try {
  163. $db = \Config\Database::connect();
  164. $builder = $db->table('USER_LIST');
  165. $result = $builder->insert($insertData);
  166. if (!$result) {
  167. $error = $db->error();
  168. return $this->response->setJSON([
  169. 'status' => 'fail',
  170. 'message' => 'DB Error: '.$error['message']
  171. ])->setStatusCode(500);
  172. }
  173. } catch (\Throwable $e) {
  174. return $this->response->setJSON([
  175. 'status' => 'fail',
  176. 'message' => 'Exception: ' . $e->getMessage()
  177. ])->setStatusCode(500);
  178. }
  179. // 회원가입 성공 시, JSON 응답으로 결과 반환 (200 OK)
  180. return $this->response->setJSON([
  181. 'status' => 'success',
  182. 'message' => '회원가입이 완료되었습니다.'
  183. ])->setStatusCode(200);
  184. }
  185. public function joinVendor()
  186. {
  187. $postData = $this->request->getJSON(true);
  188. // 필수값 추출
  189. $id = $postData['ID'] ?? null;
  190. $password = $postData['PASSWORD'] ?? null;
  191. $name = $postData['NAME'] ?? null;
  192. $company_name = $postData['COMPANY_NAME'] ?? null;
  193. $company_number = $postData['COMPANY_NUMBER'] ?? null;
  194. $hp = $postData['HP'] ?? null;
  195. $email = $postData['EMAIL'] ?? null;
  196. $privacy_agree = $postData['PRIVACY_AGREE'] ?? 'N';
  197. $thirdparty_agree = $postData['THIRDPARTY_AGREE'] ?? 'N';
  198. // 필수값 검증
  199. if (empty($name) || empty($email)) {
  200. return redirect()->back()->with('error', '필수 정보를 입력해 주세요.');
  201. }
  202. // insert용 데이터 준비
  203. $insertData = [
  204. 'ID' => $id,
  205. 'PASSWORD' => $password,
  206. 'NAME' => $name,
  207. 'COMPANY_NAME' => $company_name,
  208. 'HP' => $hp,
  209. 'EMAIL' => $email,
  210. 'COMPANY_NUMBER' => $company_number,
  211. 'PRIVACY_AGREE' => $privacy_agree,
  212. 'THIRDPARTY_AGREE' => $thirdparty_agree,
  213. ];
  214. if (!empty($password)) {
  215. $insertData['PASSWORD'] = password_hash($password, PASSWORD_DEFAULT);
  216. }
  217. // DB 연결 및 INSERT
  218. try {
  219. $db = \Config\Database::connect();
  220. $builder = $db->table('VENDOR_LIST');
  221. $result = $builder->insert($insertData);
  222. if (!$result) {
  223. $error = $db->error();
  224. return $this->response->setJSON([
  225. 'status' => 'fail',
  226. 'message' => 'DB Error: '.$error['message']
  227. ])->setStatusCode(500);
  228. }
  229. } catch (\Throwable $e) {
  230. return $this->response->setJSON([
  231. 'status' => 'fail',
  232. 'message' => 'Exception: ' . $e->getMessage()
  233. ])->setStatusCode(500);
  234. }
  235. // 회원가입 성공 시, JSON 응답으로 결과 반환 (200 OK)
  236. return $this->response->setJSON([
  237. 'status' => 'success',
  238. 'message' => '회원가입이 완료되었습니다.'
  239. ])->setStatusCode(200);
  240. }
  241. public function joinBrand()
  242. {
  243. $postData = $this->request->getJSON(true);
  244. // 필수값 추출
  245. $id = $postData['ID'] ?? null;
  246. $password = $postData['PASSWORD'] ?? null;
  247. $name = $postData['NAME'] ?? null;
  248. $company_name = $postData['COMPANY_NAME'] ?? null;
  249. $company_number = $postData['COMPANY_NUMBER'] ?? null;
  250. $hp = $postData['HP'] ?? null;
  251. $email = $postData['EMAIL'] ?? null;
  252. $privacy_agree = $postData['PRIVACY_AGREE'] ?? 'N';
  253. $thirdparty_agree = $postData['THIRDPARTY_AGREE'] ?? 'N';
  254. // 필수값 검증
  255. if (empty($name) || empty($email)) {
  256. return redirect()->back()->with('error', '필수 정보를 입력해 주세요.');
  257. }
  258. // insert용 데이터 준비
  259. $insertData = [
  260. 'ID' => $id,
  261. 'PASSWORD' => $password,
  262. 'NAME' => $name,
  263. 'COMPANY_NAME' => $company_name,
  264. 'HP' => $hp,
  265. 'EMAIL' => $email,
  266. 'COMPANY_NUMBER' => $company_number,
  267. 'PRIVACY_AGREE' => $privacy_agree,
  268. 'THIRDPARTY_AGREE' => $thirdparty_agree,
  269. ];
  270. if (!empty($password)) {
  271. $insertData['PASSWORD'] = password_hash($password, PASSWORD_DEFAULT);
  272. }
  273. // DB 연결 및 INSERT
  274. try {
  275. $db = \Config\Database::connect();
  276. $builder = $db->table('BRAND_LIST');
  277. $result = $builder->insert($insertData);
  278. if (!$result) {
  279. $error = $db->error();
  280. return $this->response->setJSON([
  281. 'status' => 'fail',
  282. 'message' => 'DB Error: '.$error['message']
  283. ])->setStatusCode(500);
  284. }
  285. } catch (\Throwable $e) {
  286. return $this->response->setJSON([
  287. 'status' => 'fail',
  288. 'message' => 'Exception: ' . $e->getMessage()
  289. ])->setStatusCode(500);
  290. }
  291. // 회원가입 성공 시, JSON 응답으로 결과 반환 (200 OK)
  292. return $this->response->setJSON([
  293. 'status' => 'success',
  294. 'message' => '회원가입이 완료되었습니다.'
  295. ])->setStatusCode(200);
  296. }
  297. //구글 로그인 환경 회원 탈퇴
  298. public function withdrawal()
  299. {
  300. // 1. 요청에서 사용자 SEQ 추출 (예: POST or GET)
  301. $postData = $this->request->getJSON(true);
  302. $seq = $postData['SEQ'];
  303. $googleAccessToken = $postData['GOOGLE_REFRESH_TOKEN'];
  304. // 2. 사용자 정보 조회 (구글 토큰 포함)
  305. $user = $this->userModel->find($seq);
  306. if (!$user) {
  307. return $this->response->setJSON(['error' => 'User not found'])->setStatusCode(404);
  308. }
  309. // 3. 구글 인증 연결 해제(access_token 필요)
  310. if ($googleAccessToken) {
  311. $this->revokeGoogleAccess($googleAccessToken);
  312. }
  313. // 4. USER_LIST에서 사용자 삭제
  314. $this->userModel->delete($seq);
  315. // 5. 응답 반환
  316. return $this->response->setJSON([
  317. 'result' => 'User account deleted and Google link revoked'
  318. ])->setStatusCode(200);
  319. }
  320. /***********************************
  321. * 카카오 간편로그인 / 가입
  322. **********************************/
  323. //카카오 인증
  324. public function kakaoLogin()
  325. {
  326. $clientId = '1f8376b18a02a00f2e4e5594f9ace6d4'; // 카카오 REST API 키로 변경
  327. $redirectUri = urlencode('https://shopdeli.mycafe24.com/auth/kakao'); // 콜백 URL (ex: https://도메인/auth/kakaoCallback)
  328. $url = "https://kauth.kakao.com/oauth/authorize?client_id={$clientId}&redirect_uri={$redirectUri}&response_type=code";
  329. return redirect()->to($url);
  330. }
  331. //카카오 인증후 진행
  332. public function kakao()
  333. {
  334. $code = $this->request->getGet('code');
  335. $clientId = '1f8376b18a02a00f2e4e5594f9ace6d4'; // 카카오 REST API 키
  336. $redirectUri = 'https://shopdeli.mycafe24.com/auth/kakao'; // 콜백 URL
  337. $tokenUrl = "https://kauth.kakao.com/oauth/token";
  338. // 토큰 요청
  339. $tokenData = [
  340. 'grant_type' => 'authorization_code',
  341. 'scopes' => 'offline_access',
  342. 'client_id' => $clientId,
  343. 'redirect_uri' => $redirectUri,
  344. 'code' => $code,
  345. ];
  346. $client = \Config\Services::curlrequest();
  347. $tokenResponse = $client->post($tokenUrl, [
  348. 'form_params' => $tokenData
  349. ]);
  350. $tokenResult = json_decode($tokenResponse->getBody(), true);
  351. $accessTokenKakao = $tokenResult['access_token'];
  352. $refreshTokenKakao = $tokenResult['refresh_token'];
  353. // 사용자 정보 요청
  354. $userUrl = "https://kapi.kakao.com/v2/user/me";
  355. $userResponse = $client->get($userUrl, [
  356. 'headers' => [
  357. 'Authorization' => 'Bearer ' . $accessTokenKakao,
  358. ]
  359. ]);
  360. $userInfo = json_decode($userResponse->getBody(), true);
  361. $userInfo['access_token'] = $accessTokenKakao;
  362. $userInfo['refresh_token'] = $refreshTokenKakao; //회원탈퇴시 이용 로그인시 마다 신규 리프래시 토큰 발급 받게 됨
  363. // 여기에 회원가입/로그인 처리를 구현하세요
  364. // 예: $userInfo['kakao_account']['email'], $userInfo['properties']['nickname']
  365. //DB 커넥션
  366. $db = \Config\Database::connect();
  367. // 1. USER_LIST에서 이메일로 조회
  368. $id = $userInfo['id'];
  369. $email = $userInfo['kakao_account']['email'];
  370. $builder = $db->table('USER_LIST');
  371. $user = $builder->where('EMAIL', $email)->get()->getRowArray();
  372. if($user){
  373. $jwtSecret = env('JWT_SECRET');
  374. $kid = env('JWT_KID');
  375. if (empty($jwtSecret) || empty($kid)) {
  376. return $this->failServerError('환경변수가 누락되었습니다. 관리자에게 문의하세요.');
  377. }
  378. if (!class_exists('\App\Libraries\JwtLib\JWT')) {
  379. return $this->failServerError('JWT 라이브러리를 찾을 수 없습니다.');
  380. }
  381. $issuedAt = time();
  382. $accessExpire = $issuedAt + 60 * 15; // 15분
  383. $refreshExpire = $issuedAt + 60 * 60 * 24 * 14; // 14일
  384. $accessPayload = [
  385. 'iat' => $issuedAt,
  386. 'exp' => $accessExpire,
  387. 'sub' => $id,
  388. //'name' => $name ?? '',
  389. ];
  390. $refreshPayload = [
  391. 'iat' => $issuedAt,
  392. 'exp' => $refreshExpire,
  393. 'sub' => $id,
  394. ];
  395. try {
  396. $accessToken = JWT::encode($accessPayload, $jwtSecret, 'HS256', $kid);
  397. $refreshToken = JWT::encode($refreshPayload, $jwtSecret, 'HS256', $kid);
  398. // (선택) DB의 리프레시 토큰 값 업데이트
  399. $db = \Config\Database::connect();
  400. $builder = $db->table('USER_LIST');
  401. $builder->where('ID', $id)->update([
  402. 'REFRESH_TOKEN' => $refreshToken,
  403. 'KAKAO_REFRESH_TOKEN' => $userInfo['refresh_token']
  404. ]);
  405. } catch (\Throwable $e) {
  406. return $this->failServerError('JWT 생성 오류: ' . $e->getMessage());
  407. }
  408. unset($user['PASSWORD']); // 혹시 비번이 있다면 노출 방지
  409. $query = http_build_query([
  410. "accessToken" => $accessToken,
  411. "refreshToken" => $refreshToken,
  412. "user" => urlencode(json_encode($user)),
  413. ]);
  414. //return redirect()->to(base_url("auth/popupClose?$query"));
  415. // 개발진행중 풀 URL
  416. return redirect()->to(self::FRONTEND_BASE_URL."/auth/popupClose?$query");
  417. }else{
  418. // 회원이 없으면 회원가입
  419. $type = 1;
  420. // ID는 auto_increment라면 생략
  421. $authData = [
  422. 'ID' => $id,
  423. //'NAME' => $name,
  424. 'EMAIL' => $email,
  425. 'TYPE' => $type,
  426. 'JOIN' => '1',
  427. 'KAKAO_REFRESH_TOKEN' => $userInfo['refresh_token'] ?? null
  428. ];
  429. $query = http_build_query([
  430. "user" => urlencode(json_encode($authData)),
  431. ]);
  432. //return redirect()->to(base_url("auth/popupClose?$query"));
  433. // 개발진행중 풀 URL
  434. return redirect()->to(self::FRONTEND_BASE_URL."/auth/popupClose?$query");
  435. }
  436. }
  437. //카카오 회원탈퇴
  438. public function kakaoWithdrawal(){
  439. // 1. 요청에서 사용자 SEQ 추출 (예: POST or GET)
  440. $postData = $this->request->getJSON(true);
  441. $seq = $postData['SEQ'];
  442. $refreshTokenKaKao = $postData['KAKAO_REFRESH_TOKEN'];
  443. // 2. 사용자 정보 조회 (구글 토큰 포함)
  444. $user = $this->userModel->find($seq);
  445. if (!$user) {
  446. return $this->response->setJSON(['error' => 'User not found'])->setStatusCode(404);
  447. }
  448. // 3. 카카오 언링크 처리
  449. $tokenUrl = "https://kauth.kakao.com/oauth/token";
  450. $tokenData = [
  451. 'grant_type' => 'refresh_token',
  452. 'client_id' => '1f8376b18a02a00f2e4e5594f9ace6d4',
  453. 'refresh_token' => $refreshTokenKaKao, // DB에서 불러온 값
  454. ];
  455. $client = \Config\Services::curlrequest();
  456. $tokenResponse = $client->post($tokenUrl, [
  457. 'form_params' => $tokenData
  458. ]);
  459. $tokenResult = json_decode($tokenResponse->getBody(), true);
  460. $accessTokenKakao = $tokenResult['access_token'];
  461. // (2) 발급받은 access_token으로 연결 끊기 요청
  462. $userUnlinkUrl = "https://kapi.kakao.com/v1/user/unlink";
  463. $response = $client->post($userUnlinkUrl, [
  464. 'headers' => [
  465. 'Authorization' => 'Bearer ' . $accessTokenKakao,
  466. ]
  467. ]);
  468. // 4. USER_LIST에서 사용자 삭제
  469. $this->userModel->delete($seq);
  470. // 5. 응답 반환
  471. return $this->response->setJSON([
  472. 'result' => 'User account deleted and Google link revoked'
  473. ])->setStatusCode(200);
  474. }
  475. /***********************************
  476. * 내아버 간편로그인 / 가입
  477. **********************************/
  478. //네이버 인증
  479. public function naverLogin()
  480. {
  481. $client_id = 'tPw7dRu1r7yY89O5gN7n';
  482. $redirect_uri = urlencode('https://shopdeli.mycafe24.com/auth/naver'); // ex) https://your.site/naver-callback
  483. $state = bin2hex(random_bytes(8));
  484. session()->set('naver_state', $state);
  485. $naver_auth_url = "https://nid.naver.com/oauth2.0/authorize?"
  486. . "response_type=code"
  487. . "&client_id={$client_id}"
  488. . "&client_icon=https://ndevthumb-phinf.pstatic.net/20250708_43/1751954347202gc3db_JPEG/x49C3CDfcWcI20250708145907.jpeg"
  489. . "&redirect_uri={$redirect_uri}"
  490. . "&state={$state}";
  491. // 네이버 인증/동의화면으로 리디렉트
  492. return redirect()->to($naver_auth_url);
  493. }
  494. public function naver(){
  495. $client_id = 'tPw7dRu1r7yY89O5gN7n';
  496. $client_secret = 'Pgan4lv9l9';
  497. $state = $this->request->getGet('state');
  498. $code = $this->request->getGet('code');
  499. if ($state !== session()->get('naver_state')) {
  500. exit('CSRF 실패');
  501. }
  502. // 토큰 발급
  503. $token_url = "https://nid.naver.com/oauth2.0/token"
  504. . "?grant_type=authorization_code"
  505. . "&client_id={$client_id}"
  506. . "&client_secret={$client_secret}"
  507. . "&code={$code}"
  508. . "&state={$state}";
  509. // cURL로 토큰 요청
  510. $ch = curl_init();
  511. curl_setopt($ch, CURLOPT_URL, $token_url);
  512. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  513. $token_response = curl_exec($ch);
  514. curl_close($ch);
  515. // JSON 파싱
  516. $token_data = json_decode($token_response, true);
  517. $access_token = $token_data['access_token'] ?? null;
  518. // 회원 정보 요청
  519. if ($access_token) {
  520. $headers = [
  521. "Authorization: Bearer {$access_token}"
  522. ];
  523. $ch = curl_init();
  524. curl_setopt($ch, CURLOPT_URL, "https://openapi.naver.com/v1/nid/me");
  525. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  526. curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
  527. $user_info = curl_exec($ch);
  528. curl_close($ch);
  529. $user_info_arr = json_decode($user_info, true);
  530. $user_info_arr['refresh_token'] = $token_data['refresh_token'];
  531. $user_info_arr['access_token'] = $token_data['access_token'];
  532. //DB 커넥션
  533. $db = \Config\Database::connect();
  534. // 1. USER_LIST에서 이메일로 조회
  535. $id = $user_info_arr['response']['id'];
  536. $email = $user_info_arr['response']['email'];
  537. $name = $user_info_arr['response']['name'];
  538. $phone = $user_info_arr['response']['mobile'];
  539. $builder = $db->table('USER_LIST');
  540. $user = $builder->where('EMAIL', $email)->get()->getRowArray();
  541. if($user){
  542. $jwtSecret = env('JWT_SECRET');
  543. $kid = env('JWT_KID');
  544. if (empty($jwtSecret) || empty($kid)) {
  545. return $this->failServerError('환경변수가 누락되었습니다. 관리자에게 문의하세요.');
  546. }
  547. if (!class_exists('\App\Libraries\JwtLib\JWT')) {
  548. return $this->failServerError('JWT 라이브러리를 찾을 수 없습니다.');
  549. }
  550. $issuedAt = time();
  551. $accessExpire = $issuedAt + 60 * 15; // 15분
  552. $refreshExpire = $issuedAt + 60 * 60 * 24 * 14; // 14일
  553. $accessPayload = [
  554. 'iat' => $issuedAt,
  555. 'exp' => $accessExpire,
  556. 'sub' => $id,
  557. 'name' => $name ?? '',
  558. ];
  559. $refreshPayload = [
  560. 'iat' => $issuedAt,
  561. 'exp' => $refreshExpire,
  562. 'sub' => $id,
  563. ];
  564. try {
  565. $accessToken = JWT::encode($accessPayload, $jwtSecret, 'HS256', $kid);
  566. $refreshToken = JWT::encode($refreshPayload, $jwtSecret, 'HS256', $kid);
  567. // (선택) DB의 리프레시 토큰 값 업데이트
  568. $db = \Config\Database::connect();
  569. $builder = $db->table('USER_LIST');
  570. $builder->where('ID', $id)->update([
  571. 'REFRESH_TOKEN' => $refreshToken,
  572. 'NAVER_REFRESH_TOKEN' => $user_info_arr['refresh_token']
  573. ]);
  574. } catch (\Throwable $e) {
  575. return $this->failServerError('JWT 생성 오류: ' . $e->getMessage());
  576. }
  577. unset($user['PASSWORD']); // 혹시 비번이 있다면 노출 방지
  578. $query = http_build_query([
  579. "accessToken" => $accessToken,
  580. "refreshToken" => $refreshToken,
  581. "user" => urlencode(json_encode($user)),
  582. ]);
  583. //return redirect()->to(base_url("auth/popupClose?$query"));
  584. // 개발진행중 풀 URL
  585. return redirect()->to(self::FRONTEND_BASE_URL."/auth/popupClose?$query");
  586. }else{
  587. // 회원이 없으면 회원가입
  588. $type = 1;
  589. // ID는 auto_increment라면 생략
  590. $authData = [
  591. 'ID' => $id,
  592. 'NAME' => $name,
  593. 'PHONE' => $phone,
  594. 'EMAIL' => $email,
  595. 'TYPE' => $type,
  596. 'JOIN' => '1',
  597. 'NAVER_REFRESH_TOKEN' => $user_info_arr['refresh_token'] ?? null
  598. ];
  599. $query = http_build_query([
  600. "user" => urlencode(json_encode($authData)),
  601. ]);
  602. //return redirect()->to(base_url("auth/popupClose?$query"));
  603. // 개발진행중 풀 URL
  604. return redirect()->to(self::FRONTEND_BASE_URL."/auth/popupClose?$query");
  605. }
  606. } else {
  607. // 오류 처리
  608. }
  609. }
  610. public function naverWithdrawal($refreshToken)
  611. {
  612. // 1. 요청에서 사용자 SEQ 추출 (예: POST or GET)
  613. $postData = $this->request->getJSON(true);
  614. $seq = $postData['SEQ'];
  615. $refreshTokenNaver = $postData['NAVER_REFRESH_TOKEN'];
  616. // 2. 사용자 정보 조회 (구글 토큰 포함)
  617. $user = $this->userModel->find($seq);
  618. if (!$user) {
  619. return $this->response->setJSON(['error' => 'User not found'])->setStatusCode(404);
  620. }
  621. $clientId = 'tPw7dRu1r7yY89O5gN7n';
  622. $clientSecret = 'Pgan4lv9l9';
  623. // 1. 리프레시 토큰으로 엑세스 토큰 재발급 요청
  624. $tokenUrl = "https://nid.naver.com/oauth2.0/token";
  625. $tokenParams = [
  626. 'grant_type' => 'refresh_token',
  627. 'client_id' => $clientId,
  628. 'client_secret' => $clientSecret,
  629. 'refresh_token' => $refreshTokenNaver
  630. ];
  631. $ch = curl_init();
  632. curl_setopt($ch, CURLOPT_URL, $tokenUrl);
  633. curl_setopt($ch, CURLOPT_POST, true);
  634. curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($tokenParams));
  635. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  636. $tokenResponse = curl_exec($ch);
  637. $tokenError = curl_error($ch);
  638. curl_close($ch);
  639. if ($tokenError) {
  640. return [
  641. 'success' => false,
  642. 'message' => "토큰 재발급 오류: " . $tokenError,
  643. ];
  644. }
  645. $tokenData = json_decode($tokenResponse, true);
  646. if (empty($tokenData['access_token'])) {
  647. return [
  648. 'success' => false,
  649. 'message' => '엑세스 토큰 재발급 실패: ' . ($tokenData['error_description'] ?? '알 수 없는 오류'),
  650. ];
  651. }
  652. $accessToken = $tokenData['access_token'];
  653. // 2. 엑세스 토큰으로 연결끊기
  654. $withdrawUrl = "https://nid.naver.com/oauth2.0/token";
  655. $withdrawParams = [
  656. 'grant_type' => 'delete',
  657. 'client_id' => $clientId,
  658. 'client_secret' => $clientSecret,
  659. 'access_token' => $accessToken,
  660. 'service_provider' => 'NAVER',
  661. ];
  662. $ch2 = curl_init();
  663. curl_setopt($ch2, CURLOPT_URL, $withdrawUrl);
  664. curl_setopt($ch2, CURLOPT_POST, true);
  665. curl_setopt($ch2, CURLOPT_POSTFIELDS, http_build_query($withdrawParams));
  666. curl_setopt($ch2, CURLOPT_RETURNTRANSFER, true);
  667. $withdrawResponse = curl_exec($ch2);
  668. $withdrawError = curl_error($ch2);
  669. curl_close($ch2);
  670. if ($withdrawError) {
  671. return [
  672. 'success' => false,
  673. 'message' => "연결해제 요청 오류: " . $withdrawError,
  674. ];
  675. }
  676. $withdrawData = json_decode($withdrawResponse, true);
  677. if (isset($withdrawData['result']) && $withdrawData['result'] === 'success') {
  678. return [
  679. 'success' => true,
  680. 'message' => '네이버 연동 해제 완료',
  681. ];
  682. // 4. USER_LIST에서 사용자 삭제
  683. $this->userModel->delete($seq);
  684. // 5. 응답 반환
  685. return $this->response->setJSON([
  686. 'result' => 'User account deleted and Google link revoked'
  687. ])->setStatusCode(200);
  688. } else {
  689. return [
  690. 'success' => false,
  691. 'message' => $withdrawData['error'] ?? '연동 해제 실패',
  692. ];
  693. }
  694. }
  695. /***********************************
  696. * @param $refreshTokenGoogle
  697. * @return void
  698. **********************************/
  699. // 구글 연결 가입정보 연결 해제(회원가입중 페이지 빠져나올경우 리프래시 토큰이 사라짐 고객이 직접 계정에서 해제 필요)
  700. protected function revokeGoogleAccess($refreshTokenGoogle)
  701. {
  702. // 1. 리프레시 토큰으로 엑세스 토큰 발급
  703. $client_id = '373780605211-diojebh7mug45urv9rnqdil6n0b1ogge.apps.googleusercontent.com';
  704. $client_secret = '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';
  705. $refresh_token = $refreshTokenGoogle;
  706. $token_url = 'https://oauth2.googleapis.com/token';
  707. $params = [
  708. 'client_id' => $client_id,
  709. 'client_secret' => $client_secret,
  710. 'refresh_token' => $refresh_token,
  711. 'grant_type' => 'refresh_token',
  712. ];
  713. $ch = curl_init();
  714. curl_setopt($ch, CURLOPT_URL, $token_url);
  715. curl_setopt($ch, CURLOPT_POST, true);
  716. curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params));
  717. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  718. $response = curl_exec($ch);
  719. curl_close($ch);
  720. $data = json_decode($response, true);
  721. // 2. 새 엑세스 토큰으로 연결 끊기
  722. if (!empty($data['access_token'])) {
  723. $accessToken = $data['access_token'];
  724. $revoke_url = 'https://accounts.google.com/o/oauth2/revoke?token=' . urlencode($accessToken);
  725. $ch = curl_init($revoke_url);
  726. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  727. curl_exec($ch);
  728. curl_close($ch);
  729. } else {
  730. // TODO : 엑세스 토큰 발급 실패 처리 필요
  731. }
  732. }
  733. /***********************************
  734. * 아이디 체크
  735. **********************************/
  736. public function checkId()
  737. {
  738. $postData = $this->request->getJSON(true);
  739. // POST 데이터에서 값 추출
  740. $id = $postData['id'] ?? null;
  741. $type = $postData['type'] ?? null;
  742. // 필수값 검증
  743. if (empty($id) || empty($type)) {
  744. return $this->response->setJSON([
  745. 'status' => 'fail',
  746. 'message' => '필수 정보가 누락되었습니다.'
  747. ])->setStatusCode(400);
  748. }
  749. $db = \Config\Database::connect();
  750. try {
  751. // type에 따라 다른 테이블에서 ID 중복 체크
  752. if ($type === 'vendor') {
  753. // 벤더 회원가입의 경우 VENDOR_LIST 테이블 체크
  754. $builder = $db->table('VENDOR_LIST');
  755. $existingUser = $builder->where('ID', $id)->get()->getRowArray();
  756. if ($existingUser) {
  757. return $this->response->setJSON([
  758. 'isDuplicate' => true,
  759. 'message' => '이미 사용 중인 아이디입니다.'
  760. ])->setStatusCode(200);
  761. }
  762. } elseif ($type === 'brand') {
  763. // 브랜드 회원가입의 경우 BRAND_LIST 테이블 체크
  764. $builder = $db->table('BRAND_LIST');
  765. $existingUser = $builder->where('ID', $id)->get()->getRowArray();
  766. if ($existingUser) {
  767. return $this->response->setJSON([
  768. 'isDuplicate' => true,
  769. 'message' => '이미 사용 중인 아이디입니다.'
  770. ])->setStatusCode(200);
  771. }
  772. } elseif ($type === 'influence' || $type === 'influencer') {
  773. // 인플루언서(일반회원가입)의 경우 USER_LIST 테이블 체크
  774. $builder = $db->table('USER_LIST');
  775. $existingUser = $builder->where('ID', $id)->get()->getRowArray();
  776. if ($existingUser) {
  777. return $this->response->setJSON([
  778. 'isDuplicate' => true,
  779. 'message' => '이미 사용 중인 아이디입니다.'
  780. ])->setStatusCode(200);
  781. }
  782. } else {
  783. return $this->response->setJSON([
  784. 'status' => 'fail',
  785. 'message' => '유효하지 않은 회원 유형입니다.'
  786. ])->setStatusCode(400);
  787. }
  788. // ID가 사용 가능한 경우
  789. return $this->response->setJSON([
  790. 'isDuplicate' => false,
  791. 'message' => '사용 가능한 아이디입니다.'
  792. ])->setStatusCode(200);
  793. } catch (\Throwable $e) {
  794. return $this->response->setJSON([
  795. 'status' => 'fail',
  796. 'message' => 'DB 오류: ' . $e->getMessage()
  797. ])->setStatusCode(500);
  798. }
  799. }
  800. }