297 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
			
		
		
	
	
			297 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
<!DOCTYPE html>
 | 
						|
<html lang="en">
 | 
						|
<head>
 | 
						|
    <meta charset="UTF-8" />
 | 
						|
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 | 
						|
    <title>Completing Authentication</title>
 | 
						|
    
 | 
						|
    <!-- Tailwind CSS -->
 | 
						|
    <script src="https://cdn.tailwindcss.com"></script>
 | 
						|
    
 | 
						|
    <!-- Alpine.js -->
 | 
						|
    <script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script>
 | 
						|
    
 | 
						|
 | 
						|
    
 | 
						|
    <script>
 | 
						|
        // Configure tailwind
 | 
						|
        tailwind.config = {
 | 
						|
            darkMode: 'class',
 | 
						|
            theme: {
 | 
						|
                extend: {
 | 
						|
                    colors: {
 | 
						|
                        border: "hsl(var(--border))",
 | 
						|
                        input: "hsl(var(--input))",
 | 
						|
                        ring: "hsl(var(--ring))",
 | 
						|
                        background: "hsl(var(--background))",
 | 
						|
                        foreground: "hsl(var(--foreground))",
 | 
						|
                        primary: {
 | 
						|
                            DEFAULT: "hsl(var(--primary))",
 | 
						|
                            foreground: "hsl(var(--primary-foreground))",
 | 
						|
                        },
 | 
						|
                        secondary: {
 | 
						|
                            DEFAULT: "hsl(var(--secondary))",
 | 
						|
                            foreground: "hsl(var(--secondary-foreground))",
 | 
						|
                        },
 | 
						|
                        destructive: {
 | 
						|
                            DEFAULT: "hsl(var(--destructive))",
 | 
						|
                            foreground: "hsl(var(--destructive-foreground))",
 | 
						|
                        },
 | 
						|
                        muted: {
 | 
						|
                            DEFAULT: "hsl(var(--muted))",
 | 
						|
                            foreground: "hsl(var(--muted-foreground))",
 | 
						|
                        },
 | 
						|
                        accent: {
 | 
						|
                            DEFAULT: "hsl(var(--accent))",
 | 
						|
                            foreground: "hsl(var(--accent-foreground))",
 | 
						|
                        },
 | 
						|
                        popover: {
 | 
						|
                            DEFAULT: "hsl(var(--popover))",
 | 
						|
                            foreground: "hsl(var(--popover-foreground))",
 | 
						|
                        },
 | 
						|
                        card: {
 | 
						|
                            DEFAULT: "hsl(var(--card))",
 | 
						|
                            foreground: "hsl(var(--card-foreground))",
 | 
						|
                        },
 | 
						|
                    },
 | 
						|
                    borderRadius: {
 | 
						|
                        lg: "var(--radius)",
 | 
						|
                        md: "calc(var(--radius) - 2px)",
 | 
						|
                        sm: "calc(var(--radius) - 4px)",
 | 
						|
                    },
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
    </script>
 | 
						|
    <style type="text/tailwindcss">
 | 
						|
        @layer base {
 | 
						|
            :root {
 | 
						|
                --background: 0 0% 100%;
 | 
						|
                --foreground: 222.2 84% 4.9%;
 | 
						|
                --muted: 210 40% 96.1%;
 | 
						|
                --muted-foreground: 215.4 16.3% 46.9%;
 | 
						|
                --popover: 0 0% 100%;
 | 
						|
                --popover-foreground: 222.2 84% 4.9%;
 | 
						|
                --card: 0 0% 100%;
 | 
						|
                --card-foreground: 222.2 84% 4.9%;
 | 
						|
                --border: 214.3 31.8% 91.4%;
 | 
						|
                --input: 214.3 31.8% 91.4%;
 | 
						|
                --primary: 222.2 47.4% 11.2%;
 | 
						|
                --primary-foreground: 210 40% 98%;
 | 
						|
                --secondary: 210 40% 96.1%;
 | 
						|
                --secondary-foreground: 222.2 47.4% 11.2%;
 | 
						|
                --accent: 210 40% 96.1%;
 | 
						|
                --accent-foreground: 222.2 47.4% 11.2%;
 | 
						|
                --destructive: 0 84.2% 60.2%;
 | 
						|
                --destructive-foreground: 210 40% 98%;
 | 
						|
                --ring: 215 20.2% 65.1%;
 | 
						|
                --radius: 0.5rem;
 | 
						|
            }
 | 
						|
            
 | 
						|
            .dark {
 | 
						|
                --background: 222.2 84% 4.9%;
 | 
						|
                --foreground: 210 40% 98%;
 | 
						|
                --muted: 217.2 32.6% 17.5%;
 | 
						|
                --muted-foreground: 215 20.2% 65.1%;
 | 
						|
                --popover: 222.2 84% 4.9%;
 | 
						|
                --popover-foreground: 210 40% 98%;
 | 
						|
                --card: 222.2 84% 4.9%;
 | 
						|
                --card-foreground: 210 40% 98%;
 | 
						|
                --border: 217.2 32.6% 17.5%;
 | 
						|
                --input: 217.2 32.6% 17.5%;
 | 
						|
                --primary: 210 40% 98%;
 | 
						|
                --primary-foreground: 222.2 47.4% 11.2%;
 | 
						|
                --secondary: 217.2 32.6% 17.5%;
 | 
						|
                --secondary-foreground: 210 40% 98%;
 | 
						|
                --accent: 217.2 32.6% 17.5%;
 | 
						|
                --accent-foreground: 210 40% 98%;
 | 
						|
                --destructive: 0 62.8% 30.6%;
 | 
						|
                --destructive-foreground: 210 40% 98%;
 | 
						|
                --ring: 212.7 26.8% 83.9%;
 | 
						|
            }
 | 
						|
        }
 | 
						|
        
 | 
						|
        @layer base {
 | 
						|
            * {
 | 
						|
                @apply border-border;
 | 
						|
            }
 | 
						|
            body {
 | 
						|
                @apply bg-background text-foreground;
 | 
						|
            }
 | 
						|
        }
 | 
						|
        
 | 
						|
        [x-cloak] { display: none !important; }
 | 
						|
        
 | 
						|
        .animate-spin {
 | 
						|
            animation: spin 1s linear infinite;
 | 
						|
        }
 | 
						|
        
 | 
						|
        @keyframes spin {
 | 
						|
            0% { transform: rotate(0deg); }
 | 
						|
            100% { transform: rotate(360deg); }
 | 
						|
        }
 | 
						|
    </style>
 | 
						|
</head>
 | 
						|
<body class="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100">
 | 
						|
    <div x-data="oauthCallback()" x-init="handleOAuthCallback">
 | 
						|
        <div class="min-h-screen flex items-center justify-center p-4">
 | 
						|
            <div class="max-w-md w-full">
 | 
						|
                <div class="bg-white rounded-2xl shadow-xl overflow-hidden">
 | 
						|
                    <div class="p-8 text-center">
 | 
						|
                        <div class="mx-auto flex items-center justify-center h-16 w-16 rounded-full bg-blue-100 mb-6">
 | 
						|
                            <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-8 w-8 text-blue-600">
 | 
						|
                                <path d="M18 6 6 18"></path>
 | 
						|
                                <path d="m6 6 12 12"></path>
 | 
						|
                            </svg>
 | 
						|
                        </div>
 | 
						|
                        
 | 
						|
                        <h2 class="text-2xl font-bold text-gray-900 mb-2">Completing Authentication</h2>
 | 
						|
                        <p class="text-gray-600 mb-6">Please wait while we complete your Gitea login...</p>
 | 
						|
                        
 | 
						|
                        <div class="flex justify-center mb-4">
 | 
						|
                            <div class="animate-spin rounded-full h-10 w-10 border-t-2 border-b-2 border-blue-500"></div>
 | 
						|
                        </div>
 | 
						|
                        
 | 
						|
                        <p class="text-sm text-gray-500">This should only take a few seconds</p>
 | 
						|
                    </div>
 | 
						|
                </div>
 | 
						|
            </div>
 | 
						|
        </div>
 | 
						|
    </div>
 | 
						|
    
 | 
						|
    <script>
 | 
						|
        function oauthCallback() {
 | 
						|
            return {
 | 
						|
                async handleOAuthCallback() {
 | 
						|
                    // Get the code from the URL parameters
 | 
						|
                    const urlParams = new URLSearchParams(window.location.search);
 | 
						|
                    const code = urlParams.get('code');
 | 
						|
                    
 | 
						|
                    if (!code) {
 | 
						|
                        this.showToast("Error", "No authorization code received from Gitea", "error");
 | 
						|
                        setTimeout(() => {
 | 
						|
                            window.location.href = '/admin';
 | 
						|
                        }, 2000);
 | 
						|
                        return;
 | 
						|
                    }
 | 
						|
 | 
						|
                    // Show processing status
 | 
						|
                    document.querySelector('h2').textContent = "Verifying Credentials";
 | 
						|
                    document.querySelector('p').textContent = "Checking your Gitea account information...";
 | 
						|
 | 
						|
                    try {
 | 
						|
                        // Send the code to our backend to exchange for a JWT token
 | 
						|
                        const response = await fetch('/admin/api/login/oauth/gitea/final', {
 | 
						|
                            method: 'POST',
 | 
						|
                            headers: {
 | 
						|
                                'Content-Type': 'application/json',
 | 
						|
                            },
 | 
						|
                            body: JSON.stringify({ code: code }),
 | 
						|
                        });
 | 
						|
 | 
						|
                        const data = await response.json();
 | 
						|
 | 
						|
                        if (response.ok && data.token) {
 | 
						|
                            // Store the token in localStorage
 | 
						|
                            localStorage.setItem('adminToken', data.token);
 | 
						|
                            
 | 
						|
                            // Show success status
 | 
						|
                            document.querySelector('h2').textContent = "Success!";
 | 
						|
                            document.querySelector('p').textContent = "You're being redirected to the dashboard...";
 | 
						|
                            
 | 
						|
                            const spinner = document.querySelector('.animate-spin');
 | 
						|
                            spinner.outerHTML = `
 | 
						|
                                <div class="mx-auto mb-4">
 | 
						|
                                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-12 w-12 text-green-500 mx-auto">
 | 
						|
                                        <path d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10z"></path>
 | 
						|
                                        <path d="m9 12 2 2 4-4"></path>
 | 
						|
                                    </svg>
 | 
						|
                                </div>
 | 
						|
                            `;
 | 
						|
                            
 | 
						|
                            this.showToast("Success", data.message || "Login successful");
 | 
						|
                            
 | 
						|
                            // Redirect to the admin dashboard after a short delay
 | 
						|
                            setTimeout(() => {
 | 
						|
                                window.location.href = '/admin';
 | 
						|
                            }, 1500);
 | 
						|
                        } else {
 | 
						|
                            // Show error status
 | 
						|
                            document.querySelector('h2').textContent = "Authentication Failed";
 | 
						|
                            document.querySelector('p').textContent = "There was an issue with your login. Please try again.";
 | 
						|
                            
 | 
						|
                            const spinner = document.querySelector('.animate-spin');
 | 
						|
                            spinner.outerHTML = `
 | 
						|
                                <div class="mx-auto mb-4">
 | 
						|
                                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-12 w-12 text-red-500 mx-auto">
 | 
						|
                                        <circle cx="12" cy="12" r="10"></circle>
 | 
						|
                                        <path d="M15 9l-6 6"></path>
 | 
						|
                                        <path d="M9 9l6 6"></path>
 | 
						|
                                    </svg>
 | 
						|
                                </div>
 | 
						|
                            `;
 | 
						|
                            
 | 
						|
                            this.showToast("Error", data.error || "Login failed", "error");
 | 
						|
                            setTimeout(() => {
 | 
						|
                                window.location.href = '/admin';
 | 
						|
                            }, 3000);
 | 
						|
                        }
 | 
						|
                    } catch (error) {
 | 
						|
                        // Show error status
 | 
						|
                        document.querySelector('h2').textContent = "Network Error";
 | 
						|
                        document.querySelector('p').textContent = "Unable to connect to authentication server. Please try again.";
 | 
						|
                        
 | 
						|
                        const spinner = document.querySelector('.animate-spin');
 | 
						|
                        spinner.outerHTML = `
 | 
						|
                            <div class="mx-auto mb-4">
 | 
						|
                                <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="h-12 w-12 text-red-500 mx-auto">
 | 
						|
                                    <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10"></path>
 | 
						|
                                    <path d="M9.84 9.78c-.97.42-1.81 1.2-2.34 2.23"></path>
 | 
						|
                                    <path d="M14.16 9.78c.97.42 1.81 1.2 2.34 2.23"></path>
 | 
						|
                                    <path d="M12 16c-.73 0-1.4-.15-2-.43"></path>
 | 
						|
                                    <path d="M12 16c.73 0 1.4-.15 2-.43"></path>
 | 
						|
                                </svg>
 | 
						|
                            </div>
 | 
						|
                        `;
 | 
						|
                        
 | 
						|
                        this.showToast("Error", "Network error. Please try again.", "error");
 | 
						|
                        setTimeout(() => {
 | 
						|
                            window.location.href = '/admin';
 | 
						|
                        }, 3000);
 | 
						|
                    }
 | 
						|
                },
 | 
						|
                
 | 
						|
                showToast(title, message, variant = 'default') {
 | 
						|
                    // Remove any existing toasts
 | 
						|
                    const existingToasts = document.querySelectorAll('.fixed.top-4');
 | 
						|
                    existingToasts.forEach(toast => toast.remove());
 | 
						|
                    
 | 
						|
                    // Simple toast notification
 | 
						|
                    const toast = document.createElement('div');
 | 
						|
                    toast.className = `fixed top-4 right-4 p-4 rounded-md shadow-lg z-50 max-w-md ${
 | 
						|
                        variant === 'error' ? 'bg-red-100 border border-red-300 text-red-700' : 'bg-green-100 border border-green-300 text-green-700'
 | 
						|
                    }`;
 | 
						|
                    toast.innerHTML = `
 | 
						|
                        <div class="flex justify-between items-start">
 | 
						|
                            <div>
 | 
						|
                                <div class="font-bold">${title}</div>
 | 
						|
                                <div>${message}</div>
 | 
						|
                            </div>
 | 
						|
                            <button class="ml-4 text-gray-600 hover:text-gray-900" onclick="this.parentElement.parentElement.remove()">×</button>
 | 
						|
                        </div>
 | 
						|
                    `;
 | 
						|
                    
 | 
						|
                    document.body.appendChild(toast);
 | 
						|
                    
 | 
						|
                    // Auto remove after 5 seconds
 | 
						|
                    setTimeout(() => {
 | 
						|
                        if (toast.parentElement) {
 | 
						|
                            toast.remove();
 | 
						|
                        }
 | 
						|
                    }, 5000);
 | 
						|
                }
 | 
						|
            };
 | 
						|
        }
 | 
						|
    </script>
 | 
						|
</body>
 | 
						|
</html> |