From a35d8bc608c94266d34ed7dfdd61fc65e17e7541 Mon Sep 17 00:00:00 2001 From: hide_d Date: Mon, 8 Nov 2021 04:18:36 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=9E=90=EB=8F=99=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- f_install/sql/common_schema.sql | 4 +- hwe/ts/gateway/login.ts | 118 +++++++++++++++- oauth_kakao/j_login_oauth.php | 198 ++++++++++++++++----------- src/sammo/API/Login/LoginByID.php | 61 +++++++-- src/sammo/API/Login/LoginByToken.php | 137 ++++++++++++++++-- src/sammo/API/Login/ReqNonce.php | 37 +++++ src/sammo/Session.php | 26 ++-- 7 files changed, 467 insertions(+), 114 deletions(-) create mode 100644 src/sammo/API/Login/ReqNonce.php diff --git a/f_install/sql/common_schema.sql b/f_install/sql/common_schema.sql index a28c17a5..c119febc 100644 --- a/f_install/sql/common_schema.sql +++ b/f_install/sql/common_schema.sql @@ -77,12 +77,12 @@ ENGINE=Aria; CREATE TABLE `login_token` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `user_id` INT(11) NOT NULL, - `base_token` INT(11) NOT NULL, + `base_token` VARCHAR(20) NOT NULL COLLATE 'utf8mb4_general_ci', `reg_ip` VARCHAR(40) NOT NULL COLLATE 'utf8mb4_general_ci', `reg_date` DATETIME NOT NULL, `expire_date` DATETIME NOT NULL, PRIMARY KEY (`id`), - UNIQUE INDEX `by_token` (`user_id`, `base_token`), + UNIQUE INDEX `by_token` (`base_token`), INDEX `by_date` (`user_id`, `expire_date`) ) COLLATE='utf8mb4_general_ci' diff --git a/hwe/ts/gateway/login.ts b/hwe/ts/gateway/login.ts index a12b44b7..8112cecc 100644 --- a/hwe/ts/gateway/login.ts +++ b/hwe/ts/gateway/login.ts @@ -11,9 +11,12 @@ import { unwrap_any } from '../util/unwrap_any'; import { sha512 } from 'js-sha512'; import { unwrap } from '../util/unwrap'; import { InvalidResponse } from '../defs'; +import internal from 'stream'; +import { delay } from '../util/delay'; type LoginResponse = { result: true, + nextToken: [number, string] | undefined, } | { result: false, reqOTP: boolean, @@ -29,6 +32,19 @@ type OTPResponse = { reason: string, } +type AutoLoginNonceResponse = { + result: true, + loginNonce: string, +} | InvalidResponse; + +type AutoLoginResponse = { + result: true, + nextToken: [number, string] | undefined, +} | { + result: false, + silent: boolean, + reason: string, +} declare global { interface Window { getOAuthToken: (mode: string, scope_list: string[]) => void; @@ -41,6 +57,92 @@ declare const kakao_oauth_redirect_uri: string; let oauthMode: string | null = null; +const TOKEN_VERSION = 1; +const LOGIN_TOKEN_KEY = 'sammo_login_token'; +function regNextToken(tokenInfo: [number, string]) { + localStorage.setItem(LOGIN_TOKEN_KEY, JSON.stringify([TOKEN_VERSION, tokenInfo, Date.now()])); +} + +function getToken(): [number, string] | undefined { + const trialToken = localStorage.getItem(LOGIN_TOKEN_KEY); + if (!trialToken) { + return; + } + const tokenItems = JSON.parse(trialToken); + if (tokenItems[0] != TOKEN_VERSION) { + console.log(tokenItems); + resetToken(); + return; + } + const [, token,] = tokenItems; + return token; +} + +function resetToken(){ + localStorage.removeItem(LOGIN_TOKEN_KEY); +} + +async function tryAutoLogin() { + try { + const tokenInfo = getToken(); + if (!tokenInfo) { + return; + } + + const [tokenID, token] = tokenInfo; + + const result = await axios.post('api.php', { + path: ["Login", "ReqNonce"] + }); + + if(!result){ + //api 에러. + return; + } + + if(!result.data.result){ + resetToken(); + return; + } + + const nonce = result.data.loginNonce; + + const hashedToken = sha512(token + nonce); + const _loginResult = await axios.post('api.php', { + path: ["Login", "LoginByToken"], + args: { + 'hashedToken':hashedToken, + 'token_id':tokenID, + } + }); + + if(!_loginResult){ + return; + } + + const loginResult = _loginResult.data; + if(!loginResult.result){ + if(!loginResult.silent){ + alert(loginResult.reason); + } + console.error(loginResult.reason); + return; + } + + if(loginResult.nextToken){ + regNextToken(loginResult.nextToken); + } + window.location.href = "./"; + + } + catch(e){ + console.error(e); + return; + } + + +} + function getOAuthToken(mode: string, scope_list?: string[] | string) { if (mode === undefined) { mode = 'login'; @@ -115,6 +217,9 @@ async function doLoginUsingOAuth() { } if (result.result) { + if (result.nextToken) { + regNextToken(result.nextToken); + } window.location.href = "./"; return; } @@ -156,9 +261,16 @@ function postOAuthResult(mode: string) { window.postOAuthResult = postOAuthResult; -$(function ($) { +$(async function ($) { setAxiosXMLHttpRequest(); + //로그인 먼저 해볼 것 + if(getToken()){ + void tryAutoLogin(); + await delay(100); + } + + type LoginFormType = { username: string, password: string, @@ -215,6 +327,9 @@ $(function ($) { if (result.result) { + if (result.nextToken) { + regNextToken(result.nextToken); + } window.location.href = "./"; return; } @@ -276,7 +391,6 @@ $(function ($) { getOAuthToken('login', ['account_email', 'talk_message']); }) - //TODO: 모바일에서 크기 비례 지도 다시 띄우기? /* if (document.body.clientWidth < 700) { diff --git a/oauth_kakao/j_login_oauth.php b/oauth_kakao/j_login_oauth.php index 673d02b4..4d32e003 100644 --- a/oauth_kakao/j_login_oauth.php +++ b/oauth_kakao/j_login_oauth.php @@ -1,28 +1,30 @@ isLoggedIn()){ +if ($session->isLoggedIn()) { $session->logout(); } $canLogin = RootDB::db()->queryFirstField('SELECT `LOGIN` FROM `system` WHERE `NO` = 1'); -if($canLogin != 'Y'){ +if ($canLogin != 'Y') { Json::die([ - 'result'=>false, - 'reqOTP'=>false, - 'reason'=>'현재는 로그인이 금지되어있습니다!', - 'noRetry'=>true + 'result' => false, + 'reqOTP' => false, + 'reason' => '현재는 로그인이 금지되어있습니다!', + 'noRetry' => true ]); } @@ -35,84 +37,84 @@ $refresh_token_expires = $session->refresh_token_expires; $email = $session->kaccount_email; -if(!$access_token || !$expires){ +if (!$access_token || !$expires) { Json::die([ - 'result'=>false, - 'reqOTP'=>false, - 'reason'=>'카카오로그인이 이루어지지 않았습니다.' + 'result' => false, + 'reqOTP' => false, + 'reason' => '카카오로그인이 이루어지지 않았습니다.' ]); } //TODO: join과 login의 동작이 비슷하다. helper class로 묶자. $restAPI = new Kakao_REST_API_Helper($access_token); -if($expires < $now && (!$refresh_token || ($refresh_token_expires < $now))){ +if ($expires < $now && (!$refresh_token || ($refresh_token_expires < $now))) { Json::die([ - 'result'=>false, - 'reqOTP'=>false, - 'reason'=>'로그인 토큰 만료.'.$refresh_token_expires.' 다시 카카오로그인을 수행해주세요.' + 'result' => false, + 'reqOTP' => false, + 'reason' => '로그인 토큰 만료.' . $refresh_token_expires . ' 다시 카카오로그인을 수행해주세요.' ]); } -if($expires < $now){ +if ($expires < $now) { $session->kaccount_email = null; $email = null; $result = $restAPI->refresh_access_token($refresh_token); - if(!isset($refresh_token)){ + if (!isset($refresh_token)) { Json::die([ - 'result'=>false, - 'reqOTP'=>false, - 'reason'=>'카카오 로그인 과정 중 추가 갱신 절차를 실패했습니다' + 'result' => false, + 'reqOTP' => false, + 'reason' => '카카오 로그인 과정 중 추가 갱신 절차를 실패했습니다' ]); } $access_token = $result['access_token']; $expires = TimeUtil::nowAddSeconds($result['expires_in']); - if(isset($result['refresh_token'])){ + if (isset($result['refresh_token'])) { $refresh_token = Util::array_get($result['refresh_token']); $refresh_token_expires = TimeUtil::nowAddSeconds($result['refresh_token_expires_in']); } } -if(!$email){ +if (!$email) { $me = $restAPI->meWithEmail(); $me['code'] = Util::array_get($me['code'], 0); - if ($me['code']< 0) { + if ($me['code'] < 0) { Json::die([ - 'result'=>false, - 'reqOTP'=>false, - 'reason'=>'카카오로그인이 정상적으로 수행되지 않았습니다.' + 'result' => false, + 'reqOTP' => false, + 'reason' => '카카오로그인이 정상적으로 수행되지 않았습니다.' ]); } - $kakao_account = $me['kakao_account']??null; + $kakao_account = $me['kakao_account'] ?? null; if (!$kakao_account) { Json::die([ - 'result'=>false, - 'reqOTP'=>false, - 'reason'=>'카카오 로그인 정보를 제대로 받아오지 못했습니다.' + 'result' => false, + 'reqOTP' => false, + 'reason' => '카카오 로그인 정보를 제대로 받아오지 못했습니다.' ]); } - if(!($kakao_account['has_email']??false)){ + if (!($kakao_account['has_email'] ?? false)) { Json::die([ - 'result'=>false, - 'reqOTP'=>false, - 'reason'=>'이메일 정보 공유를 허가해 주셔야 합니다.', + 'result' => false, + 'reqOTP' => false, + 'reason' => '이메일 정보 공유를 허가해 주셔야 합니다.', ]); } - $validEmail = $kakao_account['is_email_valid']??false; - $verifiedEmail = $kakao_account['is_email_verified']??false; + $validEmail = $kakao_account['is_email_valid'] ?? false; + $verifiedEmail = $kakao_account['is_email_verified'] ?? false; - if(!$validEmail || !$verifiedEmail){ + if (!$validEmail || !$verifiedEmail) { $restAPI->unlink(); Json::die([ - 'result'=>false, - 'reqOTP'=>false, - 'reason'=>'카카오 계정 이메일이 아직 인증되지 않았습니다', + 'result' => false, + 'reqOTP' => false, + 'reason' => '카카오 계정 이메일이 아직 인증되지 않았습니다', ]); } @@ -122,86 +124,122 @@ if(!$email){ $userInfo = $RootDB->queryFirstRow( - 'SELECT `no`, `id`, `name`, `grade`, `delete_after`, `acl`, oauth_info, token_valid_until from member where email=%s',$email); + 'SELECT `no`, `id`, `name`, `grade`, `delete_after`, `acl`, oauth_info, token_valid_until from member where email=%s', + $email +); -if(!$userInfo){ +if (!$userInfo) { $restAPI->unlink(); $session->access_token = null; Json::die([ - 'result'=>false, - 'reqOTP'=>false, - 'reason'=>'카카오로그인에 해당하는 계정이 없습니다. 재 가입을 시도해주세요.', - 'aux'=>$session->tmpx, + 'result' => false, + 'reqOTP' => false, + 'reason' => '카카오로그인에 해당하는 계정이 없습니다. 재 가입을 시도해주세요.', + 'aux' => $session->tmpx, ]); } -if($userInfo['delete_after']){ - if($userInfo['delete_after'] < $now){ +if ($userInfo['delete_after']) { + if ($userInfo['delete_after'] < $now) { $restAPI->unlink(); $session->access_token = null; $RootDB->delete('member', 'no=%i', $userInfo['no']); Json::die([ - 'result'=>false, - 'reqOTP'=>false, - 'reason'=>"기간 만기로 삭제되었습니다. 재 가입을 시도해주세요.", + 'result' => false, + 'reqOTP' => false, + 'reason' => "기간 만기로 삭제되었습니다. 재 가입을 시도해주세요.", ]); - } - else{ + } else { $restAPI->unlink(); $session->access_token = null; Json::die([ - 'result'=>false, - 'reqOTP'=>false, - 'reason'=>"삭제 요청된 계정입니다.[{$userInfo['delete_after']}]" + 'result' => false, + 'reqOTP' => false, + 'reason' => "삭제 요청된 계정입니다.[{$userInfo['delete_after']}]" ]); } - } -$oauthInfo = Json::decode($userInfo['oauth_info'])??[]; +$oauthInfo = Json::decode($userInfo['oauth_info']) ?? []; $oauthInfo['accessToken'] = $access_token; $oauthInfo['accessTokenValidUntil'] = $expires; -if($refresh_token){ +if ($refresh_token) { $oauthInfo['refreshToken'] = $refresh_token; $oauthInfo['refreshTokenValidUntil'] = $refresh_token_expires; } RootDB::db()->update('member', [ - 'oauth_info'=>Json::encode($oauthInfo) + 'oauth_info' => Json::encode($oauthInfo) ], 'no=%i', $userInfo['no']); $tokenValidUntil = $userInfo['token_valid_until']; -if(!$tokenValidUntil || $tokenValidUntil < $now){ - if(!createOTPbyUserNO($userInfo['no'])){ +if (!$tokenValidUntil || $tokenValidUntil < $now) { + if (!createOTPbyUserNO($userInfo['no'])) { Json::die([ - 'result'=>false, - 'reqOTP'=>false, - 'reason'=>'인증 코드를 보내는데 실패했습니다.' + 'result' => false, + 'reqOTP' => false, + 'reason' => '인증 코드를 보내는데 실패했습니다.' ]); } - $session->login($userInfo['no'], $userInfo['id'], $userInfo['grade'], true, $userInfo['token_valid_until'], Json::decode($userInfo['acl']??'{}')); + $session->login($userInfo['no'], $userInfo['id'], $userInfo['grade'], true, $userInfo['token_valid_until'], null, Json::decode($userInfo['acl'] ?? '{}')); Json::die([ - 'result'=>false, - 'reqOTP'=>true, - 'reason'=>'인증 코드를 입력해주세요.' + 'result' => false, + 'reqOTP' => true, + 'reason' => '인증 코드를 입력해주세요.' ]); } -$RootDB->insert('member_log',[ - 'member_no'=>$userInfo['no'], - 'action_type'=>'login', - 'action'=>Json::encode([ - 'ip'=>Util::get_client_ip(true), - 'type'=>'kakao' +$RootDB->insert('member_log', [ + 'member_no' => $userInfo['no'], + 'action_type' => 'login', + 'action' => Json::encode([ + 'ip' => Util::get_client_ip(true), + 'type' => 'kakao' ]) ]); -$session->login($userInfo['no'], $userInfo['id'], $userInfo['grade'], false, $userInfo['token_valid_until'], Json::decode($userInfo['acl']??'{}')); + +//TODO: 이동하면서 이부분 정리 +$nowDate = TimeUtil::now(); +$RootDB->delete( + 'login_token', + 'user_id = %i AND expire_date < %s', + $userID, + $nowDate +); +$RootDB->query( + 'DELETE invalid FROM login_token AS invalid + JOIN + ( SELECT id + FROM login_token + WHERE user_id = %i + ORDER BY id DESC + LIMIT 8,1 + ) AS valid + ON invalid.id < valid.id WHERE user_id = %i', + $userID, + $userID +); +$token = Util::randomStr(20); +$RootDB->insert('login_token', [ + 'user_id' => $userInfo['no'], + 'base_token' => $token, + 'reg_ip' => Util::get_client_ip(true), + 'reg_date' => $nowDate, + 'expire_date' => TimeUtil::nowAddDays(7) +]); +$tokenID = $RootDB->insertId(); + +$session->login($userInfo['no'], $userInfo['id'], $userInfo['grade'], false, $userInfo['token_valid_until'], $tokenID, Json::decode($userInfo['acl'] ?? '{}')); + + + Json::die([ - 'result'=>true, - 'reqOTP'=>false, - 'reason'=>'로그인 되었습니다.' -]); \ No newline at end of file + 'result' => true, + 'reqOTP' => false, + 'nextToken' => [$tokenID, $token], + 'reason' => '로그인 되었습니다.' +]); diff --git a/src/sammo/API/Login/LoginByID.php b/src/sammo/API/Login/LoginByID.php index 1aa0d8ac..3e4ed269 100644 --- a/src/sammo/API/Login/LoginByID.php +++ b/src/sammo/API/Login/LoginByID.php @@ -2,6 +2,7 @@ namespace sammo\API\Login; +use DateTime; use sammo\Session; use DateTimeInterface; use sammo\BaseAPI; @@ -34,6 +35,47 @@ class LoginByID extends \sammo\BaseAPI return BaseAPI::NO_SESSION; } + public function scrubToken(int $userID) + { + $RootDB = RootDB::db(); + $nowDate = TimeUtil::now(); + $RootDB->delete( + 'login_token', + 'user_id = %i AND expire_date < %s', + $userID, + $nowDate + ); + $RootDB->query( + 'DELETE invalid FROM login_token AS invalid + JOIN + ( SELECT id + FROM login_token + WHERE user_id = %i + ORDER BY id DESC + LIMIT 8,1 + ) AS valid + ON invalid.id < valid.id WHERE user_id = %i', + $userID, + $userID + ); + } + + public function addToken(int $userID) + { + $RootDB = RootDB::db(); + $nowDate = TimeUtil::now(); + $token = Util::randomStr(20); + $RootDB->insert('login_token', [ + 'user_id' => $userID, + 'base_token' => $token, + 'reg_ip' => Util::get_client_ip(true), + 'reg_date' => $nowDate, + 'expire_date' => TimeUtil::nowAddDays(7) + ]); + $tokenID = $RootDB->insertId(); + return [$tokenID, $token]; + } + public function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag) { $RootDB = RootDB::db(); @@ -82,16 +124,6 @@ class LoginByID extends \sammo\BaseAPI } } - $RootDB->insert('member_log', [ - 'member_no' => $userInfo['no'], - 'action_type' => 'login', - 'action' => Json::encode([ - 'ip' => Util::get_client_ip(true), - 'type' => 'plain' - ]) - ]); - - $RootDB->insert('member_log', [ 'member_no' => $userInfo['no'], 'action_type' => 'login', @@ -104,7 +136,7 @@ class LoginByID extends \sammo\BaseAPI if ($userInfo['oauth_type'] == 'KAKAO') { $oauthFailResult = KakaoUtil::kakaoOAuthCheck($userInfo); if ($oauthFailResult !== null) { - $session->login($userInfo['no'], $userInfo['id'], $userInfo['grade'], true, $userInfo['token_valid_until'], Json::decode($userInfo['acl'] ?? '{}')); + $session->login($userInfo['no'], $userInfo['id'], $userInfo['grade'], true, $userInfo['token_valid_until'], null, Json::decode($userInfo['acl'] ?? '{}')); [$oauthReqOTP, $oauthFailReason] = $oauthFailResult; return [ 'result' => false, @@ -114,11 +146,16 @@ class LoginByID extends \sammo\BaseAPI } } + $this->scrubToken($userInfo['no']); + $nextToken = $this->addToken($userInfo['no']); + + $session->login($userInfo['no'], $userInfo['id'], $userInfo['grade'], false, $userInfo['token_valid_until'], $nextToken[0], Json::decode($userInfo['acl'] ?? '{}')); + - $session->login($userInfo['no'], $userInfo['id'], $userInfo['grade'], false, $userInfo['token_valid_until'], Json::decode($userInfo['acl'] ?? '{}')); return [ 'result' => true, 'reqOTP' => false, + 'nextToken' => $nextToken, 'reason' => '로그인 되었습니다.' ]; } diff --git a/src/sammo/API/Login/LoginByToken.php b/src/sammo/API/Login/LoginByToken.php index cec1d485..1bdc646e 100644 --- a/src/sammo/API/Login/LoginByToken.php +++ b/src/sammo/API/Login/LoginByToken.php @@ -12,18 +12,139 @@ use sammo\TimeUtil; use sammo\Util; use sammo\Validator; -class LoginByToken extends \sammo\BaseAPI{ +class LoginByToken extends LoginByID +{ - public function getRequiredSessionMode(): int { - return \sammo\BaseAPI::NO_SESSION; + public function getRequiredSessionMode(): int + { + return \sammo\BaseAPI::NO_SESSION; //XXX: Token 때문에 엄밀히는 NO_SESSION이 아님 } - public function validateArgs(): ?string { + public function validateArgs(): ?string + { + $v = new Validator($this->args); + $v + ->rule('required', [ + 'token_id', + 'hashedToken' + ]); + + if (!$v->validate()) { + return $v->errorStr(); + } return null; } - public function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag) { - return '미구현'; - } + public function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag) + { + $loginNonce = $session->loginNonce ?? null; + $loginNonceExpired = $session->loginNonceExpired ?? null; + if (!is_string($loginNonce) || !is_string($loginNonceExpired)) { + return '자동 로그인: 절차 오류'; + } -} \ No newline at end of file + $RootDB = RootDB::db(); + if ($session->isLoggedIn()) { + $session->logout(); + } + + $token_id = $this->args['token_id']; + + $token_info = $RootDB->queryFirstRow('SELECT * FROM login_token WHERE id = %i', $token_id); + if (!$token_info) { + return [ + 'result' => false, + 'silent' => true, + 'reason' => 'failed' + ]; + } + + $hashedToken = $this->args['hashedToken']; + $tokenAnswer = hash('sha512', "{$token_info['base_token']}{$loginNonce}"); + if (strtolower($hashedToken) != strtolower($tokenAnswer)) { + return [ + 'result' => false, + 'silent' => true, + 'reason' => 'failed' + ]; + } + + $userInfo = $RootDB->queryFirstRow( + 'SELECT `no`, `id`, `name`, `grade`, `delete_after`, `acl`, oauth_id, oauth_type, oauth_info, token_valid_until ' . + 'from member where NO = %i', + $token_info['user_id'] + ); + if(!$userInfo){ + return '자동 로그인: 올바른 계정이 아닙니다.'; + } + + $canLogin = $RootDB->queryFirstField('SELECT `LOGIN` FROM `system` WHERE `NO` = 1'); + if ($canLogin != 'Y' && $userInfo['grade'] < 5) { + return '자동 로그인: 현재는 로그인이 금지되어있습니다!'; + } + + $nowDate = TimeUtil::now(); + if ($userInfo['delete_after']) { + if ($userInfo['delete_after'] < $nowDate) { + $RootDB->delete('member', 'no=%i', $userInfo['no']); + return [ + 'result' => false, + 'silent' => true, + 'reason' => "기간 만기" + ]; + } else { + return [ + 'result' => false, + 'silent' => true, + 'reason' => "삭제 요청된 계정[{$userInfo['delete_after']}]" + ]; + } + } + + $RootDB->insert('member_log', [ + 'member_no' => $userInfo['no'], + 'action_type' => 'login', + 'action' => Json::encode([ + 'ip' => Util::get_client_ip(true), + 'type' => 'auto' + ]) + ]); + + if ($userInfo['oauth_type'] == 'KAKAO') { + $oauthFailResult = KakaoUtil::kakaoOAuthCheck($userInfo); + if ($oauthFailResult !== null) { + $session->login($userInfo['no'], $userInfo['id'], $userInfo['grade'], true, $userInfo['token_valid_until'], null, Json::decode($userInfo['acl'] ?? '{}')); + [$oauthReqOTP, $oauthFailReason] = $oauthFailResult; + $RootDB->delete( + 'login_token', + 'id = %i', $token_id + ); + return $oauthFailReason; + } + } + + $session->login($userInfo['no'], $userInfo['id'], $userInfo['grade'], false, $userInfo['token_valid_until'], $token_id, Json::decode($userInfo['acl'] ?? '{}')); + $this->scrubToken($userInfo['no']); + + $nextDate = TimeUtil::nowAddDays(2); + if($nextDate < $token_info['expire_date']){ + return [ + 'result' => true, + 'silent' => true, + 'reason' => 'success' + ]; + } + + $RootDB->delete( + 'login_token', + 'id = %i', $token_id + ); + $nextToken = $this->addToken($token_id); + return [ + 'result' => true, + 'silent' => true, + 'nextToken' => $nextToken, + 'reason' => 'success' + ]; + } +} diff --git a/src/sammo/API/Login/ReqNonce.php b/src/sammo/API/Login/ReqNonce.php new file mode 100644 index 00000000..1b4d24bf --- /dev/null +++ b/src/sammo/API/Login/ReqNonce.php @@ -0,0 +1,37 @@ +loginNonce = $loginNonce; + $session->loginNonceExpired = $loginNonceExpired; + return [ + 'result'=>true, + 'loginNonce'=>$loginNonce, + ]; + } + +} \ No newline at end of file diff --git a/src/sammo/Session.php b/src/sammo/Session.php index a9b198dc..f3dbe49f 100644 --- a/src/sammo/Session.php +++ b/src/sammo/Session.php @@ -10,8 +10,9 @@ namespace sammo; * @property string $ip IP * @property bool $reqOTP 인증 코드 필요 * @property array $acl 권한 + * @property int|null $tokenID 토큰ID * @property string $tokenValidUntil 로그인 토큰 길이 - * + * * @property int $generalID 장수 번호 (게임 로그인 필요) * @property string $generalName 장수 이름 (게임 로그인 필요) */ @@ -35,7 +36,7 @@ class Session const GAME_KEY_GENERAL_ID = '_g_no'; const GAME_KEY_GENERAL_NAME = '_g_name'; const GAME_KEY_EXPECTED_DEADTIME = '_g_deadtime'; - + private $writeClosed = false; private $sessionID = null; @@ -170,7 +171,7 @@ class Session return Util::array_get($_SESSION[$name]); } - public function login(int $userID, string $userName, int $grade, bool $reqOTP, ?string $tokenValidUntil, array $acl): Session + public function login(int $userID, string $userName, int $grade, bool $reqOTP, ?string $tokenValidUntil, ?int $tokenID, array $acl): Session { $this->set('userID', $userID); $this->set('userName', $userName); @@ -180,6 +181,7 @@ class Session $this->set('acl', $acl); $this->set('reqOTP', $reqOTP); $this->set('tokenValidUntil', $tokenValidUntil); + $this->set('tokenID', $tokenID); return $this; } @@ -197,7 +199,11 @@ class Session if (class_exists('\\sammo\\UniqueConst')) { $this->logoutGame(); } - + + if($this->tokenID??null){ + RootDB::db()->delete('login_token', 'id = %i', $this->tokenID); + } + $this->set('userID', null); $this->set('userName', null); $this->set('userGrade', null); @@ -261,7 +267,7 @@ class Session } return $this; } - + $turnterm = $gameStor->turnterm; $isUnited = $gameStor->isunited != 0; @@ -319,7 +325,7 @@ class Session } else { $obj = self::getInstance(); } - + return $obj->userGrade; } @@ -335,12 +341,12 @@ class Session } else { $obj = self::getInstance(); } - + return $obj->userID; } /** - * + * */ public static function getGeneralID(bool $requireLogin = false, string $exitPath = '..') { @@ -349,7 +355,7 @@ class Session } else { $obj = self::getInstance(); } - + return $obj->generalID??0; } @@ -367,7 +373,7 @@ class Session return false; } } - + if ($this->userID) { return true; } else {