Newer
Older
springboot-auth201 / src / main / resources / templates / register-client.html
@agalyaramadoss agalyaramadoss on 29 Nov 36 KB register page developed
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Register OAuth Client - Heaerie SSO</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: 'Roboto', 'Arial', sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            padding: 20px;
            display: flex;
            justify-content: center;
            align-items: center;
        }

        .container {
            background: white;
            border-radius: 12px;
            box-shadow: 0 20px 60px rgba(0,0,0,0.3);
            padding: 40px;
            width: 100%;
            max-width: 700px;
        }

        .header {
            text-align: center;
            margin-bottom: 30px;
        }

        .logo {
            font-size: 28px;
            font-weight: 500;
            color: #1a73e8;
            margin-bottom: 10px;
        }

        .logo-heaerie {
            color: #202124;
            font-weight: 400;
        }

        .logo-sso {
            color: #1a73e8;
            font-weight: 500;
        }

        .title {
            font-size: 24px;
            color: #202124;
            margin-bottom: 8px;
        }

        .subtitle {
            font-size: 14px;
            color: #5f6368;
        }

        .success-message {
            background-color: #e8f5e9;
            border: 1px solid #c3e6cb;
            border-radius: 8px;
            padding: 20px;
            margin-bottom: 24px;
            display: none;
        }

        .success-message.show {
            display: block;
        }

        .success-title {
            color: #1e8e3e;
            font-weight: 600;
            font-size: 16px;
            margin-bottom: 12px;
        }

        .client-info {
            background: #f8f9fa;
            border-radius: 6px;
            padding: 15px;
            margin-top: 10px;
        }

        .client-info-item {
            margin-bottom: 12px;
        }

        .client-info-label {
            font-weight: 600;
            color: #202124;
            font-size: 13px;
            margin-bottom: 4px;
        }

        .client-info-value {
            background: white;
            padding: 10px;
            border-radius: 4px;
            font-family: 'Courier New', monospace;
            font-size: 13px;
            color: #1a73e8;
            word-break: break-all;
            border: 1px solid #dadce0;
        }

        .copy-btn {
            background: #1a73e8;
            color: white;
            border: none;
            padding: 6px 12px;
            border-radius: 4px;
            font-size: 12px;
            cursor: pointer;
            margin-top: 8px;
        }

        .copy-btn:hover {
            background: #1765cc;
        }

        .form-section {
            margin-bottom: 24px;
        }

        .section-title {
            font-size: 16px;
            color: #202124;
            font-weight: 600;
            margin-bottom: 16px;
            padding-bottom: 8px;
            border-bottom: 2px solid #f0f0f0;
        }

        .form-group {
            margin-bottom: 20px;
        }

        .form-group label {
            display: block;
            font-size: 14px;
            color: #202124;
            margin-bottom: 6px;
            font-weight: 500;
        }

        .form-group label .required {
            color: #d93025;
        }

        .form-group input,
        .form-group select,
        .form-group textarea {
            width: 100%;
            padding: 12px;
            border: 1px solid #dadce0;
            border-radius: 4px;
            font-size: 14px;
            color: #202124;
            transition: border-color 0.2s;
            font-family: inherit;
        }

        .form-group textarea {
            resize: vertical;
            min-height: 80px;
        }

        .form-group input:focus,
        .form-group select:focus,
        .form-group textarea:focus {
            outline: none;
            border-color: #1a73e8;
            box-shadow: 0 0 0 2px rgba(26, 115, 232, 0.1);
        }

        .help-text {
            font-size: 12px;
            color: #5f6368;
            margin-top: 4px;
        }

        .checkbox-group {
            margin-bottom: 12px;
        }

        .checkbox-group label {
            display: flex;
            align-items: center;
            font-weight: 400;
            cursor: pointer;
        }

        .checkbox-group input[type="checkbox"] {
            width: 18px;
            height: 18px;
            margin-right: 8px;
            cursor: pointer;
            accent-color: #1a73e8;
        }

        .radio-group {
            display: flex;
            gap: 24px;
            margin-top: 8px;
        }

        .radio-option {
            display: flex;
            align-items: center;
        }

        .radio-option input[type="radio"] {
            width: 18px;
            height: 18px;
            margin-right: 8px;
            cursor: pointer;
            accent-color: #1a73e8;
        }

        .radio-option label {
            margin: 0;
            cursor: pointer;
        }

        .btn-container {
            display: flex;
            gap: 12px;
            margin-top: 32px;
            justify-content: flex-end;
        }

        .btn {
            padding: 12px 32px;
            border: none;
            border-radius: 4px;
            font-size: 14px;
            font-weight: 500;
            cursor: pointer;
            transition: all 0.2s;
        }

        .btn-secondary {
            background-color: white;
            color: #1a73e8;
            border: 1px solid #dadce0;
        }

        .btn-secondary:hover {
            background-color: #f8f9fa;
        }

        .btn-primary {
            background-color: #1a73e8;
            color: white;
        }

        .btn-primary:hover {
            background-color: #1765cc;
            box-shadow: 0 2px 4px rgba(26,115,232,0.4);
        }

        .btn-primary:disabled {
            background-color: #dadce0;
            cursor: not-allowed;
        }

        .info-box {
            background-color: #e8f4fd;
            border-left: 4px solid #1a73e8;
            padding: 16px;
            border-radius: 4px;
            margin-bottom: 24px;
        }

        .info-box-title {
            font-weight: 600;
            color: #1a73e8;
            margin-bottom: 8px;
        }

        .info-box-text {
            font-size: 13px;
            color: #202124;
            line-height: 1.5;
        }

        .error-message {
            background-color: #fce8e6;
            border: 1px solid #f5c6cb;
            border-radius: 8px;
            padding: 16px;
            margin-bottom: 24px;
            display: none;
            color: #d93025;
        }

        .error-message.show {
            display: block;
        }

        .uri-input-group {
            position: relative;
        }

        .uri-list {
            margin-top: 12px;
        }

        .uri-item {
            background: #f8f9fa;
            border: 1px solid #dadce0;
            border-radius: 4px;
            padding: 10px 12px;
            margin-bottom: 8px;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }

        .uri-text {
            font-size: 13px;
            color: #202124;
            word-break: break-all;
            flex: 1;
        }

        .remove-uri-btn {
            background: #d93025;
            color: white;
            border: none;
            border-radius: 4px;
            padding: 4px 12px;
            font-size: 12px;
            cursor: pointer;
            margin-left: 12px;
            white-space: nowrap;
        }

        .remove-uri-btn:hover {
            background: #c5221f;
        }

        .add-uri-btn {
            background: #f8f9fa;
            color: #1a73e8;
            border: 1px solid #dadce0;
            border-radius: 4px;
            padding: 8px 16px;
            font-size: 13px;
            cursor: pointer;
            margin-top: 8px;
            width: 100%;
        }

        .add-uri-btn:hover {
            background: #e8f0fe;
        }

        .validation-error {
            color: #d93025;
            font-size: 12px;
            margin-top: 4px;
            display: none;
        }

        .validation-error.show {
            display: block;
        }

        .char-counter {
            font-size: 12px;
            color: #5f6368;
            text-align: right;
            margin-top: 4px;
        }

        .example-link {
            font-size: 12px;
            color: #1a73e8;
            cursor: pointer;
            text-decoration: underline;
            margin-left: 8px;
        }

        .example-link:hover {
            color: #1765cc;
        }

        .token-lifetime-group {
            display: flex;
            gap: 12px;
            align-items: flex-end;
        }

        .token-lifetime-group .form-group {
            flex: 1;
            margin-bottom: 0;
        }

        .advanced-section {
            background: #f8f9fa;
            border-radius: 8px;
            padding: 20px;
            margin-top: 16px;
        }

        .toggle-advanced {
            background: none;
            border: none;
            color: #1a73e8;
            font-size: 14px;
            cursor: pointer;
            display: flex;
            align-items: center;
            gap: 8px;
            padding: 0;
            font-weight: 500;
        }

        .toggle-advanced:hover {
            text-decoration: underline;
        }

        .toggle-icon {
            transition: transform 0.3s;
        }

        .toggle-icon.open {
            transform: rotate(180deg);
        }

        .loading-overlay {
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background: rgba(0, 0, 0, 0.5);
            display: none;
            justify-content: center;
            align-items: center;
            z-index: 9999;
        }

        .loading-overlay.show {
            display: flex;
        }

        .spinner {
            border: 4px solid #f3f3f3;
            border-top: 4px solid #1a73e8;
            border-radius: 50%;
            width: 50px;
            height: 50px;
            animation: spin 1s linear infinite;
        }

        @keyframes spin {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
        }

        @media (max-width: 640px) {
            .container {
                padding: 24px;
            }

            .btn-container {
                flex-direction: column-reverse;
            }

            .btn {
                width: 100%;
            }
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <div class="logo">
                <span class="logo-heaerie">heaerie</span>
                <span class="logo-sso"> sso</span>
            </div>
            <h1 class="title">Register OAuth 2.1 Client</h1>
            <p class="subtitle">Create a new OAuth client application</p>
        </div>

        <div class="error-message" id="errorMessage">
            <strong>Registration Failed</strong>
            <div id="errorText" style="margin-top: 8px;"></div>
        </div>

        <div class="success-message" id="successMessage">
            <div class="success-title">✓ Client Registered Successfully!</div>
            <div class="client-info">
                <div class="client-info-item">
                    <div class="client-info-label">Client ID</div>
                    <div class="client-info-value" id="generatedClientId">client-id-here</div>
                    <button class="copy-btn" onclick="copyToClipboard('generatedClientId')">Copy</button>
                </div>
                <div class="client-info-item" id="clientSecretContainer" style="display:none;">
                    <div class="client-info-label">Client Secret</div>
                    <div class="client-info-value" id="generatedClientSecret">client-secret-here</div>
                    <button class="copy-btn" onclick="copyToClipboard('generatedClientSecret')">Copy</button>
                    <div class="help-text" style="margin-top: 8px; color: #d93025;">
                        ⚠️ Save this secret securely. It won't be shown again.
                    </div>
                </div>
                <div class="client-info-item">
                    <div class="client-info-label">Configuration Summary</div>
                    <div id="configSummary" style="margin-top: 8px; font-size: 13px; line-height: 1.8;">
                        <!-- Will be populated dynamically -->
                    </div>
                </div>
            </div>
            <div class="btn-container" style="margin-top: 24px;">
                <button class="btn btn-secondary" onclick="downloadConfig()">
                    📥 Download Config
                </button>
                <button class="btn btn-primary" onclick="location.reload()">
                    Register Another Client
                </button>
            </div>
        </div>

        <div class="info-box">
            <div class="info-box-title">📋 Before You Begin</div>
            <div class="info-box-text">
                Configure your OAuth 2.1 client settings below. Public clients (SPAs, mobile apps) don't require a client secret. 
                Confidential clients (server-side apps) will receive a client secret after registration.
            </div>
        </div>

        <form id="registerForm">

            <div class="form-section">
                <div class="section-title">Basic Information</div>

                <div class="form-group">
                    <label for="clientName">
                        Client Name <span class="required">*</span>
                    </label>
                    <input 
                        type="text" 
                        id="clientName" 
                        name="clientName" 
                        placeholder="My Application"
                        required
                        autofocus
                        maxlength="100"
                        oninput="updateCharCounter('clientName', 100)"
                    />
                    <div class="help-text">
                        A human-readable name for your application
                        <span class="example-link" onclick="fillExample('spa')">SPA Example</span>
                        <span class="example-link" onclick="fillExample('mobile')">Mobile Example</span>
                        <span class="example-link" onclick="fillExample('server')">Server Example</span>
                    </div>
                    <div class="char-counter" id="clientNameCounter">0 / 100</div>
                    <div class="validation-error" id="clientNameError">Please enter a valid client name</div>
                </div>

                <div class="form-group">
                    <label for="clientType">
                        Client Type <span class="required">*</span>
                    </label>
                    <div class="radio-group">
                        <div class="radio-option">
                            <input 
                                type="radio" 
                                id="publicClient" 
                                name="clientType" 
                                value="public" 
                                checked
                                onchange="updateClientTypeFields()"
                            />
                            <label for="publicClient">Public Client (SPA, Mobile)</label>
                        </div>
                        <div class="radio-option">
                            <input 
                                type="radio" 
                                id="confidentialClient" 
                                name="clientType" 
                                value="confidential"
                                onchange="updateClientTypeFields()"
                            />
                            <label for="confidentialClient">Confidential Client (Server)</label>
                        </div>
                    </div>
                    <div class="help-text">Public clients cannot securely store secrets</div>
                </div>

                <div class="form-group">
                    <label for="description">
                        Description
                    </label>
                    <textarea 
                        id="description" 
                        name="description"
                        placeholder="Brief description of your application"
                    ></textarea>
                </div>
            </div>

            <div class="form-section">
                <div class="section-title">OAuth Configuration</div>

                <div class="form-group">
                    <label for="redirectUris">
                        Redirect URIs <span class="required">*</span>
                    </label>
                    <div class="uri-input-group">
                        <input 
                            type="url" 
                            id="redirectUriInput" 
                            placeholder="http://localhost:3000/callback"
                            onkeypress="handleUriKeyPress(event, 'redirect')"
                        />
                        <button type="button" class="add-uri-btn" onclick="addUri('redirect')">
                            + Add Redirect URI
                        </button>
                    </div>
                    <div class="uri-list" id="redirectUriList"></div>
                    <div class="help-text">Where users will be redirected after authorization</div>
                    <div class="validation-error" id="redirectUriError">Please add at least one valid redirect URI</div>
                </div>

                <div class="form-group">
                    <label for="logoutRedirectUris">
                        Post-Logout Redirect URIs
                    </label>
                    <div class="uri-input-group">
                        <input 
                            type="url" 
                            id="logoutRedirectUriInput" 
                            placeholder="http://localhost:3000/"
                            onkeypress="handleUriKeyPress(event, 'logout')"
                        />
                        <button type="button" class="add-uri-btn" onclick="addUri('logout')">
                            + Add Logout URI
                        </button>
                    </div>
                    <div class="uri-list" id="logoutRedirectUriList"></div>
                    <div class="help-text">Where users will be redirected after logout (optional)</div>
                </div>

                <div class="form-group">
                    <label>Grant Types <span class="required">*</span></label>
                    <div class="checkbox-group">
                        <label>
                            <input type="checkbox" name="grantTypes" value="authorization_code" checked />
                            <span>Authorization Code (Recommended)</span>
                        </label>
                    </div>
                    <div class="checkbox-group">
                        <label>
                            <input type="checkbox" name="grantTypes" value="refresh_token" checked />
                            <span>Refresh Token</span>
                        </label>
                    </div>
                    <div class="checkbox-group" id="clientCredentialsOption" style="display:none;">
                        <label>
                            <input type="checkbox" name="grantTypes" value="client_credentials" />
                            <span>Client Credentials (Server-to-Server)</span>
                        </label>
                    </div>
                </div>

                <div class="form-group">
                    <label>Scopes <span class="required">*</span></label>
                    <div class="checkbox-group">
                        <label>
                            <input type="checkbox" name="scopes" value="profile" checked />
                            <span>profile</span>
                        </label>
                    </div>
                    <div class="checkbox-group">
                        <label>
                            <input type="checkbox" name="scopes" value="email" />
                            <span>email</span>
                        </label>
                    </div>
                    <div class="checkbox-group">
                        <label>
                            <input type="checkbox" name="scopes" value="read" checked />
                            <span>read</span>
                        </label>
                    </div>
                    <div class="checkbox-group">
                        <label>
                            <input type="checkbox" name="scopes" value="write" />
                            <span>write</span>
                        </label>
                    </div>
                </div>
            </div>

            <div class="form-section">
                <div class="section-title">Security Settings</div>

                <div class="form-group">
                    <label>
                        <input type="checkbox" name="requireConsent" id="requireConsent" checked />
                        Require User Consent
                    </label>
                    <div class="help-text">Users must approve access to their data</div>
                </div>

                <div class="form-group">
                    <label>
                        <input type="checkbox" name="requirePkce" id="requirePkce" checked />
                        Require PKCE (OAuth 2.1)
                    </label>
                    <div class="help-text">Proof Key for Code Exchange - highly recommended for security</div>
                </div>

                <button type="button" class="toggle-advanced" onclick="toggleAdvanced()">
                    <span class="toggle-icon" id="toggleIcon">▼</span>
                    Advanced Settings
                </button>

                <div class="advanced-section" id="advancedSection" style="display: none;">
                    <div class="form-group">
                        <label for="accessTokenValidity">Access Token Lifetime</label>
                        <div class="token-lifetime-group">
                            <div class="form-group">
                                <input 
                                    type="number" 
                                    id="accessTokenValidity" 
                                    name="accessTokenValidity"
                                    value="3600"
                                    min="300"
                                    max="86400"
                                />
                                <div class="help-text">Seconds (default: 3600 = 1 hour)</div>
                            </div>
                        </div>
                    </div>

                    <div class="form-group">
                        <label for="refreshTokenValidity">Refresh Token Lifetime</label>
                        <div class="token-lifetime-group">
                            <div class="form-group">
                                <input 
                                    type="number" 
                                    id="refreshTokenValidity" 
                                    name="refreshTokenValidity"
                                    value="2592000"
                                    min="3600"
                                    max="31536000"
                                />
                                <div class="help-text">Seconds (default: 2592000 = 30 days)</div>
                            </div>
                        </div>
                    </div>

                    <div class="form-group">
                        <label>
                            <input type="checkbox" name="reuseRefreshTokens" id="reuseRefreshTokens" />
                            Reuse Refresh Tokens
                        </label>
                        <div class="help-text">If unchecked, refresh tokens are rotated (OAuth 2.1 recommended)</div>
                    </div>
                </div>
            </div>

            <div class="btn-container">
                <button type="button" class="btn btn-secondary" onclick="window.location.href='/'">
                    Cancel
                </button>
                <button type="submit" class="btn btn-primary">
                    Register Client
                </button>
            </div>
        </form>
    </div>

    <div class="loading-overlay" id="loadingOverlay">
        <div class="spinner"></div>
    </div>

    <script>
        // Store URIs
        const redirectUris = [];
        const logoutUris = [];

        function updateClientTypeFields() {
            const isConfidential = document.getElementById('confidentialClient').checked;
            document.getElementById('clientCredentialsOption').style.display = 
                isConfidential ? 'block' : 'none';
        }

        function updateCharCounter(fieldId, maxLength) {
            const field = document.getElementById(fieldId);
            const counter = document.getElementById(fieldId + 'Counter');
            if (counter) {
                counter.textContent = `${field.value.length} / ${maxLength}`;
            }
        }

        function toggleAdvanced() {
            const section = document.getElementById('advancedSection');
            const icon = document.getElementById('toggleIcon');
            if (section.style.display === 'none') {
                section.style.display = 'block';
                icon.classList.add('open');
            } else {
                section.style.display = 'none';
                icon.classList.remove('open');
            }
        }

        function handleUriKeyPress(event, type) {
            if (event.key === 'Enter') {
                event.preventDefault();
                addUri(type);
            }
        }

        function addUri(type) {
            const inputId = type === 'redirect' ? 'redirectUriInput' : 'logoutRedirectUriInput';
            const input = document.getElementById(inputId);
            const uri = input.value.trim();
            
            if (!uri) return;
            
            // Validate URI
            try {
                new URL(uri);
            } catch (e) {
                alert('Please enter a valid URL');
                return;
            }

            const uriArray = type === 'redirect' ? redirectUris : logoutUris;
            
            if (uriArray.includes(uri)) {
                alert('This URI has already been added');
                return;
            }

            uriArray.push(uri);
            input.value = '';
            renderUris(type);
        }

        function removeUri(type, index) {
            const uriArray = type === 'redirect' ? redirectUris : logoutUris;
            uriArray.splice(index, 1);
            renderUris(type);
        }

        function renderUris(type) {
            const listId = type === 'redirect' ? 'redirectUriList' : 'logoutRedirectUriList';
            const list = document.getElementById(listId);
            const uriArray = type === 'redirect' ? redirectUris : logoutUris;
            
            list.innerHTML = uriArray.map((uri, index) => `
                <div class="uri-item">
                    <span class="uri-text">${uri}</span>
                    <button type="button" class="remove-uri-btn" onclick="removeUri('${type}', ${index})">
                        Remove
                    </button>
                </div>
            `).join('');
        }

        function fillExample(type) {
            const examples = {
                spa: {
                    name: 'My React App',
                    type: 'public',
                    description: 'Single Page Application built with React',
                    redirectUris: ['http://localhost:3000/callback', 'https://myapp.com/callback'],
                    logoutUris: ['http://localhost:3000/', 'https://myapp.com/'],
                    scopes: ['profile', 'read']
                },
                mobile: {
                    name: 'My Mobile App',
                    type: 'public',
                    description: 'Native mobile application for iOS and Android',
                    redirectUris: ['myapp://callback', 'https://myapp.com/mobile/callback'],
                    logoutUris: ['myapp://logout'],
                    scopes: ['profile', 'read', 'write']
                },
                server: {
                    name: 'My Server App',
                    type: 'confidential',
                    description: 'Server-side application with secure backend',
                    redirectUris: ['https://myapp.com/oauth/callback'],
                    logoutUris: ['https://myapp.com/'],
                    scopes: ['profile', 'read', 'write']
                }
            };

            const example = examples[type];
            document.getElementById('clientName').value = example.name;
            document.getElementById('description').value = example.description;
            
            if (example.type === 'public') {
                document.getElementById('publicClient').checked = true;
            } else {
                document.getElementById('confidentialClient').checked = true;
            }
            updateClientTypeFields();

            // Clear and add URIs
            redirectUris.length = 0;
            logoutUris.length = 0;
            redirectUris.push(...example.redirectUris);
            logoutUris.push(...example.logoutUris);
            renderUris('redirect');
            renderUris('logout');

            // Check scopes
            document.querySelectorAll('input[name="scopes"]').forEach(input => {
                input.checked = example.scopes.includes(input.value);
            });

            updateCharCounter('clientName', 100);
        }

        function copyToClipboard(elementId) {
            const element = document.getElementById(elementId);
            const text = element.textContent;
            
            navigator.clipboard.writeText(text).then(() => {
                const btn = element.nextElementSibling;
                const originalText = btn.textContent;
                btn.textContent = '✓ Copied!';
                setTimeout(() => {
                    btn.textContent = originalText;
                }, 2000);
            });
        }

        function validateForm() {
            let isValid = true;
            
            // Validate client name
            const clientName = document.getElementById('clientName').value.trim();
            if (!clientName) {
                document.getElementById('clientNameError').classList.add('show');
                isValid = false;
            } else {
                document.getElementById('clientNameError').classList.remove('show');
            }

            // Validate redirect URIs
            if (redirectUris.length === 0) {
                document.getElementById('redirectUriError').classList.add('show');
                isValid = false;
            } else {
                document.getElementById('redirectUriError').classList.remove('show');
            }

            return isValid;
        }

        function downloadConfig() {
            const config = {
                clientId: document.getElementById('generatedClientId').textContent,
                clientSecret: document.getElementById('generatedClientSecret').textContent || null,
                clientType: document.querySelector('input[name="clientType"]:checked').value,
                redirectUris: redirectUris,
                logoutRedirectUris: logoutUris,
                authorizationEndpoint: window.location.origin + '/oauth2/authorize',
                tokenEndpoint: window.location.origin + '/oauth2/token',
                userInfoEndpoint: window.location.origin + '/userinfo',
                jwksUri: window.location.origin + '/oauth2/jwks',
                issuer: window.location.origin
            };

            const blob = new Blob([JSON.stringify(config, null, 2)], { type: 'application/json' });
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = `oauth-client-${config.clientId}.json`;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            URL.revokeObjectURL(url);
        }

        // Handle form submission
        document.getElementById('registerForm').addEventListener('submit', async (e) => {
            e.preventDefault();
            
            if (!validateForm()) {
                return;
            }

            document.getElementById('loadingOverlay').classList.add('show');
            document.getElementById('errorMessage').classList.remove('show');
            
            const formData = new FormData(e.target);
            const data = {
                clientName: formData.get('clientName'),
                clientType: formData.get('clientType'),
                description: formData.get('description'),
                redirectUris: redirectUris,
                logoutRedirectUris: logoutUris,
                grantTypes: formData.getAll('grantTypes'),
                scopes: formData.getAll('scopes'),
                requireConsent: formData.get('requireConsent') === 'on',
                requirePkce: formData.get('requirePkce') === 'on',
                accessTokenValidity: parseInt(formData.get('accessTokenValidity')) || 3600,
                refreshTokenValidity: parseInt(formData.get('refreshTokenValidity')) || 2592000,
                reuseRefreshTokens: formData.get('reuseRefreshTokens') === 'on'
            };

            try {
                const response = await fetch('/oauth/register', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json'
                    },
                    body: JSON.stringify(data)
                });

                document.getElementById('loadingOverlay').classList.remove('show');

                if (response.ok) {
                    const result = await response.json();
                    
                    // Show success message
                    document.getElementById('generatedClientId').textContent = result.clientId;
                    
                    if (result.clientSecret) {
                        document.getElementById('generatedClientSecret').textContent = result.clientSecret;
                        document.getElementById('clientSecretContainer').style.display = 'block';
                    } else {
                        document.getElementById('clientSecretContainer').style.display = 'none';
                    }

                    // Build configuration summary
                    const summary = `
                        <div><strong>Type:</strong> ${data.clientType === 'public' ? 'Public Client' : 'Confidential Client'}</div>
                        <div><strong>Grant Types:</strong> ${data.grantTypes.join(', ')}</div>
                        <div><strong>Scopes:</strong> ${data.scopes.join(', ')}</div>
                        <div><strong>PKCE Required:</strong> ${data.requirePkce ? 'Yes' : 'No'}</div>
                        <div><strong>User Consent:</strong> ${data.requireConsent ? 'Required' : 'Not Required'}</div>
                        <div><strong>Redirect URIs:</strong> ${redirectUris.length}</div>
                    `;
                    document.getElementById('configSummary').innerHTML = summary;
                    
                    document.getElementById('successMessage').classList.add('show');
                    document.getElementById('registerForm').style.display = 'none';
                    document.querySelector('.info-box').style.display = 'none';
                    
                    // Scroll to top
                    window.scrollTo({ top: 0, behavior: 'smooth' });
                } else {
                    const error = await response.text();
                    document.getElementById('errorText').textContent = error || 'Registration failed. Please try again.';
                    document.getElementById('errorMessage').classList.add('show');
                    window.scrollTo({ top: 0, behavior: 'smooth' });
                }
            } catch (error) {
                console.error('Error:', error);
                document.getElementById('loadingOverlay').classList.remove('show');
                document.getElementById('errorText').textContent = 'An error occurred. Please check your connection and try again.';
                document.getElementById('errorMessage').classList.add('show');
                window.scrollTo({ top: 0, behavior: 'smooth' });
            }
        });

        // Initialize
        updateClientTypeFields();
    </script>
</body>
</html>