this repo has no description
1<script lang="ts"> 2 import { navigate } from '../lib/router.svelte' 3 4 let code = $state('') 5 let submitting = $state(false) 6 let error = $state<string | null>(null) 7 8 function getRequestUri(): string | null { 9 const params = new URLSearchParams(window.location.hash.split('?')[1] || '') 10 return params.get('request_uri') 11 } 12 13 function getChannel(): string { 14 const params = new URLSearchParams(window.location.hash.split('?')[1] || '') 15 return params.get('channel') || 'email' 16 } 17 18 async function handleSubmit(e: Event) { 19 e.preventDefault() 20 const requestUri = getRequestUri() 21 if (!requestUri) { 22 error = 'Missing request_uri parameter' 23 return 24 } 25 26 submitting = true 27 error = null 28 29 try { 30 const response = await fetch('/oauth/authorize/2fa', { 31 method: 'POST', 32 headers: { 33 'Content-Type': 'application/json', 34 'Accept': 'application/json' 35 }, 36 body: JSON.stringify({ 37 request_uri: requestUri, 38 code: code.trim() 39 }) 40 }) 41 42 const data = await response.json() 43 44 if (!response.ok) { 45 error = data.error_description || data.error || 'Verification failed' 46 submitting = false 47 return 48 } 49 50 if (data.redirect_uri) { 51 window.location.href = data.redirect_uri 52 return 53 } 54 55 error = 'Unexpected response from server' 56 submitting = false 57 } catch { 58 error = 'Failed to connect to server' 59 submitting = false 60 } 61 } 62 63 function handleCancel() { 64 const requestUri = getRequestUri() 65 if (requestUri) { 66 navigate(`/oauth/login?request_uri=${encodeURIComponent(requestUri)}`) 67 } else { 68 window.history.back() 69 } 70 } 71 72 let channel = $derived(getChannel()) 73</script> 74 75<div class="oauth-2fa-container"> 76 <h1>Two-Factor Authentication</h1> 77 <p class="subtitle"> 78 A verification code has been sent to your {channel}. 79 Enter the code below to continue. 80 </p> 81 82 {#if error} 83 <div class="error">{error}</div> 84 {/if} 85 86 <form onsubmit={handleSubmit}> 87 <div class="field"> 88 <label for="code">Verification Code</label> 89 <input 90 id="code" 91 type="text" 92 bind:value={code} 93 placeholder="Enter 6-digit code" 94 disabled={submitting} 95 required 96 maxlength="6" 97 pattern="[0-9]{6}" 98 autocomplete="one-time-code" 99 inputmode="numeric" 100 /> 101 </div> 102 103 <div class="actions"> 104 <button type="button" class="cancel-btn" onclick={handleCancel} disabled={submitting}> 105 Cancel 106 </button> 107 <button type="submit" class="submit-btn" disabled={submitting || code.trim().length !== 6}> 108 {submitting ? 'Verifying...' : 'Verify'} 109 </button> 110 </div> 111 </form> 112</div> 113 114<style> 115 .oauth-2fa-container { 116 max-width: 400px; 117 margin: 4rem auto; 118 padding: 2rem; 119 } 120 121 h1 { 122 margin: 0 0 0.5rem 0; 123 } 124 125 .subtitle { 126 color: var(--text-secondary); 127 margin: 0 0 2rem 0; 128 } 129 130 form { 131 display: flex; 132 flex-direction: column; 133 gap: 1rem; 134 } 135 136 .field { 137 display: flex; 138 flex-direction: column; 139 gap: 0.25rem; 140 } 141 142 label { 143 font-size: 0.875rem; 144 font-weight: 500; 145 } 146 147 input { 148 padding: 0.75rem; 149 border: 1px solid var(--border-color-light); 150 border-radius: 4px; 151 font-size: 1.5rem; 152 letter-spacing: 0.5em; 153 text-align: center; 154 background: var(--bg-input); 155 color: var(--text-primary); 156 } 157 158 input:focus { 159 outline: none; 160 border-color: var(--accent); 161 } 162 163 .error { 164 padding: 0.75rem; 165 background: var(--error-bg); 166 border: 1px solid var(--error-border); 167 border-radius: 4px; 168 color: var(--error-text); 169 margin-bottom: 1rem; 170 } 171 172 .actions { 173 display: flex; 174 gap: 1rem; 175 margin-top: 0.5rem; 176 } 177 178 .actions button { 179 flex: 1; 180 padding: 0.75rem; 181 border: none; 182 border-radius: 4px; 183 font-size: 1rem; 184 cursor: pointer; 185 transition: background-color 0.15s; 186 } 187 188 .actions button:disabled { 189 opacity: 0.6; 190 cursor: not-allowed; 191 } 192 193 .cancel-btn { 194 background: var(--bg-secondary); 195 color: var(--text-primary); 196 border: 1px solid var(--border-color); 197 } 198 199 .cancel-btn:hover:not(:disabled) { 200 background: var(--error-bg); 201 border-color: var(--error-border); 202 color: var(--error-text); 203 } 204 205 .submit-btn { 206 background: var(--accent); 207 color: white; 208 } 209 210 .submit-btn:hover:not(:disabled) { 211 background: var(--accent-hover); 212 } 213</style>