{"record":{"id":"44d9f01c3e873e2a","repo":"instructure/canvas-lms","slug":"an-object-with-an-interface-for-loading-settings-must-be-44d9f0","errorCode":null,"errorMessage":"an object with an interface for loading settings must be specified as 'settings_store'","messagePattern":"an object with an interface for loading settings must be specified as 'settings_store'","errorType":"exception","errorClass":"UnconfiguredError","httpStatus":null,"severity":"error","filePath":"gems/canvas_security/lib/canvas_security.rb","lineNumber":53,"sourceCode":"\n  # TODO: Maybe at one point Setting will be\n  # a gem on it's own or some other dependable module.\n  # For the moment, this is a convenient way to inject\n  # this base class without needing to depend on it directly.\n  mattr_writer :settings_store\n  mattr_accessor :region, :environment\n\n  # Expected interface for this object is:\n  #   object.get(setting_name, 'default_value') # [ returning a string ]\n  #\n  # In this instance, it's expected that canvas is going to inject\n  # the Setting class, but we want to break depednencies that directly\n  # point to canvas.\n  def self.settings_store(safe_invoke: false)\n    return @@settings_store if @@settings_store\n    return nil if safe_invoke\n\n    raise UnconfiguredError, \"an object with an interface for loading settings must be specified as 'settings_store'\"\n  end\n\n  class AuthenticationError < RuntimeError\n    def response_status\n      401\n    end\n  end\n\n  class InvalidToken < AuthenticationError\n  end\n\n  class TokenExpired < AuthenticationError\n  end\n\n  class InvalidJwtKey < AuthenticationError\n  end\n\n  def self.encryption_key","sourceCodeStart":35,"sourceCodeEnd":71,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/gems/canvas_security/lib/canvas_security.rb#L35-L71","documentation":"CanvasSecurity decouples itself from Canvas's Setting class: consumers must inject an object exposing the settings-loading interface via CanvasSecurity.settings_store = .... Reading CanvasSecurity.settings_store when none was injected raises UnconfiguredError (unless safe_invoke: true, which returns nil).","triggerScenarios":"Invoking any CanvasSecurity API that reads settings (e.g. jwt encryption key lookups) before the host app assigned CanvasSecurity.settings_store = SomeStore.","commonSituations":"Using the canvas_security gem standalone or in a new service without initializer configuration; boot-order issues where CanvasSecurity is used before initializers run; test suites without the usual Canvas initializer.","solutions":["Add `CanvasSecurity.settings_store = Setting` (or an equivalent object responding to the settings interface) in an initializer","Ensure the initializer runs before any code path touching CanvasSecurity (fix boot/autoload ordering)","Use `settings_store(safe_invoke: true)` in code that tolerates an unconfigured store"],"exampleFix":"// before\nCanvasSecurity::ServicesJwt.for_user(domain, user)\n// after\n# config/initializers/canvas_security.rb\nCanvasSecurity.settings_store = Setting\nCanvasSecurity::ServicesJwt.for_user(domain, user)","handlingStrategy":"validation","validationCode":"CanvasSecurity.settings_store(safe_invoke: true) or raise 'CanvasSecurity not configured'","typeGuard":"def canvas_security_configured? = !CanvasSecurity.settings_store(safe_invoke: true).nil?","tryCatchPattern":"begin\n  CanvasSecurity::ServicesJwt.for_user(domain, user)\nrescue CanvasSecurity::UnconfiguredError\n  configure_canvas_security!\n  retry\nend","preventionTips":["Set CanvasSecurity.settings_store in an initializer","Verify gem boot order in standalone services","Add a smoke test asserting the store is configured"],"tags":["ruby","configuration","initialization"],"backgroundTag":"missing-required-config","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"}