---
id: w4/m16/t002
title: "Approve/deny server actions → Hydra admin accept (granted scopes, remember: true) / reject"
worker: worker4
status: done
estimate: 35m
depends_on: [w4/m16/t001]
---

## Objective

Wire the consent page's Approve and Deny buttons to server actions that complete the Hydra consent flow. Approve calls `acceptOAuth2ConsentRequest` with the granted scopes + audience and `remember: true` / `remember_for: REMEMBER_FOR_SECONDS` (so a second authorization within the window skips consent — the same remember shape the trusted path already uses); Deny calls `rejectOAuth2ConsentRequest` with a standard OAuth `access_denied` error. Both redirect the browser to Hydra's `redirect_to`.

## Context

- The trusted-path accept in `hydra-consent.ts` (`grant_scope: consent.requested_scope`, `grant_access_token_audience: consent.requested_access_token_audience`, `remember: true`) is the shape to reuse — factor it so headless and UI-approved accepts share one accept helper.
- `@ory/client-fetch`'s `OAuth2Api` has both `acceptOAuth2ConsentRequest` and `rejectOAuth2ConsentRequest`; reject takes `{ error: "access_denied", error_description: … }` and also returns a `redirect_to` (Hydra bounces the agent back to its redirect_uri with the OAuth error).
- Server actions must run server-side only (TanStack Start server functions) — same `HYDRA_ADMIN_URL` custody rules as t001.

## Steps

1. Factor the existing accept call into a shared helper; add a reject helper.
2. Add approve/deny server functions taking the `consent_challenge`; each returns Hydra's `redirect_to` for the client to navigate to.
3. Wire the consent page buttons: pending/disabled states, navigate to `redirect_to` on completion; surface a Hydra failure as a visible error state (not a silent hang).

## Files

- `dashboard/src/common/server-fn/hydra-consent.ts`
- `dashboard/src/features/auth/` (consent page component)
- `dashboard/src/features/auth/locales/{en,zh}.ts`

## Acceptance criteria

- [ ] Approve → Hydra accept with the requested scopes/audience granted, `remember: true` → browser lands back at the agent's redirect_uri with a code; a repeat authorization inside the remember window skips the consent page.
- [ ] Deny → Hydra reject → the agent receives `error=access_denied` at its redirect_uri; no token is issuable from that flow.
- [ ] A Hydra admin failure during accept/reject shows an error state; nothing is granted.
- [ ] `yarn typecheck` + `yarn lint` clean.

## Out of scope

- CSRF and subject↔session binding on these actions (t003 — do not ship this task to prod without t003).
- Per-scope checkboxes (grant-all-requested only; scope pruning is future work if an agent ever over-asks).
