diff --git a/.gitattributes b/.gitattributes index 30f0c8e8..28cee3ff 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,6 +1 @@ *.html linguist-detectable=false - -# Upstream-merge ergonomics. Lockfiles are regenerated post-merge rather -# than merged line-by-line — see REBRANDING.md "Sync workflow". -Gemfile.lock -merge -yarn.lock -merge diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1008f3d3..77d912e4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,22 +1,8 @@ --- name: CI on: [push] -permissions: read-all jobs: - rebrand_check: - name: Rebrand check - runs-on: ubuntu-latest - timeout-minutes: 5 - steps: - - uses: actions/checkout@v4 - - name: Install Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: 4.0.1 - - name: Run rebrand-check - run: ruby bin/rebrand-check - rubocop: name: Rubocop runs-on: ubuntu-latest @@ -74,12 +60,12 @@ jobs: steps: - uses: actions/checkout@v4 - name: Install Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v1 with: node-version: 20.19.0 - name: Cache directory path id: yarn-cache-dir-path - run: echo "dir=$(yarn cache dir)" >> "$GITHUB_OUTPUT" + run: echo "::set-output name=dir::$(yarn cache dir)" - uses: actions/cache@v4 id: yarn-cache with: @@ -133,7 +119,7 @@ jobs: env: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres - POSTGRES_DB: wabosign_test + POSTGRES_DB: docuseal_test ports: ["5432:5432"] options: >- --health-cmd pg_isready @@ -148,7 +134,7 @@ jobs: with: ruby-version: 4.0.5 - name: Set up Node - uses: actions/setup-node@v4 + uses: actions/setup-node@v1 with: node-version: 20.19.0 - name: Install Chrome @@ -185,7 +171,7 @@ jobs: RAILS_ENV: test NODE_ENV: test COVERAGE: true - DATABASE_URL: postgres://postgres:postgres@localhost:5432/wabosign_test + DATABASE_URL: postgres://postgres:postgres@localhost:5432/docuseal_test run: | bundle exec rake db:create bundle exec rake db:migrate diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 0bf637dc..2eb09ddc 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -9,24 +9,19 @@ jobs: build: runs-on: ubuntu-24.04-arm timeout-minutes: 30 - permissions: - contents: read - packages: write steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v3 with: submodules: recursive - name: Docker meta id: meta - uses: docker/metadata-action@v5 + uses: docker/metadata-action@v4 with: - images: ghcr.io/wabolabs/wabosign - tags: | - type=semver,pattern={{version}} - type=raw,value=latest + images: docuseal/docuseal + tags: type=semver,pattern={{version}} - name: Set up QEMU uses: docker/setup-qemu-action@v3 @@ -37,12 +32,11 @@ jobs: - name: Create .version file run: echo ${{ github.ref_name }} > .version - - name: Login to GitHub Container Registry + - name: Login to Docker Hub uses: docker/login-action@v3 with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and push Docker image uses: docker/build-push-action@v6 @@ -51,7 +45,3 @@ jobs: push: true platforms: linux/amd64,linux/arm64 tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - build-args: | - VERSION=${{ github.ref_name }} - REVISION=${{ github.sha }} diff --git a/.gitignore b/.gitignore index e955dfa0..bf8764f1 100644 --- a/.gitignore +++ b/.gitignore @@ -38,4 +38,3 @@ yarn-debug.log* /ee dump.rdb *.onnx -/vendor diff --git a/.rubocop.yml b/.rubocop.yml index 65898a22..0e332ff6 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -24,10 +24,6 @@ Metrics/BlockLength: Style/Documentation: Enabled: false -Rails/Exit: - Exclude: - - spec/rails_helper.rb - Lint/MissingSuper: Enabled: false diff --git a/.version b/.version index f0bb29e7..e69de29b 100644 --- a/.version +++ b/.version @@ -1 +0,0 @@ -1.3.0 diff --git a/Dockerfile b/Dockerfile index 7885b29f..8b49ae14 100644 --- a/Dockerfile +++ b/Dockerfile @@ -42,26 +42,14 @@ RUN echo "gem 'shakapacker'" > Gemfile && ./bin/shakapacker FROM ruby:4.0.5-alpine AS app -ARG VERSION="dev" -ARG REVISION="unknown" - -LABEL org.opencontainers.image.title="WaboSign" \ - org.opencontainers.image.description="Self-hosted, open-source document filling and signing platform — a WaboSign fork of DocuSeal." \ - org.opencontainers.image.url="https://github.com/wabolabs/wabosign" \ - org.opencontainers.image.source="https://github.com/wabolabs/wabosign" \ - org.opencontainers.image.documentation="https://github.com/wabolabs/wabosign#readme" \ - org.opencontainers.image.vendor="WaboLabs" \ - org.opencontainers.image.licenses="AGPL-3.0-or-later" \ - org.opencontainers.image.version="${VERSION}" \ - org.opencontainers.image.revision="${REVISION}" - ENV RAILS_ENV=production ENV BUNDLE_WITHOUT="development:test" ENV OPENSSL_CONF=/etc/openssl_legacy.cnf WORKDIR /app -RUN apk add --no-cache libpq vips redis onnxruntime +RUN apk add --no-cache libpq vips redis onnxruntime && \ + rm -f /usr/bin/onnx_test_runner /usr/bin/onnxruntime_test RUN addgroup -g 2000 wabosign && adduser -u 2000 -G wabosign -s /bin/sh -D -h /home/wabosign wabosign diff --git a/Gemfile b/Gemfile index bc770a36..36783f0d 100644 --- a/Gemfile +++ b/Gemfile @@ -21,14 +21,10 @@ gem 'faraday' gem 'faraday-follow_redirects' gem 'google-cloud-storage', require: false gem 'hexapdf' -gem 'image_processing' gem 'jwt', require: false gem 'lograge' gem 'numo-narray-alt', require: false gem 'oj' -gem 'omniauth', '~> 2.1' -gem 'omniauth-google-oauth2', '~> 1.2' -gem 'omniauth-rails_csrf_protection', '~> 1.0' gem 'onnxruntime', require: false gem 'pagy' gem 'pg', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 8d15dd29..933cf9a8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -264,9 +264,6 @@ GEM strscan (>= 3.1.2) i18n (1.14.8) concurrent-ruby (~> 1.0) - image_processing (1.14.0) - mini_magick (>= 4.9.5, < 6) - ruby-vips (>= 2.0.17, < 3) io-console (0.8.2) irb (1.18.0) pp (>= 0.6.0) @@ -274,7 +271,7 @@ GEM rdoc (>= 4.0.0) reline (>= 0.4.2) jmespath (1.6.2) - json (2.19.5) + json (2.19.7) jwt (3.2.0) base64 language_server-protocol (3.17.0.5) @@ -308,8 +305,6 @@ GEM marcel (1.1.0) matrix (0.4.3) method_source (1.1.0) - mini_magick (5.3.1) - logger mini_mime (1.1.5) minitest (6.0.6) drb (~> 2.0) @@ -434,7 +429,7 @@ GEM erb psych (>= 4.0.0) tsort - redis-client (0.28.0) + redis-client (0.29.0) connection_pool regexp_parser (2.11.3) reline (0.6.3) @@ -516,12 +511,12 @@ GEM rack-proxy (>= 0.6.1) railties (>= 5.2) semantic_range (>= 2.3.0) - sidekiq (8.1.2) + sidekiq (8.1.6) connection_pool (>= 3.0.0) json (>= 2.16.0) logger (>= 1.7.0) rack (>= 3.2.0) - redis-client (>= 0.26.0) + redis-client (>= 0.29.0) signet (0.21.0) addressable (~> 2.8) faraday (>= 0.17.5, < 3.a) @@ -624,7 +619,6 @@ DEPENDENCIES foreman google-cloud-storage hexapdf - image_processing jwt letter_opener_web lograge diff --git a/LICENSE_ADDITIONAL_TERMS b/LICENSE_ADDITIONAL_TERMS index dfbee5d2..cac2a50b 100644 --- a/LICENSE_ADDITIONAL_TERMS +++ b/LICENSE_ADDITIONAL_TERMS @@ -1,17 +1,5 @@ Additional Terms In accordance with Section 7(b) of the GNU Affero General Public License, -covered works derived from this software must retain attribution to: - - 1. WaboSign — https://sign.wabo.cc — https://github.com/wabolabs/wabosign - 2. DocuSeal (the upstream from which WaboSign is forked) — - https://github.com/docusealco/docuseal - -Attribution must remain visible in interactive user interfaces (e.g. footer, -About page, generated emails, audit-trail PDFs, signing-completion screens). - -WaboSign itself complies with this requirement by displaying both the -WaboSign and DocuSeal credits in the "Powered by" footer (see -app/views/shared/_powered_by.html.erb), in email attributions (see -app/views/shared/_email_attribution.html.erb), in generated PDF audit -trails, and in this repository's README and NOTICE files. +a covered work must retain the original DocuSeal attribution in interactive +user interfaces. diff --git a/README.md b/README.md index a842638d..64a917ad 100644 --- a/README.md +++ b/README.md @@ -1,77 +1,107 @@

- - WaboSign + + DocuSeal
- WaboSign + DocuSeal

- Self-hosted document filling and signing + Open source document filling and signing

- +

+ + Docker releases + + + + + + Follow @docusealco + +

-WaboSign is a self-hosted, open-source platform for secure digital document signing and processing. Create PDF forms, fill them in online from any device, and collect signatures with an easy-to-use, mobile-optimized web tool. +DocuSeal is an open source platform that provides secure and efficient digital document signing and processing. Create PDF forms to have them filled and signed online on any device with an easy-to-use, mobile-optimized web tool.

+

+ ✨ Live Demo + | + ☁️ Try in Cloud +

-WaboSign is a fork of [DocuSeal](https://github.com/docusealco/docuseal) under AGPLv3, with the upstream's "Pro" feature paywall removed so that every shipped capability is available out of the box on a self-hosted deployment. +[![Demo](https://github.com/docusealco/docuseal/assets/5418788/d8703ea3-361a-423f-8bfe-eff1bd9dbe14)](https://demo.docuseal.tech) ## Features - - PDF form fields builder (WYSIWYG) -- 12 field types (Signature, Date, File, Checkbox, Phone, Verification, etc.) +- 12 field types available (Signature, Date, File, Checkbox etc.) - Multiple submitters per document - Automated emails via SMTP -- File storage on disk or AWS S3, Google Storage, Azure Blob +- Files storage on disk or AWS S3, Google Storage, Azure Cloud - Automatic PDF eSignature - PDF signature verification -- User management and roles -- Mobile-optimized signing flow -- 14 UI languages -- API + Webhooks for integrations -- SMS invitations via [BulkVS, Twilio, VoIP.ms, or SignalWire](SMS.md) -- Bulk send via CSV / XLSX import -- Google Workspace SSO ([setup guide](GOOGLE_SSO.md)) +- Users management +- Mobile-optimized +- 7 UI languages with signing available in 14 languages +- API and Webhooks for integrations +- Easy to deploy in minutes + +## Pro Features +- Company logo and white-label +- User roles +- Automated reminders +- Invitation and identity verification via SMS - Conditional fields and formulas -- Custom branding (logo, colors, reply-to) -- Easy Docker deployment +- Bulk send with CSV, XLSX spreadsheet import +- SSO / SAML +- Template creation with HTML API ([Guide](https://www.docuseal.com/guides/create-pdf-document-fillable-form-with-html-api)) +- Template creation with PDF or DOCX and field tags API ([Guide](https://www.docuseal.com/guides/use-embedded-text-field-tags-in-the-pdf-to-create-a-fillable-form)) +- Embedded signing form ([React](https://github.com/docusealco/docuseal-react), [Vue](https://github.com/docusealco/docuseal-vue), [Angular](https://github.com/docusealco/docuseal-angular) or [JavaScript](https://www.docuseal.com/docs/embedded)) +- Embedded document form builder ([React](https://github.com/docusealco/docuseal-react), [Vue](https://github.com/docusealco/docuseal-vue), [Angular](https://github.com/docusealco/docuseal-angular) or [JavaScript](https://www.docuseal.com/docs/embedded)) +- [Learn more](https://www.docuseal.com/pricing) + +## Deploy + +|Heroku|Railway| +|:--:|:---:| +| [Deploy on Heroku](https://heroku.com/deploy?template=https://github.com/docusealco/docuseal-heroku) | [Deploy on Railway](https://railway.com/deploy/IGoDnc?referralCode=ruU7JR)| +|**DigitalOcean**|**Render**| +| [Deploy on DigitalOcean](https://cloud.digitalocean.com/apps/new?repo=https://github.com/docusealco/docuseal-digitalocean/tree/master&refcode=421d50f53990) | [Deploy to Render](https://render.com/deploy?repo=https://github.com/docusealco/docuseal-render) -## Docker +#### Docker ```sh -docker run --name wabosign -p 3000:3000 -v .:/data ghcr.io/wabolabs/wabosign:1.3.2 +docker run --name docuseal -p 3000:3000 -v.:/data docuseal/docuseal ``` -`:latest` always tracks the most recent release; pin a `MAJOR.MINOR.PATCH` tag for reproducible deployments. +By default DocuSeal docker container uses an SQLite database to store data and configurations. Alternatively, it is possible to use PostgreSQL or MySQL databases by specifying the `DATABASE_URL` env variable. -By default the container uses SQLite for data. Point at PostgreSQL or MySQL by setting `DATABASE_URL`. - -### Docker Compose +#### Docker Compose +Download docker-compose.yml into your private server: ```sh -sudo HOST=sign.example.com docker compose up +curl https://raw.githubusercontent.com/docusealco/docuseal/master/docker-compose.yml > docker-compose.yml ``` -Make sure your DNS points at the server so Caddy can issue an SSL cert automatically. - -## Authentication +Run the app under a custom domain over https using docker compose (make sure your DNS points to the server to automatically issue ssl certs with Caddy): +```sh +sudo HOST=your-domain-name.com docker compose up +``` -WaboSign ships with email + password (Devise) and TOTP two-factor auth out of the box. Google Workspace SSO can be enabled by setting three environment variables — see [GOOGLE_SSO.md](GOOGLE_SSO.md) for the full operator guide. +## For Businesses +### Integrate seamless document signing into your web or mobile apps with DocuSeal -## Releases +At DocuSeal we have expertise and technologies to make documents creation, filling, signing and processing seamlessly integrated with your product. We specialize in working with various industries, including **Banking, Healthcare, Transport, Real Estate, eCommerce, KYC, CRM, and other software products** that require bulk document signing. By leveraging DocuSeal, we can assist in reducing the overall cost of developing and processing electronic documents while ensuring security and compliance with local electronic document laws. -- **Current release:** 1.3.2 — see [CHANGELOG.md](CHANGELOG.md). -- **Container image:** `ghcr.io/wabolabs/wabosign:1.3.2` (or `:latest`). -- **Versioning:** `MAJOR.MINOR.PATCH` per [semver.org](https://semver.org). -- **Tagging triggers a build:** pushing a `MAJOR.MINOR.PATCH` git tag runs [`.github/workflows/docker.yml`](.github/workflows/docker.yml), which builds `linux/amd64` + `linux/arm64` and pushes to GHCR. +[Book a Meeting](https://www.docuseal.com/contact) ## License -WaboSign is distributed under the [GNU Affero General Public License v3.0](LICENSE), with the §7(b) [Additional Terms](LICENSE_ADDITIONAL_TERMS) preserved from upstream. - -WaboSign is a fork of [DocuSeal](https://github.com/docusealco/docuseal) © 2023–2026 DocuSeal LLC. The upstream attribution required by §7(b) is preserved in interactive UIs and in the [NOTICE](NOTICE) file. Modifications © 2026 the WaboSign authors. +Distributed under the AGPLv3 License with Section 7(b) Additional Terms. See [LICENSE](https://github.com/docusealco/docuseal/blob/master/LICENSE) and [LICENSE_ADDITIONAL_TERMS](https://github.com/docusealco/docuseal/blob/master/LICENSE_ADDITIONAL_TERMS) for more information. +Unless otherwise noted, all files © 2023-2026 DocuSeal LLC. -## Acknowledgements +## Tools -This software builds on the substantial work of the [DocuSeal](https://github.com/docusealco/docuseal) team. Their open-source release made this fork possible. WaboSign retains the embedding SDK contract (``, `@docuseal/react`, `@docuseal/vue`, `@docuseal/angular`) so existing DocuSeal embedding code continues to work. +- [Signature Maker](https://www.docuseal.com/online-signature) +- [Sign Document Online](https://www.docuseal.com/sign-documents-online) +- [Fill PDF Online](https://www.docuseal.com/fill-pdf) diff --git a/SECURITY.md b/SECURITY.md index f4b8c160..fe863c52 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,12 +1,14 @@ # Reporting a Vulnerability -If you discover a security concern or vulnerability in WaboSign, please report it privately by email to **wabosign@wabo.cc** rather than opening a public GitHub issue. We will acknowledge receipt, work with you to validate the report, and ship a fix. +If you come across any security concern or vulnarability, please report the information via email to security@wabosign.com instead of opening a GitHub issue. We will promptly respond and will collaborate with you to validate the issue, and resolve it ASAP. -## Out of scope +**We have a bug bounty program to reward security researchers.** + +Out of scope vulnerabilities: - CSRF - DNSSEC, CAA, CSP headers - DNS or email security related -- Rate limiting +- Rate Limiting -We reserve the right to classify any reported issue as out of scope. +Note: We reserve the right to classify any reported vulnerability as out of scope for the bug bounty program. diff --git a/app/controllers/api/attachments_controller.rb b/app/controllers/api/attachments_controller.rb index 1d24f8ca..2bb0db92 100644 --- a/app/controllers/api/attachments_controller.rb +++ b/app/controllers/api/attachments_controller.rb @@ -11,8 +11,6 @@ module Api @submitter = Submitter.find_by!(slug: params[:submitter_slug]) unless can_upload?(@submitter) - Rollbar.error("Can't upload: #{@submitter.id}") if defined?(Rollbar) - return render json: { error: I18n.t('form_has_been_archived') }, status: :unprocessable_content end @@ -33,9 +31,11 @@ module Api return render json: { error: "#{params[:type]} error, try to sign on another device" }, status: :unprocessable_content end + + metadata = { analyzed: true, identified: true, width: image.width, height: image.height } end - attachment = Submitters.create_attachment!(@submitter, file) + attachment = Submitters.create_attachment!(@submitter, file, metadata:) if params[:remember_signature] == 'true' && @submitter.email.present? cookies.encrypted[:signature_uuids] = build_new_cookie_signatures_json(@submitter, attachment) diff --git a/app/controllers/api/templates_controller.rb b/app/controllers/api/templates_controller.rb index 87b756ea..b3d05cec 100644 --- a/app/controllers/api/templates_controller.rb +++ b/app/controllers/api/templates_controller.rb @@ -9,20 +9,7 @@ module Api templates = paginate(templates.preload(:author, folder: :parent_folder)) - schema_documents = - ActiveStorage::Attachment.where(record_id: templates.map(&:id), - record_type: 'Template', - name: :documents, - uuid: templates.flat_map { |t| t.schema.pluck('attachment_uuid') }) - .preload(:blob) - - preview_image_attachments = - ActiveStorage::Attachment.joins(:blob) - .where(blob: { filename: ['0.png', '0.jpg'] }) - .where(record_id: schema_documents.map(&:id), - record_type: 'ActiveStorage::Attachment', - name: :preview_images) - .preload(:blob) + schema_documents, dynamic_documents, preview_image_attachments = preload_relations(templates) expires_at = Accounts.link_expires_at(current_account) @@ -30,6 +17,7 @@ module Api data: templates.map do |t| Templates::SerializeForApi.call(t, schema_documents: schema_documents.select { |e| e.record_id == t.id }, + dynamic_documents:, preview_image_attachments:, expires_at:) end, @@ -88,6 +76,41 @@ module Api private + def preload_relations(templates) + schema_documents = + ActiveStorage::Attachment.where(record_id: templates.map(&:id), + record_type: 'Template', + name: :documents, + uuid: templates.flat_map { |t| t.schema.pluck('attachment_uuid') }) + .preload(:blob) + + dynamic_document_uuids = + templates.flat_map { |t| t.schema.select { |item| item['dynamic'] }.pluck('attachment_uuid') } + + dynamic_documents = + if dynamic_document_uuids.present? + DynamicDocument.where(template: templates.map(&:id)) + .where(uuid: dynamic_document_uuids) + .preload(current_version: { document_attachment: :blob }) + .select(:id, :uuid, :template_id, :sha1, :created_at, :updated_at) + else + DynamicDocument.none + end + + preview_attachment_ids = + schema_documents.map(&:id) + dynamic_documents.filter_map { |d| d.current_version&.document_attachment&.id } + + preview_image_attachments = + ActiveStorage::Attachment.joins(:blob) + .where(blob: { filename: ['0.png', '0.jpg'] }) + .where(record_id: preview_attachment_ids, + record_type: 'ActiveStorage::Attachment', + name: :preview_images) + .preload(:blob) + + [schema_documents, dynamic_documents, preview_image_attachments] + end + def filter_templates(templates, params) templates = Templates.search(current_user, templates, params[:q]) templates = params[:archived].in?(['true', true]) ? templates.archived : templates.active diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index b1d488d9..a2e17cda 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -121,6 +121,12 @@ class ApplicationController < ActionController::Base Wabosign.default_url_options[:host] end + def maybe_redirect_com + return if request.domain != 'wabosign.co' + + redirect_to request.url.gsub('.co/', '.com/'), allow_other_host: true, status: :moved_permanently + end + def set_csp request.content_security_policy = current_content_security_policy.tap do |policy| policy.default_src :self diff --git a/app/controllers/console_redirect_controller.rb b/app/controllers/console_redirect_controller.rb new file mode 100644 index 00000000..b4d17ea0 --- /dev/null +++ b/app/controllers/console_redirect_controller.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +class ConsoleRedirectController < ApplicationController + skip_before_action :authenticate_user! + skip_authorization_check + + def index + if request.path == '/upgrade' + params[:redir] = Wabosign.multitenant? ? "#{Wabosign::CONSOLE_URL}/plans" : "#{Wabosign::CONSOLE_URL}/on_premises" + end + + params[:redir] = "#{Wabosign::CONSOLE_URL}/manage" if request.path == '/manage' + + return redirect_to(new_user_session_path({ redir: params[:redir] }.compact)) if true_user.blank? + + auth = JsonWebToken.encode(uuid: true_user.uuid, + scope: :console, + exp: 1.minute.from_now.to_i) + + redir_uri = Addressable::URI.parse(params[:redir]) + path = redir_uri.path if params[:redir].to_s.starts_with?(Wabosign::CONSOLE_URL) + + redirect_to "#{Wabosign::CONSOLE_URL}#{path}?#{{ **redir_uri&.query_values, 'auth' => auth }.to_query}", + allow_other_host: true + end +end diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index dd37a681..841c20dd 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -28,7 +28,6 @@ class DashboardController < ApplicationController def maybe_redirect_mfa_setup return unless signed_in? return if current_user.otp_required_for_login - return if session[:bypass_otp_for_sso] || current_user.signed_in_via_sso? return if !current_user.otp_required_for_login && !AccountConfig.exists?(value: true, account_id: current_user.account_id, diff --git a/app/controllers/embed_scripts_controller.rb b/app/controllers/embed_scripts_controller.rb index 5a9bf9ff..ed5d4483 100644 --- a/app/controllers/embed_scripts_controller.rb +++ b/app/controllers/embed_scripts_controller.rb @@ -1,13 +1,18 @@ # frozen_string_literal: true class EmbedScriptsController < ActionController::Metal - DUMMY_SCRIPT = <<~JAVASCRIPT + DUMMY_SCRIPT = <<~JAVASCRIPT.freeze const DummyBuilder = class extends HTMLElement { connectedCallback() { this.innerHTML = `
-

Embedded components not loaded

-

The embed assets could not be served from this WaboSign instance. Check that the host is reachable and that the embed script is being served from the same origin.

+

Upgrade to Pro

+

Unlock embedded components by upgrading to Pro

+
+ + Learn More + +
`; } @@ -15,11 +20,11 @@ class EmbedScriptsController < ActionController::Metal const DummyForm = class extends DummyBuilder {}; - if (!window.customElements.get('wabosign-builder')) { + if (!window.customElements.get('docuseal-builder')) { window.customElements.define('docuseal-builder', DummyBuilder); } - if (!window.customElements.get('wabosign-form')) { + if (!window.customElements.get('docuseal-form')) { window.customElements.define('docuseal-form', DummyForm); } JAVASCRIPT diff --git a/app/controllers/enquiries_controller.rb b/app/controllers/enquiries_controller.rb new file mode 100644 index 00000000..27a307cb --- /dev/null +++ b/app/controllers/enquiries_controller.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +class EnquiriesController < ApplicationController + skip_before_action :authenticate_user! + skip_authorization_check + + def create + if params[:talk_to_sales] == 'on' + Faraday.post(Wabosign::ENQUIRIES_URL, + enquiry_params.merge(type: :talk_to_sales).to_json, + 'Content-Type' => 'application/json') + end + + head :ok + end + + private + + def enquiry_params + params.require(:user).permit(:email) + end +end diff --git a/app/controllers/errors_controller.rb b/app/controllers/errors_controller.rb index 02dccc7a..5f29ed61 100644 --- a/app/controllers/errors_controller.rb +++ b/app/controllers/errors_controller.rb @@ -1,12 +1,34 @@ # frozen_string_literal: true class ErrorsController < ActionController::Base + ENTERPRISE_FEATURE_MESSAGE = + 'This feature is available in Pro Edition: https://www.wabosign.com/pricing' + + ENTERPRISE_PATHS = [ + '/submissions/html', + '/api/submissions/html', + '/templates/html', + '/api/templates/html', + '/submissions/pdf', + '/api/submissions/pdf', + '/templates/pdf', + '/api/templates/pdf', + '/templates/doc', + '/api/templates/doc', + '/templates/docx', + '/api/templates/docx' + ].freeze + SAFE_ERROR_MESSAGE_CLASSES = [ ActionDispatch::Http::Parameters::ParseError, JSON::ParserError ].freeze def show + if request.original_fullpath.in?(ENTERPRISE_PATHS) && error_status_code == 404 + return render json: { status: 404, message: ENTERPRISE_FEATURE_MESSAGE }, status: :not_found + end + respond_to do |f| f.json do set_cors_headers diff --git a/app/controllers/esign_settings_controller.rb b/app/controllers/esign_settings_controller.rb index 5e6685ac..c1c74843 100644 --- a/app/controllers/esign_settings_controller.rb +++ b/app/controllers/esign_settings_controller.rb @@ -11,6 +11,8 @@ class EsignSettingsController < ApplicationController end end + prepend_before_action :maybe_redirect_com, only: %i[show] + before_action :load_encrypted_config authorize_resource :encrypted_config, parent: false, only: %i[new create] authorize_resource :encrypted_config, only: %i[update destroy show] diff --git a/app/controllers/mcp_controller.rb b/app/controllers/mcp_controller.rb index 51bc165c..0ee39415 100644 --- a/app/controllers/mcp_controller.rb +++ b/app/controllers/mcp_controller.rb @@ -47,8 +47,7 @@ class McpController < ActionController::API end def user_from_api_key - auth = request.headers['Authorization'].to_s - token = auth.start_with?('Bearer ') ? auth[7..].strip.presence : nil + token = request.headers['Authorization'].to_s[/\ABearer\s+(.+)\z/, 1] return if token.blank? diff --git a/app/controllers/mfa_setup_controller.rb b/app/controllers/mfa_setup_controller.rb index 1065fcfb..9e12a445 100644 --- a/app/controllers/mfa_setup_controller.rb +++ b/app/controllers/mfa_setup_controller.rb @@ -20,8 +20,7 @@ class MfaSetupController < ApplicationController redirect_to settings_profile_index_path, notice: I18n.t('2fa_has_been_configured') else - @provision_url = current_user.otp_provisioning_uri(current_user.email, - issuer: Wabosign.branded_product_name(current_account)) + @provision_url = current_user.otp_provisioning_uri(current_user.email, issuer: Wabosign.product_name) @error_message = I18n.t('code_is_invalid') @@ -50,7 +49,6 @@ class MfaSetupController < ApplicationController current_user.save! - @provision_url = current_user.otp_provisioning_uri(current_user.email, - issuer: Wabosign.branded_product_name(current_account)) + @provision_url = current_user.otp_provisioning_uri(current_user.email, issuer: Wabosign.product_name) end end diff --git a/app/controllers/newsletters_controller.rb b/app/controllers/newsletters_controller.rb new file mode 100644 index 00000000..2b3ba0aa --- /dev/null +++ b/app/controllers/newsletters_controller.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +class NewslettersController < ApplicationController + skip_authorization_check + + def show; end + + def update + Faraday.post(Wabosign::NEWSLETTER_URL, newsletter_params.to_json, 'Content-Type' => 'application/json') + rescue StandardError => e + Rails.logger.error(e) + ensure + redirect_to root_path + end + + private + + def newsletter_params + params.require(:user).permit(:email) + end +end diff --git a/app/controllers/personalization_settings_controller.rb b/app/controllers/personalization_settings_controller.rb index 86c97cad..600e3bbb 100644 --- a/app/controllers/personalization_settings_controller.rb +++ b/app/controllers/personalization_settings_controller.rb @@ -2,7 +2,6 @@ class PersonalizationSettingsController < ApplicationController ALLOWED_KEYS = [ - AccountConfig::BRAND_NAME_KEY, AccountConfig::FORM_COMPLETED_BUTTON_KEY, AccountConfig::SUBMITTER_INVITATION_EMAIL_KEY, AccountConfig::SUBMITTER_INVITATION_REMINDER_EMAIL_KEY, diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 8cb95548..2f94ea4c 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -22,15 +22,14 @@ class SessionsController < Devise::SessionsController super end - def destroy - session.delete(:bypass_otp_for_sso) - super - end - private def after_sign_in_path_for(...) - return params[:redir] if params[:redir].present? + if params[:redir].present? + return console_redirect_index_path(redir: params[:redir]) if params[:redir].starts_with?(Wabosign::CONSOLE_URL) + + return params[:redir] + end super end diff --git a/app/controllers/setup_controller.rb b/app/controllers/setup_controller.rb index ee8dd7ce..db506928 100644 --- a/app/controllers/setup_controller.rb +++ b/app/controllers/setup_controller.rb @@ -40,7 +40,7 @@ class SetupController < ApplicationController sign_in(@user) - redirect_to root_path + redirect_to newsletter_path else render :index, status: :unprocessable_content end diff --git a/app/controllers/sms_settings_controller.rb b/app/controllers/sms_settings_controller.rb index 6b7b7d5d..96168605 100644 --- a/app/controllers/sms_settings_controller.rb +++ b/app/controllers/sms_settings_controller.rb @@ -1,67 +1,16 @@ # frozen_string_literal: true class SmsSettingsController < ApplicationController - SECRET_KEYS = %w[basic_auth_token twilio_auth_token voipms_api_password signalwire_api_token].freeze - before_action :load_encrypted_config authorize_resource :encrypted_config, only: :index - authorize_resource :encrypted_config, parent: false, only: %i[create test_message] + authorize_resource :encrypted_config, parent: false, except: :index def index; end - def create - new_value = build_sms_value - - if @encrypted_config.update(value: new_value) - redirect_to settings_sms_path, notice: I18n.t('changes_have_been_saved') - else - render :index, status: :unprocessable_content - end - rescue StandardError => e - flash[:alert] = e.message - render :index, status: :unprocessable_content - end - - def test_message - to = params[:phone].to_s.strip - if to.blank? - flash[:alert] = 'Enter a phone number to test against.' - return redirect_to(settings_sms_path) - end - - Sms.send_message(account: current_account, - to: to, - text: "Test SMS from #{Wabosign.branded_product_name(current_account)}.") - - redirect_to settings_sms_path, notice: "Test SMS dispatched to #{to}." - rescue Sms::Error => e - redirect_to settings_sms_path, alert: "Test failed: #{e.message}" - rescue StandardError => e - redirect_to settings_sms_path, alert: "Unexpected error: #{e.message}" - end - private def load_encrypted_config @encrypted_config = - EncryptedConfig.find_or_initialize_by(account: current_account, - key: EncryptedConfig::SMS_CONFIGS_KEY) - end - - def build_sms_value - submitted = params.require(:encrypted_config).permit(value: {})[:value].to_h - existing = @encrypted_config.value || {} - - # Password fields are rendered without their saved value, so an unedited - # submit posts back an empty string. Preserve the saved secret in that case - # so users can edit unrelated fields without re-pasting credentials. - SECRET_KEYS.each do |key| - submitted[key] = existing[key] if submitted[key].to_s.empty? - end - - submitted['enabled'] = submitted['enabled'].to_s == '1' || submitted['enabled'].to_s == 'true' - submitted['provider'] = (submitted['provider'].presence || 'bulkvs').to_s - - submitted.compact + EncryptedConfig.find_or_initialize_by(account: current_account, key: 'sms_configs') end end diff --git a/app/controllers/sso_settings_controller.rb b/app/controllers/sso_settings_controller.rb index 538d6792..3e9b6bba 100644 --- a/app/controllers/sso_settings_controller.rb +++ b/app/controllers/sso_settings_controller.rb @@ -3,45 +3,13 @@ class SsoSettingsController < ApplicationController before_action :load_encrypted_config authorize_resource :encrypted_config, only: :index - authorize_resource :encrypted_config, parent: false, only: :create def index; end - def create - new_value = build_sso_value - - if @encrypted_config.update(value: new_value) - redirect_to settings_sso_index_path, notice: I18n.t('changes_have_been_saved') - else - render :index, status: :unprocessable_content - end - rescue StandardError => e - flash[:alert] = e.message - render :index, status: :unprocessable_content - end - private def load_encrypted_config @encrypted_config = - EncryptedConfig.find_or_initialize_by(account: current_account, - key: EncryptedConfig::GOOGLE_SSO_KEY) - end - - def build_sso_value - submitted = params.require(:encrypted_config).permit(value: {})[:value].to_h - existing = @encrypted_config.value || {} - - # Don't clobber the saved secret with a blank one — the field is - # rendered empty (we never echo it back) so an unchanged form would - # otherwise wipe it out. - submitted['client_secret'] = existing['client_secret'] if submitted['client_secret'].to_s.empty? - - submitted['allowed_domains'] = - submitted.delete('allowed_domains_csv').to_s.split(',').map(&:strip).reject(&:empty?) - - submitted['enabled'] = submitted['enabled'].to_s == '1' || submitted['enabled'].to_s == 'true' - - submitted.compact + EncryptedConfig.find_or_initialize_by(account: current_account, key: 'saml_configs') end end diff --git a/app/controllers/start_form_controller.rb b/app/controllers/start_form_controller.rb index ca9cac5e..22fa4ffa 100644 --- a/app/controllers/start_form_controller.rb +++ b/app/controllers/start_form_controller.rb @@ -7,6 +7,7 @@ class StartFormController < ApplicationController skip_authorization_check around_action :with_browser_locale, only: %i[show update completed] + before_action :maybe_redirect_com, only: %i[show completed] before_action :load_resubmit_submitter, only: :update before_action :load_template before_action :authorize_start!, only: :update @@ -82,7 +83,7 @@ class StartFormController < ApplicationController @submitter = Submitter.where(submission: @template.submissions) .where.not(completed_at: nil) - .find_by!(required_params.slice('email', 'phone')) + .find_by!(required_params.except('name')) end private @@ -100,11 +101,18 @@ class StartFormController < ApplicationController def load_resubmit_submitter @resubmit_submitter = if params[:resubmit].present? && !params[:resubmit].in?([true, 'true']) - Submitter.find_by(slug: params[:resubmit]) + submitter = Submitter.find_by(slug: params[:resubmit]) + + submitter if submitter && can_resubmit?(submitter) end end + def can_resubmit?(submitter) + submitter.account.account_configs.find_or_initialize_by(key: AccountConfig::ALLOW_TO_RESUBMIT).value != false + end + def authorize_start! + return redirect_to submit_form_path(@resubmit_submitter.slug) if @resubmit_submitter && @template.archived_at? return redirect_to start_form_path(@template.slug) if @template.archived_at? return if @resubmit_submitter @@ -120,7 +128,7 @@ class StartFormController < ApplicationController required_params = required_fields.index_with { |key| submitter_params[key] } - find_params = required_params.slice('email', 'phone') + find_params = required_params.except('name') submitter = Submitter.new if find_params.compact_blank.blank? diff --git a/app/controllers/submissions_controller.rb b/app/controllers/submissions_controller.rb index 200c3648..2e1bba67 100644 --- a/app/controllers/submissions_controller.rb +++ b/app/controllers/submissions_controller.rb @@ -6,6 +6,8 @@ class SubmissionsController < ApplicationController load_and_authorize_resource :submission, only: %i[show destroy] + prepend_before_action :maybe_redirect_com, only: %i[show] + before_action only: :create do authorize!(:create, Submission) end diff --git a/app/controllers/submissions_preview_controller.rb b/app/controllers/submissions_preview_controller.rb index 48da4354..ed83c407 100644 --- a/app/controllers/submissions_preview_controller.rb +++ b/app/controllers/submissions_preview_controller.rb @@ -5,6 +5,8 @@ class SubmissionsPreviewController < ApplicationController skip_before_action :authenticate_user! skip_authorization_check + prepend_before_action :maybe_redirect_com, only: %i[show completed] + TTL = 40.minutes def show diff --git a/app/controllers/templates_controller.rb b/app/controllers/templates_controller.rb index f32e1e1e..ae54665a 100644 --- a/app/controllers/templates_controller.rb +++ b/app/controllers/templates_controller.rb @@ -1,10 +1,6 @@ # frozen_string_literal: true class TemplatesController < ApplicationController - TEMPLATE_FIELDS = %i[id author_id folder_id external_id name slug - schema fields submitters variables_schema preferences - shared_link source archived_at created_at updated_at].freeze - load_and_authorize_resource :template def show @@ -31,19 +27,7 @@ class TemplatesController < ApplicationController def new; end def edit - ActiveRecord::Associations::Preloader.new( - records: [@template], - associations: [{ schema_documents: [:blob, { preview_images_attachments: :blob }] }] - ).call - - @template_data = - @template.as_json(only: TEMPLATE_FIELDS).merge( - documents: @template.schema_documents.as_json( - only: %i[id uuid], - methods: %i[metadata signed_key], - include: { preview_images: { only: %i[id], methods: %i[url metadata filename] } } - ) - ).to_json + @template_data = Templates.serialize_for_builder(@template) render :edit, layout: 'plain' end diff --git a/app/controllers/templates_detect_fields_controller.rb b/app/controllers/templates_detect_fields_controller.rb index 56b06cd5..a0cd54ff 100644 --- a/app/controllers/templates_detect_fields_controller.rb +++ b/app/controllers/templates_detect_fields_controller.rb @@ -16,7 +16,12 @@ class TemplatesDetectFieldsController < ApplicationController page_number = params[:page].presence&.to_i documents.each do |document| - io = StringIO.new(document.download) + io = + if document.image? + StringIO.new(document.preview_images.joins(:blob).find_by(blob: { filename: ['0.png', '0.jpg'] }).download) + else + StringIO.new(document.download) + end Templates::DetectFields.call(io, attachment: document, page_number:) do |(attachment_uuid, page, fields)| sse.write({ attachment_uuid:, page:, fields: }) diff --git a/app/controllers/templates_preview_controller.rb b/app/controllers/templates_preview_controller.rb index e132b131..602fc622 100644 --- a/app/controllers/templates_preview_controller.rb +++ b/app/controllers/templates_preview_controller.rb @@ -4,18 +4,7 @@ class TemplatesPreviewController < ApplicationController load_and_authorize_resource :template def show - ActiveRecord::Associations::Preloader.new( - records: [@template], - associations: [{ schema_documents: { preview_images_attachments: :blob } }] - ).call - - @template_data = - @template.as_json.merge( - documents: @template.schema_documents.as_json( - methods: %i[metadata signed_key], - include: { preview_images: { methods: %i[url metadata filename] } } - ) - ).to_json + @template_data = Templates.serialize_for_builder(@template) render :show, layout: 'plain' end diff --git a/app/controllers/templates_uploads_controller.rb b/app/controllers/templates_uploads_controller.rb index 7d8b1bae..ea4febf0 100644 --- a/app/controllers/templates_uploads_controller.rb +++ b/app/controllers/templates_uploads_controller.rb @@ -5,7 +5,9 @@ class TemplatesUploadsController < ApplicationController layout 'plain' - def show; end + def show + redirect_to root_path if params[:url].blank? + end def create url_params = create_file_params_from_url if params[:url].present? diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 3e02de85..69b20db5 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -6,8 +6,6 @@ class UsersController < ApplicationController before_action :build_user, only: %i[new create] authorize_resource :user, only: %i[new create] - before_action(only: :index) { authorize!(:manage, current_account) } - def index @users = if params[:status] == 'archived' diff --git a/app/javascript/application.js b/app/javascript/application.js index bec3cc0b..6aa39fc8 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -54,6 +54,7 @@ import GoogleDriveFilePicker from './elements/google_drive_file_picker' import OpenModal from './elements/open_modal' import BarChart from './elements/bar_chart' import FieldCondition from './elements/field_condition' +import ConfirmUpload from './elements/confirm_upload' import * as TurboInstantClick from './lib/turbo_instant_click' @@ -146,6 +147,7 @@ safeRegisterElement('google-drive-file-picker', GoogleDriveFilePicker) safeRegisterElement('open-modal', OpenModal) safeRegisterElement('bar-chart', BarChart) safeRegisterElement('field-condition', FieldCondition) +safeRegisterElement('confirm-upload', ConfirmUpload) safeRegisterElement('template-builder', class extends HTMLElement { connectedCallback () { @@ -197,10 +199,35 @@ safeRegisterElement('template-builder', class extends HTMLElement { } onSubmit = (e) => { - if (e.detail.success && e.detail?.formSubmission?.formElement?.id === 'submitters_form') { - e.detail.fetchResponse.response.json().then((data) => { - this.component.template.submitters = data.submitters - }) + if (e.detail.success) { + if (e.detail?.formSubmission?.formElement?.id === 'submitters_form') { + e.detail.fetchResponse.response.json().then((data) => { + this.component.template.submitters = data.submitters + }) + } + + if (e.detail?.formSubmission?.formElement?.action?.endsWith('/prefillable_fields')) { + e.detail.fetchResponse.response.text().then((data) => { + const doc = new DOMParser().parseFromString(data, 'text/html') + const fragment = doc.querySelector('turbo-stream template').content + + const prefillableUuidsIndex = {} + + fragment.querySelectorAll('[name="field_uuid"]').forEach((field) => { + prefillableUuidsIndex[field.value] = true + }) + + this.component.template.fields.forEach((field) => { + if (prefillableUuidsIndex[field.uuid]) { + field.prefillable = true + field.readonly = true + } else if (field.prefillable) { + delete field.prefillable + delete field.readonly + } + }) + }) + } } } diff --git a/app/javascript/elements/confirm_upload.js b/app/javascript/elements/confirm_upload.js new file mode 100644 index 00000000..aa39601d --- /dev/null +++ b/app/javascript/elements/confirm_upload.js @@ -0,0 +1,27 @@ +import { target, targetable } from '@github/catalyst/lib/targetable' + +export default targetable(class extends HTMLElement { + static [target.static] = [ + 'prompt', + 'processing', + 'logo' + ] + + connectedCallback () { + this.form.addEventListener('submit', this.onSubmit) + } + + disconnectedCallback () { + this.form.removeEventListener('submit', this.onSubmit) + } + + onSubmit = () => { + this.prompt.classList.add('hidden') + this.processing.classList.remove('hidden') + this.logo.classList.add('animate-bounce') + } + + get form () { + return this.querySelector('form') + } +}) diff --git a/app/javascript/submission_form/completed.vue b/app/javascript/submission_form/completed.vue index f5db3546..6c07a09c 100644 --- a/app/javascript/submission_form/completed.vue +++ b/app/javascript/submission_form/completed.vue @@ -77,17 +77,17 @@ - {{ t('view_on_github') }} + Star on Github @@ -98,20 +98,14 @@
{{ t('powered_by') }} {{ productName }} - — {{ t('based_on') }} - DocuSeal (AGPLv3) + >DocuSeal - {{ t('open_source_documents_software') }}
@@ -148,21 +142,6 @@ export default { required: false, default: true }, - productName: { - type: String, - required: false, - default: 'WaboSign' - }, - productUrl: { - type: String, - required: false, - default: 'https://sign.wabo.cc' - }, - githubUrl: { - type: String, - required: false, - default: 'https://github.com/wabolabs/wabosign' - }, hasSignatureFields: { type: Boolean, required: false, diff --git a/app/javascript/submission_form/date_step.vue b/app/javascript/submission_form/date_step.vue index 245573da..f60fd6eb 100644 --- a/app/javascript/submission_form/date_step.vue +++ b/app/javascript/submission_form/date_step.vue @@ -27,6 +27,7 @@ + - - <%= f.check_box :value, class: 'toggle', checked: account_config.value == true %> - + <% if !Wabosign.multitenant? || can?(:manage, :delegate_form) %> + + <%= f.check_box :value, class: 'toggle', checked: account_config.value == true %> + + <% else %> + " data-turbo="false" data-tip="<%= I18n.t('unlock_with_docuseal_pro') %>" class="flex tooltip"> + <%= f.check_box :value, class: 'toggle pointer-events-none', checked: account_config.value == true, disabled: true %> + + <% end %> <% end %> <% end %> @@ -233,7 +245,7 @@ <% end %> <% end %> - <% if true # personalization_advanced gate removed in fork %> + <% if !Wabosign.multitenant? || can?(:manage, :personalization_advanced) %> <% account_config = AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::ENFORCE_SIGNING_ORDER_KEY) %> <% if can?(:manage, account_config) %> <%= form_for account_config, url: account_configs_path, method: :post do |f| %> @@ -252,7 +264,7 @@ <% end %> <% end %> <% end %> - <% if true # personalization_advanced gate removed in fork %> + <% if !Wabosign.multitenant? || can?(:manage, :personalization_advanced) %> <% account_config = AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::WITH_FILE_LINKS_KEY) %> <% if can?(:manage, account_config) %> <%= form_for account_config, url: account_configs_path, method: :post do |f| %> diff --git a/app/views/devise/mailer/reset_password_instructions.html.erb b/app/views/devise/mailer/reset_password_instructions.html.erb index 5163b1aa..e6e9060c 100644 --- a/app/views/devise/mailer/reset_password_instructions.html.erb +++ b/app/views/devise/mailer/reset_password_instructions.html.erb @@ -5,6 +5,6 @@

<%= t('if_you_didnt_request_this_you_can_ignore_this_email') %>

<%= t('thanks') %>,
- <%= Wabosign.branded_product_name(@resource&.account) %> + <%= Wabosign.product_name %>

<% content_for(:remove_attribution, true) %> diff --git a/app/views/devise/sessions/_omniauthable.html.erb b/app/views/devise/sessions/_omniauthable.html.erb index b5fb0245..e69de29b 100644 --- a/app/views/devise/sessions/_omniauthable.html.erb +++ b/app/views/devise/sessions/_omniauthable.html.erb @@ -1,12 +0,0 @@ -<% if Wabosign.google_sso_enabled? %> -
<%= t('or') %>
-
- <%= button_to user_google_oauth2_omniauth_authorize_path, - method: :post, - data: { turbo: false }, - class: 'base-button !bg-white !text-base-content border border-base-300 flex items-center justify-center gap-2' do %> - - <%= t('sign_in_with_google') %> - <% end %> -
-<% end %> diff --git a/app/views/esign_settings/_default_signature_row.html.erb b/app/views/esign_settings/_default_signature_row.html.erb new file mode 100644 index 00000000..b8dda50d --- /dev/null +++ b/app/views/esign_settings/_default_signature_row.html.erb @@ -0,0 +1,25 @@ + + + <%= svg_icon('discount_check_filled', class: 'w-6 h-6 text-green-500') %> + + <%= t('wabosign_trusted_signature') %> +
+ <%= svg_icon('circle_question', class: 'w-4 h-4 stroke-1') %> +
+
+ + + " class="btn btn-neutral btn-sm text-white"> + <%= t('unlock_with_docuseal_pro') %> + + + +
+ <%= button_to settings_esign_path, method: :put, params: { name: Wabosign::AATL_CERT_NAME }, class: 'btn btn-outline btn-neutral btn-xs whitespace-nowrap', title: t('make_default'), disabled: true do %> + <%= t('make_default') %> + <% end %> +
+ + + + diff --git a/app/views/esign_settings/show.html.erb b/app/views/esign_settings/show.html.erb index 9740f504..74bd96c8 100644 --- a/app/views/esign_settings/show.html.erb +++ b/app/views/esign_settings/show.html.erb @@ -99,11 +99,14 @@ <% end %> + <% unless Wabosign.multitenant? %> + <%= render 'default_signature_row' %> + <% end %> <% encrypted_config = EncryptedConfig.find_or_initialize_by(account: current_account, key: EncryptedConfig::TIMESTAMP_SERVER_URL_KEY) %> - <% if can?(:manage, encrypted_config) %> + <% if !Wabosign.multitenant? && can?(:manage, encrypted_config) %>

diff --git a/app/views/invitations/edit.html.erb b/app/views/invitations/edit.html.erb index fb3a26f3..2f348964 100644 --- a/app/views/invitations/edit.html.erb +++ b/app/views/invitations/edit.html.erb @@ -1,7 +1,7 @@

<%= svg_icon('waving_hand', class: 'h-10 w-10') %> - <%= t('welcome_to_product_name', product_name: Wabosign.branded_product_name(current_account)) %> + <%= t('welcome_to_product_name', product_name: Wabosign.product_name) %>

<%= form_for(resource, as: resource_name, url: invitation_path, html: { method: :put, class: 'space-y-6' }) do |f| %>
diff --git a/app/views/layouts/_head_tags.html.erb b/app/views/layouts/_head_tags.html.erb index 1f50e55e..5dd01fcf 100644 --- a/app/views/layouts/_head_tags.html.erb +++ b/app/views/layouts/_head_tags.html.erb @@ -1,5 +1,4 @@ -<% brand = Wabosign.branded_product_name(signed_in? ? current_account : nil) %> - <%= content_for(:html_title) || (signed_in? ? brand : "#{brand} | Open Source Document Signing") %> + <%= content_for(:html_title) || (signed_in? ? 'WaboSign' : 'WaboSign | Open Source Document Signing') %> <%= render 'shared/meta' %> diff --git a/app/views/mcp_settings/index.html.erb b/app/views/mcp_settings/index.html.erb index c6000dd0..a6116658 100644 --- a/app/views/mcp_settings/index.html.erb +++ b/app/views/mcp_settings/index.html.erb @@ -33,7 +33,7 @@

-

<%= t('connect_to_product_name_mcp', product_name: Wabosign.branded_product_name(current_account)) %>

+

<%= t('connect_to_docuseal_mcp') %>

<%= t('add_the_following_to_your_mcp_client_configuration') %>:

<% text = JSON.pretty_generate({ mcpServers: { wabosign: { type: 'http', url: "#{root_url(Wabosign.default_url_options)}mcp", headers: { Authorization: "Bearer #{@mcp_token.token}" } } } }).strip %> diff --git a/app/views/newsletters/show.html.erb b/app/views/newsletters/show.html.erb new file mode 100644 index 00000000..b33a7c40 --- /dev/null +++ b/app/views/newsletters/show.html.erb @@ -0,0 +1,14 @@ +
+

👨‍💻 <%= t('developer_newsletters') %>

+ <%= form_for current_user, url: newsletter_path do |f| %> +
+ <%= f.email_field :email, placeholder: t('email'), required: true, class: 'base-input' %> +
+
+ <%= f.button button_title, class: 'base-button' %> +
+ + <% end %> +
diff --git a/app/views/notifications_settings/_reminder_banner.html.erb b/app/views/notifications_settings/_reminder_banner.html.erb new file mode 100644 index 00000000..926e952d --- /dev/null +++ b/app/views/notifications_settings/_reminder_banner.html.erb @@ -0,0 +1 @@ +<%= render 'reminder_placeholder' %> diff --git a/app/views/notifications_settings/_reminder_form.html.erb b/app/views/notifications_settings/_reminder_form.html.erb index 58b4bec5..d38ccc22 100644 --- a/app/views/notifications_settings/_reminder_form.html.erb +++ b/app/views/notifications_settings/_reminder_form.html.erb @@ -2,7 +2,7 @@ <%= f.hidden_field :key %>
<% record = Struct.new(:first_duration, :second_duration, :third_duration).new(*(f.object.value || {}).values_at('first_duration', 'second_duration', 'third_duration')) %> - <% durations = AccountConfigs::REMINDER_DURATIONS.keys.map { |v| [t(v.underscore), v] } %> + <% durations = (Wabosign.multitenant? ? AccountConfigs::REMINDER_DURATIONS.except('one_hour', 'two_hours') : AccountConfigs::REMINDER_DURATIONS).keys.map { |v| [t(v.underscore), v] } %>
<%= f.fields_for :value, record do |ff| %> diff --git a/app/views/notifications_settings/_reminder_placeholder.html.erb b/app/views/notifications_settings/_reminder_placeholder.html.erb new file mode 100644 index 00000000..f030668d --- /dev/null +++ b/app/views/notifications_settings/_reminder_placeholder.html.erb @@ -0,0 +1,15 @@ +
+ <%= svg_icon('info_circle', class: 'w-6 h-6') %> +
+

+ <%= t('unlock_with_docuseal_pro') %> +

+

+ <%= t('send_automatic_email_reminders_to_your_recipients') %> +
+ " data-turbo="false"> + <%= t('learn_more') %> + +

+
+
diff --git a/app/views/notifications_settings/index.html.erb b/app/views/notifications_settings/index.html.erb index 39e800b8..b163e8e8 100644 --- a/app/views/notifications_settings/index.html.erb +++ b/app/views/notifications_settings/index.html.erb @@ -26,6 +26,7 @@ <%= t('sign_request_email_reminders') %>

+ <%= render 'reminder_banner' %> <%= render 'reminder_form', config: @reminder_config %>
diff --git a/app/views/pages/landing.html.erb b/app/views/pages/landing.html.erb index d6dfc532..4f20ea0f 100644 --- a/app/views/pages/landing.html.erb +++ b/app/views/pages/landing.html.erb @@ -5,10 +5,10 @@
<%= render 'shared/logo', width: '100', height: '100' %>

- <%= Wabosign.branded_product_name %> + WaboSign

<% if Wabosign.version.present? %> - + v<%= Wabosign.version %> <% end %> @@ -27,7 +27,7 @@

Easy to Start

- Run on your own host using Docker container, or deploy on your favorite managed PaaS. + Run on your own host using Docker container, or deploy on your favorite managed PaaS with a single click.

@@ -67,7 +67,7 @@

Open Source

- Source code is available on GitHub.
+ Source code is available under github.com/docusealco.
Open-source contributors are always ready to help!

diff --git a/app/views/personalization_settings/_documents_copy_email_form.html.erb b/app/views/personalization_settings/_documents_copy_email_form.html.erb index 7cd66cf5..5e37cf6a 100644 --- a/app/views/personalization_settings/_documents_copy_email_form.html.erb +++ b/app/views/personalization_settings/_documents_copy_email_form.html.erb @@ -14,10 +14,12 @@ <%= ff.text_field :subject, required: true, class: 'base-input', dir: 'auto' %> <%= render 'personalization_settings/email_body_field', ff:, config: f.object %> -
- <%= ff.label :reply_to, t('reply_to'), class: 'label' %> - <%= ff.email_field :reply_to, class: 'base-input', dir: 'auto', placeholder: t(:email) %> -
+ <% if can?(:manage, :reply_to) || can?(:manage, :personalization_advanced) %> +
+ <%= ff.label :reply_to, t('reply_to'), class: 'label' %> + <%= ff.email_field :reply_to, class: 'base-input', dir: 'auto', placeholder: t(:email) %> +
+ <% end %>
@@ -31,18 +33,22 @@ <%= ff.check_box :attach_audit_log, { checked: ff.object.attach_audit_log != false, class: 'toggle' }, 'true', 'false' %>
-
- - <%= t('bcc_recipients') %> - - <%= ff.check_box :bcc_recipients, { checked: ff.object.bcc_recipients == true, class: 'toggle' }, 'true', 'false' %> -
-
- - <%= t('send_emails_automatically_on_completion') %> - - <%= ff.check_box :enabled, { checked: ff.object.enabled != false, class: 'toggle' }, 'true', 'false' %> -
+ <% unless Wabosign.multitenant? %> +
+ + <%= t('bcc_recipients') %> + + <%= ff.check_box :bcc_recipients, { checked: ff.object.bcc_recipients == true, class: 'toggle' }, 'true', 'false' %> +
+ <% end %> + <% if !Wabosign.multitenant? || can?(:manage, :personalization_advanced) %> +
+ + <%= t('send_emails_automatically_on_completion') %> + + <%= ff.check_box :enabled, { checked: ff.object.enabled != false, class: 'toggle' }, 'true', 'false' %> +
+ <% end %>
<% end %>
diff --git a/app/views/personalization_settings/_logo_form.html.erb b/app/views/personalization_settings/_logo_form.html.erb index 09d13d96..fc6f3ac7 100644 --- a/app/views/personalization_settings/_logo_form.html.erb +++ b/app/views/personalization_settings/_logo_form.html.erb @@ -1,32 +1 @@ -
- <% if current_account.logo.attached? %> -
-
- <%= image_tag rails_blob_path(current_account.logo, disposition: 'inline'), - class: 'w-12 h-12 object-contain bg-white rounded', - alt: current_account.name %> - <%= current_account.logo.filename %> -
- <%= button_to 'Remove', settings_account_logo_path, method: :delete, - class: 'btn btn-sm btn-outline btn-error', - data: { turbo_confirm: 'Remove the uploaded logo?' } %> -
- <% end %> - - <%= form_with url: settings_account_logo_path, method: :post, - multipart: true, html: { class: 'space-y-3', autocomplete: 'off' } do %> -
- - -
- - <% end %> - -

- Replaces the default WaboSign mark on the sign-in page, signing flow, dashboard navbar, share-link QR page, and audit-trail PDFs. Browser favicons and the PWA manifest icon stay on the default brand. -

-
+<%= render 'logo_placeholder' %> diff --git a/app/views/personalization_settings/_logo_placeholder.html.erb b/app/views/personalization_settings/_logo_placeholder.html.erb new file mode 100644 index 00000000..a4759ad3 --- /dev/null +++ b/app/views/personalization_settings/_logo_placeholder.html.erb @@ -0,0 +1,15 @@ +
+ <%= svg_icon('info_circle', class: 'w-6 h-6') %> +
+

+ <%= t('unlock_with_docuseal_pro') %> +

+

+ <%= t('display_your_company_name_and_logo_when_signing_documents') %> +
+ " data-turbo="false"> + <%= t('learn_more') %> + +

+
+
diff --git a/app/views/personalization_settings/show.html.erb b/app/views/personalization_settings/show.html.erb index 2618526f..438da311 100644 --- a/app/views/personalization_settings/show.html.erb +++ b/app/views/personalization_settings/show.html.erb @@ -8,12 +8,7 @@ <%= render 'signature_request_email_form' %> <%= render 'documents_copy_email_form' %> <%= render 'submitter_completed_email_form' %> - <%= render 'signature_request_sms_form' %>
-

- Product name -

- <%= render 'brand_name_form' %>

<%= t('company_logo') %>

diff --git a/app/views/profile/index.html.erb b/app/views/profile/index.html.erb index 9f1ec645..016f29ef 100644 --- a/app/views/profile/index.html.erb +++ b/app/views/profile/index.html.erb @@ -36,7 +36,7 @@ <% if signature %>
<%= button_to button_title(title: t('remove'), disabled_with: t('removing')), user_signature_path, method: :delete, class: 'right-0 top-0 absolute link' %> - +
<% end %> @@ -49,7 +49,7 @@ <% if initials %>
<%= button_to button_title(title: t('remove'), disabled_with: t('removing')), user_initials_path, method: :delete, class: 'right-0 top-0 absolute link' %> - +
<% end %>
diff --git a/app/views/pwa/manifest.json.erb b/app/views/pwa/manifest.json.erb index 3211fd12..18aafb96 100644 --- a/app/views/pwa/manifest.json.erb +++ b/app/views/pwa/manifest.json.erb @@ -1,7 +1,6 @@ -<% brand = Wabosign.branded_product_name %> { - "name": "<%= brand %>", - "short_name": "<%= brand %>", + "name": "<%= Wabosign.product_name %>", + "short_name": "<%= Wabosign.product_name %>", "id": "/", "icons": [ { @@ -19,7 +18,7 @@ "display": "standalone", "scope": "/", "orientation": "any", - "description": "<%= brand %> is an open source platform that provides secure and efficient digital document signing and processing.", + "description": "<%= Wabosign.product_name %> is an open source platform that provides secure and efficient digital document signing and processing.", "categories": ["productivity", "utilities"], "theme_color": "#FAF7F4", "background_color": "#FAF7F4" diff --git a/app/views/shared/_attribution.html.erb b/app/views/shared/_attribution.html.erb index 9a006d55..bc464bba 100644 --- a/app/views/shared/_attribution.html.erb +++ b/app/views/shared/_attribution.html.erb @@ -1 +1 @@ -<%= render 'shared/powered_by', with_counter: local_assigns[:with_counter], link_path: local_assigns[:link_path], account: local_assigns[:account] %> +<%= render 'shared/powered_by', with_counter: local_assigns[:with_counter], link_path: local_assigns[:link_path] %> diff --git a/app/views/shared/_email_attribution.html.erb b/app/views/shared/_email_attribution.html.erb index 401246a0..8cfd4988 100644 --- a/app/views/shared/_email_attribution.html.erb +++ b/app/views/shared/_email_attribution.html.erb @@ -1,17 +1,10 @@

---

-<% brand = Wabosign.branded_product_name(@current_account) %>

<% if @current_account&.testing? %> - <%= t('sent_using_product_name_in_testing_mode_html', product_url: "#{Wabosign::PRODUCT_EMAIL_URL}/start", product_name: brand) %> + <%= t('sent_using_product_name_in_testing_mode_html', product_url: "#{Docuseal::PRODUCT_EMAIL_URL}/start", product_name: Docuseal.product_name) %> <% else %> - <%= t('sent_using_product_name_free_document_signing_html', product_url: "#{Wabosign::PRODUCT_EMAIL_URL}/start", product_name: brand) %> + <%= t('sent_using_product_name_free_document_signing_html', product_url: "#{Docuseal::PRODUCT_EMAIL_URL}/start", product_name: Docuseal.product_name) %> <% end %>

-<%# AGPL §7(b) DocuSeal attribution. Do not remove or rebrand. %> -

- <%= t('based_on') %> - <%= Wabosign::UPSTREAM_NAME %> - (AGPLv3) -

diff --git a/app/views/shared/_github.html.erb b/app/views/shared/_github.html.erb index 4060b7ac..cb1ca982 100644 --- a/app/views/shared/_github.html.erb +++ b/app/views/shared/_github.html.erb @@ -1,6 +1,6 @@ <%= svg_icon('start', class: 'h-3 w-3') %> - 16k + 17k diff --git a/app/views/shared/_github_button.html.erb b/app/views/shared/_github_button.html.erb index a8547e9d..369c891e 100644 --- a/app/views/shared/_github_button.html.erb +++ b/app/views/shared/_github_button.html.erb @@ -1,10 +1,10 @@ - + - GitHub + GitHub diff --git a/app/views/shared/_logo.html.erb b/app/views/shared/_logo.html.erb index 3634b097..57e6e0d9 100644 --- a/app/views/shared/_logo.html.erb +++ b/app/views/shared/_logo.html.erb @@ -1,4 +1,4 @@ - - + + diff --git a/app/views/shared/_meta.html.erb b/app/views/shared/_meta.html.erb index edab477b..d2908749 100644 --- a/app/views/shared/_meta.html.erb +++ b/app/views/shared/_meta.html.erb @@ -1,15 +1,14 @@ <% if Wabosign.demo? || (request.path != '/' && !devise_controller?) %> <% end %> -<% brand = Wabosign.branded_product_name(signed_in? ? current_account : nil) %> -<% title = content_for(:html_title) || (signed_in? ? brand : "#{brand} | Open Source Document Signing") %> +<% title = content_for(:html_title) || (signed_in? ? 'WaboSign' : 'WaboSign | Open Source Document Signing') %> <% description = content_for(:html_description) || 'Open source, self-hosted tool to streamline document filling and signing. Create custom PDF forms to complete and sign with an easy to use online tool.' %> - + <% if content_for(:disable_image_preview) %> @@ -20,6 +19,8 @@ "> <% end %> + + diff --git a/app/views/shared/_navbar.html.erb b/app/views/shared/_navbar.html.erb index 4b14859d..4fe90a4a 100644 --- a/app/views/shared/_navbar.html.erb +++ b/app/views/shared/_navbar.html.erb @@ -11,7 +11,7 @@ <% if signed_in? %>
<% if Wabosign.demo? %> - + <%= t('sign_up') %>
-<%# AGPL §7(b) DocuSeal attribution. Do not remove or rebrand. %> -
- <%= t('based_on') %> - <%= Wabosign::UPSTREAM_NAME %> - (AGPLv3) + <%= Docuseal.product_name %> - <%= t('open_source_documents_software') %>
diff --git a/app/views/shared/_settings_nav.html.erb b/app/views/shared/_settings_nav.html.erb index b321bca4..ab61de6c 100644 --- a/app/views/shared/_settings_nav.html.erb +++ b/app/views/shared/_settings_nav.html.erb @@ -64,13 +64,33 @@ <% end %> <% end %> + <% if !Wabosign.demo? && can?(:manage, EncryptedConfig) && (current_user != true_user || !current_account.linked_account_account) %> +
  • + <%= content_for(:pro_link) || link_to(Wabosign.multitenant? ? console_redirect_index_path(redir: "#{Wabosign::CONSOLE_URL}/plans") : "#{Wabosign::CLOUD_URL}/sign_up?#{{ redir: "#{Wabosign::CONSOLE_URL}/on_premises" }.to_query}", class: 'text-base hover:bg-base-300', data: { turbo: false }) do %> + <%= t('plans') %> + <%= t('pro') %> + <% end %> +
  • + <% end %> <% if !Wabosign.demo? && can?(:manage, EncryptedConfig) && (current_user == true_user || current_account.testing?) %> - <% if can?(:read, EncryptedConfig.new(key: 'saml_configs', account: current_account)) && true_user == current_user %> +
  • + <%= link_to Wabosign.multitenant? ? console_redirect_index_path(redir: "#{Wabosign::CONSOLE_URL}#{'/test' if current_account.testing?}/api") : "#{Wabosign::CONSOLE_URL}/on_premises", class: 'text-base hover:bg-base-300', data: { turbo: false } do %> + <% if Wabosign.multitenant? %> API <% else %> <%= t('console') %> <% end %> + <% end %> +
  • + <% if Wabosign.multitenant? %> +
  • + <%= link_to console_redirect_index_path(redir: "#{Wabosign::CONSOLE_URL}#{'/test' if current_account.testing?}/embedding/form"), class: 'text-base hover:bg-base-300', data: { turbo: false } do %> + <%= t('embedding') %> + <% end %> +
  • + <% end %> + <% if (!Wabosign.multitenant? || can?(:manage, :saml_sso)) && can?(:read, EncryptedConfig.new(key: 'saml_configs', account: current_account)) && true_user == current_user %>
  • <%= link_to 'SSO', settings_sso_index_path, class: 'text-base hover:bg-base-300' %>
  • <% end %> - <% if can?(:read, McpToken) && can?(:manage, :mcp) %> + <% if !Wabosign.multitenant? && can?(:read, McpToken) && can?(:manage, :mcp) %>
  • <%= link_to 'MCP', settings_mcp_index_path, class: 'text-base hover:bg-base-300' %>
  • @@ -104,12 +124,24 @@ <%= svg_icon('brand_github', class: 'w-8 h-8') %> +
    + + <%= svg_icon('brand_discord', class: 'w-8 h-8') %> + +
    + <%= capture do %> +
    + + <%= svg_icon('brand_openai', class: 'w-8 h-8') %> + +
    + <% end %> <%= Wabosign::SUPPORT_EMAIL %> <% if Wabosign.version.present? && !Wabosign.multitenant? && can?(:manage, EncryptedConfig) %> - + v<%= Wabosign.version %> <% end %> diff --git a/app/views/shared/_title.html.erb b/app/views/shared/_title.html.erb index 8b3969af..27310df9 100644 --- a/app/views/shared/_title.html.erb +++ b/app/views/shared/_title.html.erb @@ -1,2 +1,2 @@ -<%= render 'shared/account_logo', account: current_account %> -<%= Wabosign.branded_product_name(current_account) %> +<%= render 'shared/logo' %> +WaboSign diff --git a/app/views/sms_settings/_placeholder.html.erb b/app/views/sms_settings/_placeholder.html.erb new file mode 100644 index 00000000..15fa33b0 --- /dev/null +++ b/app/views/sms_settings/_placeholder.html.erb @@ -0,0 +1,15 @@ +
    + <%= svg_icon('info_circle', class: 'w-6 h-6') %> +
    +

    + <%= t('send_signature_requests_via_sms') %> +

    +

    + <%= t('unlock_with_docuseal_pro') %> +
    + " data-turbo="false"> + <%= t('learn_more') %> + +

    +
    +
    diff --git a/app/views/sms_settings/index.html.erb b/app/views/sms_settings/index.html.erb index 592d6cc6..ce3d516c 100644 --- a/app/views/sms_settings/index.html.erb +++ b/app/views/sms_settings/index.html.erb @@ -2,190 +2,7 @@ <%= render 'shared/settings_nav' %>

    SMS

    - - <% value = @encrypted_config.value || {} %> - <% sms_live = Sms.enabled_for?(current_account) %> - <% provider_labels = { 'bulkvs' => 'BulkVS', 'twilio' => 'Twilio', 'voipms' => 'VoIP.ms', 'signalwire' => 'SignalWire' } %> - <% sending_number_keys = { 'twilio' => 'twilio_from', 'voipms' => 'voipms_did', 'signalwire' => 'signalwire_from' } %> - <% sending_number = value[sending_number_keys[value['provider'].to_s] || 'from_number'] %> - <% selected_provider = value['provider'].presence || 'bulkvs' %> - - <% if sms_live %> -
    - <%= svg_icon('discount_check_filled', class: 'w-6 h-6') %> -
    -

    SMS is enabled

    -

    - Provider: <%= provider_labels[value['provider'].to_s] || value['provider'].to_s.upcase %>. - From: <%= sending_number %>. -

    -
    -
    - <% else %> -
    - <%= svg_icon('info_circle', class: 'w-6 h-6') %> -
    -

    SMS provider is not configured

    -

    - WaboSign supports - BulkVS, - Twilio, - VoIP.ms, and - SignalWire. - Pick a provider below and paste its credentials. -

    -
    -
    - <% end %> - - <%= form_for @encrypted_config, url: settings_sms_path, method: :post, html: { autocomplete: 'off', class: 'space-y-4', id: 'sms_settings_form' } do |f| %> - <%= f.fields_for :value do |ff| %> -
    - -
    -
    - <%= ff.label :provider, 'Provider', class: 'label' %> - <%= ff.select :provider, - Sms::SUPPORTED_PROVIDERS.map { |p| [provider_labels[p] || p, p] }, - { selected: selected_provider }, - class: 'base-select', - id: 'sms_provider_select' %> -
    - -
    -
    - <%= ff.label :basic_auth_token, 'BulkVS Basic Auth Token', class: 'label' %> - <%= ff.password_field :basic_auth_token, value: '', class: 'base-input', placeholder: value['basic_auth_token'].present? ? '*************' : 'Paste from BulkVS portal' %> - <% if value['basic_auth_token'].present? %> - Leave blank to keep the saved token. - <% else %> - In the BulkVS portal, open the API tab and copy the pre-encoded Basic Auth header value (do not include "Basic "). - <% end %> -
    -
    - <%= ff.label :from_number, 'From Number', class: 'label' %> - <%= ff.text_field :from_number, value: value['from_number'], class: 'base-input', placeholder: '15551234567' %> - E.164 format (digits only, country code first; e.g. 15551234567). -
    -
    - <%= ff.label :delivery_webhook_url, 'Delivery Status Webhook (optional)', class: 'label' %> - <%= ff.url_field :delivery_webhook_url, value: value['delivery_webhook_url'], class: 'base-input', placeholder: 'https://your-app.example/webhooks/sms' %> - If set, BulkVS will POST delivery-status events here for each message. -
    -
    - -
    -
    - <%= ff.label :twilio_account_sid, 'Twilio Account SID', class: 'label' %> - <%= ff.text_field :twilio_account_sid, value: value['twilio_account_sid'], class: 'base-input', placeholder: 'AC...' %> - From your Twilio Console "Account Info" panel. -
    -
    - <%= ff.label :twilio_auth_token, 'Twilio Auth Token', class: 'label' %> - <%= ff.password_field :twilio_auth_token, value: '', class: 'base-input', placeholder: value['twilio_auth_token'].present? ? '*************' : 'Click "show" in the Console to reveal' %> - <% if value['twilio_auth_token'].present? %> - Leave blank to keep the saved token. - <% else %> - Found next to the Account SID in the Twilio Console. - <% end %> -
    -
    - <%= ff.label :twilio_from, 'From Number', class: 'label' %> - <%= ff.text_field :twilio_from, value: value['twilio_from'], class: 'base-input', placeholder: '+15551234567' %> - Twilio number purchased in Phone Numbers → Manage. Use full E.164 with leading +. -
    -
    - -
    -
    - <%= ff.label :voipms_api_username, 'API Username', class: 'label' %> - <%= ff.text_field :voipms_api_username, value: value['voipms_api_username'], class: 'base-input', placeholder: 'your-account@example.com' %> - Your VoIP.ms portal login email. -
    -
    - <%= ff.label :voipms_api_password, 'API Password', class: 'label' %> - <%= ff.password_field :voipms_api_password, value: '', class: 'base-input', placeholder: value['voipms_api_password'].present? ? '*************' : 'Set this at voip.ms/m/api.php' %> - <% if value['voipms_api_password'].present? %> - Leave blank to keep the saved password. - <% else %> - Set the dedicated API password at voip.ms/m/api.php — this is not your portal login password. On the same page, enable API access and whitelist this server's egress IP, or every call will fail with ip_not_authorized. - <% end %> -
    -
    - <%= ff.label :voipms_did, 'DID (Sending Number)', class: 'label' %> - <%= ff.text_field :voipms_did, value: value['voipms_did'], class: 'base-input', placeholder: '5551234567' %> - An SMS-enabled DID from Manage DIDs. Digits only, no +. The DID must have the SMS feature enabled. -
    -
    - -
    -
    - <%= ff.label :signalwire_space_url, 'Space URL', class: 'label' %> - <%= ff.text_field :signalwire_space_url, value: value['signalwire_space_url'], class: 'base-input', placeholder: 'yourname.signalwire.com' %> - From Dashboard → API. Omit https://. -
    -
    - <%= ff.label :signalwire_project_id, 'Project ID', class: 'label' %> - <%= ff.text_field :signalwire_project_id, value: value['signalwire_project_id'], class: 'base-input', placeholder: '00000000-0000-0000-0000-000000000000' %> - The UUID labelled "Your Project ID" on the API tab. -
    -
    - <%= ff.label :signalwire_api_token, 'API Token', class: 'label' %> - <%= ff.password_field :signalwire_api_token, value: '', class: 'base-input', placeholder: value['signalwire_api_token'].present? ? '*************' : 'PT...' %> - <% if value['signalwire_api_token'].present? %> - Leave blank to keep the saved token. - <% else %> - Generate on the API tab. The token must have the Messaging scope enabled or sends return 401. - <% end %> -
    -
    - <%= ff.label :signalwire_from, 'From Number', class: 'label' %> - <%= ff.text_field :signalwire_from, value: value['signalwire_from'], class: 'base-input', placeholder: '+15551234567' %> - A SignalWire number from Phone Numbers. Full E.164 with leading +. -
    -
    - <% end %> -
    - <%= f.button button_title(title: t('save'), disabled_with: t('saving')), class: 'base-button' %> -
    - <% end %> - - <% if sms_live %> -
    -
    -

    Send a test SMS

    - <%= form_with url: test_message_settings_sms_path, method: :post, html: { autocomplete: 'off', class: 'space-y-3' } do |f| %> -
    - - - A short test message is sent to this number using your saved config. -
    -
    - -
    - <% end %> -
    -
    - <% end %> + <%= render 'placeholder' %>
    - - diff --git a/app/views/sso_settings/_placeholder.html.erb b/app/views/sso_settings/_placeholder.html.erb new file mode 100644 index 00000000..709f892a --- /dev/null +++ b/app/views/sso_settings/_placeholder.html.erb @@ -0,0 +1,15 @@ +
    + <%= svg_icon('info_circle', class: 'w-6 h-6') %> +
    +

    + <%= t('single_sign_on_with_saml_2_0') %> +

    +

    + <%= t('unlock_with_docuseal_pro') %> +
    + " data-turbo="false"> + <%= t('learn_more') %> + +

    +
    +
    diff --git a/app/views/sso_settings/index.html.erb b/app/views/sso_settings/index.html.erb index 0eaf0562..a0ebddba 100644 --- a/app/views/sso_settings/index.html.erb +++ b/app/views/sso_settings/index.html.erb @@ -1,81 +1,8 @@
    <%= render 'shared/settings_nav' %>
    -

    Google SSO

    - - <% creds = Wabosign.google_sso_credentials %> - <% value = @encrypted_config.value || {} %> - - <% if creds[:source] == :env %> -
    - <%= svg_icon('info_circle', class: 'w-6 h-6') %> -
    -

    Google SSO is configured via environment variables

    -

    - GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET are set on the running process, so ENV-driven configuration is in effect. ENV always takes precedence over anything saved on this page. Unset the env vars (and restart) to switch to the values configured here. -

    -
    -
    - <% elsif creds[:source] == :db %> -
    - <%= svg_icon('discount_check_filled', class: 'w-6 h-6') %> -
    -

    Google SSO is enabled

    -

    - <% if creds[:allowed_domains].any? %> - Allowed Workspace domain<%= 's' if creds[:allowed_domains].size > 1 %>: <%= creds[:allowed_domains].join(', ') %>. - <% else %> - Warning: no domain allowlist is set. Any Google account can sign in. - <% end %> -

    -
    -
    - <% else %> -
    - <%= svg_icon('info_circle', class: 'w-6 h-6') %> -
    -

    Google SSO is not configured

    -

    - Fill in your Google Cloud OAuth client details below. The OAuth redirect URI to register in Google Cloud Console is - <%= begin - "#{root_url}auth/google_oauth2/callback" - rescue StandardError - '/auth/google_oauth2/callback' - end %>. -

    -
    -
    - <% end %> - - <%= form_for @encrypted_config, url: settings_sso_index_path, method: :post, html: { autocomplete: 'off', class: 'space-y-4' } do |f| %> - <%= f.fields_for :value do |ff| %> -
    - -
    -
    - <%= ff.label :client_id, 'Client ID', class: 'label' %> - <%= ff.text_field :client_id, value: value['client_id'], class: 'base-input', placeholder: '1234567890.apps.googleusercontent.com' %> -
    -
    - <%= ff.label :client_secret, 'Client Secret', class: 'label' %> - <%= ff.password_field :client_secret, class: 'base-input', placeholder: value['client_secret'].present? ? '*************' : 'GOCSPX-…' %> - <% if value['client_secret'].present? %> - Leave blank to keep the saved secret. - <% end %> -
    -
    - <%= ff.label :allowed_domains_csv, 'Allowed Workspace Domains', class: 'label' %> - <%= ff.text_field :allowed_domains_csv, value: Array(value['allowed_domains']).join(', '), class: 'base-input', placeholder: 'wabo.cc, partner.example' %> - Comma-separated. Only Google accounts whose Workspace hd claim matches one of these domains can sign in. Leave blank to allow any Google account (not recommended). -
    - <% end %> -
    - <%= f.button button_title(title: t('save'), disabled_with: t('saving')), class: 'base-button' %> -
    - <% end %> +

    SAML SSO

    + <%= render 'placeholder' %>
    diff --git a/app/views/start_form/_banner.html.erb b/app/views/start_form/_banner.html.erb index 3d8c6b52..75479be8 100644 --- a/app/views/start_form/_banner.html.erb +++ b/app/views/start_form/_banner.html.erb @@ -1 +1 @@ -<%= render 'start_form/brand_logo' %> +<%= render 'start_form/wabosign_logo' %> diff --git a/app/views/start_form/_docuseal_logo.html.erb b/app/views/start_form/_docuseal_logo.html.erb new file mode 100644 index 00000000..f65f6686 --- /dev/null +++ b/app/views/start_form/_docuseal_logo.html.erb @@ -0,0 +1,6 @@ + + + <%= render 'shared/logo', width: '50px', height: '50px' %> + +

    WaboSign

    +
    diff --git a/app/views/start_form/email_verification.html.erb b/app/views/start_form/email_verification.html.erb index 6e4b29f6..94e3ea3e 100644 --- a/app/views/start_form/email_verification.html.erb +++ b/app/views/start_form/email_verification.html.erb @@ -1,4 +1,4 @@ -<% content_for(:html_title, "#{@template.name} | #{Wabosign.branded_product_name(@template.account)}") %> +<% content_for(:html_title, "#{@template.name} | WaboSign") %> <% I18n.with_locale(@template.account.locale) do %> <% content_for(:html_description, t('account_name_has_invited_you_to_fill_and_sign_documents_online_effortlessly_with_a_secure_fast_and_user_friendly_digital_document_signing_solution', account_name: @template.account.name)) %> <% end %> diff --git a/app/views/start_form/private.html.erb b/app/views/start_form/private.html.erb index cdc1e738..7dff97db 100644 --- a/app/views/start_form/private.html.erb +++ b/app/views/start_form/private.html.erb @@ -1,4 +1,4 @@ -<% content_for(:html_title, "#{@template.name} | #{Wabosign.branded_product_name(@template.account)}") %> +<% content_for(:html_title, "#{@template.name} | WaboSign") %> <% I18n.with_locale(@template.account.locale) do %> <% content_for(:html_description, t('share_link_is_currently_disabled')) %> <% end %> diff --git a/app/views/start_form/show.html.erb b/app/views/start_form/show.html.erb index d81d0c37..fbb5837f 100644 --- a/app/views/start_form/show.html.erb +++ b/app/views/start_form/show.html.erb @@ -1,4 +1,4 @@ -<% content_for(:html_title, "#{@template.name} | #{Wabosign.branded_product_name(@template.account)}") %> +<% content_for(:html_title, "#{@template.name} | WaboSign") %> <% I18n.with_locale(@template.account.locale) do %> <% content_for(:html_description, t('account_name_has_invited_you_to_fill_and_sign_documents_online_effortlessly_with_a_secure_fast_and_user_friendly_digital_document_signing_solution', account_name: @template.account.name)) %> <% end %> @@ -52,9 +52,7 @@ <% if link_form_fields.include?('phone') %>
    <%= f.label :phone, t('phone'), class: 'label' %> - - <%= f.telephone_field :phone, value: params[:phone] || @submitter.phone, pattern: '^\+[0-9\s\-]+$', required: true, class: 'base-input w-full', placeholder: t(multiple_fields ? 'provide_your_phone_in_international_format' : 'provide_your_phone_in_international_format_to_start') %> - + <%= f.telephone_field :phone, value: params[:phone] || @submitter.phone, pattern: '^\+[0-9\s\-]+$', required: true, title: t('use_international_format_1xxx_'), class: 'base-input w-full', placeholder: t(multiple_fields ? 'provide_your_phone_in_international_format' : 'provide_your_phone_in_international_format_to_start') %>
    <% end %> diff --git a/app/views/submissions/_bulk_send_placeholder.html.erb b/app/views/submissions/_bulk_send_placeholder.html.erb index ae530771..80315e3a 100644 --- a/app/views/submissions/_bulk_send_placeholder.html.erb +++ b/app/views/submissions/_bulk_send_placeholder.html.erb @@ -5,7 +5,11 @@ <%= t('bulk_send_from_excel_xlsx_or_csv') %>

    - Bulk send via CSV/XLSX import is not bundled with this open-source edition. Use the JSON API (POST /api/submissions with an array of submitters) to create submissions in bulk programmatically. + <%= t('unlock_with_docuseal_pro') %> +
    + " data-turbo="false"> + <%= t('learn_more') %> +

    diff --git a/app/views/submissions/_logo.html.erb b/app/views/submissions/_logo.html.erb index cb0685af..f6b67f5c 100644 --- a/app/views/submissions/_logo.html.erb +++ b/app/views/submissions/_logo.html.erb @@ -1 +1 @@ -<%= render 'shared/account_logo', account: local_assigns[:account] || @submission&.account || @submitter&.account, width: 40, height: 40 %> +<%= render 'shared/logo', width: 40, height: 40 %> diff --git a/app/views/submissions/_send_sms.html.erb b/app/views/submissions/_send_sms.html.erb index f1622a4a..4637e3ac 100644 --- a/app/views/submissions/_send_sms.html.erb +++ b/app/views/submissions/_send_sms.html.erb @@ -1,11 +1,3 @@ -<% if Sms.enabled_for?(current_account) && local_assigns[:submitter]&.phone.present? %> -
    - -
    -<% end %> +
    + <%= render 'sms_settings/placeholder' %> +
    diff --git a/app/views/submissions/_send_sms_button.html.erb b/app/views/submissions/_send_sms_button.html.erb index 7017b1d0..203a6099 100644 --- a/app/views/submissions/_send_sms_button.html.erb +++ b/app/views/submissions/_send_sms_button.html.erb @@ -1,24 +1,5 @@
    - <% if Sms.enabled_for?(current_account) %> - <% if submitter.phone.present? %> - <%= button_to submitter_send_sms_path(submitter_id: submitter.id), - method: :post, - class: 'btn btn-sm btn-primary w-full', - data: { turbo_confirm: submitter.sent_at? ? t('are_you_sure_') : nil } do %> - <%= submitter.sent_at? ? t('re_send_sms') : t('send_sms') %> - <% end %> - <% else %> -
    - -
    - <% end %> - <% else %> -
    - -
    - <% end %> +
    + <%= link_to submitter.sent_at? ? t('re_send_sms') : t('send_sms'), Wabosign.multitenant? ? console_redirect_index_path(redir: "#{Wabosign::CONSOLE_URL}/plans") : "#{Wabosign::CLOUD_URL}/sign_up?#{{ redir: "#{Wabosign::CONSOLE_URL}/on_premises" }.to_query}", class: 'btn btn-sm btn-primary text-gray-400 w-full' %> +
    diff --git a/app/views/submissions/show.html.erb b/app/views/submissions/show.html.erb index 7922949c..3d23b90a 100644 --- a/app/views/submissions/show.html.erb +++ b/app/views/submissions/show.html.erb @@ -266,10 +266,12 @@
    <% if field['type'].in?(%w[signature initials]) %>
    - <%= field['name'] || field['title'] || field['type'] %> + <% img_height = attachments_index[value].metadata['height'] %> + <%= field['name'] || field['title'] || field['type'] %>
    <% elsif field['type'].in?(['image', 'stamp', 'kba']) && attachments_index[value].image? %> - <%= field['name'] || field['title'] || field['type'] %> + <% img_height = attachments_index[value].metadata['height'] %> + <%= field['name'] || field['title'] || field['type'] %> <% elsif field['type'].in?(['file', 'payment', 'image']) %>
    <% Array.wrap(value).each do |val| %> diff --git a/app/views/submissions_filters/_filter_modal.html.erb b/app/views/submissions_filters/_filter_modal.html.erb index d066015a..8794197a 100644 --- a/app/views/submissions_filters/_filter_modal.html.erb +++ b/app/views/submissions_filters/_filter_modal.html.erb @@ -1,6 +1,5 @@ -<% filter_path = params[:path].to_s.start_with?('/') ? params[:path] : '/' %> <%= render 'shared/turbo_modal', title: local_assigns[:title] do %> - <%= form_for '', url: filter_path, method: :get, data: { turbo_frame: :_top }, html: { autocomplete: :off } do |f| %> + <%= form_for '', url: params[:path], method: :get, data: { turbo_frame: :_top }, html: { autocomplete: :off } do |f| %> <%= hidden_field_tag :q, params[:q] if params[:q].present? %> <% local_assigns[:default_params].each do |key, value| %> <%= hidden_field_tag(key, value) if value.present? %> @@ -11,7 +10,7 @@
    <% if params[:with_remove] %>
    - <%= link_to t('remove_filter'), "#{filter_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]}?#{request.query_parameters.slice('q').merge(local_assigns[:default_params]).to_query}", class: 'link', data: { turbo_frame: :_top } %>
    <% end %> <% end %> diff --git a/app/views/submit_form/_banner.html.erb b/app/views/submit_form/_banner.html.erb index 5e2475d0..727f310e 100644 --- a/app/views/submit_form/_banner.html.erb +++ b/app/views/submit_form/_banner.html.erb @@ -1,3 +1,3 @@
    - <%= render 'submit_form/brand_logo' %> + <%= render 'submit_form/wabosign_logo' %>
    diff --git a/app/views/submit_form/_docuseal_logo.html.erb b/app/views/submit_form/_docuseal_logo.html.erb new file mode 100644 index 00000000..c633f860 --- /dev/null +++ b/app/views/submit_form/_docuseal_logo.html.erb @@ -0,0 +1,4 @@ + + <%= render 'shared/logo', class: 'w-9 h-9 md:w-12 md:h-12' %> + <%= Wabosign.product_name %> + diff --git a/app/views/submit_form/email_2fa.html.erb b/app/views/submit_form/email_2fa.html.erb index 4033d354..2e7f1d19 100644 --- a/app/views/submit_form/email_2fa.html.erb +++ b/app/views/submit_form/email_2fa.html.erb @@ -1,4 +1,4 @@ -<% content_for(:html_title, "#{@submitter.submission.name || @submitter.submission.template.name} | #{Wabosign.branded_product_name(@submitter.account)}") %> +<% content_for(:html_title, "#{@submitter.submission.name || @submitter.submission.template.name} | WaboSign") %> <% I18n.with_locale(@submitter.account.locale) do %> <% content_for(:html_description, t('account_name_has_invited_you_to_fill_and_sign_documents_online_effortlessly_with_a_secure_fast_and_user_friendly_digital_document_signing_solution', account_name: @submitter.account.name)) %> <% end %> diff --git a/app/views/submit_form/show.html.erb b/app/views/submit_form/show.html.erb index a4ae78d4..d6c02948 100644 --- a/app/views/submit_form/show.html.erb +++ b/app/views/submit_form/show.html.erb @@ -1,4 +1,4 @@ -<% content_for(:html_title, "#{@submitter.submission.name || @submitter.submission.template.name} | #{Wabosign.branded_product_name(@submitter.account)}") %> +<% content_for(:html_title, "#{@submitter.submission.name || @submitter.submission.template.name} | WaboSign") %> <% I18n.with_locale(@submitter.account.locale) do %> <% content_for(:html_description, t('account_name_has_invited_you_to_fill_and_sign_documents_online_effortlessly_with_a_secure_fast_and_user_friendly_digital_document_signing_solution', account_name: @submitter.account.name)) %> <% end %> @@ -21,20 +21,31 @@

    <%= @submitter.submission.name || @submitter.submission.template.name %>

    -
    +
    + <% if @form_configs[:with_decline] %> + + <% end %> + <% if @form_configs[:with_delegate] %> - + <% if @form_configs[:with_decline] %> - + <% end %> <% if @form_configs[:with_partial_download] %> - +
    -