Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions src/pages/_api/api/faucet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { createClient, http } from 'viem'
import { tempoModerato } from 'viem/chains'
import { Actions } from 'viem/tempo'

function getClient() {
return createClient({
chain: tempoModerato,
transport: http(),
})
}

export async function POST(request: Request): Promise<Response> {
const origin = request.headers.get('origin')
const corsHeaders = cors(origin)

let body: { address?: string }
try {
body = await request.json()
} catch {
return Response.json(
{ data: null, error: 'Invalid request: could not parse JSON' },
{ status: 400, headers: corsHeaders },
)
}

const address = body?.address
if (!address || !/^0x[a-fA-F0-9]{40}$/.test(address)) {
return Response.json(
{ data: null, error: 'Invalid or missing address' },
{ status: 400, headers: corsHeaders },
)
}

return fund(address.toLowerCase() as `0x${string}`, corsHeaders)
}

export async function OPTIONS(request: Request): Promise<Response> {
const origin = request.headers.get('origin')
return new Response(null, { status: 200, headers: cors(origin) })
}

async function fund(address: `0x${string}`, headers: Record<string, string>): Promise<Response> {
try {
const client = getClient()
const hashes = await Actions.faucet.fund(client, { account: address })

const data = hashes.map((hash) => ({ hash }))
return Response.json({ data, error: null }, { headers })
} catch (error) {
console.error('Faucet error:', error)
const message = error instanceof Error ? error.message : 'Unknown error occurred'
return Response.json({ data: null, error: message }, { status: 500, headers })
}
}

function cors(origin: string | null): Record<string, string> {
const allowedOrigins = ['https://docs.tempo.xyz']

if (origin?.includes('vercel.app')) allowedOrigins.push(origin)
if (process.env.NODE_ENV === 'development') allowedOrigins.push('http://localhost:5173')

const headers: Record<string, string> = {
'Access-Control-Allow-Methods': 'POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, x-api-token',
}

if (origin && allowedOrigins.some((allowed) => origin.startsWith(allowed)))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Match CORS origins exactly instead of using startsWith

The CORS check uses origin.startsWith(allowed), so an attacker-controlled origin like https://docs.tempo.xyz.attacker.com passes the whitelist and receives Access-Control-Allow-Origin. This weakens the intended browser-origin restriction for this endpoint; compare parsed origins/hosts with exact trusted values (or enforce proper subdomain boundary checks) rather than prefix matching.

Useful? React with 👍 / 👎.

headers['Access-Control-Allow-Origin'] = origin

return headers
}
8 changes: 4 additions & 4 deletions src/pages/guide/use-accounts/add-funds.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,14 @@ Send test stablecoins to any address.

<div className="h-4" />

Request tokens programmatically via the [Tempo API](https://api.tempo.xyz/docs).
Request tokens programmatically via the faucet API.

<div className="h-4"/>

```bash
curl "https://api.tempo.xyz/actions/faucet\
?chainId=42431\
&address=<YOUR_ADDRESS>"
curl -X POST https://docs.tempo.xyz/api/faucet \
-H "Content-Type: application/json" \
-d '{"address": "<YOUR_ADDRESS>"}'
```

<div className="h-4"/>
Expand Down
8 changes: 4 additions & 4 deletions src/pages/quickstart/faucet.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,14 @@ Send test stablecoins to any address.

<div className="h-4" />

Request tokens programmatically via the [Tempo API](https://api.tempo.xyz/docs).
Request tokens programmatically via the faucet API.

<div className="h-4"/>

```bash
curl "https://api.tempo.xyz/actions/faucet\
?chainId=42431\
&address=<YOUR_ADDRESS>"
curl -X POST https://docs.tempo.xyz/api/faucet \
-H "Content-Type: application/json" \
-d '{"address": "<YOUR_ADDRESS>"}'
```

<div className="h-4"/>
Expand Down