getLoggedInUser(); if ($user) { header('Location: /'); exit(); } $doLogin = $_SERVER['REQUEST_METHOD'] === 'POST'; $error = ''; $nyuU = ''; // 二段階認証のコードの入力中 $ticket = ''; if ($doLogin) { if (!is_string($_POST['csrf_token'] ?? null) || !\verify_csrf_token($_POST['csrf_token'])) { header('Location: /'); exit(); } unset($_POST['csrf_token']); $a = []; $isBot = false; if (count($_POST) === 4) { $i = 0; foreach ($_POST as $p) { $a[(int)$i] = $p; if ($i >= 2 && $p != '') $isBot = true; $i++; } } if ($isBot) { header('Location: /'); exit(); } $loginError = 'エラー:ユーザー名又はパスワードが一致していません。'; $username = is_string($a[0] ?? null) ? $a[0] : ''; $password = is_string($a[1] ?? null) ? $a[1] : ''; if (is_string($_POST['ticket'] ?? null)) { // 2段階目:認証アプリのコード $nyuU = is_string($_POST['username'] ?? null) ? $_POST['username'] : ''; if ($auth->isLoginThrottled($nyuU)) { $error = 'エラー:ログインの試行回数が多過ぎます。暫くしてから再度お試し下さい。'; $ticket = $_POST['ticket']; } else { $auth = new Auth($nyuU); $result = $auth->verifyTwoFactor($nyuU, $_POST['ticket'], is_string($_POST['code'] ?? null) ? $_POST['code'] : ''); if ($result->isSuccess) { $auth->clearLoginFailures(); header('Location: /'); exit(); } $auth->recordLoginFailure($nyuU); $error = $result->message; $ticket = $result->data['ticket'] ?? ''; } } else if ($username === '' || $password === '') { $error = $loginError; } else if ($auth->isLoginThrottled($username)) { $nyuU = $username; $error = 'エラー:ログインの試行回数が多過ぎます。暫くしてから再度お試し下さい。'; } else { $auth = new Auth($username); $res = $auth->isUserExist($username); $result = $res->isSuccess ? $auth->setToken($username, $password) : \Result::Error($loginError); if (!empty($result->data['twoFactor'])) { // パスワードは合っている。コードの画面へ $nyuU = $username; $ticket = $result->data['ticket']; } else if (!$result->isSuccess) { $auth->recordLoginFailure($username); $nyuU = $username; $error = $res->isSuccess ? $result->message : $loginError; } else { $auth->clearLoginFailures(); header('Location: /'); exit(); } } } $tmpl = new Template('/'); $pagetit = 'サインイン'; $description = 'サイトにサインイン'; $tmpl->assign('pagetit', $pagetit); $tmpl->assign('curPage', 'auth'); $tmpl->assign('custCss', false); $tmpl->assign('menu', $this->getMenu()); $tmpl->assign('description', $description); $tmpl->assign('error', $error); $tmpl->assign('nyuU', $nyuU); $tmpl->assign('ticket', $ticket); $tmpl->render($ticket !== '' ? 'twofactor' : 'login'); } catch (\Exception $e) { throw new \Exception($e->getMessage()); } } public function logout(array $params): void { if (!AUTH_ENABLED) return; try { $auth = new Auth; $user = $auth->getLoggedInUser(); if (!$user) { header('Location: /'); exit(); } $auth->logout(); header('Location: /'); exit(); } catch (\Exception $e) { throw new \Exception($e->getMessage()); } } public function register(array $params): void { if (!AUTH_REGISTER_ENABLED) return; try { $auth = new Auth; $user = $auth->getLoggedInUser(); if ($user) { header('Location: /'); exit(); } $doRegister = $_SERVER['REQUEST_METHOD'] === 'POST'; $error = ''; $nyuU = ''; $nyuE = ''; if ($doRegister) { if (!is_string($_POST['csrf_token'] ?? null) || !\verify_csrf_token($_POST['csrf_token'])) { header('Location: /'); exit(); } unset($_POST['csrf_token']); $a = []; $isBot = false; if (count($_POST) === 8) { $i = 0; foreach ($_POST as $p) { $a[(int)$i] = $p; if ($i >= 4 && $p != '') $isBot = true; $i++; } } if ($isBot) { header('Location: /'); exit(); } $auth = new Auth; $res = $auth->mkUser($a[0] ?? null, $a[1] ?? null, $a[2] ?? null, $a[3] ?? null); if (!$res->isSuccess) { $error = $res->message; $nyuU = $a[0] ?? ''; $nyuE = $a[3] ?? ''; } else if ($res->data['verify'] ?? false) { // 確認メールのリンクを開くまでログインしない $this->renderVerify('登録', $res->message, ''); return; } else { $auth = new Auth($a[0]); $auth->setToken($a[0], $a[1]); header('Location: /'); exit(); } } $tmpl = new Template('/'); $pagetit = '登録'; $description = 'サイトに登録'; $tmpl->assign('pagetit', $pagetit); $tmpl->assign('curPage', 'auth'); $tmpl->assign('custCss', false); $tmpl->assign('menu', $this->getMenu()); $tmpl->assign('description', $description); $tmpl->assign('error', $error); $tmpl->assign('nyuU', $nyuU); $tmpl->assign('nyuE', $nyuE); $tmpl->render('register'); } catch (\Exception $e) { throw new \Exception($e->getMessage()); } } /** * 確認メールのリンク(/verify/{token}) * * @param array $params パラメータ配列 * @return void */ public function verify(array $params): void { if (!AUTH_ENABLED) return; try { $auth = new Auth; $res = $auth->verifyEmailToken((string)($params['token'] ?? '')); if ($res->isSuccess) $this->renderVerify('メールアドレスの確認', $res->message, ''); else $this->renderVerify('メールアドレスの確認', '', $res->message); } catch (\Exception $e) { throw new \Exception($e->getMessage()); } } private function renderVerify(string $pagetit, string $message, string $error): void { $tmpl = new Template('/'); $tmpl->assign('pagetit', $pagetit); $tmpl->assign('curPage', 'auth'); $tmpl->assign('custCss', false); $tmpl->assign('menu', $this->getMenu()); $tmpl->assign('description', ''); $tmpl->assign('message', $message); $tmpl->assign('error', $error); $tmpl->render('verify'); } /** * プロフィールの編集(/profile/{name}/edit)。本人と管理者だけ * * @param array $params パラメータ配列 * @return void */ public function editProfile(array $params): void { if (!AUTH_ENABLED) return; $name = (string)($params['name'] ?? ''); $auth = new Auth; $user = $auth->getLoggedInUser(); if (!$user) { header('Location: /login'); exit(); } $edit = $auth->getEditableUser($name); if (!$edit) { header('Location: /404'); exit(); } $error = ''; $message = ''; if ($_SERVER['REQUEST_METHOD'] === 'POST') { if (!is_string($_POST['csrf_token'] ?? null) || !\verify_csrf_token($_POST['csrf_token'])) { $error = 'セッションの有効期限が切れました。もう一度保存して下さい。'; } else { $in = [ 'displayname' => (string)($_POST['displayname'] ?? ''), 'gender' => (string)($_POST['gender'] ?? '-1'), 'namecolor' => (string)($_POST['namecolor'] ?? ''), 'bio' => (string)($_POST['bio'] ?? ''), 'removeAvatar' => isset($_POST['removeAvatar']), ]; if ($edit->isSelf) { foreach (['currentPassword', 'newPassword', 'newPasswordVerify', 'email'] as $k) $in[$k] = (string)($_POST[$k] ?? ''); } // ロールの欄が送られた時だけ変える(欄が無いフォームから送られても、全部外してBANにしない様に) if ($edit->isAdmin && isset($_POST['rolesForm'])) $in['roles'] = array_map('intval', (array)($_POST['roles'] ?? [])); $res = $auth->updateProfile($name, $in, $_FILES['avatar'] ?? null); if ($res->isSuccess) { // Fediverseの設定とプロフィールの配信 if (ACTIVITYPUB_MULTIUSER) { $fresh = $res->data['user']; if (can_federate($fresh)) { Actor::saveSettings($name, ['locked' => isset($_POST['locked'])]); Social::sendProfileUpdate($name); } } $message = (string)$res->message; $edit = $auth->getEditableUser($name); } else { $error = (string)$res->message; } } } $tmpl = new Template('/'); $tmpl->assign('pagetit', 'プロフィールの編集'); $tmpl->assign('curPage', 'auth'); $tmpl->assign('custCss', false); $tmpl->assign('menu', $this->getMenu()); $tmpl->assign('description', ''); $tmpl->assign('user', $user); $tmpl->assign('e', $edit); $tmpl->assign('error', $error); $tmpl->assign('message', $message); $tmpl->assign('roles', Roles::LABELS); $tmpl->assign('fediEnabled', ACTIVITYPUB_MULTIUSER); $tmpl->assign('locked', ACTIVITYPUB_MULTIUSER && $edit->fedi ? (bool)Actor::settings($name)['locked'] : false); $tmpl->assign('twoFactor', $auth->twoFactorAvailable()); $tmpl->assign('csrf', make_csrf_token()); $tmpl->render('editprofile'); } /** * 二段階認証の設定(QRコードと秘密鍵、有効・無効) */ public function twoFactor(array $params): void { if (!AUTH_ENABLED) return; $name = (string)($params['name'] ?? ''); $auth = new Auth; $user = $auth->getLoggedInUser(); if (!$user) { header('Location: /login'); exit(); } $error = ''; $message = ''; if ($_SERVER['REQUEST_METHOD'] === 'POST') { if (!is_string($_POST['csrf_token'] ?? null) || !\verify_csrf_token($_POST['csrf_token'])) { $error = 'セッションの有効期限が切れました。もう一度お試し下さい。'; } else { $code = (string)($_POST['code'] ?? ''); $res = ($_POST['action'] ?? '') === 'disable' ? $auth->disableTwoFactor($name, (string)($_POST['password'] ?? ''), $code) : $auth->enableTwoFactor($name, $code); if ($res->isSuccess) $message = (string)$res->message; else $error = (string)$res->message; } } $status = $auth->twoFactorStatus($name); if (!$status->isSuccess) { header('Location: /404'); exit(); } $s = (object)$status->data; $tmpl = new Template('/'); $tmpl->assign('pagetit', '二段階認証'); $tmpl->assign('curPage', 'auth'); $tmpl->assign('custCss', false); $tmpl->assign('menu', $this->getMenu()); $tmpl->assign('description', ''); $tmpl->assign('user', $user); $tmpl->assign('s', $s); // 手で入力し易い様に4文字毎に区切る $tmpl->assign('secretGroups', isset($s->secret) ? trim(chunk_split($s->secret, 4, ' ')) : ''); $tmpl->assign('error', $error); $tmpl->assign('message', $message); $tmpl->assign('csrf', make_csrf_token()); $tmpl->render('twofactorsetup'); } /** * 設定中の秘密鍵のQRコード(PNG、本人だけ) */ public function twoFactorQr(array $params): void { if (!AUTH_ENABLED) return; $auth = new Auth; $status = $auth->getLoggedInUser() ? $auth->twoFactorStatus((string)($params['name'] ?? '')) : null; if (!$status || !isset($status->data['uri'])) { http_response_code(404); exit(); } header('Content-Type: image/png'); header('Cache-Control: no-store'); echo Image::encode((new Qr($status->data['uri']))->bitmap(4), 'image/png'); exit(); } public function profile(array $params): void { if (!AUTH_ENABLED) return; $name = ''; if (isset($params['name'])) $name = $params['name']; if ($name === '') { header('Location: /'); exit(); } try { $auth = new Auth; $user = $auth->getLoggedInUser(); $u = $auth->getUser($name); if (!$u) { header('Location: /404'); exit(); } $tmpl = new Template('/'); $suffix = $u->gender === '男' ? 'くん' : ($u->gender === '女' ? 'ちゃん' : 'さん'); $pagetit = $u->altName.$suffix.'のプロフィール'; $description = ''; $tmpl->assign('pagetit', $pagetit); $tmpl->assign('curPage', 'auth'); $tmpl->assign('custCss', false); $tmpl->assign('menu', $this->getMenu()); $tmpl->assign('description', $description); $tmpl->assign('user', $user); $tmpl->assign('u', $u); $tmpl->assign('name', $name); $tmpl->render('profile'); } catch (\Exception $e) { throw new \Exception($e->getMessage()); } } }