( baseUrl: string, clientId: string )
| 151 | export const DEFAULT_DEVICE_POLL_INTERVAL_SECONDS = 5; |
| 152 | |
| 153 | export async function startDeviceAuthorization( |
| 154 | baseUrl: string, |
| 155 | clientId: string |
| 156 | ): Promise<DeviceAuthorizationResponse> { |
| 157 | // Hostname is shown on the server's verification page so the user can confirm |
| 158 | // that the device they're authorizing matches the one running the CLI |
| 159 | // (RFC 8628 §5.4 phishing resistance). Best-effort. |
| 160 | const params = new URLSearchParams({ client_id: clientId }); |
| 161 | try { |
| 162 | const hostname = os.hostname(); |
| 163 | if (hostname) params.set("hostname", hostname); |
| 164 | } catch { |
| 165 | // ignore |
| 166 | } |
| 167 | |
| 168 | const response = await fetch(`${baseUrl}/api/oauth/device/code`, { |
| 169 | method: "POST", |
| 170 | headers: { "Content-Type": "application/x-www-form-urlencoded" }, |
| 171 | body: params.toString(), |
| 172 | }); |
| 173 | |
| 174 | if (!response.ok) { |
| 175 | const err = (await response.json().catch(() => ({}))) as TokenErrorResponse; |
| 176 | throw new Error(err.error_description || err.error || "Failed to start device authorization"); |
| 177 | } |
| 178 | |
| 179 | return (await response.json()) as DeviceAuthorizationResponse; |
| 180 | } |
| 181 | |
| 182 | export interface PollDeviceTokenResult { |
| 183 | status: "approved" | "pending" | "slow_down" | "denied" | "expired" | "transient"; |
no outgoing calls
no test coverage detected