<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SAP MCP Server - Authentication</title>
<!-- SAP 72 Font -->
<link href="https://fonts.googleapis.com/css2?family=72:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: '72', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #0f2027 0%, #203a43 50%, #2c5364 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.container {
background: white;
border-radius: 10px;
box-shadow: 0 15px 30px rgba(0, 0, 0, 0.2);
padding: 40px;
width: 100%;
max-width: 450px;
}
.header {
text-align: center;
margin-bottom: 30px;
}
.logo {
width: 60px;
height: 60px;
background: #0070f3;
border-radius: 50%;
margin: 0 auto 20px;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
color: white;
font-weight: bold;
}
h1 {
color: #333;
font-size: 24px;
margin-bottom: 8px;
}
.subtitle {
color: #666;
font-size: 14px;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
color: #333;
font-weight: 500;
margin-bottom: 5px;
}
input[type="text"], input[type="password"] {
width: 100%;
padding: 12px;
border: 1px solid #ddd;
border-radius: 5px;
font-size: 16px;
transition: border-color 0.3s ease;
}
input[type="text"]:focus, input[type="password"]:focus {
outline: none;
border-color: #0070f3;
box-shadow: 0 0 0 3px rgba(0, 112, 243, 0.1);
}
.btn {
width: 100%;
padding: 12px;
background: #0070f3;
color: white;
border: none;
border-radius: 5px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: background-color 0.3s ease;
margin-bottom: 10px;
}
.btn-secondary {
background: #f5f5f5;
color: #333;
border: 1px solid #ddd;
}
.btn-secondary:hover {
background: #e9ecef;
}
.auth-method-selection {
text-align: center;
}
.method-info {
margin-top: 15px;
text-align: center;
}
.method-info small {
color: #666;
font-style: italic;
}
.btn:hover {
background: #0051a2;
}
.btn:disabled {
background: #ccc;
cursor: not-allowed;
}
.error-card {
background: #f8d7da;
border: 1px solid #f5c6cb;
border-radius: 8px;
padding: 20px;
text-align: center;
color: #721c24;
}
.error-card h3 {
margin-bottom: 10px;
color: #721c24;
}
.error-card ul {
background: rgba(255, 255, 255, 0.7);
border-radius: 4px;
padding: 10px;
margin: 15px 0;
}
.error-card code {
background: rgba(0, 0, 0, 0.1);
padding: 2px 4px;
border-radius: 3px;
font-family: 'Monaco', 'Menlo', monospace;
}
.retry-btn {
background: #dc3545;
color: white;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
font-weight: 600;
margin-top: 10px;
}
.retry-btn:hover {
background: #c82333;
}
.loading {
display: none;
align-items: center;
justify-content: center;
gap: 10px;
}
.spinner {
width: 20px;
height: 20px;
border: 2px solid #ffffff;
border-top: 2px solid transparent;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.error {
color: #dc3545;
font-size: 14px;
margin-top: 10px;
padding: 10px;
background: #f8d7da;
border: 1px solid #f5c6cb;
border-radius: 5px;
display: none;
}
.success {
display: none;
text-align: center;
}
.success-icon {
width: 60px;
height: 60px;
background: #28a745;
border-radius: 50%;
margin: 0 auto 20px;
display: flex;
align-items: center;
justify-content: center;
font-size: 30px;
color: white;
}
.section {
background: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 5px;
padding: 15px;
margin: 20px 0;
}
.section h3 {
color: #495057;
margin-bottom: 10px;
font-size: 16px;
}
.section p {
color: #6c757d;
font-size: 14px;
line-height: 1.4;
margin-bottom: 10px;
}
.download-btn, .copy-btn {
display: inline-block;
padding: 8px 16px;
background: #28a745;
color: white;
text-decoration: none;
border-radius: 3px;
font-size: 14px;
margin-right: 10px;
border: none;
cursor: pointer;
}
.download-btn:hover, .copy-btn:hover {
background: #1e7e34;
}
.config-text {
background: #f1f3f4;
padding: 10px;
border-radius: 3px;
font-family: monospace;
font-size: 12px;
margin: 10px 0;
overflow-x: auto;
}
.back-btn {
background: #6c757d;
margin-top: 15px;
}
.back-btn:hover {
background: #545b62;
}
</style>
</head>
<body>
<div class="container">
<!-- Login Form -->
<div id="loginForm">
<div class="header">
<div class="logo">S</div>
<h1>SAP MCP Server</h1>
<p class="subtitle" id="subtitle">Redirecting to SAP Identity Authentication Service...</p>
</div>
<!-- Direct SAP IAS Redirect -->
<div id="iasRedirectMessage" class="auth-method-selection">
<div style="text-align: center; padding: 20px;">
<div class="loading" style="display: flex; justify-content: center; margin-bottom: 20px;">
<div class="spinner"></div>
</div>
<p>Redirecting to SAP Identity Authentication Service...</p>
<small>You will be redirected to the standard SAP login page in a moment.</small>
</div>
</div>
</div>
<!-- Success State -->
<div id="successState" class="success">
<div class="success-icon">✓</div>
<h2>Authentication Successful!</h2>
<p>Welcome back! Your session is active and ready to use.</p>
<div id="userInfoSection" class="section">
<h3>👤 Your Session</h3>
<p><strong>User:</strong> <span id="userName">Loading...</span></p>
<p><strong>Session ID:</strong> <span id="sessionIdDisplay">Loading...</span></p>
</div>
<div id="adminSection" class="section" style="display: none;">
<h3>🔧 Administration</h3>
<p>You have admin privileges.</p>
<a href="javascript:openAdminDashboard()" class="download-btn">🛠️ View Admin Dashboard</a>
</div>
</div>
</div>
<script>
let sessionId = null;
let tokenData = null;
let currentAuthMethod = 'selection';
// OAuth 2.0 Flow Functions - Direct redirect to SAP IAS
async function initiateOAuth2Flow() {
console.log('initiateOAuth2Flow called!');
try {
console.log('Setting loading state...');
setLoadingState('Redirecting to SAP IAS login page...');
// Use proper callback endpoint (configured in IAS) - force HTTPS for Cloud Foundry
const protocol = window.location.host.includes('.cfapps.') || window.location.host.includes('.ondemand.com') ? 'https:' : window.location.protocol;
const redirectUri = `${protocol}//${window.location.host}/auth/callback`;
const state = generateRandomString(32);
// Store state for validation when callback returns
sessionStorage.setItem('oauth_state', state);
sessionStorage.setItem('oauth_redirect_after_success', window.location.pathname);
// Get IAS configuration and redirect directly to SAP IAS (standard OAuth flow)
try {
console.log('Fetching config from /auth/debug/config...');
const configResponse = await fetch('/auth/debug/config');
console.log('Config response status:', configResponse.status);
const config = await configResponse.json();
console.log('Config loaded:', config);
if (!config.authorizationEndpoint || !config.clientId) {
throw new Error('IAS configuration not available');
}
// Direct redirect to SAP IAS authorization endpoint (standard OAuth 2.0)
const iasAuthUrl = config.authorizationEndpoint + '?' + new URLSearchParams({
client_id: config.clientId,
response_type: 'code',
redirect_uri: redirectUri,
scope: 'openid profile email groups',
state: state
}).toString();
console.log('Redirecting to SAP IAS:', iasAuthUrl);
console.log('About to set window.location.href...');
// Redirect to standard SAP IAS login page
window.location.href = iasAuthUrl;
console.log('window.location.href set to:', iasAuthUrl);
} catch (configError) {
console.error('Could not load IAS configuration:', configError);
document.getElementById('loginContainer').innerHTML = `
<div class="error-card">
<h3>🔧 Configuration Error</h3>
<p>SAP Identity Authentication Service is not properly configured.</p>
<p>Please ensure the following environment variables are set:</p>
<ul style="text-align: left; margin: 1rem 0;">
<li><code>SAP_IAS_URL</code> - Your SAP IAS tenant URL</li>
<li><code>SAP_IAS_CLIENT_ID</code> - OAuth client ID</li>
<li><code>SAP_IAS_CLIENT_SECRET</code> - OAuth client secret</li>
</ul>
<button onclick="location.reload()" class="retry-btn">🔄 Retry</button>
</div>
`;
}
} catch (error) {
console.error('OAuth initiation error:', error);
showError('Failed to initiate OAuth flow: ' + error.message);
}
}
function generateRandomString(length) {
const charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let result = '';
for (let i = 0; i < length; i++) {
result += charset.charAt(Math.floor(Math.random() * charset.length));
}
return result;
}
// Legacy functions removed - direct SAP IAS redirect only
function setLoadingState(message) {
const loginForm = document.getElementById('loginForm');
loginForm.innerHTML = `
<div class="header">
<div class="logo">S</div>
<h1>SAP MCP Server</h1>
<p class="subtitle">${message}</p>
</div>
<div style="text-align: center; padding: 20px;">
<div class="loading" style="display: flex; justify-content: center;">
<div class="spinner"></div>
</div>
</div>
`;
}
function showError(message) {
const errorDiv = document.getElementById('errorMessage');
if (errorDiv) {
errorDiv.textContent = message;
errorDiv.style.display = 'block';
} else {
alert(message); // Fallback
}
}
// Event Listeners (removed - automatic redirect now)
function showSuccessState() {
document.getElementById('loginForm').style.display = 'none';
document.getElementById('successState').style.display = 'block';
updateSuccessDisplay();
}
async function updateSuccessDisplay() {
// Get user session information and update display
try {
const response = await fetch('/auth/status', {
headers: {
'x-mcp-session-id': sessionId
}
});
if (response.ok) {
const status = await response.json();
// Update user information display
document.getElementById('userName').textContent = status.user || 'Unknown';
document.getElementById('sessionIdDisplay').textContent = sessionId || 'Unknown';
// Show admin sections if user has admin privileges
if (status.scopes && status.scopes.some(scope => scope.includes('admin'))) {
document.getElementById('adminSection').style.display = 'block';
document.getElementById('adminButton').style.display = 'inline-block';
}
} else {
// If status check fails, show generic info
document.getElementById('userName').textContent = 'Authenticated User';
document.getElementById('sessionIdDisplay').textContent = sessionId || 'Unknown';
}
} catch (error) {
console.log('Could not get session info:', error);
// Show what we know
document.getElementById('userName').textContent = 'Authenticated User';
document.getElementById('sessionIdDisplay').textContent = sessionId || 'Unknown';
}
// Check for redirect parameter and redirect after successful authentication
const urlParams = new URLSearchParams(window.location.search);
const redirectUrl = urlParams.get('redirect');
if (redirectUrl) {
setTimeout(() => {
window.location.href = redirectUrl;
}, 3000); // Give user 3 seconds to see session info before redirecting
}
}
function openAdminDashboard() {
// Open admin dashboard with session ID parameter
const adminUrl = `/auth/admin?session=${encodeURIComponent(sessionId)}`;
window.open(adminUrl, '_blank');
}
// Handle OAuth 2.0 callback
async function handleOAuth2Callback() {
const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get('code');
const state = urlParams.get('state');
const error = urlParams.get('error');
const storedState = sessionStorage.getItem('oauth_state');
if (error) {
showError(`OAuth error: ${error}`);
return false;
}
if (!code) {
return false; // Not an OAuth callback
}
// Validate state parameter
if (!state || state !== storedState) {
showError('OAuth state mismatch. Please try again.');
return true; // Handled as error
}
try {
setLoadingState('Processing OAuth callback...');
// Exchange authorization code for tokens - force HTTPS for Cloud Foundry
const protocol = window.location.host.includes('.cfapps.') || window.location.host.includes('.ondemand.com') ? 'https:' : window.location.protocol;
const response = await fetch('/auth/token', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
grant_type: 'authorization_code',
code: code,
redirect_uri: `${protocol}//${window.location.host}/auth/callback`
})
});
const result = await response.json();
if (response.ok && result.session_id) {
sessionId = result.session_id;
tokenData = { sessionId: result.session_id, user: 'OAuth User' };
// Clear OAuth parameters from URL
sessionStorage.removeItem('oauth_state');
window.history.replaceState({}, document.title, window.location.pathname);
showSuccessState();
} else {
throw new Error(result.error_description || result.message || 'OAuth token exchange failed');
}
} catch (error) {
console.error('OAuth callback error:', error);
showError('OAuth authentication failed: ' + error.message);
}
return true; // OAuth callback handled
}
// Check URL parameters and existing authentication on page load
window.addEventListener('load', async () => {
const urlParams = new URLSearchParams(window.location.search);
const urlSessionId = urlParams.get('session');
const success = urlParams.get('success');
const error = urlParams.get('error');
const code = urlParams.get('code'); // OAuth authorization code
console.log('Page load - URL params:', { success, error, code, session: urlSessionId });
// First check if this is an OAuth callback
if (await handleOAuth2Callback()) {
return; // OAuth callback was handled
}
if (error) {
showError(decodeURIComponent(error));
return; // Stop here on error
} else if (success && urlSessionId) {
// Success callback from server
sessionId = urlSessionId;
tokenData = { sessionId: urlSessionId, user: 'OAuth User' };
showSuccessState();
// Clean up URL
window.history.replaceState({}, document.title, window.location.pathname);
return;
}
// Check for existing session before redirecting
try {
const response = await fetch('/auth/status');
if (response.ok) {
const status = await response.json();
if (status.authenticated) {
sessionId = status.sessionId;
tokenData = status;
showSuccessState();
return;
}
}
} catch (error) {
// No existing session, continue to redirect
}
// Only redirect if no callback parameters and no existing session
console.log('No session found, redirecting to SAP IAS...');
console.log('Calling initiateOAuth2Flow immediately...');
try {
await initiateOAuth2Flow();
} catch (error) {
console.error('Error in initiateOAuth2Flow:', error);
}
});
</script>
</body>
</html>