{"record":{"id":"86467ab5adc307f8","repo":"Stirling-Tools/Stirling-PDF","slug":"member-has-no-backend-identity","errorCode":null,"errorMessage":"Member has no backend identity","messagePattern":"Member has no backend identity","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"frontend/editor/src/portal/api/users.ts","lineNumber":397,"sourceCode":"    return;\n  }\n\n  // target === \"member\": drop any leadership, and normalise a non-ROLE_USER authority.\n  if (holdsLeader && member.teamId) {\n    await apiClient.local.form(\"/api/v1/team/removeOwner\", {\n      teamId: String(member.teamId),\n      userId: member.id,\n    });\n  }\n  if (holdsAdmin || holdsWebOnly)\n    await setAuthority(member.username, \"ROLE_USER\");\n}\n\nexport async function setMemberSuspended(\n  member: Member,\n  suspended: boolean,\n): Promise<void> {\n  if (!member.username) throw new Error(\"Member has no backend identity\");\n  await apiClient.local.form(\n    `/api/v1/user/admin/changeUserEnabled/${encodeURIComponent(member.username)}`,\n    { enabled: String(!suspended) },\n  );\n}\n\nexport async function removeMember(member: Member): Promise<void> {\n  if (!member.username) throw new Error(\"Member has no backend identity\");\n  await apiClient.local.json(\n    `/api/v1/user/admin/deleteUser/${encodeURIComponent(member.username)}`,\n    { method: \"POST\" },\n  );\n}\n\nexport interface ResetPasswordOptions {\n  /** The new password (client-generated or admin-typed). */\n  newPassword: string;\n  forcePasswordChange?: boolean;","sourceCodeStart":379,"sourceCodeEnd":415,"githubUrl":"https://github.com/Stirling-Tools/Stirling-PDF/blob/9ef20dcab80b85041912f045e17a6aea1d08f969/frontend/editor/src/portal/api/users.ts#L379-L415","documentation":"Thrown by setMemberSuspended() when member.username is falsy. The suspension call hits /api/v1/user/admin/changeUserEnabled/{username}, which requires a real backend username. Members without a username are pure fixtures/invited-not-yet-provisioned rows that have no backend identity, so suspending them is impossible.","triggerScenarios":"An admin attempts to suspend a member whose username is undefined — an invited-but-not-provisioned member, a demo fixture, or a row that never completed backend provisioning.","commonSituations":"Member is in 'invited' status and never accepted (no backend user created); a fixture-only member loaded in a demo/mock context; the roster sync dropped the username field.","solutions":["Check member.username (and member.status) before offering the suspend action; hide it for invited/fixture members.","If the member should have a username, re-run provisioning/invitation acceptance first.","Validate at the call site and surface 'this member has no backend account yet' to the admin."],"exampleFix":"// before — throws for members with no backend identity\nexport async function setMemberSuspended(member: Member, suspended: boolean) {\n  if (!member.username) throw new Error(\"Member has no backend identity\");\n  ...\n}\n\n// after — typed error + UI gating on username presence\nif (!member.username) throw new NoBackendIdentityError(member.id);\n// UI: disable the Suspend button when !member.username","handlingStrategy":"validation","validationCode":"import type { Member } from \"@portal/api/users\";\nfunction hasBackendIdentity(member: Member): boolean { return !!member.username; }\n// before suspend: if (!hasBackendIdentity(member)) blockSuspend();","typeGuard":"function isNoBackendIdentity(e: unknown): boolean {\n  return e instanceof Error && /no backend identity/i.test(e.message);\n}","tryCatchPattern":"try { await setMemberSuspended(member, true); }\ncatch (e) { if (isNoBackendIdentity(e)) { notify('Member has no backend account yet.'); return; } throw e; }","preventionTips":["Hide/disable the Suspend action for members with no username (invited/fixture).","Check member.username and member.status before offering backend actions.","Re-provision invited members before allowing backend mutations."],"tags":["portal","users","validation","backend-identity","admin"],"backgroundTag":null,"analyzedSha":"9ef20dcab80b85041912f045e17a6aea1d08f969","analyzedAt":"2026-08-13T22:11:39.827Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}