22a01dc1c6
## Problem Fixes #21248. After upgrading, workspace members who open a password reset link while a token pair still exists in local storage get a generic `You do not have permission to perform this action.` (FORBIDDEN) error, blocking account recovery. ## Root cause Every GraphQL request passes through `GraphQLHydrateRequestFromTokenMiddleware` before any resolver. If a token is present it validates it; if no token is present it short-circuits and lets the request through unauthenticated. The reset flow was only ever designed for the unauthenticated case (the user is logged out, so no token exists). Two intended changes broke that assumption: - A token pair now persists in local storage at reset time (unified `accessOrWorkspaceAgnosticToken` + tokenPair moved off session cookies into local storage). - The Apollo auth link attaches `authorization: Bearer <token>` whenever any token pair exists, regardless of the operation. So the public `validatePasswordResetToken` / `updatePasswordViaResetToken` operations now arrive with a token that the middleware rejects, producing FORBIDDEN before the resolver runs. Note: the `PublicEndpointGuard` / `NoPermissionGuard` on these resolvers both just `return true` — they do not inspect headers and are not the gate. The middleware is. ## Fix Add a generic `skipAuthToken` operation-context flag. The auth link omits the `Authorization` header when a request sets it, staying agnostic of any specific operation or endpoint. The two public reset operations opt in at their call site in `PasswordReset.tsx`. This restores the exact unauthenticated path the flow was designed for, regardless of whether a token pair happens to sit in local storage. Nothing is reverted; all authenticated traffic is unaffected. ## Testing Ran the built frontend against a local backend, logged in so a `tokenPairState` was present in local storage, then opened a reset link and inspected the outgoing `ValidatePasswordResetToken` request: - Request headers: `accept`, `content-type`, `x-locale` only. No `authorization` header, despite a token pair being present. - With an invalid token the response is the resolver-level `Token is invalid` error (it reaches the resolver) instead of the middleware's FORBIDDEN. - With a valid token the query succeeds (`validatePasswordResetToken` returns the email + `hasPassword`) and the Set/Change Password form renders, so the recovery flow completes. Lint and typecheck pass on the changed files.