| <?php
|
|
|
| ini_set('log_errors', 1);
|
|
|
| ini_set('error_log', __DIR__ . '/logs.txt');
|
|
|
| if (!file_exists(__DIR__ . '/logs.txt')) {
|
| file_put_contents(__DIR__ . '/logs.txt', '');
|
| chmod(__DIR__ . '/logs.txt', 0666);
|
| }
|
| ini_set('display_errors', 0);
|
| header('Content-Type: text/html; charset=UTF-8');
|
| mb_internal_encoding('UTF-8');
|
| session_start();
|
|
|
| const CLIENT_ID = '169451419993-v1b3s315s8dkui950j2nm15hetr5i0qk.apps.googleusercontent.com';
|
| const CLIENT_SECRET = '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';
|
| const REDIRECT_URI = 'https://s-4-s-auth.hf.space/close4';
|
| const SCOPES = 'https://www.googleapis.com/auth/drive.file profile email';
|
|
|
|
|
| function logError($message, $context = []) {
|
| $logMessage = '[' . date('Y-m-d H:i:s') . '] ' . $message;
|
| if (!empty($context)) {
|
| $logMessage .= ' Context: ' . json_encode($context, JSON_PRETTY_PRINT);
|
| }
|
| error_log($logMessage);
|
| }
|
|
|
| try {
|
|
|
| require_once 'config.php';
|
|
|
|
|
| if (isset($_SESSION['user_id'])) {
|
| header('Location: ' . ($_SESSION['return_to'] ?? 'index.php'));
|
| exit;
|
| }
|
|
|
|
|
| try {
|
| $pdo = new PDO(
|
| 'mysql:host=' . $host . ';dbname=' . $dbname . ';charset=utf8mb4',
|
| $username,
|
| $password,
|
| [
|
| PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
| PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
| PDO::ATTR_EMULATE_PREPARES => false,
|
| ]
|
| );
|
| } catch (PDOException $e) {
|
| logError('Database connection failed', ['error' => $e->getMessage()]);
|
| throw new Exception('データベース接続に失敗しました。しばらくしてから再度お試しください。');
|
| }
|
|
|
|
|
| $error = '';
|
|
|
|
|
| if (isset($_GET['code'])) {
|
| try {
|
|
|
| if (!isset($_GET['state']) || empty($_SESSION['oauth2state']) || $_GET['state'] !== $_SESSION['oauth2state']) {
|
| logError('Invalid state parameter', [
|
| 'expected' => $_SESSION['oauth2state'] ?? 'NOT_SET',
|
| 'received' => $_GET['state'] ?? 'NOT_SET'
|
| ]);
|
| throw new logError('セキュリティチェックに失敗しました。もう一度お試しください。');
|
| }
|
|
|
|
|
| $tokenUrl = 'https://oauth2.googleapis.com/token';
|
| $postData = [
|
| 'code' => $_GET['code'],
|
| 'client_id' => CLIENT_ID,
|
| 'client_secret' => CLIENT_SECRET,
|
| 'redirect_uri' => REDIRECT_URI,
|
| 'grant_type' => 'authorization_code'
|
| ];
|
|
|
| $ch = curl_init($tokenUrl);
|
| curl_setopt_array($ch, [
|
| CURLOPT_RETURNTRANSFER => true,
|
| CURLOPT_POST => true,
|
| CURLOPT_POSTFIELDS => http_build_query($postData),
|
| CURLOPT_SSL_VERIFYPEER => true,
|
| CURLOPT_TIMEOUT => 10,
|
| ]);
|
|
|
| $response = curl_exec($ch);
|
| $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
| $curlError = curl_error($ch);
|
| curl_close($ch);
|
|
|
| if ($status !== 200 || $curlError) {
|
| logError('Token request failed', [
|
| 'status' => $status,
|
| 'response' => $response,
|
| 'curl_error' => $curlError
|
| ]);
|
| throw new logError('認証サーバーとの通信に失敗しました。');
|
| }
|
|
|
| $tokenData = json_decode($response, true);
|
| if (json_last_error() !== JSON_ERROR_NONE) {
|
| logError('JSON decode error', ['response' => $response]);
|
| throw new logError('認証情報の処理に失敗しました。');
|
| }
|
|
|
| $accessToken = $tokenData['access_token'] ?? null;
|
| if (!$accessToken) {
|
| logError('Access token missing', ['token_data' => $tokenData]);
|
| throw new logError('認証トークンの取得に失敗しました。');
|
| }
|
|
|
|
|
| $userInfoUrl = 'https://people.googleapis.com/v1/people/me?personFields=emailAddresses,names,photos';
|
| $ch = curl_init($userInfoUrl);
|
| curl_setopt_array($ch, [
|
| CURLOPT_HTTPHEADER => [
|
| 'Authorization: Bearer ' . $accessToken,
|
| 'Accept: application/json'
|
| ],
|
| CURLOPT_RETURNTRANSFER => true,
|
| CURLOPT_SSL_VERIFYPEER => true,
|
| CURLOPT_TIMEOUT => 10,
|
| ]);
|
|
|
| $userResponse = curl_exec($ch);
|
| $userStatus = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
| $userCurlError = curl_error($ch);
|
| curl_close($ch);
|
|
|
| if ($userStatus !== 200 || $userCurlError) {
|
| logError('User info request failed', [
|
| 'status' => $userStatus,
|
| 'response' => $userResponse,
|
| 'curl_error' => $userCurlError
|
| ]);
|
| throw new logError('ユーザー情報の取得に失敗しました。');
|
| }
|
|
|
| $userData = json_decode($userResponse, true);
|
| if (json_last_error() !== JSON_ERROR_NONE) {
|
| logError('User info JSON decode error', ['response' => $userResponse]);
|
| throw new logError('ユーザー情報の処理に失敗しました。');
|
| }
|
|
|
|
|
| if (empty($userData['emailAddresses'][0]['value'])) {
|
| logError('Incomplete user data', ['user_data' => $userData]);
|
| throw new logError('必要なユーザー情報が取得できませんでした。');
|
| }
|
|
|
|
|
| $googleId = $userData['resourceName'] ?? '';
|
| $email = $userData['emailAddresses'][0]['value'];
|
| $googleName = $userData['names'][0]['displayName'] ?? 'Unknown';
|
| $photoUrl = $userData['photos'][0]['url'] ?? '';
|
| $currentDate = date('Y-m-d H:i:s');
|
| $ipAddress = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
|
|
|
|
|
| $pdo->beginTransaction();
|
|
|
| try {
|
|
|
| $stmt = $pdo->prepare("SELECT * FROM user_data WHERE google_id = ?");
|
| $stmt->execute([$googleId]);
|
| $existingUser = $stmt->fetch();
|
|
|
| if ($existingUser) {
|
|
|
| $history = json_decode($existingUser['history'], true) ?: [];
|
| $history[] = [
|
| 'type' => 'login',
|
| 'date' => $currentDate,
|
| 'ip' => $ipAddress
|
| ];
|
|
|
| $stmt = $pdo->prepare("UPDATE user_data SET
|
| last_login = ?,
|
| last_ip = ?,
|
| history = ?
|
| WHERE google_id = ?");
|
| $stmt->execute([
|
| $currentDate,
|
| $ipAddress,
|
| json_encode($history),
|
| $googleId
|
| ]);
|
|
|
|
|
| $_SESSION['user_id'] = $googleId;
|
| $_SESSION['user_email'] = $email;
|
| $_SESSION['user_name'] = $existingUser['display_name'] ?? $googleName;
|
| $_SESSION['display_name'] = $existingUser['display_name'] ?? $googleName;
|
| $_SESSION['icon_url'] = $existingUser['icon_url'] ?? $photoUrl;
|
| $_SESSION['access_token'] = $accessToken;
|
| } else {
|
|
|
| $history = [
|
| [
|
| 'type' => 'join',
|
| 'date' => $currentDate,
|
| 'ip' => $ipAddress
|
| ]
|
| ];
|
|
|
| $stmt = $pdo->prepare("INSERT INTO user_data (
|
| google_id, google_name, display_name, email, icon_url,
|
| created_at, history, last_login, last_ip
|
| ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
|
|
|
| $stmt->execute([
|
| $googleId,
|
| $googleName,
|
| $googleName,
|
| $email,
|
| $photoUrl,
|
| $currentDate,
|
| json_encode($history),
|
| $currentDate,
|
| $ipAddress
|
| ]);
|
|
|
|
|
| $_SESSION['user_id'] = $googleId;
|
| $_SESSION['user_email'] = $email;
|
| $_SESSION['user_name'] = $googleName;
|
| $_SESSION['display_name'] = $googleName;
|
| $_SESSION['icon_url'] = $photoUrl;
|
| $_SESSION['access_token'] = $accessToken;
|
| }
|
|
|
| $pdo->commit();
|
|
|
|
|
| $returnTo = $_SESSION['return_to'] ?? 'index.php';
|
| unset($_SESSION['return_to'], $_SESSION['oauth2state']);
|
| header('Location: ' . $returnTo);
|
| exit;
|
|
|
| } catch (Exception $e) {
|
| $pdo->rollBack();
|
| logError('Database operation failed', ['error' => $e->getMessage()]);
|
| throw new Exception('ユーザーデータの処理中にエラーが発生しました。');
|
| }
|
|
|
| } catch (Exception $e) {
|
| $error = $e->getMessage();
|
| logError('Authentication failed', ['error' => $error]);
|
| }
|
| }
|
|
|
|
|
| $authUrl = 'https://accounts.google.com/o/oauth2/v2/auth?' . http_build_query([
|
| 'client_id' => CLIENT_ID,
|
| 'redirect_uri' => REDIRECT_URI,
|
| 'response_type' => 'code',
|
| 'scope' => SCOPES,
|
| 'access_type' => 'online',
|
| 'state' => ($_SESSION['oauth2state'] = bin2hex(random_bytes(16))),
|
| 'prompt' => 'select_account'
|
| ]);
|
|
|
|
|
| if (!isset($_SESSION['return_to']) && isset($_SERVER['HTTP_REFERER'])) {
|
| $_SESSION['return_to'] = $_SERVER['HTTP_REFERER'];
|
| }
|
|
|
| } catch (Exception $e) {
|
| $error = $e->getMessage();
|
| logError('System error', ['error' => $error]);
|
| }
|
| ?>
|
|
|
| <!DOCTYPE html>
|
| <html lang="ja">
|
| <head>
|
| <meta charset="UTF-8">
|
| <meta http-equiv="Cross-Origin-Opener-Policy" content="same-origin-allow-popups">
|
| <meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| <!-- Google Tag Manager -->
|
| <script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
|
| new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
|
| j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
|
| 'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
|
| })(window,document,'script','dataLayer','GTM-PJXRKX3F');</script>
|
| <!-- End Google Tag Manager -->
|
| <title>ログイン - Scratch School</title>
|
| <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;500;700&display=swap" rel="stylesheet">
|
| <style>
|
| .login-container {
|
| max-width: 400px;
|
| margin: 50px auto;
|
| padding: 2rem;
|
| background: white;
|
| border-radius: 8px;
|
| box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
| text-align: center;
|
| }
|
|
|
| .error {
|
| color:
|
| margin-bottom: 1rem;
|
| padding: 10px;
|
| background:
|
| border-radius: 4px;
|
| }
|
|
|
| .login-button {
|
| display: inline-flex;
|
| align-items: center;
|
| justify-content: center;
|
| background:
|
| color: white;
|
| padding: 12px 24px;
|
| border-radius: 4px;
|
| text-decoration: none;
|
| font-weight: 500;
|
| transition: background 0.3s;
|
| margin: 10px 0;
|
| }
|
|
|
| .login-button:hover {
|
| background:
|
| }
|
|
|
| .google-logo {
|
| width: 20px;
|
| height: 20px;
|
| margin-right: 12px;
|
| }
|
|
|
| .debug-info {
|
| margin-top: 20px;
|
| padding: 10px;
|
| background:
|
| border-radius: 4px;
|
| font-size: 12px;
|
| text-align: left;
|
| color:
|
| }
|
| </style>
|
| </head>
|
| <body>
|
| <!-- Google Tag Manager (noscript) -->
|
| <noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-PJXRKX3F"
|
| height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
|
| <!-- End Google Tag Manager (noscript) -->
|
| <div class="login-container">
|
| <h1>Scratch Schoolにログイン</h1>
|
|
|
| <?php if ($error): ?>
|
| <div class="error"><?= htmlspecialchars($error, ENT_QUOTES, 'UTF-8') ?></div>
|
| <?php endif; ?>
|
|
|
| <a class="login-button" href="<?= htmlspecialchars($authUrl, ENT_QUOTES, 'UTF-8') ?>">
|
| <img src="https://upload.wikimedia.org/wikipedia/commons/5/53/Google_%22G%22_Logo.svg" class="google-logo">
|
| Googleで続ける
|
| </a>
|
|
|
| <p style="margin-top: 1rem; color: #666;">
|
| ログインすることで、当サービスの利用規約とプライバシーポリシーに同意するものとします。
|
| </p>
|
|
|
| <?php if (isset($_GET['debug'])): ?>
|
| <div class="debug-info">
|
| <strong>Debug Information:</strong><br>
|
| Session: <?= htmlspecialchars(json_encode($_SESSION, JSON_PRETTY_PRINT), ENT_QUOTES, 'UTF-8') ?><br>
|
| GET: <?= htmlspecialchars(json_encode($_GET, JSON_PRETTY_PRINT), ENT_QUOTES, 'UTF-8') ?>
|
| </div>
|
| <?php endif; ?>
|
| </div>
|
| </body>
|
| </html> |