toeverything/AFFiNE · error · ActionForbidden

action_forbidden

action_forbidden

Error message

You are not allowed to perform this action.

What it means

The deprecated GraphQL field UserType.token (authResolver.clientToken) mints a legacy client session token and is protected: if the parent user's id differs from the authenticated CurrentUser's id, it throws ActionForbidden (action_forbidden). You can only ever request this field for yourself; its deprecationReason directs clients to the auth session exchange flow instead.

Solutions

  1. Only include token in selections where user.id === current signed-in user id
  2. Migrate to the auth session exchange flow (POST /api/auth/session/exchange with the one-time code) - the field is deprecated
  3. Audit shared GraphQL fragments so the deprecated selection is not silently included for other users
  4. Remove the field from bulk/admin listings entirely

Example fix

# before
query AdminUsers {
  users { id email token } # token forbidden for every non-self user
}

# after
query AdminUsers {
  users { id email } # drop the deprecated field
}

# self token: use the session exchange instead of `token`
# clientToken -> POST /api/auth/session/exchange { code, installationId, platform }
Defensive patterns

Strategy: validation

Validate before calling

function buildUserSelection(currentUserId: string, targetUserId: string) {
  const base = 'id email';
  // deprecated self-only field: include only when querying yourself
  return targetUserId === currentUserId ? `${base} token` : base;
}

Prevention

When it happens

Trigger: A GraphQL query selecting user { token } where the user argument/id resolved is another user (e.g. via a user lookup by email); admin tooling listing users and naively selecting token on every node; cached queries replayed under a different signed-in user.

Common situations: Admin dashboards bulk-querying the field; queries built by stitching fragments that include the deprecated selection; clients still on the pre-exchange auth model after upgrading past the deprecation.

Related errors


AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18). Data as JSON: /api/errors/86ed763ba78c4e23. Report an issue: GitHub.

Appendix: source

Thrown at packages/backend/server/src/core/auth/resolver.ts:89

  @Query(() => UserType, {
    name: 'currentUser',
    description: 'Get current user',
    nullable: true,
  })
  currentUser(@CurrentUser() user?: CurrentUser): UserType | undefined {
    return user;
  }

  @ResolveField(() => ClientTokenType, {
    name: 'token',
    deprecationReason: 'use auth session exchange instead',
  })
  async clientToken(
    @CurrentUser() currentUser: CurrentUser,
    @Parent() user: UserType
  ): Promise<ClientTokenType> {
    if (user.id !== currentUser.id) {
      throw new ActionForbidden();
    }

    const userSession = await this.auth.createUserSession(user.id);

    return {
      sessionToken: userSession.sessionId,
      token: userSession.sessionId,
      refresh: '',
    };
  }

  @Public()
  @Mutation(() => Boolean)
  async changePassword(
    @Args('token') token: string,
    @Args('newPassword') newPassword: string,
    @Args('userId', { type: () => String, nullable: true }) userId?: string
  ) {

View on GitHub (pinned to b4c8548c09)