+{"html":"<!doctype html>\n<html>\n <head>\n <meta charset=\"utf-8\" />\n <script type=\"module\" src=\"https://unpkg.com/rapidoc/dist/rapidoc-min.js\"></script>\n <style>\n .metamask-button {\n background-color: #f6851b;\n color: white;\n border: none;\n padding: 8px 16px;\n border-radius: 4px;\n cursor: pointer;\n display: flex;\n align-items: center;\n font-weight: bold;\n }\n .metamask-button img {\n margin-right: 8px;\n height: 20px;\n }\n .metamask-container {\n padding: 16px;\n border: 1px solid #ddd;\n border-radius: 4px;\n margin-bottom: 16px;\n }\n .metamask-status {\n margin-top: 8px;\n font-size: 14px;\n }\n .connected {\n color: green;\n }\n .error {\n color: red;\n }\n </style>\n </head>\n\n <body>\n <rapi-doc\n spec-url=\"/openApiJson\"\n theme=\"light\"\n render-style=\"read\"\n show-header=\"false\"\n allow-authentication=\"true\"\n allow-server-selection=\"true\"\n >\n <div slot=\"auth\" class=\"metamask-container\">\n <h3>SIWE Authentication with Metamask</h3>\n <div>\n <button id=\"connectMetamask\" class=\"metamask-button\">\n <img src=\"https://images.ctfassets.net/clixtyxoaeas/4rnpEzy1ATWRKVBOLxZ1Fm/a74dc1eed36d23d7ea6030383a4d5163/MetaMask-icon-fox.svg\" alt=\"Metamask logo\" />\n Connect with Metamask\n </button>\n <div id=\"metamaskStatus\" class=\"metamask-status\"></div>\n </div>\n <div id=\"authDetails\" style=\"margin-top: 16px; display: none;\">\n <p>Connected Address: <span id=\"connectedAddress\"></span></p>\n <button id=\"generateSiwe\" class=\"metamask-button\" style=\"background-color: #0366d6;\">\n Generate SIWE Authentication\n </button>\n <div id=\"siweStatus\" class=\"metamask-status\"></div>\n </div>\n </div>\n\n <script>\n // Implementation of SIWE functionality\n // Generate a secure random nonce\n function generateNonce() {\n const array = new Uint8Array(16);\n window.crypto.getRandomValues(array);\n return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');\n }\n\n // EIP-55 checksum address - uses ethers loaded via CDN\n let ethersLoaded = null;\n async function getChecksummedAddress(address) {\n if (!ethersLoaded) {\n // Load ethers from CDN (cached after first load)\n ethersLoaded = import('https://esm.sh/ethers@5.7.2');\n }\n const { utils } = await ethersLoaded;\n return utils.getAddress(address);\n }\n\n // Simple SiweMessage class implementation\n class SiweMessage {\n constructor(params) {\n this.domain = params.domain;\n this.address = params.address;\n this.statement = params.statement;\n this.uri = params.uri;\n this.version = params.version;\n this.chainId = params.chainId;\n this.nonce = params.nonce;\n this.issuedAt = params.issuedAt;\n this.expirationTime = params.expirationTime;\n this.notBefore = params.notBefore;\n this.requestId = params.requestId;\n this.resources = params.resources;\n }\n\n prepareMessage() {\n const header = `${this.domain} wants you to sign in with your Ethereum account:`;\n const uriField = `URI: ${this.uri}`;\n let prefix = [header, this.address].join('\\n');\n const versionField = `Version: ${this.version}`;\n const chainField = `Chain ID: ${this.chainId || '1'}`;\n const nonceField = `Nonce: ${this.nonce}`;\n\n const suffixArray = [uriField, versionField, chainField, nonceField];\n\n if (this.issuedAt) {\n suffixArray.push(`Issued At: ${this.issuedAt}`);\n }\n\n if (this.expirationTime) {\n suffixArray.push(`Expiration Time: ${this.expirationTime}`);\n }\n\n if (this.notBefore) {\n suffixArray.push(`Not Before: ${this.notBefore}`);\n }\n\n if (this.requestId) {\n suffixArray.push(`Request ID: ${this.requestId}`);\n }\n\n if (this.resources) {\n suffixArray.push(\n [`Resources:`, ...this.resources.map(x => `- ${x}`)].join('\\n')\n );\n }\n\n const suffix = suffixArray.join('\\n');\n prefix = [prefix, this.statement].join('\\n\\n');\n if (this.statement !== undefined) {\n prefix += '\\n';\n }\n return [prefix, suffix].join('\\n');\n }\n }\n\n document.addEventListener('DOMContentLoaded', function() {\n const connectBtn = document.getElementById('connectMetamask');\n const statusEl = document.getElementById('metamaskStatus');\n const authDetailsEl = document.getElementById('authDetails');\n const addressEl = document.getElementById('connectedAddress');\n const generateSiweBtn = document.getElementById('generateSiwe');\n const siweStatusEl = document.getElementById('siweStatus');\n\n let currentAccount = null;\n\n // Check if Metamask is installed\n if (typeof window.ethereum === 'undefined') {\n statusEl.textContent = 'Metamask not detected. Please install Metamask extension.';\n statusEl.classList.add('error');\n connectBtn.disabled = true;\n return;\n }\n\n // Connect to Metamask\n connectBtn.addEventListener('click', async function() {\n try {\n statusEl.textContent = 'Connecting to Metamask...';\n\n // Request account access\n const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });\n currentAccount = accounts[0];\n\n // Update UI\n statusEl.textContent = 'Connected to Metamask';\n statusEl.classList.add('connected');\n addressEl.textContent = currentAccount;\n authDetailsEl.style.display = 'block';\n\n // Listen for account changes\n window.ethereum.on('accountsChanged', function (accounts) {\n if (accounts.length === 0) {\n // User disconnected\n currentAccount = null;\n statusEl.textContent = 'Disconnected from Metamask';\n statusEl.classList.remove('connected');\n authDetailsEl.style.display = 'none';\n } else {\n // Account changed\n currentAccount = accounts[0];\n addressEl.textContent = currentAccount;\n statusEl.textContent = 'Connected to Metamask';\n statusEl.classList.add('connected');\n }\n });\n\n } catch (error) {\n console.error(error);\n statusEl.textContent = 'Error connecting to Metamask: ' + error.message;\n statusEl.classList.add('error');\n }\n });\n\n // Generate SIWE message and signature\n generateSiweBtn.addEventListener('click', async function() {\n if (!currentAccount) {\n siweStatusEl.textContent = 'Please connect to Metamask first';\n siweStatusEl.classList.add('error');\n return;\n }\n\n try {\n siweStatusEl.textContent = 'Generating SIWE message...';\n\n // Create a SIWE message using the SiweMessage class\n const domain = window.location.host;\n const origin = window.location.origin;\n const statement = 'Sign in with Ethereum to authenticate with Vincent Registry API';\n\n // Generate a secure nonce\n const nonce = generateNonce();\n\n // Get checksummed address (EIP-55 required by SIWE)\n const checksummedAddress = await getChecksummedAddress(currentAccount);\n\n // Create a SiweMessage object\n const siweMessage = new SiweMessage({\n domain,\n address: checksummedAddress,\n statement,\n uri: origin,\n version: '1',\n chainId: 1,\n nonce,\n issuedAt: new Date().toISOString()\n });\n\n // Prepare the message for signing\n const message = siweMessage.prepareMessage();\n\n // Request signature from user\n siweStatusEl.textContent = 'Please sign the message in Metamask...';\n const signature = await window.ethereum.request({\n method: 'personal_sign',\n params: [message, currentAccount]\n });\n\n // Format the Authorization header value - Base64 encode the JSON payload\n const payload = JSON.stringify({ message, signature });\n const base64Payload = btoa(payload);\n const authHeader = `SIWE ${base64Payload}`;\n\n // Set the API key in RapiDoc\n const rapidoc = document.querySelector('rapi-doc');\n rapidoc.setApiKey('C', authHeader);\n\n siweStatusEl.textContent = 'SIWE authentication generated and applied!';\n siweStatusEl.classList.add('connected');\n\n } catch (error) {\n console.error(error);\n siweStatusEl.textContent = 'Error generating SIWE: ' + error.message;\n siweStatusEl.classList.add('error');\n }\n });\n });\n </script>\n </rapi-doc>\n </body>\n</html>\n"}
0 commit comments