feat(security): enforce auth and Zod validation in Server Actions (#802)#846
Conversation
…ndfi-org#802) Server Actions ("use server") are exposed as public endpoints, so each one must verify its caller and validate inputs even when middleware has already run. This change introduces a shared auth/validation layer and applies it to every action under apps/web/app/actions: - Add lib/auth/server-action-auth helpers: requireAuthenticatedSession, requireAdminSession, validateInput (Zod), enforceRateLimit, and a ServerActionError -> structured failure helper. - Add lib/schemas/server-actions.schemas with strict Zod schemas for every action input (UUIDs, emails, escrow status enum, Stellar shapes, foundation create payload, etc). - auth.ts: await CSRF check (was unawaited Promise -> always passed), validate sign-up / reset / session / device payloads, gate all escrow admin actions behind requireAdminSession, derive userId from the session in updateDeviceWithDeployee instead of trusting client input, rate-limit sensitive flows, and disable insertTestEscrowRecordAction in production. - foundations/create-foundation.ts: require auth, Zod-validate input, rate-limit, and emit structured audit logs. - escrow/save-escrow-contract.ts: require auth, Zod-validate input, rate-limit, and emit structured audit logs alongside the existing ownership/admin authorization check. Closes kindfi-org#802
|
@sasasamaes is attempting to deploy a commit to the kindfi Team on Vercel. A member of the Team first needs to authorize it. |
|
@sasasamaes Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
|
Warning Rate limit exceeded
To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Repository UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (3)
WalkthroughCentralized server-action auth, validation, and rate-limiting utilities were added and applied across multiple server actions. Actions (auth, escrow, foundation, device) now enforce session/admin checks, validate inputs with Zod schemas, apply per-identifier rate limits, and return standardized ServerActionFailure responses and structured logs. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant ServerAction as Server Action
participant Auth as Auth Handler
participant Validator as Input Validator
participant RateLimit as Rate Limiter
participant DB as Database
participant Logger
Client->>ServerAction: Invoke action with payload
ServerAction->>Auth: requireAuthenticatedSession / requireAdminSession
Auth-->>ServerAction: Session or throw UNAUTHORIZED/FORBIDDEN
alt Auth OK
ServerAction->>Validator: validateInput(schema, payload)
Validator-->>ServerAction: validated input or throw VALIDATION_ERROR
alt Valid Input
ServerAction->>RateLimit: enforceRateLimit(identifier, action)
RateLimit-->>ServerAction: allow or throw RATE_LIMITED
alt Allowed
ServerAction->>DB: perform DB operation using validated data
DB-->>ServerAction: result
ServerAction->>Logger: info(success)
ServerAction-->>Client: success response
else Rate Limited
ServerAction->>Logger: warn(rate limited)
ServerAction-->>Client: { success: false, code: "RATE_LIMITED" }
end
else Invalid
ServerAction->>Logger: warn(validation failed)
ServerAction-->>Client: { success: false, code: "VALIDATION_ERROR", details }
end
else Auth Failed
ServerAction->>Logger: warn(unauthorized/forbidden)
ServerAction-->>Client: { success: false, code: "UNAUTHORIZED"|"FORBIDDEN" }
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
apps/web/app/actions/auth.ts (1)
713-829:⚠️ Potential issue | 🟠 MajorConsider rate-limiting
updateDeviceWithDeployeebefore the DB lookup/update.The auth and validation improvements are solid here. This path still lacks throttling, though, so an authenticated caller can hammer credential lookups and repeated device writes. Adding
enforceRateLimit(session.user.id, 'update_device_with_deployee')before the query would keep this sensitive action aligned with the rest of the PR’s protections.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/app/actions/auth.ts` around lines 713 - 829, Before performing the DB lookup/update in updateDeviceWithDeployee, call enforceRateLimit(session.user.id, 'update_device_with_deployee') to throttle callers; place this immediately after you extract userId (const userId = session.user.id) and before the db.select(...) block, catch any error thrown by enforceRateLimit and return a structured failure (success: false, appropriate message/error) consistent with other early returns so rate-limit rejections are surfaced to callers and no DB work is performed for throttled requests.apps/web/app/actions/escrow/save-escrow-contract.ts (1)
162-239:⚠️ Potential issue | 🟠 MajorConsider making the contribution/escrow/link writes atomic.
We could enhance this by wrapping these dependent writes in a transaction or a single database function. Right now, if
contributionssucceeds butescrow_contractsorproject_escrowsfails, the action returns an error after partially mutating state, leaving orphaned records behind.Based on learnings, "In KindFi's project-escrow system, there is a strict 1:1 relationship enforced between projects and escrows. Each project must have exactly one escrow, and each escrow can belong to only one project."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/app/actions/escrow/save-escrow-contract.ts` around lines 162 - 239, The three dependent writes (insert into contributions, upsert into escrow_contracts, upsert into project_escrows) must be executed atomically to avoid orphaned records; change the implementation so the logic in save-escrow-contract.ts (the contributions insert, escrow_contracts upsert, and project_escrows upsert) is performed inside a single DB transaction or a single Postgres stored procedure and invoked via supabase.rpc instead of separate supabase.from calls. Concretely: implement a Postgres function (e.g., create_or_update_project_escrow(contributor_id, project_id, contract_id, payer_address, receiver_address, amount, platform_fee, engagement_id)) that inserts the contribution, upserts the escrow_contracts row, and upserts project_escrows with proper 1:1 constraints, then replace the three separate calls (the block using supabase.from('contributions').insert(...), the upsert on escrow_contracts, and the upsert on project_escrows) with a single supabase.rpc call to that function and handle errors from the RPC call accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/web/app/actions/auth.ts`:
- Around line 297-302: The current try block always redirects to a success URL
even though no reset logic runs; update the auth reset handler so it either
performs the real reset flow before calling redirect or returns an explicit "not
implemented" error/status instead of success. Specifically, implement or call
the actual reset-account routine (the function that sends the confirmation email
/ creates a reset token) and only invoke redirect('/reset-account?success=...')
when that routine succeeds, otherwise throw or return a clear error/redirect
like '/reset-account?error=not_available' and remove the premature success
redirect; look for the try block and the redirect call in this file to locate
where to add the reset logic or the not-implemented response.
In `@apps/web/app/actions/escrow/save-escrow-contract.ts`:
- Around line 125-147: Remove the fabricated fallbacks in save-escrow-contract
(the computed engagementId, totalAmount, payerAddress, receiverAddress and
platformFee) and instead validate that validated.escrowData and its required
properties (engagementId or projectId present, non-empty milestones or amount,
roles with payer/approver, receiver on milestones or top-level receiver, and
platformFee presence) are present before persisting; if any required fields are
missing, reject the request by returning/throwing a validation error (use the
existing validation response pattern in this action) rather than generating
defaults like `project-${projectId}`, amount `1`, or the dummy G000... address.
Ensure you update the code paths that currently rely on engagementId,
totalAmount, payerAddress, receiverAddress, and platformFee to only run after
these explicit validations pass.
In `@apps/web/lib/auth/server-action-auth.ts`:
- Around line 117-142: The current enforceRateLimit helper swallows rate limiter
errors and keeps actions runnable (fail-open); change it to fail-closed outside
development by detecting the runtime environment (e.g., NODE_ENV or an existing
isDev/isLocal flag) and, when rateLimiter.increment throws and the environment
is not local/dev, rethrow or throw a ServerActionError (e.g., 'Rate limiter
unavailable' with a distinct code like 'RATE_LIMITER_UNAVAILABLE') instead of
silently continuing; keep the existing logger.warn for observability but ensure
enforceRateLimit rethrows in production so abuse protection remains enforced.
In `@apps/web/lib/schemas/server-actions.schemas.ts`:
- Around line 25-29: The schema updateEscrowFinancialsInputSchema allows
released to exceed funded; add a cross-field refinement (using
z.object().superRefine or .refine on the object) to validate that released <=
funded and return a clear error message (attach the error to the released field
via ctx.addIssue in superRefine if possible) so invalid combos are rejected
before DB writes.
- Around line 67-88: The role and receiver address fields currently accept any
non-empty string; update the schemas to use the centralized stellarAddressSchema
instead of z.string().min(1) where appropriate: replace roles.approver,
roles.serviceProvider, roles.disputeResolver, roles.platformAddress,
roles.releaseSigner, escrowDataSchema's receiver, and escrowMilestoneSchema's
receiver with stellarAddressSchema, and keep existing numeric/optional
constraints (e.g., milestones array, amount) intact so validation enforces
proper Stellar account format across escrowDataSchema and escrowMilestoneSchema.
---
Outside diff comments:
In `@apps/web/app/actions/auth.ts`:
- Around line 713-829: Before performing the DB lookup/update in
updateDeviceWithDeployee, call enforceRateLimit(session.user.id,
'update_device_with_deployee') to throttle callers; place this immediately after
you extract userId (const userId = session.user.id) and before the
db.select(...) block, catch any error thrown by enforceRateLimit and return a
structured failure (success: false, appropriate message/error) consistent with
other early returns so rate-limit rejections are surfaced to callers and no DB
work is performed for throttled requests.
In `@apps/web/app/actions/escrow/save-escrow-contract.ts`:
- Around line 162-239: The three dependent writes (insert into contributions,
upsert into escrow_contracts, upsert into project_escrows) must be executed
atomically to avoid orphaned records; change the implementation so the logic in
save-escrow-contract.ts (the contributions insert, escrow_contracts upsert, and
project_escrows upsert) is performed inside a single DB transaction or a single
Postgres stored procedure and invoked via supabase.rpc instead of separate
supabase.from calls. Concretely: implement a Postgres function (e.g.,
create_or_update_project_escrow(contributor_id, project_id, contract_id,
payer_address, receiver_address, amount, platform_fee, engagement_id)) that
inserts the contribution, upserts the escrow_contracts row, and upserts
project_escrows with proper 1:1 constraints, then replace the three separate
calls (the block using supabase.from('contributions').insert(...), the upsert on
escrow_contracts, and the upsert on project_escrows) with a single supabase.rpc
call to that function and handle errors from the RPC call accordingly.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 762af42e-a4ad-425a-a6bb-e304a3b8ee26
📒 Files selected for processing (5)
apps/web/app/actions/auth.tsapps/web/app/actions/escrow/save-escrow-contract.tsapps/web/app/actions/foundations/create-foundation.tsapps/web/lib/auth/server-action-auth.tsapps/web/lib/schemas/server-actions.schemas.ts
…ions Address review feedback on PR kindfi-org#846: - Fail closed on rate limiter errors in production so abuse protection stays in place during a Redis outage; keep dev behaviour permissive. - Reject incomplete escrow payloads instead of fabricating placeholder Stellar addresses, engagement IDs and amounts in saveEscrowContract. - Apply stellarAddressSchema to escrow role and receiver fields so malformed account IDs are rejected before persistence. - Reject released > funded in updateEscrowFinancialsInputSchema. - Stop showing a misleading success redirect in requestResetAccountAction while the reset flow is still a TODO; surface a clear unavailable error.
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/web/app/actions/escrow/save-escrow-contract.ts (1)
16-65: 🧹 Nitpick | 🔵 TrivialDrop the duplicated top-level
engagementIdand preferz.inferoveras SaveEscrowContractParams.The interface and validation flow look clean overall. Two small polish opportunities:
- The top-level
engagementId?: string(line 19) is unused — line 126 readsescrowData.engagementId. The mirror insaveEscrowContractInputSchema(engagementId: z.string().min(1).optional()) is similarly unread. Removing both keeps the contract honest.validated = validateInput(...) as SaveEscrowContractParamson line 61 throws away the precise Zod-inferred shape (e.g. theescrowMilestoneSchemaelement types). We could enhance type safety by deriving the type from the schema instead.♻️ Proposed refactor
-import { saveEscrowContractInputSchema } from '~/lib/schemas/server-actions.schemas' +import { + saveEscrowContractInputSchema, + type SaveEscrowContractInput, +} from '~/lib/schemas/server-actions.schemas' @@ -interface SaveEscrowContractParams { - projectId: string - contractId: string - engagementId?: string - escrowData: { - … - } -} +type SaveEscrowContractParams = SaveEscrowContractInput @@ - let validated: SaveEscrowContractParams - try { - validated = validateInput( - saveEscrowContractInputSchema, - params, - 'saveEscrowContractAction', - ) as SaveEscrowContractParams + let validated: SaveEscrowContractParams + try { + validated = validateInput( + saveEscrowContractInputSchema, + params, + 'saveEscrowContractAction', + )…with a matching export on the schema module:
export const saveEscrowContractInputSchema = z.object({ … }) +export type SaveEscrowContractInput = z.infer<typeof saveEscrowContractInputSchema>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/app/actions/escrow/save-escrow-contract.ts` around lines 16 - 65, Remove the duplicated top-level engagementId from the SaveEscrowContractParams interface and from the corresponding validation schema (so only escrowData.engagementId is used/read), then improve typing by deriving the validated variable from the Zod schema instead of using a blind cast: replace the "as SaveEscrowContractParams" cast in saveEscrowContractAction with a z.infer<typeof saveEscrowContractInputSchema>-based type (export that inferred type from the schema module if necessary) so types like the escrowMilestoneSchema element types are preserved; ensure validateInput(...) still uses saveEscrowContractInputSchema and that references like escrowData.engagementId remain unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/web/app/actions/auth.ts`:
- Around line 700-742: The function updateDeviceWithDeployee is missing a
rate-limit check; after obtaining the authenticated userId (const userId =
session.user.id) add an enforceRateLimit call using the same pattern/signature
used elsewhere in this file (e.g., enforceRateLimit('updateDeviceWithDeployee',
userId, ...)) immediately before side-effectful work so this per-user action is
throttled; locate requireAuthenticatedSession and
updateDeviceWithDeployeeInputSchema/validateInput to mirror where other handlers
call enforceRateLimit and pass the same parameters/limits they use.
In `@apps/web/app/actions/escrow/save-escrow-contract.ts`:
- Around line 125-153: Update the payer/receiver mapping and multi-receiver
milestone handling: change the payerAddress assignment in
save-escrow-contract.ts from escrowData.roles.serviceProvider to
escrowData.roles.approver (or add an explicit comment if keeping current
mapping) so the funder is used as the payer, and stop collapsing milestone
receivers into milestones[0].receiver — either tighten the Zod schema to require
a single shared receiver across all milestones or modify the persistence logic
to store per-milestone receiver fields (update totalAmount calculation to sum
milestone.amounts and preserve each milestone.receiver when writing to
Supabase); also add validation to reject payloads that violate the chosen
constraint so malformed inputs are not silently accepted.
In `@apps/web/lib/auth/server-action-auth.ts`:
- Around line 119-152: The rate-limit handler currently assumes
rateLimiter.increment() will throw on Redis failure, but increment() actually
returns { isBlocked: false, attemptsRemaining, error } on failure, so the outer
catch is unreachable; update enforceRateLimit(identifier, action) to examine the
returned result.error and, if result.error is set, log
SERVER_ACTION_RATE_LIMIT_UNAVAILABLE with the error and—when
NODE_ENV==='production'—throw a new ServerActionError('Service temporarily
unavailable. Please try again later.','RATE_LIMITED'); similarly update other
callers (confirm route, nfts/mint route, quests/progress route) that only check
result.isBlocked to also check result.error and enforce the same production
fail-closed behavior.
- Around line 96-110: Replace the deprecated Zod error formatting by calling
z.treeifyError on the Zod error instead of result.error.format(); specifically,
in the validation block around schema.safeParse(input) where you currently log
and throw using result.error.format(), import or reference z from 'zod' and use
z.treeifyError(result.error) for both the logger.details and the
ServerActionError payload (i.e., replace result.error.format() with
z.treeifyError(result.error) in the logger.warn call and in the new
ServerActionError invocation so the code uses the Zod 4 structured-error API).
---
Outside diff comments:
In `@apps/web/app/actions/escrow/save-escrow-contract.ts`:
- Around line 16-65: Remove the duplicated top-level engagementId from the
SaveEscrowContractParams interface and from the corresponding validation schema
(so only escrowData.engagementId is used/read), then improve typing by deriving
the validated variable from the Zod schema instead of using a blind cast:
replace the "as SaveEscrowContractParams" cast in saveEscrowContractAction with
a z.infer<typeof saveEscrowContractInputSchema>-based type (export that inferred
type from the schema module if necessary) so types like the
escrowMilestoneSchema element types are preserved; ensure validateInput(...)
still uses saveEscrowContractInputSchema and that references like
escrowData.engagementId remain unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: b4d0fa50-1590-4288-ac26-d7ecae477de5
📒 Files selected for processing (4)
apps/web/app/actions/auth.tsapps/web/app/actions/escrow/save-escrow-contract.tsapps/web/lib/auth/server-action-auth.tsapps/web/lib/schemas/server-actions.schemas.ts
| const { escrowData } = validated | ||
| const engagementId = escrowData.engagementId | ||
| const payerAddress = escrowData.roles.serviceProvider | ||
| const platformFee = escrowData.platformFee | ||
|
|
||
| const milestones = escrowData.milestones | ||
| let totalAmount: number | ||
| let receiverAddress: string | ||
| if (milestones && milestones.length > 0) { | ||
| totalAmount = milestones.reduce((sum, m) => sum + m.amount, 0) | ||
| receiverAddress = milestones[0].receiver | ||
| } else if ( | ||
| escrowData.amount !== undefined && | ||
| escrowData.receiver !== undefined | ||
| ) { | ||
| totalAmount = escrowData.amount | ||
| receiverAddress = escrowData.receiver | ||
| } else { | ||
| logger.warn({ | ||
| eventType: 'ESCROW_CONTRACT_INVALID_PAYLOAD', | ||
| userId, | ||
| projectId: validated.projectId, | ||
| }) | ||
| return { | ||
| success: false, | ||
| error: | ||
| 'Escrow data must include either milestones or single-release amount and receiver', | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# 1) Look for the call-site definition of the escrow payload to see how
# serviceProvider is treated relative to payerAddress historically.
rg -nP --type=ts -C3 '\bpayer_address\b|\bpayerAddress\b'
echo '---'
# 2) Confirm whether multi-receiver milestone payloads exist in the codebase
# or are only single-receiver.
rg -nP --type=ts -C2 '\bmilestones\s*[:=]\s*\['Repository: kindfi-org/kindfi
Length of output: 7607
🏁 Script executed:
# First, let's understand the escrowData structure and roles
rg -nP --type=ts 'roles\s*[:=]|serviceProvider|platformAddress|approver' apps/web/app/actions/escrow/save-escrow-contract.ts -B2 -A2
echo "---"
# Look at the escrowData type definition
fd -e ts -path '*/types/*' | xargs grep -l 'escrowData\|SaveEscrowContractParams' | head -5
echo "---"
# Check escrow input schema to understand roles structure
rg -nP --type=ts 'roles.*schema|rolesSchema' -A10
echo "---"
# Look for actual milestone payloads with different receivers
rg -nP --type=ts 'receiver.*milestone|milestone.*receiver' -B3 -A3 | head -40Repository: kindfi-org/kindfi
Length of output: 5058
🏁 Script executed:
# Look at the engagement context to understand who initiates/funds the escrow
rg -nP --type=ts 'engagementId|engagement' apps/web/app/actions/escrow/save-escrow-contract.ts -B3 -A3 | head -50
echo "---"
# Check how serviceProvider and approver are used in the roles
rg -nP --type=ts 'approver|releaseSigner|platformAddress' apps/web/app/actions/escrow/save-escrow-contract.ts -B2 -A2
echo "---"
# Look for any tests or documentation about payer expectations
fd -e ts -e md | xargs grep -l 'payer.*semantic\|payer.*fund\|serviceProvider.*payer' 2>/dev/null | head -5
echo "---"
# Check if there are any multi-milestone test fixtures with different receivers
rg -nP 'milestones.*\[' -A20 apps/web/lib/mock-data/ --type=ts | grep -A20 'receiver'Repository: kindfi-org/kindfi
Length of output: 986
🏁 Script executed:
# Look at how the roles object is created/populated - where does it come from?
rg -nP --type=ts 'roles\s*:\s*\{|roles\.serviceProvider|roles\.approver' -B5 -A5 | head -80
echo "---"
# Search for any comments or documentation about role semantics
rg -nP --type=ts 'approver.*fund|payer.*approver|serviceProvider.*fund|serviceProvider.*receive' -B2 -A2
echo "---"
# Look at the engagement or project context to see who is the "payer"
fd -e ts -path '*/types/*' -name '*engagement*' -o -name '*project*' | head -10
echo "---"
# Check if there's any role validation or assignment logic
rg -nP --type=ts 'serviceProvider\s*=' -B5 -A5 | head -60Repository: kindfi-org/kindfi
Length of output: 7132
🏁 Script executed:
# Look at the roles card component to see what approver does vs serviceProvider
cat apps/web/components/sections/projects/manage/escrow/components/roles-card.tsx | head -100
echo "---"
# Check if there's any documentation about engagement roles
fd -e md | xargs grep -l 'payer\|approver\|serviceProvider' 2>/dev/null | head -3
echo "---"
# Look at how Trustless Work API expects payer vs receiver
rg -nP 'InitializeSingleReleaseEscrowPayload|InitializeMultiReleaseEscrowPayload' -A30 | head -80Repository: kindfi-org/kindfi
Length of output: 9855
🏁 Script executed:
# Search for the Trustless Work escrow types to understand the expected payer role
fd -type f -name "*.ts" -path "*node_modules/@trustless-work*" 2>/dev/null | head -3
echo "---"
# Look for any code that calls fundEscrow or payment logic
rg -n 'fundEscrow|releaseFunds|payment' --type=ts -B3 -A3 | head -60
echo "---"
# Check for multi-milestone test cases or examples
rg -n 'milestones.*\[.*\{.*receiver' --type=ts -A5 -B5 | head -80Repository: kindfi-org/kindfi
Length of output: 4601
🏁 Script executed:
# Look for who actually funds/pays into the escrow
rg -n 'approver.*fund|approver.*payer|fundEscrow.*approver' --type=ts -B5 -A5
echo "---"
# Check if there are any examples in docs or tests about escrow flow
fd -e md -o -e test.ts | xargs grep -l 'escrow\|payer\|approver' 2>/dev/null | head -5
echo "---"
# Look at the actual escrow save function to see all roles being used
sed -n '16,220p' apps/web/app/actions/escrow/save-escrow-contract.tsRepository: kindfi-org/kindfi
Length of output: 5496
🏁 Script executed:
# Look at how engagement or projects define who pays vs receives
rg -n 'approver.*=' apps/web/components/sections/projects/manage/escrow/hooks/use-escrow-transaction.ts -B5 -A5 | head -60
echo "---"
# Check if approver is the one who funds/initiates the escrow
rg -n 'formData\.approver' apps/web/components/sections/projects/manage/escrow/ --type=ts -B3 -A3 | head -40
echo "---"
# Look for any TODO or FIXME comments about payer/receiver mapping
rg -n 'TODO|FIXME|BUG' apps/web/app/actions/escrow/save-escrow-contract.ts --type=tsRepository: kindfi-org/kindfi
Length of output: 3777
🏁 Script executed:
# Look at who initiates/signs the escrow transaction
rg -n 'signer|initiator' apps/web/components/sections/projects/manage/escrow/hooks/use-escrow-transaction.ts -B2 -A2 | head -40
echo "---"
# Check if approver is typically the one who funds in Stellar escrow patterns
rg -n 'approver' apps/web/lib/ --type=ts -B3 -A3 | head -60
echo "---"
# Look at the contribution table to see who is tracked
rg -n 'contributor_id' services/supabase/src/database.schemas.ts -B3 -A3Repository: kindfi-org/kindfi
Length of output: 5685
🏁 Script executed:
# Check the validator more carefully - what does it actually validate?
cat apps/web/lib/validators/escrow.ts | sed -n '40,60p'
echo "---"
# Let's trace what approver is supposed to be - check form context
rg -n 'approver' apps/web/components/sections/projects/manage/escrow/context/escrow-form-context.tsx -B3 -A3
echo "---"
# Confirm: in the actual escrow payload to Trustless Work, what role does the signer have?
rg -n 'signer.*=' apps/web/components/sections/projects/manage/escrow/hooks/use-escrow-transaction.ts -B1 -A1 | head -30Repository: kindfi-org/kindfi
Length of output: 1032
Verify the payer/receiver role mapping and handle multi-receiver milestones.
Two issues worth addressing before merge:
-
Payer semantics. Line 127 sets
payerAddress = escrowData.roles.serviceProvider, but the role descriptions indicate the approver is the entity "requiring the service" (typically the funder), while the service provider delivers work and should receive payment. Consider whether payer should beescrowData.roles.approverinstead, or add a clarifying comment if this mapping is intentional. -
Multi-receiver milestones. Line 135 collapses all milestone receivers to
milestones[0].receiver, discarding others. The Zod schema allows each milestone to have its own receiver, so an escrow with two different recipient addresses will silently store only the first. Either refine the schema to require all milestones share a single receiver, or extend the database to persist per-milestone receivers.
Both pass through silently rather than being rejected, so a malformed payload still reaches Supabase.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/app/actions/escrow/save-escrow-contract.ts` around lines 125 - 153,
Update the payer/receiver mapping and multi-receiver milestone handling: change
the payerAddress assignment in save-escrow-contract.ts from
escrowData.roles.serviceProvider to escrowData.roles.approver (or add an
explicit comment if keeping current mapping) so the funder is used as the payer,
and stop collapsing milestone receivers into milestones[0].receiver — either
tighten the Zod schema to require a single shared receiver across all milestones
or modify the persistence logic to store per-milestone receiver fields (update
totalAmount calculation to sum milestone.amounts and preserve each
milestone.receiver when writing to Supabase); also add validation to reject
payloads that violate the chosen constraint so malformed inputs are not silently
accepted.
There was a problem hiding this comment.
Fixed in d1b0229.
-
Payer mapping: changed
payerAddressfromescrowData.roles.serviceProvidertoescrowData.roles.approver. The UI tooltips confirm the role semantics — the approver is "the entity requiring the service" (funder), and the service provider "delivers the work" (recipient). Added a clarifying comment alongside the assignment. -
Multi-receiver milestones: tightened
escrowDataSchemawith a refinement that rejects payloads where milestones have differingreceiveraddresses. Since the DB schema stores a singlereceiver_addressper escrow, accepting multi-receiver payloads would silently drop data. The action keeps readingmilestones[0].receiver, which is now safe by schema invariant — and a clarifying comment marks the assumption.
There was a problem hiding this comment.
@sasasamaes, both fixes look excellent — thank you for the thorough follow-through!
-
Payer mapping — Using
escrowData.roles.approveris semantically correct and the clarifying comment makes the intent explicit for future maintainers. We could further enhance this by referencing the UI tooltip language directly in the comment (e.g., "approver = entity requiring the service / funder"), but the current state is already clear. -
Multi-receiver milestones — Enforcing single-receiver invariant at the schema layer is the right approach here. The
milestones[0].receiverread is now provably safe, and the comment documenting the invariant dependency is a nice touch. This is a clean solution that keeps the persistence logic simple while preventing silent data loss.
🧠 Learnings used
Learnt from: AndlerRL
Repo: kindfi-org/kindfi PR: 128
File: apps/web/lib/types/project.types.ts:10-24
Timestamp: 2025-02-08T07:43:42.433Z
Learning: Escrow-related functionality in the project requires coordination between project table and escrow tables in Supabase for proper relationship management.
Learnt from: AndlerRL
Repo: kindfi-org/kindfi PR: 0
File: :0-0
Timestamp: 2025-01-28T10:05:16.931Z
Learning: The escrow system in this project follows a specific structure where:
1. API routes are placed in `apps/web/app/(api-routes)/escrow/`
2. Shared types and validators are in `lib/types` and `lib/validators`
3. The system uses Supabase for contract tracking
4. TL API integration is handled through the lib folder
Learnt from: AndlerRL
Repo: kindfi-org/kindfi PR: 0
File: :0-0
Timestamp: 2025-08-20T08:33:37.533Z
Learning: The Trustless Work API multi-release escrow creation requires a two-step process: 1) Create a canonical transaction hash that commits to all escrow parameters (engagementId, milestones, platformFee, etc.), 2) Use that transaction hash to generate the unsigned transaction XDR from the API. The transaction hash creation step cannot be skipped as it's fundamental to how Trustless Work API works.
Learnt from: AndlerRL
Repo: kindfi-org/kindfi PR: 0
File: :0-0
Timestamp: 2025-08-20T07:36:51.166Z
Learning: Issues `#691`, `#690`, `#692` cover milestone approval and admin controls. Issues `#688`, `#689` cover admin KYC dashboard. Issues `#679`, `#678` cover project management UI. These should be referenced to avoid duplication in future escrow-related issues.
Learnt from: AndlerRL
Repo: kindfi-org/kindfi PR: 498
File: apps/web/app/api/escrow/dispute/route.ts:145-150
Timestamp: 2025-05-02T07:27:25.203Z
Learning: In the escrow dispute system, once a milestone enters the disputed state, tracking shifts from the milestone level to the escrow level. The milestone "freezes" until the dispute is resolved, making milestone status update error checking less critical in this specific context.
Learnt from: CR
Repo: kindfi-org/kindfi PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-04T22:00:57.967Z
Learning: Applies to **/*.{ts,tsx} : Use `next-safe-action` for secure server actions
Learnt from: CR
Repo: kindfi-org/kindfi PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-04T22:00:57.967Z
Learning: Applies to **/*.{ts,tsx} : Validate all incoming API requests for security compliance
Learnt from: AndlerRL
Repo: kindfi-org/kindfi PR: 531
File: services/supabase/config.toml:13-15
Timestamp: 2025-08-12T09:51:45.176Z
Learning: In this Kindfi codebase, the next_auth schema is temporarily exposed through PostgREST API endpoints due to application code dependencies, but this creates a security risk that needs to be addressed by refactoring to use service-role credentials instead of direct REST API access to authentication tables.
Learnt from: AndlerRL
Repo: kindfi-org/kindfi PR: 0
File: :0-0
Timestamp: 2025-05-07T07:41:45.584Z
Learning: The Q&A feature implemented in PR `#504` requires improved Row Level Security (RLS) policies to properly handle user roles and permissions after the NextAuth providers implementation is completed.
If you found this review helpful, would you consider giving us a shout-out on X?
Thank you for using CodeRabbit!
- Migrate validateInput to z.treeifyError (Zod 4 API), replacing the deprecated ZodError.format(). - Make enforceRateLimit truly fail-closed in production: surface a SERVER_ERROR on the increment() result when Redis is unavailable, and reject the request when NODE_ENV === 'production'. The rate limiter catches Redis errors internally, so the previous outer try/catch was unreachable. - Add per-user rate limiting to updateDeviceWithDeployee, matching the rest of the auth actions touched by this PR. - Fix payer/receiver mapping in saveEscrowContractAction: payerAddress now derives from roles.approver (the funder), not roles.serviceProvider (the recipient). - Reject multi-receiver milestone escrows at the schema layer; the DB only stores a single receiver per escrow, so silently collapsing onto milestones[0].receiver could persist incorrect data.
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/web/lib/auth/server-action-auth.ts (1)
40-203:⚠️ Potential issue | 🟠 MajorAdopt
next-safe-actionfor secure server actions per coding guidelines, and avoid logging sensitive identifiers.This file implements a custom server action framework with manual composition of auth, validation, rate limiting, and failure handling. The coding guidelines explicitly require
next-safe-actionfor**/*.{ts,tsx}files to provide standardized security guardrails. Consider migrating these helpers into anext-safe-actionclient so new server actions inherit consistent protections without wrapper overhead.Additionally,
enforceRateLimitlogs raw identifier values at lines 128 and 139 in production. Since callers pass email addresses (e.g.,email.toLowerCase()inauth.tsforsign_upandrequest_reset_account), this exposes PII in rate-limit logs. Sanitize identifiers before logging (hash or truncate) or use non-sensitive tokens instead.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/lib/auth/server-action-auth.ts` around lines 40 - 203, This file re-implements server-action guards instead of using next-safe-action and logs raw identifiers (PII) in enforceRateLimit; refactor by migrating auth/validation/rate-limit helpers (requireAuthenticatedSession, requireAdminSession, validateInput, enforceRateLimit, toServerActionFailure) into a next-safe-action client so server actions use the standardized guardchain, and change enforceRateLimit to never log raw identifier values (replace with a one-way hash, truncated token, or redacted value before including in logger.warn payloads) and remove identifier from any production logs; ensure any references to identifier in SERVER_ACTION_RATE_LIMITED and SERVER_ACTION_RATE_LIMIT_UNAVAILABLE use the sanitized value.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/web/app/actions/auth.ts`:
- Around line 198-202: The auth logging currently emits raw emails (e.g.,
logger.info calls that include eventType 'SESSION_CREATED' and validated.email);
replace those occurrences (including the other similar blocks around the noted
ranges) so logs no longer contain plain PII: remove validated.email from the
payload and instead log either validated.userId plus a redactedEmail or a
one‑way hash of validated.email (use a shared helper like maskEmail or
hashEmail/sha256 utility) so the logger.info payload contains eventType, userId,
and redactedEmail/hash rather than the raw email.
- Around line 400-405: The admin escrow mutators (e.g.,
updateEscrowStatusAction) perform sensitive writes but lack rate limiting; add a
call to enforceRateLimit(...) immediately after await requireAdminSession('...')
and before any state-changing work to prevent hammering (use a unique action key
such as 'update-escrow-status' and include the admin/session identifier and
sensible limits), and repeat the same pattern for the other escrow mutator
functions in this file; also import enforceRateLimit where needed.
In `@apps/web/lib/auth/server-action-auth.ts`:
- Around line 123-145: The logger.warn calls that log raw rate-limit keys should
not include the plain identifier; update the blocks that handle result.isBlocked
and result.error (the logger.warn invocations near the ServerActionError and
SERVER_ACTION_RATE_LIMIT_UNAVAILABLE handling) to log a redacted or
action-scoped hash of identifier instead of the raw value (e.g., compute a
deterministic HMAC/SHA256 or mask local parts and use that value in the log
entry under the same field name), ensuring the same redaction/hashing function
is used in both places so logs remain linkable without exposing emails.
In `@apps/web/lib/schemas/server-actions.schemas.ts`:
- Around line 19-23: The schema updateEscrowMilestoneInputSchema allows
completed to exceed current; add a cross-field refinement on the z.object to
reject impossible progress by checking that completed <= current (and optionally
completed >= 0 is already enforced), and return a clear error message (e.g.,
"completed cannot exceed current") targeting the completed field; implement this
using zod's .refine on updateEscrowMilestoneInputSchema with a predicate (obj =>
obj.completed <= obj.current) and a params object specifying the message and
path to 'completed'.
- Around line 95-103: The current .refine allows both modes simultaneously;
change the predicate so modes are mutually exclusive by accepting either
(milestones present and amount/receiver absent) OR (amount and receiver present
and milestones absent). Update the error message to reflect exclusivity. Locate
the .refine call in the schema and the related saveEscrowContractAction to
ensure the validation blocks payloads that include both milestones and
single-release fields.
- Around line 36-39: The updateDeviceWithDeployeeInputSchema currently allows
any non-empty string for aaguid; change the aaguid validator in
updateDeviceWithDeployeeInputSchema to enforce a UUID (e.g., use
z.string().uuid(<optional message>)) so malformed authenticator metadata cannot
be accepted; keep credentialId as-is and ensure the zod import is used for the
new uuid check.
---
Outside diff comments:
In `@apps/web/lib/auth/server-action-auth.ts`:
- Around line 40-203: This file re-implements server-action guards instead of
using next-safe-action and logs raw identifiers (PII) in enforceRateLimit;
refactor by migrating auth/validation/rate-limit helpers
(requireAuthenticatedSession, requireAdminSession, validateInput,
enforceRateLimit, toServerActionFailure) into a next-safe-action client so
server actions use the standardized guardchain, and change enforceRateLimit to
never log raw identifier values (replace with a one-way hash, truncated token,
or redacted value before including in logger.warn payloads) and remove
identifier from any production logs; ensure any references to identifier in
SERVER_ACTION_RATE_LIMITED and SERVER_ACTION_RATE_LIMIT_UNAVAILABLE use the
sanitized value.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: d6ff32b2-f451-48a0-9cc7-aabe4a12f17d
📒 Files selected for processing (5)
apps/web/app/actions/auth.tsapps/web/app/actions/escrow/save-escrow-contract.tsapps/web/lib/auth/rate-limiter.tsapps/web/lib/auth/server-action-auth.tsapps/web/lib/schemas/server-actions.schemas.ts
| logger.info({ | ||
| eventType: 'SESSION_CREATED', | ||
| userId, | ||
| email, | ||
| userId: validated.userId, | ||
| email: validated.email, | ||
| }) |
There was a problem hiding this comment.
Drop raw emails from auth logs.
These events now emit email directly. That creates avoidable PII retention in auth/audit logs; consider logging userId, a redacted address, or a one-way hash instead.
Also applies to: 211-216, 298-300
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/app/actions/auth.ts` around lines 198 - 202, The auth logging
currently emits raw emails (e.g., logger.info calls that include eventType
'SESSION_CREATED' and validated.email); replace those occurrences (including the
other similar blocks around the noted ranges) so logs no longer contain plain
PII: remove validated.email from the payload and instead log either
validated.userId plus a redactedEmail or a one‑way hash of validated.email (use
a shared helper like maskEmail or hashEmail/sha256 utility) so the logger.info
payload contains eventType, userId, and redactedEmail/hash rather than the raw
email.
| export async function updateEscrowStatusAction( | ||
| id: string, | ||
| newStatus: EscrowStatusType, | ||
| newStatus: string, | ||
| ): Promise<EscrowResponse> { | ||
| try { | ||
| await requireAdminSession('updateEscrowStatusAction') |
There was a problem hiding this comment.
Consider throttling the admin escrow mutators too.
These three writes now have auth and validation, but they still update escrow_status without enforceRateLimit(...). A compromised admin session or accidental client loop can hammer sensitive state changes with no back-pressure.
Also applies to: 468-470, 540-541
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/app/actions/auth.ts` around lines 400 - 405, The admin escrow
mutators (e.g., updateEscrowStatusAction) perform sensitive writes but lack rate
limiting; add a call to enforceRateLimit(...) immediately after await
requireAdminSession('...') and before any state-changing work to prevent
hammering (use a unique action key such as 'update-escrow-status' and include
the admin/session identifier and sensible limits), and repeat the same pattern
for the other escrow mutator functions in this file; also import
enforceRateLimit where needed.
| if (result.isBlocked) { | ||
| logger.warn({ | ||
| eventType: 'SERVER_ACTION_RATE_LIMITED', | ||
| action, | ||
| identifier, | ||
| }) | ||
| throw new ServerActionError( | ||
| 'Too many requests. Please try again later.', | ||
| 'RATE_LIMITED', | ||
| ) | ||
| } | ||
|
|
||
| // `RateLimiter.increment` catches Redis errors internally and returns | ||
| // `{ isBlocked: false, error }` so it fails open. Detect that case here | ||
| // and fail closed in production so abuse protection is preserved during | ||
| // a Redis outage. | ||
| if (result.error) { | ||
| logger.warn({ | ||
| eventType: 'SERVER_ACTION_RATE_LIMIT_UNAVAILABLE', | ||
| action, | ||
| identifier, | ||
| error: result.error, | ||
| }) |
There was a problem hiding this comment.
Avoid logging raw rate-limit identifiers.
Some callers key the limiter with lowercased emails, so these warnings will store raw email addresses whenever the limiter trips or Redis is unavailable. Consider logging a redacted value or an action-scoped hash instead of identifier.
Also applies to: 160-164
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/lib/auth/server-action-auth.ts` around lines 123 - 145, The
logger.warn calls that log raw rate-limit keys should not include the plain
identifier; update the blocks that handle result.isBlocked and result.error (the
logger.warn invocations near the ServerActionError and
SERVER_ACTION_RATE_LIMIT_UNAVAILABLE handling) to log a redacted or
action-scoped hash of identifier instead of the raw value (e.g., compute a
deterministic HMAC/SHA256 or mask local parts and use that value in the log
entry under the same field name), ensuring the same redaction/hashing function
is used in both places so logs remain linkable without exposing emails.
| export const updateEscrowMilestoneInputSchema = z.object({ | ||
| id: z.string().uuid('Invalid escrow record id'), | ||
| current: z.number().int().min(0), | ||
| completed: z.number().int().min(0), | ||
| }) |
There was a problem hiding this comment.
Reject impossible milestone progress.
Consider adding a cross-field refinement here as well. Right now completed can exceed current, so the admin update path can persist milestone state that can never be true.
Proposed fix
-export const updateEscrowMilestoneInputSchema = z.object({
- id: z.string().uuid('Invalid escrow record id'),
- current: z.number().int().min(0),
- completed: z.number().int().min(0),
-})
+export const updateEscrowMilestoneInputSchema = z
+ .object({
+ id: z.string().uuid('Invalid escrow record id'),
+ current: z.number().int().min(0),
+ completed: z.number().int().min(0),
+ })
+ .refine((data) => data.completed <= data.current, {
+ message: 'Completed milestones cannot exceed the current milestone',
+ path: ['completed'],
+ })As per coding guidelines, "Validate all incoming API requests for security compliance".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/lib/schemas/server-actions.schemas.ts` around lines 19 - 23, The
schema updateEscrowMilestoneInputSchema allows completed to exceed current; add
a cross-field refinement on the z.object to reject impossible progress by
checking that completed <= current (and optionally completed >= 0 is already
enforced), and return a clear error message (e.g., "completed cannot exceed
current") targeting the completed field; implement this using zod's .refine on
updateEscrowMilestoneInputSchema with a predicate (obj => obj.completed <=
obj.current) and a params object specifying the message and path to 'completed'.
| export const updateDeviceWithDeployeeInputSchema = z.object({ | ||
| credentialId: z.string().min(1, 'credentialId is required'), | ||
| aaguid: z.string().min(1, 'aaguid is required'), | ||
| }) |
There was a problem hiding this comment.
Validate aaguid as a UUID.
We could enhance this by tightening aaguid beyond min(1). Accepting any non-empty string here makes it easy to persist malformed authenticator metadata.
Proposed fix
export const updateDeviceWithDeployeeInputSchema = z.object({
credentialId: z.string().min(1, 'credentialId is required'),
- aaguid: z.string().min(1, 'aaguid is required'),
+ aaguid: z.string().uuid('Invalid aaguid'),
})As per coding guidelines, "Validate all incoming API requests for security compliance".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/lib/schemas/server-actions.schemas.ts` around lines 36 - 39, The
updateDeviceWithDeployeeInputSchema currently allows any non-empty string for
aaguid; change the aaguid validator in updateDeviceWithDeployeeInputSchema to
enforce a UUID (e.g., use z.string().uuid(<optional message>)) so malformed
authenticator metadata cannot be accepted; keep credentialId as-is and ensure
the zod import is used for the new uuid check.
| .refine( | ||
| (data) => | ||
| (data.milestones && data.milestones.length > 0) || | ||
| (data.amount !== undefined && data.receiver !== undefined), | ||
| { | ||
| message: | ||
| 'Either milestones or single-release amount/receiver must be provided', | ||
| }, | ||
| ) |
There was a problem hiding this comment.
Make escrow payload modes mutually exclusive.
The current refinement only checks that at least one shape is present. A payload with both milestones and amount/receiver still passes, and saveEscrowContractAction then silently prefers milestones and ignores the single-release fields.
Proposed fix
.refine(
- (data) =>
- (data.milestones && data.milestones.length > 0) ||
- (data.amount !== undefined && data.receiver !== undefined),
+ (data) => {
+ const hasMilestones = !!data.milestones?.length
+ const hasSingleRelease =
+ data.amount !== undefined && data.receiver !== undefined
+ return hasMilestones !== hasSingleRelease
+ },
{
message:
- 'Either milestones or single-release amount/receiver must be provided',
+ 'Provide either milestones or single-release amount/receiver, but not both',
},
)As per coding guidelines, "Validate all incoming API requests for security compliance".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/lib/schemas/server-actions.schemas.ts` around lines 95 - 103, The
current .refine allows both modes simultaneously; change the predicate so modes
are mutually exclusive by accepting either (milestones present and
amount/receiver absent) OR (amount and receiver present and milestones absent).
Update the error message to reflect exclusivity. Locate the .refine call in the
schema and the related saveEscrowContractAction to ensure the validation blocks
payloads that include both milestones and single-release fields.
…re-server-actions-802 # Conflicts: # apps/web/lib/auth/rate-limiter.ts
* feat: add guard for mainnet release (#819) * feat: add guard for mainnet release * fix: spread overwrite * fix: placeholder * feat(web): standardize Zod validation for all API routes (#817) * feat(web): add validateRequest helper and Zod schemas for API validation - Add validateRequest utility for consistent validation responses - Add schemas for auth, comments, contributions, escrow, foundations, governance, kyc, nft, passkey, profile, projects, quests, referrals, stellar, streaks, tags - Add safeJsonParse in parseFormData for tags/socialLinks Made-with: Cursor * feat(web): migrate API routes to Zod validation - Replace manual validation with validateRequest + schemas across 55+ routes - Auth: callback, confirm - Comments: list, update - Contributions: create, sync - Escrow: initialize, fund, review, sign-and-submit, dispute - Foundations: create, check-slug, update, campaigns, milestones - Governance: rounds, vote - KYC: create-session - NFTs: mint, evolve, [address] - Passkey: generate options, verify registration/auth - Profile: follow, update-slug - Projects: create, update, pitch, team, highlights, members - Quests: list, progress - Referrals: create, donation, onboard - Stellar: account-info, approve, check-approval, balances, contract/invoke, devices, faucet, transfer prepare/submit - Streaks, tags, waitlist Made-with: Cursor --------- Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com> * chore: upt nft and quest performance (#821) * feat(audit): add structured audit logging for escrow and NFT operations (#822) * improve event listener performance and stabilize handlers (#825) * improve event listener performance and stabilize handlers * fix: avoid stale callback in kyc status listener * Feat/api helpers standardization (#824) * feat(web): add standardized API helper functions Introduces three helper functions in apps/web/lib/api-helpers.ts to unify the inconsistent auth, response, and error patterns across 60+ API routes: - requireSession(): validates Supabase or NextAuth sessions and returns a discriminated union { user, error: null } | { user: null, error: NextResponse } - respond(): builds consistent { success: true, data, pagination? } responses - error(): builds consistent { success: false, error: { code, message, details? } } responses with optional structured logging via the shared Logger * test(web): add unit tests for API helper functions 23 tests covering requireSession, respond, and error helpers: - requireSession: supabase/nextauth providers, authenticated and unauthenticated paths, edge cases (null user, missing session id) - respond: default 200, custom status, pagination, null/array data - error: default 500, custom status/code, details inclusion/omission, logging with true/Error/arbitrary objects, silent mode Uses dynamic import pattern with mock.module to work without a full dependency install in CI. --------- Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com> * refactor(escrow): break down escrow-admin-panel into compound components + providers (#806) (#823) - Extract EscrowFormProvider/useEscrowForm context (RORO pattern) to manage all form state in one place, replacing 15+ individual useState hooks - Add shared types.ts (EscrowFormData, MilestoneItem, EscrowAdminPanelProps) - Extract hooks: - use-project-defaults: fetches escrow count, derives suggested title/engagementId/description - use-wallet-sync: syncs wallet address into empty role fields and milestone receivers - use-escrow-validation: pure useMemo validation, no side-effects in main component - use-escrow-transaction: full deploy→sign→send→save flow, both single/multi-release - Extract compound UI components: - EscrowTypeSelector: radio group with milestone type conversion - EscrowBasicFields: title, engagementId, trustline, platformFee, amount, memo, description - EscrowRoleFields: approver, serviceProvider, releaseSigner, disputeResolver, platformAddress, receiver - EscrowMilestones: add/remove/update milestones for both escrow types - Add EscrowAdminPanelContent (~60 LOC): composes all sub-components, wires hooks - Reduce escrow-admin-panel.tsx to ~40 LOC: thin provider wrapper only - Zero TypeScript diagnostics across all new files - All existing functionality preserved with no breaking changes Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com> * fix(security): remove PII logging and enforce no-console rule (#826) * Feat/rate limit middleware #801 (#827) * fix(security): remove PII logging and enforce no-console rule * feat: add reusable withRateLimit middleware and refactor nft evolve route --------- Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com> * fix: login and formating * refactor(escrow): migrate initialize route to use API helpers (#840) * refactor(escrow): migrate initialize route to use API helpers Use requireSession for auth validation, respond/error helpers for consistent response shapes, and replace console.error with Logger via the error helper's log option. Closes #835 * refactor(escrow): address CodeRabbit review on initialize route - Add actorId: user.id to all audit log entries for consistent actor trail - Guard against sendTransaction returning falsy with audit log + 502 response - Add missing audit log on DB-insert failure branch - Align audit errorCode with client-visible response code (ESCROW_INIT_ERROR, INTERNAL_ERROR) * fix(security): prevent privilege escalation via user_id in NFT routes (#799) (#842) Centralizes the user_id override check in a shared `authorizeUserOverride` helper used by /api/nfts/evolve and /api/nfts/mint. Non-admin sessions can no longer have a body-supplied `user_id` override the authenticated identity; admin overrides return a structured `NextResponse.json` 403 and are recorded via the audit logger so privilege escalation attempts are observable. Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com> * fix: remove formdata from useeffect deps (#843) * fix: remove formdata from useeffect deps * fix: use stable milestoneReceiversKey instead of formData.milestones in useEffect deps --------- Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com> * fix(rate-limit): use preset.block instead of preset.window for Retry-After header (#844) * fix(rate-limit): use preset.block instead of preset.window for Retry-After header - Changed Retry-After header to reflect actual block duration instead of window - Updated test to verify Retry-After matches preset.block for all presets - Fixes #831 * test(rate-limit): include preset name in Retry-After assertion failures Address CodeRabbit feedback on PR #844: when the assertion fails, the error message now identifies which preset (strict/moderate/lenient) produced the mismatch, making regressions easier to diagnose. * fix(rate-limit): apply preset config to RateLimiter instance (#845) RateLimiter now accepts maxAttempts, windowSecs, blockSecs, and configId via constructor, falling back to module-level defaults for backward compatibility. The withRateLimit middleware passes preset values so strict/moderate/lenient actually behave differently. Cache keys include configId to prevent counter collisions across presets sharing an IP. Closes #830 Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com> * chore(web): remove ignorebuilderrors and fix surfaced typescript issues (#847) Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com> * refactor(api): migrate mint route to withRateLimit middleware (#836) (#848) * refactor(api): migrate mint route to withRateLimit middleware (#836) * fix(indexer): add missing tslib dependency for subql codegen * fix(indexer): resolve ESM/CommonJS issues in codegen by using process.env directly * fix(indexer): remove all ESM and workspace dependencies from codegen * fix(indexer): align stellar-sdk to v14 and resolve type mismatches in mappings * fix(indexer): update project.ts to use @stellar/stellar-sdk * Revert "fix(indexer): update project.ts to use @stellar/stellar-sdk" This reverts commit a4a1a26. * Reapply "fix(indexer): update project.ts to use @stellar/stellar-sdk" This reverts commit a49ac80. --------- Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com> * feat(security): enforce auth and Zod validation in Server Actions (#802) (#846) * feat(security): enforce auth and Zod validation in Server Actions (#802) Server Actions ("use server") are exposed as public endpoints, so each one must verify its caller and validate inputs even when middleware has already run. This change introduces a shared auth/validation layer and applies it to every action under apps/web/app/actions: - Add lib/auth/server-action-auth helpers: requireAuthenticatedSession, requireAdminSession, validateInput (Zod), enforceRateLimit, and a ServerActionError -> structured failure helper. - Add lib/schemas/server-actions.schemas with strict Zod schemas for every action input (UUIDs, emails, escrow status enum, Stellar shapes, foundation create payload, etc). - auth.ts: await CSRF check (was unawaited Promise -> always passed), validate sign-up / reset / session / device payloads, gate all escrow admin actions behind requireAdminSession, derive userId from the session in updateDeviceWithDeployee instead of trusting client input, rate-limit sensitive flows, and disable insertTestEscrowRecordAction in production. - foundations/create-foundation.ts: require auth, Zod-validate input, rate-limit, and emit structured audit logs. - escrow/save-escrow-contract.ts: require auth, Zod-validate input, rate-limit, and emit structured audit logs alongside the existing ownership/admin authorization check. Closes #802 * fix(security): tighten validation and fail-closed paths in server actions Address review feedback on PR #846: - Fail closed on rate limiter errors in production so abuse protection stays in place during a Redis outage; keep dev behaviour permissive. - Reject incomplete escrow payloads instead of fabricating placeholder Stellar addresses, engagement IDs and amounts in saveEscrowContract. - Apply stellarAddressSchema to escrow role and receiver fields so malformed account IDs are rejected before persistence. - Reject released > funded in updateEscrowFinancialsInputSchema. - Stop showing a misleading success redirect in requestResetAccountAction while the reset flow is still a TODO; surface a clear unavailable error. * fix(security): address review comments on PR #846 - Migrate validateInput to z.treeifyError (Zod 4 API), replacing the deprecated ZodError.format(). - Make enforceRateLimit truly fail-closed in production: surface a SERVER_ERROR on the increment() result when Redis is unavailable, and reject the request when NODE_ENV === 'production'. The rate limiter catches Redis errors internally, so the previous outer try/catch was unreachable. - Add per-user rate limiting to updateDeviceWithDeployee, matching the rest of the auth actions touched by this PR. - Fix payer/receiver mapping in saveEscrowContractAction: payerAddress now derives from roles.approver (the funder), not roles.serviceProvider (the recipient). - Reject multi-receiver milestone escrows at the schema layer; the DB only stores a single receiver per escrow, so silently collapsing onto milestones[0].receiver could persist incorrect data. --------- Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com> * refactor: eliminate prop-drilling of suggestedTitle/EngagementId/Description through EscrowAdminPanelContent (#841) Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com> * fix: replace direct console calls with Logger to comply with no-console ESLint rule (#849) * Remove unused escrowContractAddress prop for cleaner component (#850) * removing the unused escrowContractAddress prop from the component and its associated interfaces to keep the component signature clean and maintainable. * update workflow * chore: add stable version tranch submit post drips wave fix * chore: upgrade Bun to 1.3.14 across the monorepo Align packageManager, CI workflows, and @types/bun so local development and CI use the same runtime version. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(web): redesign home hero and waitlist onboarding Improve LATAM-focused hero and projects sections, rebuild the waitlist flow, and fix waitlist inserts with the Supabase service role client. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(web): redesign profile dashboard and harden auth logging Refresh /profile with product-grade layout aligned to the home redesign, fix passkey login feedback to only surface after failed auth, and move notification logging to a server-side path with a new notification_logs migration. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(web): redesign projects listing and unify form UX Add an emotional projects hero with metrics, a floating category ticker, and shared form primitives across auth and project flows. Require external G-address wallets for Trustless Work escrow until smart account signing is supported. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): stabilize projects filters hydration and URL sync Derive category/sort state from search params, defer Radix and marquee until mount, and simplify category badges to prevent update loops and SSR id mismatches. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(web): redesign governance page and wire community navigation Align the governance experience with KindFi’s visual language, add i18n copy, and expose /governance from header, profile, and footer entry points. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): resolve governance tab keys and profile wallet hydration Use a single keyed motion panel for governance tabs and defer external wallet UI until mount. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: replace boolean with a narrowed * fix(ci): stabilize indexer PR checks on develop and main Provide testnet CHAIN_ID/ENDPOINT in CI, install from the monorepo root with bun.lock, scope the workflow to indexer changes, and add testnet fallbacks in project.ts. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ci): remove invalid paths-ignore from indexer workflow GitHub only allows paths or paths-ignore per event, not both. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ci): skip empty indexer SubQuery tests in PR workflow Codegen and build already validate the indexer; remove the SubQuery test step until mapping handler tests are implemented. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Matias Aguilar <aaguilar1x@gmail.com> Co-authored-by: Anouk Rímola <77553677+AnoukRImola@users.noreply.github.com> Co-authored-by: Kim <72054684+kimcascante@users.noreply.github.com> Co-authored-by: Kevin Membreño Brenes <130603817+KevinMB0220@users.noreply.github.com> Co-authored-by: Sendi John <johnsendi727@gmail.com> Co-authored-by: Florencia Irupe Alonso <108772706+AlonsoFi@users.noreply.github.com> Co-authored-by: Josué Araya Marín <104031367+Josue19-08@users.noreply.github.com> Co-authored-by: Francisco Campos Díaz <sasasamaes@users.noreply.github.com> Co-authored-by: legend4Tech <121477316+legend4tech@users.noreply.github.com> Co-authored-by: Jenny T. <112415373+JennyT3@users.noreply.github.com> Co-authored-by: Sameer Ali <140313541+devxsameer@users.noreply.github.com> Co-authored-by: MiraBello <miraclebello23@gmail.com> Co-authored-by: is_reel <israelolatunle2015@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com>
…863) * feat: add guard for mainnet release (#819) * feat: add guard for mainnet release * fix: spread overwrite * fix: placeholder * feat(web): standardize Zod validation for all API routes (#817) * feat(web): add validateRequest helper and Zod schemas for API validation - Add validateRequest utility for consistent validation responses - Add schemas for auth, comments, contributions, escrow, foundations, governance, kyc, nft, passkey, profile, projects, quests, referrals, stellar, streaks, tags - Add safeJsonParse in parseFormData for tags/socialLinks Made-with: Cursor * feat(web): migrate API routes to Zod validation - Replace manual validation with validateRequest + schemas across 55+ routes - Auth: callback, confirm - Comments: list, update - Contributions: create, sync - Escrow: initialize, fund, review, sign-and-submit, dispute - Foundations: create, check-slug, update, campaigns, milestones - Governance: rounds, vote - KYC: create-session - NFTs: mint, evolve, [address] - Passkey: generate options, verify registration/auth - Profile: follow, update-slug - Projects: create, update, pitch, team, highlights, members - Quests: list, progress - Referrals: create, donation, onboard - Stellar: account-info, approve, check-approval, balances, contract/invoke, devices, faucet, transfer prepare/submit - Streaks, tags, waitlist Made-with: Cursor --------- Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com> * chore: upt nft and quest performance (#821) * feat(audit): add structured audit logging for escrow and NFT operations (#822) * improve event listener performance and stabilize handlers (#825) * improve event listener performance and stabilize handlers * fix: avoid stale callback in kyc status listener * Feat/api helpers standardization (#824) * feat(web): add standardized API helper functions Introduces three helper functions in apps/web/lib/api-helpers.ts to unify the inconsistent auth, response, and error patterns across 60+ API routes: - requireSession(): validates Supabase or NextAuth sessions and returns a discriminated union { user, error: null } | { user: null, error: NextResponse } - respond(): builds consistent { success: true, data, pagination? } responses - error(): builds consistent { success: false, error: { code, message, details? } } responses with optional structured logging via the shared Logger * test(web): add unit tests for API helper functions 23 tests covering requireSession, respond, and error helpers: - requireSession: supabase/nextauth providers, authenticated and unauthenticated paths, edge cases (null user, missing session id) - respond: default 200, custom status, pagination, null/array data - error: default 500, custom status/code, details inclusion/omission, logging with true/Error/arbitrary objects, silent mode Uses dynamic import pattern with mock.module to work without a full dependency install in CI. --------- Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com> * refactor(escrow): break down escrow-admin-panel into compound components + providers (#806) (#823) - Extract EscrowFormProvider/useEscrowForm context (RORO pattern) to manage all form state in one place, replacing 15+ individual useState hooks - Add shared types.ts (EscrowFormData, MilestoneItem, EscrowAdminPanelProps) - Extract hooks: - use-project-defaults: fetches escrow count, derives suggested title/engagementId/description - use-wallet-sync: syncs wallet address into empty role fields and milestone receivers - use-escrow-validation: pure useMemo validation, no side-effects in main component - use-escrow-transaction: full deploy→sign→send→save flow, both single/multi-release - Extract compound UI components: - EscrowTypeSelector: radio group with milestone type conversion - EscrowBasicFields: title, engagementId, trustline, platformFee, amount, memo, description - EscrowRoleFields: approver, serviceProvider, releaseSigner, disputeResolver, platformAddress, receiver - EscrowMilestones: add/remove/update milestones for both escrow types - Add EscrowAdminPanelContent (~60 LOC): composes all sub-components, wires hooks - Reduce escrow-admin-panel.tsx to ~40 LOC: thin provider wrapper only - Zero TypeScript diagnostics across all new files - All existing functionality preserved with no breaking changes Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com> * fix(security): remove PII logging and enforce no-console rule (#826) * Feat/rate limit middleware #801 (#827) * fix(security): remove PII logging and enforce no-console rule * feat: add reusable withRateLimit middleware and refactor nft evolve route --------- Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com> * fix: login and formating * refactor(escrow): migrate initialize route to use API helpers (#840) * refactor(escrow): migrate initialize route to use API helpers Use requireSession for auth validation, respond/error helpers for consistent response shapes, and replace console.error with Logger via the error helper's log option. Closes #835 * refactor(escrow): address CodeRabbit review on initialize route - Add actorId: user.id to all audit log entries for consistent actor trail - Guard against sendTransaction returning falsy with audit log + 502 response - Add missing audit log on DB-insert failure branch - Align audit errorCode with client-visible response code (ESCROW_INIT_ERROR, INTERNAL_ERROR) * fix(security): prevent privilege escalation via user_id in NFT routes (#799) (#842) Centralizes the user_id override check in a shared `authorizeUserOverride` helper used by /api/nfts/evolve and /api/nfts/mint. Non-admin sessions can no longer have a body-supplied `user_id` override the authenticated identity; admin overrides return a structured `NextResponse.json` 403 and are recorded via the audit logger so privilege escalation attempts are observable. Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com> * fix: remove formdata from useeffect deps (#843) * fix: remove formdata from useeffect deps * fix: use stable milestoneReceiversKey instead of formData.milestones in useEffect deps --------- Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com> * fix(rate-limit): use preset.block instead of preset.window for Retry-After header (#844) * fix(rate-limit): use preset.block instead of preset.window for Retry-After header - Changed Retry-After header to reflect actual block duration instead of window - Updated test to verify Retry-After matches preset.block for all presets - Fixes #831 * test(rate-limit): include preset name in Retry-After assertion failures Address CodeRabbit feedback on PR #844: when the assertion fails, the error message now identifies which preset (strict/moderate/lenient) produced the mismatch, making regressions easier to diagnose. * fix(rate-limit): apply preset config to RateLimiter instance (#845) RateLimiter now accepts maxAttempts, windowSecs, blockSecs, and configId via constructor, falling back to module-level defaults for backward compatibility. The withRateLimit middleware passes preset values so strict/moderate/lenient actually behave differently. Cache keys include configId to prevent counter collisions across presets sharing an IP. Closes #830 Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com> * chore(web): remove ignorebuilderrors and fix surfaced typescript issues (#847) Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com> * refactor(api): migrate mint route to withRateLimit middleware (#836) (#848) * refactor(api): migrate mint route to withRateLimit middleware (#836) * fix(indexer): add missing tslib dependency for subql codegen * fix(indexer): resolve ESM/CommonJS issues in codegen by using process.env directly * fix(indexer): remove all ESM and workspace dependencies from codegen * fix(indexer): align stellar-sdk to v14 and resolve type mismatches in mappings * fix(indexer): update project.ts to use @stellar/stellar-sdk * Revert "fix(indexer): update project.ts to use @stellar/stellar-sdk" This reverts commit a4a1a26. * Reapply "fix(indexer): update project.ts to use @stellar/stellar-sdk" This reverts commit a49ac80. --------- Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com> * feat(security): enforce auth and Zod validation in Server Actions (#802) (#846) * feat(security): enforce auth and Zod validation in Server Actions (#802) Server Actions ("use server") are exposed as public endpoints, so each one must verify its caller and validate inputs even when middleware has already run. This change introduces a shared auth/validation layer and applies it to every action under apps/web/app/actions: - Add lib/auth/server-action-auth helpers: requireAuthenticatedSession, requireAdminSession, validateInput (Zod), enforceRateLimit, and a ServerActionError -> structured failure helper. - Add lib/schemas/server-actions.schemas with strict Zod schemas for every action input (UUIDs, emails, escrow status enum, Stellar shapes, foundation create payload, etc). - auth.ts: await CSRF check (was unawaited Promise -> always passed), validate sign-up / reset / session / device payloads, gate all escrow admin actions behind requireAdminSession, derive userId from the session in updateDeviceWithDeployee instead of trusting client input, rate-limit sensitive flows, and disable insertTestEscrowRecordAction in production. - foundations/create-foundation.ts: require auth, Zod-validate input, rate-limit, and emit structured audit logs. - escrow/save-escrow-contract.ts: require auth, Zod-validate input, rate-limit, and emit structured audit logs alongside the existing ownership/admin authorization check. Closes #802 * fix(security): tighten validation and fail-closed paths in server actions Address review feedback on PR #846: - Fail closed on rate limiter errors in production so abuse protection stays in place during a Redis outage; keep dev behaviour permissive. - Reject incomplete escrow payloads instead of fabricating placeholder Stellar addresses, engagement IDs and amounts in saveEscrowContract. - Apply stellarAddressSchema to escrow role and receiver fields so malformed account IDs are rejected before persistence. - Reject released > funded in updateEscrowFinancialsInputSchema. - Stop showing a misleading success redirect in requestResetAccountAction while the reset flow is still a TODO; surface a clear unavailable error. * fix(security): address review comments on PR #846 - Migrate validateInput to z.treeifyError (Zod 4 API), replacing the deprecated ZodError.format(). - Make enforceRateLimit truly fail-closed in production: surface a SERVER_ERROR on the increment() result when Redis is unavailable, and reject the request when NODE_ENV === 'production'. The rate limiter catches Redis errors internally, so the previous outer try/catch was unreachable. - Add per-user rate limiting to updateDeviceWithDeployee, matching the rest of the auth actions touched by this PR. - Fix payer/receiver mapping in saveEscrowContractAction: payerAddress now derives from roles.approver (the funder), not roles.serviceProvider (the recipient). - Reject multi-receiver milestone escrows at the schema layer; the DB only stores a single receiver per escrow, so silently collapsing onto milestones[0].receiver could persist incorrect data. --------- Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com> * refactor: eliminate prop-drilling of suggestedTitle/EngagementId/Description through EscrowAdminPanelContent (#841) Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com> * fix: replace direct console calls with Logger to comply with no-console ESLint rule (#849) * Remove unused escrowContractAddress prop for cleaner component (#850) * removing the unused escrowContractAddress prop from the component and its associated interfaces to keep the component signature clean and maintainable. * update workflow * chore: add stable version tranch submit post drips wave fix * chore: upgrade Bun to 1.3.14 across the monorepo Align packageManager, CI workflows, and @types/bun so local development and CI use the same runtime version. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(web): redesign home hero and waitlist onboarding Improve LATAM-focused hero and projects sections, rebuild the waitlist flow, and fix waitlist inserts with the Supabase service role client. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(web): redesign profile dashboard and harden auth logging Refresh /profile with product-grade layout aligned to the home redesign, fix passkey login feedback to only surface after failed auth, and move notification logging to a server-side path with a new notification_logs migration. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(web): redesign projects listing and unify form UX Add an emotional projects hero with metrics, a floating category ticker, and shared form primitives across auth and project flows. Require external G-address wallets for Trustless Work escrow until smart account signing is supported. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): stabilize projects filters hydration and URL sync Derive category/sort state from search params, defer Radix and marquee until mount, and simplify category badges to prevent update loops and SSR id mismatches. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(web): redesign governance page and wire community navigation Align the governance experience with KindFi’s visual language, add i18n copy, and expose /governance from header, profile, and footer entry points. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): resolve governance tab keys and profile wallet hydration Use a single keyed motion panel for governance tabs and defer external wallet UI until mount. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: replace boolean with a narrowed * fix(ci): stabilize indexer PR checks on develop and main Provide testnet CHAIN_ID/ENDPOINT in CI, install from the monorepo root with bun.lock, scope the workflow to indexer changes, and add testnet fallbacks in project.ts. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ci): remove invalid paths-ignore from indexer workflow GitHub only allows paths or paths-ignore per event, not both. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ci): skip empty indexer SubQuery tests in PR workflow Codegen and build already validate the indexer; remove the SubQuery test step until mapping handler tests are implemented. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(auth): fail open when Redis rate limiter is unavailable Allow sign-up and other server actions to continue in production when Upstash is missing or unreachable, matching API route rate-limit behavior. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Matias Aguilar <aaguilar1x@gmail.com> Co-authored-by: Anouk Rímola <77553677+AnoukRImola@users.noreply.github.com> Co-authored-by: Kim <72054684+kimcascante@users.noreply.github.com> Co-authored-by: Kevin Membreño Brenes <130603817+KevinMB0220@users.noreply.github.com> Co-authored-by: Sendi John <johnsendi727@gmail.com> Co-authored-by: Florencia Irupe Alonso <108772706+AlonsoFi@users.noreply.github.com> Co-authored-by: Josué Araya Marín <104031367+Josue19-08@users.noreply.github.com> Co-authored-by: Francisco Campos Díaz <sasasamaes@users.noreply.github.com> Co-authored-by: legend4Tech <121477316+legend4tech@users.noreply.github.com> Co-authored-by: Jenny T. <112415373+JennyT3@users.noreply.github.com> Co-authored-by: Sameer Ali <140313541+devxsameer@users.noreply.github.com> Co-authored-by: MiraBello <miraclebello23@gmail.com> Co-authored-by: is_reel <israelolatunle2015@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com>
* feat: add guard for mainnet release (#819) * feat: add guard for mainnet release * fix: spread overwrite * fix: placeholder * feat(web): standardize Zod validation for all API routes (#817) * feat(web): add validateRequest helper and Zod schemas for API validation - Add validateRequest utility for consistent validation responses - Add schemas for auth, comments, contributions, escrow, foundations, governance, kyc, nft, passkey, profile, projects, quests, referrals, stellar, streaks, tags - Add safeJsonParse in parseFormData for tags/socialLinks Made-with: Cursor * feat(web): migrate API routes to Zod validation - Replace manual validation with validateRequest + schemas across 55+ routes - Auth: callback, confirm - Comments: list, update - Contributions: create, sync - Escrow: initialize, fund, review, sign-and-submit, dispute - Foundations: create, check-slug, update, campaigns, milestones - Governance: rounds, vote - KYC: create-session - NFTs: mint, evolve, [address] - Passkey: generate options, verify registration/auth - Profile: follow, update-slug - Projects: create, update, pitch, team, highlights, members - Quests: list, progress - Referrals: create, donation, onboard - Stellar: account-info, approve, check-approval, balances, contract/invoke, devices, faucet, transfer prepare/submit - Streaks, tags, waitlist Made-with: Cursor --------- Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com> * chore: upt nft and quest performance (#821) * feat(audit): add structured audit logging for escrow and NFT operations (#822) * improve event listener performance and stabilize handlers (#825) * improve event listener performance and stabilize handlers * fix: avoid stale callback in kyc status listener * Feat/api helpers standardization (#824) * feat(web): add standardized API helper functions Introduces three helper functions in apps/web/lib/api-helpers.ts to unify the inconsistent auth, response, and error patterns across 60+ API routes: - requireSession(): validates Supabase or NextAuth sessions and returns a discriminated union { user, error: null } | { user: null, error: NextResponse } - respond(): builds consistent { success: true, data, pagination? } responses - error(): builds consistent { success: false, error: { code, message, details? } } responses with optional structured logging via the shared Logger * test(web): add unit tests for API helper functions 23 tests covering requireSession, respond, and error helpers: - requireSession: supabase/nextauth providers, authenticated and unauthenticated paths, edge cases (null user, missing session id) - respond: default 200, custom status, pagination, null/array data - error: default 500, custom status/code, details inclusion/omission, logging with true/Error/arbitrary objects, silent mode Uses dynamic import pattern with mock.module to work without a full dependency install in CI. --------- Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com> * refactor(escrow): break down escrow-admin-panel into compound components + providers (#806) (#823) - Extract EscrowFormProvider/useEscrowForm context (RORO pattern) to manage all form state in one place, replacing 15+ individual useState hooks - Add shared types.ts (EscrowFormData, MilestoneItem, EscrowAdminPanelProps) - Extract hooks: - use-project-defaults: fetches escrow count, derives suggested title/engagementId/description - use-wallet-sync: syncs wallet address into empty role fields and milestone receivers - use-escrow-validation: pure useMemo validation, no side-effects in main component - use-escrow-transaction: full deploy→sign→send→save flow, both single/multi-release - Extract compound UI components: - EscrowTypeSelector: radio group with milestone type conversion - EscrowBasicFields: title, engagementId, trustline, platformFee, amount, memo, description - EscrowRoleFields: approver, serviceProvider, releaseSigner, disputeResolver, platformAddress, receiver - EscrowMilestones: add/remove/update milestones for both escrow types - Add EscrowAdminPanelContent (~60 LOC): composes all sub-components, wires hooks - Reduce escrow-admin-panel.tsx to ~40 LOC: thin provider wrapper only - Zero TypeScript diagnostics across all new files - All existing functionality preserved with no breaking changes Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com> * fix(security): remove PII logging and enforce no-console rule (#826) * Feat/rate limit middleware #801 (#827) * fix(security): remove PII logging and enforce no-console rule * feat: add reusable withRateLimit middleware and refactor nft evolve route --------- Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com> * fix: login and formating * refactor(escrow): migrate initialize route to use API helpers (#840) * refactor(escrow): migrate initialize route to use API helpers Use requireSession for auth validation, respond/error helpers for consistent response shapes, and replace console.error with Logger via the error helper's log option. Closes #835 * refactor(escrow): address CodeRabbit review on initialize route - Add actorId: user.id to all audit log entries for consistent actor trail - Guard against sendTransaction returning falsy with audit log + 502 response - Add missing audit log on DB-insert failure branch - Align audit errorCode with client-visible response code (ESCROW_INIT_ERROR, INTERNAL_ERROR) * fix(security): prevent privilege escalation via user_id in NFT routes (#799) (#842) Centralizes the user_id override check in a shared `authorizeUserOverride` helper used by /api/nfts/evolve and /api/nfts/mint. Non-admin sessions can no longer have a body-supplied `user_id` override the authenticated identity; admin overrides return a structured `NextResponse.json` 403 and are recorded via the audit logger so privilege escalation attempts are observable. Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com> * fix: remove formdata from useeffect deps (#843) * fix: remove formdata from useeffect deps * fix: use stable milestoneReceiversKey instead of formData.milestones in useEffect deps --------- Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com> * fix(rate-limit): use preset.block instead of preset.window for Retry-After header (#844) * fix(rate-limit): use preset.block instead of preset.window for Retry-After header - Changed Retry-After header to reflect actual block duration instead of window - Updated test to verify Retry-After matches preset.block for all presets - Fixes #831 * test(rate-limit): include preset name in Retry-After assertion failures Address CodeRabbit feedback on PR #844: when the assertion fails, the error message now identifies which preset (strict/moderate/lenient) produced the mismatch, making regressions easier to diagnose. * fix(rate-limit): apply preset config to RateLimiter instance (#845) RateLimiter now accepts maxAttempts, windowSecs, blockSecs, and configId via constructor, falling back to module-level defaults for backward compatibility. The withRateLimit middleware passes preset values so strict/moderate/lenient actually behave differently. Cache keys include configId to prevent counter collisions across presets sharing an IP. Closes #830 Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com> * chore(web): remove ignorebuilderrors and fix surfaced typescript issues (#847) Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com> * refactor(api): migrate mint route to withRateLimit middleware (#836) (#848) * refactor(api): migrate mint route to withRateLimit middleware (#836) * fix(indexer): add missing tslib dependency for subql codegen * fix(indexer): resolve ESM/CommonJS issues in codegen by using process.env directly * fix(indexer): remove all ESM and workspace dependencies from codegen * fix(indexer): align stellar-sdk to v14 and resolve type mismatches in mappings * fix(indexer): update project.ts to use @stellar/stellar-sdk * Revert "fix(indexer): update project.ts to use @stellar/stellar-sdk" This reverts commit a4a1a26. * Reapply "fix(indexer): update project.ts to use @stellar/stellar-sdk" This reverts commit a49ac80. --------- Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com> * feat(security): enforce auth and Zod validation in Server Actions (#802) (#846) * feat(security): enforce auth and Zod validation in Server Actions (#802) Server Actions ("use server") are exposed as public endpoints, so each one must verify its caller and validate inputs even when middleware has already run. This change introduces a shared auth/validation layer and applies it to every action under apps/web/app/actions: - Add lib/auth/server-action-auth helpers: requireAuthenticatedSession, requireAdminSession, validateInput (Zod), enforceRateLimit, and a ServerActionError -> structured failure helper. - Add lib/schemas/server-actions.schemas with strict Zod schemas for every action input (UUIDs, emails, escrow status enum, Stellar shapes, foundation create payload, etc). - auth.ts: await CSRF check (was unawaited Promise -> always passed), validate sign-up / reset / session / device payloads, gate all escrow admin actions behind requireAdminSession, derive userId from the session in updateDeviceWithDeployee instead of trusting client input, rate-limit sensitive flows, and disable insertTestEscrowRecordAction in production. - foundations/create-foundation.ts: require auth, Zod-validate input, rate-limit, and emit structured audit logs. - escrow/save-escrow-contract.ts: require auth, Zod-validate input, rate-limit, and emit structured audit logs alongside the existing ownership/admin authorization check. Closes #802 * fix(security): tighten validation and fail-closed paths in server actions Address review feedback on PR #846: - Fail closed on rate limiter errors in production so abuse protection stays in place during a Redis outage; keep dev behaviour permissive. - Reject incomplete escrow payloads instead of fabricating placeholder Stellar addresses, engagement IDs and amounts in saveEscrowContract. - Apply stellarAddressSchema to escrow role and receiver fields so malformed account IDs are rejected before persistence. - Reject released > funded in updateEscrowFinancialsInputSchema. - Stop showing a misleading success redirect in requestResetAccountAction while the reset flow is still a TODO; surface a clear unavailable error. * fix(security): address review comments on PR #846 - Migrate validateInput to z.treeifyError (Zod 4 API), replacing the deprecated ZodError.format(). - Make enforceRateLimit truly fail-closed in production: surface a SERVER_ERROR on the increment() result when Redis is unavailable, and reject the request when NODE_ENV === 'production'. The rate limiter catches Redis errors internally, so the previous outer try/catch was unreachable. - Add per-user rate limiting to updateDeviceWithDeployee, matching the rest of the auth actions touched by this PR. - Fix payer/receiver mapping in saveEscrowContractAction: payerAddress now derives from roles.approver (the funder), not roles.serviceProvider (the recipient). - Reject multi-receiver milestone escrows at the schema layer; the DB only stores a single receiver per escrow, so silently collapsing onto milestones[0].receiver could persist incorrect data. --------- Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com> * refactor: eliminate prop-drilling of suggestedTitle/EngagementId/Description through EscrowAdminPanelContent (#841) Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com> * fix: replace direct console calls with Logger to comply with no-console ESLint rule (#849) * Remove unused escrowContractAddress prop for cleaner component (#850) * removing the unused escrowContractAddress prop from the component and its associated interfaces to keep the component signature clean and maintainable. * update workflow * chore: add stable version tranch submit post drips wave fix * chore: upgrade Bun to 1.3.14 across the monorepo Align packageManager, CI workflows, and @types/bun so local development and CI use the same runtime version. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(web): redesign home hero and waitlist onboarding Improve LATAM-focused hero and projects sections, rebuild the waitlist flow, and fix waitlist inserts with the Supabase service role client. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(web): redesign profile dashboard and harden auth logging Refresh /profile with product-grade layout aligned to the home redesign, fix passkey login feedback to only surface after failed auth, and move notification logging to a server-side path with a new notification_logs migration. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(web): redesign projects listing and unify form UX Add an emotional projects hero with metrics, a floating category ticker, and shared form primitives across auth and project flows. Require external G-address wallets for Trustless Work escrow until smart account signing is supported. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): stabilize projects filters hydration and URL sync Derive category/sort state from search params, defer Radix and marquee until mount, and simplify category badges to prevent update loops and SSR id mismatches. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(web): redesign governance page and wire community navigation Align the governance experience with KindFi’s visual language, add i18n copy, and expose /governance from header, profile, and footer entry points. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): resolve governance tab keys and profile wallet hydration Use a single keyed motion panel for governance tabs and defer external wallet UI until mount. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: replace boolean with a narrowed * fix(ci): stabilize indexer PR checks on develop and main Provide testnet CHAIN_ID/ENDPOINT in CI, install from the monorepo root with bun.lock, scope the workflow to indexer changes, and add testnet fallbacks in project.ts. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ci): remove invalid paths-ignore from indexer workflow GitHub only allows paths or paths-ignore per event, not both. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ci): skip empty indexer SubQuery tests in PR workflow Codegen and build already validate the indexer; remove the SubQuery test step until mapping handler tests are implemented. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(auth): fail open when Redis rate limiter is unavailable Allow sign-up and other server actions to continue in production when Upstash is missing or unreachable, matching API route rate-limit behavior. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(project-detail): persist comments, likes, follows and enable Supabase session validation (#864) * chore: remove all TODO comments from codebase (#866) * # Performance: Expand code splitting, dynamic imports, and Suspense boundaries (#868) * perf(admin): lazy-load AdminOverview with dynamic import and Suspense * perf(admin): lazy-load AdminGovernanceManager with dynamic import and Suspense * perf(admin): lazy-load AdminAnalytics with dynamic import and Suspense * perf(admin): lazy-load AdminGamificationManager with dynamic import and Suspense * perf(admin): add skeleton loaders for admin panel components * perf(profile): lazy-load CreatorProfile and DonorProfile with dynamic imports * perf(profile): lazy-load NFTCollection in creator profile * perf(profile): lazy-load NFTCollection in donor profile * perf(profile): add skeleton loader for profile view components * perf(home): lazy-load WaitlistModal in hero section * perf(home): lazy-load WaitlistModal in highlighted projects * perf(home): lazy-load WaitlistModal in user journey section * perf(shared): lazy-load WaitlistModal in CTA form * perf(gamification): lazy-load NFTCollection component * perf(demo): lazy-load SmartWalletTransferDemo with Suspense boundary * Fix spacing in Code of Conduct title * refactor: standardize logging infrastructure and unify backend request handlers across the application. (#867) Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com> * feat: remove smart wallet demo route and component (#869)- (#870) * feat: apply rate limiting to all sensitive API routes (#871) Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com> * feat(seo): align KindFi with AIO and Search Everywhere Optimization standards (#872) * feat(seo): align KindFi with AIO and technical SEO standards - Add robots.ts with explicit AI crawler policies (GPTBot, Google-Extended, PerplexityBot, ClaudeBot, anthropic-ai, Bingbot) - Add public/llms.txt to communicate platform intent to LLM crawlers - Expand sitemap with missing routes (governance, faqs, foundations) and differentiated priorities/changeFrequencies per page type - Add JSON-LD reusable component and structured-data helpers for Organization, WebSite, FAQPage, BreadcrumbList, and Event schemas - Inject Organization + WebSite JSON-LD in root layout (site-wide) - Add FAQPage + BreadcrumbList JSON-LD to /faqs page using existing FAQ data - Add BreadcrumbList JSON-LD to /projects, /about, /governance, and /projects/[slug] pages - Add Event + BreadcrumbList JSON-LD to project detail pages - Add complete metadata (title, description, OG, Twitter Card, canonical) to pages that were missing it: home, faqs, profile, governance - Improve existing metadata on about, projects, project detail, and root layout with richer entity-based descriptions and Twitter Cards - Set robots: noindex/nofollow on /profile (private authenticated page) Closes #853 * fix(seo): address CodeRabbit review comments - json-ld: escape HTML-significant chars (<, >, &) before injection to prevent script tag breakout from adversarial payload values - structured-data: export SITE_URL constant and add explicit return types (OrganizationSchema, WebSiteSchema, BreadcrumbListSchema, FAQPageSchema) to all factory functions for stable consumer contracts - sitemap: replace localhost fallback with shared SITE_URL constant so URLs are always production-safe when NEXT_PUBLIC_SITE_URL is unset - layout: unify metadataBase and OG url with shared SITE_URL; remove broken og-home.png reference (file does not exist yet) - projects/[slug]: fail fast with notFound() when project is null to prevent 200 responses on missing slugs; seed query cache with already- fetched project via setQueryData instead of a redundant prefetchSupabaseQuery * Chore/remove commented out code pr851 (#873) * chore(middleware): remove tutorial boilerplate and dead session validation block - Remove Supabase SSR tutorial copy-paste comments (try/catch tutorial note) - Remove entirely commented-out getUser() + console.log + redirect block (lines 52-63) that referenced undefined variable cookieSessionToken and was never wired up - Replace generic catch comment with actionable env var guidance - Auth is handled via NextAuth (_userSession param); Supabase client is used only for SSR cookie refresh, not for session validation Closes #851 * chore(doc-utils): remove dead export for non-existent pdf-converter module The pdf-converter module does not exist in the doc-utils directory. The commented-out export was a dead reference to a feature that was never implemented and caused confusion about the public API surface. Closes #851 * chore(stellar): remove stale dev-note comment from passkey service Remove informal '// ! Some of these fns are already in other apps...' comment at the top of stellar-passkey.service.ts. This was a leftover investigation note with no actionable guidance and no linked issue. Any follow-up sync work should be tracked in a dedicated issue/PR. Closes #851 --------- Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com> * refactor: decompose monolithic components and split shared modules (#875) Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com> * fix: build issues post wave program * chore: align Biome with monorepo standards and enforce linting in CI. Pin Biome 2.4.5, enable React/Next/Tailwind domains, and apply formatting across the repo so `biome ci` passes locally and in pre-commit. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: guard Supabase generated types from Biome and restore web build Restore database.types.ts, force-ignore generated Supabase artifacts in Biome, and add atomic generation with pre-commit and CI checks. Co-authored-by: Cursor <cursoragent@cursor.com> * chore: upd agent skills * chore: remove unused academy-wep app from monorepo Drop the stub workspace package and related Taskfile targets. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(web): migrate Tailwind v4 and refresh marketing pages Upgrade web styling stack, align about/foundations/news with shared hero patterns, fix CTA and hydration issues, and add social icon helpers. * chore: exclude .agents from Biome checks Skip vendored Vercel and Stellar skill files so CI lint no longer fails on their formatting. * chore: exclude .claude/skills from Biome checks Skip symlinked agent skill paths so lint no longer traverses vendored skill installs. * chore: exclude .codex and .cursor skills from Biome Skip symlinked skill installs under .codex/skills and .cursor/skills in lint and format. * fix: resolve Biome CI errors for lint and formatting Remove root console usage, format skills-lock.json, drop ineffective skeleton suppressions, and stabilize QA realtime hook callbacks. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): footer refresh, foundation cards, and NFT donation stats Align footer with main nav and marketing styling, restore foundation card navigation, and show correct donation counts in gamification. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Matias Aguilar <aaguilar1x@gmail.com> Co-authored-by: Anouk Rímola <77553677+AnoukRImola@users.noreply.github.com> Co-authored-by: Kim <72054684+kimcascante@users.noreply.github.com> Co-authored-by: Kevin Membreño Brenes <130603817+KevinMB0220@users.noreply.github.com> Co-authored-by: Sendi John <johnsendi727@gmail.com> Co-authored-by: Florencia Irupe Alonso <108772706+AlonsoFi@users.noreply.github.com> Co-authored-by: Josué Araya Marín <104031367+Josue19-08@users.noreply.github.com> Co-authored-by: Francisco Campos Díaz <sasasamaes@users.noreply.github.com> Co-authored-by: legend4Tech <121477316+legend4tech@users.noreply.github.com> Co-authored-by: Jenny T. <112415373+JennyT3@users.noreply.github.com> Co-authored-by: Sameer Ali <140313541+devxsameer@users.noreply.github.com> Co-authored-by: MiraBello <miraclebello23@gmail.com> Co-authored-by: is_reel <israelolatunle2015@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Mmeso_Love <mmesolove2002@gmail.com> Co-authored-by: Jefferson Youashi <119521983+clintjeff2@users.noreply.github.com> Co-authored-by: Dev001 <146340502+od-hunter@users.noreply.github.com>
* feat: add guard for mainnet release (#819) * feat: add guard for mainnet release * fix: spread overwrite * fix: placeholder * feat(web): standardize Zod validation for all API routes (#817) * feat(web): add validateRequest helper and Zod schemas for API validation - Add validateRequest utility for consistent validation responses - Add schemas for auth, comments, contributions, escrow, foundations, governance, kyc, nft, passkey, profile, projects, quests, referrals, stellar, streaks, tags - Add safeJsonParse in parseFormData for tags/socialLinks Made-with: Cursor * feat(web): migrate API routes to Zod validation - Replace manual validation with validateRequest + schemas across 55+ routes - Auth: callback, confirm - Comments: list, update - Contributions: create, sync - Escrow: initialize, fund, review, sign-and-submit, dispute - Foundations: create, check-slug, update, campaigns, milestones - Governance: rounds, vote - KYC: create-session - NFTs: mint, evolve, [address] - Passkey: generate options, verify registration/auth - Profile: follow, update-slug - Projects: create, update, pitch, team, highlights, members - Quests: list, progress - Referrals: create, donation, onboard - Stellar: account-info, approve, check-approval, balances, contract/invoke, devices, faucet, transfer prepare/submit - Streaks, tags, waitlist Made-with: Cursor --------- Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com> * chore: upt nft and quest performance (#821) * feat(audit): add structured audit logging for escrow and NFT operations (#822) * improve event listener performance and stabilize handlers (#825) * improve event listener performance and stabilize handlers * fix: avoid stale callback in kyc status listener * Feat/api helpers standardization (#824) * feat(web): add standardized API helper functions Introduces three helper functions in apps/web/lib/api-helpers.ts to unify the inconsistent auth, response, and error patterns across 60+ API routes: - requireSession(): validates Supabase or NextAuth sessions and returns a discriminated union { user, error: null } | { user: null, error: NextResponse } - respond(): builds consistent { success: true, data, pagination? } responses - error(): builds consistent { success: false, error: { code, message, details? } } responses with optional structured logging via the shared Logger * test(web): add unit tests for API helper functions 23 tests covering requireSession, respond, and error helpers: - requireSession: supabase/nextauth providers, authenticated and unauthenticated paths, edge cases (null user, missing session id) - respond: default 200, custom status, pagination, null/array data - error: default 500, custom status/code, details inclusion/omission, logging with true/Error/arbitrary objects, silent mode Uses dynamic import pattern with mock.module to work without a full dependency install in CI. --------- Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com> * refactor(escrow): break down escrow-admin-panel into compound components + providers (#806) (#823) - Extract EscrowFormProvider/useEscrowForm context (RORO pattern) to manage all form state in one place, replacing 15+ individual useState hooks - Add shared types.ts (EscrowFormData, MilestoneItem, EscrowAdminPanelProps) - Extract hooks: - use-project-defaults: fetches escrow count, derives suggested title/engagementId/description - use-wallet-sync: syncs wallet address into empty role fields and milestone receivers - use-escrow-validation: pure useMemo validation, no side-effects in main component - use-escrow-transaction: full deploy→sign→send→save flow, both single/multi-release - Extract compound UI components: - EscrowTypeSelector: radio group with milestone type conversion - EscrowBasicFields: title, engagementId, trustline, platformFee, amount, memo, description - EscrowRoleFields: approver, serviceProvider, releaseSigner, disputeResolver, platformAddress, receiver - EscrowMilestones: add/remove/update milestones for both escrow types - Add EscrowAdminPanelContent (~60 LOC): composes all sub-components, wires hooks - Reduce escrow-admin-panel.tsx to ~40 LOC: thin provider wrapper only - Zero TypeScript diagnostics across all new files - All existing functionality preserved with no breaking changes Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com> * fix(security): remove PII logging and enforce no-console rule (#826) * Feat/rate limit middleware #801 (#827) * fix(security): remove PII logging and enforce no-console rule * feat: add reusable withRateLimit middleware and refactor nft evolve route --------- Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com> * fix: login and formating * refactor(escrow): migrate initialize route to use API helpers (#840) * refactor(escrow): migrate initialize route to use API helpers Use requireSession for auth validation, respond/error helpers for consistent response shapes, and replace console.error with Logger via the error helper's log option. Closes #835 * refactor(escrow): address CodeRabbit review on initialize route - Add actorId: user.id to all audit log entries for consistent actor trail - Guard against sendTransaction returning falsy with audit log + 502 response - Add missing audit log on DB-insert failure branch - Align audit errorCode with client-visible response code (ESCROW_INIT_ERROR, INTERNAL_ERROR) * fix(security): prevent privilege escalation via user_id in NFT routes (#799) (#842) Centralizes the user_id override check in a shared `authorizeUserOverride` helper used by /api/nfts/evolve and /api/nfts/mint. Non-admin sessions can no longer have a body-supplied `user_id` override the authenticated identity; admin overrides return a structured `NextResponse.json` 403 and are recorded via the audit logger so privilege escalation attempts are observable. Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com> * fix: remove formdata from useeffect deps (#843) * fix: remove formdata from useeffect deps * fix: use stable milestoneReceiversKey instead of formData.milestones in useEffect deps --------- Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com> * fix(rate-limit): use preset.block instead of preset.window for Retry-After header (#844) * fix(rate-limit): use preset.block instead of preset.window for Retry-After header - Changed Retry-After header to reflect actual block duration instead of window - Updated test to verify Retry-After matches preset.block for all presets - Fixes #831 * test(rate-limit): include preset name in Retry-After assertion failures Address CodeRabbit feedback on PR #844: when the assertion fails, the error message now identifies which preset (strict/moderate/lenient) produced the mismatch, making regressions easier to diagnose. * fix(rate-limit): apply preset config to RateLimiter instance (#845) RateLimiter now accepts maxAttempts, windowSecs, blockSecs, and configId via constructor, falling back to module-level defaults for backward compatibility. The withRateLimit middleware passes preset values so strict/moderate/lenient actually behave differently. Cache keys include configId to prevent counter collisions across presets sharing an IP. Closes #830 Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com> * chore(web): remove ignorebuilderrors and fix surfaced typescript issues (#847) Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com> * refactor(api): migrate mint route to withRateLimit middleware (#836) (#848) * refactor(api): migrate mint route to withRateLimit middleware (#836) * fix(indexer): add missing tslib dependency for subql codegen * fix(indexer): resolve ESM/CommonJS issues in codegen by using process.env directly * fix(indexer): remove all ESM and workspace dependencies from codegen * fix(indexer): align stellar-sdk to v14 and resolve type mismatches in mappings * fix(indexer): update project.ts to use @stellar/stellar-sdk * Revert "fix(indexer): update project.ts to use @stellar/stellar-sdk" This reverts commit a4a1a26. * Reapply "fix(indexer): update project.ts to use @stellar/stellar-sdk" This reverts commit a49ac80. --------- Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com> * feat(security): enforce auth and Zod validation in Server Actions (#802) (#846) * feat(security): enforce auth and Zod validation in Server Actions (#802) Server Actions ("use server") are exposed as public endpoints, so each one must verify its caller and validate inputs even when middleware has already run. This change introduces a shared auth/validation layer and applies it to every action under apps/web/app/actions: - Add lib/auth/server-action-auth helpers: requireAuthenticatedSession, requireAdminSession, validateInput (Zod), enforceRateLimit, and a ServerActionError -> structured failure helper. - Add lib/schemas/server-actions.schemas with strict Zod schemas for every action input (UUIDs, emails, escrow status enum, Stellar shapes, foundation create payload, etc). - auth.ts: await CSRF check (was unawaited Promise -> always passed), validate sign-up / reset / session / device payloads, gate all escrow admin actions behind requireAdminSession, derive userId from the session in updateDeviceWithDeployee instead of trusting client input, rate-limit sensitive flows, and disable insertTestEscrowRecordAction in production. - foundations/create-foundation.ts: require auth, Zod-validate input, rate-limit, and emit structured audit logs. - escrow/save-escrow-contract.ts: require auth, Zod-validate input, rate-limit, and emit structured audit logs alongside the existing ownership/admin authorization check. Closes #802 * fix(security): tighten validation and fail-closed paths in server actions Address review feedback on PR #846: - Fail closed on rate limiter errors in production so abuse protection stays in place during a Redis outage; keep dev behaviour permissive. - Reject incomplete escrow payloads instead of fabricating placeholder Stellar addresses, engagement IDs and amounts in saveEscrowContract. - Apply stellarAddressSchema to escrow role and receiver fields so malformed account IDs are rejected before persistence. - Reject released > funded in updateEscrowFinancialsInputSchema. - Stop showing a misleading success redirect in requestResetAccountAction while the reset flow is still a TODO; surface a clear unavailable error. * fix(security): address review comments on PR #846 - Migrate validateInput to z.treeifyError (Zod 4 API), replacing the deprecated ZodError.format(). - Make enforceRateLimit truly fail-closed in production: surface a SERVER_ERROR on the increment() result when Redis is unavailable, and reject the request when NODE_ENV === 'production'. The rate limiter catches Redis errors internally, so the previous outer try/catch was unreachable. - Add per-user rate limiting to updateDeviceWithDeployee, matching the rest of the auth actions touched by this PR. - Fix payer/receiver mapping in saveEscrowContractAction: payerAddress now derives from roles.approver (the funder), not roles.serviceProvider (the recipient). - Reject multi-receiver milestone escrows at the schema layer; the DB only stores a single receiver per escrow, so silently collapsing onto milestones[0].receiver could persist incorrect data. --------- Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com> * refactor: eliminate prop-drilling of suggestedTitle/EngagementId/Description through EscrowAdminPanelContent (#841) Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com> * fix: replace direct console calls with Logger to comply with no-console ESLint rule (#849) * Remove unused escrowContractAddress prop for cleaner component (#850) * removing the unused escrowContractAddress prop from the component and its associated interfaces to keep the component signature clean and maintainable. * update workflow * chore: add stable version tranch submit post drips wave fix * chore: upgrade Bun to 1.3.14 across the monorepo Align packageManager, CI workflows, and @types/bun so local development and CI use the same runtime version. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(web): redesign home hero and waitlist onboarding Improve LATAM-focused hero and projects sections, rebuild the waitlist flow, and fix waitlist inserts with the Supabase service role client. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(web): redesign profile dashboard and harden auth logging Refresh /profile with product-grade layout aligned to the home redesign, fix passkey login feedback to only surface after failed auth, and move notification logging to a server-side path with a new notification_logs migration. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(web): redesign projects listing and unify form UX Add an emotional projects hero with metrics, a floating category ticker, and shared form primitives across auth and project flows. Require external G-address wallets for Trustless Work escrow until smart account signing is supported. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): stabilize projects filters hydration and URL sync Derive category/sort state from search params, defer Radix and marquee until mount, and simplify category badges to prevent update loops and SSR id mismatches. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(web): redesign governance page and wire community navigation Align the governance experience with KindFi’s visual language, add i18n copy, and expose /governance from header, profile, and footer entry points. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): resolve governance tab keys and profile wallet hydration Use a single keyed motion panel for governance tabs and defer external wallet UI until mount. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: replace boolean with a narrowed * fix(ci): stabilize indexer PR checks on develop and main Provide testnet CHAIN_ID/ENDPOINT in CI, install from the monorepo root with bun.lock, scope the workflow to indexer changes, and add testnet fallbacks in project.ts. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ci): remove invalid paths-ignore from indexer workflow GitHub only allows paths or paths-ignore per event, not both. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ci): skip empty indexer SubQuery tests in PR workflow Codegen and build already validate the indexer; remove the SubQuery test step until mapping handler tests are implemented. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(auth): fail open when Redis rate limiter is unavailable Allow sign-up and other server actions to continue in production when Upstash is missing or unreachable, matching API route rate-limit behavior. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(project-detail): persist comments, likes, follows and enable Supabase session validation (#864) * chore: remove all TODO comments from codebase (#866) * # Performance: Expand code splitting, dynamic imports, and Suspense boundaries (#868) * perf(admin): lazy-load AdminOverview with dynamic import and Suspense * perf(admin): lazy-load AdminGovernanceManager with dynamic import and Suspense * perf(admin): lazy-load AdminAnalytics with dynamic import and Suspense * perf(admin): lazy-load AdminGamificationManager with dynamic import and Suspense * perf(admin): add skeleton loaders for admin panel components * perf(profile): lazy-load CreatorProfile and DonorProfile with dynamic imports * perf(profile): lazy-load NFTCollection in creator profile * perf(profile): lazy-load NFTCollection in donor profile * perf(profile): add skeleton loader for profile view components * perf(home): lazy-load WaitlistModal in hero section * perf(home): lazy-load WaitlistModal in highlighted projects * perf(home): lazy-load WaitlistModal in user journey section * perf(shared): lazy-load WaitlistModal in CTA form * perf(gamification): lazy-load NFTCollection component * perf(demo): lazy-load SmartWalletTransferDemo with Suspense boundary * Fix spacing in Code of Conduct title * refactor: standardize logging infrastructure and unify backend request handlers across the application. (#867) Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com> * feat: remove smart wallet demo route and component (#869)- (#870) * feat: apply rate limiting to all sensitive API routes (#871) Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com> * feat(seo): align KindFi with AIO and Search Everywhere Optimization standards (#872) * feat(seo): align KindFi with AIO and technical SEO standards - Add robots.ts with explicit AI crawler policies (GPTBot, Google-Extended, PerplexityBot, ClaudeBot, anthropic-ai, Bingbot) - Add public/llms.txt to communicate platform intent to LLM crawlers - Expand sitemap with missing routes (governance, faqs, foundations) and differentiated priorities/changeFrequencies per page type - Add JSON-LD reusable component and structured-data helpers for Organization, WebSite, FAQPage, BreadcrumbList, and Event schemas - Inject Organization + WebSite JSON-LD in root layout (site-wide) - Add FAQPage + BreadcrumbList JSON-LD to /faqs page using existing FAQ data - Add BreadcrumbList JSON-LD to /projects, /about, /governance, and /projects/[slug] pages - Add Event + BreadcrumbList JSON-LD to project detail pages - Add complete metadata (title, description, OG, Twitter Card, canonical) to pages that were missing it: home, faqs, profile, governance - Improve existing metadata on about, projects, project detail, and root layout with richer entity-based descriptions and Twitter Cards - Set robots: noindex/nofollow on /profile (private authenticated page) Closes #853 * fix(seo): address CodeRabbit review comments - json-ld: escape HTML-significant chars (<, >, &) before injection to prevent script tag breakout from adversarial payload values - structured-data: export SITE_URL constant and add explicit return types (OrganizationSchema, WebSiteSchema, BreadcrumbListSchema, FAQPageSchema) to all factory functions for stable consumer contracts - sitemap: replace localhost fallback with shared SITE_URL constant so URLs are always production-safe when NEXT_PUBLIC_SITE_URL is unset - layout: unify metadataBase and OG url with shared SITE_URL; remove broken og-home.png reference (file does not exist yet) - projects/[slug]: fail fast with notFound() when project is null to prevent 200 responses on missing slugs; seed query cache with already- fetched project via setQueryData instead of a redundant prefetchSupabaseQuery * Chore/remove commented out code pr851 (#873) * chore(middleware): remove tutorial boilerplate and dead session validation block - Remove Supabase SSR tutorial copy-paste comments (try/catch tutorial note) - Remove entirely commented-out getUser() + console.log + redirect block (lines 52-63) that referenced undefined variable cookieSessionToken and was never wired up - Replace generic catch comment with actionable env var guidance - Auth is handled via NextAuth (_userSession param); Supabase client is used only for SSR cookie refresh, not for session validation Closes #851 * chore(doc-utils): remove dead export for non-existent pdf-converter module The pdf-converter module does not exist in the doc-utils directory. The commented-out export was a dead reference to a feature that was never implemented and caused confusion about the public API surface. Closes #851 * chore(stellar): remove stale dev-note comment from passkey service Remove informal '// ! Some of these fns are already in other apps...' comment at the top of stellar-passkey.service.ts. This was a leftover investigation note with no actionable guidance and no linked issue. Any follow-up sync work should be tracked in a dedicated issue/PR. Closes #851 --------- Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com> * refactor: decompose monolithic components and split shared modules (#875) Co-authored-by: Brandon Fernández <31634868+Bran18@users.noreply.github.com> * fix: build issues post wave program * chore: align Biome with monorepo standards and enforce linting in CI. Pin Biome 2.4.5, enable React/Next/Tailwind domains, and apply formatting across the repo so `biome ci` passes locally and in pre-commit. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: guard Supabase generated types from Biome and restore web build Restore database.types.ts, force-ignore generated Supabase artifacts in Biome, and add atomic generation with pre-commit and CI checks. Co-authored-by: Cursor <cursoragent@cursor.com> * chore: upd agent skills * chore: remove unused academy-wep app from monorepo Drop the stub workspace package and related Taskfile targets. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(web): migrate Tailwind v4 and refresh marketing pages Upgrade web styling stack, align about/foundations/news with shared hero patterns, fix CTA and hydration issues, and add social icon helpers. * chore: exclude .agents from Biome checks Skip vendored Vercel and Stellar skill files so CI lint no longer fails on their formatting. * chore: exclude .claude/skills from Biome checks Skip symlinked agent skill paths so lint no longer traverses vendored skill installs. * chore: exclude .codex and .cursor skills from Biome Skip symlinked skill installs under .codex/skills and .cursor/skills in lint and format. * fix: resolve Biome CI errors for lint and formatting Remove root console usage, format skills-lock.json, drop ineffective skeleton suppressions, and stabilize QA realtime hook callbacks. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): footer refresh, foundation cards, and NFT donation stats Align footer with main nav and marketing styling, restore foundation card navigation, and show correct donation counts in gamification. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): shorten wallet reminder in project donation sidebar Use friendlier copy and a compact banner so the notice fits the Support This Project panel. --------- Co-authored-by: Matias Aguilar <aaguilar1x@gmail.com> Co-authored-by: Anouk Rímola <77553677+AnoukRImola@users.noreply.github.com> Co-authored-by: Kim <72054684+kimcascante@users.noreply.github.com> Co-authored-by: Kevin Membreño Brenes <130603817+KevinMB0220@users.noreply.github.com> Co-authored-by: Sendi John <johnsendi727@gmail.com> Co-authored-by: Florencia Irupe Alonso <108772706+AlonsoFi@users.noreply.github.com> Co-authored-by: Josué Araya Marín <104031367+Josue19-08@users.noreply.github.com> Co-authored-by: Francisco Campos Díaz <sasasamaes@users.noreply.github.com> Co-authored-by: legend4Tech <121477316+legend4tech@users.noreply.github.com> Co-authored-by: Jenny T. <112415373+JennyT3@users.noreply.github.com> Co-authored-by: Sameer Ali <140313541+devxsameer@users.noreply.github.com> Co-authored-by: MiraBello <miraclebello23@gmail.com> Co-authored-by: is_reel <israelolatunle2015@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Mmeso_Love <mmesolove2002@gmail.com> Co-authored-by: Jefferson Youashi <119521983+clintjeff2@users.noreply.github.com> Co-authored-by: Dev001 <146340502+od-hunter@users.noreply.github.com>
Summary
Server Actions (
"use server") are exposed as public HTTP endpoints, so each one must verify its caller and validate inputs even after middleware has run. This PR introduces a shared auth/validation layer and applies it to every action underapps/web/app/actions/.Closes #802
Changes
New shared helpers
apps/web/lib/auth/server-action-auth.ts—requireAuthenticatedSession,requireAdminSession,validateInput(Zod),enforceRateLimit,ServerActionErrorand atoServerActionFailurehelper that produces a structured{ success: false, error, code, details }response.apps/web/lib/schemas/server-actions.schemas.ts— strict Zod schemas for every action input (UUIDs, emails, escrow status enum, Stellar shapes, foundation payload, etc).Action-by-action fixes
apps/web/app/actions/auth.tsvalidateCsrfTokencalls (the previousif (!promise)always evaluated tofalse, silently disabling CSRF protection).signUpAction,requestResetAccountAction,resetPasswordAction,createSessionActionandupdateDeviceWithDeployeeinputs with Zod.resetPasswordActionnow requires an authenticated session (recovery link establishes one) before accepting a new password.updateDeviceWithDeployeenow derivesuserIdfrom the authenticated session instead of trusting client-supplied JSON.updateEscrowStatusAction,updateEscrowMilestoneAction,updateEscrowFinancialsAction,getEscrowRecordsAction,insertTestEscrowRecordAction) now require an admin session.insertTestEscrowRecordActionis hard-disabled inNODE_ENV === 'production'.updateDeviceWithDeployee.apps/web/app/actions/foundations/create-foundation.ts— require auth, Zod-validate input, rate-limit, structured audit logs.apps/web/app/actions/escrow/save-escrow-contract.ts— require auth, Zod-validate input, rate-limit, structured audit logs alongside the existing ownership/admin authorization check.Test plan
bun run biome checkon changed filestsc --noEmit— no new errors introduced (pre-existing errors in unrelated files remain)bun test test/api-helpers.test.ts— passesbun test test/rate-limit-middleware.test.ts— passesSummary by CodeRabbit
New Features
Bug Fixes