this repo has no description
at main 4.6 kB view raw
1<script lang="ts"> 2 import { getAuthState, logout } from '../lib/auth.svelte' 3 import { navigate, routes, getFullUrl } from '../lib/router.svelte' 4 import { generateCodeVerifier, generateCodeChallenge, saveOAuthState, generateState } from '../lib/oauth' 5 import { _ } from '../lib/i18n' 6 import type { Session } from '../lib/types/api' 7 8 const auth = $derived(getAuthState()) 9 10 function getSession(): Session | null { 11 return auth.kind === 'authenticated' ? auth.session : null 12 } 13 14 function isLoading(): boolean { 15 return auth.kind === 'loading' 16 } 17 18 const session = $derived(getSession()) 19 const authLoading = $derived(isLoading()) 20 let error = $state<string | null>(null) 21 let loading = $state(true) 22 let actAsInProgress = $state(false) 23 24 function getDid(): string | null { 25 const params = new URLSearchParams(window.location.search) 26 return params.get('did') 27 } 28 29 $effect(() => { 30 if (!authLoading && !session && !actAsInProgress) { 31 navigate(routes.login) 32 } 33 }) 34 35 $effect(() => { 36 if (session && !actAsInProgress) { 37 actAsInProgress = true 38 initiateActAs() 39 } 40 }) 41 42 async function initiateActAs() { 43 const did = getDid() 44 if (!did) { 45 error = $_('actAs.noAccountSpecified') 46 loading = false 47 return 48 } 49 50 try { 51 const response = await fetch( 52 `/xrpc/_delegation.listControlledAccounts`, 53 { 54 headers: { 'Authorization': `Bearer ${session!.accessJwt}` } 55 } 56 ) 57 58 if (!response.ok) { 59 error = $_('actAs.failedToVerify') 60 loading = false 61 return 62 } 63 64 const data = await response.json() 65 const account = data.accounts?.find((a: { did: string }) => a.did === did) 66 67 if (!account) { 68 error = $_('actAs.noAccess') 69 loading = false 70 return 71 } 72 73 await logout() 74 75 const hostname = window.location.origin 76 const state = generateState() 77 const codeVerifier = generateCodeVerifier() 78 const codeChallenge = await generateCodeChallenge(codeVerifier) 79 saveOAuthState({ state, codeVerifier }) 80 81 const parResponse = await fetch('/oauth/par', { 82 method: 'POST', 83 headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, 84 body: new URLSearchParams({ 85 client_id: `${hostname}/oauth/client-metadata.json`, 86 redirect_uri: `${hostname}/`, 87 response_type: 'code', 88 scope: 'atproto', 89 state: state, 90 code_challenge: codeChallenge, 91 code_challenge_method: 'S256', 92 login_hint: account.handle 93 }) 94 }) 95 96 if (!parResponse.ok) { 97 error = $_('actAs.failedToInitiate') 98 loading = false 99 return 100 } 101 102 const parData = await parResponse.json() 103 if (parData.request_uri) { 104 window.location.href = `/app/oauth/login?request_uri=${encodeURIComponent(parData.request_uri)}` 105 } else { 106 error = $_('actAs.invalidResponse') 107 loading = false 108 } 109 } catch (e) { 110 error = $_('actAs.failedError', { values: { error: e instanceof Error ? e.message : String(e) } }) 111 loading = false 112 } 113 } 114 115 function goBack() { 116 navigate('/controllers') 117 } 118</script> 119 120<div class="page"> 121 {#if loading} 122 <div class="loading"> 123 <p>{$_('actAs.preparing')}</p> 124 </div> 125 {:else} 126 <header> 127 <h1>{$_('actAs.title')}</h1> 128 </header> 129 130 {#if error} 131 <div class="message error">{error}</div> 132 {/if} 133 134 <div class="actions"> 135 <button class="back-btn" onclick={goBack}> 136 {$_('actAs.backToControllers')} 137 </button> 138 </div> 139 {/if} 140</div> 141 142<style> 143 .page { 144 max-width: var(--width-md); 145 margin: var(--space-9) auto; 146 padding: var(--space-7); 147 } 148 149 .loading { 150 display: flex; 151 align-items: center; 152 justify-content: center; 153 min-height: 200px; 154 color: var(--text-secondary); 155 } 156 157 header { 158 margin-bottom: var(--space-6); 159 } 160 161 h1 { 162 margin: 0; 163 } 164 165 .message.error { 166 padding: var(--space-3); 167 background: var(--error-bg); 168 border: 1px solid var(--error-border); 169 border-radius: var(--radius-md); 170 color: var(--error-text); 171 margin-bottom: var(--space-4); 172 } 173 174 .actions { 175 margin-top: var(--space-4); 176 } 177 178 .back-btn { 179 padding: var(--space-3) var(--space-5); 180 border: 1px solid var(--border-color); 181 border-radius: var(--radius-md); 182 background: transparent; 183 color: var(--text-primary); 184 cursor: pointer; 185 } 186 187 .back-btn:hover { 188 background: var(--bg-card); 189 border-color: var(--accent); 190 } 191</style>