자동 로그인 (#196)

자동 로그인 구현

- login_token 테이블
- reqNonce -> loginByToken
- sha512(token + nonce)

Reviewed-on: https://storage.hided.net/gitea/devsam/core/pulls/196
Co-authored-by: hide_d <hided62@gmail.com>
Co-committed-by: hide_d <hided62@gmail.com>
This commit is contained in:
2021-11-08 04:23:07 +09:00
parent 1472cbf257
commit 673b7054aa
13 changed files with 880 additions and 312 deletions
-25
View File
@@ -357,31 +357,6 @@ function buildNationCommandClass(?string $type, General $generalObj, array $env,
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){
static $basePath = __NAMESPACE__.'\\WarUnitTrigger\\';
$classPath = ($basePath.$type);
-28
View File
@@ -1,28 +0,0 @@
<?php
namespace sammo;
abstract class BaseAPI
{
const NO_SESSION = 0;
const REQ_LOGIN = 1;
const REQ_GAME_LOGIN = 2;
const REQ_READ_ONLY = 4;
protected array $args;
protected string $rootPath;
public function __construct(string $rootPath, array $args)
{
$this->rootPath = $rootPath;
$this->args = $args;
}
abstract public function getRequiredSessionMode(): int;
abstract function validateArgs(): ?string;
/** @return null|string|array */
abstract function launch(Session $session, ?\DateTimeInterface $modifiedSince, ?string $reqEtag);
public function tryCache():?string{
return null;
}
}
+127 -10
View File
@@ -1,7 +1,7 @@
import $ from 'jquery';
import '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 { JQValidateForm, NamedRules } from '../util/jqValidateForm';
import axios from 'axios';
@@ -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<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) {
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,
@@ -194,13 +306,16 @@ $(function ($) {
try {
const response = await axios({
url: 'j_login.php',
url: 'api.php',
responseType: 'json',
method: 'post',
data: convertFormData({
username: values.username,
password: hash_pw,
})
data: {
path: ["Login", "LoginByID"],
args: {
username: values.username,
password: hash_pw,
}
}
});
result = response.data;
}
@@ -212,6 +327,9 @@ $(function ($) {
if (result.result) {
if (result.nextToken) {
regNextToken(result.nextToken);
}
window.location.href = "./";
return;
}
@@ -268,12 +386,11 @@ $(function ($) {
getOAuthToken('change_pw', 'talk_message');
});
$('#btn_kakao_login').on('click', function(e){
$('#btn_kakao_login').on('click', function (e) {
e.preventDefault();
getOAuthToken('login', ['account_email','talk_message']);
getOAuthToken('login', ['account_email', 'talk_message']);
})
//TODO: 모바일에서 크기 비례 지도 다시 띄우기?
/*
if (document.body.clientWidth < 700) {