Module: CamaleonCms::SetupToken
- Defined in:
- lib/camaleon_cms/setup_token.rb
Overview
Gates the first-run installer so setup can be completed only by someone with access to the server's environment or filesystem — not by any anonymous visitor who reaches the app on a fresh deploy (audit finding C1, vector 2). The expected token comes from ENV when set, otherwise from a generated 0600 file under tmp/; generating it logs its location once so the operator can read it. It is required only while no site exists, and is cleared once one does. See openspec/specs/installer-access-control/spec.md.
Constant Summary collapse
- ENV_KEY =
'CAMALEON_SETUP_TOKEN'.freeze
Class Method Summary collapse
-
.clear! ⇒ Object
Invalidate the generated token once setup has completed (the ENV override, if any, is untouched).
- .file_path ⇒ Object
- .read_or_generate_file ⇒ Object
-
.required? ⇒ Boolean
The installer is gated only until the first site exists; afterwards it is closed anyway.
-
.valid?(candidate) ⇒ Boolean
Constant-time comparison; false for a blank candidate or when no expected token is available.
-
.value ⇒ Object
The expected token: an explicit ENV override wins; otherwise a generated file (created on first read).
Class Method Details
.clear! ⇒ Object
Invalidate the generated token once setup has completed (the ENV override, if any, is untouched).
42 43 44 45 46 |
# File 'lib/camaleon_cms/setup_token.rb', line 42 def clear! File.delete(file_path) if File.exist?(file_path) rescue StandardError nil end |
.file_path ⇒ Object
48 49 50 |
# File 'lib/camaleon_cms/setup_token.rb', line 48 def file_path Rails.root.join('tmp/camaleon_setup_token').to_s end |
.read_or_generate_file ⇒ Object
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
# File 'lib/camaleon_cms/setup_token.rb', line 52 def read_or_generate_file return File.read(file_path).strip if File.exist?(file_path) token = SecureRandom.hex(32) FileUtils.mkdir_p(File.dirname(file_path)) # 0600 from the first byte (write-then-chmod would leave a world-readable window at default # umask), EXCL so a concurrent generator cannot clobber a token already handed to an operator. File.open(file_path, File::WRONLY | File::CREAT | File::EXCL, 0o600) { |f| f.write(token) } Rails.logger.info("Camaleon CMS: setup token written to #{file_path} — required to run the installer.") token rescue Errno::EEXIST # Lost the creation race between the exist? check and the open: adopt the winner's token. read_or_generate_file rescue StandardError nil end |
.required? ⇒ Boolean
The installer is gated only until the first site exists; afterwards it is closed anyway.
17 18 19 20 21 |
# File 'lib/camaleon_cms/setup_token.rb', line 17 def required? CamaleonCms::Site.count == 0 rescue StandardError false end |
.valid?(candidate) ⇒ Boolean
Constant-time comparison; false for a blank candidate or when no expected token is available.
33 34 35 36 37 38 39 |
# File 'lib/camaleon_cms/setup_token.rb', line 33 def valid?(candidate) candidate = candidate.to_s expected = value.to_s return false if candidate.empty? || expected.empty? ActiveSupport::SecurityUtils.secure_compare(candidate, expected) end |
.value ⇒ Object
The expected token: an explicit ENV override wins; otherwise a generated file (created on first read). Returns nil only if no ENV value is set and the file cannot be created.
25 26 27 28 29 30 |
# File 'lib/camaleon_cms/setup_token.rb', line 25 def value env = ENV[ENV_KEY].to_s return env if env.present? read_or_generate_file end |