{"record":{"id":"86ed763ba78c4e23","repo":"toeverything/AFFiNE","slug":"action-forbidden-86ed76","errorCode":"action_forbidden","errorMessage":"You are not allowed to perform this action.","messagePattern":"You are not allowed to perform this action\\.","errorType":"exception","errorClass":"ActionForbidden","httpStatus":403,"severity":"warning","filePath":"packages/backend/server/src/core/auth/resolver.ts","lineNumber":89,"sourceCode":"  @Query(() => UserType, {\n    name: 'currentUser',\n    description: 'Get current user',\n    nullable: true,\n  })\n  currentUser(@CurrentUser() user?: CurrentUser): UserType | undefined {\n    return user;\n  }\n\n  @ResolveField(() => ClientTokenType, {\n    name: 'token',\n    deprecationReason: 'use auth session exchange instead',\n  })\n  async clientToken(\n    @CurrentUser() currentUser: CurrentUser,\n    @Parent() user: UserType\n  ): Promise<ClientTokenType> {\n    if (user.id !== currentUser.id) {\n      throw new ActionForbidden();\n    }\n\n    const userSession = await this.auth.createUserSession(user.id);\n\n    return {\n      sessionToken: userSession.sessionId,\n      token: userSession.sessionId,\n      refresh: '',\n    };\n  }\n\n  @Public()\n  @Mutation(() => Boolean)\n  async changePassword(\n    @Args('token') token: string,\n    @Args('newPassword') newPassword: string,\n    @Args('userId', { type: () => String, nullable: true }) userId?: string\n  ) {","sourceCodeStart":71,"sourceCodeEnd":107,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/26c515e050211269e911f7d9cfe162a26c83ed98/packages/backend/server/src/core/auth/resolver.ts#L71-L107","documentation":"The deprecated clientToken resolver field issues a user session token for the resolved UserType. It enforces that the resolved user is the same as the authenticated currentUser; if user.id !== currentUser.id, ActionForbidden is thrown. This prevents one authenticated user from minting a session token on behalf of a different user. The field itself is marked deprecated ('use auth session exchange instead').","triggerScenarios":"A GraphQL query selecting user { token } (or clientToken) on a UserType whose id differs from the JWT/cookie-authenticated currentUser id — e.g. querying another user's profile and asking for their token field.","commonSituations":"A client still on the deprecated token field tries to fetch a token while viewing another user's profile. Misconstructed query selects token on a non-self user node. Stale client code that hasn't migrated to the auth session exchange flow.","solutions":["Migrate to the auth session exchange flow (the deprecated replacement) and stop selecting the token field.","Only request the token field on the current user (user.id === currentUser.id).","Update the client to the latest version that no longer references this deprecated field.","Audit the query to ensure it does not alias another user into the UserType parent."],"exampleFix":"# before (deprecated, errors for non-self user)\nquery { otherUser { id token { token } } }\n# after — use auth session exchange for the current user\nmutation { authSessionExchange(...) { ... } }","handlingStrategy":"type-guard","validationCode":"// only request the deprecated token field for the current user\nfunction canRequestToken(parentUser, currentUser): boolean {\n  return parentUser.id === currentUser.id;\n}","typeGuard":"function isActionForbidden(err: unknown): boolean {\n  return (\n    !!err &&\n    typeof err === 'object' &&\n    (err as { code?: string }).code === 'action_forbidden'\n  );\n}","tryCatchPattern":"try {\n  const { token } = await client.query(user.id === me.id ? TOKEN_FIELD : NO_TOKEN);\n} catch (err) {\n  if (isActionForbidden(err)) {\n    // stop selecting the deprecated token field on other users\n  }\n  throw err;\n}","preventionTips":["Migrate clients off the deprecated token field to the auth session exchange flow.","Only select user.token for the current user node.","Update the client to a version that no longer references the deprecated field.","Lint GraphQL operations to flag token selection on non-self UserType nodes."],"tags":["auth","graphql","deprecated","authorization","session"],"backgroundTag":null,"analyzedSha":"26c515e050211269e911f7d9cfe162a26c83ed98","analyzedAt":"2026-08-12T13:15:16.447Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}