Something went wrong. Try again.
FlockOff is a mass-block tool for the Atmosphere flockoff.app
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475import { NextResponse } from 'next/server'import { Agent } from '@atproto/api'import { getOAuthClient } from '@/lib/oauth'import { createBridgeToken } from '@/lib/auth-bridge'import { parseAppState, normalizeReturnTo } from '@/lib/oauth-state'import { isAllowedUser } from '@/lib/beta'import { APPVIEW_URL } from '@/lib/appview'
export async function GET(request: Request) { const { searchParams } = new URL(request.url) const appUrl = process.env.NEXT_PUBLIC_APP_URL!
try { const client = await getOAuthClient() const { session, state } = await client.callback(searchParams)
// Scope-upgrade flows carry typed app state with the DID that initiated // the upgrade. If the user switched accounts on the PDS consent screen, // refuse to swap the app session — that would be a confused-account bug. // Note: client.callback() has already exchanged the code, revoked any // prior stored session for the returned DID, and stored the new grant — // that cannot be undone here. On mismatch we only refuse to bind this // browser's iron session to the returned DID. Do not revoke the new grant // either: the returned DID may be a legitimate user of this app, and // revoking would be more destructive than leaving the stored grant alone. const upgradeState = parseAppState(state) if (upgradeState && session.did !== upgradeState.did) { console.error( `[oauth/callback] scope-upgrade DID mismatch: expected ${upgradeState.did}, got ${session.did}` ) const loginUrl = new URL('/login', appUrl) loginUrl.searchParams.set( 'error', 'Permission upgrade was completed with a different account. Please try again with the account you are signed in as.' ) return NextResponse.redirect(loginUrl.toString(), { status: 302 }) }
// Beta allowlist check — if BETA_LIST_URI is set, only list members can log in if (!(await isAllowedUser(session.did))) { const loginUrl = new URL('/login', appUrl) loginUrl.searchParams.set('error', 'access_restricted') return NextResponse.redirect(loginUrl.toString(), { status: 302 }) }
// Fetch handle + avatar via the public AppView. // Using the public agent (not the OAuth agent) is more reliable here — the // OAuth agent requires the session to be fully set up and the PDS to proxy // back to the AppView, which can fail. getProfile is public data. let handle: string | undefined let avatar: string | undefined try { const publicAgent = new Agent(APPVIEW_URL) const profile = await publicAgent.getProfile({ actor: session.did }) handle = profile.data.handle avatar = profile.data.avatar } catch { // Best-effort — DID alone is sufficient to establish a session }
// Upgrades land back where the user was; fresh logins land on the homepage const returnTo = upgradeState ? normalizeReturnTo(upgradeState.returnTo) : '/' const token = createBridgeToken(session.did, handle, avatar, returnTo) const bridgeUrl = new URL('/api/oauth/bridge', appUrl) bridgeUrl.searchParams.set('token', token)
return NextResponse.redirect(bridgeUrl.toString(), { status: 302 }) } catch (err) { console.error('[oauth/callback] failed:', err) const loginUrl = new URL('/login', appUrl) loginUrl.searchParams.set('error', 'Authentication failed. Please try again.') return NextResponse.redirect(loginUrl.toString(), { status: 302 }) }}