import { DeviceFlowClient, type OAuthUserInfo } from "@slices/oauth"; import { logger } from "../utils/logger.ts"; import { ConfigManager } from "./config.ts"; import { checkUserWaitlistAccess, showWaitlistError } from "../utils/waitlist.ts"; const DEFAULT_AIP_BASE_URL = "https://auth.slices.network"; const DEFAULT_CLIENT_ID = "24e77d48-a892-4043-b113-ea241f339397"; const DEFAULT_SCOPE = "atproto transition:generic repo:network.slices.slice repo:network.slices.lexicon repo:network.slices.actor.profile repo:network.slices.waitlist.request"; export async function performDeviceFlow( aipBaseUrl: string = DEFAULT_AIP_BASE_URL, clientId: string = DEFAULT_CLIENT_ID, scope: string = DEFAULT_SCOPE, ): Promise { const deviceClient = new DeviceFlowClient(aipBaseUrl, clientId); const config = new ConfigManager(); await config.load(); try { const authResponse = await deviceClient.requestDeviceAuthorization(scope); console.log(`\n${authResponse.verification_uri_complete}`); console.log(`Code: ${authResponse.user_code}\n`); if (authResponse.verification_uri_complete) { try { const command = Deno.build.os === "darwin" ? "open" : Deno.build.os === "windows" ? "cmd" : "xdg-open"; const args: string[] = Deno.build.os === "windows" ? ["/c", "start", authResponse.verification_uri_complete] : [authResponse.verification_uri_complete]; await new Deno.Command(command, { args }).output(); } catch { console.log("Could not open browser automatically. Please open the link manually.\n"); } } const tokenResponse = await deviceClient.pollForToken( authResponse.device_code, authResponse.interval || 5, authResponse.expires_in, ); const userInfo: OAuthUserInfo = await deviceClient.getUserInfo(tokenResponse.access_token); if (!userInfo.did) { throw new Error("Failed to retrieve user DID from authentication response."); } // Only check waitlist access for production Slices network if (aipBaseUrl === "https://auth.slices.network") { const { hasAccess, isOnWaitlist } = await checkUserWaitlistAccess(userInfo.did); if (!hasAccess) { showWaitlistError(userInfo.did, isOnWaitlist); throw new Error("Access denied: User is not on the invite list"); } } const expiresAt = tokenResponse.expires_in ? Date.now() + (tokenResponse.expires_in * 1000) : undefined; await config.save({ auth: { accessToken: tokenResponse.access_token, refreshToken: tokenResponse.refresh_token, expiresAt, did: userInfo.did, aipBaseUrl, }, }); console.log(`✓ Logged in as ${userInfo.did}`); } catch (error) { const err = error as Error; logger.error("Authentication failed:", err.message); throw error; } }