From 1c8a2d3fa0d8767a3465beb26ffebce97d9ba106 Mon Sep 17 00:00:00 2001 From: Jason Stirnaman Date: Tue, 16 Dec 2025 16:36:38 -0600 Subject: [PATCH] feat(api): Add custom auth input component for operation pages - Create api-auth-input.ts TypeScript component - Store credentials in sessionStorage (secure, cleared on tab close) - Register component in main.js - Add auth form styling with dark theme support - Add component wrapper to rapidoc-mini.html template - Fix SCSS dark theme (use CSS selectors, not non-existent mixin) --- assets/js/components/api-auth-input.ts | 128 ++++++++++++++++++ assets/js/main.js | 2 + .../styles/layouts/_api-security-schemes.scss | 87 ++++++++++++ layouts/partials/api/rapidoc-mini.html | 6 + 4 files changed, 223 insertions(+) create mode 100644 assets/js/components/api-auth-input.ts diff --git a/assets/js/components/api-auth-input.ts b/assets/js/components/api-auth-input.ts new file mode 100644 index 000000000..aa5f0b0e2 --- /dev/null +++ b/assets/js/components/api-auth-input.ts @@ -0,0 +1,128 @@ +/** + * API Auth Input Component + * + * Provides credential input fields for API operations. + * Stores credentials in sessionStorage for "Try it" requests. + */ + +interface ComponentOptions { + component: HTMLElement; +} + +interface AuthCredentials { + bearer?: string; + basic?: { username: string; password: string }; + apiKey?: string; +} + +const STORAGE_KEY = 'influxdb_api_credentials'; + +/** + * Get stored credentials from sessionStorage + */ +function getCredentials(): AuthCredentials { + try { + const stored = sessionStorage.getItem(STORAGE_KEY); + return stored ? JSON.parse(stored) : {}; + } catch { + return {}; + } +} + +/** + * Store credentials in sessionStorage + */ +function setCredentials(credentials: AuthCredentials): void { + sessionStorage.setItem(STORAGE_KEY, JSON.stringify(credentials)); +} + +/** + * Create the auth input form + */ +function createAuthForm(schemes: string[]): HTMLElement { + const form = document.createElement('div'); + form.className = 'api-auth-form'; + form.innerHTML = ` +

Authentication

+

Enter credentials to use with "Try it" requests.

+ ${ + schemes.includes('bearer') + ? ` +
+ + +
+ ` + : '' + } + ${ + schemes.includes('basic') + ? ` +
+ + +
+
+ + +
+ ` + : '' + } + + `; + return form; +} + +/** + * Initialize the auth input component + */ +export default function ApiAuthInput({ component }: ComponentOptions): void { + const schemesAttr = component.dataset.schemes || 'bearer'; + const schemes = schemesAttr.split(',').map((s) => s.trim().toLowerCase()); + + const form = createAuthForm(schemes); + component.appendChild(form); + + // Load existing credentials + const credentials = getCredentials(); + const bearerInput = form.querySelector('#auth-bearer'); + const usernameInput = form.querySelector('#auth-username'); + const passwordInput = form.querySelector('#auth-password'); + + if (bearerInput && credentials.bearer) { + bearerInput.value = credentials.bearer; + } + if (usernameInput && credentials.basic?.username) { + usernameInput.value = credentials.basic.username; + } + if (passwordInput && credentials.basic?.password) { + passwordInput.value = credentials.basic.password; + } + + // Save button handler + const saveBtn = form.querySelector('.auth-save'); + saveBtn?.addEventListener('click', () => { + const newCredentials: AuthCredentials = {}; + + if (bearerInput?.value) { + newCredentials.bearer = bearerInput.value; + } + if (usernameInput?.value || passwordInput?.value) { + newCredentials.basic = { + username: usernameInput?.value || '', + password: passwordInput?.value || '', + }; + } + + setCredentials(newCredentials); + + // Notify RapiDoc of new credentials + const rapiDoc = document.querySelector('rapi-doc'); + if (rapiDoc && 'setApiKey' in rapiDoc) { + (rapiDoc as any).setApiKey(newCredentials.bearer || ''); + } + + alert('Credentials saved for this session'); + }); +} diff --git a/assets/js/main.js b/assets/js/main.js index 77fda067a..5cfb75098 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -46,6 +46,7 @@ import SidebarSearch from './components/sidebar-search.js'; import { SidebarToggle } from './sidebar-toggle.js'; import Theme from './theme.js'; import ThemeSwitch from './theme-switch.js'; +import ApiAuthInput from './components/api-auth-input.ts'; import ApiRapiDoc from './components/api-rapidoc.ts'; import ApiToc from './components/api-toc.ts'; import RapiDocMini from './components/rapidoc-mini.ts'; @@ -80,6 +81,7 @@ const componentRegistry = { 'sidebar-toggle': SidebarToggle, theme: Theme, 'theme-switch': ThemeSwitch, + 'api-auth-input': ApiAuthInput, 'api-rapidoc': ApiRapiDoc, 'api-toc': ApiToc, 'rapidoc-mini': RapiDocMini, diff --git a/assets/styles/layouts/_api-security-schemes.scss b/assets/styles/layouts/_api-security-schemes.scss index 16380f425..ee0860d5c 100644 --- a/assets/styles/layouts/_api-security-schemes.scss +++ b/assets/styles/layouts/_api-security-schemes.scss @@ -70,3 +70,90 @@ } } } + +//////////////////////////////////////////////////////////////////////////////// +// API Auth Form - Custom credential input for operation pages +//////////////////////////////////////////////////////////////////////////////// + +.api-auth-form { + margin-bottom: 1.5rem; + padding: 1rem; + background: $g3-castle; + border: 1px solid $g5-pepper; + border-radius: 4px; + + h4 { + margin: 0 0 0.5rem 0; + } + + .auth-form-description { + margin: 0 0 1rem 0; + font-size: 0.9rem; + color: $g9-mountain; + } + + .auth-field { + margin-bottom: 1rem; + + label { + display: block; + margin-bottom: 0.25rem; + font-weight: 600; + font-size: 0.9rem; + } + + input { + width: 100%; + padding: 0.5rem; + border: 1px solid $g5-pepper; + border-radius: 3px; + font-family: inherit; + } + } + + .auth-save { + margin-top: 0.5rem; + } +} + +// Dark theme overrides - using CSS selectors (no mixin in this codebase) +[data-theme="dark"], +html:has(link[title="dark-theme"]:not([disabled])) { + .api-security-schemes { + border-top-color: $grey25; + + .security-scheme { + background: $grey15; + border-color: $grey25; + } + + .scheme-details { + dt { + color: $g15-platinum; + } + + dd code { + background: $grey20; + } + } + + .scheme-description { + border-top-color: $grey25; + } + } + + .api-auth-form { + background: $grey15; + border-color: $grey25; + + .auth-form-description { + color: $g15-platinum; + } + + .auth-field input { + background: $grey20; + border-color: $grey25; + color: $g20-white; + } + } +} diff --git a/layouts/partials/api/rapidoc-mini.html b/layouts/partials/api/rapidoc-mini.html index 2d7251077..20831f8cd 100644 --- a/layouts/partials/api/rapidoc-mini.html +++ b/layouts/partials/api/rapidoc-mini.html @@ -22,6 +22,12 @@ {{ end }} +{{/* Auth input component */}} +
+
+ {{/* Component container - TypeScript handles initialization */}}