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
- Only include token in selections where user.id === current signed-in user id
- Migrate to the auth session exchange flow (POST /api/auth/session/exchange with the one-time code) - the field is deprecated
- Audit shared GraphQL fragments so the deprecated selection is not silently included for other users
- 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
- Migrate off UserType.token to the auth session exchange endpoint
- Lint GraphQL operations for the deprecated selection so it cannot sneak into shared fragments
- Never include auth-credential fields in bulk/admin listings
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)