the statusphere demo reworked into a vite/react app in a monorepo
at main 96 lines 2.7 kB view raw
1import { OAuthResolverError } from '@atproto/oauth-client-node' 2import { isValidHandle } from '@atproto/syntax' 3import express from 'express' 4 5import { AppContext } from '#/context' 6import { getSession } from '#/session' 7 8export const createRouter = (ctx: AppContext) => { 9 const router = express.Router() 10 11 // OAuth metadata 12 router.get('/oauth-client-metadata.json', (_req, res) => { 13 res.json(ctx.oauthClient.clientMetadata) 14 }) 15 16 // OAuth callback to complete session creation 17 router.get('/oauth/callback', async (req, res) => { 18 // Get the query parameters from the URL 19 const params = new URLSearchParams(req.originalUrl.split('?')[1]) 20 21 try { 22 const { session } = await ctx.oauthClient.callback(params) 23 24 // Use the common session options 25 const clientSession = await getSession(req, res) 26 27 // Set the DID on the session 28 clientSession.did = session.did 29 await clientSession.save() 30 31 // Get the origin and determine appropriate redirect 32 const host = req.get('host') || '' 33 const protocol = req.protocol || 'http' 34 const baseUrl = `${protocol}://${host}` 35 36 ctx.logger.info( 37 `OAuth callback successful, redirecting to ${baseUrl}/oauth-callback`, 38 ) 39 40 // Redirect to the frontend oauth-callback page 41 res.redirect('/oauth-callback') 42 } catch (err) { 43 ctx.logger.error({ err }, 'oauth callback failed') 44 45 // Handle error redirect - stay on same domain 46 res.redirect('/oauth-callback?error=auth') 47 } 48 }) 49 50 // Login handler 51 router.post('/oauth/initiate', async (req, res) => { 52 // Validate 53 const handle = req.body?.handle 54 if ( 55 typeof handle !== 'string' || 56 !(isValidHandle(handle) || isValidUrl(handle)) 57 ) { 58 res.status(400).json({ error: 'Invalid handle' }) 59 return 60 } 61 62 // Initiate the OAuth flow 63 try { 64 const url = await ctx.oauthClient.authorize(handle, { 65 scope: 'atproto transition:generic', 66 }) 67 res.json({ redirectUrl: url.toString() }) 68 } catch (err) { 69 ctx.logger.error({ err }, 'oauth authorize failed') 70 const errorMsg = 71 err instanceof OAuthResolverError 72 ? err.message 73 : "Couldn't initiate login" 74 res.status(500).json({ error: errorMsg }) 75 } 76 }) 77 78 // Logout handler 79 router.post('/oauth/logout', async (req, res) => { 80 const session = await getSession(req, res) 81 session.destroy() 82 res.json({ success: true }) 83 }) 84 85 return router 86} 87 88function isValidUrl(url: string): boolean { 89 try { 90 const urlp = new URL(url) 91 // http or https 92 return urlp.protocol === 'http:' || urlp.protocol === 'https:' 93 } catch (error) { 94 return false 95 } 96}