diff --git a/Gemfile.lock b/Gemfile.lock index 5dc77e9f3..a1472da2d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -497,7 +497,7 @@ GEM ruby-vips (2.3.0) ffi (~> 1.12) logger - rubyzip (3.2.2) + rubyzip (3.7.0) securerandom (0.4.1) semantic_range (3.1.1) shakapacker (9.7.0) diff --git a/README.md b/README.md index 64a917ade..e2f6e8b92 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ DocuSeal is an open source platform that provides secure and efficient digital d - Files storage on disk or AWS S3, Google Storage, Azure Cloud - Automatic PDF eSignature - PDF signature verification -- Users management +- User management - Mobile-optimized - 7 UI languages with signing available in 14 languages - API and Webhooks for integrations diff --git a/app/controllers/api/submissions_controller.rb b/app/controllers/api/submissions_controller.rb index e55cbe03d..a00f1d543 100644 --- a/app/controllers/api/submissions_controller.rb +++ b/app/controllers/api/submissions_controller.rb @@ -98,7 +98,7 @@ module Api render json: build_create_json(submissions) rescue Submitters::NormalizeValues::BaseError, Submissions::CreateFromSubmitters::BaseError, - DownloadUtils::UnableToDownload => e + Submitters::SubmitValues::ValidationError, DownloadUtils::UnableToDownload => e Rollbar.warning(e) if defined?(Rollbar) render json: { error: e.message }, status: :unprocessable_content diff --git a/app/controllers/api/submitters_controller.rb b/app/controllers/api/submitters_controller.rb index 555dc92dd..313f84fc1 100644 --- a/app/controllers/api/submitters_controller.rb +++ b/app/controllers/api/submitters_controller.rb @@ -78,7 +78,8 @@ module Api render json: Submitters::SerializeForApi.call(@submitter, with_template: false, with_urls: true, with_events: false, params:) - rescue Submitters::NormalizeValues::BaseError, DownloadUtils::UnableToDownload => e + rescue Submitters::NormalizeValues::BaseError, Submitters::SubmitValues::ValidationError, + DownloadUtils::UnableToDownload => e Rollbar.warning(e) if defined?(Rollbar) render json: { error: e.message }, status: :unprocessable_content diff --git a/app/controllers/submit_form_email_2fas_controller.rb b/app/controllers/submit_form_email_2fas_controller.rb index 511daaf65..6f95e7544 100644 --- a/app/controllers/submit_form_email_2fas_controller.rb +++ b/app/controllers/submit_form_email_2fas_controller.rb @@ -10,12 +10,10 @@ class SubmitFormEmail2fasController < ApplicationController def create RateLimit.call("verify-2fa-code-#{@submitter.id}", limit: 2, ttl: 45.seconds, enabled: true) - RateLimit.call("verify-2fa-code-1h-#{@submitter.id}", limit: 10, ttl: 1.hour, enabled: true) - RateLimit.call("verify-2fa-code-1d-#{@submitter.id}", limit: 20, ttl: 1.day, enabled: true) - value = [@submitter.email.downcase.strip, @submitter.slug].join(':') + value = [@submitter.email.downcase.squish, @submitter.slug].join(':') - if EmailVerificationCodes.verify(params[:one_time_code].to_s.gsub(/\D/, ''), value) + if OtpChallenges.verify(params[:one_time_code].to_s.gsub(/\D/, ''), value, purpose: 'form_email_2fa') SubmissionEvents.create_with_tracking_data(@submitter, 'email_verified', request, { email: @submitter.email }) cookies.encrypted[:email_2fa_slug] = diff --git a/app/controllers/submit_form_metadata_controller.rb b/app/controllers/submit_form_metadata_controller.rb index 55c95c830..32faba108 100644 --- a/app/controllers/submit_form_metadata_controller.rb +++ b/app/controllers/submit_form_metadata_controller.rb @@ -17,12 +17,12 @@ class SubmitFormMetadataController < ApplicationController submission.schema_documents.find { |a| a.uuid == item['attachment_uuid'] } end - ActiveRecord::Associations::Preloader.new(records: documents, associations: %i[blob record]).call + ActiveRecord::Associations::Preloader.new(records: documents, associations: :blob).call text_runs = documents.to_h do |document| [ document.uuid, - DocumentMetadatas.find_or_create_for_document(document, account_id: document.record.account_id).text_runs + DocumentMetadatas.find_or_create_for_document(document, account_id: submission.account_id).text_runs ] end diff --git a/app/controllers/submit_form_prefill_attachments_controller.rb b/app/controllers/submit_form_prefill_attachments_controller.rb index d56dc2a45..dbd720c6a 100644 --- a/app/controllers/submit_form_prefill_attachments_controller.rb +++ b/app/controllers/submit_form_prefill_attachments_controller.rb @@ -4,19 +4,19 @@ class SubmitFormPrefillAttachmentsController < ActionController::API include ActiveStorage::SetCurrent def create - submitter = Submitter.find_by!(slug: params[:submit_form_slug]) + @submitter = Submitter.find_by!(slug: params[:submit_form_slug]) - unless can_upload?(submitter) + unless can_upload?(@submitter) return render json: { error: I18n.t('form_has_been_archived') }, status: :unprocessable_content end uuid, slug = ApplicationRecord.signed_id_verifier.verify(params[:prefill_token], purpose: :prefill_attachment) - raise ActiveRecord::RecordNotFound if slug != submitter.slug + raise ActiveRecord::RecordNotFound if slug != @submitter.slug source_attachment = ActiveStorage::Attachment.find_by!(uuid:) - attachment = submitter.attachments_attachments.find_or_create_by!(blob_id: source_attachment.blob_id) + attachment = @submitter.attachments_attachments.find_or_create_by!(blob_id: source_attachment.blob_id) render json: attachment.as_json(only: %i[uuid created_at], methods: %i[url filename content_type]) end diff --git a/app/controllers/template_documents_controller.rb b/app/controllers/template_documents_controller.rb index fafa6c141..6aa95da02 100644 --- a/app/controllers/template_documents_controller.rb +++ b/app/controllers/template_documents_controller.rb @@ -6,7 +6,17 @@ class TemplateDocumentsController < ApplicationController FILES_TTL = 5.minutes def index - render json: @template.schema_documents.map { |d| ActiveStorage::Blob.proxy_path(d.blob, expires_at: FILES_TTL.from_now.to_i) } + documents = @template.schema.map do |item| + if item['dynamic'] + dynamic_document = @template.schema_dynamic_documents.find { |e| e.uuid == item['attachment_uuid'] } + + DynamicDocuments::EnsureVersionGenerated.call(dynamic_document).document_attachment + else + @template.schema_documents.find { |e| e.uuid == item['attachment_uuid'] } + end + end + + render json: documents.map { |d| ActiveStorage::Blob.proxy_path(d.blob, expires_at: FILES_TTL.from_now.to_i) } end def create diff --git a/app/javascript/submission_form/i18n.js b/app/javascript/submission_form/i18n.js index 1da0bd06c..e349e56ce 100644 --- a/app/javascript/submission_form/i18n.js +++ b/app/javascript/submission_form/i18n.js @@ -13,6 +13,7 @@ const en = { must_be_valid_email: 'Must be a valid email address', must_be_valid_url: 'Must be a valid URL (starting with http:// or https://)', must_be_valid_zip: 'Must be a valid ZIP code (XXXXX or XXXXX-XXXX)', + must_be_valid_iban: 'Must be a valid IBAN', must_contain_numbers_only: 'Must contain numbers only', must_contain_letters_only: 'Must contain letters only', complete_all_required_fields_to_proceed_with_identity_verification: 'Complete all required fields to proceed with identity verification.', @@ -132,6 +133,7 @@ const es = { must_be_valid_email: 'Debe ser una dirección de correo electrónico válida', must_be_valid_url: 'Debe ser una URL válida (que comience con http:// o https://)', must_be_valid_zip: 'Debe ser un ZIP válido (XXXXX o XXXXX-XXXX)', + must_be_valid_iban: 'Debe ser un IBAN válido', must_contain_numbers_only: 'Debe contener solo números', must_contain_letters_only: 'Debe contener solo letras', complete_all_required_fields_to_proceed_with_identity_verification: 'Complete todos los campos requeridos para continuar con la verificación de identidad.', @@ -251,6 +253,7 @@ const it = { must_be_valid_email: 'Deve essere un indirizzo email valido', must_be_valid_url: 'Deve essere un URL valido (che inizia con http:// o https://)', must_be_valid_zip: 'Deve essere uno ZIP valido (XXXXX o XXXXX-XXXX)', + must_be_valid_iban: 'Deve essere un IBAN valido', must_contain_numbers_only: 'Deve contenere solo numeri', must_contain_letters_only: 'Deve contenere solo lettere', complete_all_required_fields_to_proceed_with_identity_verification: "Compila tutti i campi obbligatori per procedere con la verifica dell'identità.", @@ -370,6 +373,7 @@ const de = { must_be_valid_email: 'Muss eine gültige E-Mail-Adresse sein', must_be_valid_url: 'Muss eine gültige URL sein (beginnend mit http:// oder https://)', must_be_valid_zip: 'Muss eine gültige ZIP sein (XXXXX oder XXXXX-XXXX)', + must_be_valid_iban: 'Muss eine gültige IBAN sein', must_contain_numbers_only: 'Darf nur Zahlen enthalten', must_contain_letters_only: 'Darf nur Buchstaben enthalten', complete_all_required_fields_to_proceed_with_identity_verification: 'Füllen Sie alle Pflichtfelder aus, um mit der Identitätsprüfung fortzufahren.', @@ -489,6 +493,7 @@ const fr = { must_be_valid_email: 'Doit être une adresse e-mail valide', must_be_valid_url: 'Doit être une URL valide (commençant par http:// ou https://)', must_be_valid_zip: 'Doit être un ZIP valide (XXXXX ou XXXXX-XXXX)', + must_be_valid_iban: 'Doit être un IBAN valide', must_contain_numbers_only: 'Doit contenir uniquement des chiffres', must_contain_letters_only: 'Doit contenir uniquement des lettres', complete_all_required_fields_to_proceed_with_identity_verification: "Veuillez remplir tous les champs obligatoires pour poursuivre la vérification d'identité.", @@ -608,6 +613,7 @@ const pl = { must_be_valid_email: 'Musi być prawidłowym adresem e-mail', must_be_valid_url: 'Musi być prawidłowym adresem URL (zaczynającym się od http:// lub https://)', must_be_valid_zip: 'Musi być prawidłowym ZIP (XXXXX lub XXXXX-XXXX)', + must_be_valid_iban: 'Musi być prawidłowym numerem IBAN', must_contain_numbers_only: 'Może zawierać tylko cyfry', must_contain_letters_only: 'Może zawierać tylko litery', complete_all_required_fields_to_proceed_with_identity_verification: 'Uzupełnij wszystkie wymagane pola, aby kontynuować weryfikację tożsamości.', @@ -727,6 +733,7 @@ const uk = { must_be_valid_email: 'Має бути дійсною адресою електронної пошти', must_be_valid_url: 'Має бути дійсним URL (починаючи з http:// або https://)', must_be_valid_zip: 'Має бути дійсним ZIP (XXXXX або XXXXX-XXXX)', + must_be_valid_iban: 'Має бути дійсним IBAN', must_contain_numbers_only: 'Має містити лише цифри', must_contain_letters_only: 'Має містити лише літери', complete_all_required_fields_to_proceed_with_identity_verification: "Заповніть всі обов'язкові поля, щоб продовжити перевірку особи.", @@ -846,6 +853,7 @@ const cs = { must_be_valid_email: 'Musí být platná e-mailová adresa', must_be_valid_url: 'Musí být platná URL (začínající http:// nebo https://)', must_be_valid_zip: 'Musí být platný ZIP (XXXXX nebo XXXXX-XXXX)', + must_be_valid_iban: 'Musí být platný IBAN', must_contain_numbers_only: 'Musí obsahovat pouze čísla', must_contain_letters_only: 'Musí obsahovat pouze písmena', complete_all_required_fields_to_proceed_with_identity_verification: 'Vyplňte všechna povinná pole, abyste mohli pokračovat v ověření identity.', @@ -965,6 +973,7 @@ const pt = { must_be_valid_email: 'Deve ser um endereço de e-mail válido', must_be_valid_url: 'Deve ser uma URL válida (começando com http:// ou https://)', must_be_valid_zip: 'Deve ser um ZIP válido (XXXXX ou XXXXX-XXXX)', + must_be_valid_iban: 'Deve ser um IBAN válido', must_contain_numbers_only: 'Deve conter apenas números', must_contain_letters_only: 'Deve conter apenas letras', complete_all_required_fields_to_proceed_with_identity_verification: 'Preencha todos os campos obrigatórios para prosseguir com a verificação de identidade.', @@ -1084,6 +1093,7 @@ const he = { must_be_valid_email: 'חייבת להיות כתובת אימייל חוקית', must_be_valid_url: 'חייבת להיות כתובת URL חוקית (המתחילה ב-http:// או https://)', must_be_valid_zip: 'חייב להיות ZIP חוקי (XXXXX או XXXXX-XXXX)', + must_be_valid_iban: 'חייב להיות IBAN חוקי', must_contain_numbers_only: 'חייב להכיל מספרים בלבד', must_contain_letters_only: 'חייב להכיל אותיות בלבד', complete_all_required_fields_to_proceed_with_identity_verification: 'מלא את כל השדות הנדרשים כדי להמשיך עם אימות זהות.', @@ -1203,6 +1213,7 @@ const nl = { must_be_valid_email: 'Moet een geldig e-mailadres zijn', must_be_valid_url: 'Moet een geldige URL zijn (beginnend met http:// of https://)', must_be_valid_zip: 'Moet een geldige ZIP zijn (XXXXX of XXXXX-XXXX)', + must_be_valid_iban: 'Moet een geldig IBAN zijn', must_contain_numbers_only: 'Mag alleen cijfers bevatten', must_contain_letters_only: 'Mag alleen letters bevatten', complete_all_required_fields_to_proceed_with_identity_verification: 'Vul alle verplichte velden in om door te gaan met de identiteitsverificatie.', @@ -1322,6 +1333,7 @@ const ar = { must_be_valid_email: 'يجب أن يكون عنوان بريد إلكتروني صالحًا', must_be_valid_url: 'يجب أن يكون عنوان URL صالحًا (يبدأ بـ http:// أو https://)', must_be_valid_zip: 'يجب أن يكون ZIP صالحًا (XXXXX أو XXXXX-XXXX)', + must_be_valid_iban: 'يجب أن يكون IBAN صالحًا', must_contain_numbers_only: 'يجب أن يحتوي على أرقام فقط', must_contain_letters_only: 'يجب أن يحتوي على أحرف فقط', complete_all_required_fields_to_proceed_with_identity_verification: 'أكمل جميع الحقول المطلوبة للمتابعة في التحقق من الهوية.', @@ -1441,6 +1453,7 @@ const ko = { must_be_valid_email: '유효한 이메일 주소여야 합니다', must_be_valid_url: '유효한 URL이어야 합니다 (http:// 또는 https://로 시작)', must_be_valid_zip: '유효한 ZIP이어야 합니다 (XXXXX 또는 XXXXX-XXXX)', + must_be_valid_iban: '유효한 IBAN이어야 합니다', must_contain_numbers_only: '숫자만 포함해야 합니다', must_contain_letters_only: '문자만 포함해야 합니다', complete_all_required_fields_to_proceed_with_identity_verification: '신원 확인을 진행하려면 모든 필수 필드를 작성하십시오.', @@ -1560,6 +1573,7 @@ const ja = { must_be_valid_email: '有効なメールアドレスである必要があります', must_be_valid_url: '有効なURLである必要があります (http://またはhttps://で始まる)', must_be_valid_zip: '有効なZIPである必要があります (XXXXX または XXXXX-XXXX)', + must_be_valid_iban: '有効なIBANである必要があります', must_contain_numbers_only: '数字のみを含む必要があります', must_contain_letters_only: '文字のみを含む必要があります', complete_all_required_fields_to_proceed_with_identity_verification: '本人確認を進めるには、すべての必須項目を入力してください。', diff --git a/app/javascript/submission_form/kba_step.vue b/app/javascript/submission_form/kba_step.vue index bb2ac672e..27026d558 100644 --- a/app/javascript/submission_form/kba_step.vue +++ b/app/javascript/submission_form/kba_step.vue @@ -483,7 +483,7 @@ export default { this.clearCountdown() try { - const payload = { ...this.form, submitter_slug: this.submitterSlug } + const payload = { ...this.form } if (payload.dob) { payload.dob = payload.dob.replace(/-/g, '') @@ -497,7 +497,7 @@ export default { payload.phone = payload.phone.replace(/^\+1/, '') } - const resp = await fetch(this.baseUrl + '/api/kba', { + const resp = await fetch(this.baseUrl + `/s/${this.submitterSlug}/kba`, { method: 'POST', body: JSON.stringify(payload), ...this.fetchOptions, @@ -548,13 +548,12 @@ export default { }, {}) try { - const resp = await fetch(this.baseUrl + `/api/kba/${this.field.uuid}`, { + const resp = await fetch(this.baseUrl + `/s/${this.submitterSlug}/kba/${this.field.uuid}`, { method: 'PUT', body: JSON.stringify({ token: this.token, answers: formattedAnswers, - reference: this.reference, - submitter_slug: this.submitterSlug + reference: this.reference }), ...this.fetchOptions, headers: { 'Content-Type': 'application/json', ...this.fetchOptions.headers } diff --git a/app/javascript/submission_form/payment_step.vue b/app/javascript/submission_form/payment_step.vue index 44d516bdc..8ba8e7437 100644 --- a/app/javascript/submission_form/payment_step.vue +++ b/app/javascript/submission_form/payment_step.vue @@ -156,7 +156,7 @@ export default { return this.provider === 'paypal' }, paymentEndpoint () { - return this.isPaypal ? '/api/paypal_payments' : '/api/stripe_payments' + return `/s/${this.submitterSlug}/${this.isPaypal ? 'paypal_payments' : 'stripe_payments'}` }, sessionId () { return this.queryParams.get('stripe_session_id') || this.queryParams.get('paypal_order_id') || (this.isPaypal && this.queryParams.get('token')) @@ -255,9 +255,6 @@ export default { if (this.sessionId) { return fetch(this.baseUrl + this.paymentEndpoint + '/' + this.sessionId, { method: 'PUT', - body: JSON.stringify({ - submitter_slug: this.submitterSlug - }), ...this.fetchOptions, headers: { 'Content-Type': 'application/json', ...this.fetchOptions.headers } }).then(async (resp) => { @@ -294,7 +291,6 @@ export default { fetch(this.baseUrl + this.paymentEndpoint, { method: 'POST', body: JSON.stringify({ - submitter_slug: this.submitterSlug, field_uuid: this.field.uuid, check_status: checkStatus, referer: this.normalizedPaymentUrl() diff --git a/app/javascript/submission_form/phone_step.vue b/app/javascript/submission_form/phone_step.vue index c3e90fb3a..fae2d5bfb 100644 --- a/app/javascript/submission_form/phone_step.vue +++ b/app/javascript/submission_form/phone_step.vue @@ -285,7 +285,7 @@ export default { }, 1000) }, sendVerificationCode () { - return fetch(this.baseUrl + '/api/send_phone_verification_code', { + const options = { method: 'POST', body: JSON.stringify({ submitter_slug: this.submitterSlug, @@ -293,6 +293,14 @@ export default { phone: this.fullInternationalPhoneValue }), headers: { 'Content-Type': 'application/json' } + } + + return fetch(this.baseUrl + `/s/${this.submitterSlug}/send_phone_verification_code`, options).then((resp) => { + if (resp.status === 404) { + return fetch(this.baseUrl + '/api/send_phone_verification_code', options) + } else { + return resp + } }).then(async (resp) => { if ([422, 429].includes(resp.status)) { const data = await resp.json() diff --git a/app/javascript/submission_form/text_step.vue b/app/javascript/submission_form/text_step.vue index c8695c268..dbb9e3275 100644 --- a/app/javascript/submission_form/text_step.vue +++ b/app/javascript/submission_form/text_step.vue @@ -34,6 +34,7 @@