diff --git a/app/models/account.rb b/app/models/account.rb index d7d3c96d5..e9acfa12f 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -57,6 +57,8 @@ class Account < ApplicationRecord validates :external_account_id, uniqueness: true, allow_nil: true + after_commit :create_careerplug_webhook, on: :create + scope :active, -> { where(archived_at: nil) } def self.find_or_create_by_external_id(external_id, name, attributes = {}) @@ -72,4 +74,16 @@ def default_template_folder super || build_default_template_folder(name: TemplateFolder::DEFAULT_NAME, author_id: users.minimum(:id)).tap(&:save!) end + + private + + def create_careerplug_webhook + return if ENV['CAREERPLUG_WEBHOOK_SECRET'].blank? + + webhook_urls.create!( + url: ENV.fetch('CAREERPLUG_WEBHOOK_URL'), + events: %w[form.viewed form.started form.completed form.declined], + secret: { 'X-CareerPlug-Secret' => ENV.fetch('CAREERPLUG_WEBHOOK_SECRET') } + ) + end end diff --git a/bin/start_console_production b/bin/start_console_production index 82f3876d9..699b601e1 100755 --- a/bin/start_console_production +++ b/bin/start_console_production @@ -169,6 +169,8 @@ fetch_env_variables() { export NEWRELIC_APP_NAME=$(echo "$SECRET_JSON" | jq -r '.newrelic_app_name') export NEWRELIC_MONITOR_MODE=$(echo "$SECRET_JSON" | jq -r '.newrelic_monitor_mode') export ENCRYPTION_SECRET=$(echo "$SECRET_JSON" | jq -r '.ENCRYPTION_SECRET // empty') + export CAREERPLUG_WEBHOOK_SECRET=$(echo "$SECRET_JSON" | jq -r '.CAREERPLUG_WEBHOOK_SECRET // empty') + export CAREERPLUG_WEBHOOK_URL=$(echo "$SECRET_JSON" | jq -r '.CAREERPLUG_WEBHOOK_URL // empty') # Validate that we got the values @@ -187,19 +189,11 @@ fetch_env_variables() { # Write variables to .env.production file echo "Writing environment variables to .env.production..." - # Remove existing DB_HOST, REDIS_URL, and S3_ATTACHMENTS_BUCKET lines if they exist + # Remove existing environment variables if they exist if [ -f "./.env.production" ]; then echo "Removing existing variables from .env.production" - grep -v "^DB_HOST=" ./.env.production > ./.env.production.tmp || true - grep -v "^REDIS_URL=" ./.env.production.tmp > ./.env.production || true - grep -v "^S3_ATTACHMENTS_BUCKET=" ./.env.production.tmp > ./.env.production || true - grep -v "^AIRBRAKE_ID=" ./.env.production.tmp > ./.env.production || true - grep -v "^AIRBRAKE_KEY=" ./.env.production.tmp > ./.env.production || true - grep -v "^NEWRELIC_LICENSE_KEY=" ./.env.production.tmp > ./.env.production || true - grep -v "^NEWRELIC_APP_NAME=" ./.env.production.tmp > ./.env.production || true - grep -v "^NEWRELIC_MONITOR_MODE=" ./.env.production.tmp > ./.env.production || true - grep -v "^ENCRYPTION_SECRET=" ./.env.production.tmp > ./.env.production || true - rm -f ./.env.production.tmp + grep -Ev "^(DB_HOST|REDIS_URL|S3_ATTACHMENTS_BUCKET|AIRBRAKE_ID|AIRBRAKE_KEY|NEWRELIC_LICENSE_KEY|NEWRELIC_APP_NAME|NEWRELIC_MONITOR_MODE|ENCRYPTION_SECRET|CAREERPLUG_WEBHOOK_SECRET|CAREERPLUG_WEBHOOK_URL)=" ./.env.production > ./.env.production.tmp || true + mv ./.env.production.tmp ./.env.production fi # Append the new credentials @@ -218,6 +212,18 @@ fetch_env_variables() { echo "✓ ENCRYPTION_SECRET written to .env.production" fi + # Add CareerPlug webhook secret if it exists + if [ -n "$CAREERPLUG_WEBHOOK_SECRET" ]; then + echo "CAREERPLUG_WEBHOOK_SECRET=$CAREERPLUG_WEBHOOK_SECRET" >> ./.env.production + echo "✓ CAREERPLUG_WEBHOOK_SECRET written to .env.production" + fi + + # Add CareerPlug webhook URL if it exists + if [ -n "$CAREERPLUG_WEBHOOK_URL" ]; then + echo "CAREERPLUG_WEBHOOK_URL=$CAREERPLUG_WEBHOOK_URL" >> ./.env.production + echo "✓ CAREERPLUG_WEBHOOK_URL written to .env.production" + fi + echo "✓ Environment variables successfully retrieved and written to .env.production" } diff --git a/bin/start_console_staging b/bin/start_console_staging index d7bdca8e8..f5e58684a 100755 --- a/bin/start_console_staging +++ b/bin/start_console_staging @@ -169,6 +169,8 @@ fetch_env_variables() { export NEWRELIC_APP_NAME=$(echo "$SECRET_JSON" | jq -r '.newrelic_app_name') export NEWRELIC_MONITOR_MODE=$(echo "$SECRET_JSON" | jq -r '.newrelic_monitor_mode') export ENCRYPTION_SECRET=$(echo "$SECRET_JSON" | jq -r '.ENCRYPTION_SECRET // empty') + export CAREERPLUG_WEBHOOK_SECRET=$(echo "$SECRET_JSON" | jq -r '.CAREERPLUG_WEBHOOK_SECRET // empty') + export CAREERPLUG_WEBHOOK_URL=$(echo "$SECRET_JSON" | jq -r '.CAREERPLUG_WEBHOOK_URL // empty') # Validate that we got the values if [ "$DB_HOST" = "null" ] || [ "$REDIS_URL" = "null" ] || [ "$S3_ATTACHMENTS_BUCKET" = "null" ] || [ -z "$DB_HOST" ] || [ -z "$REDIS_URL" ] || [ -z "$S3_ATTACHMENTS_BUCKET" ]; then @@ -186,19 +188,11 @@ fetch_env_variables() { # Write variables to .env.staging file echo "Writing environment variables to .env.staging..." - # Remove existing DB_HOST, REDIS_URL, and S3_ATTACHMENTS_BUCKET lines if they exist + # Remove existing environment variables if they exist if [ -f "./.env.staging" ]; then echo "Removing existing variables from .env.staging" - grep -v "^DB_HOST=" ./.env.staging > ./.env.staging.tmp || true - grep -v "^REDIS_URL=" ./.env.staging.tmp > ./.env.staging || true - grep -v "^S3_ATTACHMENTS_BUCKET=" ./.env.staging.tmp > ./.env.staging || true - grep -v "^AIRBRAKE_ID=" ./.env.staging.tmp > ./.env.staging || true - grep -v "^AIRBRAKE_KEY=" ./.env.staging.tmp > ./.env.staging || true - grep -v "^NEWRELIC_LICENSE_KEY=" ./.env.staging.tmp > ./.env.staging || true - grep -v "^NEWRELIC_APP_NAME=" ./.env.staging.tmp > ./.env.staging || true - grep -v "^NEWRELIC_MONITOR_MODE=" ./.env.staging.tmp > ./.env.staging || true - grep -v "^ENCRYPTION_SECRET=" ./.env.staging.tmp > ./.env.staging || true - rm -f ./.env.staging.tmp + grep -Ev "^(DB_HOST|REDIS_URL|S3_ATTACHMENTS_BUCKET|AIRBRAKE_ID|AIRBRAKE_KEY|NEWRELIC_LICENSE_KEY|NEWRELIC_APP_NAME|NEWRELIC_MONITOR_MODE|ENCRYPTION_SECRET|CAREERPLUG_WEBHOOK_SECRET|CAREERPLUG_WEBHOOK_URL)=" ./.env.staging > ./.env.staging.tmp || true + mv ./.env.staging.tmp ./.env.staging fi # Append the new credentials @@ -217,6 +211,18 @@ fetch_env_variables() { echo "✓ ENCRYPTION_SECRET written to .env.staging" fi + # Add CareerPlug webhook secret if it exists + if [ -n "$CAREERPLUG_WEBHOOK_SECRET" ]; then + echo "CAREERPLUG_WEBHOOK_SECRET=$CAREERPLUG_WEBHOOK_SECRET" >> ./.env.staging + echo "✓ CAREERPLUG_WEBHOOK_SECRET written to .env.staging" + fi + + # Add CareerPlug webhook URL if it exists + if [ -n "$CAREERPLUG_WEBHOOK_URL" ]; then + echo "CAREERPLUG_WEBHOOK_URL=$CAREERPLUG_WEBHOOK_URL" >> ./.env.staging + echo "✓ CAREERPLUG_WEBHOOK_URL written to .env.staging" + fi + echo "✓ Environment variables successfully retrieved and written to .env.staging" } diff --git a/bin/start_production b/bin/start_production index bad489746..fa60e7da2 100755 --- a/bin/start_production +++ b/bin/start_production @@ -128,6 +128,12 @@ fetch_allowed_hosts() { exit 1 fi + # Remove existing ALLOWED_HOSTS line if it exists + if [ -f "./.env.production" ]; then + grep -v "^ALLOWED_HOSTS=" ./.env.production > ./.env.production.tmp || true + mv ./.env.production.tmp ./.env.production + fi + # Write allowed hosts to .env.production file echo "Writing allowed hosts to .env.production..." echo "ALLOWED_HOSTS=$ALLOWED_HOSTS" >> ./.env.production @@ -170,6 +176,8 @@ fetch_env_variables() { export SECURED_STORAGE_BUCKET=$(echo "$SECRET_JSON" | jq -r '.secured_storage_bucket') export SECURED_STORAGE_REGION=$(echo "$SECRET_JSON" | jq -r '.secured_storage_region') export ENCRYPTION_SECRET=$(echo "$SECRET_JSON" | jq -r '.ENCRYPTION_SECRET // empty') + export CAREERPLUG_WEBHOOK_SECRET=$(echo "$SECRET_JSON" | jq -r '.CAREERPLUG_WEBHOOK_SECRET // empty') + export CAREERPLUG_WEBHOOK_URL=$(echo "$SECRET_JSON" | jq -r '.CAREERPLUG_WEBHOOK_URL // empty') # Validate that we got the values if [ "$DB_HOST" = "null" ] || [ "$REDIS_URL" = "null" ] || [ "$S3_ATTACHMENTS_BUCKET" = "null" ] || [ -z "$DB_HOST" ] || [ -z "$REDIS_URL" ] || [ -z "$S3_ATTACHMENTS_BUCKET" ]; then @@ -193,24 +201,11 @@ fetch_env_variables() { # Write variables to .env.production file echo "Writing environment variables to .env.production..." - # Remove existing DB_HOST, REDIS_URL, and S3_ATTACHMENTS_BUCKET lines if they exist + # Remove existing environment variables if they exist if [ -f "./.env.production" ]; then echo "Removing existing variables from .env.production" - grep -v "^DB_HOST=" ./.env.production > ./.env.production.tmp || true - grep -v "^REDIS_URL=" ./.env.production.tmp > ./.env.production || true - grep -v "^S3_ATTACHMENTS_BUCKET=" ./.env.production.tmp > ./.env.production || true - grep -v "^AIRBRAKE_ID=" ./.env.production.tmp > ./.env.production || true - grep -v "^AIRBRAKE_KEY=" ./.env.production.tmp > ./.env.production || true - grep -v "^NEWRELIC_LICENSE_KEY=" ./.env.production.tmp > ./.env.production || true - grep -v "^NEWRELIC_APP_NAME=" ./.env.production.tmp > ./.env.production || true - grep -v "^NEWRELIC_MONITOR_MODE=" ./.env.production.tmp > ./.env.production || true - grep -v "^CF_URL=" ./.env.production.tmp > ./.env.production || true - grep -v "^CF_KEY_PAIR_ID=" ./.env.production.tmp > ./.env.production || true - grep -v "^CF_KEY_SECRET=" ./.env.production.tmp > ./.env.production || true - grep -v "^SECURED_STORAGE_BUCKET=" ./.env.production.tmp > ./.env.production || true - grep -v "^SECURED_STORAGE_REGION=" ./.env.production.tmp > ./.env.production || true - grep -v "^ENCRYPTION_SECRET=" ./.env.production.tmp > ./.env.production || true - rm -f ./.env.production.tmp + grep -Ev "^(DB_HOST|REDIS_URL|S3_ATTACHMENTS_BUCKET|AIRBRAKE_ID|AIRBRAKE_KEY|NEWRELIC_LICENSE_KEY|NEWRELIC_APP_NAME|NEWRELIC_MONITOR_MODE|CF_URL|CF_KEY_PAIR_ID|CF_KEY_SECRET|SECURED_STORAGE_BUCKET|SECURED_STORAGE_REGION|ENCRYPTION_SECRET|CAREERPLUG_WEBHOOK_SECRET|CAREERPLUG_WEBHOOK_URL)=" ./.env.production > ./.env.production.tmp || true + mv ./.env.production.tmp ./.env.production fi # Append the new credentials @@ -234,6 +229,18 @@ fetch_env_variables() { echo "✓ ENCRYPTION_SECRET written to .env.production" fi + # Add CareerPlug webhook secret if it exists + if [ -n "$CAREERPLUG_WEBHOOK_SECRET" ]; then + echo "CAREERPLUG_WEBHOOK_SECRET=$CAREERPLUG_WEBHOOK_SECRET" >> ./.env.production + echo "✓ CAREERPLUG_WEBHOOK_SECRET written to .env.production" + fi + + # Add CareerPlug webhook URL if it exists + if [ -n "$CAREERPLUG_WEBHOOK_URL" ]; then + echo "CAREERPLUG_WEBHOOK_URL=$CAREERPLUG_WEBHOOK_URL" >> ./.env.production + echo "✓ CAREERPLUG_WEBHOOK_URL written to .env.production" + fi + echo "✓ Environment variables successfully retrieved and written to .env.production" } diff --git a/bin/start_staging b/bin/start_staging index 1c8512b4f..f41f19e07 100755 --- a/bin/start_staging +++ b/bin/start_staging @@ -174,6 +174,8 @@ fetch_env_variables() { export SECURED_STORAGE_BUCKET=$(echo "$SECRET_JSON" | jq -r '.secured_storage_bucket') export SECURED_STORAGE_REGION=$(echo "$SECRET_JSON" | jq -r '.secured_storage_region') export ENCRYPTION_SECRET=$(echo "$SECRET_JSON" | jq -r '.ENCRYPTION_SECRET // empty') + export CAREERPLUG_WEBHOOK_SECRET=$(echo "$SECRET_JSON" | jq -r '.CAREERPLUG_WEBHOOK_SECRET // empty') + export CAREERPLUG_WEBHOOK_URL=$(echo "$SECRET_JSON" | jq -r '.CAREERPLUG_WEBHOOK_URL // empty') # Validate that we got the values @@ -198,24 +200,11 @@ fetch_env_variables() { # Write variables to .env.staging file echo "Writing environment variables to .env.staging..." - # Remove existing DB_HOST, REDIS_URL, and S3_ATTACHMENTS_BUCKET lines if they exist + # Remove existing environment variables if they exist if [ -f "./.env.staging" ]; then echo "Removing existing variables from .env.staging" - grep -v "^DB_HOST=" ./.env.staging > ./.env.staging.tmp || true - grep -v "^REDIS_URL=" ./.env.staging.tmp > ./.env.staging || true - grep -v "^S3_ATTACHMENTS_BUCKET=" ./.env.staging.tmp > ./.env.staging || true - grep -v "^AIRBRAKE_ID=" ./.env.staging.tmp > ./.env.staging || true - grep -v "^AIRBRAKE_KEY=" ./.env.staging.tmp > ./.env.staging || true - grep -v "^NEWRELIC_LICENSE_KEY=" ./.env.staging.tmp > ./.env.staging || true - grep -v "^NEWRELIC_APP_NAME=" ./.env.staging.tmp > ./.env.staging || true - grep -v "^NEWRELIC_MONITOR_MODE=" ./.env.staging.tmp > ./.env.staging || true - grep -v "^CF_URL=" ./.env.staging.tmp > ./.env.staging || true - grep -v "^CF_KEY_PAIR_ID=" ./.env.staging.tmp > ./.env.staging || true - grep -v "^CF_KEY_SECRET=" ./.env.staging.tmp > ./.env.staging || true - grep -v "^SECURED_STORAGE_BUCKET=" ./.env.staging.tmp > ./.env.staging || true - grep -v "^SECURED_STORAGE_REGION=" ./.env.staging.tmp > ./.env.staging || true - grep -v "^ENCRYPTION_SECRET=" ./.env.staging.tmp > ./.env.staging || true - rm -f ./.env.staging.tmp + grep -Ev "^(DB_HOST|REDIS_URL|S3_ATTACHMENTS_BUCKET|AIRBRAKE_ID|AIRBRAKE_KEY|NEWRELIC_LICENSE_KEY|NEWRELIC_APP_NAME|NEWRELIC_MONITOR_MODE|CF_URL|CF_KEY_PAIR_ID|CF_KEY_SECRET|SECURED_STORAGE_BUCKET|SECURED_STORAGE_REGION|ENCRYPTION_SECRET|CAREERPLUG_WEBHOOK_SECRET|CAREERPLUG_WEBHOOK_URL)=" ./.env.staging > ./.env.staging.tmp || true + mv ./.env.staging.tmp ./.env.staging fi # Append the new credentials @@ -239,6 +228,18 @@ fetch_env_variables() { echo "✓ ENCRYPTION_SECRET written to .env.staging" fi + # Add CareerPlug webhook secret if it exists + if [ -n "$CAREERPLUG_WEBHOOK_SECRET" ]; then + echo "CAREERPLUG_WEBHOOK_SECRET=$CAREERPLUG_WEBHOOK_SECRET" >> ./.env.staging + echo "✓ CAREERPLUG_WEBHOOK_SECRET written to .env.staging" + fi + + # Add CareerPlug webhook URL if it exists + if [ -n "$CAREERPLUG_WEBHOOK_URL" ]; then + echo "CAREERPLUG_WEBHOOK_URL=$CAREERPLUG_WEBHOOK_URL" >> ./.env.staging + echo "✓ CAREERPLUG_WEBHOOK_URL written to .env.staging" + fi + echo "✓ Environment variables successfully retrieved and written to .env.staging" } diff --git a/config/dotenv.rb b/config/dotenv.rb index 110119c00..ee98368c2 100644 --- a/config/dotenv.rb +++ b/config/dotenv.rb @@ -1,5 +1,10 @@ # frozen_string_literal: true +if ENV['RAILS_ENV'] == 'development' + require 'dotenv' + Dotenv.load('.env') +end + if ENV['RAILS_ENV'] == 'production' || ENV['RAILS_ENV'] == 'staging' if !ENV['AWS_SECRET_MANAGER_ID'].to_s.empty? require 'aws-sdk-secretsmanager' diff --git a/lib/tasks/webhooks.rake b/lib/tasks/webhooks.rake new file mode 100644 index 000000000..cd892c335 --- /dev/null +++ b/lib/tasks/webhooks.rake @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +namespace :webhooks do + desc 'Configure CareerPlug webhook secret from CAREERPLUG_WEBHOOK_SECRET env var' + task configure_careerplug: :environment do + secret = ENV.fetch('CAREERPLUG_WEBHOOK_SECRET') do + if Rails.env.development? + 'development_webhook_secret' + else + abort 'CAREERPLUG_WEBHOOK_SECRET environment variable is required' + end + end + + webhook_urls = WebhookUrl.where('url LIKE ? OR url LIKE ? OR url LIKE ?', + '%careerplug%', '%cpats%', '%localhost:3000%') + + if webhook_urls.any? + webhook_urls.find_each do |webhook_url| + webhook_url.update!(secret: { 'X-CareerPlug-Secret' => secret }) + puts "Updated webhook secret for #{webhook_url.url}" + end + puts "Updated #{webhook_urls.count} webhook URL(s)" + else + puts 'No CareerPlug webhook URLs found. Available webhooks:' + WebhookUrl.find_each { |w| puts " - #{w.id}: #{w.url}" } + end + end + + desc 'Set up development webhook URLs for all accounts (creates URLs + configures secret)' + task setup_development: :environment do + abort 'This task is only for development' unless Rails.env.development? + + url = 'http://localhost:3000/api/docuseal/events' + secret = { 'X-CareerPlug-Secret' => 'development_webhook_secret' } + events = %w[form.viewed form.started form.completed form.declined] + + created = 0 + updated = 0 + + Account.find_each do |account| + webhook_url = WebhookUrl.find_or_initialize_by(account: account, sha1: Digest::SHA1.hexdigest(url)) + + if webhook_url.new_record? + webhook_url.assign_attributes(url: url, events: events, secret: secret) + webhook_url.save! + created += 1 + puts "Created webhook URL for account #{account.id}: #{account.name}" + elsif webhook_url.secret != secret + webhook_url.update!(secret: secret) + updated += 1 + puts "Updated webhook secret for account #{account.id}: #{account.name}" + end + end + + puts "Done: #{created} created, #{updated} updated" + end +end diff --git a/spec/models/account_create_careerplug_webhook_spec.rb b/spec/models/account_create_careerplug_webhook_spec.rb new file mode 100644 index 000000000..140a9792a --- /dev/null +++ b/spec/models/account_create_careerplug_webhook_spec.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Account, '#create_careerplug_webhook' do + around do |example| + original_secret = ENV.fetch('CAREERPLUG_WEBHOOK_SECRET', nil) + original_url = ENV.fetch('CAREERPLUG_WEBHOOK_URL', nil) + + # Set required env vars for webhook creation + ENV['CAREERPLUG_WEBHOOK_SECRET'] = 'test_secret' + ENV['CAREERPLUG_WEBHOOK_URL'] = 'http://example.com/webhook' + + example.run + + # Restore original env vars + ENV['CAREERPLUG_WEBHOOK_SECRET'] = original_secret + ENV['CAREERPLUG_WEBHOOK_URL'] = original_url + end + + describe 'CareerPlug webhook creation' do + it 'creates webhook after successful account creation' do + account = build(:account) + expect(account.webhook_urls).to be_empty + + account.save! + + expect(account.webhook_urls.count).to eq(1) + webhook = account.webhook_urls.first + expect(webhook.url).to eq('http://example.com/webhook') + expect(webhook.events).to eq(['form.viewed', 'form.started', 'form.completed', 'form.declined']) + expect(webhook.secret).to eq({ 'X-CareerPlug-Secret' => 'test_secret' }) + end + + it 'does not create webhook if account creation fails' do + # This test verifies that after_commit behavior works correctly + # by simulating a transaction rollback + + expect do + described_class.transaction do + create(:account) + # Simulate some error that would cause rollback + raise ActiveRecord::Rollback + end + end.not_to change(described_class, :count) + + expect do + described_class.transaction do + create(:account) + raise ActiveRecord::Rollback + end + end.not_to change(WebhookUrl, :count) + end + + it 'does not create webhook when CAREERPLUG_WEBHOOK_SECRET is blank' do + original_secret = ENV.fetch('CAREERPLUG_WEBHOOK_SECRET', nil) + ENV['CAREERPLUG_WEBHOOK_SECRET'] = '' + + account = create(:account) + expect(account.webhook_urls.count).to eq(0) + + ENV['CAREERPLUG_WEBHOOK_SECRET'] = original_secret + end + end +end