Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 157 additions & 0 deletions docroot/superset/guestok.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
<?php
/**
* Guest Token Generator for Superset Embedded Dashboard
*
* Generates JWT guest tokens using RSA private key signature (RS256).
* More secure than symmetric keys - uses public/private key pair.
*
* No authentication required - generates guest tokens for anyone.
*/

header('Content-Type: application/json');

// ============================================================================
// CONFIGURATION - Update these values to match your Superset config.py
// ============================================================================

// Path to RSA private key file (keep this secure!)
$PRIVATE_KEY_PATH = __DIR__ . '/embedded_private.pem';

// This should match GUEST_TOKEN_JWT_AUDIENCE in Superset config.py
$JWT_AUDIENCE = 'helioviewer_audience';

// Dashboard ID
$DASHBOARD_ID = 'd682ae6a-62b3-4372-a8e6-a367aec3bad0';

// Token expiration time (in seconds) - 5 minutes default
$TOKEN_EXPIRATION = 300;

// ============================================================================
// JWT Helper Functions
// ============================================================================

/**
* Base64 URL encode
*/
function base64UrlEncode($data) {
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}

/**
* Create JWT token using RSA signature (RS256)
*/
function createJWT($payload, $privateKeyPath, $algorithm = 'RS256') {
// Read private key
$privateKey = file_get_contents($privateKeyPath);
if ($privateKey === false) {
throw new Exception("Failed to read private key from: $privateKeyPath");
}

// Load private key resource
$keyResource = openssl_pkey_get_private($privateKey);
if ($keyResource === false) {
throw new Exception("Failed to load private key. Error: " . openssl_error_string());
}

// Header
$header = [
'typ' => 'JWT',
'alg' => $algorithm
];

$headerEncoded = base64UrlEncode(json_encode($header));
$payloadEncoded = base64UrlEncode(json_encode($payload));

// Data to sign
$dataToSign = $headerEncoded . '.' . $payloadEncoded;

// Sign with RSA private key
$signature = '';
$success = openssl_sign($dataToSign, $signature, $keyResource, OPENSSL_ALGO_SHA256);

if (!$success) {
throw new Exception("Failed to sign JWT. Error: " . openssl_error_string());
}

// Note: In PHP 8.0+, key resources are automatically freed, no need to call openssl_free_key()

$signatureEncoded = base64UrlEncode($signature);

// JWT token
return $headerEncoded . '.' . $payloadEncoded . '.' . $signatureEncoded;
}

/**
* Generate guest token payload
*/
function generateGuestTokenPayload($dashboardId, $audience, $expiration) {
$now = time();

return [
// User information
'user' => [
'username' => 'guest_' . uniqid(),
'first_name' => 'Guest',
'last_name' => 'User'
],

// Resources this token grants access to
'resources' => [
[
'type' => 'dashboard',
'id' => $dashboardId
]
],

// Row Level Security rules (empty = no restrictions)
'rls_rules' => [],

// JWT standard claims
'aud' => $audience,
'iat' => $now,
'exp' => $now + $expiration,
'type' => 'guest'
];
}

// ============================================================================
// Main Execution
// ============================================================================

try {
// Only allow POST requests
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode([
'error' => 'Method not allowed. Use POST.'
]);
exit;
}

// Validate configuration
if (!file_exists($PRIVATE_KEY_PATH)) {
throw new Exception("Private key file not found at: $PRIVATE_KEY_PATH");
}

// Generate token payload
$payload = generateGuestTokenPayload($DASHBOARD_ID, $JWT_AUDIENCE, $TOKEN_EXPIRATION);

// Create JWT token using RSA private key
$token = createJWT($payload, $PRIVATE_KEY_PATH);

// Return the guest token
echo json_encode([
'token' => $token,
'success' => true
]);

} catch (Exception $e) {
http_response_code(500);
echo json_encode([
'error' => $e->getMessage(),
'success' => false
]);

// Log the error
error_log("Superset guest token error: " . $e->getMessage());
}
186 changes: 186 additions & 0 deletions docroot/superset/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Superset Embedded Dashboard</title>
<style>
body {
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
}

#superset-container {
width: 100%;
height: 100vh;
display: flex;
flex-direction: column;
}

.header {
background-color: #20a7c9;
color: white;
padding: 15px 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}

.header h1 {
margin: 0;
font-size: 24px;
font-weight: 500;
}

#my-superset-container {
flex: 1;
width: 100%;
height: 100%;
min-height: 800px;
overflow: hidden;
}

/* Make the iframe inside fill the container */
#my-superset-container iframe {
width: 100% !important;
height: 100% !important;
border: none;
}

.loading {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
font-size: 18px;
color: #666;
}

.error {
padding: 20px;
margin: 20px;
background-color: #fee;
border: 1px solid #fcc;
border-radius: 4px;
color: #c33;
}
</style>
</head>
<body>
<div id="superset-container">
<div class="header">
<h1>Superset Dashboard</h1>
</div>
<div id="my-superset-container">
<div class="loading">Loading dashboard...</div>
</div>
</div>

<!-- Load Superset Embedded SDK from CDN -->
<script src="https://unpkg.com/@superset-ui/embedded-sdk@0.2.0"></script>

<script>
// Configuration
const config = {
// Dashboard ID from Superset (replace with your actual dashboard ID)
dashboardId: "d682ae6a-62b3-4372-a8e6-a367aec3bad0",

// Superset domain (replace with your Superset instance URL)
supersetDomain: "https://ec2-54-234-42-121.compute-1.amazonaws.com",

// Dashboard UI configuration (optional)
dashboardUiConfig: {
hideTitle: false,
hideTab: false,
hideChartControls: false,
filters: {
visible: true,
expanded: false
},
urlParams: {
// Add any URL parameters here
// foo: 'value1',
// bar: 'value2'
}
},

// Optional iframe sandbox attributes
iframeSandboxExtras: ['allow-top-navigation', 'allow-popups-to-escape-sandbox'],

// Optional referrer policy
referrerPolicy: "origin"
};

/**
* Fetch guest token from your backend
* This function should make a request to your backend API
* which in turn calls Superset's /security/guest_token endpoint
*/
async function fetchGuestToken() {
try {
// Call guestok.php in the same directory
const response = await fetch('guestok.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
// Include any authentication headers your backend requires
credentials: 'include'
});

if (!response.ok) {
throw new Error(`Failed to fetch guest token: ${response.statusText}`);
}

const data = await response.json();
return data.token;
} catch (error) {
console.error('Error fetching guest token:', error);
showError('Failed to authenticate with Superset. Please check your configuration.');
throw error;
}
}

/**
* Display error message
*/
function showError(message) {
const container = document.getElementById('my-superset-container');
container.innerHTML = `<div class="error">${message}</div>`;
}

/**
* Initialize and embed the Superset dashboard
*/
function initializeDashboard() {
try {
// Check if SDK is loaded
if (typeof supersetEmbeddedSdk === 'undefined') {
showError('Superset Embedded SDK failed to load from CDN.');
return;
}

// Embed the dashboard
supersetEmbeddedSdk.embedDashboard({
id: config.dashboardId,
supersetDomain: config.supersetDomain,
mountPoint: document.getElementById("my-superset-container"),
fetchGuestToken: fetchGuestToken,
dashboardUiConfig: config.dashboardUiConfig,
iframeSandboxExtras: config.iframeSandboxExtras,
referrerPolicy: config.referrerPolicy
});
} catch (error) {
console.error('Error initializing dashboard:', error);
showError('Failed to initialize dashboard: ' + error.message);
}
}

// Initialize when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initializeDashboard);
} else {
initializeDashboard();
}
</script>
</body>
</html>
Loading