Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
673b7054aa | ||
|
|
1472cbf257 | ||
|
|
d806935c58 | ||
|
|
6b56eb79e1 | ||
|
|
d2aea16744 | ||
|
|
bb800f032e |
@@ -5,7 +5,7 @@
|
|||||||
<title>닫혀 있습니다.</title>
|
<title>닫혀 있습니다.</title>
|
||||||
<script>
|
<script>
|
||||||
setTimeout(function(){
|
setTimeout(function(){
|
||||||
window.location = window.location+'/../';
|
window.location.href = '../';
|
||||||
}, 5000);
|
}, 5000);
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
background-color:black;
|
background-color:black;
|
||||||
color:yellow;
|
color:yellow;
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -151,3 +151,28 @@ function logExceptionByCustomHandler(\Throwable $e)
|
|||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
set_exception_handler('\\sammo\\logExceptionByCustomHandler');
|
set_exception_handler('\\sammo\\logExceptionByCustomHandler');
|
||||||
|
|
||||||
|
function getAPIExecutorClass($path){
|
||||||
|
|
||||||
|
static $basePath = __NAMESPACE__.'\\API\\';
|
||||||
|
if(is_string($path)){
|
||||||
|
}
|
||||||
|
else if(is_array($path)){
|
||||||
|
$path = join('\\', $path);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
throw new \InvalidArgumentException("{$path}는 올바른 API 지시자가 아님");
|
||||||
|
}
|
||||||
|
|
||||||
|
$classPath = str_replace('/', '\\', $basePath.$path);
|
||||||
|
|
||||||
|
if(class_exists($classPath)){
|
||||||
|
return $classPath;
|
||||||
|
}
|
||||||
|
throw new \InvalidArgumentException("{$path}는 올바른 API 경로가 아님");
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildAPIExecutorClass($type, string $rootPath, array $args):\sammo\BaseAPI{
|
||||||
|
$class = getAPIExecutorClass($type);
|
||||||
|
return new $class($rootPath, $args);
|
||||||
|
}
|
||||||
@@ -58,7 +58,7 @@ CREATE TABLE `member_log` (
|
|||||||
INDEX `action` (`member_no`, `action_type`, `date`),
|
INDEX `action` (`member_no`, `action_type`, `date`),
|
||||||
INDEX `member` (`member_no`, `date`)
|
INDEX `member` (`member_no`, `date`)
|
||||||
)
|
)
|
||||||
ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
|
ENGINE=Aria DEFAULT CHARSET=utf8mb4;
|
||||||
|
|
||||||
###################
|
###################
|
||||||
# KV storage
|
# KV storage
|
||||||
@@ -72,4 +72,19 @@ CREATE TABLE if not exists `storage` (
|
|||||||
UNIQUE INDEX `key` (`namespace`, `key`)
|
UNIQUE INDEX `key` (`namespace`, `key`)
|
||||||
)
|
)
|
||||||
COLLATE='utf8mb4_general_ci'
|
COLLATE='utf8mb4_general_ci'
|
||||||
ENGINE=MyISAM
|
ENGINE=Aria;
|
||||||
|
|
||||||
|
CREATE TABLE `login_token` (
|
||||||
|
`id` INT(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`user_id` 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` (`base_token`),
|
||||||
|
INDEX `by_date` (`user_id`, `expire_date`)
|
||||||
|
)
|
||||||
|
COLLATE='utf8mb4_general_ci'
|
||||||
|
ENGINE=Aria
|
||||||
|
;
|
||||||
@@ -357,31 +357,6 @@ function buildNationCommandClass(?string $type, General $generalObj, array $env,
|
|||||||
return new $class($generalObj, $env, $lastTurn, $arg);
|
return new $class($generalObj, $env, $lastTurn, $arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAPIExecutorClass($path){
|
|
||||||
|
|
||||||
static $basePath = __NAMESPACE__.'\\API\\';
|
|
||||||
if(is_string($path)){
|
|
||||||
}
|
|
||||||
else if(is_array($path)){
|
|
||||||
$path = join('\\', $path);
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
throw new \InvalidArgumentException("{$path}는 올바른 API 지시자가 아님");
|
|
||||||
}
|
|
||||||
|
|
||||||
$classPath = str_replace('/', '\\', $basePath.$path);
|
|
||||||
|
|
||||||
if(class_exists($classPath)){
|
|
||||||
return $classPath;
|
|
||||||
}
|
|
||||||
throw new \InvalidArgumentException("{$path}는 올바른 API 경로가 아님");
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildAPIExecutorClass($type, string $rootPath, array $args):\sammo\BaseAPI{
|
|
||||||
$class = getAPIExecutorClass($type);
|
|
||||||
return new $class($rootPath, $args);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getWarUnitTriggerClass(string $type){
|
function getWarUnitTriggerClass(string $type){
|
||||||
static $basePath = __NAMESPACE__.'\\WarUnitTrigger\\';
|
static $basePath = __NAMESPACE__.'\\WarUnitTrigger\\';
|
||||||
$classPath = ($basePath.$type);
|
$classPath = ($basePath.$type);
|
||||||
|
|||||||
@@ -624,12 +624,12 @@ function updateNationState()
|
|||||||
$targetKillTurn
|
$targetKillTurn
|
||||||
);
|
);
|
||||||
$nationGenList = General::createGeneralObjListFromDB($nationGenIDList, ['belong', 'npc', 'aux'], 2);
|
$nationGenList = General::createGeneralObjListFromDB($nationGenIDList, ['belong', 'npc', 'aux'], 2);
|
||||||
$chiefObj = null;
|
$chiefID = null;
|
||||||
|
|
||||||
$uniqueLotteryWeightList = [];
|
$uniqueLotteryWeightList = [];
|
||||||
foreach ($nationGenList as $nationGen) {
|
foreach ($nationGenList as $nationGen) {
|
||||||
if ($nationGen->getVar('officer_level') == 12) {
|
if ($nationGen->getVar('officer_level') == 12) {
|
||||||
$chiefObj = $nationGen;
|
$chiefID = $nationGen->getID();
|
||||||
}
|
}
|
||||||
$trialCnt = count(GameConst::$allItems);
|
$trialCnt = count(GameConst::$allItems);
|
||||||
|
|
||||||
@@ -670,8 +670,10 @@ function updateNationState()
|
|||||||
$winnerObj->applyDB($db);
|
$winnerObj->applyDB($db);
|
||||||
}
|
}
|
||||||
|
|
||||||
if($chiefObj){
|
if($chiefID){
|
||||||
|
$chiefObj = General::createGeneralObjFromDB($chiefID, ['belong', 'npc', 'aux'], 2);
|
||||||
$chiefObj->increaseInheritancePoint('unifier', 250 * $levelDiff);
|
$chiefObj->increaseInheritancePoint('unifier', 250 * $levelDiff);
|
||||||
|
$chiefObj->applyDB($db);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,25 +11,29 @@ class event_전투특기_무쌍 extends \sammo\BaseItem{
|
|||||||
protected $id = 61;
|
protected $id = 61;
|
||||||
protected $rawName = '비급';
|
protected $rawName = '비급';
|
||||||
protected $name = '비급(무쌍)';
|
protected $name = '비급(무쌍)';
|
||||||
protected $info = '[전투] 대미지 +10%, 피해 -5%, 공격 시 필살 확률 +10%p, <br>승리 수만큼 대미지 0.20%씩 추가 상승(최대50%)<br>승리 수만큼 피해 0.05%씩 감소(최대20%)';
|
protected $info = '[전투] 대미지 +5%, 피해 -2%, 공격 시 필살 확률 +10%p, <br>승리 수의 로그 비례로 대미지 상승(10회 ⇒ +5%, 40회 ⇒ +15%)<br>승리 수의 로그 비례로 피해 감소(10회 ⇒ -2%, 40회 ⇒ -6%)';
|
||||||
protected $cost = 100;
|
protected $cost = 100;
|
||||||
protected $buyable = true;
|
protected $buyable = true;
|
||||||
protected $consumable = false;
|
protected $consumable = false;
|
||||||
protected $reqSecu = 3000;
|
protected $reqSecu = 3000;
|
||||||
|
|
||||||
public function onCalcStat(General $general, string $statName, $value, $aux=null){
|
public function onCalcStat(General $general, string $statName, $value, $aux=null){
|
||||||
if($statName === 'warCriticalRatio' && $aux['isAttacker']??false){
|
if($statName === 'warCriticalRatio' && ($aux['isAttacker']??false)){
|
||||||
return $value += 0.1;
|
return $value += 0.1;
|
||||||
}
|
}
|
||||||
return $value;
|
return $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getWarPowerMultiplier(WarUnit $unit):array{
|
public function getWarPowerMultiplier(WarUnit $unit):array{
|
||||||
$attackMultiplier = 1.1;
|
$generalWarSpecial = $unit->getGeneral()->getSpecialWar();
|
||||||
$defenceMultiplier = 0.95;
|
if($generalWarSpecial !== null && $generalWarSpecial->getName() == '무쌍'){
|
||||||
|
return [1, 1];
|
||||||
|
}
|
||||||
|
$attackMultiplier = 1.05;
|
||||||
|
$defenceMultiplier = 0.98;
|
||||||
$killnum = $unit->getGeneral()->getRankVar('killnum');
|
$killnum = $unit->getGeneral()->getRankVar('killnum');
|
||||||
$attackMultiplier += Util::valueFit($killnum * 0.01 * 0.2, null, 0.5);
|
$attackMultiplier += log(max(1, $killnum / 5), 2) / 20;
|
||||||
$defenceMultiplier -= Util::valueFit($killnum * 0.01 * 0.05, null, 0.2);
|
$defenceMultiplier -= log(max(1, $killnum / 5), 2) / 50;
|
||||||
return [$attackMultiplier, $defenceMultiplier];
|
return [$attackMultiplier, $defenceMultiplier];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10,7 +10,7 @@ class che_event_무쌍 extends \sammo\BaseSpecial{
|
|||||||
|
|
||||||
protected $id = 61;
|
protected $id = 61;
|
||||||
protected $name = '무쌍';
|
protected $name = '무쌍';
|
||||||
protected $info = '[전투] 대미지 +10%, 피해 -5%, 공격 시 필살 확률 +10%p, <br>승리 수만큼 대미지 0.20%씩 추가 상승(최대40%)<br>승리 수만큼 피해 0.05%씩 감소(최대30%)';
|
protected $info = '[전투] 대미지 +5%, 피해 -2%, 공격 시 필살 확률 +10%p, <br>승리 수의 로그 비례로 대미지 상승(10회 ⇒ +5%, 40회 ⇒ +15%)<br>승리 수의 로그 비례로 피해 감소(10회 ⇒ -2%, 40회 ⇒ -6%)';
|
||||||
|
|
||||||
static $selectWeightType = SpecialityHelper::WEIGHT_NORM;
|
static $selectWeightType = SpecialityHelper::WEIGHT_NORM;
|
||||||
static $selectWeight = 1;
|
static $selectWeight = 1;
|
||||||
@@ -19,18 +19,22 @@ class che_event_무쌍 extends \sammo\BaseSpecial{
|
|||||||
];
|
];
|
||||||
|
|
||||||
public function onCalcStat(General $general, string $statName, $value, $aux=null){
|
public function onCalcStat(General $general, string $statName, $value, $aux=null){
|
||||||
if($statName === 'warCriticalRatio' && $aux['isAttacker']??false){
|
if($statName === 'warCriticalRatio' && ($aux['isAttacker']??false)){
|
||||||
return $value += 0.1;
|
return $value += 0.1;
|
||||||
}
|
}
|
||||||
return $value;
|
return $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getWarPowerMultiplier(WarUnit $unit):array{
|
public function getWarPowerMultiplier(WarUnit $unit):array{
|
||||||
$attackMultiplier = 1.1;
|
$generalWarSpecial = $unit->getGeneral()->getSpecialWar();
|
||||||
$defenceMultiplier = 1;
|
if($generalWarSpecial !== null && $generalWarSpecial->getName() == '무쌍'){
|
||||||
|
return [1, 1];
|
||||||
|
}
|
||||||
|
$attackMultiplier = 1.05;
|
||||||
|
$defenceMultiplier = 0.98;
|
||||||
$killnum = $unit->getGeneral()->getRankVar('killnum');
|
$killnum = $unit->getGeneral()->getRankVar('killnum');
|
||||||
$attackMultiplier += Util::valueFit($killnum * 0.01 * 0.2, null, 0.4);
|
$attackMultiplier += log(max(1, $killnum / 5), 2) / 20;
|
||||||
$defenceMultiplier -= Util::valueFit($killnum * 0.01 * 0.05, null, 0.3);
|
$defenceMultiplier -= log(max(1, $killnum / 5), 2) / 50;
|
||||||
return [$attackMultiplier, $defenceMultiplier];
|
return [$attackMultiplier, $defenceMultiplier];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10,7 +10,7 @@ class che_무쌍 extends \sammo\BaseSpecial{
|
|||||||
|
|
||||||
protected $id = 61;
|
protected $id = 61;
|
||||||
protected $name = '무쌍';
|
protected $name = '무쌍';
|
||||||
protected $info = '[전투] 대미지 +10%, 피해 -5%, 공격 시 필살 확률 +10%p, <br>승리 수만큼 대미지 0.20%씩 추가 상승(최대50%)<br>승리 수만큼 피해 0.05%씩 감소(최대20%)';
|
protected $info = '[전투] 대미지 +5%, 피해 -2%, 공격 시 필살 확률 +10%p, <br>승리 수의 로그 비례로 대미지 상승(10회 ⇒ +5%, 40회 ⇒ +15%)<br>승리 수의 로그 비례로 피해 감소(10회 ⇒ -2%, 40회 ⇒ -6%)';
|
||||||
|
|
||||||
static $selectWeightType = SpecialityHelper::WEIGHT_NORM;
|
static $selectWeightType = SpecialityHelper::WEIGHT_NORM;
|
||||||
static $selectWeight = 1;
|
static $selectWeight = 1;
|
||||||
@@ -19,18 +19,18 @@ class che_무쌍 extends \sammo\BaseSpecial{
|
|||||||
];
|
];
|
||||||
|
|
||||||
public function onCalcStat(General $general, string $statName, $value, $aux=null){
|
public function onCalcStat(General $general, string $statName, $value, $aux=null){
|
||||||
if($statName === 'warCriticalRatio' && $aux['isAttacker']??false){
|
if($statName === 'warCriticalRatio' && ($aux['isAttacker']??false)){
|
||||||
return $value += 0.1;
|
return $value += 0.1;
|
||||||
}
|
}
|
||||||
return $value;
|
return $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getWarPowerMultiplier(WarUnit $unit):array{
|
public function getWarPowerMultiplier(WarUnit $unit):array{
|
||||||
$attackMultiplier = 1.1;
|
$attackMultiplier = 1.05;
|
||||||
$defenceMultiplier = 0.95;
|
$defenceMultiplier = 0.98;
|
||||||
$killnum = $unit->getGeneral()->getRankVar('killnum');
|
$killnum = $unit->getGeneral()->getRankVar('killnum');
|
||||||
$attackMultiplier += Util::valueFit($killnum * 0.01 * 0.2, null, 0.5);
|
$attackMultiplier += log(max(1, $killnum / 5), 2) / 20;
|
||||||
$defenceMultiplier -= Util::valueFit($killnum * 0.01 * 0.05, null, 0.2);
|
$defenceMultiplier -= log(max(1, $killnum / 5), 2) / 50;
|
||||||
return [$attackMultiplier, $defenceMultiplier];
|
return [$attackMultiplier, $defenceMultiplier];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -89,7 +89,7 @@ class che_장비매매 extends Command\GeneralCommand{
|
|||||||
else if($itemCode == $general->getVar($itemType)){
|
else if($itemCode == $general->getVar($itemType)){
|
||||||
$this->fullConditionConstraints[] = ConstraintHelper::AlwaysFail('이미 가지고 있습니다.');
|
$this->fullConditionConstraints[] = ConstraintHelper::AlwaysFail('이미 가지고 있습니다.');
|
||||||
}
|
}
|
||||||
else if($itemType != 'item' && !buildItemClass($general->getVar($itemType))->isBuyable()){
|
else if(!buildItemClass($general->getVar($itemType))->isBuyable()){
|
||||||
$this->fullConditionConstraints[] = ConstraintHelper::AlwaysFail('이미 진귀한 것을 가지고 있습니다.');
|
$this->fullConditionConstraints[] = ConstraintHelper::AlwaysFail('이미 진귀한 것을 가지고 있습니다.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+127
-10
@@ -1,7 +1,7 @@
|
|||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import 'popper.js';
|
import 'popper.js';
|
||||||
import Popper from 'popper.js';
|
import Popper from 'popper.js';
|
||||||
(window as unknown as {Popper:unknown}).Popper = Popper;
|
(window as unknown as { Popper: unknown }).Popper = Popper;
|
||||||
import 'bootstrap';
|
import 'bootstrap';
|
||||||
import { JQValidateForm, NamedRules } from '../util/jqValidateForm';
|
import { JQValidateForm, NamedRules } from '../util/jqValidateForm';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
@@ -11,9 +11,12 @@ import { unwrap_any } from '../util/unwrap_any';
|
|||||||
import { sha512 } from 'js-sha512';
|
import { sha512 } from 'js-sha512';
|
||||||
import { unwrap } from '../util/unwrap';
|
import { unwrap } from '../util/unwrap';
|
||||||
import { InvalidResponse } from '../defs';
|
import { InvalidResponse } from '../defs';
|
||||||
|
import internal from 'stream';
|
||||||
|
import { delay } from '../util/delay';
|
||||||
|
|
||||||
type LoginResponse = {
|
type LoginResponse = {
|
||||||
result: true,
|
result: true,
|
||||||
|
nextToken: [number, string] | undefined,
|
||||||
} | {
|
} | {
|
||||||
result: false,
|
result: false,
|
||||||
reqOTP: boolean,
|
reqOTP: boolean,
|
||||||
@@ -29,6 +32,19 @@ type OTPResponse = {
|
|||||||
reason: string,
|
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 {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
getOAuthToken: (mode: string, scope_list: string[]) => void;
|
getOAuthToken: (mode: string, scope_list: string[]) => void;
|
||||||
@@ -41,6 +57,92 @@ declare const kakao_oauth_redirect_uri: string;
|
|||||||
|
|
||||||
let oauthMode: string | null = null;
|
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<AutoLoginNonceResponse>('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<AutoLoginResponse>('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) {
|
function getOAuthToken(mode: string, scope_list?: string[] | string) {
|
||||||
if (mode === undefined) {
|
if (mode === undefined) {
|
||||||
mode = 'login';
|
mode = 'login';
|
||||||
@@ -115,6 +217,9 @@ async function doLoginUsingOAuth() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (result.result) {
|
if (result.result) {
|
||||||
|
if (result.nextToken) {
|
||||||
|
regNextToken(result.nextToken);
|
||||||
|
}
|
||||||
window.location.href = "./";
|
window.location.href = "./";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -156,9 +261,16 @@ function postOAuthResult(mode: string) {
|
|||||||
window.postOAuthResult = postOAuthResult;
|
window.postOAuthResult = postOAuthResult;
|
||||||
|
|
||||||
|
|
||||||
$(function ($) {
|
$(async function ($) {
|
||||||
setAxiosXMLHttpRequest();
|
setAxiosXMLHttpRequest();
|
||||||
|
|
||||||
|
//로그인 먼저 해볼 것
|
||||||
|
if(getToken()){
|
||||||
|
void tryAutoLogin();
|
||||||
|
await delay(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
type LoginFormType = {
|
type LoginFormType = {
|
||||||
username: string,
|
username: string,
|
||||||
password: string,
|
password: string,
|
||||||
@@ -194,13 +306,16 @@ $(function ($) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await axios({
|
const response = await axios({
|
||||||
url: 'j_login.php',
|
url: 'api.php',
|
||||||
responseType: 'json',
|
responseType: 'json',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data: convertFormData({
|
data: {
|
||||||
username: values.username,
|
path: ["Login", "LoginByID"],
|
||||||
password: hash_pw,
|
args: {
|
||||||
})
|
username: values.username,
|
||||||
|
password: hash_pw,
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
result = response.data;
|
result = response.data;
|
||||||
}
|
}
|
||||||
@@ -212,6 +327,9 @@ $(function ($) {
|
|||||||
|
|
||||||
|
|
||||||
if (result.result) {
|
if (result.result) {
|
||||||
|
if (result.nextToken) {
|
||||||
|
regNextToken(result.nextToken);
|
||||||
|
}
|
||||||
window.location.href = "./";
|
window.location.href = "./";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -268,12 +386,11 @@ $(function ($) {
|
|||||||
getOAuthToken('change_pw', 'talk_message');
|
getOAuthToken('change_pw', 'talk_message');
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#btn_kakao_login').on('click', function(e){
|
$('#btn_kakao_login').on('click', function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
getOAuthToken('login', ['account_email','talk_message']);
|
getOAuthToken('login', ['account_email', 'talk_message']);
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
//TODO: 모바일에서 크기 비례 지도 다시 띄우기?
|
//TODO: 모바일에서 크기 비례 지도 다시 띄우기?
|
||||||
/*
|
/*
|
||||||
if (document.body.clientWidth < 700) {
|
if (document.body.clientWidth < 700) {
|
||||||
|
|||||||
-181
@@ -1,181 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace sammo;
|
|
||||||
|
|
||||||
require(__DIR__ . '/vendor/autoload.php');
|
|
||||||
require(__DIR__ . '/oauth_kakao/lib.join.php');
|
|
||||||
|
|
||||||
WebUtil::requireAJAX();
|
|
||||||
|
|
||||||
use \kakao\Kakao_REST_API_Helper as Kakao_REST_API_Helper;
|
|
||||||
|
|
||||||
$RootDB = RootDB::db();
|
|
||||||
$session = Session::getInstance();
|
|
||||||
if ($session->isLoggedIn()) {
|
|
||||||
$session->logout();
|
|
||||||
}
|
|
||||||
|
|
||||||
$username = mb_strtolower(Util::getPost('username'), 'utf-8');
|
|
||||||
$password = Util::getPost('password');
|
|
||||||
|
|
||||||
if (!$username || !$password) {
|
|
||||||
Json::die([
|
|
||||||
'result' => false,
|
|
||||||
'reqOTP' => false,
|
|
||||||
'reason' => '올바르지 않은 입력입니다.'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function kakaoOAuthCheck(array $userInfo): ?array
|
|
||||||
{
|
|
||||||
|
|
||||||
if (!\kakao\KakaoKey::REST_KEY) {
|
|
||||||
return [false, '카카오 API 앱이 등록되지 않았습니다. 관리자에게 문의해 주세요.'];
|
|
||||||
}
|
|
||||||
|
|
||||||
$oauthID = $userInfo['oauth_id'];
|
|
||||||
$oauthInfo = Json::decode($userInfo['oauth_info']) ?? [];
|
|
||||||
if (!$oauthInfo) {
|
|
||||||
return [false, 'OAuth 정보가 보관되어 있지 않습니다. 카카오 로그인을 수행해 주세요.'];
|
|
||||||
}
|
|
||||||
|
|
||||||
$accessToken = $oauthInfo['accessToken'] ?? null;
|
|
||||||
$refreshToken = $oauthInfo['refreshToken'] ?? null;
|
|
||||||
$accessTokenValidUntil = $oauthInfo['accessTokenValidUntil'] ?? null;
|
|
||||||
$refreshTokenValidUntil = $oauthInfo['refreshTokenValidUntil'] ?? null;
|
|
||||||
$OTPValue = $oauthInfo['OTPValue'] ?? null;
|
|
||||||
$OTPTrialUntil = $oauthInfo['OTPTrialUntil'] ?? null;
|
|
||||||
$tokenValidUntil = $userInfo['token_valid_until'];
|
|
||||||
|
|
||||||
if (!$accessToken || !$refreshToken || !$accessTokenValidUntil || !$refreshTokenValidUntil) {
|
|
||||||
return [false, 'OAuth 정보가 보관되어 있지 않습니다. 카카오 로그인을 수행해 주세요.'];
|
|
||||||
}
|
|
||||||
|
|
||||||
$now = TimeUtil::now();
|
|
||||||
|
|
||||||
if ($now > $refreshTokenValidUntil) {
|
|
||||||
return [false, '로그인 토큰이 만료되었습니다. 카카오 로그인을 수행해 주세요.'];
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($now > $accessTokenValidUntil) {
|
|
||||||
$apiHelper = new Kakao_REST_API_Helper($accessToken);
|
|
||||||
$refreshResult = $apiHelper->refresh_access_token($refreshToken);
|
|
||||||
if (!$refreshResult) {
|
|
||||||
return [false, '로그인 토큰 자동 갱신을 실패했습니다. 카카오 로그인을 수행해 주세요.'];
|
|
||||||
}
|
|
||||||
|
|
||||||
$accessToken = $refreshResult['access_token'] ?? null;
|
|
||||||
|
|
||||||
if (!$accessToken) {
|
|
||||||
trigger_error("refreshToken 에러 " . Json::encode($refreshResult) . "," . $refreshToken . "," . substr(\kakao\KakaoKey::REST_KEY, 0, 6), E_USER_NOTICE);
|
|
||||||
return [false, '로그인 토큰 자동 갱신을 실패했습니다. 카카오 로그인을 수행해 주세요.'];
|
|
||||||
}
|
|
||||||
$accessTokenValidUntil = TimeUtil::nowAddSeconds($refreshResult['expires_in']);
|
|
||||||
|
|
||||||
$oauthInfo['accessToken'] = $accessToken;
|
|
||||||
$oauthInfo['accessTokenValidUntil'] = $accessTokenValidUntil;
|
|
||||||
|
|
||||||
$refreshToken = $refreshResult['refresh_token'] ?? null;
|
|
||||||
if ($refreshToken) {
|
|
||||||
$refreshTokenValidUntil = TimeUtil::nowAddSeconds($refreshResult['refresh_token_expires_in']);
|
|
||||||
|
|
||||||
$oauthInfo['refreshToken'] = $refreshToken;
|
|
||||||
$oauthInfo['refresh_token_expires_in'] = $refreshTokenValidUntil;
|
|
||||||
}
|
|
||||||
|
|
||||||
RootDB::db()->update('member', [
|
|
||||||
'oauth_info' => Json::encode($oauthInfo)
|
|
||||||
], 'no=%i', $userInfo['no']);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($tokenValidUntil && $now <= $tokenValidUntil) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
//인증 시스템 가동
|
|
||||||
$session = Session::getInstance();
|
|
||||||
$session->access_token = $accessToken;
|
|
||||||
$session->expires = $accessTokenValidUntil;
|
|
||||||
$session->refresh_token = $refreshToken;
|
|
||||||
$session->refresh_token_expires = $refreshTokenValidUntil;
|
|
||||||
|
|
||||||
if (!createOTPbyUserNO($userInfo['no'])) {
|
|
||||||
return [false, '인증 코드를 보내는데 실패했습니다.'];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [true, '인증 코드를 입력해주세요'];
|
|
||||||
}
|
|
||||||
|
|
||||||
$userInfo = $RootDB->queryFirstRow(
|
|
||||||
'SELECT `no`, `id`, `name`, `grade`, `delete_after`, `acl`, oauth_id, oauth_type, oauth_info, token_valid_until ' .
|
|
||||||
'from member where id=%s_username AND ' .
|
|
||||||
'pw=sha2(concat(salt, %s_password, salt), 512)',
|
|
||||||
[
|
|
||||||
'username' => $username,
|
|
||||||
'password' => $password
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!$userInfo) {
|
|
||||||
Json::die([
|
|
||||||
'result' => false,
|
|
||||||
'reqOTP' => false,
|
|
||||||
'reason' => '아이디나 비밀번호가 올바르지 않습니다.'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$canLogin = $RootDB->queryFirstField('SELECT `LOGIN` FROM `system` WHERE `NO` = 1');
|
|
||||||
if ($canLogin != 'Y' && $userInfo['grade'] < 5) {
|
|
||||||
Json::die([
|
|
||||||
'result' => false,
|
|
||||||
'reason' => '현재는 로그인이 금지되어있습니다!'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$nowDate = TimeUtil::now();
|
|
||||||
if ($userInfo['delete_after']) {
|
|
||||||
if ($userInfo['delete_after'] < $nowDate) {
|
|
||||||
$RootDB->delete('member', 'no=%i', $userInfo['no']);
|
|
||||||
Json::die([
|
|
||||||
'result' => false,
|
|
||||||
'reqOTP' => false,
|
|
||||||
'reason' => "기간 만기로 삭제되었습니다. 재 가입을 시도해주세요."
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
Json::die([
|
|
||||||
'result' => false,
|
|
||||||
'reqOTP' => false,
|
|
||||||
'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' => 'plain'
|
|
||||||
])
|
|
||||||
]);
|
|
||||||
|
|
||||||
if ($userInfo['oauth_type'] == 'KAKAO') {
|
|
||||||
$oauthFailResult = kakaoOAuthCheck($userInfo);
|
|
||||||
if ($oauthFailResult !== null) {
|
|
||||||
$session->login($userInfo['no'], $userInfo['id'], $userInfo['grade'], true, $userInfo['token_valid_until'], Json::decode($userInfo['acl'] ?? '{}'));
|
|
||||||
[$oauthReqOTP, $oauthFailReason] = $oauthFailResult;
|
|
||||||
Json::die([
|
|
||||||
'result' => false,
|
|
||||||
'reqOTP' => $oauthReqOTP,
|
|
||||||
'reason' => $oauthFailReason
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
$session->login($userInfo['no'], $userInfo['id'], $userInfo['grade'], false, $userInfo['token_valid_until'], Json::decode($userInfo['acl'] ?? '{}'));
|
|
||||||
Json::die([
|
|
||||||
'result' => true,
|
|
||||||
'reqOTP' => false,
|
|
||||||
'reason' => '로그인 되었습니다.'
|
|
||||||
]);
|
|
||||||
+123
-82
@@ -1,25 +1,30 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace sammo;
|
namespace sammo;
|
||||||
|
|
||||||
require(__DIR__.'/../vendor/autoload.php');
|
require(__DIR__ . '/../vendor/autoload.php');
|
||||||
require('lib.join.php');
|
require('lib.join.php');
|
||||||
|
|
||||||
|
|
||||||
|
//TODO: KakaoOauth를 API로 정식으로 이동
|
||||||
|
|
||||||
WebUtil::requireAJAX();
|
WebUtil::requireAJAX();
|
||||||
|
|
||||||
use \kakao\Kakao_REST_API_Helper as Kakao_REST_API_Helper;
|
use \kakao\Kakao_REST_API_Helper as Kakao_REST_API_Helper;
|
||||||
|
|
||||||
$RootDB = RootDB::db();
|
$RootDB = RootDB::db();
|
||||||
$session = Session::getInstance();
|
$session = Session::getInstance();
|
||||||
if($session->isLoggedIn()){
|
if ($session->isLoggedIn()) {
|
||||||
$session->logout();
|
$session->logout();
|
||||||
}
|
}
|
||||||
|
|
||||||
$canLogin = RootDB::db()->queryFirstField('SELECT `LOGIN` FROM `system` WHERE `NO` = 1');
|
$canLogin = RootDB::db()->queryFirstField('SELECT `LOGIN` FROM `system` WHERE `NO` = 1');
|
||||||
if($canLogin != 'Y'){
|
if ($canLogin != 'Y') {
|
||||||
Json::die([
|
Json::die([
|
||||||
'result'=>false,
|
'result' => false,
|
||||||
'reqOTP'=>false,
|
'reqOTP' => false,
|
||||||
'reason'=>'현재는 로그인이 금지되어있습니다!',
|
'reason' => '현재는 로그인이 금지되어있습니다!',
|
||||||
'noRetry'=>true
|
'noRetry' => true
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,173 +37,209 @@ $refresh_token_expires = $session->refresh_token_expires;
|
|||||||
$email = $session->kaccount_email;
|
$email = $session->kaccount_email;
|
||||||
|
|
||||||
|
|
||||||
if(!$access_token || !$expires){
|
if (!$access_token || !$expires) {
|
||||||
Json::die([
|
Json::die([
|
||||||
'result'=>false,
|
'result' => false,
|
||||||
'reqOTP'=>false,
|
'reqOTP' => false,
|
||||||
'reason'=>'카카오로그인이 이루어지지 않았습니다.'
|
'reason' => '카카오로그인이 이루어지지 않았습니다.'
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: join과 login의 동작이 비슷하다. helper class로 묶자.
|
//TODO: join과 login의 동작이 비슷하다. helper class로 묶자.
|
||||||
$restAPI = new Kakao_REST_API_Helper($access_token);
|
$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([
|
Json::die([
|
||||||
'result'=>false,
|
'result' => false,
|
||||||
'reqOTP'=>false,
|
'reqOTP' => false,
|
||||||
'reason'=>'로그인 토큰 만료.'.$refresh_token_expires.' 다시 카카오로그인을 수행해주세요.'
|
'reason' => '로그인 토큰 만료.' . $refresh_token_expires . ' 다시 카카오로그인을 수행해주세요.'
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if($expires < $now){
|
if ($expires < $now) {
|
||||||
$session->kaccount_email = null;
|
$session->kaccount_email = null;
|
||||||
$email = null;
|
$email = null;
|
||||||
|
|
||||||
$result = $restAPI->refresh_access_token($refresh_token);
|
$result = $restAPI->refresh_access_token($refresh_token);
|
||||||
if(!isset($refresh_token)){
|
if (!isset($refresh_token)) {
|
||||||
Json::die([
|
Json::die([
|
||||||
'result'=>false,
|
'result' => false,
|
||||||
'reqOTP'=>false,
|
'reqOTP' => false,
|
||||||
'reason'=>'카카오 로그인 과정 중 추가 갱신 절차를 실패했습니다'
|
'reason' => '카카오 로그인 과정 중 추가 갱신 절차를 실패했습니다'
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$access_token = $result['access_token'];
|
$access_token = $result['access_token'];
|
||||||
$expires = TimeUtil::nowAddSeconds($result['expires_in']);
|
$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 = Util::array_get($result['refresh_token']);
|
||||||
$refresh_token_expires = TimeUtil::nowAddSeconds($result['refresh_token_expires_in']);
|
$refresh_token_expires = TimeUtil::nowAddSeconds($result['refresh_token_expires_in']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if(!$email){
|
if (!$email) {
|
||||||
$me = $restAPI->meWithEmail();
|
$me = $restAPI->meWithEmail();
|
||||||
$me['code'] = Util::array_get($me['code'], 0);
|
$me['code'] = Util::array_get($me['code'], 0);
|
||||||
if ($me['code']< 0) {
|
if ($me['code'] < 0) {
|
||||||
Json::die([
|
Json::die([
|
||||||
'result'=>false,
|
'result' => false,
|
||||||
'reqOTP'=>false,
|
'reqOTP' => false,
|
||||||
'reason'=>'카카오로그인이 정상적으로 수행되지 않았습니다.'
|
'reason' => '카카오로그인이 정상적으로 수행되지 않았습니다.'
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$kakao_account = $me['kakao_account']??null;
|
$kakao_account = $me['kakao_account'] ?? null;
|
||||||
if (!$kakao_account) {
|
if (!$kakao_account) {
|
||||||
Json::die([
|
Json::die([
|
||||||
'result'=>false,
|
'result' => false,
|
||||||
'reqOTP'=>false,
|
'reqOTP' => false,
|
||||||
'reason'=>'카카오 로그인 정보를 제대로 받아오지 못했습니다.'
|
'reason' => '카카오 로그인 정보를 제대로 받아오지 못했습니다.'
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!($kakao_account['has_email']??false)){
|
if (!($kakao_account['has_email'] ?? false)) {
|
||||||
Json::die([
|
Json::die([
|
||||||
'result'=>false,
|
'result' => false,
|
||||||
'reqOTP'=>false,
|
'reqOTP' => false,
|
||||||
'reason'=>'이메일 정보 공유를 허가해 주셔야 합니다.',
|
'reason' => '이메일 정보 공유를 허가해 주셔야 합니다.',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$validEmail = $kakao_account['is_email_valid']??false;
|
$validEmail = $kakao_account['is_email_valid'] ?? false;
|
||||||
$verifiedEmail = $kakao_account['is_email_verified']??false;
|
$verifiedEmail = $kakao_account['is_email_verified'] ?? false;
|
||||||
|
|
||||||
if(!$validEmail || !$verifiedEmail){
|
if (!$validEmail || !$verifiedEmail) {
|
||||||
$restAPI->unlink();
|
$restAPI->unlink();
|
||||||
Json::die([
|
Json::die([
|
||||||
'result'=>false,
|
'result' => false,
|
||||||
'reqOTP'=>false,
|
'reqOTP' => false,
|
||||||
'reason'=>'카카오 계정 이메일이 아직 인증되지 않았습니다',
|
'reason' => '카카오 계정 이메일이 아직 인증되지 않았습니다',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$email = $kakao_account['email'];
|
$email = $kakao_account['email'];
|
||||||
$session->kaccount_email = $email;
|
$session->kaccount_email = $email;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
$userInfo = $RootDB->queryFirstRow(
|
$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();
|
$restAPI->unlink();
|
||||||
$session->access_token = null;
|
$session->access_token = null;
|
||||||
Json::die([
|
Json::die([
|
||||||
'result'=>false,
|
'result' => false,
|
||||||
'reqOTP'=>false,
|
'reqOTP' => false,
|
||||||
'reason'=>'카카오로그인에 해당하는 계정이 없습니다. 재 가입을 시도해주세요.',
|
'reason' => '카카오로그인에 해당하는 계정이 없습니다. 재 가입을 시도해주세요.',
|
||||||
'aux'=>$session->tmpx,
|
'aux' => $session->tmpx,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if($userInfo['delete_after']){
|
if ($userInfo['delete_after']) {
|
||||||
if($userInfo['delete_after'] < $now){
|
if ($userInfo['delete_after'] < $now) {
|
||||||
$restAPI->unlink();
|
$restAPI->unlink();
|
||||||
$session->access_token = null;
|
$session->access_token = null;
|
||||||
$RootDB->delete('member', 'no=%i', $userInfo['no']);
|
$RootDB->delete('member', 'no=%i', $userInfo['no']);
|
||||||
Json::die([
|
Json::die([
|
||||||
'result'=>false,
|
'result' => false,
|
||||||
'reqOTP'=>false,
|
'reqOTP' => false,
|
||||||
'reason'=>"기간 만기로 삭제되었습니다. 재 가입을 시도해주세요.",
|
'reason' => "기간 만기로 삭제되었습니다. 재 가입을 시도해주세요.",
|
||||||
]);
|
]);
|
||||||
}
|
} else {
|
||||||
else{
|
|
||||||
$restAPI->unlink();
|
$restAPI->unlink();
|
||||||
$session->access_token = null;
|
$session->access_token = null;
|
||||||
Json::die([
|
Json::die([
|
||||||
'result'=>false,
|
'result' => false,
|
||||||
'reqOTP'=>false,
|
'reqOTP' => false,
|
||||||
'reason'=>"삭제 요청된 계정입니다.[{$userInfo['delete_after']}]"
|
'reason' => "삭제 요청된 계정입니다.[{$userInfo['delete_after']}]"
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$oauthInfo = Json::decode($userInfo['oauth_info'])??[];
|
$oauthInfo = Json::decode($userInfo['oauth_info']) ?? [];
|
||||||
$oauthInfo['accessToken'] = $access_token;
|
$oauthInfo['accessToken'] = $access_token;
|
||||||
$oauthInfo['accessTokenValidUntil'] = $expires;
|
$oauthInfo['accessTokenValidUntil'] = $expires;
|
||||||
if($refresh_token){
|
if ($refresh_token) {
|
||||||
$oauthInfo['refreshToken'] = $refresh_token;
|
$oauthInfo['refreshToken'] = $refresh_token;
|
||||||
$oauthInfo['refreshTokenValidUntil'] = $refresh_token_expires;
|
$oauthInfo['refreshTokenValidUntil'] = $refresh_token_expires;
|
||||||
}
|
}
|
||||||
|
|
||||||
RootDB::db()->update('member', [
|
RootDB::db()->update('member', [
|
||||||
'oauth_info'=>Json::encode($oauthInfo)
|
'oauth_info' => Json::encode($oauthInfo)
|
||||||
], 'no=%i', $userInfo['no']);
|
], 'no=%i', $userInfo['no']);
|
||||||
|
|
||||||
|
|
||||||
$tokenValidUntil = $userInfo['token_valid_until'];
|
$tokenValidUntil = $userInfo['token_valid_until'];
|
||||||
|
|
||||||
if(!$tokenValidUntil || $tokenValidUntil < $now){
|
if (!$tokenValidUntil || $tokenValidUntil < $now) {
|
||||||
if(!createOTPbyUserNO($userInfo['no'])){
|
if (!createOTPbyUserNO($userInfo['no'])) {
|
||||||
Json::die([
|
Json::die([
|
||||||
'result'=>false,
|
'result' => false,
|
||||||
'reqOTP'=>false,
|
'reqOTP' => false,
|
||||||
'reason'=>'인증 코드를 보내는데 실패했습니다.'
|
'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([
|
Json::die([
|
||||||
'result'=>false,
|
'result' => false,
|
||||||
'reqOTP'=>true,
|
'reqOTP' => true,
|
||||||
'reason'=>'인증 코드를 입력해주세요.'
|
'reason' => '인증 코드를 입력해주세요.'
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
$RootDB->insert('member_log',[
|
$RootDB->insert('member_log', [
|
||||||
'member_no'=>$userInfo['no'],
|
'member_no' => $userInfo['no'],
|
||||||
'action_type'=>'login',
|
'action_type' => 'login',
|
||||||
'action'=>Json::encode([
|
'action' => Json::encode([
|
||||||
'ip'=>Util::get_client_ip(true),
|
'ip' => Util::get_client_ip(true),
|
||||||
'type'=>'kakao'
|
'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([
|
Json::die([
|
||||||
'result'=>true,
|
'result' => true,
|
||||||
'reqOTP'=>false,
|
'reqOTP' => false,
|
||||||
'reason'=>'로그인 되었습니다.'
|
'nextToken' => [$tokenID, $token],
|
||||||
]);
|
'reason' => '로그인 되었습니다.'
|
||||||
|
]);
|
||||||
|
|||||||
@@ -0,0 +1,162 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace sammo\API\Login;
|
||||||
|
|
||||||
|
use DateTime;
|
||||||
|
use sammo\Session;
|
||||||
|
use DateTimeInterface;
|
||||||
|
use sammo\BaseAPI;
|
||||||
|
use sammo\Json;
|
||||||
|
use sammo\KakaoUtil;
|
||||||
|
use sammo\RootDB;
|
||||||
|
use sammo\TimeUtil;
|
||||||
|
use sammo\Util;
|
||||||
|
use sammo\Validator;
|
||||||
|
|
||||||
|
class LoginByID extends \sammo\BaseAPI
|
||||||
|
{
|
||||||
|
public function validateArgs(): ?string
|
||||||
|
{
|
||||||
|
$v = new Validator($this->args);
|
||||||
|
$v
|
||||||
|
->rule('required', [
|
||||||
|
'username',
|
||||||
|
'password'
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!$v->validate()) {
|
||||||
|
return $v->errorStr();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRequiredSessionMode(): int
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
if ($session->isLoggedIn()) {
|
||||||
|
$session->logout();
|
||||||
|
}
|
||||||
|
|
||||||
|
$username = $this->args['username'];
|
||||||
|
$password = $this->args['password'];
|
||||||
|
|
||||||
|
$userInfo = $RootDB->queryFirstRow(
|
||||||
|
'SELECT `no`, `id`, `name`, `grade`, `delete_after`, `acl`, oauth_id, oauth_type, oauth_info, token_valid_until ' .
|
||||||
|
'from member where id=%s_username AND ' .
|
||||||
|
'pw=sha2(concat(salt, %s_password, salt), 512)',
|
||||||
|
[
|
||||||
|
'username' => $username,
|
||||||
|
'password' => $password
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
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,
|
||||||
|
'reqOTP' => false,
|
||||||
|
'reason' => "기간 만기로 삭제되었습니다. 재 가입을 시도해주세요."
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
return [
|
||||||
|
'result' => false,
|
||||||
|
'reqOTP' => false,
|
||||||
|
'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' => 'plain'
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
|
||||||
|
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;
|
||||||
|
return [
|
||||||
|
'result' => false,
|
||||||
|
'reqOTP' => $oauthReqOTP,
|
||||||
|
'reason' => $oauthFailReason
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$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'] ?? '{}'));
|
||||||
|
|
||||||
|
|
||||||
|
return [
|
||||||
|
'result' => true,
|
||||||
|
'reqOTP' => false,
|
||||||
|
'nextToken' => $nextToken,
|
||||||
|
'reason' => '로그인 되었습니다.'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace sammo\API\Login;
|
||||||
|
|
||||||
|
use sammo\Session;
|
||||||
|
use DateTimeInterface;
|
||||||
|
use sammo\BaseAPI;
|
||||||
|
use sammo\Json;
|
||||||
|
use sammo\KakaoUtil;
|
||||||
|
use sammo\RootDB;
|
||||||
|
use sammo\TimeUtil;
|
||||||
|
use sammo\Util;
|
||||||
|
use sammo\Validator;
|
||||||
|
|
||||||
|
class LoginByToken extends LoginByID
|
||||||
|
{
|
||||||
|
|
||||||
|
public function getRequiredSessionMode(): int
|
||||||
|
{
|
||||||
|
return \sammo\BaseAPI::NO_SESSION; //XXX: Token 때문에 엄밀히는 NO_SESSION이 아님
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
$loginNonce = $session->loginNonce ?? null;
|
||||||
|
$loginNonceExpired = $session->loginNonceExpired ?? null;
|
||||||
|
if (!is_string($loginNonce) || !is_string($loginNonceExpired)) {
|
||||||
|
return '자동 로그인: 절차 오류';
|
||||||
|
}
|
||||||
|
|
||||||
|
$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'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace sammo\API\Login;
|
||||||
|
|
||||||
|
use sammo\Session;
|
||||||
|
use DateTimeInterface;
|
||||||
|
use sammo\BaseAPI;
|
||||||
|
use sammo\Json;
|
||||||
|
use sammo\KakaoUtil;
|
||||||
|
use sammo\RootDB;
|
||||||
|
use sammo\TimeUtil;
|
||||||
|
use sammo\Util;
|
||||||
|
use sammo\Validator;
|
||||||
|
|
||||||
|
class ReqNonce extends BaseAPI{
|
||||||
|
|
||||||
|
public function getRequiredSessionMode(): int {
|
||||||
|
return \sammo\BaseAPI::NO_SESSION;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function validateArgs(): ?string
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag) {
|
||||||
|
$loginNonce = Util::randomStr(16);
|
||||||
|
$loginNonceExpired = TimeUtil::nowAddSeconds(2);
|
||||||
|
$session->loginNonce = $loginNonce;
|
||||||
|
$session->loginNonceExpired = $loginNonceExpired;
|
||||||
|
return [
|
||||||
|
'result'=>true,
|
||||||
|
'loginNonce'=>$loginNonce,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -40,7 +40,7 @@ class APIHelper
|
|||||||
|
|
||||||
$sessionMode = $obj->getRequiredSessionMode();
|
$sessionMode = $obj->getRequiredSessionMode();
|
||||||
if ($sessionMode === BaseAPI::NO_SESSION) {
|
if ($sessionMode === BaseAPI::NO_SESSION) {
|
||||||
$session = null;
|
$session = Session::getInstance();//XXX: NoSession이면 진짜 NoSession이어야..?
|
||||||
} else {
|
} else {
|
||||||
if ($sessionMode & BaseAPI::REQ_GAME_LOGIN) {
|
if ($sessionMode & BaseAPI::REQ_GAME_LOGIN) {
|
||||||
$session = Session::requireGameLogin();
|
$session = Session::requireGameLogin();
|
||||||
@@ -94,7 +94,7 @@ class APIHelper
|
|||||||
], $cacheResult === null ? Json::NO_CACHE : 0);
|
], $cacheResult === null ? Json::NO_CACHE : 0);
|
||||||
}
|
}
|
||||||
Json::die($result, $cacheResult === null ? Json::NO_CACHE : 0);
|
Json::die($result, $cacheResult === null ? Json::NO_CACHE : 0);
|
||||||
} catch (\Exception $e) {
|
} catch (\Throwable $e) {
|
||||||
Json::dieWithReason($e->getMessage());
|
Json::dieWithReason($e->getMessage());
|
||||||
} catch (mixed $e) {
|
} catch (mixed $e) {
|
||||||
Json::dieWithReason(strval($e));
|
Json::dieWithReason(strval($e));
|
||||||
|
|||||||
@@ -0,0 +1,221 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace sammo;
|
||||||
|
|
||||||
|
use kakao\Kakao_REST_API_Helper;
|
||||||
|
|
||||||
|
class KakaoUtil
|
||||||
|
{
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
static function checkUsernameDup($username)
|
||||||
|
{
|
||||||
|
if (!$username) {
|
||||||
|
return '계정명을 입력해주세요';
|
||||||
|
}
|
||||||
|
|
||||||
|
$username = mb_strtolower($username, 'utf-8');
|
||||||
|
$length = strlen($username);
|
||||||
|
if ($length < 4 || $length > 64) {
|
||||||
|
return '적절하지 않은 길이입니다.';
|
||||||
|
}
|
||||||
|
|
||||||
|
$cnt = RootDB::db()->queryFirstField('SELECT count(no) FROM member WHERE `id` = %s LIMIT 1', $username);
|
||||||
|
if ($cnt != 0) {
|
||||||
|
return '이미 사용중인 계정명입니다';
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function checkNicknameDup($nickname)
|
||||||
|
{
|
||||||
|
if (!$nickname) {
|
||||||
|
return '닉네임을 입력해주세요';
|
||||||
|
}
|
||||||
|
|
||||||
|
$length = mb_strwidth($nickname, 'utf-8');
|
||||||
|
if ($length < 1 || $length > 18) {
|
||||||
|
return '적절하지 않은 길이입니다.';
|
||||||
|
}
|
||||||
|
|
||||||
|
$cnt = RootDB::db()->queryFirstField('SELECT count(no) FROM member WHERE `name` = %s LIMIT 1', $nickname);
|
||||||
|
if ($cnt != 0) {
|
||||||
|
return '이미 사용중인 닉네임입니다';
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static function checkEmailDup($email)
|
||||||
|
{
|
||||||
|
if (!$email) {
|
||||||
|
return '이메일을 입력해주세요';
|
||||||
|
}
|
||||||
|
|
||||||
|
$length = strlen($email);
|
||||||
|
if ($length < 1 || $length > 64) {
|
||||||
|
return '적절하지 않은 길이입니다.';
|
||||||
|
}
|
||||||
|
|
||||||
|
$userInfo = RootDB::db()->queryFirstField('SELECT `no`, `delete_after` FROM member WHERE `email` = %s LIMIT 1', $email);
|
||||||
|
if ($userInfo) {
|
||||||
|
if (!$userInfo['delete_after']) {
|
||||||
|
return '이미 사용중인 이메일입니다. 관리자에게 문의해주세요.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($userInfo['delete_after'] >= $userInfo) {
|
||||||
|
return "삭제 요청된 계정입니다.[{$userInfo['delete_after']}]";
|
||||||
|
}
|
||||||
|
|
||||||
|
//$userInfo['delete_after'] < $userInfo
|
||||||
|
RootDB::db()->delete('member', 'no=%i', $userInfo['no']);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function createOTPbyUserNO(int $userNo): bool
|
||||||
|
{
|
||||||
|
$userInfo = RootDB::db()->queryFirstRow('SELECT oauth_info FROM member WHERE no=%i', $userNo);
|
||||||
|
if (!$userInfo) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$oauthInfo = Json::decode($userInfo['oauth_info']);
|
||||||
|
if (!$oauthInfo) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$accessToken = $oauthInfo['accessToken'];
|
||||||
|
$OTPValue = $oauthInfo['OTPValue'] ?? null;
|
||||||
|
$OTPTrialUntil = $oauthInfo['OTPTrialUntil'] ?? null;
|
||||||
|
|
||||||
|
$now = TimeUtil::now();
|
||||||
|
|
||||||
|
|
||||||
|
if ($OTPTrialUntil && $OTPValue && $OTPTrialUntil > $now) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[$OTPValue, $OTPTrialUntil] = static::createOTP($accessToken);
|
||||||
|
|
||||||
|
if (!$OTPValue) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$oauthInfo['OTPValue'] = $OTPValue;
|
||||||
|
$oauthInfo['OTPTrialUntil'] = $OTPTrialUntil;
|
||||||
|
$oauthInfo['OTPTrialCount'] = 3;
|
||||||
|
|
||||||
|
RootDB::db()->update('member', [
|
||||||
|
'oauth_info' => Json::encode($oauthInfo)
|
||||||
|
], 'no=%i', $userNo);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function createOTP(string $accessToken): ?array
|
||||||
|
{
|
||||||
|
$restAPI = new Kakao_REST_API_Helper($accessToken);
|
||||||
|
|
||||||
|
$OTPValue = Util::randRangeInt(1000, 9999);
|
||||||
|
$OTPTrialUntil = TimeUtil::nowAddSeconds(180);
|
||||||
|
|
||||||
|
$sendResult = $restAPI->talk_to_me_default([
|
||||||
|
"object_type" => "text",
|
||||||
|
"text" => "인증 코드는 $OTPValue 입니다. $OTPTrialUntil 이내에 입력해주세요.",
|
||||||
|
"link" => [
|
||||||
|
"web_url" => ServConfig::getServerBasepath(),
|
||||||
|
"mobile_web_url" => ServConfig::getServerBasepath()
|
||||||
|
],
|
||||||
|
"button_title" => "로그인 페이지 열기"
|
||||||
|
]);
|
||||||
|
$sendResult['code'] = Util::array_get($sendResult['code'], 0);
|
||||||
|
if ($sendResult['code'] < 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [$OTPValue, $OTPTrialUntil];
|
||||||
|
}
|
||||||
|
|
||||||
|
static function kakaoOAuthCheck(array $userInfo): ?array
|
||||||
|
{
|
||||||
|
|
||||||
|
if (!\kakao\KakaoKey::REST_KEY) {
|
||||||
|
return [false, '카카오 API 앱이 등록되지 않았습니다. 관리자에게 문의해 주세요.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$oauthID = $userInfo['oauth_id'];
|
||||||
|
$oauthInfo = Json::decode($userInfo['oauth_info']) ?? [];
|
||||||
|
if (!$oauthInfo) {
|
||||||
|
return [false, 'OAuth 정보가 보관되어 있지 않습니다. 카카오 로그인을 수행해 주세요.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$accessToken = $oauthInfo['accessToken'] ?? null;
|
||||||
|
$refreshToken = $oauthInfo['refreshToken'] ?? null;
|
||||||
|
$accessTokenValidUntil = $oauthInfo['accessTokenValidUntil'] ?? null;
|
||||||
|
$refreshTokenValidUntil = $oauthInfo['refreshTokenValidUntil'] ?? null;
|
||||||
|
$OTPValue = $oauthInfo['OTPValue'] ?? null;
|
||||||
|
$OTPTrialUntil = $oauthInfo['OTPTrialUntil'] ?? null;
|
||||||
|
$tokenValidUntil = $userInfo['token_valid_until'];
|
||||||
|
|
||||||
|
if (!$accessToken || !$refreshToken || !$accessTokenValidUntil || !$refreshTokenValidUntil) {
|
||||||
|
return [false, 'OAuth 정보가 보관되어 있지 않습니다. 카카오 로그인을 수행해 주세요.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$now = TimeUtil::now();
|
||||||
|
|
||||||
|
if ($now > $refreshTokenValidUntil) {
|
||||||
|
return [false, '로그인 토큰이 만료되었습니다. 카카오 로그인을 수행해 주세요.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($now > $accessTokenValidUntil) {
|
||||||
|
$apiHelper = new Kakao_REST_API_Helper($accessToken);
|
||||||
|
$refreshResult = $apiHelper->refresh_access_token($refreshToken);
|
||||||
|
if (!$refreshResult) {
|
||||||
|
return [false, '로그인 토큰 자동 갱신을 실패했습니다. 카카오 로그인을 수행해 주세요.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$accessToken = $refreshResult['access_token'] ?? null;
|
||||||
|
|
||||||
|
if (!$accessToken) {
|
||||||
|
trigger_error("refreshToken 에러 " . Json::encode($refreshResult) . "," . $refreshToken . "," . substr(\kakao\KakaoKey::REST_KEY, 0, 6), E_USER_NOTICE);
|
||||||
|
return [false, '로그인 토큰 자동 갱신을 실패했습니다. 카카오 로그인을 수행해 주세요.'];
|
||||||
|
}
|
||||||
|
$accessTokenValidUntil = TimeUtil::nowAddSeconds($refreshResult['expires_in']);
|
||||||
|
|
||||||
|
$oauthInfo['accessToken'] = $accessToken;
|
||||||
|
$oauthInfo['accessTokenValidUntil'] = $accessTokenValidUntil;
|
||||||
|
|
||||||
|
$refreshToken = $refreshResult['refresh_token'] ?? null;
|
||||||
|
if ($refreshToken) {
|
||||||
|
$refreshTokenValidUntil = TimeUtil::nowAddSeconds($refreshResult['refresh_token_expires_in']);
|
||||||
|
|
||||||
|
$oauthInfo['refreshToken'] = $refreshToken;
|
||||||
|
$oauthInfo['refresh_token_expires_in'] = $refreshTokenValidUntil;
|
||||||
|
}
|
||||||
|
|
||||||
|
RootDB::db()->update('member', [
|
||||||
|
'oauth_info' => Json::encode($oauthInfo)
|
||||||
|
], 'no=%i', $userInfo['no']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($tokenValidUntil && $now <= $tokenValidUntil) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
//인증 시스템 가동
|
||||||
|
$session = Session::getInstance();
|
||||||
|
$session->access_token = $accessToken;
|
||||||
|
$session->expires = $accessTokenValidUntil;
|
||||||
|
$session->refresh_token = $refreshToken;
|
||||||
|
$session->refresh_token_expires = $refreshTokenValidUntil;
|
||||||
|
|
||||||
|
if (!createOTPbyUserNO($userInfo['no'])) {
|
||||||
|
return [false, '인증 코드를 보내는데 실패했습니다.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [true, '인증 코드를 입력해주세요'];
|
||||||
|
}
|
||||||
|
}
|
||||||
+16
-10
@@ -10,8 +10,9 @@ namespace sammo;
|
|||||||
* @property string $ip IP
|
* @property string $ip IP
|
||||||
* @property bool $reqOTP 인증 코드 필요
|
* @property bool $reqOTP 인증 코드 필요
|
||||||
* @property array $acl 권한
|
* @property array $acl 권한
|
||||||
|
* @property int|null $tokenID 토큰ID
|
||||||
* @property string $tokenValidUntil 로그인 토큰 길이
|
* @property string $tokenValidUntil 로그인 토큰 길이
|
||||||
*
|
*
|
||||||
* @property int $generalID 장수 번호 (게임 로그인 필요)
|
* @property int $generalID 장수 번호 (게임 로그인 필요)
|
||||||
* @property string $generalName 장수 이름 (게임 로그인 필요)
|
* @property string $generalName 장수 이름 (게임 로그인 필요)
|
||||||
*/
|
*/
|
||||||
@@ -35,7 +36,7 @@ class Session
|
|||||||
const GAME_KEY_GENERAL_ID = '_g_no';
|
const GAME_KEY_GENERAL_ID = '_g_no';
|
||||||
const GAME_KEY_GENERAL_NAME = '_g_name';
|
const GAME_KEY_GENERAL_NAME = '_g_name';
|
||||||
const GAME_KEY_EXPECTED_DEADTIME = '_g_deadtime';
|
const GAME_KEY_EXPECTED_DEADTIME = '_g_deadtime';
|
||||||
|
|
||||||
|
|
||||||
private $writeClosed = false;
|
private $writeClosed = false;
|
||||||
private $sessionID = null;
|
private $sessionID = null;
|
||||||
@@ -170,7 +171,7 @@ class Session
|
|||||||
return Util::array_get($_SESSION[$name]);
|
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('userID', $userID);
|
||||||
$this->set('userName', $userName);
|
$this->set('userName', $userName);
|
||||||
@@ -180,6 +181,7 @@ class Session
|
|||||||
$this->set('acl', $acl);
|
$this->set('acl', $acl);
|
||||||
$this->set('reqOTP', $reqOTP);
|
$this->set('reqOTP', $reqOTP);
|
||||||
$this->set('tokenValidUntil', $tokenValidUntil);
|
$this->set('tokenValidUntil', $tokenValidUntil);
|
||||||
|
$this->set('tokenID', $tokenID);
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,7 +199,11 @@ class Session
|
|||||||
if (class_exists('\\sammo\\UniqueConst')) {
|
if (class_exists('\\sammo\\UniqueConst')) {
|
||||||
$this->logoutGame();
|
$this->logoutGame();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if($this->tokenID??null){
|
||||||
|
RootDB::db()->delete('login_token', 'id = %i', $this->tokenID);
|
||||||
|
}
|
||||||
|
|
||||||
$this->set('userID', null);
|
$this->set('userID', null);
|
||||||
$this->set('userName', null);
|
$this->set('userName', null);
|
||||||
$this->set('userGrade', null);
|
$this->set('userGrade', null);
|
||||||
@@ -261,7 +267,7 @@ class Session
|
|||||||
}
|
}
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
$turnterm = $gameStor->turnterm;
|
$turnterm = $gameStor->turnterm;
|
||||||
$isUnited = $gameStor->isunited != 0;
|
$isUnited = $gameStor->isunited != 0;
|
||||||
|
|
||||||
@@ -319,7 +325,7 @@ class Session
|
|||||||
} else {
|
} else {
|
||||||
$obj = self::getInstance();
|
$obj = self::getInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
return $obj->userGrade;
|
return $obj->userGrade;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -335,12 +341,12 @@ class Session
|
|||||||
} else {
|
} else {
|
||||||
$obj = self::getInstance();
|
$obj = self::getInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
return $obj->userID;
|
return $obj->userID;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public static function getGeneralID(bool $requireLogin = false, string $exitPath = '..')
|
public static function getGeneralID(bool $requireLogin = false, string $exitPath = '..')
|
||||||
{
|
{
|
||||||
@@ -349,7 +355,7 @@ class Session
|
|||||||
} else {
|
} else {
|
||||||
$obj = self::getInstance();
|
$obj = self::getInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
return $obj->generalID??0;
|
return $obj->generalID??0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -367,7 +373,7 @@ class Session
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->userID) {
|
if ($this->userID) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user