From 1b08aa0c58b203d02de65fcd77cb1bc0cee02d7e Mon Sep 17 00:00:00 2001 From: hided62 Date: Tue, 17 Feb 2026 19:12:28 +0900 Subject: [PATCH] =?UTF-8?q?wip:=20=EB=94=94=EB=B2=84=EA=B7=B8=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hwe/ts/SammoRootAPI.ts | 6 +- hwe/ts/gateway/login.ts | 124 +++++++++++++++------------ src/sammo/API/Login/LoginByToken.php | 28 ++++++ src/sammo/API/Login/ReqNonce.php | 13 +++ 4 files changed, 112 insertions(+), 59 deletions(-) diff --git a/hwe/ts/SammoRootAPI.ts b/hwe/ts/SammoRootAPI.ts index 0d3284c0..0d8fd7b6 100644 --- a/hwe/ts/SammoRootAPI.ts +++ b/hwe/ts/SammoRootAPI.ts @@ -1,6 +1,6 @@ import type { AutoLoginFailed, AutoLoginNonceResponse, AutoLoginResponse, LoginFailed, LoginResponse } from "./defs/API/Login"; import { APIPathGen } from "./util/APIPathGen"; -import { callSammoAPI, extractHttpMethod, GET, POST, type APICallT, type APITail, type InvalidResponse, type RawArgType, type ValidResponse } from "./util/callSammoAPI"; +import { callSammoAPI, extractHttpMethod, POST, type APICallT, type APITail, type InvalidResponse, type RawArgType, type ValidResponse } from "./util/callSammoAPI"; export type { ValidResponse, InvalidResponse }; const apiRealPath = { @@ -18,7 +18,7 @@ const apiRealPath = { hashedToken: string, token_id: number, }, AutoLoginResponse, AutoLoginFailed>, - ReqNonce: GET as APICallT + ReqNonce: POST as APICallT }, } as const; @@ -30,4 +30,4 @@ export const SammoRootAPI = APIPathGen(apiRealPath, (path: string[], tail: APITa } return callSammoAPI(method, path.join('/'), args, pathParam); }; -}); \ No newline at end of file +}); diff --git a/hwe/ts/gateway/login.ts b/hwe/ts/gateway/login.ts index 0f65830d..984fadad 100644 --- a/hwe/ts/gateway/login.ts +++ b/hwe/ts/gateway/login.ts @@ -40,7 +40,7 @@ function getToken(): [number, string] | undefined { console.log('no token'); return; } - const tokenItems = JSON.parse(trialToken) as [number, [number, string], string]; + const tokenItems = JSON.parse(trialToken) as [number, [number, string], number]; if (tokenItems[0] != TOKEN_VERSION) { console.log(tokenItems); resetToken(); @@ -55,61 +55,73 @@ function resetToken() { localStorage.removeItem(LOGIN_TOKEN_KEY); } -async function tryAutoLogin() { - try { - const tokenInfo = getToken(); - if (!tokenInfo) { - return; - } - - const [tokenID, token] = tokenInfo; - - const result = await SammoRootAPI.Login.ReqNonce(undefined, true); - - if (!result) { - //api 에러. - return; - } - - if (!result.result) { - resetToken(); - return; - } - - const nonce = result.loginNonce; - console.debug('try auto login with token', tokenID, token, nonce); - - const hashedToken = sha512(token + nonce); - const loginResult = await SammoRootAPI.Login.LoginByToken({ - 'hashedToken': hashedToken, - 'token_id': tokenID, - }, true); - - 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) { - if (isString(e)) { - alert(e); - } - console.error(e); - return; - } - - -} - +async function tryAutoLogin() { + try { + const tokenInfo = getToken(); + if (!tokenInfo) { + return; + } + + const [tokenID, token] = tokenInfo; + + for (let attempt = 0; attempt < 2; attempt++) { + const reqNonceStartAt = Date.now(); + const nonceResult = await SammoRootAPI.Login.ReqNonce(undefined, true); + + if (!nonceResult) { + //api 에러. + return; + } + + if (!nonceResult.result) { + resetToken(); + return; + } + + const nonce = nonceResult.loginNonce; + console.debug( + 'try auto login with token', + tokenID, + `attempt:${attempt + 1}`, + `reqNonceElapsed:${Date.now() - reqNonceStartAt}ms` + ); + + const hashedToken = sha512(token + nonce); + const loginResult = await SammoRootAPI.Login.LoginByToken({ + 'hashedToken': hashedToken, + 'token_id': tokenID, + }, true); + + if (!loginResult.result) { + if (loginResult.reason === '자동 로그인: 절차 오류' && attempt === 0) { + console.warn('auto login failed by procedure error. retrying once.'); + await delay(150); + continue; + } + + if (!loginResult.silent) { + alert(loginResult.reason); + } + console.error(loginResult.reason); + return; + } + + if (loginResult.nextToken) { + regNextToken(loginResult.nextToken); + } + window.location.href = "./"; + return; + } + } + catch (e) { + if (isString(e)) { + alert(e); + } + console.error(e); + return; + } +} + function getOAuthToken(mode: string, scope_list?: string[] | string) { if (mode === undefined) { mode = 'login'; diff --git a/src/sammo/API/Login/LoginByToken.php b/src/sammo/API/Login/LoginByToken.php index 3c2130fc..d5c31fa1 100644 --- a/src/sammo/API/Login/LoginByToken.php +++ b/src/sammo/API/Login/LoginByToken.php @@ -13,6 +13,8 @@ use sammo\TimeUtil; use sammo\Util; use sammo\Validator; +use function sammo\logError; + class LoginByToken extends LoginByID { static array $sensitiveArgs = ['hashedToken']; @@ -42,6 +44,32 @@ class LoginByToken extends LoginByID $loginNonce = $session->loginNonce ?? null; $loginNonceExpired = $session->loginNonceExpired ?? null; if (!is_string($loginNonce) || !is_string($loginNonceExpired)) { + logError( + 'AutoLoginProcedureError', + Json::encode([ + 'phase' => 'missing_nonce', + 'sid' => session_id(), + 'cookie_sid' => $_COOKIE[session_name()] ?? null, + 'checked_at' => TimeUtil::now(true), + 'host' => gethostname() ?: php_uname('n'), + 'php' => PHP_VERSION, + 'session_status' => session_status(), + 'session_save_handler' => ini_get('session.save_handler') ?: null, + 'session_save_path' => ini_get('session.save_path') ?: null, + 'session_use_strict_mode' => ini_get('session.use_strict_mode') ?: null, + 'session_cookie_samesite' => ini_get('session.cookie_samesite') ?: null, + 'session_cookie_secure' => ini_get('session.cookie_secure') ?: null, + 'request_method' => $_SERVER['REQUEST_METHOD'] ?? null, + 'request_uri' => $_SERVER['REQUEST_URI'] ?? null, + 'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? null, + 'token_id' => $this->args['token_id'] ?? null, + 'nonce_type' => gettype($loginNonce), + 'nonce_expired_type' => gettype($loginNonceExpired), + 'reqnonce_context' => $session->loginNonceDebug ?? null, + ]), + __FILE__, + [] + ); return '자동 로그인: 절차 오류'; } diff --git a/src/sammo/API/Login/ReqNonce.php b/src/sammo/API/Login/ReqNonce.php index ae3da82b..eb4395a0 100644 --- a/src/sammo/API/Login/ReqNonce.php +++ b/src/sammo/API/Login/ReqNonce.php @@ -32,6 +32,19 @@ class ReqNonce extends BaseAPI $loginNonceExpired = TimeUtil::nowAddSeconds(2); $session->loginNonce = $loginNonce; $session->loginNonceExpired = $loginNonceExpired; + $session->loginNonceDebug = Json::encode([ + 'phase' => 'issue_nonce', + 'sid' => session_id(), + 'cookie_sid' => $_COOKIE[session_name()] ?? null, + 'issued_at' => TimeUtil::now(true), + 'host' => gethostname() ?: php_uname('n'), + 'php' => PHP_VERSION, + 'session_save_handler' => ini_get('session.save_handler') ?: null, + 'session_save_path' => ini_get('session.save_path') ?: null, + 'session_use_strict_mode' => ini_get('session.use_strict_mode') ?: null, + 'session_cookie_samesite' => ini_get('session.cookie_samesite') ?: null, + 'session_cookie_secure' => ini_get('session.cookie_secure') ?: null, + ]); return [ 'result' => true, 'loginNonce' => $loginNonce,