-
Notifications
You must be signed in to change notification settings - Fork 13
fixed sendlink pwd security, should be same speed. #1347
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
13d937f
9aea57e
b9afe99
d17328d
0cd4a3b
4d47477
c0992c8
5b01344
0e42b0a
015b879
a49a68a
ace5867
85b09c1
0038b8f
e6aecb1
657f6fb
594ea77
a2e6af0
6d47059
acaf412
a7f718f
45df69e
f1212d5
63ea9e9
830f640
27195fc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,40 +1,64 @@ | ||
| import { getLinkDetails } from '@/app/actions/claimLinks' | ||
| import { Claim } from '@/components' | ||
| import { BASE_URL } from '@/constants' | ||
| import { BASE_URL, PEANUT_WALLET_TOKEN_DECIMALS, PEANUT_WALLET_TOKEN_SYMBOL } from '@/constants' | ||
| import { formatAmount, resolveAddressToUsername } from '@/utils' | ||
| import { type Metadata } from 'next' | ||
| import getOrigin from '@/lib/hosting/get-origin' | ||
| import { sendLinksApi } from '@/services/sendLinks' | ||
| import { formatUnits } from 'viem' | ||
|
|
||
| export const dynamic = 'force-dynamic' | ||
|
|
||
| async function getClaimLinkData(searchParams: { [key: string]: string | string[] | undefined }, siteUrl: string) { | ||
| if (!searchParams.i || !searchParams.c) return null | ||
|
|
||
| try { | ||
| const queryParams = new URLSearchParams() | ||
| Object.entries(searchParams).forEach(([key, val]) => { | ||
| if (Array.isArray(val)) { | ||
| val.forEach((v) => queryParams.append(key, v)) | ||
| } else if (val) { | ||
| queryParams.append(key, val) | ||
| } | ||
| }) | ||
| // Use backend API with belt-and-suspenders logic (DB + blockchain fallback) | ||
| const contractVersion = (searchParams.v as string) || 'v4.3' | ||
|
|
||
| const url = `${siteUrl}/claim?${queryParams.toString()}` | ||
| const linkDetails = await getLinkDetails(url) | ||
| const sendLink = await sendLinksApi.getByParams({ | ||
| chainId: searchParams.c as string, | ||
| depositIdx: searchParams.i as string, | ||
| contractVersion, | ||
| }) | ||
|
Comment on lines
+18
to
22
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Server bundle pulls in
Fix: fetch directly here (server-safe) or create a server-only wrapper module. Example inline replacement: import { PEANUT_API_URL } from '@/constants'
import { fetchWithSentry, jsonParse } from '@/utils'
const sendLink = await (async () => {
const url = `${PEANUT_API_URL}/send-links?c=${searchParams.c}&v=${searchParams.v}&i=${searchParams.i}`
const res = await fetchWithSentry(url, { method: 'GET' })
if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`)
return jsonParse(await res.text())
})()Alternatively, add a new 🤖 Prompt for AI Agents |
||
| // Backend always provides token details (from DB or blockchain fallback) | ||
| // Use fallback from consts if not available | ||
| const tokenDecimals = sendLink.tokenDecimals ?? PEANUT_WALLET_TOKEN_DECIMALS | ||
| const tokenSymbol = sendLink.tokenSymbol ?? PEANUT_WALLET_TOKEN_SYMBOL | ||
|
|
||
| // Transform to linkDetails format for metadata` | ||
| const linkDetails = { | ||
| senderAddress: sendLink.senderAddress, | ||
| tokenAmount: formatUnits(sendLink.amount, tokenDecimals), | ||
| tokenSymbol, | ||
| claimed: sendLink.status === 'CLAIMED' || sendLink.status === 'CANCELLED', | ||
| } | ||
|
|
||
| // Get username from sender address | ||
| const username = linkDetails?.senderAddress | ||
| ? await resolveAddressToUsername(linkDetails.senderAddress, siteUrl) | ||
| : null | ||
| // Get username from sender - use sender.username if available (from backend) | ||
| let username: string | null = sendLink.sender?.username || null | ||
|
|
||
| // If no username in backend data, try ENS resolution with timeout | ||
Hugo0 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| if (!username && linkDetails.senderAddress) { | ||
| try { | ||
| // ENS race condition - catch errors to prevent Promise.race from throwing | ||
| const timeoutPromise = new Promise<null>((resolve) => setTimeout(() => resolve(null), 3000)) | ||
| const resolvePromise = resolveAddressToUsername(linkDetails.senderAddress, siteUrl).catch((err) => { | ||
| console.error('ENS resolution failed:', err) | ||
| return null | ||
| }) | ||
| username = await Promise.race([resolvePromise, timeoutPromise]) | ||
| } catch (ensError) { | ||
| console.error('ENS resolution failed:', ensError) | ||
| username = null | ||
| } | ||
| } | ||
|
|
||
| if (username) { | ||
| console.log('username', username) | ||
| console.log('Resolved username:', username) | ||
| } | ||
|
Comment on lines
55
to
57
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid logging usernames in server logs.
- if (username) {
- console.log('Resolved username:', username)
- }
+ // optionally emit only in debug builds
🤖 Prompt for AI Agents |
||
|
|
||
| return { linkDetails, username } | ||
| } catch (e) { | ||
| console.log('error: ', e) | ||
| console.error('Error fetching claim link data:', e) | ||
| return null | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -194,7 +194,7 @@ export default function PaymentPage({ recipient, flow = 'request_pay' }: Props) | |||||
| dispatch(paymentActions.setView('INITIAL')) | ||||||
| } | ||||||
| } else { | ||||||
| setError(getErrorProps({ error, isUser: !!user })) | ||||||
| setError(getErrorProps({ error, isUser: !!user, recipient })) | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
|
|
@@ -587,14 +587,26 @@ const getDefaultError: (isUser: boolean) => ValidationErrorViewProps = (isUser) | |||||
| redirectTo: isUser ? '/home' : '/setup', | ||||||
| }) | ||||||
|
|
||||||
| function getErrorProps({ error, isUser }: { error: ParseUrlError; isUser: boolean }): ValidationErrorViewProps { | ||||||
| function getErrorProps({ | ||||||
| error, | ||||||
| isUser, | ||||||
| recipient, | ||||||
| }: { | ||||||
| error: ParseUrlError | ||||||
| isUser: boolean | ||||||
| recipient: string[] | ||||||
| }): ValidationErrorViewProps { | ||||||
| const username = recipient[0] || 'unknown' | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add fallback for potential undefined recipient. If the Consider this defensive adjustment: - const username = recipient[0] || 'unknown'
+ const username = (recipient && recipient[0]) || 'this user'This provides a more natural fallback message if the recipient array is empty or undefined. 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
|
|
||||||
| switch (error.message) { | ||||||
| case EParseUrlError.INVALID_RECIPIENT: | ||||||
| return { | ||||||
| title: 'Invalid Recipient', | ||||||
| message: 'The recipient you are trying to pay is invalid. Please check the URL and try again.', | ||||||
| buttonText: isUser ? 'Go to home' : 'Create your Peanut Wallet', | ||||||
| redirectTo: isUser ? '/home' : '/setup', | ||||||
| title: `We don't know any @${username}`, | ||||||
| message: 'Are you sure you clicked on the right link?', | ||||||
| buttonText: 'Go back to home', | ||||||
| redirectTo: '/home', | ||||||
| showLearnMore: false, | ||||||
| supportMessageTemplate: 'I clicked on this link but got an error: {url}', | ||||||
| } | ||||||
| case EParseUrlError.INVALID_CHAIN: | ||||||
| return { | ||||||
|
|
||||||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,13 +1,16 @@ | ||
| 'use client' | ||
|
|
||
| import StatusViewWrapper from '@/components/Global/StatusViewWrapper' | ||
| import ValidationErrorView from '@/components/Payment/Views/Error.validation.view' | ||
|
|
||
| export const NotFoundClaimLink = () => { | ||
| return ( | ||
| <StatusViewWrapper | ||
| title="Sorryyy" | ||
| description="This link is malformed. Are you sure you copied it correctly?" | ||
| supportCtaText="Deposit not found. Are you sure your link is correct?" | ||
| <ValidationErrorView | ||
| title="This link seems broken!" | ||
| message="Are you sure you clicked on the right link?" | ||
| buttonText="Go back to home" | ||
| redirectTo="/home" | ||
| showLearnMore={false} | ||
| supportMessageTemplate="I clicked on this link but got an error: {url}" | ||
| /> | ||
| ) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,12 +1,16 @@ | ||
| 'use client' | ||
|
|
||
| import StatusViewWrapper from '@/components/Global/StatusViewWrapper' | ||
| import ValidationErrorView from '@/components/Payment/Views/Error.validation.view' | ||
|
|
||
| export const WrongPasswordClaimLink = () => { | ||
| return ( | ||
| <StatusViewWrapper | ||
| title="Sorryyy" | ||
| description="This link is malformed. Are you sure you copied it correctly?" | ||
| <ValidationErrorView | ||
| title="This link seems broken!" | ||
| message="Are you sure you clicked on the right link?" | ||
| buttonText="Go back to home" | ||
| redirectTo="/home" | ||
| showLearnMore={false} | ||
| supportMessageTemplate="I clicked on this link but got an error: {url}" | ||
| /> | ||
| ) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
did we need this? what about the other params? what do we need them for?