{"record":{"id":"f0ca09096fdbff91","repo":"instructure/canvas-lms","slug":"unauthorized","errorCode":null,"errorMessage":"Unauthorized","messagePattern":"Unauthorized","errorType":"exception","errorClass":"GraphQL::ExecutionError","httpStatus":null,"severity":"error","filePath":"app/graphql/mutations/accept_enrollment_invitation.rb","lineNumber":36,"sourceCode":"# 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          {\n            enrollment: nil,\n            success: false,\n            errors: [{ message: I18n.t(\"Failed to accept enrollment invitation\") }]\n          }\n        end\n      rescue => e","sourceCodeStart":18,"sourceCodeEnd":54,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/graphql/mutations/accept_enrollment_invitation.rb#L18-L54","documentation":"After locating the enrollment, the mutation verifies it belongs to the caller: unless enrollment.user == user it raises 'Unauthorized'. This prevents accepting someone else's invitation even if you know its uuid — enrollment invitations are non-transferable.","triggerScenarios":"Accepting an invitation while logged in as a different user than the invited one: a parent/admin logged into an admin session clicking a teacher's invitation link; two accounts in one browser (impersonation or shared machine); forwarding an invitation email to a colleague who accepts it under their own login.","commonSituations":"Shared workstations with multiple Canvas accounts; admins impersonating users whose sessions carry the wrong current_user; automation replaying invitation uuids captured from another user's email.","solutions":["Log in as the exact invited user before executing the mutation.","Cross-check enrollment.user_id against the current user client-side and redirect to a login/account-switch screen on mismatch.","Stop forwarding invitation links; re-invite the correct user from the course People page instead.","Verify no session-impersonation (as_user_id) is active when accepting invitations.","In tests/scripts, use the invited user's token, not an admin's."],"exampleFix":"// before\nacceptEnrollmentInvitation({ enrollmentUuid }) // any logged-in user\n// after\nif (enrollmentUserId !== currentUser.id) redirect(\"/login?switch_user=true\")\nelse acceptEnrollmentInvitation({ enrollmentUuid })","handlingStrategy":"validation","validationCode":"// before accepting, confirm the invitation belongs to the logged-in user\nconst invitedUserId = decodeInvitationUserId(enrollmentUuid) // or fetched enrollment.user_id\nif (String(invitedUserId) !== String(currentUser.id))\n  throw new Error(\"this invitation belongs to a different user; log in as the invited account\")","typeGuard":"const ownsEnrollment = (enrollment, user) => String(enrollment.user_id) === String(user.id)","tryCatchPattern":"try {\n  await client.request(ACCEPT_INVITATION, { enrollmentUuid })\n} catch (e) {\n  if (/^Unauthorized$/.test(e.message)) return showWrongAccountScreen()\n  throw e\n}","preventionTips":["Never forward invitation links between accounts","Avoid accepting invitations while impersonating another user","Show which account is logged in on invitation landing pages","Use the invited user's token in automation"],"tags":["graphql","authorization","enrollment","ownership"],"backgroundTag":"permission-denied","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"}