true, 'message' => 'Settings updated successfully']); } else { echo json_encode(['success' => false, 'message' => 'Invalid request method']); } exit; } // Check for saved credentials in cookies $savedTenantId = $_COOKIE['tenantId'] ?? ''; $savedClientId = $_COOKIE['clientId'] ?? ''; // Ask for Microsoft credentials if not stored if (empty($savedTenantId) || empty($savedClientId) || !isset($_SESSION['clientSecret'])) { if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['tenantId'])) { $tenantId = trim($_POST['tenantId']); $clientId = trim($_POST['clientId']); $clientSecret = trim($_POST['clientSecret']); // Save tenant ID and client ID in cookies (30 days) setcookie('tenantId', $tenantId, time() + (30 * 24 * 60 * 60), '/', '', true, true); setcookie('clientId', $clientId, time() + (30 * 24 * 60 * 60), '/', '', true, true); // Keep only client secret in session $_SESSION['clientSecret'] = $clientSecret; $_SESSION['showLogs'] = isset($_POST['showLogs']) ? true : false; header('Location: ' . $_SERVER['PHP_SELF']); exit; } echo ' Enter Credentials

Enter Microsoft Graph Credentials


API permissions required:
  • Policy.ReadWrite.AuthenticationMethod
  • UserAuthenticationMethod.ReadWrite.All
  • User.Read.All
  • Directory.Read.All
Grant admin consent for all permissions
© Token2
'; exit; } // Initialize showLogs setting if not set if (!isset($_SESSION['showLogs'])) { $_SESSION['showLogs'] = true; } // Credentials - get from cookies and session $tenantId = $_COOKIE['tenantId'] ?? ''; $clientId = $_COOKIE['clientId'] ?? ''; $clientSecret = $_SESSION['clientSecret'] ?? ''; // Get Access Token function getAccessToken($tenantId, $clientId, $clientSecret) { $url = "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token"; $data = ['grant_type' => 'client_credentials', 'client_id' => $clientId, 'client_secret' => $clientSecret, 'scope' => 'https://graph.microsoft.com/.default']; $ch = curl_init(); if (LOCAL_APP == 1 ) { curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); } // Apply proxy settings applyProxySettings($ch); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data)); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/x-www-form-urlencoded']); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $resp = json_decode($response, true); // Check for authentication errors if ($httpCode !== 200 || !isset($resp['access_token'])) { $error = $resp['error'] ?? 'unknown_error'; $errorDescription = $resp['error_description'] ?? 'Unknown authentication error'; // Log the specific error for debugging error_log("Authentication failed: HTTP $httpCode - Error: $error - Description: $errorDescription"); // Check for common credential errors if (strpos($errorDescription, 'AADSTS70002') !== false || strpos($errorDescription, 'invalid_client') !== false) { throw new Exception("Invalid Client ID or Client Secret. Please check your credentials in Settings."); } elseif (strpos($errorDescription, 'AADSTS90002') !== false || strpos($errorDescription, 'invalid_tenant') !== false) { throw new Exception("Invalid Tenant ID. Please check your tenant ID in Settings."); } elseif (strpos($error, 'invalid_client') !== false) { throw new Exception("Invalid credentials. Please verify your Tenant ID, Client ID, and Client Secret in Settings."); } else { throw new Exception("Authentication failed: " . $errorDescription); } } return $resp['access_token']; } // Graph API functions function fetchTokens($accessToken) { $url = "https://graph.microsoft.com/beta/directory/authenticationMethodDevices/hardwareOathDevices"; $ch = curl_init(); if (LOCAL_APP == 1 ) { curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); } // Apply proxy settings applyProxySettings($ch); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer $accessToken", "Content-Type: application/json"]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $resp = json_decode($response, true); // Check for permission errors if ($httpCode === 403 || $httpCode === 401) { $error = $resp['error'] ?? []; $errorCode = $error['code'] ?? ''; $errorMessage = $error['message'] ?? ''; // Detect specific permission issues if (strpos($errorCode, 'Authorization_RequestDenied') !== false || strpos($errorMessage, 'Insufficient privileges') !== false || strpos($errorMessage, 'Access denied') !== false) { throw new Exception("Missing API Permissions: Your application doesn't have the required Microsoft Graph permissions. Please ensure these permissions are granted and admin consent is provided:\n\n" . "Required permissions for Hardware OATH Tokens:\n" . "• Policy.ReadWrite.AuthenticationMethod (Application)\n" . "• AuthenticationMethodDevice.ReadWrite.All (Application)\n" . "• Directory.Read.All (Application)\n" . "• User.Read.All (Application)\n\n" . "Steps to fix:\n" . "1. Go to Azure Portal → App Registrations → Your App\n" . "2. Select 'API permissions'\n" . "3. Add the missing permissions listed above\n" . "4. Click 'Grant admin consent for [tenant]'\n\n" . "Technical details: " . $errorMessage); } // Generic permission error throw new Exception("Permission Error: " . $errorMessage . " (HTTP $httpCode)"); } return ['value' => $resp['value'] ?? [], 'log' => $resp]; } function importTokens($accessToken, $data) { $url = "https://graph.microsoft.com/beta/directory/authenticationMethodDevices/hardwareOathDevices"; $ch = curl_init(); if (LOCAL_APP == 1 ) { curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); } // Apply proxy settings applyProxySettings($ch); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PATCH"); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer $accessToken", "Content-Type: application/json"]); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $resp = json_decode(curl_exec($ch), true); curl_close($ch); return $resp; } function importCSVTokens($accessToken, $csvData, $importMode = 'import_assign_activate') { $results = []; $lines = explode("\n", $csvData); // Remove BOM if present $lines[0] = preg_replace('/^\xEF\xBB\xBF/', '', $lines[0]); $headers = str_getcsv(array_shift($lines)); // Trim whitespace from headers $headers = array_map('trim', $headers); foreach ($lines as $lineNum => $line) { if (empty(trim($line))) continue; $data = str_getcsv($line); // Skip if not enough columns if (count($data) < count($headers)) { $results["line_" . ($lineNum + 2)] = [ 'import_success' => false, 'import_log' => ['error' => 'Insufficient columns in CSV line'], 'token_id' => null, ]; continue; } $data = array_combine($headers, $data); $upn = trim($data['upn'] ?? ''); $serialNumber = trim($data['serial number'] ?? ''); $secretKey = trim($data['secret key'] ?? ''); $timeInterval = (int)($data['timeinterval'] ?? 30); $manufacturer = trim($data['manufacturer'] ?? ''); $model = trim($data['model'] ?? ''); // Validate required fields if (empty($serialNumber) || empty($secretKey)) { $results[$serialNumber ?: "line_" . ($lineNum + 2)] = [ 'import_success' => false, 'import_log' => ['error' => 'Missing required fields: serial number or secret key'], 'token_id' => null, ]; continue; } // For modes that require assignment, validate UPN if (($importMode === 'import_assign' || $importMode === 'import_assign_activate') && empty($upn)) { $results[$serialNumber] = [ 'import_success' => false, 'import_log' => ['error' => 'UPN is required for assignment mode'], 'token_id' => null, ]; continue; } // Process secret key - remove spaces and convert to uppercase $secretKey = strtoupper(str_replace(' ', '', $secretKey)); // Validate base32 format if (!preg_match('/^[A-Z2-7]+$/', $secretKey)) { $results[$serialNumber] = [ 'import_success' => false, 'import_log' => ['error' => 'Invalid secret key format. Must be Base32 (A-Z, 2-7)'], 'token_id' => null, ]; continue; } // Remove any existing padding and ensure proper length $secretKey = rtrim($secretKey, '='); // Validate minimum length (TOTP secrets should be at least 16 characters) if (strlen($secretKey) < 16) { $results[$serialNumber] = [ 'import_success' => false, 'import_log' => ['error' => 'Secret key too short. Must be at least 16 characters'], 'token_id' => null, ]; continue; } // Determine hash function based on secret key length $hashFunction = strlen($secretKey) <= 32 ? 'hmacsha1' : 'hmacsha256'; $tokenData = [ "displayName" => "$manufacturer $model - $serialNumber", // Required property "serialNumber" => $serialNumber, "manufacturer" => $manufacturer, "model" => $model, "secretKey" => $secretKey, // Use raw base32 string "timeIntervalInSeconds" => $timeInterval, "hashFunction" => $hashFunction, // Set based on secret key length ]; $url = "https://graph.microsoft.com/beta/directory/authenticationMethodDevices/hardwareOathDevices"; $ch = curl_init(); if (LOCAL_APP == 1 ) { curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); } // Apply proxy settings applyProxySettings($ch); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($tokenData)); curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer $accessToken", "Content-Type: application/json"]); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $resp = json_decode(curl_exec($ch), true); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $tokenId = $resp['id'] ?? null; $results[$serialNumber] = [ 'import_success' => ($httpCode === 200 || $httpCode === 201), 'import_log' => $resp, 'token_id' => $tokenId, 'http_code' => $httpCode, 'request_data' => $tokenData, // Include request for debugging 'import_mode' => $importMode, ]; // Only proceed with assignment if import was successful and mode requires it if ($tokenId && ($importMode === 'import_assign' || $importMode === 'import_assign_activate') && $upn) { // Assign the token to the user $assignUrl = "https://graph.microsoft.com/beta/users/$upn/authentication/hardwareOathMethods"; $assignData = ["device" => ["id" => $tokenId]]; $assignCh = curl_init(); if (LOCAL_APP == 1 ) { curl_setopt($assignCh, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($assignCh, CURLOPT_SSL_VERIFYHOST, false); } // Apply proxy settings - FIXED applyProxySettings($assignCh); curl_setopt($assignCh, CURLOPT_URL, $assignUrl); curl_setopt($assignCh, CURLOPT_POST, true); curl_setopt($assignCh, CURLOPT_POSTFIELDS, json_encode($assignData)); curl_setopt($assignCh, CURLOPT_HTTPHEADER, ["Authorization: Bearer $accessToken", "Content-Type: application/json"]); curl_setopt($assignCh, CURLOPT_RETURNTRANSFER, true); $assignResp = json_decode(curl_exec($assignCh), true); $assignHttpCode = curl_getinfo($assignCh, CURLINFO_HTTP_CODE); curl_close($assignCh); $results[$serialNumber]['assign_success'] = ($assignHttpCode === 200 || $assignHttpCode === 201); $results[$serialNumber]['assign_log'] = $assignResp; $results[$serialNumber]['assign_http_code'] = $assignHttpCode; // Only attempt activation if assignment was successful and mode requires it if ($results[$serialNumber]['assign_success'] && $importMode === 'import_assign_activate') { // Activate the token $activateUrl = "https://graph.microsoft.com/beta/users/$upn/authentication/hardwareOathMethods/$tokenId/activate"; $activateData = ["verificationCode" => generateTOTPCode($secretKey)]; // $activateCh = curl_init(); if (LOCAL_APP == 1 ) { curl_setopt($activateCh, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($activateCh, CURLOPT_SSL_VERIFYHOST, false); } // Apply proxy settings - FIXED applyProxySettings($activateCh); curl_setopt($activateCh, CURLOPT_URL, $activateUrl); curl_setopt($activateCh, CURLOPT_POST, true); curl_setopt($activateCh, CURLOPT_POSTFIELDS, json_encode($activateData)); curl_setopt($activateCh, CURLOPT_HTTPHEADER, ["Authorization: Bearer $accessToken", "Content-Type: application/json"]); curl_setopt($activateCh, CURLOPT_RETURNTRANSFER, true); $activateResp = json_decode(curl_exec($activateCh), true); $activateHttpCode = curl_getinfo($activateCh, CURLINFO_HTTP_CODE); curl_close($activateCh); $results[$serialNumber]['activate_success'] = ($activateHttpCode === 200 || $activateHttpCode === 204); $results[$serialNumber]['activate_log'] = $activateResp; $results[$serialNumber]['activate_http_code'] = $activateHttpCode; } } } return $results; } function getUsers($accessToken, $query = '') { // Enhanced debugging function $debugInfo = [ 'query' => $query, 'access_token_length' => strlen($accessToken), 'timestamp' => date('Y-m-d H:i:s') ]; // Build the filter properly if (!empty($query)) { $filter = "startswith(displayName,'$query') or startswith(userPrincipalName,'$query')"; $url = "https://graph.microsoft.com/v1.0/users?\$top=50&\$filter=" . urlencode($filter); } else { // If no query, get all users (first 50) $url = "https://graph.microsoft.com/v1.0/users?\$top=50"; } $debugInfo['url'] = $url; $ch = curl_init(); if (LOCAL_APP == 1 ) { curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); } else { curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); } // Apply proxy settings applyProxySettings($ch); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer $accessToken", "Content-Type: application/json"]); curl_setopt($ch, CURLOPT_TIMEOUT, 30); curl_setopt($ch, CURLOPT_VERBOSE, true); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $curlError = curl_error($ch); $curlInfo = curl_getinfo($ch); curl_close($ch); $debugInfo['http_code'] = $httpCode; $debugInfo['curl_error'] = $curlError; $debugInfo['response_length'] = strlen($response); $debugInfo['response_preview'] = substr($response, 0, 200); // Log debug info error_log("getUsers Debug: " . json_encode($debugInfo)); // Check for curl errors if ($response === false) { error_log("CURL Error in getUsers: " . $curlError); return [ 'debug' => $debugInfo, 'error' => 'CURL Error: ' . $curlError, 'users' => [] ]; } // Check for HTTP errors if ($httpCode !== 200) { error_log("HTTP Error in getUsers: HTTP $httpCode - Response: " . $response); // Parse response for permission errors $resp = json_decode($response, true); if ($httpCode === 403 || $httpCode === 401) { $error = $resp['error'] ?? []; $errorCode = $error['code'] ?? ''; $errorMessage = $error['message'] ?? ''; if (strpos($errorCode, 'Authorization_RequestDenied') !== false || strpos($errorMessage, 'Insufficient privileges') !== false) { return [ 'debug' => $debugInfo, 'error' => 'Missing Permission: Your application needs User.Read.All permission to list users. Please add this permission in Azure Portal → App Registrations → API permissions.', 'permission_error' => true, 'users' => [] ]; } } return [ 'debug' => $debugInfo, 'error' => "HTTP $httpCode", 'response' => $response, 'users' => [] ]; } $resp = json_decode($response, true); // Check for JSON decode errors if (json_last_error() !== JSON_ERROR_NONE) { error_log("JSON Decode Error in getUsers: " . json_last_error_msg()); return [ 'debug' => $debugInfo, 'error' => 'JSON Error: ' . json_last_error_msg(), 'users' => [] ]; } $debugInfo['user_count'] = count($resp['value'] ?? []); // SUCCESS - return users without debug info return [ 'users' => $resp['value'] ?? [], 'success' => true ]; } function assignToken($accessToken, $userId, $tokenId) { $url = "https://graph.microsoft.com/beta/users/$userId/authentication/hardwareOathMethods"; $data = ["device" => ["id" => $tokenId]]; $ch = curl_init(); if (LOCAL_APP == 1 ) { curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); } // Apply proxy settings applyProxySettings($ch); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer $accessToken", "Content-Type: application/json"]); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $resp = json_decode($response, true); // Check for permission errors if ($httpCode === 403 || $httpCode === 401) { $error = $resp['error'] ?? []; $errorMessage = $error['message'] ?? ''; if (strpos($errorMessage, 'Insufficient privileges') !== false) { return [ 'success' => false, 'log' => $resp, 'permission_error' => 'Missing Permission: Your application needs AuthenticationMethodDevice.ReadWrite.All permission to assign tokens to users.' ]; } } return ['success' => ($httpCode === 200 || $httpCode === 201), 'log' => $resp]; } function activateToken($accessToken, $userId, $tokenId, $code) { $url = "https://graph.microsoft.com/beta/users/$userId/authentication/hardwareOathMethods/$tokenId/activate"; $data = ["verificationCode" => $code]; $ch = curl_init(); if (LOCAL_APP == 1 ) { curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); } // Apply proxy settings applyProxySettings($ch); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer $accessToken", "Content-Type: application/json"]); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $resp = json_decode($response, true); // Check for permission errors if ($httpCode === 403 || $httpCode === 401) { $error = $resp['error'] ?? []; $errorMessage = $error['message'] ?? ''; if (strpos($errorMessage, 'Insufficient privileges') !== false) { return [ 'success' => false, 'log' => $resp, 'permission_error' => 'Missing Permission: Your application needs AuthenticationMethodDevice.ReadWrite.All permission to activate tokens.' ]; } } return ['success' => ($httpCode === 200 || $httpCode === 204), 'log' => $resp]; } function unassignToken($accessToken, $userId, $tokenId) { $url = "https://graph.microsoft.com/beta/users/$userId/authentication/hardwareOathMethods/$tokenId"; $ch = curl_init(); if (LOCAL_APP == 1 ) { curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); } // Apply proxy settings applyProxySettings($ch); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE"); curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer $accessToken"]); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $resp = json_decode($response, true); // Check for permission errors if ($httpCode === 403 || $httpCode === 401) { $error = $resp['error'] ?? []; $errorMessage = $error['message'] ?? ''; if (strpos($errorMessage, 'Insufficient privileges') !== false) { return [ 'success' => false, 'log' => $resp, 'permission_error' => 'Missing Permission: Your application needs AuthenticationMethodDevice.ReadWrite.All permission to unassign tokens.' ]; } } return ['success' => ($httpCode === 200 || $httpCode === 204), 'log' => $resp]; } function deleteToken($accessToken, $tokenId) { $url = "https://graph.microsoft.com/beta/directory/authenticationMethodDevices/hardwareOathDevices/$tokenId"; $ch = curl_init(); if (LOCAL_APP == 1 ) { curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); } // Apply proxy settings applyProxySettings($ch); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE"); curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer $accessToken"]); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $resp = json_decode($response, true); // Check for permission errors if ($httpCode === 403 || $httpCode === 401) { $error = $resp['error'] ?? []; $errorMessage = $error['message'] ?? ''; if (strpos($errorMessage, 'Insufficient privileges') !== false) { return [ 'success' => false, 'log' => $resp, 'permission_error' => 'Missing Permission: Your application needs Policy.ReadWrite.AuthenticationMethod permission to delete tokens.' ]; } } return ['success' => ($httpCode === 200 || $httpCode === 204), 'log' => $resp]; } function generateTOTPCode($base32Secret, $timeInterval = 30) { try { // Determine hash algorithm based on secret length (like JavaScript version) $algorithm = strlen($base32Secret) <= 32 ? 'sha1' : 'sha256'; // Base32 decode $base32Chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'; $base32CharsFlipped = array_flip(str_split($base32Chars)); $paddedSecret = $base32Secret; $remainder = strlen($paddedSecret) % 8; if ($remainder > 0) { $paddedSecret .= str_repeat('=', 8 - $remainder); } $binaryString = ''; for ($i = 0; $i < strlen($paddedSecret); $i += 8) { $chunk = substr($paddedSecret, $i, 8); if ($chunk === '========') break; $binaryChunk = ''; for ($j = 0; $j < strlen($chunk); $j++) { if ($chunk[$j] === '=') break; if (!isset($base32CharsFlipped[$chunk[$j]])) { return false; // Invalid character } $binaryChunk .= str_pad(decbin($base32CharsFlipped[$chunk[$j]]), 5, '0', STR_PAD_LEFT); } // Convert binary string to bytes for ($k = 0; $k < strlen($binaryChunk); $k += 8) { if (strlen($binaryChunk) - $k >= 8) { $binaryString .= chr(bindec(substr($binaryChunk, $k, 8))); } } } // Get current time counter $timeCounter = floor(time() / $timeInterval); // Create time counter as 8-byte big-endian $timeBytes = pack('N*', 0, $timeCounter); // Generate HMAC with appropriate algorithm $hash = hash_hmac($algorithm, $timeBytes, $binaryString, true); // Dynamic truncation $hashLength = strlen($hash); $offset = ord($hash[$hashLength - 1]) & 0x0f; $code = ( ((ord($hash[$offset]) & 0x7f) << 24) | ((ord($hash[$offset + 1]) & 0xff) << 16) | ((ord($hash[$offset + 2]) & 0xff) << 8) | (ord($hash[$offset + 3]) & 0xff) ) % 1000000; return str_pad($code, 6, '0', STR_PAD_LEFT); } catch (Exception $e) { error_log("TOTP generation error: " . $e->getMessage()); return false; } } // Initialize message variables $message = ''; $csvMessage = ''; $importLog = null; $csvImportLog = null; $showMessages = false; // Only show messages immediately after the relevant operation $initialMessage = ''; // Handle AJAX - these should not show persistent messages if (isset($_GET['action'])) { header('Content-Type: application/json'); try { $accessToken = getAccessToken($tenantId, $clientId, $clientSecret); } catch (Exception $e) { echo json_encode(['success' => false, 'error' => $e->getMessage(), 'credential_error' => true]); exit; } $post = $_POST; $res = ['success' => false, 'log' => '']; switch ($_GET['action']) { case 'get_users': $query = $_GET['query'] ?? ''; $result = getUsers($accessToken, $query); // Check if the result contains success indicator if (isset($result['success']) && $result['success']) { $res = ['success' => true, 'users' => $result['users']]; } else { // Error case - has debug info $res = [ 'success' => false, 'users' => $result['users'] ?? [], 'debug' => $result['debug'] ?? null, 'error' => $result['error'] ?? 'Unknown error' ]; } break; case 'assign_token': $res = assignToken($accessToken, $post['user_id'] ?? '', $post['token_id'] ?? ''); break; case 'activate_token': $res = activateToken($accessToken, $post['user_id'] ?? '', $post['token_id'] ?? '', $post['verification_code'] ?? ''); break; case 'unassign_token': $res = unassignToken($accessToken, $post['user_id'] ?? '', $post['token_id'] ?? ''); break; case 'delete_token': $res = deleteToken($accessToken, $post['token_id'] ?? ''); break; case 'import_csv': $csvData = $post['csv_data'] ?? ''; $importMode = $post['import_mode'] ?? 'import_assign_activate'; $res = ['success' => true, 'log' => importCSVTokens($accessToken, $csvData, $importMode)]; break; } echo json_encode($res); exit; } // Handle file uploads - these SHOULD show persistent messages only for their specific operations if ($_SERVER['REQUEST_METHOD'] === 'POST') { try { // Handle JSON import if (isset($_FILES['importFile'])) { $data = json_decode(file_get_contents($_FILES['importFile']['tmp_name']), true); if ($data) { $accessToken = getAccessToken($tenantId, $clientId, $clientSecret); $importLog = [ 'action' => 'Import Tokens', 'request' => $data, 'response' => importTokens($accessToken, $data), ]; $message = ($importLog['response'] ? 'Tokens imported' : 'Failed import'); $showMessages = true; // Flag to show this specific message } else { $message = 'Invalid JSON'; $showMessages = true; } } // Handle CSV file upload elseif (isset($_FILES['csvFile'])) { $csvData = file_get_contents($_FILES['csvFile']['tmp_name']); $importMode = $_POST['import_mode'] ?? 'import_assign_activate'; if ($csvData !== false) { $accessToken = getAccessToken($tenantId, $clientId, $clientSecret); $csvImportLog = [ 'action' => 'Import CSV Tokens', 'filename' => $_FILES['csvFile']['name'], 'import_mode' => $importMode, 'response' => importCSVTokens($accessToken, $csvData, $importMode), ]; $csvMessage = 'CSV Tokens imported from file: ' . $_FILES['csvFile']['name'] . ' (Mode: ' . $importMode . ')'; $showMessages = true; // Flag to show this specific message } else { $csvMessage = 'Failed to read CSV file'; $showMessages = true; } } // Handle textarea CSV import (keeping both options) - but only if no file was uploaded elseif (isset($_POST['csv_data'])) { $csvData = $_POST['csv_data']; $importMode = $_POST['import_mode'] ?? 'import_assign_activate'; $accessToken = getAccessToken($tenantId, $clientId, $clientSecret); $csvImportLog = [ 'action' => 'Import CSV Tokens', 'request' => $csvData, 'import_mode' => $importMode, 'response' => importCSVTokens($accessToken, $csvData, $importMode), ]; $csvMessage = 'CSV Tokens imported from textarea (Mode: ' . $importMode . ')'; $showMessages = true; // Flag to show this specific message } } catch (Exception $e) { // Handle credential errors $credentialError = $e->getMessage(); if (isset($_FILES['importFile'])) { $message = 'Authentication failed: ' . $credentialError; $showMessages = true; } elseif (isset($_FILES['csvFile']) || isset($_POST['csv_data'])) { $csvMessage = 'Authentication failed: ' . $credentialError; $showMessages = true; } } } // Fetch tokens and capture the response for logging $credentialError = null; $tokens = []; $initialLog = null; try { $fetchResult = fetchTokens(getAccessToken($tenantId, $clientId, $clientSecret)); $tokens = $fetchResult['value']; $initialLog = [ 'action' => 'Initial Load', 'endpoint' => 'fetchTokens', 'response' => $fetchResult['log'], ]; } catch (Exception $e) { $credentialError = $e->getMessage(); $initialLog = [ 'action' => 'Initial Load', 'endpoint' => 'fetchTokens', 'error' => $credentialError, ]; } ?> Hardware Tokens

TOTP Tokens Inventory

Clear session

Settings updated successfully!
Authentication Error:
Please check your credentials in

Please check your credentials in

Please check your credentials in

Serial Device Hash Time User Status Seen Actions
s
© Token2