Scratch space for learning atproto app development
1import { OAuthResolverError } from '@atproto/oauth-client-node' 2import { isValidHandle } from '@atproto/syntax' 3import express from 'express' 4import { createSession, destroySession, getSession } from '#/auth/session' 5import type { AppContext } from '#/config' 6import { home } from '#/pages/home' 7import { login } from '#/pages/login' 8import { page } from '#/view' 9import { handler } from './util' 10 11export const createRouter = (ctx: AppContext) => { 12 const router = express.Router() 13 14 router.get( 15 '/jwks.json', 16 handler((_req, res) => { 17 return res.json(ctx.oauthClient.jwks) 18 }), 19 ) 20 21 router.get( 22 '/client-metadata.json', 23 handler((_req, res) => { 24 return res.json(ctx.oauthClient.clientMetadata) 25 }), 26 ) 27 28 router.get( 29 '/oauth/callback', 30 handler(async (req, res) => { 31 const params = new URLSearchParams(req.originalUrl.split('?')[1]) 32 try { 33 const { agent } = await ctx.oauthClient.callback(params) 34 await createSession(req, res, agent.accountDid) 35 } catch (err) { 36 ctx.logger.error({ err }, 'oauth callback failed') 37 return res.redirect('/?error') 38 } 39 return res.redirect('/') 40 }), 41 ) 42 43 router.get( 44 '/login', 45 handler(async (_req, res) => { 46 return res.type('html').send(page(login({}))) 47 }), 48 ) 49 50 router.post( 51 '/login', 52 handler(async (req, res) => { 53 const handle = req.body?.handle 54 if (typeof handle !== 'string' || !isValidHandle(handle)) { 55 return res.type('html').send(page(login({ error: 'invalid handle' }))) 56 } 57 try { 58 const url = await ctx.oauthClient.authorize(handle) 59 return res.redirect(url.toString()) 60 } catch (err) { 61 ctx.logger.error({ err }, 'oauth authorize failed') 62 return res.type('html').send( 63 page( 64 login({ 65 error: err instanceof OAuthResolverError ? err.message : "couldn't initiate login", 66 }), 67 ), 68 ) 69 } 70 }), 71 ) 72 73 router.post( 74 '/logout', 75 handler(async (req, res) => { 76 await destroySession(req, res) 77 return res.redirect('/') 78 }), 79 ) 80 81 router.get( 82 '/', 83 handler(async (req, res) => { 84 const session = await getSession(req, res) 85 const agent = 86 session && 87 (await ctx.oauthClient.restore(session.did).catch(async (err) => { 88 ctx.logger.warn({ err }, 'oauth restore failed') 89 await destroySession(req, res) 90 return null 91 })) 92 const posts = await ctx.db.selectFrom('post').selectAll().orderBy('indexedAt', 'desc').limit(10).execute() 93 if (!agent) { 94 return res.type('html').send(page(home({ posts }))) 95 } 96 const { data: profile } = await agent.getProfile({ actor: session.did }) 97 return res.type('html').send(page(home({ posts, profile }))) 98 }), 99 ) 100 101 return router 102}