Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 33ca930055 | |||
| 3b396f8421 | |||
| aa77e3a8d3 | |||
| 75316d8d87 | |||
| a999109a5c | |||
| bdd33c7d6b | |||
| 61c5ee22a0 | |||
| 12c5b909e0 | |||
| 871ef6dda6 | |||
| 40052a2d7c | |||
| 347be0137d | |||
| fe6baba8bf | |||
| ed8c313bd4 | |||
| ca0acb34d6 | |||
| 34ea639c25 | |||
| 680ab9dbed | |||
| 9377766e52 | |||
| 7a2c37454e | |||
| 8f6f418f54 | |||
| 9b7745c565 |
@@ -27,20 +27,18 @@ class SubmissionsDownloadController < ApplicationController
|
||||
|
||||
Submissions::EnsureResultGenerated.call(last_submitter)
|
||||
|
||||
if last_submitter.completed_at < TTL.ago && !signature_valid && !current_user_submitter?(last_submitter)
|
||||
Rollbar.info("TTL: #{last_submitter.id}") if defined?(Rollbar)
|
||||
if !signature_valid && !current_user_submitter?(last_submitter)
|
||||
return head :not_found unless Submitters::AuthorizedForForm.call(@submitter, current_user, request)
|
||||
|
||||
return head :not_found
|
||||
if last_submitter.completed_at < TTL.ago
|
||||
Rollbar.info("TTL: #{last_submitter.id}") if defined?(Rollbar)
|
||||
|
||||
return head :not_found
|
||||
end
|
||||
end
|
||||
|
||||
if params[:combined] == 'true'
|
||||
url = build_combined_url(@submitter)
|
||||
|
||||
if url
|
||||
render json: [url]
|
||||
else
|
||||
head :not_found
|
||||
end
|
||||
respond_with_combined(last_submitter)
|
||||
else
|
||||
render json: build_urls(last_submitter)
|
||||
end
|
||||
@@ -48,8 +46,18 @@ class SubmissionsDownloadController < ApplicationController
|
||||
|
||||
private
|
||||
|
||||
def respond_with_combined(submitter)
|
||||
url = build_combined_url(submitter)
|
||||
|
||||
if url
|
||||
render json: [url]
|
||||
else
|
||||
head :not_found
|
||||
end
|
||||
end
|
||||
|
||||
def current_user_submitter?(submitter)
|
||||
current_user && current_user.account.submitters.exists?(id: submitter.id)
|
||||
current_user && current_ability.can?(:read, submitter)
|
||||
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 update]
|
||||
before_action :maybe_require_link_2fa, only: %i[show]
|
||||
|
||||
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
|
||||
if require_email_2fa?(@submitter)
|
||||
unless Submitters::AuthorizedForForm.call(@submitter, current_user, request)
|
||||
return render json: { error: I18n.t('verification_required_refresh_the_page_and_pass_2fa') },
|
||||
status: :unprocessable_content
|
||||
end
|
||||
@@ -84,7 +84,9 @@ class SubmitFormController < ApplicationController
|
||||
def completed
|
||||
raise ActionController::RoutingError, I18n.t('not_found') if @submitter.account.archived_at?
|
||||
|
||||
redirect_to submit_form_path(params[:submit_form_slug]) if require_email_2fa?(@submitter)
|
||||
return if Submitters::AuthorizedForForm.call(@submitter, current_user, request)
|
||||
|
||||
redirect_to submit_form_path(params[:submit_form_slug])
|
||||
end
|
||||
|
||||
def success; end
|
||||
@@ -92,10 +94,7 @@ class SubmitFormController < ApplicationController
|
||||
private
|
||||
|
||||
def maybe_require_link_2fa
|
||||
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
|
||||
return if Submitters::AuthorizedForForm.pass_link_2fa?(@submitter, current_user, request)
|
||||
|
||||
redirect_to start_form_path(@submitter.submission.template.slug)
|
||||
end
|
||||
@@ -117,12 +116,4 @@ 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,7 +11,9 @@ class SubmitFormDeclineController < ApplicationController
|
||||
submitter.completed_at? ||
|
||||
submitter.submission.archived_at? ||
|
||||
submitter.submission.expired? ||
|
||||
submitter.submission.template&.archived_at?
|
||||
submitter.submission.template&.archived_at? ||
|
||||
!Submitters::AuthorizedForForm.call(submitter, current_user,
|
||||
request)
|
||||
|
||||
ApplicationRecord.transaction do
|
||||
submitter.update!(declined_at: Time.current)
|
||||
|
||||
@@ -17,7 +17,8 @@ class SubmitFormDownloadController < ApplicationController
|
||||
@submitter.submission.template&.archived_at? ||
|
||||
AccountConfig.exists?(account_id: @submitter.account_id,
|
||||
key: AccountConfig::ALLOW_TO_PARTIAL_DOWNLOAD_KEY,
|
||||
value: false)
|
||||
value: false) ||
|
||||
!Submitters::AuthorizedForForm.call(@submitter, current_user, request)
|
||||
|
||||
last_completed_submitter = @submitter.submission.submitters
|
||||
.where.not(id: @submitter.id)
|
||||
|
||||
@@ -12,7 +12,8 @@ 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?
|
||||
if @submitter.submission.template&.archived_at? || @submitter.submission.archived_at? ||
|
||||
!Submitters::AuthorizedForForm.call(@submitter, current_user, request)
|
||||
return redirect_to submit_form_path(@submitter.slug)
|
||||
end
|
||||
|
||||
|
||||
@@ -45,7 +45,8 @@ class SubmitFormInviteController < ApplicationController
|
||||
!submitter.completed_at? &&
|
||||
!submitter.submission.archived_at? &&
|
||||
!submitter.submission.expired? &&
|
||||
!submitter.submission.template&.archived_at?
|
||||
!submitter.submission.template&.archived_at? &&
|
||||
Submitters::AuthorizedForForm.call(submitter, current_user, request)
|
||||
end
|
||||
|
||||
def filter_invite_submitters(submitter, key = 'invite_by_uuid')
|
||||
|
||||
@@ -7,10 +7,12 @@ class SubmitFormValuesController < ApplicationController
|
||||
def index
|
||||
submitter = Submitter.find_by!(slug: params[:submit_form_slug])
|
||||
|
||||
return render json: {} if submitter.completed_at? || submitter.declined_at?
|
||||
return render json: {} if submitter.submission.template&.archived_at? ||
|
||||
return render json: {} if submitter.completed_at? ||
|
||||
submitter.declined_at? ||
|
||||
submitter.submission.template&.archived_at? ||
|
||||
submitter.submission.archived_at? ||
|
||||
submitter.submission.expired?
|
||||
submitter.submission.expired? ||
|
||||
!Submitters::AuthorizedForForm.call(submitter, current_user, request)
|
||||
|
||||
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]).body)
|
||||
tempfile.write(DownloadUtils.call(params[:url], validate: true).body)
|
||||
tempfile.rewind
|
||||
|
||||
filename = URI.decode_www_form_component(params[:filename]) if params[:filename].present?
|
||||
|
||||
@@ -161,6 +161,11 @@ export default {
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
fetchOptions: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => ({})
|
||||
},
|
||||
completedButton: {
|
||||
type: Object,
|
||||
required: false,
|
||||
@@ -214,7 +219,10 @@ export default {
|
||||
download () {
|
||||
this.isDownloading = true
|
||||
|
||||
fetch(this.baseUrl + `/submitters/${this.submitterSlug}/download`).then(async (response) => {
|
||||
fetch(this.baseUrl + `/submitters/${this.submitterSlug}/download`, {
|
||||
method: 'GET',
|
||||
...this.fetchOptions
|
||||
}).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,6 +530,7 @@
|
||||
v-else-if="isInvite"
|
||||
:submitters="inviteSubmitters"
|
||||
:optional-submitters="optionalInviteSubmitters"
|
||||
:fetch-options="fetchOptions"
|
||||
:submitter-slug="submitterSlug"
|
||||
:authenticity-token="authenticityToken"
|
||||
:url="baseUrl + submitPath + '/invite'"
|
||||
@@ -543,6 +544,7 @@
|
||||
: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"
|
||||
@@ -678,6 +680,11 @@ export default {
|
||||
required: false,
|
||||
default: () => []
|
||||
},
|
||||
fetchOptions: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => ({})
|
||||
},
|
||||
optionalInviteSubmitters: {
|
||||
type: Array,
|
||||
required: false,
|
||||
@@ -1467,7 +1474,8 @@ export default {
|
||||
} else {
|
||||
return fetch(this.baseUrl + this.submitPath, {
|
||||
method: 'POST',
|
||||
body: formData || new FormData(this.$refs.form)
|
||||
body: formData || new FormData(this.$refs.form),
|
||||
...this.fetchOptions
|
||||
}).then((response) => {
|
||||
if (response.status === 200) {
|
||||
currentFieldUuids.forEach((fieldUuid) => {
|
||||
|
||||
@@ -78,6 +78,11 @@ export default {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
fetchOptions: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => ({})
|
||||
},
|
||||
optionalSubmitters: {
|
||||
type: Array,
|
||||
required: false,
|
||||
@@ -108,7 +113,8 @@ export default {
|
||||
|
||||
return fetch(this.url, {
|
||||
method: 'POST',
|
||||
body: new FormData(this.$refs.form)
|
||||
body: new FormData(this.$refs.form),
|
||||
...this.fetchOptions
|
||||
}).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
|
||||
return this.isSelectModeRef.value && !this.drawFieldType && this.editable && !this.drawField && !this.drawCustomField
|
||||
},
|
||||
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'
|
||||
raise HttpsError, 'Only HTTPS is allowed.' if uri.scheme != 'https' || [443, nil].exclude?(uri.port)
|
||||
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, value: value['password'], class: 'base-input' %>
|
||||
<%= ff.password_field :password, class: 'base-input', required: value['password'].present?, placeholder: value['password'].present? ? '*************' : '' %>
|
||||
</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="<%= params[:modal] %>"></open-modal>
|
||||
<open-modal src="<%= url_for(url_params) %>"></open-modal>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<turbo-frame id="modal"></turbo-frame>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"id": "/",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/logo.svg",
|
||||
"src": "/favicon.svg",
|
||||
"type": "image/svg+xml",
|
||||
"sizes": "any"
|
||||
},
|
||||
|
||||
@@ -27,4 +27,5 @@
|
||||
<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.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">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<%= fff.label :secret_access_key, class: 'label' %>
|
||||
<%= fff.password_field :secret_access_key, value: configs['secret_access_key'], required: true, class: 'base-input' %>
|
||||
<%= fff.password_field :secret_access_key, required: true, class: 'base-input', placeholder: configs['secret_access_key'].present? ? '*************' : '' %>
|
||||
</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, value: configs['storage_access_key'], required: true, class: 'base-input' %>
|
||||
<%= fff.password_field :storage_access_key, required: true, class: 'base-input', placeholder: configs['storage_access_key'].present? ? '*************' : '' %>
|
||||
</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, value: configs['credentials'], required: true, class: 'base-textarea w-full font-mono', rows: 4 %>
|
||||
<%= fff.text_area :credentials, required: true, class: 'base-textarea w-full font-mono', rows: 4, placeholder: configs['credentials'].present? ? "{\n**REDACTED**\n}" : '' %>
|
||||
</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? %>
|
||||
<% elsif @submission.submitters.to_a.size == 1 && !@submission.expired? && !@submission.submitters.to_a.first.declined_at? && !@submission.archived_at? && !@submission.template&.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.expired? && !submitter.start_form_submission_events.any? %>
|
||||
<% 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? %>
|
||||
<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? && 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? && !@submission.template&.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? && can?(:update, @submission) && !@submission.expired? && !submitter.declined_at? %>
|
||||
<% if signed_in? && submitter && submitter.phone && !submitter.completed_at && !@submission.archived_at? && !@submission.template&.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? && can?(:create, @submission) && !@submission.expired? && !submitter.declined_at? %>
|
||||
<% if signed_in? && submitter && !submitter.completed_at? && !@submission.archived_at? && !@submission.template&.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.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">
|
||||
<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">
|
||||
<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.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">
|
||||
<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">
|
||||
<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.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">
|
||||
<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">
|
||||
<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.to_unsafe_h.except(:status)), class: 'rounded-lg ml-1 hover:bg-base-content hover:text-white' do %>
|
||||
<%= link_to url_for(params: request.query_parameters.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.to_unsafe_h.except(:folder)), class: 'rounded-lg ml-1 hover:bg-base-content hover:text-white' do %>
|
||||
<%= link_to url_for(params: request.query_parameters.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.to_unsafe_h.except(:author)), class: 'rounded-lg ml-1 hover:bg-base-content hover:text-white' do %>
|
||||
<%= link_to url_for(params: request.query_parameters.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.to_unsafe_h.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: request.query_parameters.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.to_unsafe_h.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: request.query_parameters.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]}?#{params.to_unsafe_h.slice(:q).merge(local_assigns[:default_params]).to_query}", class: 'link', data: { turbo_frame: :_top } %>
|
||||
<%= 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 } %>
|
||||
</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.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">
|
||||
<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">
|
||||
<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.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">
|
||||
<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">
|
||||
<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.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">
|
||||
<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">
|
||||
<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.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]'}" %>
|
||||
<%= 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]'}" %>
|
||||
</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]
|
||||
resources :timestamp_server, only: %i[create] unless Docuseal.multitenant?
|
||||
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)
|
||||
def call(url, validate: Docuseal.multitenant?)
|
||||
uri = begin
|
||||
URI(url)
|
||||
rescue URI::Error
|
||||
Addressable::URI.parse(url).normalize
|
||||
end
|
||||
|
||||
validate_uri!(uri) if Docuseal.multitenant?
|
||||
validate_uri!(uri) if validate
|
||||
|
||||
resp = conn.get(uri)
|
||||
resp = conn(validate:).get(uri)
|
||||
|
||||
raise UnableToDownload, "Error loading: #{uri}" if resp.status >= 400
|
||||
|
||||
@@ -52,14 +52,15 @@ module DownloadUtils
|
||||
end
|
||||
|
||||
def validate_uri!(uri)
|
||||
raise UnableToDownload, "Error loading: #{uri}. Only HTTPS is allowed." if uri.scheme != 'https'
|
||||
raise UnableToDownload, "Error loading: #{uri}. Only HTTPS is allowed." if uri.scheme != 'https' ||
|
||||
[443, nil].exclude?(uri.port)
|
||||
raise UnableToDownload, "Error loading: #{uri}. Can't download from localhost." if uri.host.in?(LOCALHOSTS)
|
||||
end
|
||||
|
||||
def conn
|
||||
def conn(validate: Docuseal.multitenant?)
|
||||
Faraday.new do |faraday|
|
||||
faraday.response :follow_redirects, callback: lambda { |_, new_env|
|
||||
validate_uri!(new_env[:url]) if Docuseal.multitenant?
|
||||
validate_uri!(new_env[:url]) if validate
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -22,7 +22,7 @@ module SendWebhookRequest
|
||||
end
|
||||
|
||||
if Docuseal.multitenant?
|
||||
raise HttpsError, 'Only HTTPS is allowed.' if uri.scheme != 'https' &&
|
||||
raise HttpsError, 'Only HTTPS is allowed.' if (uri.scheme != 'https' || [443, nil].exclude?(uri.port)) &&
|
||||
!AccountConfig.exists?(key: :allow_http,
|
||||
account_id: webhook_url.account_id)
|
||||
raise LocalhostError, "Can't send to localhost." if uri.host.in?(LOCALHOSTS)
|
||||
|
||||
@@ -18,7 +18,8 @@ module Submissions
|
||||
def plain_search(submissions, keyword, search_values: false, search_template: false)
|
||||
return submissions if keyword.blank?
|
||||
|
||||
term = "%#{keyword.downcase}%"
|
||||
sanitized = ActiveRecord::Base.sanitize_sql_like(keyword.downcase)
|
||||
term = "%#{sanitized}%"
|
||||
|
||||
arel_table = Submitter.arel_table
|
||||
|
||||
@@ -31,7 +32,7 @@ module Submissions
|
||||
if search_template
|
||||
submissions = submissions.left_joins(:template)
|
||||
|
||||
arel = arel.or(Template.arel_table[:name].lower.matches("%#{keyword.downcase}%"))
|
||||
arel = arel.or(Template.arel_table[:name].lower.matches("%#{sanitized}%"))
|
||||
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
|
||||
font_size = [(([page.box.width, page.box.height].min / A4_SIZE[0].to_f) * 9).to_i, 4].max
|
||||
cnv = page.canvas(type: :overlay)
|
||||
|
||||
text =
|
||||
|
||||
@@ -14,6 +14,7 @@ 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
|
||||
@@ -133,7 +134,7 @@ module Submitters
|
||||
filename: file.original_filename,
|
||||
content_type: file.content_type)
|
||||
else
|
||||
ActiveStorage::Blob.find_signed(params[:blob_signed_id])
|
||||
raise ArgumentError, 'file param is missing'
|
||||
end
|
||||
|
||||
ActiveStorage::Attachment.create!(
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
# 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).body
|
||||
data = DownloadUtils.call(url, validate: true).body
|
||||
|
||||
checksum = Digest::MD5.base64digest(data)
|
||||
|
||||
|
||||
@@ -20,7 +20,9 @@ module TemplateFolders
|
||||
def search(folders, keyword)
|
||||
return folders if keyword.blank?
|
||||
|
||||
folders.where(TemplateFolder.arel_table[:name].lower.matches("%#{keyword.downcase}%"))
|
||||
sanitized = ActiveRecord::Base.sanitize_sql_like(keyword.downcase)
|
||||
|
||||
folders.where(TemplateFolder.arel_table[:name].lower.matches("%#{sanitized}%"))
|
||||
end
|
||||
|
||||
def filter_active_folders(template_folders, templates)
|
||||
|
||||
@@ -52,7 +52,9 @@ module Templates
|
||||
def plain_search(templates, keyword)
|
||||
return templates if keyword.blank?
|
||||
|
||||
templates.where(Template.arel_table[:name].lower.matches("%#{keyword.downcase}%"))
|
||||
sanitized = ActiveRecord::Base.sanitize_sql_like(keyword.downcase)
|
||||
|
||||
templates.where(Template.arel_table[:name].lower.matches("%#{sanitized}%"))
|
||||
end
|
||||
|
||||
def fulltext_search(current_user, templates, keyword)
|
||||
|
||||
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 695 B After Width: | Height: | Size: 807 B |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,5 @@
|
||||
<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>
|
||||
|
After Width: | Height: | Size: 4.7 KiB |
@@ -16,6 +16,10 @@ 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,6 +16,10 @@ 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,6 +16,10 @@ 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,6 +16,10 @@ 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,6 +13,10 @@ 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,6 +13,10 @@ 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,6 +13,10 @@ 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,6 +12,10 @@ 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,6 +12,10 @@ 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,6 +54,7 @@ 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,7 +61,6 @@ 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'])
|
||||
|
||||