A minimal starter for ATProto logins in Astro
at main 3.4 kB view raw
1--- 2import '../styles.css' 3import { getSession } from '../lib/session' 4import { getOAuthClient } from '../lib/context' 5import { Agent } from '@atproto/api' 6 7const session = getSession(Astro.cookies) 8const oauthClient = getOAuthClient(Astro.cookies) 9 10let agent: Agent | null = null 11let profile: any = null 12 13if (session.did) { 14 try { 15 const oauthSession = await oauthClient.restore(session.did) 16 if (oauthSession) { 17 agent = new Agent(oauthSession) 18 19 try { 20 const profileResponse = await agent.app.bsky.actor.getProfile({ 21 actor: agent.assertDid, 22 }) 23 profile = profileResponse.data 24 } catch (err) { 25 console.warn('Failed to fetch profile:', err) 26 } 27 } 28 } catch (err) { 29 console.warn('OAuth restore failed:', err) 30 session.destroy() 31 } 32} 33 34const displayName = profile?.displayName || agent?.assertDid || 'User' 35const handle = profile?.handle || agent?.assertDid || '' 36const avatar = profile?.avatar 37const description = profile?.description 38--- 39 40<html lang="en" data-theme="dracula"> 41 <head> 42 <meta charset="utf-8" /> 43 <link rel="icon" type="image/svg+xml" href="/favicon.svg" /> 44 <meta name="viewport" content="width=device-width" /> 45 <meta name="generator" content={Astro.generator} /> 46 <title>ATProto Login</title> 47 </head> 48 <body> 49 <div class="min-h-screen flex items-center justify-center"> 50 <div class="card w-96 bg-base-200 shadow-xl p-8"> 51 <div class="card-body"> 52 { 53 agent ? ( 54 <> 55 <h2 class="card-title justify-center mb-4">Welcome!</h2> 56 <div class="space-y-4"> 57 <div class="flex flex-col items-center text-center"> 58 {avatar && ( 59 <div class="avatar mb-4"> 60 <div class="w-24 rounded-full"> 61 <img src={avatar} alt={displayName} /> 62 </div> 63 </div> 64 )} 65 <p class="text-lg font-semibold">{displayName}</p> 66 <p class="text-sm opacity-70">{handle}</p> 67 {description && ( 68 <p class="text-sm mt-2 opacity-80">{description}</p> 69 )} 70 </div> 71 <form method="POST" action="/api/logout" class="w-full"> 72 <button type="submit" class="btn btn-error w-full"> 73 Logout 74 </button> 75 </form> 76 </div> 77 </> 78 ) : ( 79 <> 80 <h2 class="card-title justify-center mb-6">ATProto Login</h2> 81 <form method="POST" action="/api/login"> 82 <div class="join join-vertical w-full"> 83 <input 84 type="text" 85 placeholder="Enter your handle (e.g. alice.bsky.social)" 86 class="input input-bordered join-item w-full" 87 name="handle" 88 required 89 /> 90 <button type="submit" class="btn btn-primary join-item w-full"> 91 Login 92 </button> 93 </div> 94 </form> 95 </> 96 ) 97 } 98 </div> 99 </div> 100 </div> 101 </body> 102</html>