{"record":{"id":"c8269fb7b536a137","repo":"instructure/canvas-lms","slug":"unauthorized-reject-enrollment-invitation","errorCode":null,"errorMessage":"Unauthorized","messagePattern":"Unauthorized","errorType":"exception","errorClass":"GraphQL::ExecutionError","httpStatus":null,"severity":"error","filePath":"app/graphql/mutations/reject_enrollment_invitation.rb","lineNumber":36,"sourceCode":"# with this program. If not, see <http://www.gnu.org/licenses/>.\n#\n\nmodule Mutations\n  class RejectEnrollmentInvitation < 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.reject\n          {\n            enrollment:,\n            success: true\n          }\n        else\n          {\n            enrollment: nil,\n            success: false,\n            errors: [{ message: I18n.t(\"Failed to reject enrollment invitation\") }]\n          }\n        end\n      rescue => e","sourceCodeStart":18,"sourceCodeEnd":54,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/graphql/mutations/reject_enrollment_invitation.rb#L18-L54","documentation":"Raised when the enrollment found by uuid does not belong to the current user (enrollment.user != current_user). The mutation only lets the invited user reject their own invitation, preventing users from rejecting others' enrollments.","triggerScenarios":"A logged-in user submits a rejection for someone else's enrollment uuid; shared/forwarded invitation link opened by another account; admin or observer attempting the mutation on behalf of a student.","commonSituations":"Multiple accounts logged in across browser profiles; forwarding the invitation email to a colleague who is already logged into their own Canvas account; automation using a service account token.","solutions":["Log in as the user the enrollment belongs to before rejecting","Match the invitation email's target account (check email address vs active session)","If an admin must reject, use the enrollments admin API instead of this user-scoped mutation","Verify enrollment.user_id == current_user.id before calling"],"exampleFix":"// before\nrejectEnrollmentInvitation(forwardedUuid) // session user != invitee\n// after\nif (enrollment.userId === currentUser.id) {\n  rejectEnrollmentInvitation(enrollment.uuid)\n}","handlingStrategy":"validation","validationCode":"if (enrollment.userId !== currentUser.id) {\n  show('This invitation belongs to a different account — log in as ' + enrollment.userEmail)\n  return\n}","typeGuard":"function isOwnEnrollment(enrollment, user) { return Boolean(user) && enrollment.userId === user.id }","tryCatchPattern":"try {\n  await rejectEnrollmentInvitation({ enrollmentUuid })\n} catch (e) {\n  if (e.message.includes('Unauthorized')) show('Log in with the account the invitation was sent to')\n  else throw e\n}","preventionTips":["Show the invitee email on the action page and warn on account mismatch","Avoid forwarding authenticated invitation links","For admin workflows, use admin enrollment APIs instead","Verify enrollment.user_id matches the session user before mutating"],"tags":["graphql","authorization","enrollments"],"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"}