toeverything/AFFiNE · warning · ActionForbidden
action_forbidden
action_forbidden
Error message
You are not allowed to perform this action.
What it means
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').
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 26c515e050)
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.
Example fix
# before (deprecated, errors for non-self user)
query { otherUser { id token { token } } }
# after — use auth session exchange for the current user
mutation { authSessionExchange(...) { ... } } Defensive patterns
Strategy: type-guard
Validate before calling
// only request the deprecated token field for the current user
function canRequestToken(parentUser, currentUser): boolean {
return parentUser.id === currentUser.id;
} Type guard
function isActionForbidden(err: unknown): boolean {
return (
!!err &&
typeof err === 'object' &&
(err as { code?: string }).code === 'action_forbidden'
);
} Try / catch
try {
const { token } = await client.query(user.id === me.id ? TOKEN_FIELD : NO_TOKEN);
} catch (err) {
if (isActionForbidden(err)) {
// stop selecting the deprecated token field on other users
}
throw err;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12).
Data as JSON: /api/errors/86ed763ba78c4e23.
Report an issue: GitHub.