toeverything/AFFiNE · error · AuthenticationRequired
authentication_required
authentication_required
Error message
You must sign in first to access this resource.
What it means
Thrown by acceptInvitation when inviteId does not match any email invitation (no workspaceUser record) so it is treated as an invitation-by-link, and there is no authenticated user in the GraphQL context. Link invitations require an existing account because the server must attach the user to the workspace.
Solutions
- Redirect the user to the sign-in page with the invite link as the return URL, then call acceptInvitation again after login.
- If calling the API directly, send the session cookie or Authorization token for an existing account.
- Register an account first if the user does not have one, then retry the link.
Defensive patterns
Strategy: validation
Validate before calling
// Only fire accept for link invites once a session exists
const me = await gql.request(CURRENT_USER_QUERY);
if (!me.currentUser) {
// preserve the invite URL across login
router.push(`/sign-in?redirect=${encodeURIComponent(location.pathname)}`);
} else {
await gql.request(ACCEPT_INVITATION, { inviteId });
} Try / catch
try {
await acceptInvitation(inviteId);
} catch (e) {
if (getErrorCode(e) === 'authentication_required') {
redirectToSignInWithReturnUrl(currentUrl);
}
} Prevention
- Gate the invite-accept route behind an auth check so anonymous users are redirected to sign-in with the invite URL preserved.
- Wait for the auth/session restoration promise to settle before firing invitation mutations.
When it happens
Trigger: An anonymous visitor follows an invite link (e.g. /invite/<inviteId>) and the client fires the acceptInvitation mutation before a sign-in session exists.
Common situations: Invite link opened in an incognito window or after session expiry; frontend routing calls acceptInvitation before the auth provider finished restoring the session; API consumers calling the mutation without an Authorization header.
Related errors
AI-assisted analysis of toeverything/AFFiNE@2af30773ae (2026-08-18).
Data as JSON: /api/errors/12920541ba62e9a9.
Report an issue: GitHub.
Appendix: source
Thrown at packages/backend/server/src/core/workspaces/resolvers/member.ts:587
@Args('inviteId') inviteId: string,
@Args('workspaceId', { deprecationReason: 'never used', nullable: true })
_workspaceId: string,
@Args('sendAcceptMail', {
nullable: true,
deprecationReason: 'never used',
})
_sendAcceptMail: boolean
) {
const role = await this.models.workspaceUser.getById(inviteId);
// invitation by email
if (role) {
if (user.id !== role.userId) {
throw new InvitationAccountMismatch();
}
await this.acceptInvitationByEmail(role);
} else {
// invitation by link
const invitation = await this.cache.get<{
workspaceId: string;
inviterUserId: string;
}>(`workspace:inviteLinkId:${inviteId}`);
if (!invitation) {
throw new InvalidInvitation();
}
const role = await this.models.workspaceUser.get(
invitation.workspaceId,
user.id
);
if (role) {
// if status is pending, should accept the invitation directly
if (role.status === WorkspaceMemberStatus.Pending) {
await this.acceptInvitationByEmail(role);View on GitHub (pinned to 2af30773ae)