lewis.diff
1diff --git a/frontend/src/routes/OAuth2FA.svelte b/frontend/src/routes/OAuth2FA.svelte
2index 2eaf565..a9218e6 100644
3--- a/frontend/src/routes/OAuth2FA.svelte
4+++ b/frontend/src/routes/OAuth2FA.svelte
5@@ -32,7 +32,7 @@
6 method: 'POST',
7 headers: {
8 'Content-Type': 'application/json',
9- 'Accept': 'application/json'
10+ Accept: 'application/json'
11 },
12 body: JSON.stringify({
13 request_uri: requestUri,
14@@ -50,6 +50,11 @@
15
16 if (data.redirect_uri) {
17 window.location.href = data.redirect_uri
18+ const a = document.createElement('a')
19+ a.href = data.redirect_uri
20+ a.style.display = 'none'
21+ document.body.appendChild(a)
22+ a.click()
23 return
24 }
25
26diff --git a/frontend/src/routes/OAuthAccounts.svelte b/frontend/src/routes/OAuthAccounts.svelte
27index 013b3e9..29289e1 100644
28--- a/frontend/src/routes/OAuthAccounts.svelte
29+++ b/frontend/src/routes/OAuthAccounts.svelte
30@@ -58,7 +58,7 @@
31 method: 'POST',
32 headers: {
33 'Content-Type': 'application/json',
34- 'Accept': 'application/json'
35+ Accept: 'application/json'
36 },
37 body: JSON.stringify({
38 request_uri: requestUri,
39@@ -80,12 +80,19 @@
40 }
41
42 if (data.needs_2fa) {
43- navigate(routes.oauth2fa, { params: { request_uri: requestUri, channel: data.channel || '' } })
44+ navigate(routes.oauth2fa, {
45+ params: { request_uri: requestUri, channel: data.channel || '' }
46+ })
47 return
48 }
49
50 if (data.redirect_uri) {
51 window.location.href = data.redirect_uri
52+ const a = document.createElement('a')
53+ a.href = data.redirect_uri
54+ a.style.display = 'none'
55+ document.body.appendChild(a)
56+ a.click()
57 return
58 }
59
60@@ -128,12 +135,7 @@
61
62 <div class="accounts-list">
63 {#each accounts as account}
64- <button
65- type="button"
66- class="account-item"
67- class:disabled={submitting}
68- onclick={() => !submitting && handleSelectAccount(account.did)}
69- >
70+ <button type="button" class="account-item" class:disabled={submitting} onclick={() => !submitting && handleSelectAccount(account.did)}>
71 <div class="account-info">
72 <span class="account-handle">@{account.handle}</span>
73 <span class="account-email">{account.email}</span>
74@@ -202,7 +204,9 @@
75 cursor: pointer;
76 text-align: left;
77 width: 100%;
78- transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
79+ transition:
80+ border-color var(--transition-fast),
81+ box-shadow var(--transition-fast);
82 }
83
84 .account-item:hover:not(.disabled) {
85diff --git a/frontend/src/routes/OAuthConsent.svelte b/frontend/src/routes/OAuthConsent.svelte
86index 2123f13..5486456 100644
87--- a/frontend/src/routes/OAuthConsent.svelte
88+++ b/frontend/src/routes/OAuthConsent.svelte
89@@ -57,12 +57,7 @@
90 const data: ConsentData = await response.json()
91 consentData = data
92
93- scopeSelections = Object.fromEntries(
94- data.scopes.map((scope) => [
95- scope.scope,
96- scope.required ? true : scope.granted ?? true,
97- ])
98- )
99+ scopeSelections = Object.fromEntries(data.scopes.map((scope) => [scope.scope, scope.required ? true : (scope.granted ?? true)]))
100
101 if (!data.show_consent) {
102 await submitConsent()
103@@ -93,8 +88,8 @@
104 body: JSON.stringify({
105 request_uri: consentData.request_uri,
106 approved_scopes: approvedScopes,
107- remember: rememberChoice,
108- }),
109+ remember: rememberChoice
110+ })
111 })
112
113 if (!response.ok) {
114@@ -107,6 +102,11 @@
115 const data = await response.json()
116 if (data.redirect_uri) {
117 window.location.href = data.redirect_uri
118+ const a = document.createElement('a')
119+ a.href = data.redirect_uri
120+ a.style.display = 'none'
121+ document.body.appendChild(a)
122+ a.click()
123 }
124 } catch {
125 error = $_('oauth.error.genericError')
126@@ -135,7 +135,7 @@
127 }
128
129 function handleScopeToggle(scope: string) {
130- const scopeInfo = consentData?.scopes.find(s => s.scope === scope)
131+ const scopeInfo = consentData?.scopes.find((s) => s.scope === scope)
132 if (scopeInfo?.required) return
133 scopeSelections[scope] = !scopeSelections[scope]
134 }
135@@ -144,7 +144,7 @@
136 return scopes.reduce(
137 (groups, scope) => ({
138 ...groups,
139- [scope.category]: [...(groups[scope.category] ?? []), scope],
140+ [scope.category]: [...(groups[scope.category] ?? []), scope]
141 }),
142 {} as Record<string, ScopeInfo[]>
143 )
144@@ -176,7 +176,9 @@
145 <img src={consentData.logo_uri} alt="" class="client-logo" />
146 {/if}
147 <h1>{consentData.client_name || $_('oauth.consent.title')}</h1>
148- <p class="subtitle">{$_('oauth.consent.appWantsAccess', { values: { app: '' } })}</p>
149+ <p class="subtitle">
150+ {$_('oauth.consent.appWantsAccess', { values: { app: '' } })}
151+ </p>
152 {#if consentData.client_uri}
153 <a href={consentData.client_uri} target="_blank" rel="noopener noreferrer" class="client-link">
154 {consentData.client_uri}
155@@ -186,7 +188,9 @@
156
157 <div class="account-info">
158 {#if consentData.is_delegation}
159- <div class="delegation-badge">{$_('oauthConsent.delegatedAccess')}</div>
160+ <div class="delegation-badge">
161+ {$_('oauthConsent.delegatedAccess')}
162+ </div>
163 <div class="delegation-info">
164 <div class="info-row">
165 <span class="label">{$_('oauthConsent.actingAs')}</span>
166@@ -204,7 +208,17 @@
167 {#if consentData.delegation_level && consentData.delegation_level !== 'Owner'}
168 <div class="permissions-notice">
169 <div class="notice-header">
170- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
171+ <svg
172+ xmlns="http://www.w3.org/2000/svg"
173+ width="16"
174+ height="16"
175+ viewBox="0 0 24 24"
176+ fill="none"
177+ stroke="currentColor"
178+ stroke-width="2"
179+ stroke-linecap="round"
180+ stroke-linejoin="round"><circle cx="12" cy="12" r="10" /><line x1="12" y1="8" x2="12" y2="12" /><line x1="12" y1="16" x2="12.01" y2="16" /></svg
181+ >
182 <span>{$_('oauthConsent.permissionsLimited')}</span>
183 </div>
184 <p class="notice-text">
185@@ -213,7 +227,9 @@
186 {:else if consentData.delegation_level === 'Editor'}
187 {$_('oauthConsent.editorLimitedDesc')}
188 {:else}
189- {$_('oauthConsent.permissionsLimitedDesc', { values: { level: consentData.delegation_level } })}
190+ {$_('oauthConsent.permissionsLimitedDesc', {
191+ values: { level: consentData.delegation_level }
192+ })}
193 {/if}
194 </p>
195 </div>
196@@ -243,12 +259,7 @@
197 <h3 class="category-title">{category}</h3>
198 {#each scopes as scope}
199 <label class="scope-item" class:required={scope.required}>
200- <input
201- type="checkbox"
202- checked={scopeSelections[scope.scope]}
203- disabled={scope.required || submitting}
204- onchange={() => handleScopeToggle(scope.scope)}
205- />
206+ <input type="checkbox" checked={scopeSelections[scope.scope]} disabled={scope.required || submitting} onchange={() => handleScopeToggle(scope.scope)} />
207 <div class="scope-info">
208 <span class="scope-name">{scope.display_name}</span>
209 <span class="scope-description">{scope.description}</span>
210@@ -524,7 +535,7 @@
211 border-style: dashed;
212 }
213
214- .scope-item input[type="checkbox"] {
215+ .scope-item input[type='checkbox'] {
216 flex-shrink: 0;
217 width: 18px;
218 height: 18px;
219diff --git a/frontend/src/routes/OAuthDelegation.svelte b/frontend/src/routes/OAuthDelegation.svelte
220index 77d0c19..6976dde 100644
221--- a/frontend/src/routes/OAuthDelegation.svelte
222+++ b/frontend/src/routes/OAuthDelegation.svelte
223@@ -1,11 +1,7 @@
224 <script lang="ts">
225 import { navigate, routes } from '../lib/router.svelte'
226 import { _ } from '../lib/i18n'
227- import {
228- prepareRequestOptions,
229- serializeAssertionResponse,
230- type WebAuthnRequestOptionsResponse,
231- } from '../lib/webauthn'
232+ import { prepareRequestOptions, serializeAssertionResponse, type WebAuthnRequestOptionsResponse } from '../lib/webauthn'
233
234 let delegatedDid = $state<string | null>(null)
235 let delegatedHandle = $state<string | null>(null)
236@@ -123,7 +119,7 @@
237 method: 'POST',
238 headers: {
239 'Content-Type': 'application/json',
240- 'Accept': 'application/json'
241+ Accept: 'application/json'
242 },
243 body: JSON.stringify({
244 request_uri: requestUri,
245@@ -141,9 +137,9 @@
246 const { options } = await startResponse.json()
247 const publicKeyOptions = prepareRequestOptions(options as WebAuthnRequestOptionsResponse)
248
249- const credential = await navigator.credentials.get({
250+ const credential = (await navigator.credentials.get({
251 publicKey: publicKeyOptions
252- }) as PublicKeyCredential | null
253+ })) as PublicKeyCredential | null
254
255 if (!credential) {
256 error = $_('oauthDelegation.passkeyCancelled')
257@@ -157,7 +153,7 @@
258 method: 'POST',
259 headers: {
260 'Content-Type': 'application/json',
261- 'Accept': 'application/json'
262+ Accept: 'application/json'
263 },
264 body: JSON.stringify({
265 request_uri: requestUri,
266@@ -182,12 +178,19 @@
267 }
268
269 if (data.needs_2fa) {
270- navigate(routes.oauth2fa, { params: { request_uri: requestUri, channel: data.channel || '' } })
271+ navigate(routes.oauth2fa, {
272+ params: { request_uri: requestUri, channel: data.channel || '' }
273+ })
274 return
275 }
276
277 if (data.redirect_uri) {
278 window.location.href = data.redirect_uri
279+ const a = document.createElement('a')
280+ a.href = data.redirect_uri
281+ a.style.display = 'none'
282+ document.body.appendChild(a)
283+ a.click()
284 return
285 }
286
287@@ -216,7 +219,7 @@
288 method: 'POST',
289 headers: {
290 'Content-Type': 'application/json',
291- 'Accept': 'application/json'
292+ Accept: 'application/json'
293 },
294 body: JSON.stringify({
295 request_uri: requestUri,
296@@ -241,12 +244,19 @@
297 }
298
299 if (data.needs_2fa) {
300- navigate(routes.oauth2fa, { params: { request_uri: requestUri, channel: data.channel || '' } })
301+ navigate(routes.oauth2fa, {
302+ params: { request_uri: requestUri, channel: data.channel || '' }
303+ })
304 return
305 }
306
307 if (data.redirect_uri) {
308 window.location.href = data.redirect_uri
309+ const a = document.createElement('a')
310+ a.href = data.redirect_uri
311+ a.style.display = 'none'
312+ document.body.appendChild(a)
313+ a.click()
314 return
315 }
316
317@@ -271,7 +281,7 @@
318 method: 'POST',
319 headers: {
320 'Content-Type': 'application/json',
321- 'Accept': 'application/json'
322+ Accept: 'application/json'
323 },
324 body: JSON.stringify({ request_uri: requestUri })
325 })
326@@ -279,6 +289,11 @@
327 const data = await response.json()
328 if (data.redirect_uri) {
329 window.location.href = data.redirect_uri
330+ const a = document.createElement('a')
331+ a.href = data.redirect_uri
332+ a.style.display = 'none'
333+ document.body.appendChild(a)
334+ a.click()
335 }
336 } catch {
337 window.history.back()
338@@ -301,7 +316,9 @@
339 <header class="page-header">
340 <h1>{$_('oauthDelegation.title')}</h1>
341 <p class="subtitle">
342- {$_('oauthDelegation.isDelegated', { values: { handle: delegatedHandle } })}
343+ {$_('oauthDelegation.isDelegated', {
344+ values: { handle: delegatedHandle }
345+ })}
346 <br />{$_('oauthDelegation.enterControllerHandle')}
347 </p>
348 </header>
349@@ -337,7 +354,12 @@
350 <header class="page-header">
351 <h1>{$_('oauthDelegation.signInAsController')}</h1>
352 <p class="subtitle">
353- {$_('oauthDelegation.authenticateAs', { values: { controller: '@' + controllerIdentifier.replace(/^@/, ''), delegated: delegatedHandle } })}
354+ {$_('oauthDelegation.authenticateAs', {
355+ values: {
356+ controller: '@' + controllerIdentifier.replace(/^@/, ''),
357+ delegated: delegatedHandle
358+ }
359+ })}
360 </p>
361 </header>
362
363@@ -354,12 +376,7 @@
364 <div class="auth-methods">
365 <div class="passkey-method">
366 <h3>{$_('oauthDelegation.signInWithPasskey')}</h3>
367- <button
368- type="button"
369- class="passkey-btn"
370- onclick={handlePasskeyLogin}
371- disabled={submitting}
372- >
373+ <button type="button" class="passkey-btn" onclick={handlePasskeyLogin} disabled={submitting}>
374 <svg class="passkey-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
375 <path d="M15 7a4 4 0 1 0-8 0 4 4 0 0 0 8 0z" />
376 <path d="M17 17v4l3-2-3-2z" />
377@@ -378,14 +395,7 @@
378 <div class="password-method">
379 <h3>{$_('oauthDelegation.password')}</h3>
380 <div class="field">
381- <input
382- type="password"
383- bind:value={password}
384- disabled={submitting}
385- required
386- autocomplete="current-password"
387- placeholder={$_('oauthDelegation.enterPassword')}
388- />
389+ <input type="password" bind:value={password} disabled={submitting} required autocomplete="current-password" placeholder={$_('oauthDelegation.enterPassword')} />
390 </div>
391
392 <label class="remember-device">
393@@ -401,14 +411,7 @@
394 {:else}
395 <div class="field">
396 <label for="password">{$_('oauthDelegation.password')}</label>
397- <input
398- id="password"
399- type="password"
400- bind:value={password}
401- disabled={submitting}
402- required
403- autocomplete="current-password"
404- />
405+ <input id="password" type="password" bind:value={password} disabled={submitting} required autocomplete="current-password" />
406 </div>
407
408 <label class="remember-device">
409@@ -584,8 +587,8 @@
410 font-weight: var(--font-medium);
411 }
412
413- input[type="password"],
414- input[type="text"] {
415+ input[type='password'],
416+ input[type='text'] {
417 padding: var(--space-3);
418 border: 1px solid var(--border-color);
419 border-radius: var(--radius-md);
420@@ -677,7 +680,9 @@
421 border-radius: var(--radius-md);
422 font-size: var(--text-base);
423 cursor: pointer;
424- transition: background-color var(--transition-fast), border-color var(--transition-fast);
425+ transition:
426+ background-color var(--transition-fast),
427+ border-color var(--transition-fast);
428 }
429
430 .passkey-btn:hover:not(:disabled) {
431diff --git a/frontend/src/routes/OAuthLogin.svelte b/frontend/src/routes/OAuthLogin.svelte
432index 2f954a1..924e43c 100644
433--- a/frontend/src/routes/OAuthLogin.svelte
434+++ b/frontend/src/routes/OAuthLogin.svelte
435@@ -1,11 +1,7 @@
436 <script lang="ts">
437 import { navigate, routes, getFullUrl } from '../lib/router.svelte'
438 import { _ } from '../lib/i18n'
439- import {
440- prepareRequestOptions,
441- serializeAssertionResponse,
442- type WebAuthnRequestOptionsResponse,
443- } from '../lib/webauthn'
444+ import { prepareRequestOptions, serializeAssertionResponse, type WebAuthnRequestOptionsResponse } from '../lib/webauthn'
445
446 let username = $state('')
447 let password = $state('')
448@@ -53,7 +49,7 @@
449
450 try {
451 const response = await fetch(`/oauth/authorize?request_uri=${encodeURIComponent(requestUri)}`, {
452- headers: { 'Accept': 'application/json' }
453+ headers: { Accept: 'application/json' }
454 })
455 if (response.ok) {
456 const data = await response.json()
457@@ -100,7 +96,9 @@
458 if (!hasPassword && !hasPasskeys && isDelegated && data.did) {
459 const requestUri = getRequestUri()
460 if (requestUri) {
461- navigate(routes.oauthDelegation, { params: { request_uri: requestUri, delegated_did: data.did } })
462+ navigate(routes.oauthDelegation, {
463+ params: { request_uri: requestUri, delegated_did: data.did }
464+ })
465 return
466 }
467 }
468@@ -115,7 +113,6 @@
469 }
470 }
471
472-
473 async function handlePasskeyLogin() {
474 const requestUri = getRequestUri()
475 if (!requestUri || !username) {
476@@ -131,7 +128,7 @@
477 method: 'POST',
478 headers: {
479 'Content-Type': 'application/json',
480- 'Accept': 'application/json'
481+ Accept: 'application/json'
482 },
483 body: JSON.stringify({
484 request_uri: requestUri,
485@@ -149,9 +146,9 @@
486 const { options } = await startResponse.json()
487 const publicKeyOptions = prepareRequestOptions(options as WebAuthnRequestOptionsResponse)
488
489- const credential = await navigator.credentials.get({
490+ const credential = (await navigator.credentials.get({
491 publicKey: publicKeyOptions
492- }) as PublicKeyCredential | null
493+ })) as PublicKeyCredential | null
494
495 if (!credential) {
496 error = $_('common.error')
497@@ -165,7 +162,7 @@
498 method: 'POST',
499 headers: {
500 'Content-Type': 'application/json',
501- 'Accept': 'application/json'
502+ Accept: 'application/json'
503 },
504 body: JSON.stringify({
505 request_uri: requestUri,
506@@ -187,12 +184,19 @@
507 }
508
509 if (data.needs_2fa) {
510- navigate(routes.oauth2fa, { params: { request_uri: requestUri, channel: data.channel || '' } })
511+ navigate(routes.oauth2fa, {
512+ params: { request_uri: requestUri, channel: data.channel || '' }
513+ })
514 return
515 }
516
517 if (data.redirect_uri) {
518 window.location.href = data.redirect_uri
519+ const a = document.createElement('a')
520+ a.href = data.redirect_uri
521+ a.style.display = 'none'
522+ document.body.appendChild(a)
523+ a.click()
524 return
525 }
526
527@@ -225,7 +229,7 @@
528 method: 'POST',
529 headers: {
530 'Content-Type': 'application/json',
531- 'Accept': 'application/json'
532+ Accept: 'application/json'
533 },
534 body: JSON.stringify({
535 request_uri: requestUri,
536@@ -249,12 +253,19 @@
537 }
538
539 if (data.needs_2fa) {
540- navigate(routes.oauth2fa, { params: { request_uri: requestUri, channel: data.channel || '' } })
541+ navigate(routes.oauth2fa, {
542+ params: { request_uri: requestUri, channel: data.channel || '' }
543+ })
544 return
545 }
546
547 if (data.redirect_uri) {
548 window.location.href = data.redirect_uri
549+ const a = document.createElement('a')
550+ a.href = data.redirect_uri
551+ a.style.display = 'none'
552+ document.body.appendChild(a)
553+ a.click()
554 return
555 }
556
557@@ -279,7 +290,7 @@
558 method: 'POST',
559 headers: {
560 'Content-Type': 'application/json',
561- 'Accept': 'application/json'
562+ Accept: 'application/json'
563 },
564 body: JSON.stringify({ request_uri: requestUri })
565 })
566@@ -287,6 +298,11 @@
567 const data = await response.json()
568 if (data.redirect_uri) {
569 window.location.href = data.redirect_uri
570+ const a = document.createElement('a')
571+ a.href = data.redirect_uri
572+ a.style.display = 'none'
573+ document.body.appendChild(a)
574+ a.click()
575 }
576 } catch {
577 window.history.back()
578@@ -313,15 +329,7 @@
579 <form onsubmit={handleSubmit}>
580 <div class="field">
581 <label for="username">{$_('register.handle')}</label>
582- <input
583- id="username"
584- type="text"
585- bind:value={username}
586- placeholder={$_('register.emailPlaceholder')}
587- disabled={submitting}
588- required
589- autocomplete="username"
590- />
591+ <input id="username" type="text" bind:value={username} placeholder={$_('register.emailPlaceholder')} disabled={submitting} required autocomplete="username" />
592 </div>
593
594 {#if passkeySupported && username.length >= 3}
595@@ -334,7 +342,11 @@
596 class:passkey-unavailable={!hasPasskeys || checkingSecurityStatus || !securityStatusChecked}
597 onclick={handlePasskeyLogin}
598 disabled={submitting || !hasPasskeys || !username || checkingSecurityStatus || !securityStatusChecked}
599- title={checkingSecurityStatus ? $_('oauth.login.passkeyHintChecking') : hasPasskeys ? $_('oauth.login.passkeyHintAvailable') : $_('oauth.login.passkeyHintNotAvailable')}
600+ title={checkingSecurityStatus
601+ ? $_('oauth.login.passkeyHintChecking')
602+ : hasPasskeys
603+ ? $_('oauth.login.passkeyHintAvailable')
604+ : $_('oauth.login.passkeyHintNotAvailable')}
605 >
606 <svg class="passkey-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
607 <path d="M15 7a4 4 0 1 0-8 0 4 4 0 0 0 8 0z" />
608@@ -393,14 +405,7 @@
609 {:else}
610 <div class="field">
611 <label for="password">{$_('oauth.login.password')}</label>
612- <input
613- id="password"
614- type="password"
615- bind:value={password}
616- disabled={submitting}
617- required
618- autocomplete="current-password"
619- />
620+ <input id="password" type="password" bind:value={password} disabled={submitting} required autocomplete="current-password" />
621 </div>
622
623 <label class="remember-device">
624@@ -420,7 +425,9 @@
625 </form>
626
627 <p class="help-links">
628- <a href={getFullUrl(routes.resetPassword)}>{$_('login.forgotPassword')}</a> · <a href={getFullUrl(routes.requestPasskeyRecovery)}>{$_('login.lostPasskey')}</a>
629+ <a href={getFullUrl(routes.resetPassword)}>{$_('login.forgotPassword')}</a>
630+ ·
631+ <a href={getFullUrl(routes.requestPasskeyRecovery)}>{$_('login.lostPasskey')}</a>
632 </p>
633 </div>
634
635@@ -560,8 +567,8 @@
636 font-weight: var(--font-medium);
637 }
638
639- input[type="text"],
640- input[type="password"] {
641+ input[type='text'],
642+ input[type='password'] {
643 padding: var(--space-3);
644 border: 1px solid var(--border-color);
645 border-radius: var(--radius-md);
646@@ -640,7 +647,6 @@
647 background: var(--accent-hover);
648 }
649
650-
651 .passkey-btn {
652 display: flex;
653 align-items: center;
654@@ -654,7 +660,10 @@
655 border-radius: var(--radius-md);
656 font-size: var(--text-base);
657 cursor: pointer;
658- transition: background-color var(--transition-fast), border-color var(--transition-fast), opacity var(--transition-fast);
659+ transition:
660+ background-color var(--transition-fast),
661+ border-color var(--transition-fast),
662+ opacity var(--transition-fast);
663 }
664
665 .passkey-btn:hover:not(:disabled) {
666diff --git a/frontend/src/routes/OAuthPasskey.svelte b/frontend/src/routes/OAuthPasskey.svelte
667index 88281a0..afdb925 100644
668--- a/frontend/src/routes/OAuthPasskey.svelte
669+++ b/frontend/src/routes/OAuthPasskey.svelte
670@@ -4,7 +4,7 @@
671 import {
672 prepareRequestOptions,
673 serializeAssertionResponse,
674- type WebAuthnRequestOptionsResponse,
675+ type WebAuthnRequestOptionsResponse
676 } from '../lib/webauthn'
677
678 let loading = $state(false)
679@@ -37,7 +37,7 @@
680 const startResponse = await fetch(`/oauth/authorize/passkey?request_uri=${encodeURIComponent(requestUri)}`, {
681 method: 'GET',
682 headers: {
683- 'Accept': 'application/json'
684+ Accept: 'application/json'
685 }
686 })
687
688@@ -67,7 +67,7 @@
689 method: 'POST',
690 headers: {
691 'Content-Type': 'application/json',
692- 'Accept': 'application/json'
693+ Accept: 'application/json'
694 },
695 body: JSON.stringify({
696 request_uri: requestUri,
697@@ -85,6 +85,11 @@
698
699 if (finishData.redirect_uri) {
700 window.location.href = finishData.redirect_uri
701+ const a = document.createElement('a')
702+ a.href = finishData.redirect_uri
703+ a.style.display = 'none'
704+ document.body.appendChild(a)
705+ a.click()
706 return
707 }
708
709diff --git a/frontend/src/routes/OAuthTotp.svelte b/frontend/src/routes/OAuthTotp.svelte
710index fe0b632..ab5236f 100644
711--- a/frontend/src/routes/OAuthTotp.svelte
712+++ b/frontend/src/routes/OAuthTotp.svelte
713@@ -28,7 +28,7 @@
714 method: 'POST',
715 headers: {
716 'Content-Type': 'application/json',
717- 'Accept': 'application/json'
718+ Accept: 'application/json'
719 },
720 body: JSON.stringify({
721 request_uri: requestUri,
722@@ -47,6 +47,11 @@
723
724 if (data.redirect_uri) {
725 window.location.href = data.redirect_uri
726+ const a = document.createElement('a')
727+ a.href = data.redirect_uri
728+ a.style.display = 'none'
729+ document.body.appendChild(a)
730+ a.click()
731 return
732 }
733
734@@ -108,11 +113,7 @@
735 </div>
736
737 <label class="trust-device-label">
738- <input
739- type="checkbox"
740- bind:checked={trustDevice}
741- disabled={submitting}
742- />
743+ <input type="checkbox" bind:checked={trustDevice} disabled={submitting} />
744 <span>{$_('oauth.totp.trustDevice')}</span>
745 </label>
746
747@@ -245,7 +246,7 @@
748 margin-top: var(--space-2);
749 }
750
751- .trust-device-label input[type="checkbox"] {
752+ .trust-device-label input[type='checkbox'] {
753 width: auto;
754 margin: 0;
755 }