···
import { env } from '#/env'
import { handler } from '#/lib/http'
import { page } from '#/lib/view'
-
import { home } from '#/pages/home'
import { login } from '#/pages/login'
type Session = { did?: string }
···
handler(async (req: Request, res: Response) => {
const params = new URLSearchParams(req.originalUrl.split('?')[1])
-
const { session } = await ctx.oauthClient.callback(params)
-
const clientSession = await getIronSession<Session>(req, res, {
password: env.COOKIE_SECRET,
-
assert(!clientSession.did, 'session already exists')
-
clientSession.did = session.did
-
await clientSession.save()
ctx.logger.error({ err }, 'oauth callback failed')
···
handler((req: Request, res: Response) => {
-
res.type('html').send(page(login({})))
···
express.urlencoded({ extended: true }),
handler(async (req: Request, res: Response) => {
-
const input = req.body?.input
-
if (typeof input !== 'string') {
.send(page(login({ error: 'invalid input' })))
···
const url = await ctx.oauthClient.authorize(input, {
scope: 'atproto transition:generic',
res.redirect(url.toString())
ctx.logger.error({ err }, 'oauth authorize failed')
-
err instanceof OAuthResolverError
-
: "couldn't initiate login",
···
const service = env.PDS_URL ?? 'https://bsky.social'
const url = await ctx.oauthClient.authorize(service, {
scope: 'atproto transition:generic',
res.redirect(url.toString())
···
handler(async (req: Request, res: Response) => {
// If the user is signed in, get an agent which communicates with their server
const agent = await getSessionAgent(req, res, ctx)
···
// Serve the logged-out view
-
.send(page(home({ statuses, didHandleMap })))
// Fetch additional information about the logged-in user
···
// Serve the logged-in view
···
express.urlencoded({ extended: true }),
handler(async (req: Request, res: Response) => {
// If the user is signed in, get an agent which communicates with their server
const agent = await getSessionAgent(req, res, ctx)
-
.send('<h1>Error: Session required</h1>')
-
// Construct & validate their status record
-
const rkey = TID.nextStr()
-
$type: 'xyz.statusphere.status',
-
status: req.body?.status,
-
createdAt: new Date().toISOString(),
-
if (!Status.validateRecord(record).success) {
-
.send('<h1>Error: Invalid status</h1>')
-
// Write the status record to the user's repository
-
const res = await agent.com.atproto.repo.putRecord({
-
collection: 'xyz.statusphere.status',
-
ctx.logger.warn({ err }, 'failed to write record')
-
.send('<h1>Error: Failed to write record</h1>')
-
// Optimistically update our SQLite
-
// This isn't strictly necessary because the write event will be
-
// handled in #/firehose/ingestor.ts, but it ensures that future reads
-
// will be up-to-date after this method finishes.
-
authorDid: agent.assertDid,
-
createdAt: record.createdAt,
-
indexedAt: new Date().toISOString(),
-
'failed to update computed view; ignoring as it should be caught by the firehose',
-
return res.redirect('/')
···
import { env } from '#/env'
import { handler } from '#/lib/http'
import { page } from '#/lib/view'
+
import { home, STATUS_OPTIONS } from '#/pages/home'
import { login } from '#/pages/login'
+
import { ifString } from './lib/util'
type Session = { did?: string }
···
handler(async (req: Request, res: Response) => {
const params = new URLSearchParams(req.originalUrl.split('?')[1])
+
// Load the session cookie
+
const session = await getIronSession<Session>(req, res, {
password: env.COOKIE_SECRET,
+
// If the user is already signed in, destroy the old credentials
+
const oauthSession = await ctx.oauthClient.restore(session.did)
+
if (oauthSession) oauthSession.signOut()
+
ctx.logger.warn({ err }, 'oauth restore failed')
+
// Complete the OAuth flow
+
const oauth = await ctx.oauthClient.callback(params)
+
// Update the session cookie
+
session.did = oauth.session.did
+
if (oauth.state?.startsWith('status:')) {
+
const status = oauth.state.slice(7)
+
const agent = new Agent(oauth.session)
+
await updateStatus(agent, status)
+
const message = err instanceof Error ? err.message : 'Unknown error'
+
return res.redirect(`/?error=${encodeURIComponent(message)}`)
+
// Redirect to the homepage
ctx.logger.error({ err }, 'oauth callback failed')
···
handler((req: Request, res: Response) => {
+
const state = ifString(req.query.state)
+
res.type('html').send(page(login({ state })))
···
express.urlencoded({ extended: true }),
handler(async (req: Request, res: Response) => {
+
const input = ifString(req.body.input)
+
const state = ifString(req.body.state)
.send(page(login({ error: 'invalid input' })))
···
const url = await ctx.oauthClient.authorize(input, {
scope: 'atproto transition:generic',
res.redirect(url.toString())
ctx.logger.error({ err }, 'oauth authorize failed')
+
err instanceof OAuthResolverError
+
: "couldn't initiate login"
+
res.type('html').send(page(login({ state, error })))
···
const service = env.PDS_URL ?? 'https://bsky.social'
const url = await ctx.oauthClient.authorize(service, {
scope: 'atproto transition:generic',
+
state: ifString(req.query.state),
res.redirect(url.toString())
···
handler(async (req: Request, res: Response) => {
+
const error = ifString(req.query.error)
// If the user is signed in, get an agent which communicates with their server
const agent = await getSessionAgent(req, res, ctx)
···
// Serve the logged-out view
+
.send(page(home({ error, statuses, didHandleMap })))
// Fetch additional information about the logged-in user
···
// Serve the logged-in view
+
.send(page(home({ error, statuses, didHandleMap, profile, myStatus })))
···
express.urlencoded({ extended: true }),
handler(async (req: Request, res: Response) => {
+
const status = req.body?.status
// If the user is signed in, get an agent which communicates with their server
const agent = await getSessionAgent(req, res, ctx)
+
return void res.redirect(
+
`/login?state=status:${encodeURIComponent(status)}`,
+
await updateStatus(agent, status)
+
return res.redirect('/')
+
const message = err instanceof Error ? err.message : 'Unknown error'
+
return res.redirect(`/?error=${encodeURIComponent(message)}`)
+
async function updateStatus(agent: Agent, status: unknown) {
+
if (typeof status !== 'string' || !STATUS_OPTIONS.includes(status)) {
+
throw new Error('Invalid status')
+
// Construct & validate their status record
+
const rkey = TID.nextStr()
+
$type: 'xyz.statusphere.status',
+
createdAt: new Date().toISOString(),
+
if (!Status.validateRecord(record).success) {
+
throw new Error('Invalid status record')
+
// Write the status record to the user's repository
+
const res = await agent.com.atproto.repo
+
collection: 'xyz.statusphere.status',
+
ctx.logger.error({ err }, 'failed to write record')
+
throw new Error('Failed to write record', { cause: err })
+
// Optimistically update our SQLite
+
// This isn't strictly necessary because the write event will be
+
// handled in #/firehose/ingestor.ts, but it ensures that future reads
+
// will be up-to-date after this method finishes.
+
authorDid: agent.assertDid,
+
createdAt: record.createdAt,
+
indexedAt: new Date().toISOString(),
+
'failed to update computed view; ignoring as it should be caught by the firehose',