Compare commits

..

No commits in common. '33ca930055e35bc8c017bd2d1217a055a8e43a82' and 'a389e8cc38fafabd23425603d7ffb14c52844bd1' have entirely different histories.

@ -27,37 +27,29 @@ class SubmissionsDownloadController < ApplicationController
Submissions::EnsureResultGenerated.call(last_submitter)
if !signature_valid && !current_user_submitter?(last_submitter)
return head :not_found unless Submitters::AuthorizedForForm.call(@submitter, current_user, request)
if last_submitter.completed_at < TTL.ago
if last_submitter.completed_at < TTL.ago && !signature_valid && !current_user_submitter?(last_submitter)
Rollbar.info("TTL: #{last_submitter.id}") if defined?(Rollbar)
return head :not_found
end
end
if params[:combined] == 'true'
respond_with_combined(last_submitter)
else
render json: build_urls(last_submitter)
end
end
private
def respond_with_combined(submitter)
url = build_combined_url(submitter)
url = build_combined_url(@submitter)
if url
render json: [url]
else
head :not_found
end
else
render json: build_urls(last_submitter)
end
end
private
def current_user_submitter?(submitter)
current_user && current_ability.can?(:read, submitter)
current_user && current_user.account.submitters.exists?(id: submitter.id)
end
def build_urls(submitter)

@ -9,15 +9,15 @@ class SubmitFormController < ApplicationController
before_action :load_submitter, only: %i[show update completed]
before_action :maybe_render_locked_page, only: :show
before_action :maybe_require_link_2fa, only: %i[show]
before_action :maybe_require_link_2fa, only: %i[show update]
CONFIG_KEYS = [].freeze
def show
submission = @submitter.submission
return render :email_2fa unless Submitters::AuthorizedForForm.pass_email_2fa?(@submitter, request)
return redirect_to submit_form_completed_path(@submitter.slug) if @submitter.completed_at?
return render :email_2fa if require_email_2fa?(@submitter)
@form_configs = Submitters::FormConfigs.call(@submitter, CONFIG_KEYS)
@ -48,7 +48,7 @@ class SubmitFormController < ApplicationController
end
def update
unless Submitters::AuthorizedForForm.call(@submitter, current_user, request)
if require_email_2fa?(@submitter)
return render json: { error: I18n.t('verification_required_refresh_the_page_and_pass_2fa') },
status: :unprocessable_content
end
@ -84,9 +84,7 @@ class SubmitFormController < ApplicationController
def completed
raise ActionController::RoutingError, I18n.t('not_found') if @submitter.account.archived_at?
return if Submitters::AuthorizedForForm.call(@submitter, current_user, request)
redirect_to submit_form_path(params[:submit_form_slug])
redirect_to submit_form_path(params[:submit_form_slug]) if require_email_2fa?(@submitter)
end
def success; end
@ -94,7 +92,10 @@ class SubmitFormController < ApplicationController
private
def maybe_require_link_2fa
return if Submitters::AuthorizedForForm.pass_link_2fa?(@submitter, current_user, request)
return if @submitter.submission.source != 'link'
return unless @submitter.submission.template&.preferences&.dig('shared_link_2fa') == true
return if cookies.encrypted[:email_2fa_slug] == @submitter.slug
return if @submitter.email == current_user&.email && current_user&.account_id == @submitter.account_id
redirect_to start_form_path(@submitter.submission.template.slug)
end
@ -116,4 +117,12 @@ class SubmitFormController < ApplicationController
ActiveStorage::Attachment.where(record: submission.submitters, name: :attachments)
.preload(:blob).index_by(&:uuid)
end
def require_email_2fa?(submitter)
return false if submitter.submission.template&.preferences&.dig('require_email_2fa') != true &&
submitter.preferences['require_email_2fa'] != true
return false if cookies.encrypted[:email_2fa_slug] == submitter.slug
true
end
end

@ -11,9 +11,7 @@ class SubmitFormDeclineController < ApplicationController
submitter.completed_at? ||
submitter.submission.archived_at? ||
submitter.submission.expired? ||
submitter.submission.template&.archived_at? ||
!Submitters::AuthorizedForForm.call(submitter, current_user,
request)
submitter.submission.template&.archived_at?
ApplicationRecord.transaction do
submitter.update!(declined_at: Time.current)

@ -17,8 +17,7 @@ class SubmitFormDownloadController < ApplicationController
@submitter.submission.template&.archived_at? ||
AccountConfig.exists?(account_id: @submitter.account_id,
key: AccountConfig::ALLOW_TO_PARTIAL_DOWNLOAD_KEY,
value: false) ||
!Submitters::AuthorizedForForm.call(@submitter, current_user, request)
value: false)
last_completed_submitter = @submitter.submission.submitters
.where.not(id: @submitter.id)

@ -12,8 +12,7 @@ class SubmitFormDrawSignatureController < ApplicationController
return redirect_to submit_form_completed_path(@submitter.slug) if @submitter.completed_at?
if @submitter.submission.template&.archived_at? || @submitter.submission.archived_at? ||
!Submitters::AuthorizedForForm.call(@submitter, current_user, request)
if @submitter.submission.template&.archived_at? || @submitter.submission.archived_at?
return redirect_to submit_form_path(@submitter.slug)
end

@ -45,8 +45,7 @@ class SubmitFormInviteController < ApplicationController
!submitter.completed_at? &&
!submitter.submission.archived_at? &&
!submitter.submission.expired? &&
!submitter.submission.template&.archived_at? &&
Submitters::AuthorizedForForm.call(submitter, current_user, request)
!submitter.submission.template&.archived_at?
end
def filter_invite_submitters(submitter, key = 'invite_by_uuid')

@ -7,12 +7,10 @@ class SubmitFormValuesController < ApplicationController
def index
submitter = Submitter.find_by!(slug: params[:submit_form_slug])
return render json: {} if submitter.completed_at? ||
submitter.declined_at? ||
submitter.submission.template&.archived_at? ||
return render json: {} if submitter.completed_at? || submitter.declined_at?
return render json: {} if submitter.submission.template&.archived_at? ||
submitter.submission.archived_at? ||
submitter.submission.expired? ||
!Submitters::AuthorizedForForm.call(submitter, current_user, request)
submitter.submission.expired?
value = submitter.values[params['field_uuid']]
attachment = submitter.attachments.where(created_at: params[:after]..).find_by(uuid: value) if value.present?

@ -56,7 +56,7 @@ class TemplatesUploadsController < ApplicationController
def create_file_params_from_url
tempfile = Tempfile.new
tempfile.binmode
tempfile.write(DownloadUtils.call(params[:url], validate: true).body)
tempfile.write(DownloadUtils.call(params[:url]).body)
tempfile.rewind
filename = URI.decode_www_form_component(params[:filename]) if params[:filename].present?

@ -161,11 +161,6 @@ export default {
required: false,
default: false
},
fetchOptions: {
type: Object,
required: false,
default: () => ({})
},
completedButton: {
type: Object,
required: false,
@ -219,10 +214,7 @@ export default {
download () {
this.isDownloading = true
fetch(this.baseUrl + `/submitters/${this.submitterSlug}/download`, {
method: 'GET',
...this.fetchOptions
}).then(async (response) => {
fetch(this.baseUrl + `/submitters/${this.submitterSlug}/download`).then(async (response) => {
if (response.ok) {
const urls = await response.json()
const isMobileSafariIos = 'ontouchstart' in window && navigator.maxTouchPoints > 0 && /AppleWebKit/i.test(navigator.userAgent)

@ -530,7 +530,6 @@
v-else-if="isInvite"
:submitters="inviteSubmitters"
:optional-submitters="optionalInviteSubmitters"
:fetch-options="fetchOptions"
:submitter-slug="submitterSlug"
:authenticity-token="authenticityToken"
:url="baseUrl + submitPath + '/invite'"
@ -544,7 +543,6 @@
:has-signature-fields="stepFields.some((fields) => fields.some((f) => ['signature', 'initials'].includes(f.type)))"
:has-multiple-documents="hasMultipleDocuments"
:completed-button="completedRedirectUrl ? {} : completedButton"
:fetch-options="fetchOptions"
:completed-message="completedRedirectUrl ? {} : completedMessage"
:with-send-copy-button="withSendCopyButton && !completedRedirectUrl"
:with-download-button="withDownloadButton && !completedRedirectUrl && !dryRun"
@ -680,11 +678,6 @@ export default {
required: false,
default: () => []
},
fetchOptions: {
type: Object,
required: false,
default: () => ({})
},
optionalInviteSubmitters: {
type: Array,
required: false,
@ -1474,8 +1467,7 @@ export default {
} else {
return fetch(this.baseUrl + this.submitPath, {
method: 'POST',
body: formData || new FormData(this.$refs.form),
...this.fetchOptions
body: formData || new FormData(this.$refs.form)
}).then((response) => {
if (response.status === 200) {
currentFieldUuids.forEach((fieldUuid) => {

@ -78,11 +78,6 @@ export default {
type: Array,
required: true
},
fetchOptions: {
type: Object,
required: false,
default: () => ({})
},
optionalSubmitters: {
type: Array,
required: false,
@ -113,8 +108,7 @@ export default {
return fetch(this.url, {
method: 'POST',
body: new FormData(this.$refs.form),
...this.fetchOptions
body: new FormData(this.$refs.form)
}).then((response) => {
if (response.status === 200) {
this.$emit('success')

@ -261,7 +261,7 @@ export default {
},
computed: {
isSelectMode () {
return this.isSelectModeRef.value && !this.drawFieldType && this.editable && !this.drawField && !this.drawCustomField
return this.isSelectModeRef.value && !this.drawFieldType && this.editable && !this.drawField
},
pageSelectedAreas () {
if (!this.selectedAreasRef.value) return []

@ -26,7 +26,7 @@ class SendTestWebhookRequestJob
Addressable::URI.parse(webhook_url.url).normalize
end
raise HttpsError, 'Only HTTPS is allowed.' if uri.scheme != 'https' || [443, nil].exclude?(uri.port)
raise HttpsError, 'Only HTTPS is allowed.' if uri.scheme != 'https'
raise LocalhostError, "Can't send to localhost." if uri.host.in?(SendWebhookRequest::LOCALHOSTS)
end

@ -22,7 +22,7 @@
</div>
<div class="form-control">
<%= ff.label :password, 'Password', class: 'label' %>
<%= ff.password_field :password, class: 'base-input', required: value['password'].present?, placeholder: value['password'].present? ? '*************' : '' %>
<%= ff.password_field :password, value: value['password'], class: 'base-input' %>
</div>
</div>
<div class="grid md:grid-cols-2 gap-4">

@ -22,7 +22,7 @@
<% if params[:modal].present? %>
<% url_params = Rails.application.routes.recognize_path(params[:modal], method: :get) %>
<% if url_params[:action] == 'new' %>
<open-modal src="<%= url_for(url_params) %>"></open-modal>
<open-modal src="<%= params[:modal] %>"></open-modal>
<% end %>
<% end %>
<turbo-frame id="modal"></turbo-frame>

@ -4,7 +4,7 @@
"id": "/",
"icons": [
{
"src": "/favicon.svg",
"src": "/logo.svg",
"type": "image/svg+xml",
"sizes": "any"
},

@ -27,5 +27,4 @@
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="96x96" href="/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<meta name="theme-color" content="#faf7f5">

@ -6,7 +6,7 @@
<% end %>
<% if params[:q].present? %>
<div class="relative">
<a href="<%= url_for(params: request.query_parameters.except('q')) %>" title="<%= t('clear') %>" class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-auto text-neutral text-2xl font-extralight">
<a href="<%= url_for(params.to_unsafe_h.except(:q)) %>" title="<%= t('clear') %>" class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-auto text-neutral text-2xl font-extralight">
&times;
</a>
</div>

@ -8,7 +8,7 @@
</div>
<div class="form-control">
<%= fff.label :secret_access_key, class: 'label' %>
<%= fff.password_field :secret_access_key, required: true, class: 'base-input', placeholder: configs['secret_access_key'].present? ? '*************' : '' %>
<%= fff.password_field :secret_access_key, value: configs['secret_access_key'], required: true, class: 'base-input' %>
</div>
</div>
<div class="grid md:grid-cols-2 gap-4">

@ -13,7 +13,7 @@
</div>
<div class="form-control">
<%= fff.label :storage_access_key, 'Storage Access Key', class: 'label' %>
<%= fff.password_field :storage_access_key, required: true, class: 'base-input', placeholder: configs['storage_access_key'].present? ? '*************' : '' %>
<%= fff.password_field :storage_access_key, value: configs['storage_access_key'], required: true, class: 'base-input' %>
</div>
<% end %>
<% end %>

@ -13,7 +13,7 @@
</div>
<div class="form-control">
<%= fff.label :credentials, 'Credentials (JSON key content)', class: 'label' %>
<%= fff.text_area :credentials, required: true, class: 'base-textarea w-full font-mono', rows: 4, placeholder: configs['credentials'].present? ? "{\n**REDACTED**\n}" : '' %>
<%= fff.text_area :credentials, value: configs['credentials'], required: true, class: 'base-textarea w-full font-mono', rows: 4 %>
</div>
<% end %>
<% end %>

@ -68,7 +68,7 @@
<% end %>
</div>
<% end %>
<% elsif @submission.submitters.to_a.size == 1 && !@submission.expired? && !@submission.submitters.to_a.first.declined_at? && !@submission.archived_at? && !@submission.template&.archived_at? %>
<% elsif @submission.submitters.to_a.size == 1 && !@submission.expired? && !@submission.submitters.to_a.first.declined_at? && !@submission.archived_at? %>
<%= render 'shared/clipboard_copy', text: submit_form_url(slug: @submission.submitters.to_a.first.slug, host: form_link_host), class: 'base-button', icon_class: 'w-6 h-6 text-white', copy_title: t('copy_share_link'), copied_title: t('copied_to_clipboard') %>
<% end %>
</div>
@ -159,7 +159,7 @@
<%= (@submission.template_submitters || @submission.template.submitters).find { |e| e['uuid'] == submitter&.uuid }&.dig('name') || "#{(index + 1).ordinalize} Submitter" %>
</span>
</div>
<% if signed_in? && can?(:update, @submission) && submitter && !submitter.completed_at? && !submitter.declined_at? && !@submission.archived_at? && !@submission.template&.archived_at? && !@submission.expired? && !submitter.start_form_submission_events.any? %>
<% if signed_in? && can?(:update, @submission) && submitter && !submitter.completed_at? && !submitter.declined_at? && !@submission.archived_at? && !@submission.expired? && !submitter.start_form_submission_events.any? %>
<span class="tooltip tooltip-left" data-tip="<%= t('edit') %>">
<%= link_to edit_submitter_path(submitter), class: 'shrink-0 inline md:hidden md:group-hover:inline', data: { turbo_frame: 'modal' } do %>
<%= svg_icon('pencil', class: 'w-5 h-5') %>
@ -225,15 +225,15 @@
</span>
</div>
<% end %>
<% if signed_in? && submitter && submitter.email && !submitter.completed_at && !@submission.archived_at? && !@submission.template&.archived_at? && can?(:update, @submission) && Accounts.can_send_emails?(current_account) && !@submission.expired? && !submitter.declined_at? %>
<% if signed_in? && submitter && submitter.email && !submitter.completed_at && !@submission.archived_at? && can?(:update, @submission) && Accounts.can_send_emails?(current_account) && !@submission.expired? && !submitter.declined_at? %>
<div class="mt-2 mb-1">
<%= button_to button_title(title: submitter.sent_at? ? t('re_send_email') : t('send_email'), disabled_with: t('sending')), submitter_send_email_index_path(submitter_slug: submitter.slug), class: 'btn btn-sm btn-primary w-full' %>
</div>
<% end %>
<% if signed_in? && submitter && submitter.phone && !submitter.completed_at && !@submission.archived_at? && !@submission.template&.archived_at? && can?(:update, @submission) && !@submission.expired? && !submitter.declined_at? %>
<% if signed_in? && submitter && submitter.phone && !submitter.completed_at && !@submission.archived_at? && can?(:update, @submission) && !@submission.expired? && !submitter.declined_at? %>
<%= render 'submissions/send_sms_button', submitter: %>
<% end %>
<% if signed_in? && submitter && !submitter.completed_at? && !@submission.archived_at? && !@submission.template&.archived_at? && can?(:create, @submission) && !@submission.expired? && !submitter.declined_at? %>
<% if signed_in? && submitter && !submitter.completed_at? && !@submission.archived_at? && can?(:create, @submission) && !@submission.expired? && !submitter.declined_at? %>
<div class="mt-2 mb-1">
<a class="btn btn-sm btn-primary w-full" target="_blank" href="<%= submit_form_path(slug: submitter.slug) %>">
<%= t('sign_in_person') %>

@ -35,19 +35,19 @@
<% if is_show_tabs %>
<div class="flex items-center flex-col md:flex-row md:flex-wrap gap-2 mb-4">
<div class="flex items-center md:items-end flex-col md:flex-row gap-2 w-full md:w-fit">
<a href="<%= url_for(params: request.query_parameters.except('status')) %>" class="<%= params[:status].blank? ? 'border-neutral-700' : 'border-neutral-300' %> flex h-10 px-2 py-1 text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl w-full md:w-48 hover:border-neutral-600">
<a href="<%= url_for(params.to_unsafe_h.except(:status)) %>" class="<%= params[:status].blank? ? 'border-neutral-700' : 'border-neutral-300' %> flex h-10 px-2 py-1 text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl w-full md:w-48 hover:border-neutral-600">
<div class="flex items-center space-x-1">
<%= svg_icon('list', class: 'w-5 h-5') %>
<span class="font-normal"><%= t('all') %></span>
</div>
</a>
<a href="<%= url_for(params: request.query_parameters.merge('status' => 'pending')) %>" class="<%= params[:status] == 'pending' ? 'border-neutral-700' : 'border-neutral-300' %> flex h-10 px-2 py-1 text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl w-full md:w-48 hover:border-neutral-600">
<a href="<%= url_for(params.to_unsafe_h.merge(status: :pending)) %>" class="<%= params[:status] == 'pending' ? 'border-neutral-700' : 'border-neutral-300' %> flex h-10 px-2 py-1 text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl w-full md:w-48 hover:border-neutral-600">
<div class="flex items-center space-x-1">
<%= svg_icon('clock', class: 'w-5 h-5') %>
<span class="font-normal"><%= t('pending') %></span>
</div>
</a>
<a href="<%= url_for(params: request.query_parameters.merge('status' => 'completed')) %>" class="<%= params[:status] == 'completed' ? 'border-neutral-700' : 'border-neutral-300' %> flex h-10 px-2 py-1 text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl w-full md:w-48 hover:border-neutral-600">
<a href="<%= url_for(params.to_unsafe_h.merge(status: :completed)) %>" class="<%= params[:status] == 'completed' ? 'border-neutral-700' : 'border-neutral-300' %> flex h-10 px-2 py-1 text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl w-full md:w-48 hover:border-neutral-600">
<div class="flex items-center space-x-1">
<%= svg_icon('circle_check', class: 'w-5 h-5') %>
<span class="font-normal"><%= t('completed') %></span>

@ -5,7 +5,7 @@
<%= svg_icon(icon, class: 'w-5 h-5 shrink-0') %>
<span class="font-normal truncate"><%= t(params[:status]) %></span>
<% end %>
<%= link_to url_for(params: request.query_parameters.except('status')), class: 'rounded-lg ml-1 hover:bg-base-content hover:text-white' do %>
<%= link_to url_for(params.to_unsafe_h.except(:status)), class: 'rounded-lg ml-1 hover:bg-base-content hover:text-white' do %>
<%= svg_icon('x', class: 'w-5 h-5') %>
<% end %>
</div>
@ -16,7 +16,7 @@
<%= svg_icon('folder', class: 'w-5 h-5 shrink-0') %>
<span class="font-normal truncate"><%= params[:folder] %></span>
<% end %>
<%= link_to url_for(params: request.query_parameters.except('folder')), class: 'rounded-lg ml-1 hover:bg-base-content hover:text-white' do %>
<%= link_to url_for(params.to_unsafe_h.except(:folder)), class: 'rounded-lg ml-1 hover:bg-base-content hover:text-white' do %>
<%= svg_icon('x', class: 'w-5 h-5') %>
<% end %>
</div>
@ -27,7 +27,7 @@
<%= svg_icon('user', class: 'w-5 h-5 shrink-0') %>
<span class="font-normal truncate"><%= current_account.users.accessible_by(current_ability).where(account: current_account).find_by(email: params[:author])&.full_name || 'NA' %></span>
<% end %>
<%= link_to url_for(params: request.query_parameters.except('author')), class: 'rounded-lg ml-1 hover:bg-base-content hover:text-white' do %>
<%= link_to url_for(params.to_unsafe_h.except(:author)), class: 'rounded-lg ml-1 hover:bg-base-content hover:text-white' do %>
<%= svg_icon('x', class: 'w-5 h-5') %>
<% end %>
</div>
@ -46,7 +46,7 @@
<% end %>
</span>
<% end %>
<%= link_to url_for(params: request.query_parameters.except('completed_at_from', 'completed_at_to')), class: 'rounded-lg ml-1 hover:bg-base-content hover:text-white' do %>
<%= link_to url_for(params.to_unsafe_h.except(:completed_at_from, :completed_at_to)), class: 'rounded-lg ml-1 hover:bg-base-content hover:text-white' do %>
<%= svg_icon('x', class: 'w-5 h-5') %>
<% end %>
</div>
@ -65,7 +65,7 @@
<% end %>
</span>
<% end %>
<%= link_to url_for(params: request.query_parameters.except('created_at_to', 'created_at_from')), class: 'rounded-lg ml-1 hover:bg-base-content hover:text-white' do %>
<%= link_to url_for(params.to_unsafe_h.except(:created_at_to, :created_at_from)), class: 'rounded-lg ml-1 hover:bg-base-content hover:text-white' do %>
<%= svg_icon('x', class: 'w-5 h-5') %>
<% end %>
</div>

@ -10,7 +10,7 @@
</div>
<% if params[:with_remove] %>
<div class="text-center w-full mt-4">
<%= link_to t('remove_filter'), "#{params[:path]}?#{request.query_parameters.slice('q').merge(local_assigns[:default_params]).to_query}", class: 'link', data: { turbo_frame: :_top } %>
<%= link_to t('remove_filter'), "#{params[:path]}?#{params.to_unsafe_h.slice(:q).merge(local_assigns[:default_params]).to_query}", class: 'link', data: { turbo_frame: :_top } %>
</div>
<% end %>
<% end %>

@ -30,7 +30,7 @@
<% if is_show_tabs %>
<div class="flex items-center flex-col md:flex-row md:flex-wrap gap-2 mb-4">
<div class="flex items-center md:items-end flex-col md:flex-row gap-2 w-full md:w-fit">
<a href="<%= url_for(params: request.query_parameters.except('status')) %>" class="<%= params[:status].blank? ? 'border-neutral-700' : 'border-neutral-300' %> flex h-10 px-2 py-1 text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl w-full md:w-48 hover:border-neutral-700">
<a href="<%= url_for(params.to_unsafe_h.except(:status)) %>" class="<%= params[:status].blank? ? 'border-neutral-700' : 'border-neutral-300' %> flex h-10 px-2 py-1 text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl w-full md:w-48 hover:border-neutral-700">
<div class="flex items-center space-x-1">
<%= svg_icon('list', class: 'w-5 h-5') %>
<span class="font-normal"><%= t('all') %></span>
@ -41,7 +41,7 @@
</div>
<% end %>
</a>
<a href="<%= url_for(params: request.query_parameters.merge('status' => 'pending')) %>" class="<%= params[:status] == 'pending' ? 'border-neutral-700' : 'border-neutral-300' %> flex h-10 px-2 py-1 text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl w-full md:w-48 hover:border-neutral-700">
<a href="<%= url_for(params.to_unsafe_h.merge(status: :pending)) %>" class="<%= params[:status] == 'pending' ? 'border-neutral-700' : 'border-neutral-300' %> flex h-10 px-2 py-1 text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl w-full md:w-48 hover:border-neutral-700">
<div class="flex items-center space-x-1">
<%= svg_icon('clock', class: 'w-5 h-5') %>
<span class="font-normal"><%= t('pending') %></span>
@ -52,7 +52,7 @@
</div>
<% end %>
</a>
<a href="<%= url_for(params: request.query_parameters.merge('status' => 'completed')) %>" class="<%= params[:status] == 'completed' ? 'border-neutral-700' : 'border-neutral-300' %> flex h-10 px-2 py-1 text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl w-full md:w-48 hover:border-neutral-700">
<a href="<%= url_for(params.to_unsafe_h.merge(status: :completed)) %>" class="<%= params[:status] == 'completed' ? 'border-neutral-700' : 'border-neutral-300' %> flex h-10 px-2 py-1 text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl w-full md:w-48 hover:border-neutral-700">
<div class="flex items-center space-x-1">
<%= svg_icon('circle_check', class: 'w-5 h-5') %>
<span class="font-normal"><%= t('completed') %></span>

@ -85,9 +85,9 @@
<div class="mt-6">
<h2 id="log" class="text-3xl font-bold"><%= t('events_log') %></h2>
<div class="tabs border-b mt-4">
<%= link_to t('all'), url_for(params: request.query_parameters.except('status')), style: 'margin-bottom: -1px', class: "tab h-10 text-base #{params[:status].blank? ? 'tab-active tab-bordered' : 'pb-[3px]'}" %>
<%= link_to t('succeeded'), url_for(params: request.query_parameters.merge('status' => 'success')), style: 'margin-bottom: -1px', class: "tab h-10 text-base #{params[:status] == 'success' ? 'tab-active tab-bordered' : 'pb-[3px]'}" %>
<%= link_to t('failed'), url_for(params: request.query_parameters.merge('status' => 'error')), style: 'margin-bottom: -1px', class: "tab h-10 text-base #{params[:status] == 'error' ? 'tab-active tab-bordered' : 'pb-[3px]'}" %>
<%= link_to t('all'), url_for(params.to_unsafe_h.except(:status)), style: 'margin-bottom: -1px', class: "tab h-10 text-base #{params[:status].blank? ? 'tab-active tab-bordered' : 'pb-[3px]'}" %>
<%= link_to t('succeeded'), url_for(params.to_unsafe_h.merge(status: 'success')), style: 'margin-bottom: -1px', class: "tab h-10 text-base #{params[:status] == 'success' ? 'tab-active tab-bordered' : 'pb-[3px]'}" %>
<%= link_to t('failed'), url_for(params.to_unsafe_h.merge(status: 'error')), style: 'margin-bottom: -1px', class: "tab h-10 text-base #{params[:status] == 'error' ? 'tab-active tab-bordered' : 'pb-[3px]'}" %>
</div>
<% if @webhook_events.present? %>
<div class="divide-y divide-base-300 rounded-lg">

@ -56,7 +56,7 @@ Rails.application.routes.draw do
resources :account_custom_fields, only: %i[create]
resources :user_configs, only: %i[create]
resources :encrypted_user_configs, only: %i[destroy]
resources :timestamp_server, only: %i[create] unless Docuseal.multitenant?
resources :timestamp_server, only: %i[create]
resources :dashboard, only: %i[index]
resources :setup, only: %i[index create]
resource :newsletter, only: %i[show update]

@ -35,16 +35,16 @@ module DownloadUtils
module_function
def call(url, validate: Docuseal.multitenant?)
def call(url)
uri = begin
URI(url)
rescue URI::Error
Addressable::URI.parse(url).normalize
end
validate_uri!(uri) if validate
validate_uri!(uri) if Docuseal.multitenant?
resp = conn(validate:).get(uri)
resp = conn.get(uri)
raise UnableToDownload, "Error loading: #{uri}" if resp.status >= 400
@ -52,15 +52,14 @@ module DownloadUtils
end
def validate_uri!(uri)
raise UnableToDownload, "Error loading: #{uri}. Only HTTPS is allowed." if uri.scheme != 'https' ||
[443, nil].exclude?(uri.port)
raise UnableToDownload, "Error loading: #{uri}. Only HTTPS is allowed." if uri.scheme != 'https'
raise UnableToDownload, "Error loading: #{uri}. Can't download from localhost." if uri.host.in?(LOCALHOSTS)
end
def conn(validate: Docuseal.multitenant?)
def conn
Faraday.new do |faraday|
faraday.response :follow_redirects, callback: lambda { |_, new_env|
validate_uri!(new_env[:url]) if validate
validate_uri!(new_env[:url]) if Docuseal.multitenant?
}
end
end

@ -22,7 +22,7 @@ module SendWebhookRequest
end
if Docuseal.multitenant?
raise HttpsError, 'Only HTTPS is allowed.' if (uri.scheme != 'https' || [443, nil].exclude?(uri.port)) &&
raise HttpsError, 'Only HTTPS is allowed.' if uri.scheme != 'https' &&
!AccountConfig.exists?(key: :allow_http,
account_id: webhook_url.account_id)
raise LocalhostError, "Can't send to localhost." if uri.host.in?(LOCALHOSTS)

@ -18,8 +18,7 @@ module Submissions
def plain_search(submissions, keyword, search_values: false, search_template: false)
return submissions if keyword.blank?
sanitized = ActiveRecord::Base.sanitize_sql_like(keyword.downcase)
term = "%#{sanitized}%"
term = "%#{keyword.downcase}%"
arel_table = Submitter.arel_table
@ -32,7 +31,7 @@ module Submissions
if search_template
submissions = submissions.left_joins(:template)
arel = arel.or(Template.arel_table[:name].lower.matches("%#{sanitized}%"))
arel = arel.or(Template.arel_table[:name].lower.matches("%#{keyword.downcase}%"))
end
submissions.joins(:submitters).where(arel).group(:id)

@ -162,7 +162,7 @@ module Submissions
pdf.trailer.info[:DocumentID] = document_id
pdf.pages.each do |page|
font_size = [(([page.box.width, page.box.height].min / A4_SIZE[0].to_f) * 9).to_i, 4].max
font_size = (([page.box.width, page.box.height].min / A4_SIZE[0].to_f) * 9).to_i
cnv = page.canvas(type: :overlay)
text =

@ -14,7 +14,6 @@ module Submitters
UnableToSendCode = Class.new(StandardError)
InvalidOtp = Class.new(StandardError)
MaliciousFileExtension = Class.new(StandardError)
ArgumentError = Class.new(StandardError)
DANGEROUS_EXTENSIONS = Set.new(%w[
exe com bat cmd scr pif vbs vbe js jse wsf wsh msi msp
@ -134,7 +133,7 @@ module Submitters
filename: file.original_filename,
content_type: file.content_type)
else
raise ArgumentError, 'file param is missing'
ActiveStorage::Blob.find_signed(params[:blob_signed_id])
end
ActiveStorage::Attachment.create!(

@ -1,45 +0,0 @@
# frozen_string_literal: true
module Submitters
module AuthorizedForForm
Unauthorized = Class.new(StandardError)
module_function
def call(submitter, current_user, request)
pass_email_2fa?(submitter, request) && pass_link_2fa?(submitter, current_user, request)
end
def pass_email_2fa?(submitter, request)
return false unless submitter
return true if submitter.submission.template&.preferences&.dig('require_email_2fa') != true &&
submitter.preferences['require_email_2fa'] != true
return true if request.cookie_jar.encrypted[:email_2fa_slug] == submitter.slug
token = request.params[:two_factor_token].presence || request.headers['x-two-factor-token'].presence
return true if token.present? &&
Submitter.signed_id_verifier.verified(token, purpose: :email_two_factor) == submitter.slug
false
end
def pass_link_2fa?(submitter, current_user, request)
return false unless submitter
return true if submitter.submission.source != 'link'
return true unless submitter.submission.template&.preferences&.dig('shared_link_2fa') == true
return true if request.cookie_jar.encrypted[:email_2fa_slug] == submitter.slug
return true if submitter.email == current_user&.email && current_user&.account_id == submitter.account_id
if (token = request.params[:two_factor_token].presence || request.headers['x-two-factor-token'].presence)
link_2fa_key = [submitter.email.downcase.squish, submitter.submission.template.slug].join(':')
return true if Submitter.signed_id_verifier.verified(token, purpose: :email_two_factor) == link_2fa_key
end
false
end
end
end

@ -236,7 +236,7 @@ module Submitters
return blob if blob
data = DownloadUtils.call(url, validate: true).body
data = DownloadUtils.call(url).body
checksum = Digest::MD5.base64digest(data)

@ -20,9 +20,7 @@ module TemplateFolders
def search(folders, keyword)
return folders if keyword.blank?
sanitized = ActiveRecord::Base.sanitize_sql_like(keyword.downcase)
folders.where(TemplateFolder.arel_table[:name].lower.matches("%#{sanitized}%"))
folders.where(TemplateFolder.arel_table[:name].lower.matches("%#{keyword.downcase}%"))
end
def filter_active_folders(template_folders, templates)

@ -52,9 +52,7 @@ module Templates
def plain_search(templates, keyword)
return templates if keyword.blank?
sanitized = ActiveRecord::Base.sanitize_sql_like(keyword.downcase)
templates.where(Template.arel_table[:name].lower.matches("%#{sanitized}%"))
templates.where(Template.arel_table[:name].lower.matches("%#{keyword.downcase}%"))
end
def fulltext_search(current_user, templates, keyword)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 807 B

After

Width:  |  Height:  |  Size: 695 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

@ -1,5 +0,0 @@
<svg height="180" width="180" viewBox="0 0 180 180" xmlns="http://www.w3.org/2000/svg">
<circle fill="white" cx="90" cy="90" r="88"/>
<path fill="#e0753f" d="M 178.224 72.09 c -0.296 -1.463 -0.627 -2.919 -0.996 -4.364 -0.293 -1.151 -0.616 -2.293 -0.956 -3.433 -0.301 -1.008 -0.612 -2.014 -0.95 -3.012 -0.531 -1.578 -1.113 -3.142 -1.735 -4.694 -0.216 -0.54 -0.433 -1.082 -0.661 -1.618 -0.195 -0.462 -0.399 -0.917 -0.601 -1.375 -0.262 -0.591 -0.53 -1.177 -0.804 -1.762 -0.074 -0.159 -0.151 -0.315 -0.226 -0.474 -0.209 -0.441 -0.422 -0.881 -0.638 -1.318 -0.076 -0.154 -0.153 -0.306 -0.229 -0.459 -0.236 -0.471 -0.477 -0.939 -0.721 -1.406 -0.053 -0.101 -0.105 -0.201 -0.158 -0.302 -1.143 -2.16 -2.367 -4.269 -3.68 -6.322 -0.116 -0.181 -0.237 -0.359 -0.355 -0.539 -0.094 -0.144 -0.189 -0.288 -0.284 -0.432 -0.284 -0.431 -0.57 -0.861 -0.862 -1.287 -0.112 -0.164 -0.225 -0.326 -0.338 -0.489 -0.193 -0.279 -0.382 -0.56 -0.579 -0.836 -0.089 -0.125 -0.182 -0.249 -0.273 -0.374 -0.13 -0.182 -0.264 -0.362 -0.395 -0.542 -0.277 -0.38 -0.556 -0.76 -0.838 -1.135 -0.15 -0.199 -0.303 -0.395 -0.454 -0.593 -0.21 -0.274 -0.417 -0.552 -0.63 -0.823 -0.055 -0.069 -0.111 -0.136 -0.166 -0.205 -0.482 -0.61 -0.971 -1.216 -1.47 -1.814 -0.129 -0.155 -0.262 -0.306 -0.392 -0.461 -0.402 -0.476 -0.808 -0.95 -1.22 -1.417 -0.186 -0.212 -0.375 -0.422 -0.563 -0.631 -0.384 -0.428 -0.773 -0.854 -1.167 -1.276 -0.176 -0.189 -0.351 -0.379 -0.529 -0.567 -0.564 -0.595 -1.134 -1.186 -1.716 -1.768 -1.091 -1.091 -2.207 -2.15 -3.346 -3.178 -1.016 -0.919 -2.05 -1.815 -3.103 -2.684 -0.772 -0.636 -1.557 -1.255 -2.348 -1.864 -3.465 -2.67 -7.112 -5.075 -10.927 -7.209 -2.869 -1.604 -5.83 -3.06 -8.883 -4.351 -2.443 -1.033 -4.922 -1.948 -7.428 -2.756 -8.879 -2.863 -18.13 -4.318 -27.605 -4.318 -3.19 0 -6.354 0.169 -9.488 0.496 -4.036 0.421 -8.019 1.114 -11.94 2.073 -1.732 0.423 -3.452 0.892 -5.157 1.42 -2.856 0.883 -5.673 1.912 -8.447 3.085 -2.645 1.118 -5.222 2.357 -7.729 3.711 -2.574 1.39 -5.073 2.901 -7.494 4.533 -1.195 0.805 -2.37 1.64 -3.527 2.503 -1.156 0.864 -2.292 1.756 -3.408 2.676 -0.553 0.456 -1.1 0.919 -1.643 1.389 -1.649 1.427 -3.252 2.92 -4.806 4.473 -2.582 2.582 -4.991 5.299 -7.222 8.138 -0.892 1.135 -1.756 2.292 -2.59 3.467 -0.417 0.588 -0.827 1.18 -1.23 1.778 -0.403 0.597 -0.798 1.199 -1.186 1.806 -0.388 0.607 -0.769 1.218 -1.143 1.835 -2.241 3.697 -4.216 7.562 -5.916 11.582 -1.095 2.589 -2.059 5.217 -2.901 7.877 -0.153 0.482 -0.3 0.965 -0.444 1.449 -0.339 1.14 -0.663 2.282 -0.956 3.433 -0.369 1.446 -0.7 2.901 -0.996 4.364 -1.034 5.121 -1.618 10.343 -1.749 15.637 -0.018 0.757 -0.028 1.514 -0.028 2.274 0 1.123 0.02 2.244 0.062 3.361 0.285 7.82 1.568 15.475 3.825 22.879 0.044 0.147 0.088 0.295 0.133 0.441 0.877 2.823 1.894 5.608 3.054 8.35 0.85 2.009 1.769 3.98 2.755 5.912 0.539 1.057 1.105 2.099 1.685 3.132 4.013 7.142 8.98 13.698 14.846 19.564 7.713 7.713 16.611 13.878 26.477 18.352 0.705 0.32 1.415 0.632 2.131 0.935 2.081 0.88 4.185 1.679 6.313 2.396 9.217 3.106 18.85 4.677 28.719 4.677 8.031 0 15.902 -1.047 23.522 -3.107 0.633 -0.172 1.266 -0.35 1.895 -0.535 0.757 -0.222 1.509 -0.456 2.26 -0.698 0.717 -0.232 1.431 -0.474 2.145 -0.723 1.752 -0.616 3.49 -1.281 5.211 -2.009 0.755 -0.319 1.503 -0.651 2.247 -0.989 1.237 -0.563 2.459 -1.15 3.664 -1.766 0.644 -0.328 1.283 -0.665 1.917 -1.009 1.654 -0.896 3.274 -1.848 4.865 -2.844 5.736 -3.591 11.06 -7.827 15.912 -12.679 0.775 -0.775 1.534 -1.562 2.278 -2.36 5.204 -5.59 9.636 -11.754 13.246 -18.417 0.343 -0.634 0.68 -1.274 1.009 -1.917 0.482 -0.944 0.943 -1.9 1.392 -2.863 0.471 -1.007 0.928 -2.021 1.364 -3.049 1.22 -2.886 2.281 -5.82 3.187 -8.793 0.559 -1.833 1.056 -3.68 1.494 -5.542 0.108 -0.458 0.211 -0.916 0.312 -1.376 0.194 -0.883 0.373 -1.77 0.539 -2.659 1.02 -5.455 1.542 -11.02 1.542 -16.663 0 -6.074 -0.595 -12.058 -1.776 -17.911 z m -161.733 19.614 c -1.118 -56.662 44.604 -74.877 60.998 -67.647 2.187 0.965 4.732 2.431 7.042 2.96 5.295 1.213 13.432 -3.113 13.521 6.273 0.078 8.156 -3.389 13.108 -10.797 16.177 -7.539 3.124 -14.777 9.181 -19.95 15.493 -21.487 26.216 -31.231 68.556 -7.565 94.296 -13.679 -5.545 -42.418 -25.467 -43.248 -67.552 z m 91.109 72.619 c -0.053 0.008 -4.171 0.775 -4.171 0.775 0 0 -15.862 -22.957 -23.509 -21.719 11.291 16.04 12.649 22.625 12.649 22.625 -0.053 0.001 -0.107 0.001 -0.161 0.003 -51.831 2.131 -42.785 -64.026 -28.246 -86.502 -1.555 13.073 8.878 39.992 39.034 44.1 9.495 1.293 32.302 -3.275 41.015 -11.38 0.098 1.825 0.163 3.85 0.159 6.013 -0.046 23.538 -13.47 42.743 -36.77 46.085 z m 30.575 -15.708 c 9.647 -9.263 12.869 -27.779 9.103 -44.137 -4.608 -20.011 -28.861 -32.383 -40.744 -35.564 5.766 -8.089 27.908 -14.274 39.567 5.363 -5.172 -10.519 -13.556 -23.023 -1.732 -33.128 12.411 13.329 19.411 29.94 20.161 48.7 0.75 18.753 -6.64 41.768 -26.355 58.765 z"/>
<circle fill="#e0753f" cx="71.927" cy="32.004" r="2.829"/>
</svg>

Before

Width:  |  Height:  |  Size: 4.7 KiB

@ -16,10 +16,6 @@ RSpec.describe SendFormCompletedWebhookRequestJob do
end
describe '#perform' do
around do |example|
freeze_time { example.run }
end
before do
stub_request(:post, webhook_url.url).to_return(status: 200)
end

@ -16,10 +16,6 @@ RSpec.describe SendFormDeclinedWebhookRequestJob do
end
describe '#perform' do
around do |example|
freeze_time { example.run }
end
before do
stub_request(:post, webhook_url.url).to_return(status: 200)
end

@ -16,10 +16,6 @@ RSpec.describe SendFormStartedWebhookRequestJob do
end
describe '#perform' do
around do |example|
freeze_time { example.run }
end
before do
stub_request(:post, webhook_url.url).to_return(status: 200)
end

@ -16,10 +16,6 @@ RSpec.describe SendFormViewedWebhookRequestJob do
end
describe '#perform' do
around do |example|
freeze_time { example.run }
end
before do
stub_request(:post, webhook_url.url).to_return(status: 200)
end

@ -13,10 +13,6 @@ RSpec.describe SendSubmissionCompletedWebhookRequestJob do
end
describe '#perform' do
around do |example|
freeze_time { example.run }
end
before do
stub_request(:post, webhook_url.url).to_return(status: 200)
end

@ -13,10 +13,6 @@ RSpec.describe SendSubmissionCreatedWebhookRequestJob do
end
describe '#perform' do
around do |example|
freeze_time { example.run }
end
before do
stub_request(:post, webhook_url.url).to_return(status: 200)
end

@ -13,10 +13,6 @@ RSpec.describe SendSubmissionExpiredWebhookRequestJob do
end
describe '#perform' do
around do |example|
freeze_time { example.run }
end
before do
stub_request(:post, webhook_url.url).to_return(status: 200)
end

@ -12,10 +12,6 @@ RSpec.describe SendTemplateCreatedWebhookRequestJob do
end
describe '#perform' do
around do |example|
freeze_time { example.run }
end
before do
stub_request(:post, webhook_url.url).to_return(status: 200)
end

@ -12,10 +12,6 @@ RSpec.describe SendTemplateUpdatedWebhookRequestJob do
end
describe '#perform' do
around do |example|
freeze_time { example.run }
end
before do
stub_request(:post, webhook_url.url).to_return(status: 200)
end

@ -54,7 +54,6 @@ RSpec.configure do |config|
config.include FactoryBot::Syntax::Methods
config.include Devise::Test::IntegrationHelpers
config.include SigningFormHelper
config.include ActiveSupport::Testing::TimeHelpers
config.before(:each, type: :system) do
if ENV['HEADLESS'] == 'false'

@ -61,6 +61,7 @@ RSpec.describe 'Email Settings' do
expect(page).to have_field('Host', with: encrypted_config.value['host'])
expect(page).to have_field('Port', with: encrypted_config.value['port'])
expect(page).to have_field('Username', with: encrypted_config.value['username'])
expect(page).to have_field('Password', with: encrypted_config.value['password'])
expect(page).to have_field('Domain', with: encrypted_config.value['domain'])
expect(page).to have_select('Authentication', selected: 'Plain')
expect(page).to have_field('Send from Email', with: encrypted_config.value['from_email'])

Loading…
Cancel
Save