Skip to content

feat(security): enforce auth and Zod validation in Server Actions (#802)#846

Merged
Bran18 merged 5 commits into
kindfi-org:developfrom
sasasamaes:feat/secure-server-actions-802
Apr 28, 2026
Merged

feat(security): enforce auth and Zod validation in Server Actions (#802)#846
Bran18 merged 5 commits into
kindfi-org:developfrom
sasasamaes:feat/secure-server-actions-802

Conversation

@sasasamaes
Copy link
Copy Markdown
Contributor

@sasasamaes sasasamaes commented Apr 27, 2026

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 under apps/web/app/actions/.

Closes #802

Changes

New shared helpers

  • apps/web/lib/auth/server-action-auth.tsrequireAuthenticatedSession, requireAdminSession, validateInput (Zod), enforceRateLimit, ServerActionError and a toServerActionFailure helper 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.ts
    • Fixed unawaited validateCsrfToken calls (the previous if (!promise) always evaluated to false, silently disabling CSRF protection).
    • Validate signUpAction, requestResetAccountAction, resetPasswordAction, createSessionAction and updateDeviceWithDeployee inputs with Zod.
    • resetPasswordAction now requires an authenticated session (recovery link establishes one) before accepting a new password.
    • updateDeviceWithDeployee now derives userId from the authenticated session instead of trusting client-supplied JSON.
    • All escrow admin actions (updateEscrowStatusAction, updateEscrowMilestoneAction, updateEscrowFinancialsAction, getEscrowRecordsAction, insertTestEscrowRecordAction) now require an admin session.
    • insertTestEscrowRecordAction is hard-disabled in NODE_ENV === 'production'.
    • Added rate limiting for sign-up, password reset, session creation and 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 check on changed files
  • tsc --noEmit — no new errors introduced (pre-existing errors in unrelated files remain)
  • bun test test/api-helpers.test.ts — passes
  • bun test test/rate-limit-middleware.test.ts — passes
  • Smoke test sign-up / password reset flows
  • Verify escrow admin pages still load for admin users and 403 for non-admins
  • Verify foundation creation still works for authenticated users

Summary by CodeRabbit

  • New Features

    • Centralized server-action auth and schema-based input validation across auth, escrow, device, and foundation flows.
    • Admin-only protections for escrow management; session-required device and password reset flows.
    • Per-identifier/action rate limiting applied widely.
  • Bug Fixes

    • Consistent structured failure responses and standardized error handling/logging.
    • Disabled test-only escrow insertion in production and tightened validation to prevent invalid payloads.

…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
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Apr 27, 2026

@sasasamaes is attempting to deploy a commit to the kindfi Team on Vercel.

A member of the Team first needs to authorize it.

@drips-wave
Copy link
Copy Markdown

drips-wave Bot commented Apr 27, 2026

@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! 🚀

Learn more about application limits

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 27, 2026

Warning

Rate limit exceeded

@Bran18 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 50 minutes and 54 seconds before requesting another review.

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 @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6b1ca32c-7d8c-42ab-9e65-64bd7f563c7c

📥 Commits

Reviewing files that changed from the base of the PR and between d1b0229 and d8044f4.

📒 Files selected for processing (3)
  • apps/web/app/api/nfts/mint/route.ts
  • apps/web/components/sections/projects/manage/escrow/hooks/use-wallet-sync.ts
  • apps/web/lib/auth/rate-limiter.ts

Walkthrough

Centralized 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

Cohort / File(s) Summary
Auth Infrastructure
apps/web/lib/auth/server-action-auth.ts
New utilities and types: ServerActionError/codes, ServerActionFailure, getAuthenticatedSession, requireAuthenticatedSession, requireAdminSession, validateInput, enforceRateLimit, toServerActionFailure. Centralizes auth, validation, rate-limiting, and error mapping.
Schemas
apps/web/lib/schemas/server-actions.schemas.ts
New Zod schemas/types for escrow status, escrow updates, device payloads, signup/reset/reset-password (with confirm refinement), create-session, save-escrow-contract (with Stellar address checks and milestone/amount refinements), and create-foundation constraints; exports stellarAddressSchema.
Auth Actions
apps/web/app/actions/auth.ts
Sign-up, session creation, reset flows refactored: schema validation, async CSRF where needed, per-identifier rate limiting, structured failure returns, signature change for createSessionAction(input), admin-only wrappers for escrow admin actions, removal of console warnings.
Escrow Contract Save
apps/web/app/actions/escrow/save-escrow-contract.ts
Now uses requireAuthenticatedSession, validates saveEscrowContractInputSchema via validateInput, enforces per-user rate limit, uses validated fields for DB ops, computes totals from validated milestones/amount/receiver, standardizes failures and replaces console logs with Logger.
Escrow Admin Actions
apps/web/app/actions/auth.ts (escrow admin portions)
Admin-only enforcement via requireAdminSession, input schemas applied for status/milestone/financial updates, validated values used for DB and logging, insertTestEscrowRecordAction disabled in production.
Foundation Creation
apps/web/app/actions/foundations/create-foundation.ts
Replaced manual session checks with requireAuthenticatedSession, input validated by createFoundationInputSchema, per-user rate limiting, and structured Logger usage.
Device Update
apps/web/app/actions/.../updateDeviceWithDeployee.ts
Now requires authenticated session, parses/validates JSON via updateDeviceWithDeployeeInputSchema, enforces rate limit by user id, returns standardized unauthorized/invalid-payload failures.
Rate Limiter
apps/web/lib/auth/rate-limiter.ts
Increment now returns error: AuthErrorType.SERVER_ERROR when Redis is unavailable or errors occur; Redis error logging formatting adjusted.
Observability & Logging
multiple files
Replaced ad-hoc console.* logs with structured Logger calls and standardized failure mapping via toServerActionFailure.

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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

difficulty: hard

Suggested reviewers

  • AndlerRL
  • Bran18

Poem

Bright schemas line the gateway, checks keep mischief out,
Sessions guard the passages, and rate limits hum about.
Errors now wear uniforms, logs sing clear and smart,
Centralized guardrails steady every server part.
Cheers to safer actions and tidier art!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 26.09% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main changes: introducing centralized auth enforcement and Zod validation across Server Actions with a focus on security improvements.
Linked Issues check ✅ Passed All coding objectives from issue #802 are comprehensively addressed: authentication checks added to all actions, authorization enforced (admin/user-based), Zod input validation implemented, rate limiting added to sensitive operations, and structured logging integrated.
Out of Scope Changes check ✅ Passed All changes directly support the security hardening objectives. Rate limiter error handling improvements and schema definitions are necessary foundational changes supporting the auth/validation implementation.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot added feature New requested feature webapp web app related refactor Code refactoring and improvements backend Back-end development related labels Apr 27, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

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 | 🟠 Major

Consider rate-limiting updateDeviceWithDeployee before 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 | 🟠 Major

Consider 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 contributions succeeds but escrow_contracts or project_escrows fails, 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

📥 Commits

Reviewing files that changed from the base of the PR and between d15bdce and 29db1d5.

📒 Files selected for processing (5)
  • apps/web/app/actions/auth.ts
  • apps/web/app/actions/escrow/save-escrow-contract.ts
  • apps/web/app/actions/foundations/create-foundation.ts
  • apps/web/lib/auth/server-action-auth.ts
  • apps/web/lib/schemas/server-actions.schemas.ts

Comment thread apps/web/app/actions/auth.ts Outdated
Comment thread apps/web/app/actions/escrow/save-escrow-contract.ts Outdated
Comment thread apps/web/lib/auth/server-action-auth.ts
Comment thread apps/web/lib/schemas/server-actions.schemas.ts Outdated
Comment thread apps/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.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

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 | 🔵 Trivial

Drop the duplicated top-level engagementId and prefer z.infer over as SaveEscrowContractParams.

The interface and validation flow look clean overall. Two small polish opportunities:

  • The top-level engagementId?: string (line 19) is unused — line 126 reads escrowData.engagementId. The mirror in saveEscrowContractInputSchema (engagementId: z.string().min(1).optional()) is similarly unread. Removing both keeps the contract honest.
  • validated = validateInput(...) as SaveEscrowContractParams on line 61 throws away the precise Zod-inferred shape (e.g. the escrowMilestoneSchema element 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

📥 Commits

Reviewing files that changed from the base of the PR and between 29db1d5 and 37b14f2.

📒 Files selected for processing (4)
  • apps/web/app/actions/auth.ts
  • apps/web/app/actions/escrow/save-escrow-contract.ts
  • apps/web/lib/auth/server-action-auth.ts
  • apps/web/lib/schemas/server-actions.schemas.ts

Comment thread apps/web/app/actions/auth.ts
Comment on lines +125 to +153
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',
}
}
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Apr 27, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 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 -40

Repository: 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 -60

Repository: 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 -80

Repository: 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 -80

Repository: 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.ts

Repository: 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=ts

Repository: 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 -A3

Repository: 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 -30

Repository: kindfi-org/kindfi

Length of output: 1032


Verify the payer/receiver role mapping and handle multi-receiver milestones.

Two issues worth addressing before merge:

  1. 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 be escrowData.roles.approver instead, or add a clarifying comment if this mapping is intentional.

  2. 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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in d1b0229.

  1. Payer mapping: changed payerAddress from escrowData.roles.serviceProvider to escrowData.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.

  2. Multi-receiver milestones: tightened escrowDataSchema with a refinement that rejects payloads where milestones have differing receiver addresses. Since the DB schema stores a single receiver_address per escrow, accepting multi-receiver payloads would silently drop data. The action keeps reading milestones[0].receiver, which is now safe by schema invariant — and a clarifying comment marks the assumption.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@sasasamaes, both fixes look excellent — thank you for the thorough follow-through!

  1. Payer mapping — Using escrowData.roles.approver is 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.

  2. Multi-receiver milestones — Enforcing single-receiver invariant at the schema layer is the right approach here. The milestones[0].receiver read 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!

Comment thread apps/web/lib/auth/server-action-auth.ts
Comment thread apps/web/lib/auth/server-action-auth.ts
- 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.
@coderabbitai coderabbitai Bot added the difficulty: hard This issue requires a high to a challenged high effort on complexity label Apr 28, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

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 | 🟠 Major

Adopt next-safe-action for 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-action for **/*.{ts,tsx} files to provide standardized security guardrails. Consider migrating these helpers into a next-safe-action client so new server actions inherit consistent protections without wrapper overhead.

Additionally, enforceRateLimit logs raw identifier values at lines 128 and 139 in production. Since callers pass email addresses (e.g., email.toLowerCase() in auth.ts for sign_up and request_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

📥 Commits

Reviewing files that changed from the base of the PR and between 37b14f2 and d1b0229.

📒 Files selected for processing (5)
  • apps/web/app/actions/auth.ts
  • apps/web/app/actions/escrow/save-escrow-contract.ts
  • apps/web/lib/auth/rate-limiter.ts
  • apps/web/lib/auth/server-action-auth.ts
  • apps/web/lib/schemas/server-actions.schemas.ts

Comment on lines 198 to 202
logger.info({
eventType: 'SESSION_CREATED',
userId,
email,
userId: validated.userId,
email: validated.email,
})
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Comment on lines 400 to +405
export async function updateEscrowStatusAction(
id: string,
newStatus: EscrowStatusType,
newStatus: string,
): Promise<EscrowResponse> {
try {
await requireAdminSession('updateEscrowStatusAction')
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +123 to +145
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,
})
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +19 to +23
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),
})
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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'.

Comment on lines +36 to +39
export const updateDeviceWithDeployeeInputSchema = z.object({
credentialId: z.string().min(1, 'credentialId is required'),
aaguid: z.string().min(1, 'aaguid is required'),
})
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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.

Comment on lines +95 to +103
.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',
},
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

@Bran18 Bran18 merged commit 1f3a740 into kindfi-org:develop Apr 28, 2026
1 of 3 checks passed
Bran18 added a commit that referenced this pull request May 26, 2026
* 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>
Bran18 added a commit that referenced this pull request May 27, 2026
…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>
Bran18 added a commit that referenced this pull request Jun 2, 2026
* 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>
Bran18 added a commit that referenced this pull request Jun 2, 2026
* 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend Back-end development related difficulty: hard This issue requires a high to a challenged high effort on complexity feature New requested feature refactor Code refactoring and improvements webapp web app related

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Security: Enforce auth inside Server Actions ("use server") and add input validation

2 participants