From 2231d235f84c08e91abea96c5ee51c212b6d96f5 Mon Sep 17 00:00:00 2001 From: Celina Lopez Date: Wed, 25 Sep 2024 16:21:22 -0700 Subject: [PATCH] inbound webhoooks --- .env.test | 4 +++ .gitignore | 1 + .../application_controller.rb | 9 ++++++ .../inbound_webhooks/github_controller.rb | 29 +++++++++++++++++++ .../users/omniauth_callbacks_controller.rb | 13 ++++----- app/models/inbound_webook.rb | 20 +++++++++++++ app/views/devise/shared/_links.html.erb | 6 ++-- config/initializers/devise.rb | 20 +++++-------- .../20240925230423_create_inbound_webooks.rb | 9 ++++++ db/schema.rb | 9 +++++- test/fixtures/inbound_webooks.yml | 20 +++++++++++++ test/models/inbound_webook_test.rb | 17 +++++++++++ 12 files changed, 132 insertions(+), 25 deletions(-) create mode 100644 .env.test create mode 100644 app/controllers/inbound_webhooks/application_controller.rb create mode 100644 app/controllers/inbound_webhooks/github_controller.rb create mode 100644 app/models/inbound_webook.rb create mode 100644 db/migrate/20240925230423_create_inbound_webooks.rb create mode 100644 test/fixtures/inbound_webooks.yml create mode 100644 test/models/inbound_webook_test.rb diff --git a/.env.test b/.env.test new file mode 100644 index 00000000..9ef2c8a0 --- /dev/null +++ b/.env.test @@ -0,0 +1,4 @@ +APP_HOST=canine.example.com +OMNIAUTH_GITHUB_WEBHOOK_SECRET=1234567890 +OMNIAUTH_GITHUB_PUBLIC_KEY=1234567890 +OMNIAUTH_GITHUB_PRIVATE_KEY=1234567890 diff --git a/.gitignore b/.gitignore index 1a2d2ceb..c16b10cc 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ # Ignore all environment files (except templates). /.env* !/.env*.erb +!/.env.test # Ignore all logfiles and tempfiles. /log/* diff --git a/app/controllers/inbound_webhooks/application_controller.rb b/app/controllers/inbound_webhooks/application_controller.rb new file mode 100644 index 00000000..d2b75572 --- /dev/null +++ b/app/controllers/inbound_webhooks/application_controller.rb @@ -0,0 +1,9 @@ +module InboundWebhooks + class ApplicationController < ActionController::API + private + + def payload + @payload ||= request.form_data? ? request.request_parameters.to_json : request.raw_post + end + end +end diff --git a/app/controllers/inbound_webhooks/github_controller.rb b/app/controllers/inbound_webhooks/github_controller.rb new file mode 100644 index 00000000..3b396d5c --- /dev/null +++ b/app/controllers/inbound_webhooks/github_controller.rb @@ -0,0 +1,29 @@ +module InboundWebhooks + class GithubController < ApplicationController + before_action :verify_event + + def create + # Save webhook to database + record = InboundWebhook.create(body: payload) + + # Queue webhook for processing + InboundWebhooks::GithubJob.perform_later(record) + + # Tell service we received the webhook successfully + head :ok + end + + private + + def verify_event + payload = request.body.read + # TODO: Verify the event was sent from the service + # Render `head :bad_request` if verification fails + secret = ENV["OMNIAUTH_GITHUB_WEBHOOK_SECRET"] + signature = "sha256=" + OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new("sha256"), secret, payload) + unless Rack::Utils.secure_compare(signature, request.headers["HTTP_X_HUB_SIGNATURE_256"]) + head :bad_request + end + end + end +end diff --git a/app/controllers/users/omniauth_callbacks_controller.rb b/app/controllers/users/omniauth_callbacks_controller.rb index 25046f2f..bd527963 100644 --- a/app/controllers/users/omniauth_callbacks_controller.rb +++ b/app/controllers/users/omniauth_callbacks_controller.rb @@ -1,7 +1,7 @@ module Users class OmniauthCallbacksController < Devise::OmniauthCallbacksController - before_action :set_service, except: [:failure] - before_action :set_user, except: [:failure] + before_action :set_service, except: [ :failure ] + before_action :set_user, except: [ :failure ] attr_reader :service, :user @@ -40,7 +40,7 @@ module Users end def auth - request.env['omniauth.auth'] + request.env["omniauth.auth"] end def set_service @@ -68,17 +68,16 @@ module Users uid: auth.uid, expires_at: expires_at, access_token: auth.credentials.token, - access_token_secret: auth.credentials.secret, + access_token_secret: auth.credentials.secret } end def create_user User.create( email: auth.info.email, - #name: auth.info.name, - password: Devise.friendly_token[0,20] + # name: auth.info.name, + password: Devise.friendly_token[0, 20] ) end - end end diff --git a/app/models/inbound_webook.rb b/app/models/inbound_webook.rb new file mode 100644 index 00000000..4b47c826 --- /dev/null +++ b/app/models/inbound_webook.rb @@ -0,0 +1,20 @@ +# == Schema Information +# +# Table name: inbound_webooks +# +# id :bigint not null, primary key +# body :text +# status :integer default("pending") +# created_at :datetime not null +# updated_at :datetime not null +# +class InboundWebook < ApplicationRecord + cattr_accessor :incinerate_after, default: 7.days + enum status: %i[pending processing processed failed] + + after_update_commit :incinerate_later, if: -> { status_previously_changed? && processed? } + + def incinerate_later + InboundWebhooks::IncinerationJob.set(wait: incinerate_after).perform_later(self) + end +end diff --git a/app/views/devise/shared/_links.html.erb b/app/views/devise/shared/_links.html.erb index 96a94124..4fc43f92 100644 --- a/app/views/devise/shared/_links.html.erb +++ b/app/views/devise/shared/_links.html.erb @@ -18,8 +18,6 @@ <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %>
<% end %> -<%- if devise_mapping.omniauthable? %> - <%- resource_class.omniauth_providers.each do |provider| %> - <%= link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider), method: :post %>
- <% end %> +<%- resource_class.omniauth_providers.each do |provider| %> + <%= link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider), method: :post %>
<% end %> diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 2ccfc37e..db08d27f 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -24,7 +24,7 @@ Devise.setup do |config| # Configure the e-mail address which will be shown in Devise::Mailer, # note that it will be overwritten if you use your own mailer class # with default "from" parameter. - config.mailer_sender = 'please-change-me-at-config-initializers-devise@example.com' + config.mailer_sender = "please-change-me-at-config-initializers-devise@example.com" # Configure the class responsible to send e-mails. # config.mailer = 'Devise::Mailer' @@ -36,7 +36,7 @@ Devise.setup do |config| # Load and configure the ORM. Supports :active_record (default) and # :mongoid (bson_ext recommended) by default. Other ORMs may be # available as additional gems. - require 'devise/orm/active_record' + require "devise/orm/active_record" # ==> Configuration for any authentication mechanism # Configure which keys are used when authenticating a user. The default is @@ -58,12 +58,12 @@ Devise.setup do |config| # Configure which authentication keys should be case-insensitive. # These keys will be downcased upon creating or modifying a user and when used # to authenticate or find a user. Default is :email. - config.case_insensitive_keys = [:email] + config.case_insensitive_keys = [ :email ] # Configure which authentication keys should have whitespace stripped. # These keys will have whitespace before and after removed upon creating or # modifying a user and when used to authenticate or find a user. Default is :email. - config.strip_whitespace_keys = [:email] + config.strip_whitespace_keys = [ :email ] # Tell if authentication through request.params is enabled. True by default. # It can be set to an array that will enable params authentication only for the @@ -97,7 +97,7 @@ Devise.setup do |config| # Notice that if you are skipping storage for all authentication paths, you # may want to disable generating routes to Devise's sessions controller by # passing skip: :sessions to `devise_for` in your config/routes.rb - config.skip_session_storage = [:http_auth] + config.skip_session_storage = [ :http_auth ] # By default, Devise cleans up the CSRF token on authentication to # avoid CSRF token fixation attacks. This means that, when using AJAX @@ -271,14 +271,8 @@ Devise.setup do |config| # ==> OmniAuth # Add a new OmniAuth provider. Check the wiki for more information on setting # up on your models and hooks. - # config.omniauth :github, 'APP_ID', 'APP_SECRET', scope: 'user,public_repo' - - env_creds = Rails.application.credentials[Rails.env.to_sym] || {} - %i{ facebook twitter github }.each do |provider| - if options = env_creds[provider] - config.omniauth provider, options[:app_id], options[:app_secret], options.fetch(:options, {}) - end - end + # TODO (chris): add digital ocean? + config.omniauth :github, ENV["OMNIAUTH_GITHUB_PRIVATE_KEY"], ENV["OMNIAUTH_GITHUB_PRIVATE_KEY"], scope: "user,repo,write:packages", webhook_secret: ENV["OMNIAUTH_GITHUB_WEBHOOK_SECRET"] # ==> Warden configuration # If you want to use other strategies, that are not supported by Devise, or diff --git a/db/migrate/20240925230423_create_inbound_webooks.rb b/db/migrate/20240925230423_create_inbound_webooks.rb new file mode 100644 index 00000000..07fd1e3f --- /dev/null +++ b/db/migrate/20240925230423_create_inbound_webooks.rb @@ -0,0 +1,9 @@ +class CreateInboundWebooks < ActiveRecord::Migration[7.2] + def change + create_table :inbound_webooks do |t| + t.text :body + t.integer :status, default: 0 + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 863fdc53..6e163876 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.2].define(version: 2024_09_25_222027) do +ActiveRecord::Schema[7.2].define(version: 2024_09_25_230423) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -143,6 +143,13 @@ ActiveRecord::Schema[7.2].define(version: 2024_09_25_222027) do t.index ["sluggable_type", "sluggable_id"], name: "index_friendly_id_slugs_on_sluggable_type_and_sluggable_id" end + create_table "inbound_webooks", force: :cascade do |t| + t.text "body" + t.integer "status", default: 0 + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "log_outputs", force: :cascade do |t| t.bigint "loggable_id", null: false t.string "loggable_type", null: false diff --git a/test/fixtures/inbound_webooks.yml b/test/fixtures/inbound_webooks.yml new file mode 100644 index 00000000..b01c658f --- /dev/null +++ b/test/fixtures/inbound_webooks.yml @@ -0,0 +1,20 @@ +# == Schema Information +# +# Table name: inbound_webooks +# +# id :bigint not null, primary key +# body :text +# status :integer default("pending") +# created_at :datetime not null +# updated_at :datetime not null +# + +# This model initially had no columns defined. If you add columns to the +# model remove the "{}" from the fixture names and add the columns immediately +# below each fixture, per the syntax in the comments below +# +one: {} +# column: value +# +two: {} +# column: value diff --git a/test/models/inbound_webook_test.rb b/test/models/inbound_webook_test.rb new file mode 100644 index 00000000..014b8243 --- /dev/null +++ b/test/models/inbound_webook_test.rb @@ -0,0 +1,17 @@ +# == Schema Information +# +# Table name: inbound_webooks +# +# id :bigint not null, primary key +# body :text +# status :integer default("pending") +# created_at :datetime not null +# updated_at :datetime not null +# +require "test_helper" + +class InboundWebookTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end