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