486 lines
15 KiB
PHP

<?php
// OAuth Callback Handler
// This page handles the OAuth callback from external providers
$code = $_GET['code'] ?? null;
$state = $_GET['state'] ?? null;
$error = $_GET['error'] ?? null;
$error_description = $_GET['error_description'] ?? null;
// OAuth Configuration (in production, store these securely)
$oauth_config = [
'google' => [
'client_id' => 'YOUR_GOOGLE_CLIENT_ID',
'client_secret' => 'YOUR_GOOGLE_CLIENT_SECRET',
'token_url' => 'https://oauth2.googleapis.com/token',
'userinfo_url' => 'https://www.googleapis.com/oauth2/v2/userinfo',
'redirect_uri' => URL::Link('auth/callback')
]
];
// Check for OAuth errors
if ($error) {
$error_message = $error_description ?: $error;
URL::$fragments['error'] = "OAuth Error: " . htmlspecialchars($error_message);
URL::$fragments['error_type'] = 'oauth_error';
} else if ($code && $state) {
// Determine which OAuth provider this is for (you'd need to store this during the auth initiation)
$service = $_SESSION['oauth_service'] ?? 'google';
if (!isset($oauth_config[$service])) {
URL::$fragments['error'] = "Unknown OAuth service: $service";
URL::$fragments['error_type'] = 'invalid_service';
} else {
$config = $oauth_config[$service];
// Check if configuration is complete
if ($config['client_id'] === 'YOUR_GOOGLE_CLIENT_ID' ||
$config['client_secret'] === 'YOUR_GOOGLE_CLIENT_SECRET') {
URL::$fragments['demo_mode'] = true;
URL::$fragments['success'] = "OAuth callback received successfully! (Demo Mode)";
URL::$fragments['code'] = substr($code, 0, 20) . '...';
URL::$fragments['state'] = $state;
URL::$fragments['service'] = $service;
} else {
// STEP 1: Exchange authorization code for access token
$token_response = HTTP::post($config['token_url'], [
'client_id' => $config['client_id'],
'client_secret' => $config['client_secret'],
'code' => $code,
'grant_type' => 'authorization_code',
'redirect_uri' => $config['redirect_uri']
]);
if (!$token_response['success']) {
URL::$fragments['error'] = "Token exchange failed: " . ($token_response['error'] ?? 'Unknown error');
URL::$fragments['error_type'] = 'token_exchange_failed';
URL::$fragments['debug'] = $token_response;
} else {
$tokens = $token_response['data'];
$access_token = $tokens['access_token'];
// STEP 2: Get user profile information
$profile_response = HTTP::get_with_token($config['userinfo_url'], $access_token);
if (!$profile_response['success']) {
URL::$fragments['error'] = "Failed to get user profile: " . ($profile_response['error'] ?? 'Unknown error');
URL::$fragments['error_type'] = 'profile_fetch_failed';
URL::$fragments['debug'] = $profile_response;
} else {
$profile = $profile_response['data'];
// STEP 3: Create or login user
// This is where you'd typically:
// 1. Check if user exists by email
// 2. Create new user if doesn't exist
// 3. Update user profile with OAuth data
// 4. Set session variables
// For demo purposes, just set session data
$_SESSION['user_id'] = $profile['id'];
$_SESSION['user_email'] = $profile['email'];
$_SESSION['user_name'] = $profile['name'];
$_SESSION['user_picture'] = $profile['picture'] ?? null;
$_SESSION['oauth_provider'] = $service;
$_SESSION['logged_in_at'] = time();
URL::$fragments['success'] = "Successfully logged in with " . ucfirst($service) . "!";
URL::$fragments['user_profile'] = $profile;
URL::$fragments['redirect_to'] = 'dashboard'; // Where to redirect after success
}
}
}
}
// Clean up temporary OAuth session data
unset($_SESSION['oauth_service']);
unset($_SESSION['oauth_state']);
} else {
URL::$fragments['error'] = "Invalid OAuth callback - missing required parameters";
URL::$fragments['error_type'] = 'invalid_callback';
}
?>
<div class="oauth-callback-page">
<div class="callback-container">
<?php if (isset(URL::$fragments['error'])): ?>
<div class="callback-result error">
<div class="result-icon">✗</div>
<h2>Authentication Failed</h2>
<p><?= safe(URL::$fragments['error']) ?></p>
<div class="callback-actions">
<a href="<?= URL::Link('auth') ?>" class="btn btn-primary">Try Again</a>
<a href="<?= URL::Link('') ?>" class="btn btn-outline">Go Home</a>
</div>
</div>
<?php elseif (isset(URL::$fragments['success'])): ?>
<div class="callback-result success">
<div class="result-icon">✓</div>
<h2>Authentication Successful</h2>
<p><?= safe(URL::$fragments['success']) ?></p>
<?php if (isset(URL::$fragments['user_profile'])): ?>
<div class="user-profile">
<h3>Welcome back!</h3>
<div class="profile-info">
<?php if (URL::$fragments['user_profile']['picture']): ?>
<img src="<?= safe(URL::$fragments['user_profile']['picture']) ?>" alt="Profile Picture" class="profile-picture">
<?php endif; ?>
<div class="profile-details">
<p><strong>Name:</strong> <?= safe(URL::$fragments['user_profile']['name']) ?></p>
<p><strong>Email:</strong> <?= safe(URL::$fragments['user_profile']['email']) ?></p>
<p><strong>Provider:</strong> <?= safe(ucfirst($_SESSION['oauth_provider'])) ?></p>
</div>
</div>
</div>
<div class="callback-actions">
<a href="<?= URL::Link(URL::$fragments['redirect_to'] ?? '') ?>" class="btn btn-primary">Continue to Dashboard</a>
<a href="<?= URL::Link('auth') ?>" class="btn btn-outline">Back to Auth Demo</a>
</div>
<?php elseif (isset(URL::$fragments['demo_mode'])): ?>
<div class="callback-details">
<h3>Demo Mode - Next Steps for Implementation:</h3>
<div class="demo-info">
<p><strong>Service:</strong> <?= safe(URL::$fragments['service']) ?></p>
<p><strong>Code:</strong> <?= safe(URL::$fragments['code']) ?></p>
<p><strong>State:</strong> <?= safe(URL::$fragments['state']) ?></p>
</div>
<ol>
<li><strong>Configure OAuth credentials</strong>
<div class="code-snippet">
<code>Replace 'YOUR_GOOGLE_CLIENT_ID' and 'YOUR_GOOGLE_CLIENT_SECRET' with actual values</code>
</div>
</li>
<li><strong>Exchange authorization code for access token</strong></li>
<li><strong>Use access token to get user profile</strong></li>
<li><strong>Create or login user account</strong></li>
<li><strong>Set session variables</strong></li>
<li><strong>Redirect to dashboard/profile</strong></li>
</ol>
<div class="implementation-example">
<h4>Complete Implementation Example:</h4>
<div class="code-block-container">
<div class="terminal-header primary-gradient"></div>
<div class="terminal-controls">
<div class="window-dot red"></div>
<div class="window-dot yellow"></div>
<div class="window-dot green"></div>
<span class="mono-text">oauth-handler.php</span>
</div>
<pre class="code-block"><code><?= htmlspecialchars('// Exchange code for token
$token_response = HTTP::post("https://oauth2.googleapis.com/token", [
"client_id" => $client_id,
"client_secret" => $client_secret,
"code" => $code,
"grant_type" => "authorization_code",
"redirect_uri" => $redirect_uri
]);
// Get user profile
$profile_response = HTTP::get_with_token(
"https://www.googleapis.com/oauth2/v2/userinfo",
$token_response["data"]["access_token"]
);
// Login/create user
$profile = $profile_response["data"];
$user = User::FindByEmail($profile["email"]) ?: User::Create([
"email" => $profile["email"],
"name" => $profile["name"],
"picture" => $profile["picture"],
"oauth_provider" => "google",
"oauth_id" => $profile["id"]
]);
// Set session
$_SESSION["user_id"] = $user["id"];
$_SESSION["user_email"] = $user["email"];
$_SESSION["logged_in_at"] = time();
// Redirect to dashboard
URL::Redirect("dashboard");') ?></code></pre>
</div>
</div>
</div>
<div class="callback-actions">
<a href="<?= URL::Link('auth') ?>" class="btn btn-primary">Back to Auth Demo</a>
<a href="<?= URL::Link('') ?>" class="btn btn-outline">Go Home</a>
</div>
<?php endif; ?>
<?php if (isset(URL::$fragments['debug'])): ?>
<div class="debug-section">
<h4>Debug Information</h4>
<pre class="debug-content"><?= htmlspecialchars(json_encode(URL::$fragments['debug'], JSON_PRETTY_PRINT)) ?></pre>
</div>
<?php endif; ?>
</div>
<?php endif; ?>
</div>
</div>
<style>
.oauth-callback-page {
min-height: 70vh;
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
}
.callback-container {
max-width: 800px;
width: 100%;
}
.callback-result {
background: var(--surface);
border-radius: var(--radius-lg);
padding: 3rem;
text-align: center;
border: 1px solid;
box-shadow: var(--shadow-lg);
}
.callback-result.success {
border-color: var(--success, #22c55e);
background: var(--success-bg, rgba(34, 197, 94, 0.05));
}
.callback-result.error {
border-color: var(--error, #ef4444);
background: var(--error-bg, rgba(239, 68, 68, 0.05));
}
.result-icon {
font-size: 4rem;
margin-bottom: 1rem;
font-weight: bold;
}
.callback-result.success .result-icon {
color: var(--success, #22c55e);
}
.callback-result.error .result-icon {
color: var(--error, #ef4444);
}
.callback-result h2 {
margin-bottom: 1rem;
color: var(--text-primary);
font-size: 1.8rem;
}
.callback-result > p {
margin-bottom: 2rem;
color: var(--text-secondary);
font-size: 1.1rem;
}
.callback-details {
text-align: left;
background: var(--bg-secondary);
border-radius: var(--radius-md);
padding: 2rem;
margin: 2rem 0;
border: 1px solid var(--border);
}
.callback-details h3 {
margin-bottom: 1rem;
color: var(--text-primary);
font-size: 1.2rem;
}
.callback-details ol {
margin-bottom: 2rem;
padding-left: 1.5rem;
}
.callback-details li {
margin-bottom: 1rem;
color: var(--text-secondary);
line-height: 1.5;
}
.code-snippet {
margin-top: 0.5rem;
padding: 0.5rem;
background: var(--bg-color);
border-radius: var(--radius-sm);
border: 1px solid var(--border);
font-family: 'Courier New', monospace;
}
.code-snippet code {
color: var(--text-primary);
font-size: 0.9rem;
}
.implementation-example h4 {
margin-bottom: 1rem;
color: var(--text-primary);
font-size: 1.1rem;
}
.callback-actions {
display: flex;
gap: 1rem;
justify-content: center;
flex-wrap: wrap;
}
.callback-actions .btn {
min-width: 140px;
}
.user-profile {
text-align: left;
background: var(--bg-secondary);
border-radius: var(--radius-md);
padding: 1.5rem;
margin: 2rem 0;
border: 1px solid var(--border);
}
.user-profile h3 {
margin-bottom: 1rem;
color: var(--text-primary);
text-align: center;
}
.profile-info {
display: flex;
align-items: center;
gap: 1rem;
}
.profile-picture {
width: 60px;
height: 60px;
border-radius: 50%;
border: 2px solid var(--border);
object-fit: cover;
}
.profile-details {
flex: 1;
}
.profile-details p {
margin-bottom: 0.5rem;
color: var(--text-secondary);
font-size: 0.9rem;
}
.profile-details strong {
color: var(--text-primary);
}
.demo-info {
background: var(--bg-color);
padding: 1rem;
border-radius: var(--radius-sm);
margin-bottom: 1rem;
border: 1px solid var(--border);
}
.demo-info p {
margin-bottom: 0.5rem;
font-family: 'Courier New', monospace;
font-size: 0.85rem;
color: var(--text-secondary);
}
.demo-info strong {
color: var(--text-primary);
}
.debug-section {
margin-top: 2rem;
padding: 1rem;
background: var(--bg-color);
border-radius: var(--radius-md);
border: 1px solid var(--border);
}
.debug-section h4 {
margin-bottom: 1rem;
color: var(--text-primary);
font-size: 1rem;
}
.debug-content {
background: none;
margin: 0;
padding: 0;
font-size: 0.75rem;
color: var(--text-secondary);
white-space: pre-wrap;
word-break: break-all;
font-family: 'Courier New', monospace;
}
@media (max-width: 600px) {
.profile-info {
flex-direction: column;
text-align: center;
}
.profile-picture {
align-self: center;
}
}
.oauth-callback-page {
padding: 1rem;
}
.callback-result {
padding: 2rem;
}
.callback-details {
padding: 1.5rem;
}
.result-icon {
font-size: 3rem;
}
.callback-result h2 {
font-size: 1.5rem;
}
.callback-actions {
flex-direction: column;
}
.callback-actions .btn {
width: 100%;
}
}
</style>
<script>
// Auto-redirect after successful authentication (optional)
$(document).ready(function() {
const isSuccess = <?= isset(URL::$fragments['success']) ? 'true' : 'false' ?>;
if (isSuccess) {
// You could add a countdown timer here
// setTimeout(() => {
// window.location.href = "<?= URL::Link('') ?>";
// }, 5000);
}
});
</script>