{"record":{"id":"42df537cba7d2b87","repo":"instructure/canvas-lms","slug":"must-be-logged-in","errorCode":null,"errorMessage":"Must be logged in","messagePattern":"Must be logged in","errorType":"exception","errorClass":"GraphQL::ExecutionError","httpStatus":null,"severity":"error","filePath":"app/graphql/mutations/accept_enrollment_invitation.rb","lineNumber":30,"sourceCode":"# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY\n# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR\n# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more\n# details.\n#\n# You should have received a copy of the GNU Affero General Public License along\n# with this program. If not, see <http://www.gnu.org/licenses/>.\n#\n\nmodule Mutations\n  class AcceptEnrollmentInvitation < BaseMutation\n    argument :enrollment_uuid, String, required: true\n\n    field :enrollment, Types::EnrollmentType, null: true\n    field :success, Boolean, null: false\n\n    def resolve(input:, **)\n      user = context[:current_user]\n      raise GraphQL::ExecutionError, I18n.t(\"Must be logged in\") unless user\n\n      enrollment = Enrollment.where(uuid: input[:enrollment_uuid]).first\n      raise GraphQL::ExecutionError, I18n.t(\"Enrollment invitation not found\") unless enrollment\n\n      # Verify the enrollment belongs to the current user\n      raise GraphQL::ExecutionError, I18n.t(\"Unauthorized\") unless enrollment.user == user\n\n      # Verify the enrollment is in invited state\n      raise GraphQL::ExecutionError, I18n.t(\"Enrollment is not in invited state\") unless enrollment.invited?\n\n      begin\n        if enrollment.accept!\n          {\n            enrollment:,\n            success: true\n          }\n        else\n          {","sourceCodeStart":12,"sourceCodeEnd":48,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/graphql/mutations/accept_enrollment_invitation.rb#L12-L48","documentation":"AcceptEnrollmentInvitation mutation requires context[:current_user]; when the GraphQL context carries no authenticated user it raises I18n.t(\"Must be logged in\") as a GraphQL::ExecutionError. The mutation deliberately checks authentication before touching the enrollment record so anonymous tokens fail fast with a clear message.","triggerScenarios":"Calling mutation acceptEnrollmentInvitation with an unauthenticated token: missing/expired access token, token not sent in the Authorization header, or a public/anonymous GraphQL request resolving this mutation.","commonSituations":"Invitation-acceptance flows in email clients where the deep link opens the app before login; expired OAuth tokens after inactivity; missing Authorization header in a script hitting /api/graphql; incorrectly configured middleware that fails to populate context[:current_user].","solutions":["Authenticate first: obtain an access token (login or OAuth) and send it as `Authorization: Bearer <token>` on the GraphQL request.","Check token expiry and refresh/re-login before retrying the mutation.","Confirm the GraphQL controller actually populates context[:current_user] (session or token auth) in your environment.","In the UI, ensure the invitation link routes through login before executing the mutation.","Test with a known-good token to distinguish auth failure from mutation bugs."],"exampleFix":"// before\nfetch(\"/api/graphql\", { method: \"POST\", body: JSON.stringify({ query, variables }) })\n// after\nfetch(\"/api/graphql\", { method: \"POST\", headers: { Authorization: `Bearer ${token}` }, body: JSON.stringify({ query, variables }) })","handlingStrategy":"try-catch","validationCode":"// before calling the mutation\nif (!accessToken || isExpired(accessToken)) await refreshTokenOrRedirectToLogin()\n// and ensure request headers include Authorization: Bearer <token>","typeGuard":"const hasAuth = (ctx) => typeof ctx?.currentUser?.id !== \"undefined\" && ctx.currentUser.id !== null","tryCatchPattern":"try {\n  const res = await client.request(ACCEPT_INVITATION_MUTATION, { enrollmentUuid })\n} catch (e) {\n  if (/Must be logged in/i.test(e.message)) return redirectToLogin()\n  throw e\n}","preventionTips":["Send Authorization header on every GraphQL request","Refresh tokens proactively before invitation flows","Route invitation deep links through login when no session exists","Verify GraphQL context auth setup in custom middleware"],"tags":["graphql","authentication","mutation","token"],"backgroundTag":"authentication-required","analyzedSha":"1c9f0bb8013ed69c4f2efe11fd483025469b7e6c","analyzedAt":"2026-09-15T20:33:18.891Z","contentChangedAt":"2026-09-15T20:33:18.891Z","schemaVersion":2},"datasetVersion":"2026-09-23T02:17:17.105Z"}