diff --git a/app/controllers/submissions_controller.rb b/app/controllers/submissions_controller.rb
index 389e9591..3b663bad 100644
--- a/app/controllers/submissions_controller.rb
+++ b/app/controllers/submissions_controller.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
class SubmissionsController < ApplicationController
+ include PrefillFieldsHelper
+
skip_before_action :verify_authenticity_token
before_action :load_template, only: %i[new create]
authorize_resource :template, only: %i[new create]
@@ -15,6 +17,10 @@ class SubmissionsController < ApplicationController
def show
@submission = Submissions.preload_with_pages(@submission)
+ @available_ats_fields = extract_ats_prefill_fields
+
+ # Optional: store in session for persistence across requests
+ session[:ats_prefill_fields] = @available_ats_fields if @available_ats_fields.any?
unless @submission.submitters.all?(&:completed_at?)
ActiveRecord::Associations::Preloader.new(
@@ -91,6 +97,7 @@ class SubmissionsController < ApplicationController
private
+
def save_template_message(template, params)
template.preferences['request_email_subject'] = params[:subject] if params[:subject].present?
template.preferences['request_email_body'] = params[:body] if params[:body].present?
diff --git a/app/controllers/templates_controller.rb b/app/controllers/templates_controller.rb
index 2828ce58..2c77ed73 100644
--- a/app/controllers/templates_controller.rb
+++ b/app/controllers/templates_controller.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
class TemplatesController < ApplicationController
+ include PrefillFieldsHelper
+
skip_before_action :maybe_redirect_to_setup
skip_before_action :verify_authenticity_token
@@ -39,6 +41,9 @@ class TemplatesController < ApplicationController
associations: [schema_documents: [:blob, { preview_images_attachments: :blob }]]
).call
+ # Process ATS fields for template editing
+ @available_ats_fields = extract_ats_prefill_fields
+
@template_data =
@template.as_json.merge(
documents: @template.schema_documents.as_json(
diff --git a/app/helpers/prefill_fields_helper.rb b/app/helpers/prefill_fields_helper.rb
new file mode 100644
index 00000000..f5ba22be
--- /dev/null
+++ b/app/helpers/prefill_fields_helper.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module PrefillFieldsHelper
+ def extract_ats_prefill_fields
+ return [] if params[:ats_fields].blank?
+
+ begin
+ decoded_json = Base64.urlsafe_decode64(params[:ats_fields])
+ field_names = JSON.parse(decoded_json)
+
+ # Validate that we got an array of strings
+ return [] unless field_names.is_a?(Array) && field_names.all?(String)
+
+ # Filter to only expected field name patterns
+ valid_fields = field_names.select { |name| valid_ats_field_name?(name) }
+
+ # Log successful field reception
+ Rails.logger.info "Received #{valid_fields.length} ATS prefill fields: #{valid_fields.join(', ')}"
+
+ valid_fields
+ rescue StandardError => e
+ Rails.logger.warn "Failed to parse ATS prefill fields: #{e.message}"
+ []
+ end
+ end
+
+ private
+
+ def valid_ats_field_name?(name)
+ # Only allow expected field name patterns (security)
+ name.match?(/\A(employee|manager|account|location)_[a-z_]+\z/)
+ end
+end
diff --git a/app/javascript/template_builder/builder.vue b/app/javascript/template_builder/builder.vue
index d60d9e5d..b05f1d67 100644
--- a/app/javascript/template_builder/builder.vue
+++ b/app/javascript/template_builder/builder.vue
@@ -55,7 +55,6 @@
id="title_container"
class="flex justify-between py-1.5 items-center pr-4 top-0 z-10 title-container"
:class="{ sticky: withStickySubmitters || isBreakpointLg }"
- :style="{ backgroundColor }"
>
diff --git a/spec/helpers/prefill_fields_helper_spec.rb b/spec/helpers/prefill_fields_helper_spec.rb
new file mode 100644
index 00000000..9cd00f9e
--- /dev/null
+++ b/spec/helpers/prefill_fields_helper_spec.rb
@@ -0,0 +1,153 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe PrefillFieldsHelper, type: :helper do
+ describe '#extract_ats_prefill_fields' do
+ it 'extracts valid field names from base64 encoded parameter' do
+ fields = %w[employee_first_name employee_email manager_firstname]
+ encoded = Base64.urlsafe_encode64(fields.to_json)
+
+ allow(helper).to receive(:params).and_return({ ats_fields: encoded })
+
+ result = helper.extract_ats_prefill_fields
+ expect(result).to eq(fields)
+ end
+
+ it 'returns empty array for invalid base64' do
+ allow(helper).to receive(:params).and_return({ ats_fields: 'invalid_base64' })
+
+ result = helper.extract_ats_prefill_fields
+ expect(result).to eq([])
+ end
+
+ it 'returns empty array for invalid JSON' do
+ invalid_json = Base64.urlsafe_encode64('invalid json')
+ allow(helper).to receive(:params).and_return({ ats_fields: invalid_json })
+
+ result = helper.extract_ats_prefill_fields
+ expect(result).to eq([])
+ end
+
+ it 'filters out invalid field names' do
+ fields = %w[employee_first_name malicious_field account_name invalid-field]
+ encoded = Base64.urlsafe_encode64(fields.to_json)
+
+ allow(helper).to receive(:params).and_return({ ats_fields: encoded })
+
+ result = helper.extract_ats_prefill_fields
+ expect(result).to eq(%w[employee_first_name account_name])
+ end
+
+ it 'returns empty array when no ats_fields parameter' do
+ allow(helper).to receive(:params).and_return({})
+
+ result = helper.extract_ats_prefill_fields
+ expect(result).to eq([])
+ end
+
+ it 'returns empty array when ats_fields parameter is empty' do
+ allow(helper).to receive(:params).and_return({ ats_fields: '' })
+
+ result = helper.extract_ats_prefill_fields
+ expect(result).to eq([])
+ end
+
+ it 'returns empty array when decoded JSON is not an array' do
+ not_array = Base64.urlsafe_encode64({ field: 'employee_name' }.to_json)
+ allow(helper).to receive(:params).and_return({ ats_fields: not_array })
+
+ result = helper.extract_ats_prefill_fields
+ expect(result).to eq([])
+ end
+
+ it 'returns empty array when array contains non-string values' do
+ mixed_array = ['employee_first_name', 123, 'manager_firstname']
+ encoded = Base64.urlsafe_encode64(mixed_array.to_json)
+
+ allow(helper).to receive(:params).and_return({ ats_fields: encoded })
+
+ result = helper.extract_ats_prefill_fields
+ expect(result).to eq([])
+ end
+
+ it 'accepts all valid field name patterns' do
+ fields = %w[
+ employee_first_name
+ employee_middle_name
+ employee_last_name
+ employee_email
+ manager_firstname
+ manager_lastname
+ account_name
+ location_name
+ location_street
+ ]
+ encoded = Base64.urlsafe_encode64(fields.to_json)
+
+ allow(helper).to receive(:params).and_return({ ats_fields: encoded })
+
+ result = helper.extract_ats_prefill_fields
+ expect(result).to eq(fields)
+ end
+
+ it 'logs successful field reception' do
+ fields = %w[employee_first_name employee_email]
+ encoded = Base64.urlsafe_encode64(fields.to_json)
+
+ allow(helper).to receive(:params).and_return({ ats_fields: encoded })
+ allow(Rails.logger).to receive(:info)
+
+ helper.extract_ats_prefill_fields
+
+ expect(Rails.logger).to have_received(:info).with(
+ 'Received 2 ATS prefill fields: employee_first_name, employee_email'
+ )
+ end
+
+ it 'logs parsing errors' do
+ allow(helper).to receive(:params).and_return({ ats_fields: 'invalid_base64' })
+ allow(Rails.logger).to receive(:warn)
+
+ helper.extract_ats_prefill_fields
+
+ expect(Rails.logger).to have_received(:warn).with(
+ a_string_matching(/Failed to parse ATS prefill fields:/)
+ )
+ end
+ end
+
+ describe '#valid_ats_field_name?' do
+ it 'returns true for valid employee field names' do
+ expect(helper.send(:valid_ats_field_name?, 'employee_first_name')).to be true
+ expect(helper.send(:valid_ats_field_name?, 'employee_email')).to be true
+ expect(helper.send(:valid_ats_field_name?, 'employee_phone_number')).to be true
+ end
+
+ it 'returns true for valid manager field names' do
+ expect(helper.send(:valid_ats_field_name?, 'manager_firstname')).to be true
+ expect(helper.send(:valid_ats_field_name?, 'manager_lastname')).to be true
+ expect(helper.send(:valid_ats_field_name?, 'manager_email')).to be true
+ end
+
+ it 'returns true for valid account field names' do
+ expect(helper.send(:valid_ats_field_name?, 'account_name')).to be true
+ expect(helper.send(:valid_ats_field_name?, 'account_id')).to be true
+ end
+
+ it 'returns true for valid location field names' do
+ expect(helper.send(:valid_ats_field_name?, 'location_name')).to be true
+ expect(helper.send(:valid_ats_field_name?, 'location_street')).to be true
+ expect(helper.send(:valid_ats_field_name?, 'location_city')).to be true
+ end
+
+ it 'returns false for invalid field names' do
+ expect(helper.send(:valid_ats_field_name?, 'malicious_field')).to be false
+ expect(helper.send(:valid_ats_field_name?, 'invalid-field')).to be false
+ expect(helper.send(:valid_ats_field_name?, 'EMPLOYEE_NAME')).to be false
+ expect(helper.send(:valid_ats_field_name?, 'employee')).to be false
+ expect(helper.send(:valid_ats_field_name?, 'employee_')).to be false
+ expect(helper.send(:valid_ats_field_name?, '_employee_name')).to be false
+ end
+ end
+end