├── log
└── .keep
├── storage
└── .keep
├── tmp
└── .keep
├── vendor
└── .keep
├── lib
├── assets
│ └── .keep
└── tasks
│ └── .keep
├── public
├── favicon.ico
├── apple-touch-icon.png
├── apple-touch-icon-precomposed.png
├── robots.txt
├── 500.html
├── 404.html
└── 422.html
├── test
├── helpers
│ └── .keep
├── mailers
│ └── .keep
├── models
│ ├── .keep
│ └── secret_test.rb
├── system
│ ├── .keep
│ └── secrets_test.rb
├── controllers
│ ├── .keep
│ └── secrets_controller_test.rb
├── fixtures
│ ├── .keep
│ ├── files
│ │ └── .keep
│ └── secrets.yml
├── integration
│ └── .keep
├── application_system_test_case.rb
└── test_helper.rb
├── app
├── assets
│ ├── images
│ │ └── .keep
│ ├── javascripts
│ │ ├── channels
│ │ │ └── .keep
│ │ ├── secrets.coffee
│ │ ├── cable.js
│ │ └── application.js
│ ├── config
│ │ └── manifest.js
│ └── stylesheets
│ │ ├── secrets.scss
│ │ ├── application.scss
│ │ └── scaffolds.scss
├── models
│ ├── concerns
│ │ └── .keep
│ ├── application_record.rb
│ └── secret.rb
├── controllers
│ ├── concerns
│ │ └── .keep
│ ├── application_controller.rb
│ └── secrets_controller.rb
├── views
│ ├── layouts
│ │ ├── mailer.text.erb
│ │ ├── mailer.html.erb
│ │ ├── application.html.erb
│ │ └── _header.html.erb
│ └── secrets
│ │ ├── show.html.erb
│ │ ├── show.json.jbuilder
│ │ ├── index.json.jbuilder
│ │ ├── _secret.json.jbuilder
│ │ ├── new.html.erb
│ │ ├── unlocked.html.erb
│ │ ├── preview.html.erb
│ │ ├── index.html.erb
│ │ ├── _form.html.erb
│ │ └── _unlock.html.erb
├── helpers
│ ├── secrets_helper.rb
│ └── application_helper.rb
├── jobs
│ └── application_job.rb
├── channels
│ └── application_cable
│ │ ├── channel.rb
│ │ └── connection.rb
└── mailers
│ └── application_mailer.rb
├── .tool-versions
├── .dockerignore
├── Procfile
├── package.json
├── bin
├── rake
├── bundle
├── rails
├── yarn
├── update
└── setup
├── .env
├── config
├── spring.rb
├── environment.rb
├── initializers
│ ├── mime_types.rb
│ ├── prometheus.rb
│ ├── filter_parameter_logging.rb
│ ├── application_controller_renderer.rb
│ ├── cookies_serializer.rb
│ ├── rack_attack.rb
│ ├── backtrace_silencers.rb
│ ├── scheduler.rb
│ ├── wrap_parameters.rb
│ ├── assets.rb
│ ├── inflections.rb
│ └── content_security_policy.rb
├── boot.rb
├── routes.rb
├── cable.yml
├── credentials.yml.enc
├── database.yml
├── locales
│ └── en.yml
├── storage.yml
├── application.rb
├── puma.rb
└── environments
│ ├── test.rb
│ ├── development.rb
│ └── production.rb
├── db
├── migrate
│ ├── 20190523033055_remove_title.rb
│ └── 20190220073346_create_secrets.rb
├── seeds.rb
└── schema.rb
├── entrypoint.sh
├── Rakefile
├── docker-compose.yml
├── show.html.erb
├── config.ru
├── Dockerfile
├── .gitignore
├── LICENSE
├── README.md
├── Gemfile
├── .github
└── workflows
│ └── docker-image.yml
└── Gemfile.lock
/log/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/storage/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tmp/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/vendor/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/assets/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/tasks/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/helpers/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/mailers/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/models/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/system/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/assets/images/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/controllers/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/integration/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/models/concerns/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/files/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.tool-versions:
--------------------------------------------------------------------------------
1 | ruby 3.3.0
2 |
--------------------------------------------------------------------------------
/app/controllers/concerns/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | .docker
2 | .env*
3 |
--------------------------------------------------------------------------------
/app/assets/javascripts/channels/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/apple-touch-icon-precomposed.png:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/views/layouts/mailer.text.erb:
--------------------------------------------------------------------------------
1 | <%= yield %>
2 |
--------------------------------------------------------------------------------
/app/helpers/secrets_helper.rb:
--------------------------------------------------------------------------------
1 | module SecretsHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/helpers/application_helper.rb:
--------------------------------------------------------------------------------
1 | module ApplicationHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/jobs/application_job.rb:
--------------------------------------------------------------------------------
1 | class ApplicationJob < ActiveJob::Base
2 | end
3 |
--------------------------------------------------------------------------------
/app/views/secrets/show.html.erb:
--------------------------------------------------------------------------------
1 | <%= render 'unlock', secret: @secret %>
2 |
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: bundle exec puma -t 5:5 -p ${PORT:-3000} -e ${RACK_ENV:-production}
2 |
--------------------------------------------------------------------------------
/app/views/secrets/show.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.partial! "secrets/secret", secret: @secret
2 |
--------------------------------------------------------------------------------
/app/views/secrets/index.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.array! @secrets, partial: 'secrets/secret', as: :secret
2 |
--------------------------------------------------------------------------------
/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | class ApplicationController < ActionController::Base
2 | end
3 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "burn-after-clicking",
3 | "private": true,
4 | "dependencies": {}
5 | }
6 |
--------------------------------------------------------------------------------
/bin/rake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require_relative '../config/boot'
3 | require 'rake'
4 | Rake.application.run
5 |
--------------------------------------------------------------------------------
/app/models/application_record.rb:
--------------------------------------------------------------------------------
1 | class ApplicationRecord < ActiveRecord::Base
2 | self.abstract_class = true
3 | end
4 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
2 |
--------------------------------------------------------------------------------
/app/channels/application_cable/channel.rb:
--------------------------------------------------------------------------------
1 | module ApplicationCable
2 | class Channel < ActionCable::Channel::Base
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
1 | SECRET_BODY_CRYPT_KEY='nMkmmjZigFZQ6tyixzJ8JZfiLdTr2kVYZbykdWED'
2 | METRICS_PASS='metrics:foobar'
3 | HEALTH_PASS='health:bazbat'
4 |
--------------------------------------------------------------------------------
/app/assets/config/manifest.js:
--------------------------------------------------------------------------------
1 | //= link_tree ../images
2 | //= link_directory ../javascripts .js
3 | //= link_directory ../stylesheets .css
4 |
--------------------------------------------------------------------------------
/app/channels/application_cable/connection.rb:
--------------------------------------------------------------------------------
1 | module ApplicationCable
2 | class Connection < ActionCable::Connection::Base
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/bin/bundle:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
3 | load Gem.bin_path('bundler', 'bundle')
4 |
--------------------------------------------------------------------------------
/app/mailers/application_mailer.rb:
--------------------------------------------------------------------------------
1 | class ApplicationMailer < ActionMailer::Base
2 | default from: 'from@example.com'
3 | layout 'mailer'
4 | end
5 |
--------------------------------------------------------------------------------
/config/spring.rb:
--------------------------------------------------------------------------------
1 | %w[
2 | .ruby-version
3 | .rbenv-vars
4 | tmp/restart.txt
5 | tmp/caching-dev.txt
6 | ].each { |path| Spring.watch(path) }
7 |
--------------------------------------------------------------------------------
/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | APP_PATH = File.expand_path('../config/application', __dir__)
3 | require_relative '../config/boot'
4 | require 'rails/commands'
5 |
--------------------------------------------------------------------------------
/db/migrate/20190523033055_remove_title.rb:
--------------------------------------------------------------------------------
1 | class RemoveTitle < ActiveRecord::Migration[5.2]
2 | def change
3 | remove_column :secrets, :title
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/app/views/secrets/_secret.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.extract! secret, :id, :body, :password, :expiration, :created_at, :updated_at
2 | json.url secret_url(secret, format: :json)
3 |
--------------------------------------------------------------------------------
/config/environment.rb:
--------------------------------------------------------------------------------
1 | # Load the Rails application.
2 | require_relative 'application'
3 |
4 | # Initialize the Rails application.
5 | Rails.application.initialize!
6 |
--------------------------------------------------------------------------------
/test/models/secret_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class SecretTest < ActiveSupport::TestCase
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/config/initializers/mime_types.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new mime types for use in respond_to blocks:
4 | # Mime::Type.register "text/richtext", :rtf
5 |
--------------------------------------------------------------------------------
/test/application_system_test_case.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
4 | driven_by :selenium, using: :chrome, screen_size: [1400, 1400]
5 | end
6 |
--------------------------------------------------------------------------------
/app/views/secrets/new.html.erb:
--------------------------------------------------------------------------------
1 |
8 |
500
9 |
10 |
Something went wrong.
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
404
9 |
10 |
The page you requested was not found.
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/public/422.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
422
9 |
10 |
The change you have requested was rejected.
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/config/initializers/wrap_parameters.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # This file contains settings for ActionController::ParamsWrapper which
4 | # is enabled by default.
5 |
6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
7 | ActiveSupport.on_load(:action_controller) do
8 | wrap_parameters format: [:json]
9 | end
10 |
11 | # To enable root element in JSON for ActiveRecord objects.
12 | # ActiveSupport.on_load(:active_record) do
13 | # self.include_root_in_json = true
14 | # end
15 |
--------------------------------------------------------------------------------
/show.html.erb:
--------------------------------------------------------------------------------
1 | <%= bootstrap_form_with(model: secret, local: true) do |form| %>
2 | <% if secret.errors.any? %>
3 |
2 |
5 |
6 |
Important
7 |
Every secret will be encrypted before being saved.
8 |
If you supply a password, that password will be used to encrypt the secret.
9 |
This password must be supplied to decrypt and unlock the saved secret.
10 |
A secret can only be viewed once. Once viewed it is deleted.
11 | <%= link_to 'New Secret', new_secret_path, class: "btn btn-primary" %>
12 |
13 |
14 |
--------------------------------------------------------------------------------
/config/initializers/assets.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Version of your assets, change this if you want to expire all your assets.
4 | Rails.application.config.assets.version = '1.0'
5 |
6 | # Add additional assets to the asset load path.
7 | # Rails.application.config.assets.paths << Emoji.images_path
8 | # Add Yarn node_modules folder to the asset load path.
9 | Rails.application.config.assets.paths << Rails.root.join('node_modules')
10 |
11 | # Precompile additional assets.
12 | # application.js, application.css, and all non-JS/CSS in the app/assets
13 | # folder are already added.
14 | # Rails.application.config.assets.precompile += %w( admin.js admin.css )
15 |
--------------------------------------------------------------------------------
/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new inflection rules using the following format. Inflections
4 | # are locale specific, and you may define rules for as many different
5 | # locales as you wish. All of these examples are active by default:
6 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
7 | # inflect.plural /^(ox)$/i, '\1en'
8 | # inflect.singular /^(ox)en/i, '\1'
9 | # inflect.irregular 'person', 'people'
10 | # inflect.uncountable %w( fish sheep )
11 | # end
12 |
13 | # These inflection rules are supported but not enabled by default:
14 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
15 | # inflect.acronym 'RESTful'
16 | # end
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files.
2 | #
3 | # If you find yourself ignoring temporary files generated by your text editor
4 | # or operating system, you probably want to add a global ignore instead:
5 | # git config --global core.excludesfile '~/.gitignore_global'
6 |
7 | # Ignore bundler config.
8 | /.bundle
9 |
10 | # Ignore all logfiles and tempfiles.
11 | /log/*
12 | /tmp/*
13 | !/log/.keep
14 | !/tmp/.keep
15 |
16 | # Ignore uploaded files in development
17 | /storage/*
18 | !/storage/.keep
19 |
20 | /node_modules
21 | /yarn-error.log
22 |
23 | /public/assets
24 | .byebug_history
25 |
26 | # Ignore master key for decrypting credentials and more.
27 | /config/master.key
28 |
29 | *.swp
30 |
--------------------------------------------------------------------------------
/app/views/layouts/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
14 |
15 | <%= render 'layouts/header' %>
16 |
17 |
18 | <% flash.each do |name, msg| %>
19 | <%= content_tag(:div, msg, class: "alert alert-info") %>
20 | <% end %>
21 |
22 | <%= yield %>
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/application.scss:
--------------------------------------------------------------------------------
1 | /*
2 | * This is a manifest file that'll be compiled into application.css, which will include all the files
3 | * listed below.
4 | *
5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's
6 | * vendor/assets/stylesheets directory can be referenced here using a relative path.
7 | *
8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9 | * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10 | * files in this directory. Styles in this file should be added after the last require_* statement.
11 | * It is generally better to create a new file per style scope.
12 | *
13 | */
14 | @import "bootstrap";
15 | @import "rails_bootstrap_forms";
16 |
--------------------------------------------------------------------------------
/app/views/secrets/_form.html.erb:
--------------------------------------------------------------------------------
1 | <%= bootstrap_form_with(model: secret, local: true) do |form| %>
2 | <% if secret.errors.any? %>
3 |
4 |
<%= pluralize(secret.errors.count, "error") %> prohibited this secret from being saved:
5 |
6 |
7 | <% secret.errors.full_messages.each do |message| %>
8 | <%= message %>
9 | <% end %>
10 |
11 |
12 | <% end %>
13 |
14 | <%= form.text_area :body, max: 20000, size: "82x20" %>
15 | <%= form.text_field :password, options = {min: 14, max: 255} %>
16 | <%= form.number_field :expiration, options = {placeholder: "in hours...", min: 1, max: 72} %>
17 |
18 |
19 | <%= form.submit %>
20 | <%= link_to 'Back', secrets_path, class: "btn btn-primary" %>
21 |
22 | <% end %>
23 |
--------------------------------------------------------------------------------
/app/assets/javascripts/application.js:
--------------------------------------------------------------------------------
1 | // This is a manifest file that'll be compiled into application.js, which will include all the files
2 | // listed below.
3 | //
4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, or any plugin's
5 | // vendor/assets/javascripts directory can be referenced here using a relative path.
6 | //
7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8 | // compiled file. JavaScript code in this file should be added after the last require_* statement.
9 | //
10 | // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
11 | // about supported directives.
12 | //
13 | //= require rails-ujs
14 | //= require activestorage
15 | //= require turbolinks
16 | //= require jquery3
17 | //= require popper
18 | //= require bootstrap-sprockets
19 | //= require_tree .
20 |
--------------------------------------------------------------------------------
/bin/update:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require 'fileutils'
3 | include FileUtils
4 |
5 | # path to your application root.
6 | APP_ROOT = File.expand_path('..', __dir__)
7 |
8 | def system!(*args)
9 | system(*args) || abort("\n== Command #{args} failed ==")
10 | end
11 |
12 | chdir APP_ROOT do
13 | # This script is a way to update your development environment automatically.
14 | # Add necessary update steps to this file.
15 |
16 | puts '== Installing dependencies =='
17 | system! 'gem install bundler --conservative'
18 | system('bundle check') || system!('bundle install')
19 |
20 | # Install JavaScript dependencies if using Yarn
21 | # system('bin/yarn')
22 |
23 | puts "\n== Updating database =="
24 | system! 'bin/rails db:migrate'
25 |
26 | puts "\n== Removing old logs and tempfiles =="
27 | system! 'bin/rails log:clear tmp:clear'
28 |
29 | puts "\n== Restarting application server =="
30 | system! 'bin/rails restart'
31 | end
32 |
--------------------------------------------------------------------------------
/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | # Files in the config/locales directory are used for internationalization
2 | # and are automatically loaded by Rails. If you want to use locales other
3 | # than English, add the necessary files in this directory.
4 | #
5 | # To use the locales, use `I18n.t`:
6 | #
7 | # I18n.t 'hello'
8 | #
9 | # In views, this is aliased to just `t`:
10 | #
11 | # <%= t('hello') %>
12 | #
13 | # To use a different locale, set it with `I18n.locale`:
14 | #
15 | # I18n.locale = :es
16 | #
17 | # This would use the information in config/locales/es.yml.
18 | #
19 | # The following keys must be escaped otherwise they will not be retrieved by
20 | # the default I18n backend:
21 | #
22 | # true, false, on, off, yes, no
23 | #
24 | # Instead, surround them with single quotes.
25 | #
26 | # en:
27 | # 'true': 'foo'
28 | #
29 | # To learn more, please read the Rails Internationalization guide
30 | # available at http://guides.rubyonrails.org/i18n.html.
31 |
32 | en:
33 | hello: "Hello world"
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2019 Patrick O'Brien
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8 |
--------------------------------------------------------------------------------
/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require 'fileutils'
3 | include FileUtils
4 |
5 | # path to your application root.
6 | APP_ROOT = File.expand_path('..', __dir__)
7 |
8 | def system!(*args)
9 | system(*args) || abort("\n== Command #{args} failed ==")
10 | end
11 |
12 | chdir APP_ROOT do
13 | # This script is a starting point to setup your application.
14 | # Add necessary setup steps to this file.
15 |
16 | puts '== Installing dependencies =='
17 | system! 'gem install bundler --conservative'
18 | system('bundle check') || system!('bundle install')
19 |
20 | # Install JavaScript dependencies if using Yarn
21 | # system('bin/yarn')
22 |
23 | # puts "\n== Copying sample files =="
24 | # unless File.exist?('config/database.yml')
25 | # cp 'config/database.yml.sample', 'config/database.yml'
26 | # end
27 |
28 | puts "\n== Preparing database =="
29 | system! 'bin/rails db:setup'
30 |
31 | puts "\n== Removing old logs and tempfiles =="
32 | system! 'bin/rails log:clear tmp:clear'
33 |
34 | puts "\n== Restarting application server =="
35 | system! 'bin/rails restart'
36 | end
37 |
--------------------------------------------------------------------------------
/app/models/secret.rb:
--------------------------------------------------------------------------------
1 | require 'bcrypt'
2 |
3 | class Secret < ApplicationRecord
4 | attr_accessor :body
5 |
6 | validates :body, presence: true, length: { maximum: 20000, too_long: "%{count} characters is the maximum allowed" }
7 | validates :password, presence: true, length: {
8 | maximum: 255, too_long: "%{count} characters is the maximum allowed",
9 | minimum: 14, too_short: "%{count} characters is the minimum allowed"
10 | }
11 | validates :expiration, presence: true
12 |
13 |
14 | before_save :encrypt_body
15 |
16 | # hash the password before we save it. we will later compare the hashes to
17 | # make sure the passwords match.
18 | before_save do |secret|
19 | unless self.password.empty?
20 | secret.password = BCrypt::Password.create(self.password, cost: 19)
21 | end
22 | end
23 |
24 | private
25 |
26 | def encrypt_body
27 | salt = SecureRandom.hex(32)
28 | pass = self.password
29 | key = ActiveSupport::KeyGenerator.new(pass).generate_key(salt, 32)
30 | crypt = ActiveSupport::MessageEncryptor.new(key)
31 | data = crypt.encrypt_and_sign(self.body)
32 |
33 | self.encrypted_body_salt = salt
34 | self.encrypted_body = data
35 | end
36 | end
37 |
--------------------------------------------------------------------------------
/config/initializers/content_security_policy.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Define an application-wide content security policy
4 | # For further information see the following documentation
5 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
6 |
7 | # Rails.application.config.content_security_policy do |policy|
8 | # policy.default_src :self, :https
9 | # policy.font_src :self, :https, :data
10 | # policy.img_src :self, :https, :data
11 | # policy.object_src :none
12 | # policy.script_src :self, :https
13 | # policy.style_src :self, :https
14 |
15 | # # Specify URI for violation reports
16 | # # policy.report_uri "/csp-violation-report-endpoint"
17 | # end
18 |
19 | # If you are using UJS then enable automatic nonce generation
20 | # Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) }
21 |
22 | # Report CSP violations to a specified URI
23 | # For further information see the following documentation:
24 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only
25 | # Rails.application.config.content_security_policy_report_only = true
26 |
--------------------------------------------------------------------------------
/config/storage.yml:
--------------------------------------------------------------------------------
1 | test:
2 | service: Disk
3 | root: <%= Rails.root.join("tmp/storage") %>
4 |
5 | local:
6 | service: Disk
7 | root: <%= Rails.root.join("storage") %>
8 |
9 | # Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)
10 | # amazon:
11 | # service: S3
12 | # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
13 | # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
14 | # region: us-east-1
15 | # bucket: your_own_bucket
16 |
17 | # Remember not to checkin your GCS keyfile to a repository
18 | # google:
19 | # service: GCS
20 | # project: your_project
21 | # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %>
22 | # bucket: your_own_bucket
23 |
24 | # Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key)
25 | # microsoft:
26 | # service: AzureStorage
27 | # storage_account_name: your_account_name
28 | # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %>
29 | # container: your_container_name
30 |
31 | # mirror:
32 | # service: Mirror
33 | # primary: local
34 | # mirrors: [ amazon, google, microsoft ]
35 |
--------------------------------------------------------------------------------
/db/schema.rb:
--------------------------------------------------------------------------------
1 | # This file is auto-generated from the current state of the database. Instead
2 | # of editing this file, please use the migrations feature of Active Record to
3 | # incrementally modify your database, and then regenerate this schema definition.
4 | #
5 | # This file is the source Rails uses to define your schema when running `bin/rails
6 | # db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
7 | # be faster and is potentially less error prone than running all of your
8 | # migrations from scratch. Old migrations may fail to apply correctly if those
9 | # migrations use external dependencies or application code.
10 | #
11 | # It's strongly recommended that you check this file into your version control system.
12 |
13 | ActiveRecord::Schema[7.1].define(version: 2019_05_23_033055) do
14 | # These are extensions that must be enabled in order to support this database
15 | enable_extension "pgcrypto"
16 | enable_extension "plpgsql"
17 |
18 | create_table "secrets", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
19 | t.string "password"
20 | t.text "encrypted_body"
21 | t.text "encrypted_body_salt"
22 | t.datetime "expiration", precision: nil
23 | t.datetime "created_at", precision: nil, null: false
24 | t.datetime "updated_at", precision: nil, null: false
25 | end
26 |
27 | end
28 |
--------------------------------------------------------------------------------
/config/application.rb:
--------------------------------------------------------------------------------
1 | require_relative 'boot'
2 |
3 | require 'rails/all'
4 |
5 | # Require the gems listed in Gemfile, including any gems
6 | # you've limited to :test, :development, or :production.
7 | Bundler.require(*Rails.groups)
8 |
9 | if ['development', 'test'].include?(ENV['RAILS_ENV'])
10 | # Load up Dotenv as soon as possible
11 | Dotenv::Railtie.load
12 | end
13 |
14 | module BurnAfterClicking
15 | class Application < Rails::Application
16 | # Initialize configuration defaults for originally generated Rails version.
17 | config.load_defaults 5.2
18 |
19 | config.action_controller.forgery_protection_origin_check = false
20 |
21 | # Settings in config/environments/* take precedence over those specified here.
22 | # Application configuration can go into files in config/initializers
23 | # -- all .rb files in that directory are automatically loaded after loading
24 | # the framework and any gems in your application.
25 |
26 | # enable longer uuids for the primary key (postgres only)
27 | config.generators do |g|
28 | g.orm :active_record, primary_key_type: :uuid
29 | end
30 |
31 | # filter out the password and body from being logged
32 | config.filter_parameters += [
33 | :password,
34 | :body
35 | ]
36 |
37 | # Rack attack to slow down clients
38 | config.middleware.use Rack::Attack
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/test/system/secrets_test.rb:
--------------------------------------------------------------------------------
1 | require "application_system_test_case"
2 |
3 | class SecretsTest < ApplicationSystemTestCase
4 | setup do
5 | @secret = secrets(:one)
6 | end
7 |
8 | test "visiting the index" do
9 | visit secrets_url
10 | assert_selector "h1", text: "Secrets"
11 | end
12 |
13 | test "creating a Secret" do
14 | visit secrets_url
15 | click_on "New Secret"
16 |
17 | fill_in "Encrypted body", with: @secret.encrypted_body
18 | fill_in "Encrypted body iv", with: @secret.encrypted_body_iv
19 | fill_in "Expiration", with: @secret.expiration
20 | fill_in "Password", with: @secret.password
21 | click_on "Create Secret"
22 |
23 | assert_text "Secret was successfully created"
24 | click_on "Back"
25 | end
26 |
27 | test "updating a Secret" do
28 | visit secrets_url
29 | click_on "Edit", match: :first
30 |
31 | fill_in "Encrypted body", with: @secret.encrypted_body
32 | fill_in "Encrypted body iv", with: @secret.encrypted_body_iv
33 | fill_in "Expiration", with: @secret.expiration
34 | fill_in "Password", with: @secret.password
35 | click_on "Update Secret"
36 |
37 | assert_text "Secret was successfully updated"
38 | click_on "Back"
39 | end
40 |
41 | test "destroying a Secret" do
42 | visit secrets_url
43 | page.accept_confirm do
44 | click_on "Destroy", match: :first
45 | end
46 |
47 | assert_text "Secret was successfully destroyed"
48 | end
49 | end
50 |
--------------------------------------------------------------------------------
/app/views/layouts/_header.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%= ENV['CUSTOM_TITLE'] || "BurnAfterClicking" %>
9 |
10 |
11 |
12 |
13 |
19 |
20 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/scaffolds.scss:
--------------------------------------------------------------------------------
1 | body {
2 | background-color: #fff;
3 | color: #333;
4 | margin: 33px;
5 | font-family: verdana, arial, helvetica, sans-serif;
6 | font-size: 13px;
7 | line-height: 18px;
8 | }
9 |
10 | p, ol, ul, td {
11 | font-family: verdana, arial, helvetica, sans-serif;
12 | font-size: 13px;
13 | line-height: 18px;
14 | }
15 |
16 | pre {
17 | background-color: #eee;
18 | padding: 10px;
19 | font-size: 11px;
20 | }
21 |
22 | a {
23 | color: #000;
24 |
25 | &:visited {
26 | color: #666;
27 | }
28 |
29 | &:hover {
30 | color: #fff;
31 | background-color: #000;
32 | }
33 | }
34 |
35 | th {
36 | padding-bottom: 5px;
37 | }
38 |
39 | td {
40 | padding: 0 5px 7px;
41 | }
42 |
43 | div {
44 | &.field, &.actions {
45 | margin-bottom: 10px;
46 | }
47 | }
48 |
49 | #notice {
50 | color: green;
51 | }
52 |
53 | .field_with_errors {
54 | padding: 2px;
55 | background-color: red;
56 | display: table;
57 | }
58 |
59 | #error_explanation {
60 | width: 450px;
61 | border: 2px solid red;
62 | padding: 7px 7px 0;
63 | margin-bottom: 20px;
64 | background-color: #f0f0f0;
65 |
66 | h2 {
67 | text-align: left;
68 | font-weight: bold;
69 | padding: 5px 5px 5px 15px;
70 | font-size: 12px;
71 | margin: -7px -7px 0;
72 | background-color: #c00;
73 | color: #fff;
74 | }
75 |
76 | ul li {
77 | font-size: 12px;
78 | list-style: square;
79 | }
80 | }
81 |
82 | label {
83 | display: block;
84 | }
85 |
--------------------------------------------------------------------------------
/test/controllers/secrets_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class SecretsControllerTest < ActionDispatch::IntegrationTest
4 | setup do
5 | @secret = secrets(:one)
6 | end
7 |
8 | test "should get index" do
9 | get secrets_url
10 | assert_response :success
11 | end
12 |
13 | test "should get new" do
14 | get new_secret_url
15 | assert_response :success
16 | end
17 |
18 | test "should create secret" do
19 | assert_difference('Secret.count') do
20 | post secrets_url, params: { secret: { encrypted_body: @secret.encrypted_body, encrypted_body_iv: @secret.encrypted_body_iv, expiration: @secret.expiration, password: @secret.password } }
21 | end
22 |
23 | assert_redirected_to secret_url(Secret.last)
24 | end
25 |
26 | test "should show secret" do
27 | get secret_url(@secret)
28 | assert_response :success
29 | end
30 |
31 | test "should get edit" do
32 | get edit_secret_url(@secret)
33 | assert_response :success
34 | end
35 |
36 | test "should update secret" do
37 | patch secret_url(@secret), params: { secret: { encrypted_body: @secret.encrypted_body, encrypted_body_iv: @secret.encrypted_body_iv, expiration: @secret.expiration, password: @secret.password } }
38 | assert_redirected_to secret_url(@secret)
39 | end
40 |
41 | test "should destroy secret" do
42 | assert_difference('Secret.count', -1) do
43 | delete secret_url(@secret)
44 | end
45 |
46 | assert_redirected_to secrets_url
47 | end
48 | end
49 |
--------------------------------------------------------------------------------
/app/views/secrets/_unlock.html.erb:
--------------------------------------------------------------------------------
1 | <%= bootstrap_form_with(model: secret, local: true) do |form| %>
2 | <% if secret.errors.any? %>
3 |
4 |
Error:
5 |
6 |
7 | <% secret.errors.full_messages.each do |message| %>
8 | <%= message %>
9 | <% end %>
10 |
11 |
12 | <% end %>
13 |
14 | <% if @secret[:password].empty? %>
15 |
16 |
19 |
20 |
Are you sure?
21 | <%= hidden_field_tag 'no_password' %>
22 |
Clicking the "Show Secret" button will reveal and destroy the secret.
23 | <%= form.submit "Show Secret" %>
24 | <%= link_to 'Back', secrets_path, class: "btn btn-primary" %>
25 |
26 |
27 | <% else %>
28 |
29 |
32 |
33 | <%= form.text_field :unlock_password, label: "This Secret is Locked. Please Enter the Password." %>
34 |
Clicking the "Unlock Secret" button will reveal and destroy the secret.
35 | <%= form.submit "Unlock Secret" %>
36 | <%= link_to 'Back', secrets_path, class: "btn btn-primary" %>
37 |
38 |
39 |
40 | <% end %>
41 |
42 | <% end %>
43 |
--------------------------------------------------------------------------------
/config/puma.rb:
--------------------------------------------------------------------------------
1 | # Puma can serve each request in a thread from an internal thread pool.
2 | # The `threads` method setting takes two numbers: a minimum and maximum.
3 | # Any libraries that use thread pools should be configured to match
4 | # the maximum value specified for Puma. Default is set to 5 threads for minimum
5 | # and maximum; this matches the default thread size of Active Record.
6 | #
7 | threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
8 | threads threads_count, threads_count
9 |
10 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000.
11 | #
12 | port ENV.fetch("PORT") { 3000 }
13 |
14 | # Specifies the `environment` that Puma will run in.
15 | #
16 | environment ENV.fetch("RAILS_ENV") { "development" }
17 |
18 | # Specifies the number of `workers` to boot in clustered mode.
19 | # Workers are forked webserver processes. If using threads and workers together
20 | # the concurrency of the application would be max `threads` * `workers`.
21 | # Workers do not work on JRuby or Windows (both of which do not support
22 | # processes).
23 | #
24 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 }
25 |
26 | # Use the `preload_app!` method when specifying a `workers` number.
27 | # This directive tells Puma to first boot the application and load code
28 | # before forking the application. This takes advantage of Copy On Write
29 | # process behavior so workers use less memory.
30 | #
31 | # preload_app!
32 |
33 | # Allow puma to be restarted by `rails restart` command.
34 | plugin :tmp_restart
35 |
--------------------------------------------------------------------------------
/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # The test environment is used exclusively to run your application's
5 | # test suite. You never need to work with it otherwise. Remember that
6 | # your test database is "scratch space" for the test suite and is wiped
7 | # and recreated between test runs. Don't rely on the data there!
8 | config.cache_classes = true
9 |
10 | # Do not eager load code on boot. This avoids loading your whole application
11 | # just for the purpose of running a single test. If you are using a tool that
12 | # preloads Rails for running tests, you may have to set it to true.
13 | config.eager_load = false
14 |
15 | # Configure public file server for tests with Cache-Control for performance.
16 | config.public_file_server.enabled = true
17 | config.public_file_server.headers = {
18 | 'Cache-Control' => "public, max-age=#{1.hour.to_i}"
19 | }
20 |
21 | # Show full error reports and disable caching.
22 | config.consider_all_requests_local = true
23 | config.action_controller.perform_caching = false
24 |
25 | # Raise exceptions instead of rendering exception templates.
26 | config.action_dispatch.show_exceptions = false
27 |
28 | # Disable request forgery protection in test environment.
29 | config.action_controller.allow_forgery_protection = false
30 |
31 | # Store uploaded files on the local file system in a temporary directory
32 | config.active_storage.service = :test
33 |
34 | config.action_mailer.perform_caching = false
35 |
36 | # Tell Action Mailer not to deliver emails to the real world.
37 | # The :test delivery method accumulates sent emails in the
38 | # ActionMailer::Base.deliveries array.
39 | config.action_mailer.delivery_method = :test
40 |
41 | # Print deprecation notices to the stderr.
42 | config.active_support.deprecation = :stderr
43 |
44 | # Raises error for missing translations
45 | # config.action_view.raise_on_missing_translations = true
46 | end
47 |
--------------------------------------------------------------------------------
/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | require 'socket'
2 | require 'ipaddr'
3 |
4 | Rails.application.configure do
5 | # Settings specified here will take precedence over those in config/application.rb.
6 |
7 | # In the development environment your application's code is reloaded on
8 | # every request. This slows down response time but is perfect for development
9 | # since you don't have to restart the web server when you make code changes.
10 | config.cache_classes = false
11 |
12 | # Do not eager load code on boot.
13 | config.eager_load = false
14 |
15 | # Show full error reports.
16 | config.consider_all_requests_local = true
17 |
18 | # Enable/disable caching. By default caching is disabled.
19 | # Run rails dev:cache to toggle caching.
20 | if Rails.root.join('tmp', 'caching-dev.txt').exist?
21 | config.action_controller.perform_caching = true
22 |
23 | config.cache_store = :memory_store
24 | config.public_file_server.headers = {
25 | 'Cache-Control' => "public, max-age=#{2.days.to_i}"
26 | }
27 | else
28 | config.action_controller.perform_caching = false
29 |
30 | config.cache_store = :null_store
31 | end
32 |
33 | # Store uploaded files on the local file system (see config/storage.yml for options)
34 | config.active_storage.service = :local
35 |
36 | # Don't care if the mailer can't send.
37 | config.action_mailer.raise_delivery_errors = false
38 |
39 | config.action_mailer.perform_caching = false
40 |
41 | # Print deprecation notices to the Rails logger.
42 | config.active_support.deprecation = :log
43 |
44 | # Raise an error on page load if there are pending migrations.
45 | config.active_record.migration_error = :page_load
46 |
47 | # Highlight code that triggered database queries in logs.
48 | config.active_record.verbose_query_logs = true
49 |
50 | # Debug mode disables concatenation and preprocessing of assets.
51 | # This option may cause significant delays in view rendering with a large
52 | # number of complex assets.
53 | config.assets.debug = true
54 |
55 | # Suppress logger output for asset requests.
56 | config.assets.quiet = true
57 |
58 | # Raises error for missing translations
59 | # config.action_view.raise_on_missing_translations = true
60 |
61 | # Use an evented file watcher to asynchronously detect changes in source code,
62 | # routes, locales, etc. This feature depends on the listen gem.
63 | config.file_watcher = ActiveSupport::EventedFileUpdateChecker
64 |
65 | # if running in a docker container we want to view the console
66 | config.web_console.whitelisted_ips = Socket.ip_address_list.reduce([]) do |res, addrinfo|
67 | addrinfo.ipv4? ? res << IPAddr.new(addrinfo.ip_address).mask(24) : res
68 | end
69 | end
70 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Burn After Clicking
2 |
3 | Stop sending secrets through email.
4 |
5 | ## What is this?
6 |
7 | A tool that you can use to more securely relay information to friends, family, partners, clients, coworkers, and strangers. All secrets are encrypted before being saved to the DB, and you can optionally set a password to use for encrypting that must also be used to unlock the secret. Once the link has been viewed it disappears forever.
8 |
9 | [Check out the demo](https://burn-after-clicking.herokuapp.com/).
10 |
11 | If you are thinking about using this, please deploy it yourself so you can be more in control over your secrets.
12 |
13 | ## Running Locally
14 |
15 | ### Build with Docker
16 |
17 | ```
18 | docker-compose up --build
19 | ```
20 |
21 | ### Running Without Building
22 |
23 | ```
24 | docker-compose up
25 | ```
26 |
27 | ### Creating a Secret
28 |
29 | Browse to http://localhost:3000. There should be a button to Create a new Secret.
30 |
31 | ### Verifying the Secret is Encrypted in the DB
32 |
33 | ```
34 | docker-compose run web bin/rails dbconsole
35 | ```
36 |
37 | Once in the Postgres Console, run the following command:
38 |
39 | ```
40 | select * from secrets;
41 | ```
42 |
43 | You should get something similar returned:
44 |
45 | ```
46 | burn_development=# select * from secrets;
47 | id | password | encrypted_body | encrypted_body_salt | expiration | created_at | updated_at
48 | --------------------------------------+--------------------------------------------------------------+----------------------------------------------------------------------+------------------------------------------------------------------+----------------------------+----------------------------+----------------------------
49 | 6da6c231-d3cf-4387-a3e0-b2c2b3615e73 | $2a$10$9C.qLdU6nR2YyUFj9WUetO3wEj35WhNxLpXTJIVqdfgr/rNfBY5H2 | jziUARrV9B6c0rui71zjrvZr--g90F8A+BmkFVeLQD--sPNUpTDSm0hy9BM5ThXhJQ== | 83a72aeed9ad6ee66b895ea30fc3a557838bd580374f8c3508e95c9aec47ac32 | 2019-05-02 08:25:16.631233 | 2019-05-01 20:25:16.767563 | 2019-05-01 20:25:16.767563
50 | 27857ee7-d452-44cb-9cfc-fd69f3f701d1 | $2a$10$qikVctiH0hUXYJgkV1sLVe1pLFGlNO6W0t.uDMFCyiMiM5vDSQ6aG | drxa3uICqoZu2dh/X48=--0NllsY+BzZJ7MwZs--ayJF7KPD9w6w0xLSBIjaLQ== | f0c644c568353cddd0a1c04d3baa3e236ad0a4d43ddbc65a544b5388a379ef81 | 2019-05-01 21:52:04.1354 | 2019-05-01 20:52:04.269374 | 2019-05-01 20:52:04.269374
51 | ```
52 |
53 | ## Healthcheck and Metrics endpoints
54 |
55 | Define your healthcheck and metric endpoint passwords as environment variables. Generate the basic auth headers:
56 |
57 | ```
58 | # password is foobar, but the colon is required since we don't have a username.
59 | Base64.strict_encode64(":foobar")
60 | ```
61 |
62 | Test with curl:
63 |
64 | ```
65 | curl -D - -H 'Authorization:Basic OmZvb2Jhcg==' localhost:3000/metrics
66 | ```
67 |
68 | ## TODO
69 | * K8s templates
70 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 | git_source(:github) { |repo| "https://github.com/#{repo}.git" }
3 |
4 | ruby '3.3.0'
5 |
6 | # Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
7 | gem 'rails', '~> 7.2'
8 | # Use postgresql as the database for Active Record
9 | gem 'pg', '>= 0.18', '< 2.0'
10 | # Use Puma as the app server
11 | gem 'puma', '~> 4.3.12'
12 |
13 | # Use SCSS for stylesheets
14 | gem 'sassc-rails', '~> 2.1.1'
15 | # Use Uglifier as compressor for JavaScript assets
16 | gem 'uglifier', '>= 1.3.0'
17 | # See https://github.com/rails/execjs#readme for more supported runtimes
18 | # gem 'mini_racer', platforms: :ruby
19 |
20 | # Use CoffeeScript for .coffee assets and views
21 | gem 'coffee-rails', '~> 4.2'
22 | # Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks
23 | gem 'turbolinks', '~> 5'
24 | # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
25 | gem 'jbuilder', '~> 2.5'
26 | # Use Redis adapter to run Action Cable in production
27 | # gem 'redis', '~> 4.0'
28 |
29 | # Use ActiveModel has_secure_password
30 | gem 'bcrypt', '~> 3.1.7'
31 |
32 | # dotenv for dev and test
33 | gem 'dotenv-rails', groups: [:development, :test], require: 'dotenv/rails-now'
34 |
35 | # bootstrap is nice and easy
36 | gem 'bootstrap', '4.3.1'
37 | gem 'bootstrap_form', '>= 4.1.0'
38 | gem 'jquery-rails'
39 |
40 | # I'm taking the easy way out
41 | gem 'rufus-scheduler', '3.5.2'
42 |
43 | # sweet sweet prometheus
44 | gem 'prometheus_exporter', '0.4.16'
45 |
46 | # rack-attack to throttle requests
47 | gem 'rack-attack', '~> 6.0.0'
48 |
49 | # Nokogiri to at least 1.10.4 to address CVE-2019-5477
50 | gem 'nokogiri', '>= 1.10.4'
51 |
52 | # bump rack per CVE-2020-8184
53 | gem 'rack', '>= 2.2.3'
54 |
55 | # bump websocket-extensions per CVE-2020-7663
56 | gem 'websocket-extensions', '>= 0.1.5'
57 |
58 | # rubyzip to at least 1.3.0 to address
59 | gem 'rubyzip', '>= 1.3.0'
60 |
61 | # Use ActiveStorage variant
62 | # gem 'mini_magick', '~> 4.8'
63 |
64 | # Use Capistrano for deployment
65 | # gem 'capistrano-rails', group: :development
66 |
67 | # Reduces boot times through caching; required in config/boot.rb
68 | gem 'bootsnap', '>= 1.1.0', require: false
69 |
70 | group :development, :test do
71 | # Call 'byebug' anywhere in the code to stop execution and get a debugger console
72 | gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
73 | end
74 |
75 | group :development do
76 | # Access an interactive console on exception pages or by calling 'console' anywhere in the code.
77 | gem 'web-console', '>= 3.3.0'
78 | gem 'listen', '~> 3.5'
79 | # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
80 | gem 'spring'
81 | gem 'spring-watcher-listen', '~> 2.0.0'
82 | end
83 |
84 | group :test do
85 | # Adds support for Capybara system testing and selenium driver
86 | gem 'capybara', '>= 2.15'
87 | gem 'selenium-webdriver'
88 | # Easy installation and use of chromedriver to run system tests with Chrome
89 | gem 'chromedriver-helper'
90 | end
91 |
92 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem
93 | gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
94 |
--------------------------------------------------------------------------------
/.github/workflows/docker-image.yml:
--------------------------------------------------------------------------------
1 | #
2 | name: Create and publish a Docker image
3 |
4 | # Configures this workflow to run every time a change is pushed to the branch called `release`.
5 | on:
6 | push:
7 | branches: ['master']
8 |
9 | # Defines two custom environment variables for the workflow. These are used for the Container registry domain, and a name for the Docker image that this workflow builds.
10 | env:
11 | REGISTRY: ghcr.io
12 | IMAGE_NAME: ${{ github.repository }}
13 |
14 | # There is a single job in this workflow. It's configured to run on the latest available version of Ubuntu.
15 | jobs:
16 | build-and-push-image:
17 | runs-on: ubuntu-latest
18 | # Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job.
19 | permissions:
20 | contents: read
21 | packages: write
22 | attestations: write
23 | id-token: write
24 | #
25 | steps:
26 | - name: Checkout repository
27 | uses: actions/checkout@v4
28 | # Uses the `docker/login-action` action to log in to the Container registry registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here.
29 | - name: Log in to the Container registry
30 | uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
31 | with:
32 | registry: ${{ env.REGISTRY }}
33 | username: ${{ github.actor }}
34 | password: ${{ secrets.GITHUB_TOKEN }}
35 | # This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about) to extract tags and labels that will be applied to the specified image. The `id` "meta" allows the output of this step to be referenced in a subsequent step. The `images` value provides the base name for the tags and labels.
36 | - name: Extract metadata (tags, labels) for Docker
37 | id: meta
38 | uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
39 | with:
40 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
41 | # This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages.
42 | # It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)" in the README of the `docker/build-push-action` repository.
43 | # It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step.
44 | - name: Build and push Docker image
45 | id: push
46 | uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
47 | with:
48 | context: .
49 | push: true
50 | tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
51 | labels: ${{ steps.meta.outputs.labels }}
52 |
53 | # This step generates an artifact attestation for the image, which is an unforgeable statement about where and how it was built. It increases supply chain security for people who consume the image. For more information, see "[AUTOTITLE](/actions/security-guides/using-artifact-attestations-to-establish-provenance-for-builds)."
54 | - name: Generate artifact attestation
55 | uses: actions/attest-build-provenance@v1
56 | with:
57 | subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME}}
58 | subject-digest: ${{ steps.push.outputs.digest }}
59 | push-to-registry: true
60 |
61 |
--------------------------------------------------------------------------------
/app/controllers/secrets_controller.rb:
--------------------------------------------------------------------------------
1 | require 'bcrypt'
2 |
3 | class SecretsController < ApplicationController
4 | before_action :ensure_html_format
5 | before_action :set_secret, only: [:show, :update, :destroy]
6 | before_action :set_expiration, only: [:create]
7 |
8 | # GET /secrets
9 | # GET /secrets.json
10 | def index
11 | respond_to do |format|
12 | format.html
13 | end
14 | end
15 |
16 | # GET /secrets/1
17 | # GET /secrets/1.json
18 | def show
19 | end
20 |
21 | # GET /secrets/new
22 | def new
23 | @secret = Secret.new
24 | end
25 |
26 | # This is how we unlock the secret
27 | def update
28 | if verify_password
29 | @body = decrypt_body
30 | respond_to do |format|
31 | format.html { render :unlocked, status: :ok, location: @secret, notice: 'Secret has been unlocked and destroyed' }
32 | format.json { render :unlocked, status: :ok, location: @secret }
33 |
34 | # Destroy after rendering
35 | Thread.new { @secret.destroy }
36 | end
37 | else
38 | @secret.errors.add(:incorrect_password, " - The password you entered was incorrect. Please try again.")
39 | respond_to do |format|
40 | format.html { render :show, status: 401 }
41 | format.json { render json: @secret.errors, status: :unprocessable_entity }
42 | end
43 | end
44 | end
45 |
46 | # POST /secrets
47 | # POST /secrets.json
48 | def create
49 | @secret = Secret.new(secret_params)
50 |
51 | respond_to do |format|
52 | if @secret.save
53 | format.html { render :preview, status: :ok, location: @secret, notice: 'Secret was successfully created.' }
54 | format.json { render :preview, status: :ok, location: @secret }
55 | else
56 | format.html { render :new }
57 | format.json { render json: @secret.errors, status: :unprocessable_entity }
58 | end
59 | end
60 | end
61 |
62 | # DELETE /secrets/1
63 | # DELETE /secrets/1.json
64 | def destroy
65 | @secret.destroy
66 | respond_to do |format|
67 | format.html { redirect_to secrets_url, notice: 'Secret was successfully destroyed.' }
68 | format.json { head :no_content }
69 | end
70 | end
71 |
72 | private
73 | # Use callbacks to share common setup or constraints between actions.
74 | def set_secret
75 | @secret = Secret.find(params[:id])
76 | end
77 |
78 | # Never trust parameters from the scary internet, only allow the white list through.
79 | def secret_params
80 | params.require(:secret).permit(:body, :password, :expiration, :unlock_password)
81 | end
82 |
83 | def set_expiration
84 | expiry = params[:secret][:expiration].empty? ? 1 : params[:secret][:expiration].to_i
85 | params[:secret][:expiration] = Time.current + (expiry * 60 * 60 )
86 | end
87 |
88 | def verify_password
89 | if @secret[:password].empty?
90 | return true
91 | else
92 | if BCrypt::Password.new(@secret[:password]) == params[:secret][:unlock_password]
93 | return true
94 | else
95 | return false
96 | end
97 | end
98 | end
99 |
100 | def decrypt_body
101 | pass = params.key?(:no_password) ? ENV['SECRET_BODY_CRYPT_KEY'] : params[:secret][:unlock_password]
102 | key = ActiveSupport::KeyGenerator.new(pass).generate_key(@secret.encrypted_body_salt, 32)
103 | crypt = ActiveSupport::MessageEncryptor.new(key)
104 |
105 | return crypt.decrypt_and_verify(@secret.encrypted_body)
106 | end
107 |
108 | def ensure_html_format
109 | unless request.format.html?
110 | render plain: "Not Found", status: :not_found
111 | end
112 | end
113 |
114 | end
115 |
--------------------------------------------------------------------------------
/config/environments/production.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # Code is not reloaded between requests.
5 | config.cache_classes = true
6 |
7 | # Eager load code on boot. This eager loads most of Rails and
8 | # your application in memory, allowing both threaded web servers
9 | # and those relying on copy on write to perform better.
10 | # Rake tasks automatically ignore this option for performance.
11 | config.eager_load = true
12 |
13 | # Full error reports are disabled and caching is turned on.
14 | config.consider_all_requests_local = false
15 | config.action_controller.perform_caching = true
16 |
17 | # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"]
18 | # or in config/master.key. This key is used to decrypt credentials (and other encrypted files).
19 | # config.require_master_key = true
20 |
21 | # Disable serving static files from the `/public` folder by default since
22 | # Apache or NGINX already handles this.
23 | config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
24 |
25 | # Compress JavaScripts and CSS.
26 | config.assets.js_compressor = :uglifier
27 | # config.assets.css_compressor = :sass
28 |
29 | # Do not fallback to assets pipeline if a precompiled asset is missed.
30 | config.assets.compile = false
31 |
32 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb
33 |
34 | # Enable serving of images, stylesheets, and JavaScripts from an asset server.
35 | # config.action_controller.asset_host = 'http://assets.example.com'
36 |
37 | # Specifies the header that your server uses for sending files.
38 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
39 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
40 |
41 | # Store uploaded files on the local file system (see config/storage.yml for options)
42 | config.active_storage.service = :local
43 |
44 | # Mount Action Cable outside main process or domain
45 | # config.action_cable.mount_path = nil
46 | # config.action_cable.url = 'wss://example.com/cable'
47 | # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ]
48 |
49 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
50 | # config.force_ssl = true
51 |
52 | # Use the lowest log level to ensure availability of diagnostic information
53 | # when problems arise.
54 | config.log_level = :debug
55 |
56 | # Prepend all log lines with the following tags.
57 | config.log_tags = [ :request_id ]
58 |
59 | # Use a different cache store in production.
60 | # config.cache_store = :mem_cache_store
61 |
62 | # Use a real queuing backend for Active Job (and separate queues per environment)
63 | # config.active_job.queue_adapter = :resque
64 | # config.active_job.queue_name_prefix = "burn-after-clicking_#{Rails.env}"
65 |
66 | config.action_mailer.perform_caching = false
67 |
68 | # Ignore bad email addresses and do not raise email delivery errors.
69 | # Set this to true and configure the email server for immediate delivery to raise delivery errors.
70 | # config.action_mailer.raise_delivery_errors = false
71 |
72 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
73 | # the I18n.default_locale when a translation cannot be found).
74 | config.i18n.fallbacks = true
75 |
76 | # Send deprecation notices to registered listeners.
77 | config.active_support.deprecation = :notify
78 |
79 | # Use default logging formatter so that PID and timestamp are not suppressed.
80 | config.log_formatter = ::Logger::Formatter.new
81 |
82 | # Use a different logger for distributed setups.
83 | # require 'syslog/logger'
84 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name')
85 |
86 | if ENV["RAILS_LOG_TO_STDOUT"].present?
87 | logger = ActiveSupport::Logger.new(STDOUT)
88 | logger.formatter = config.log_formatter
89 | config.logger = ActiveSupport::TaggedLogging.new(logger)
90 | end
91 |
92 | # Do not dump schema after migrations.
93 | config.active_record.dump_schema_after_migration = false
94 | end
95 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | actioncable (7.2.0)
5 | actionpack (= 7.2.0)
6 | activesupport (= 7.2.0)
7 | nio4r (~> 2.0)
8 | websocket-driver (>= 0.6.1)
9 | zeitwerk (~> 2.6)
10 | actionmailbox (7.2.0)
11 | actionpack (= 7.2.0)
12 | activejob (= 7.2.0)
13 | activerecord (= 7.2.0)
14 | activestorage (= 7.2.0)
15 | activesupport (= 7.2.0)
16 | mail (>= 2.8.0)
17 | actionmailer (7.2.0)
18 | actionpack (= 7.2.0)
19 | actionview (= 7.2.0)
20 | activejob (= 7.2.0)
21 | activesupport (= 7.2.0)
22 | mail (>= 2.8.0)
23 | rails-dom-testing (~> 2.2)
24 | actionpack (7.2.0)
25 | actionview (= 7.2.0)
26 | activesupport (= 7.2.0)
27 | nokogiri (>= 1.8.5)
28 | racc
29 | rack (>= 2.2.4, < 3.2)
30 | rack-session (>= 1.0.1)
31 | rack-test (>= 0.6.3)
32 | rails-dom-testing (~> 2.2)
33 | rails-html-sanitizer (~> 1.6)
34 | useragent (~> 0.16)
35 | actiontext (7.2.0)
36 | actionpack (= 7.2.0)
37 | activerecord (= 7.2.0)
38 | activestorage (= 7.2.0)
39 | activesupport (= 7.2.0)
40 | globalid (>= 0.6.0)
41 | nokogiri (>= 1.8.5)
42 | actionview (7.2.0)
43 | activesupport (= 7.2.0)
44 | builder (~> 3.1)
45 | erubi (~> 1.11)
46 | rails-dom-testing (~> 2.2)
47 | rails-html-sanitizer (~> 1.6)
48 | activejob (7.2.0)
49 | activesupport (= 7.2.0)
50 | globalid (>= 0.3.6)
51 | activemodel (7.2.0)
52 | activesupport (= 7.2.0)
53 | activerecord (7.2.0)
54 | activemodel (= 7.2.0)
55 | activesupport (= 7.2.0)
56 | timeout (>= 0.4.0)
57 | activestorage (7.2.0)
58 | actionpack (= 7.2.0)
59 | activejob (= 7.2.0)
60 | activerecord (= 7.2.0)
61 | activesupport (= 7.2.0)
62 | marcel (~> 1.0)
63 | activesupport (7.2.0)
64 | base64
65 | bigdecimal
66 | concurrent-ruby (~> 1.0, >= 1.3.1)
67 | connection_pool (>= 2.2.5)
68 | drb
69 | i18n (>= 1.6, < 2)
70 | logger (>= 1.4.2)
71 | minitest (>= 5.1)
72 | securerandom (>= 0.3)
73 | tzinfo (~> 2.0, >= 2.0.5)
74 | addressable (2.8.6)
75 | public_suffix (>= 2.0.2, < 6.0)
76 | archive-zip (0.12.0)
77 | io-like (~> 0.3.0)
78 | autoprefixer-rails (10.4.16.0)
79 | execjs (~> 2)
80 | base64 (0.2.0)
81 | bcrypt (3.1.20)
82 | bigdecimal (3.1.5)
83 | bindex (0.8.1)
84 | bootsnap (1.17.1)
85 | msgpack (~> 1.2)
86 | bootstrap (4.3.1)
87 | autoprefixer-rails (>= 9.1.0)
88 | popper_js (>= 1.14.3, < 2)
89 | sassc-rails (>= 2.0.0)
90 | bootstrap_form (5.4.0)
91 | actionpack (>= 6.1)
92 | activemodel (>= 6.1)
93 | builder (3.2.4)
94 | byebug (11.1.3)
95 | capybara (3.39.2)
96 | addressable
97 | matrix
98 | mini_mime (>= 0.1.3)
99 | nokogiri (~> 1.8)
100 | rack (>= 1.6.0)
101 | rack-test (>= 0.6.3)
102 | regexp_parser (>= 1.5, < 3.0)
103 | xpath (~> 3.2)
104 | chromedriver-helper (2.1.1)
105 | archive-zip (~> 0.10)
106 | nokogiri (~> 1.8)
107 | coffee-rails (4.2.2)
108 | coffee-script (>= 2.2.0)
109 | railties (>= 4.0.0)
110 | coffee-script (2.4.1)
111 | coffee-script-source
112 | execjs
113 | coffee-script-source (1.12.2)
114 | concurrent-ruby (1.3.4)
115 | connection_pool (2.4.1)
116 | crass (1.0.6)
117 | date (3.3.4)
118 | dotenv (2.8.1)
119 | dotenv-rails (2.8.1)
120 | dotenv (= 2.8.1)
121 | railties (>= 3.2)
122 | drb (2.2.0)
123 | ruby2_keywords
124 | erubi (1.12.0)
125 | et-orbi (1.2.7)
126 | tzinfo
127 | execjs (2.9.1)
128 | ffi (1.16.3)
129 | fugit (1.9.0)
130 | et-orbi (~> 1, >= 1.2.7)
131 | raabro (~> 1.4)
132 | globalid (1.2.1)
133 | activesupport (>= 6.1)
134 | i18n (1.14.1)
135 | concurrent-ruby (~> 1.0)
136 | io-console (0.7.1)
137 | io-like (0.3.1)
138 | irb (1.14.0)
139 | rdoc (>= 4.0.0)
140 | reline (>= 0.4.2)
141 | jbuilder (2.11.5)
142 | actionview (>= 5.0.0)
143 | activesupport (>= 5.0.0)
144 | jquery-rails (4.6.0)
145 | rails-dom-testing (>= 1, < 3)
146 | railties (>= 4.2.0)
147 | thor (>= 0.14, < 2.0)
148 | listen (3.8.0)
149 | rb-fsevent (~> 0.10, >= 0.10.3)
150 | rb-inotify (~> 0.9, >= 0.9.10)
151 | logger (1.6.0)
152 | loofah (2.22.0)
153 | crass (~> 1.0.2)
154 | nokogiri (>= 1.12.0)
155 | mail (2.8.1)
156 | mini_mime (>= 0.1.1)
157 | net-imap
158 | net-pop
159 | net-smtp
160 | marcel (1.0.4)
161 | matrix (0.4.2)
162 | mini_mime (1.1.5)
163 | minitest (5.21.1)
164 | msgpack (1.7.2)
165 | net-imap (0.4.14)
166 | date
167 | net-protocol
168 | net-pop (0.1.2)
169 | net-protocol
170 | net-protocol (0.2.2)
171 | timeout
172 | net-smtp (0.5.0)
173 | net-protocol
174 | nio4r (2.7.0)
175 | nokogiri (1.16.0-aarch64-linux)
176 | racc (~> 1.4)
177 | nokogiri (1.16.0-arm-linux)
178 | racc (~> 1.4)
179 | nokogiri (1.16.0-arm64-darwin)
180 | racc (~> 1.4)
181 | nokogiri (1.16.0-x86-linux)
182 | racc (~> 1.4)
183 | nokogiri (1.16.0-x86_64-darwin)
184 | racc (~> 1.4)
185 | nokogiri (1.16.0-x86_64-linux)
186 | racc (~> 1.4)
187 | pg (1.5.4)
188 | popper_js (1.16.1)
189 | prometheus_exporter (0.4.16)
190 | psych (5.1.2)
191 | stringio
192 | public_suffix (5.0.4)
193 | puma (4.3.12)
194 | nio4r (~> 2.0)
195 | raabro (1.4.0)
196 | racc (1.7.3)
197 | rack (2.2.8)
198 | rack-attack (6.0.0)
199 | rack (>= 1.0, < 3)
200 | rack-session (1.0.2)
201 | rack (< 3)
202 | rack-test (2.1.0)
203 | rack (>= 1.3)
204 | rackup (1.0.0)
205 | rack (< 3)
206 | webrick
207 | rails (7.2.0)
208 | actioncable (= 7.2.0)
209 | actionmailbox (= 7.2.0)
210 | actionmailer (= 7.2.0)
211 | actionpack (= 7.2.0)
212 | actiontext (= 7.2.0)
213 | actionview (= 7.2.0)
214 | activejob (= 7.2.0)
215 | activemodel (= 7.2.0)
216 | activerecord (= 7.2.0)
217 | activestorage (= 7.2.0)
218 | activesupport (= 7.2.0)
219 | bundler (>= 1.15.0)
220 | railties (= 7.2.0)
221 | rails-dom-testing (2.2.0)
222 | activesupport (>= 5.0.0)
223 | minitest
224 | nokogiri (>= 1.6)
225 | rails-html-sanitizer (1.6.0)
226 | loofah (~> 2.21)
227 | nokogiri (~> 1.14)
228 | railties (7.2.0)
229 | actionpack (= 7.2.0)
230 | activesupport (= 7.2.0)
231 | irb (~> 1.13)
232 | rackup (>= 1.0.0)
233 | rake (>= 12.2)
234 | thor (~> 1.0, >= 1.2.2)
235 | zeitwerk (~> 2.6)
236 | rake (13.1.0)
237 | rb-fsevent (0.11.2)
238 | rb-inotify (0.10.1)
239 | ffi (~> 1.0)
240 | rdoc (6.6.2)
241 | psych (>= 4.0.0)
242 | regexp_parser (2.9.0)
243 | reline (0.4.2)
244 | io-console (~> 0.5)
245 | rexml (3.2.6)
246 | ruby2_keywords (0.0.5)
247 | rubyzip (2.3.2)
248 | rufus-scheduler (3.5.2)
249 | fugit (~> 1.1, >= 1.1.5)
250 | sassc (2.4.0)
251 | ffi (~> 1.9)
252 | sassc-rails (2.1.2)
253 | railties (>= 4.0.0)
254 | sassc (>= 2.0)
255 | sprockets (> 3.0)
256 | sprockets-rails
257 | tilt
258 | securerandom (0.3.1)
259 | selenium-webdriver (4.16.0)
260 | rexml (~> 3.2, >= 3.2.5)
261 | rubyzip (>= 1.2.2, < 3.0)
262 | websocket (~> 1.0)
263 | spring (2.1.1)
264 | spring-watcher-listen (2.0.1)
265 | listen (>= 2.7, < 4.0)
266 | spring (>= 1.2, < 3.0)
267 | sprockets (4.2.1)
268 | concurrent-ruby (~> 1.0)
269 | rack (>= 2.2.4, < 4)
270 | sprockets-rails (3.4.2)
271 | actionpack (>= 5.2)
272 | activesupport (>= 5.2)
273 | sprockets (>= 3.0.0)
274 | stringio (3.1.0)
275 | thor (1.3.0)
276 | tilt (2.3.0)
277 | timeout (0.4.1)
278 | turbolinks (5.2.1)
279 | turbolinks-source (~> 5.2)
280 | turbolinks-source (5.2.0)
281 | tzinfo (2.0.6)
282 | concurrent-ruby (~> 1.0)
283 | uglifier (4.2.0)
284 | execjs (>= 0.3.0, < 3)
285 | useragent (0.16.10)
286 | web-console (4.2.1)
287 | actionview (>= 6.0.0)
288 | activemodel (>= 6.0.0)
289 | bindex (>= 0.4.0)
290 | railties (>= 6.0.0)
291 | webrick (1.8.1)
292 | websocket (1.2.10)
293 | websocket-driver (0.7.6)
294 | websocket-extensions (>= 0.1.0)
295 | websocket-extensions (0.1.5)
296 | xpath (3.2.0)
297 | nokogiri (~> 1.8)
298 | zeitwerk (2.6.12)
299 |
300 | PLATFORMS
301 | aarch64-linux
302 | arm-linux
303 | arm64-darwin
304 | x86-linux
305 | x86_64-darwin
306 | x86_64-linux
307 |
308 | DEPENDENCIES
309 | bcrypt (~> 3.1.7)
310 | bootsnap (>= 1.1.0)
311 | bootstrap (= 4.3.1)
312 | bootstrap_form (>= 4.1.0)
313 | byebug
314 | capybara (>= 2.15)
315 | chromedriver-helper
316 | coffee-rails (~> 4.2)
317 | dotenv-rails
318 | jbuilder (~> 2.5)
319 | jquery-rails
320 | listen (~> 3.5)
321 | nokogiri (>= 1.10.4)
322 | pg (>= 0.18, < 2.0)
323 | prometheus_exporter (= 0.4.16)
324 | puma (~> 4.3.12)
325 | rack (>= 2.2.3)
326 | rack-attack (~> 6.0.0)
327 | rails (~> 7.2)
328 | rubyzip (>= 1.3.0)
329 | rufus-scheduler (= 3.5.2)
330 | sassc-rails (~> 2.1.1)
331 | selenium-webdriver
332 | spring
333 | spring-watcher-listen (~> 2.0.0)
334 | turbolinks (~> 5)
335 | tzinfo-data
336 | uglifier (>= 1.3.0)
337 | web-console (>= 3.3.0)
338 | websocket-extensions (>= 0.1.5)
339 |
340 | RUBY VERSION
341 | ruby 3.3.0p0
342 |
343 | BUNDLED WITH
344 | 2.5.3
345 |
--------------------------------------------------------------------------------