toeverything/AFFiNE · error · AccessDenied

access_denied

access_denied

Error message

You do not have permission to access this resource.

What it means

Thrown by the User.subscriptions GraphQL field resolver when the authenticated user (from @CurrentUser) is not the same user as the @Parent User object being resolved. It is an ownership check: subscription lists are private and can only be resolved for yourself.

Source

Thrown at packages/backend/server/src/plugins/payment/resolver.ts:577

  private subscriptionPlan(plan: string) {
    return plan === 'lifetime_pro' ? SubscriptionPlan.Pro : plan;
  }

  private subscriptionStatus(entitlement: Entitlement) {
    if (entitlement.status === 'grace') {
      return SubscriptionStatus.PastDue;
    }
    return SubscriptionStatus.Active;
  }

  @ResolveField(() => [SubscriptionType])
  async subscriptions(
    @CurrentUser() me: User,
    @Parent() user: User
  ): Promise<Subscription[]> {
    if (me.id !== user.id) {
      throw new AccessDenied();
    }

    return this.currentUserSubscriptions(user.id);
  }

  @ResolveField(() => Int, {
    name: 'invoiceCount',
    description: 'Get user invoice count',
  })
  async invoiceCount(@CurrentUser() user: CurrentUser) {
    return this.db.invoice.count({
      where: { targetId: user.id },
    });
  }

  @ResolveField(() => [InvoiceType])
  async invoices(
    @CurrentUser() me: User,

View on GitHub (pinned to b4c8548c09)

Solutions

  1. Request subscriptions only on the currentUser query or on user(id: <your own id>).
  2. Move the subscriptions field out of nested/other-user selections in your GraphQL documents.
  3. If you genuinely need another user's subscriptions (support tooling), use a server-side/admin API rather than this field.

Example fix

# before
query {
  user(id: "someone-else") { subscriptions { ... } }
}

# after
query {
  currentUser { subscriptions { ... } }
}
Defensive patterns

Strategy: validation

Validate before calling

// Only request subscriptions for the signed-in user
const { data } = await gql(`query { currentUser { id subscriptions { ... } } }`);

Try / catch

catch (e) { if (gqlErrorCode(e) === 'access_denied') { clearNestedSubscriptionsSelection(); refetchAsCurrentUser(); return; } throw e; }

Prevention

When it happens

Trigger: Querying user(id: X) { subscriptions } where X is any id other than the signed-in user's id; resolving subscriptions on a user object obtained from a nested relation (e.g. workspace member list) instead of the currentUser query.

Common situations: Admin dashboards trying to list another user's subscriptions; frontend nesting { subscriptions } under a member/owner field of a workspace query; stale auth token for user A while the page renders user B's profile.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


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