1<!DOCTYPE html><html lang="en" class="h-full"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"><title>Sign In - ClojureStream</title><meta name="description" content="Sign in to your ClojureStream account to access courses, workshops, and community."><meta name="author" content="ClojureStream"><meta name="theme-color" content="#111827"><link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"><link rel="apple-touch-icon" href="/apple-touch-icon.png"><link rel="manifest" href="/site.webmanifest"><meta name="msapplication-TileColor" content="#00aba9"><meta name="application-name" content="Sign In - ClojureStream"><meta property="og:image" content="https://clojure.stream/img/og-1200x630.png"><meta property="og:image:secure_url" content="https://clojure.stream/img/og-1200x630.png"><meta property="og:image:type" content="image/png"><meta property="og:image:width" content="1200"><meta property="og:image:height" content="630"><meta property="og:locale" content="en_US"><meta property="og:title" content="Sign In - ClojureStream"><meta property="og:type" content="website"><meta property="og:url" content="https://clojure.stream/auth/sign-in"><meta property="og:site_name" content="Sign In - ClojureStream"><meta property="og:description" content="Sign in to your ClojureStream account to access courses, workshops, and community."><meta name="twitter:card" content="summary_large_image"><meta name="twitter:site" content="@clojurestream"><meta name="twitter:creator" content="@clojurestream"><meta name="twitter:title" content="Sign In - ClojureStream"><meta name="twitter:description" content="Sign in to your ClojureStream account to access courses, workshops, and community."><meta name="twitter:image" content="https://clojure.stream/img/og-1200x630.png"><meta name="twitter:url" content="https://clojure.stream/auth/sign-in"><link rel="stylesheet" href="/fonts/inter/inter.css"><link rel="stylesheet" href="/css/main.css?v=1789434938916">
1<script src="https://cdn.jsdelivr.net/npm/@tailwindplus/elements@1" type="module"></script>
1<script type="text/javascript" nonce="ETH3yFHi3E+WqkhaL6Si5Q==">document.addEventListener('click',function(e){ 2 var toggle=e.target.closest('[data-twe-dropdown-toggle-ref]'); 3 if(toggle){ 4 e.stopPropagation(); 5 var dd=toggle.closest('[data-twe-dropdown-ref]'); 6 if(!dd)return; 7 var menu=dd.querySelector('[data-twe-dropdown-menu-ref]'); 8 if(!menu)return; 9 var open=menu.hasAttribute('data-twe-dropdown-show'); 10 document.querySelectorAll('[data-twe-dropdown-menu-ref][data-twe-dropdown-show]').forEach(function(m){ 11 m.removeAttribute('data-twe-dropdown-show');m.classList.add('hidden'); 12 }); 13 if(!open){menu.setAttribute('data-twe-dropdown-show','');menu.classList.remove('hidden');} 14 return; 15 } 16 document.querySelectorAll('[data-twe-dropdown-menu-ref][data-twe-dropdown-show]').forEach(function(m){ 17 m.removeAttribute('data-twe-dropdown-show');m.classList.add('hidden'); 18 }); 19 var btn=e.target.closest('[data-action]'); 20 if(btn){ 21 var action=btn.dataset.action; 22 if(action==='reload'){window.location.reload();} 23 else if(action==='show-modal'){var t=btn.dataset.target;if(t)document.getElementById(t).showModal();} 24 } 25 }); 26 document.addEventListener('submit',function(e){ 27 var f=e.target; 28 if(f.dataset.disableOnSubmit!=='true')return; 29 if(f.dataset.submitted){e.preventDefault();return;} 30 f.dataset.submitted='true'; 31 var b=f.querySelector('button[type=submit]'); 32 if(b){b.disabled=true;b.dataset.originalText=b.textContent;b.textContent='Saving...';} 33 }); 34 document.addEventListener('DOMContentLoaded',function(){ 35 document.querySelectorAll('[data-auto-dismiss]').forEach(function(el){ 36 var ms=parseInt(el.dataset.autoDismiss)||5000; 37 setTimeout(function(){ 38 if(typeof el.hide==='function')el.hide(); 39 else el.removeAttribute('open'); 40 },ms); 41 }); 42 try{ 43 var userTz=Intl.DateTimeFormat().resolvedOptions().timeZone; 44 document.querySelectorAll('[data-event-utc]').forEach(function(el){ 45 if(el.dataset.localTimeApplied)return; 46 var iso=el.getAttribute('data-event-utc'); 47 var eventTz=el.getAttribute('data-event-timezone'); 48 if(!iso||eventTz===userTz)return; 49 var d=new Date(iso); 50 if(isNaN(d.getTime()))return; 51 var fmt=new Intl.DateTimeFormat([],{hour:'2-digit',minute:'2-digit',hour12:false,timeZoneName:'short'}); 52 var local=document.createElement('div'); 53 local.textContent='('+fmt.format(d)+' your time)'; 54 el.parentNode.insertBefore(local,el.nextSibling); 55 el.dataset.localTimeApplied='1'; 56 }); 57 }catch(_err){/* leave canonical time untouched */} 58 });</script>
58</head><body class="body bg-gray-900 h-full overflow-y-scroll scroll-smooth"><a href="#main-content" class="sr-only focus:not-sr-only focus:absolute focus:z-50 focus:p-4 focus:bg-white focus:text-black focus:rounded">Skip to main content</a><div class="fixed inset-0 w-full h-full -z-10 pointer-events-none"><div class="absolute inset-x-0 top-0 transform-gpu overflow-hidden blur-3xl" aria-hidden="true"><div class="relative left-[calc(50%-11rem)] aspect-1155/678 w-[36.125rem] -translate-x-1/2 rotate-[30deg] bg-linear-to-tr from-lime-400 to-blue-500 opacity-30 sm:left-[calc(50%-30rem)] sm:w-[72.1875rem]" style="clip-path: polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%)"></div></div><div class="absolute inset-x-0 bottom-0 transform-gpu overflow-hidden blur-3xl" aria-hidden="true"><div class="relative left-[calc(50%+3rem)] aspect-1155/678 w-[36.125rem] -translate-x-1/2 bg-linear-to-tr from-green-300 to-sky-500 opacity-30 sm:left-[calc(50%+36rem)] sm:w-[72.1875rem]" style="clip-path: polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%)"></div></div></div><div class="relative z-10 flex flex-col min-h-full"><header><div class="fixed z-10 right-0 left-0 bottom-0 pb-[max(0.5rem,env(safe-area-inset-bottom))] sm:bottom-auto sm:top-0 sm:bg-linear-to-b sm:from-gray-900 sm:from-10% sm:via-gray-to-gray-900/0"><div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"><div class="flex items-center justify-center h-16"><nav class="flex sm:hidden gap-1 backdrop-blur-sm bg-white/5 ring-1 ring-white/10 rounded-full p-1" aria-label="Main Navigation"><a href="/" class="group flex items-center justify-center rounded-full transition px-1 text-gray-400 hover:text-white hover:bg-white/5"><img class="size-6 rounded-full" src="/img/clojurestream-logo.svg" alt="ClojureStream"></a><a href="/docs" class="group flex items-center justify-center rounded-full transition px-2 text-gray-400 hover:text-white hover:bg-white/5"><svg aria-hidden="true" fill="currentColor" width="16" xmlns="http://www.w3.org/2000/svg" data-slot="icon" class="size-4" viewBox="0 0 16 16" height="16"><path d="M7.25 3.68772C6.25693 3.24568 5.15715 3 4 3C3.44413 3 2.9015 3.05669 2.37757 3.16462C2.15412 3.21064 2 3.4115 2 3.63964V11.6344C2 11.9794 2.34235 12.2215 2.67903 12.1459C3.10405 12.0504 3.54614 12 4 12C5.1977 12 6.31344 12.3509 7.25 12.9556V3.68772Z"></path><path d="M8.75 12.9556C9.68656 12.3509 10.8023 12 12 12C12.4539 12 12.8959 12.0504 13.321 12.1459C13.6577 12.2215 14 11.9794 14 11.6344V3.63964C14 3.4115 13.8459 3.21064 13.6224 3.16462C13.0985 3.05669 12.5559 3 12 3C10.8428 3 9.74308 3.24568 8.75 3.68772V12.9556Z"></path></svg></a><a href="/courses" class="group flex items-center justify-center rounded-full transition px-2 text-gray-400 hover:text-white hover:bg-white/5"><svg aria-hidden="true" fill="currentColor" width="16" xmlns="http://www.w3.org/2000/svg" data-slot="icon" class="size-4" viewBox="0 0 16 16" height="16"><path d="M3 3.73241C3 2.54878 4.30673 1.83146 5.30531 2.46692L12.0114 6.73441C12.9376 7.32384 12.9376 8.67597 12.0114 9.2654L5.30532 13.5329C4.30673 14.1684 3 13.451 3 12.2674V3.73241Z"></path></svg></a><a href="/workshops" class="group flex items-center justify-center rounded-full transition px-2 text-gray-400 hover:text-white hover:bg-white/5"><svg aria-hidden="true" fill="currentColor" width="16" xmlns="http://www.w3.org/2000/svg" data-slot="icon" class="size-4" viewBox="0 0 16 16" height="16"><path d="M2 4C2 2.89543 2.89543 2 4 2H12C13.1046 2 14 2.89543 14 4V12C14 13.1046 13.1046 14 12 14H4C2.89543 14 2 13.1046 2 12V4ZM4.21967 5.96967C3.92678 6.26256 3.92678 6.73744 4.21967 7.03033L5.18934 8L4.21967 8.96967C3.92678 9.26256 3.92678 9.73744 4.21967 10.0303C4.51256 10.3232 4.98744 10.3232 5.28033 10.0303L6.78033 8.53033C7.07322 8.23744 7.07322 7.76256 6.78033 7.46967L5.28033 5.96967C4.98744 5.67678 4.51256 5.67678 4.21967 5.96967ZM8.75 8.5C8.33579 8.5 8 8.83579 8 9.25C8 9.66421 8.33579 10 8.75 10H11.25C11.6642 10 12 9.66421 12 9.25C12 8.83579 11.6642 8.5 11.25 8.5H8.75Z" clip-rule="evenodd" fill-rule="evenodd"></path></svg></a><a href="/jobs" class="group flex items-center justify-center rounded-full transition px-2 text-gray-400 hover:text-white hover:bg-white/5"><svg aria-hidden="true" fill="currentColor" width="16" xmlns="http://www.w3.org/2000/svg" data-slot="icon" class="size-4" viewBox="0 0 16 16" height="16"><path d="M11 4V3C11 1.89543 10.1046 1 9 1H7C5.89543 1 5 1.89543 5 3V4H4C2.89543 4 2 4.89543 2 6V9C2 10.1046 2.89543 11 4 11H12C13.1046 11 14 10.1046 14 9V6C14 4.89543 13.1046 4 12 4H11ZM9 2.5H7C6.72386 2.5 6.5 2.72386 6.5 3V4H9.5V3C9.5 2.72386 9.27614 2.5 9 2.5ZM9 9C9 9.55228 8.55228 10 8 10C7.44772 10 7 9.55228 7 9C7 8.44772 7.44772 8 8 8
58C8.55228 8 9 8.44772 9 9Z" clip-rule="evenodd" fill-rule="evenodd"></path><path d="M3 11.8291V11.9998C3 13.1044 3.89543 13.9998 5 13.9998H11C12.1046 13.9998 13 13.1044 13 11.9998V11.8291C12.6872 11.9397 12.3506 11.9998 12 11.9998H4C3.64936 11.9998 3.31278 11.9397 3 11.8291Z"></path></svg></a><a href="/podcast" class="group flex items-center justify-center rounded-full transition px-2 text-gray-400 hover:text-white hover:bg-white/5"><svg aria-hidden="true" fill="currentColor" width="16" xmlns="http://www.w3.org/2000/svg" data-slot="icon" class="size-4" viewBox="0 0 16 16" height="16"><path d="M8 1C6.89543 1 6 1.89543 6 3V7C6 8.10457 6.89543 9 8 9C9.10457 9 10 8.10457 10 7V3C10 1.89543 9.10457 1 8 1Z"></path><path d="M4.5 7C4.5 6.58579 4.16421 6.25 3.75 6.25C3.33579 6.25 3 6.58579 3 7C3 9.50652 4.84437 11.5823 7.25 11.9441V13.5H5.75C5.33579 13.5 5 13.8358 5 14.25C5 14.6642 5.33579 15 5.75 15H10.25C10.6642 15 11 14.6642 11 14.25C11 13.8358 10.6642 13.5 10.25 13.5H8.75V11.9441C11.1556 11.5823 13 9.50652 13 7C13 6.58579 12.6642 6.25 12.25 6.25C11.8358 6.25 11.5 6.58579 11.5 7C11.5 8.933 9.933 10.5 8 10.5C6.067 10.5 4.5 8.933 4.5 7Z"></path></svg></a><a href="/settings/account" class="group flex items-center rounded-full transition opacity-80 hover:opacity-100"><svg aria-hidden="true" fill="currentColor" width="16" xmlns="http://www.w3.org/2000/svg" data-slot="icon" class="size-4" viewBox="0 0 16 16" height="16"><path d="M8 8C9.65685 8 11 6.65685 11 5C11 3.34315 9.65685 2 8 2C6.34315 2 5 3.34315 5 5C5 6.65685 6.34315 8 8 8Z"></path><path d="M12.7347 14C13.3531 14 13.8275 13.439 13.607 12.8613C12.7455 10.6036 10.5597 9 7.99942 9C5.43913 9 3.25338 10.6036 2.39182 12.8613C2.17134 13.439 2.64575 14 3.26412 14H12.7347Z"></path></svg></a></nav><nav class="hidden sm:flex gap-1 backdrop-blur-sm bg-white/5 ring-1 ring-white/10 rounded-full p-1" aria-label="Main Navigation"><a href="/" class="group flex items-center rounded-full px-3 text-sm/7 font-medium transition text-gray-400 hover:text-white hover:bg-white/5" aria-current="Home"><img class="size-6 rounded-full mr-2 -ml-2" src="/img/clojurestream-logo.svg" alt="ClojureStream">Home</a><a href="/docs" class="group flex items-center rounded-full px-3 text-sm/7 font-medium transition text-gray-400 hover:text-white hover:bg-white/5" aria-current="Docs">Docs</a><a href="/courses" class="group flex items-center rounded-full px-3 text-sm/7 font-medium transition text-gray-400 hover:text-white hover:bg-white/5" aria-current="Courses">Courses</a><a href="/workshops" class="group flex items-center rounded-full px-3 text-sm/7 font-medium transition text-gray-400 hover:text-white hover:bg-white/5" aria-current="Workshops">Workshops</a><a href="/jobs" class="group flex items-center rounded-full px-3 text-sm/7 font-medium transition text-gray-400 hover:text-white hover:bg-white/5" aria-current="Jobs">Jobs</a><a href="/podcast" class="group flex items-center rounded-full px-3 text-sm/7 font-medium transition text-gray-400 hover:text-white hover:bg-white/5" aria-current="Podcast">Podcast</a><a href="/auth/sign-in" class="group flex items-center rounded-full px-3 py-0.5 ring-1 ring-teal-400 ring-inset text-sm/7 font-medium bg-blur-md hover:text-gray-900 hover:bg-teal-400 text-gray-400">Sign in</a></nav></div></div></div></header><main id="main-content" class="relative isolate container mx-auto max-w-7xl mt-8 sm:mt-24 lg:mt-32 px-4 sm:px-6 lg:px-8 flex-grow"><div class="max-w-3xl mx-auto overflow-hidden"><div class="py-5 space-y-6"><h1 class="text-2xl text-center font-bold leading-6 text-white">Sign In</h1></div><div class="mt-10 sm:mx-auto sm:w-full sm:max-w-md"><form method="POST" action="/api/actions" data-disable-on-submit="true"><input type="hidden" name="action" value="auth/login"><input type="hidden" name="__csrf-token" value="kxDQIy4UILMcieN6X6yBmNevdmprdSspkCswVlxk3uk="><div class="space-y-6"><div class="[&>[data-slot=label]+[data-slot=control]]:mt-3 [&>[data-slot=label]+[data-slot=description]]:mt-1 [&>[data-slot=description]+[data-slot=control]]:mt-3 [&>[data-slot=control]+[data-slot=description]]:mt-3 [&>[data-slot=control]+[data-slot=error]]:mt-3 *:data-[slot=label]:font-medium"><label for="email" data-slot="label" class="text-base/6 select-none sm:text-sm/6 text-white">Email</label><span data-slot="control" class="relative block w-full after:pointer-events-none after:absolute after:inset-0 after:rounded-lg after:ring-transparent after:ring-inset focus-within:after:ring-2 has-[:disabled]:opacity-50 focus-within:after:ring-teal-500"><input placeholder="[email protected]" name="email" type="email" id="email" class="relative block w-full appearance-none rounded-lg px-[13px] py-[9px] sm:px-[11px] sm:py-[5px] text-base/6 placeholder:text-zinc-500 sm:text-sm/6 text-white border border-white/10 hover:border-white/20 bg-transparent bg-white/5 focus:outline-none user-invalid:border-red-600 user-invalid:hover:border-red-600 disabled:border-white/15 disabled:bg-white/2.5 hover:disabled:border-white/15" required></span></div><input type="hidden" name="redirect" value="/settings/account"><div class="space-y-3"><button type="submit" class="w-full relative isolate inline-flex items-baseline justify-center gap-x-2 border font-semibold focus:outline-none focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500 disabled:opacity-50 [&>[data-slot=icon]]:-mx-0.5 [&>[data-slot=icon]]:shrink-0 [&>[data-slot=icon]]:self-center [&>[data-slot=icon]]:text-(--btn-icon) rounded-lg text-base/6 px-[13px] py-[9px] sm:px-[11px] sm:py-[5px] sm:text-sm/6 [&>[data-slot=icon]]:my-0.5 [&>[data-slot=icon]]:size-5 sm:[&>[data-slot=icon]]:my-1 sm:[&>[data-slot=icon]]:size-4 border-transparent bg-(--btn-bg) border-white/5 before:hidden after:absolute after:-inset-px after:-z-10 after:rounded-lg after:shadow-[inset_0_1px_rgba(255,255,255,0.15)] hover:after:bg-(--btn-hover-overlay) disabled:before:shadow-none disabled:after:shadow-none text-white [--btn-hover-overlay:rgba(255,255,255,0.1)] [--btn-bg:#0d9488] [--btn-border:rgba(15,118,110,0.9)] [--btn-icon:rgba(255,255,255,0.6)] hover:[--btn-icon:rgba(255,255,255,0.8)] cursor-default"><span class="absolute left-1/2 top-1/2 size-[max(100%,2.75rem)] -translate-x-1/2 -translate-y-1/2 [@media(pointer:fine)]:hidden" aria-hidden="true"></span>Send one-time password</button><button type="button" id="passkey-login-btn" style="display:none" class="flex w-full justify-center items-center gap-2 rounded-md bg-white/10 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-white/20 ring-1 ring-inset ring-white/10"><svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M15.75 5.25a3 3 0 0 1 3 3m3 0a6 6 0 0 1-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1 1 21.75 8.25Z"></path></svg>Sign in with passkey</button></div><p class="mt-4 text-center text-xs text-zinc-500">By signing in, you agree to our <a href="/legal/privacy-policy" class="text-zinc-400 hover:text-white">Privacy Policy</a> and <a href="/legal/terms-and-conditions" class="text-zinc-400 hover:text-white">Terms and Conditions</a>.</p></div></form></div><div style="display:none">
58<input type="hidden" name="__csrf-token" value="kxDQIy4UILMcieN6X6yBmNevdmprdSspkCswVlxk3uk="></div>
58<script type="text/javascript" nonce="ETH3yFHi3E+WqkhaL6Si5Q=="> 59 (function() { 60 // Base64URL encoding/decoding utilities 61 function base64UrlToBytes(base64url) { 62 const base64 = base64url.replace(/-/g, '+').replace(/_/g, '/'); 63 const padding = '='.repeat((4 - base64.length % 4) % 4); 64 const binary = atob(base64 + padding); 65 return Uint8Array.from(binary, c => c.charCodeAt(0)); 66 } 67 68 function bytesToBase64Url(bytes) { 69 const binary = String.fromCharCode(...new Uint8Array(bytes)); 70 const base64 = btoa(binary); 71 return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); 72 } 73 74 // Convert assertion response to JSON for server 75 function assertionToJSON(assertion) { 76 return JSON.stringify({ 77 id: assertion.id, 78 rawId: bytesToBase64Url(assertion.rawId), 79 type: assertion.type, 80 response: { 81 clientDataJSON: bytesToBase64Url(assertion.response.clientDataJSON), 82 authenticatorData: bytesToBase64Url(assertion.response.authenticatorData), 83 signature: bytesToBase64Url(assertion.response.signature), 84 userHandle: assertion.response.userHandle ? bytesToBase64Url(assertion.response.userHandle) : null 85 } 86 }); 87 } 88 89 let hasPasskeys = false; 90 let debounceTimer = null; 91 92 async function checkPasskeys(email) { 93 if (!email || !email.includes('@')) { 94 document.getElementById('passkey-login-btn').style.display = 'none'; 95 hasPasskeys = false; 96 return; 97 } 98 99 try { 100 const res = await fetch('/api/passkey/check?email=' + encodeURIComponent(email)); 101 const data = await res.json(); 102 hasPasskeys = data.hasPasskeys; 103 104 const btn = document.getElementById('passkey-login-btn'); 105 btn.style.display = hasPasskeys ? 'flex' : 'none'; 106 } catch (e) { 107 console.error('Passkey check failed:', e); 108 document.getElementById('passkey-login-btn').style.display = 'none'; 109 hasPasskeys = false; 110 } 111 } 112 113 async function loginWithPasskey() { 114 const emailInput = document.getElementById('email'); 115 const email = emailInput.value.trim().toLowerCase(); 116 const btn = document.getElementById('passkey-login-btn'); 117 const originalText = btn.textContent; 118 119 if (!email) { 120 alert('Please enter your email address first.'); 121 return; 122 } 123 124 try { 125 if (!window.PublicKeyCredential) { 126 alert('Your browser does not support passkeys.'); 127 return; 128 } 129 130 btn.textContent = 'Authenticating...'; 131 btn.disabled = true; 132 133 // 1. Fetch authentication options from server 134 const optionsRes = await fetch('/api/passkey/login/options?email=' + encodeURIComponent(email)); 135 if (!optionsRes.ok) { 136 const error = await optionsRes.json(); 137 throw new Error(error.message || 'Failed to get authentication options'); 138 } 139 const options = await optionsRes.json(); 140 141 // 2. Convert base64url fields to ArrayBuffer 142 const publicKeyOptions = { 143 ...options, 144 challenge: base64UrlToBytes(options.challenge) 145 }; 146 147 if (options.allowCredentials) { 148 publicKeyOptions.allowCredentials = options.allowCredentials.map(cred => ({ 149 ...cred, 150 id: base64UrlToBytes(cred.id) 151 })); 152 } 153 154 // 3. Get assertion via WebAuthn API 155 const assertion = await navigator.credentials.get({ publicKey: publicKeyOptions }); 156 157 // 4. Submit to server via form action 158 const form = document.createElement('form'); 159 form.method = 'POST'; 160 form.action = '/api/actions'; 161 form.style.display = 'none'; 162 163 const actionInput = document.createElement('input'); 164 actionInput.name = 'action'; 165 actionInput.value = 'passkey/login'; 166 form.appendChild(actionInput); 167 168 const emailInput2 = document.createElement('input'); 169 emailInput2.name = 'email'; 170 emailInput2.value = email; 171 form.appendChild(emailInput2); 172 173 const credentialInput = document.createElement('input'); 174 credentialInput.name = 'credential'; 175 credentialInput.value = assertionToJSON(assertion); 176 form.appendChild(credentialInput); 177 178 // Preserve redirect param if present 179 const redirectInput = document.querySelector('input[name="redirect"]'); 180 if (redirectInput && redirectInput.value) { 181 const ri = document.createElement('input'); 182 ri.name = 'redirect'; 183 ri.value = redirectInput.value; 184 form.appendChild(ri); 185 } 186 187 // Include CSRF token 188 const csrfToken = document.querySelector('input[name="__csrf-token"]'); 189 if (csrfToken) { 190 const csrfInput = document.createElement('input'); 191 csrfInput.name = '__csrf-token'; 192 csrfInput.value = csrfToken.value; 193 form.appendChild(csrfInput); 194 } 195 196 document.body.appendChild(form); 197 form.submit(); 198 199 } catch (error) { 200 console.error('Passkey login error:', error); 201 btn.textContent = originalText; 202 btn.disabled = false;
203 204 if (error.name === 'NotAllowedError') { 205 alert('Authentication was cancelled or timed out.'); 206 } else { 207 alert('Failed to authenticate with passkey: ' + error.message); 208 } 209 } 210 } 211 212 document.addEventListener('DOMContentLoaded', function() { 213 const emailInput = document.getElementById('email'); 214 const passkeyBtn = document.getElementById('passkey-login-btn'); 215 216 if (emailInput && passkeyBtn) { 217 // Check on page load if email is pre-filled 218 if (emailInput.value) { 219 checkPasskeys(emailInput.value); 220 } 221 222 // Check when email changes (debounced) 223 emailInput.addEventListener('input', function() { 224 clearTimeout(debounceTimer); 225 debounceTimer = setTimeout(() => checkPasskeys(this.value), 500); 226 }); 227 228 // Handle passkey login button click 229 passkeyBtn.addEventListener('click', loginWithPasskey); 230 } 231 }); 232 })(); 233 </script>
233</div></main><footer class="footer mt-12 mb-20 sm:mb-0"><div class="max-w-7xl mx-auto py-8 px-4 sm:px-6 md:flex md:items-center md:justify-between lg:px-8"><div class="flex justify-center space-x-6 md:order-2"><a href="https://x.com/clojurestream" class="text-gray-400 hover:text-gray-200 rounded scale-80" target="_blank" rel="noopener"><span class="sr-only">X</span><svg fill="currentColor" class="h-6 w-6" viewBox="0 0 1200 1227" aria-hidden="true" fill-rule="evenodd" clip-rule="evenodd"><path d="M714.163 519.284L1160.89 0H1055.03L667.137 450.887L357.328 0H0L468.492 681.821L0 1226.37H105.866L515.491 750.218L842.672 1226.37H1200L714.137 519.284H714.163ZM569.165 687.828L521.697 619.934L144.011 79.6944H306.615L611.412 515.685L658.88 583.579L1055.08 1150.3H892.476L569.165 687.854V687.828Z" fill-rule="evenodd" clip-rule="evenodd"></path></svg></a><a href="https://github.com/clojurestream" class="text-gray-400 hover:text-gray-200 rounded-full" target="_blank" rel="noopener"><span class="sr-only">GitHub</span><svg fill="currentColor" class="h-6 w-6" viewBox="0 0 24 24" aria-hidden="true" fill-rule="evenodd" clip-rule="evenodd"><path d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z" fill-rule="evenodd" clip-rule="evenodd"></path></svg></a></div><div class="mt-8 md:mt-0 md:order-1"><p class="text-center text-xs text-gray-500">© 2022 ClojureStream (by Muneo GmbH). All rights reserved · <a href="/changelog" class="text-gray-500 hover:text-gray-200 rounded hover:underline">Changelog</a> · <a href="/legal/privacy-policy" class="text-gray-500 hover:text-gray-200 rounded hover:underline">Privacy</a> · <a href="/legal/terms-and-conditions" class="text-gray-500 hover:text-gray-200 rounded hover:underline">Terms</a> · <a href="/climate" class="text-gray-500 hover:text-gray-200 rounded hover:underline">Climate</a></p></div></div></footer></div>
233<script nonce="ETH3yFHi3E+WqkhaL6Si5Q==">(function(){function c(){var b=a.contentDocument||(a.contentWindow&&a.contentWindow.document);if(b){var d=b.createElement('script');d.nonce='ETH3yFHi3E+WqkhaL6Si5Q==';d.innerHTML="window.__CF$cv$params={r:'a44158396cea10c2',t:'MTc5MDkxOTMzNQ=='};var a=document.createElement('script');a.nonce='ETH3yFHi3E+WqkhaL6Si5Q==';a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script>
233<script type="module" src="https://static.cloudflareinsights.com/beacon.min.js/v31edd6df95cf4e85bb4c19e7a9bdbcba1788362987495" integrity="sha512-iIg7k2xntmwu6/uSb5tpc/hySgZc4eoL31yB29W6tJFo2akwjPWcEqnCEdJvGexCL0KEQwVYv5BlowfhVz26hg==" nonce="ETH3yFHi3E+WqkhaL6Si5Q==" data-cf-beacon='{"version":"2024.11.0","token":"d4eeed365483480ea46adb43c3c36c3a","r":1,"spa":2}' crossorigin="anonymous"></script>
233 234</body></html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.