Until now, Google SSO required setting GOOGLE_CLIENT_ID /
GOOGLE_CLIENT_SECRET / GOOGLE_ALLOWED_DOMAINS in the environment and
restarting the container. This commit adds a UI-driven configuration
path that doesn't need a restart, while keeping ENV as the priority
source for production deployments.
Storage: new EncryptedConfig key `google_sso_configs` (added to
CONFIG_KEYS) with shape:
{ enabled: bool, client_id, client_secret, allowed_domains: [..] }
The secret rides on Rails' `encrypts :value` like every other
EncryptedConfig record.
Strategy registration: the Devise initializer now always registers
:google_oauth2 with a setup proc, so the omniauth routes exist
unconditionally. The setup proc calls Wabosign.google_sso_credentials
per request — that helper checks ENV first (priority) and falls back
to the DB. Empty creds yield :source => :none and the Google button
is hidden by the sign-in partial.
User model: :omniauthable + omniauth_providers: [:google_oauth2] are
now unconditional (matches the always-registered route). The
boot-time fragile gating that broke `bundle exec puma` when env vars
weren't set is gone.
Routes: omniauth_callbacks no longer depends on ENV. /settings/sso
gains a :create action. SsoSettingsController#create persists the
form payload via the existing EncryptedConfig pattern (and never
overwrites a saved secret with a blank).
View: /settings/sso is now a real form (client_id, client_secret,
allowed_domains, enabled toggle) instead of an env-only status panel.
A banner explains ENV precedence when GOOGLE_CLIENT_ID is set. The
redirect URI to register in Google Cloud Console is shown in the
"not configured" state.
User#default_sso_account now prefers the account that owns the
UI-saved config so JIT-provisioned users land in the right tenant
when an admin sets up SSO from the UI in a multi-account deployment.
Specs: the omniauth_callbacks request specs were stubbing the removed
Wabosign::GOOGLE_* constants. Switched them to
`allow(Wabosign).to receive(:google_sso_credentials)`. All 5 pass.
Smoke-tested the rebuilt image in three states:
- No ENV, no DB: container boots, /sign_in 200, no button.
- DB config saved: button appears on the very next /sign_in render.
- ENV set + DB set: ENV wins (allowed_domains and creds come from ENV).
Docs: GOOGLE_SSO.md gains a section describing the UI path and how
the two sources interact.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Devise raises "Mapping omniauth_callbacks on a resource that is not
omniauthable" at route-load time if `controllers[:omniauth_callbacks]`
is set on `devise_for`, regardless of whether `:omniauth_callbacks` is
in the `only:` list (see devise-5.0.3 lib/devise/rails/routes.rb:251).
The previous routes.rb only gated the `only:` array. Adding the same
gate to the `controllers:` hash so the omniauth callback controller is
only declared when GOOGLE_CLIENT_ID + GOOGLE_CLIENT_SECRET are present.
Reproduced the crash by running the image without the env vars; fix
verified in both states:
- No env vars: container boots, /sign_in renders 200, no Google
button present.
- Env vars set + admin user created: container boots, /sign_in
renders 200, "Sign in with Google" form posts to /auth/google_oauth2
with the /google_g.svg logo.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The image now lives at GHCR (pushed manually as :latest and
:sha-45ed368a). Aligning the rest of the repo to match:
- docker-compose.yml: image: ghcr.io/wabolabs/wabosign:latest
- README.md docker-run example: same
- .github/workflows/docker.yml: meta.images and login-action repointed
at ghcr.io. Login now uses the auto-provisioned GITHUB_TOKEN with a
job-level `permissions: packages: write`; the DOCKERHUB_USERNAME /
DOCKERHUB_TOKEN secrets are no longer needed and can be removed from
the repo settings. Also adds a `type=raw,value=latest` tag so the
semver-triggered build keeps :latest pointing at the newest release.
- REBRANDING.md: registry note updated for accuracy.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Three fixes uncovered while running the new omniauth_callbacks specs in
a Ruby 4.0.1 container:
- config/initializers/devise.rb: read GOOGLE_CLIENT_ID / SECRET /
ALLOWED_DOMAINS directly from ENV instead of via Wabosign::*. The
module isn't autoloadable yet at initializer-load time (Rails.root
isn't set), but ENV is. The User model and controllers still go
through Wabosign helpers, which load fine once Rails is up.
- app/models/user.rb: stop passing `omniauth_providers:` when
:omniauthable isn't in the modules list. Devise raises
NoMethodError omniauth_providers= otherwise. Now both the module
inclusion and the keyword are gated on Wabosign.google_sso_enabled?
- spec/requests/users/omniauth_callbacks_spec.rb: post to
user_google_oauth2_omniauth_callback_path instead of the hardcoded
/users/auth/... URL. With devise_for :users, path: '/' the actual
callback route is /auth/google_oauth2/callback. Also create a
placeholder admin user so ApplicationController#maybe_redirect_to_setup
doesn't intercept the request before the callback action runs.
Schema dump and .gitignore (adds /vendor) bundled in.
All 5 specs now pass.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds the resolved entries for omniauth 2.1.4, omniauth-google-oauth2
1.2.2, omniauth-rails_csrf_protection 1.0.2, and their transitive
deps (omniauth-oauth2, oauth2, hashie, multi_xml). Should have shipped
with the SSO commit; pulled out here so the prior commit stays
reviewable and bundle install no longer regenerates the file at boot.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds "Sign in with Google" as an additive auth path next to email and
password. When GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET are set, the
Google button appears on the sign-in page and the SSO settings page
shows an env-driven status panel. Access is restricted to Workspace
domains listed in GOOGLE_ALLOWED_DOMAINS (CSV); the hd claim is
re-verified server-side on every callback so a misconfigured Google
consent screen cannot bypass it.
New users from an allowed domain are JIT-provisioned in the default
account (oldest, or pinned via GOOGLE_DEFAULT_ACCOUNT_ID). Existing
users with a matching email get linked to their Google identity on
first sign-in; identity collisions (same email, different Google uid)
are rejected.
Google's MFA is trusted: users signed in via Google do not see the
WaboSign OTP prompt or the FORCE_MFA setup redirect. Password sign-in
keeps working unchanged, including its existing OTP gate.
Implementation:
- Devise gains :omniauthable when SSO is enabled; users get
provider/uid columns with a partial unique index that allows NULL
for password-only rows.
- Users::OmniauthCallbacksController handles /users/auth/google_oauth2/
callback, sets session[:bypass_otp_for_sso], and redirects on failure.
- SessionsController#destroy clears the bypass flag on sign-out.
- DashboardController#maybe_redirect_mfa_setup honours the flag and
User#signed_in_via_sso?.
- The previously empty _omniauthable.html.erb stub now renders the
Google button.
Request specs cover happy path, link-existing-user, domain rejection,
identity collision, and 2FA bypass.
GOOGLE_SSO.md is the operator-facing setup, behaviour, verification,
and troubleshooting guide. README links to it.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Renames the product to WaboSign across UI, mailers, locales, assets, and
internal Ruby module. Keeps the upstream DocuSeal attribution required by
AGPLv3 §7(b) in the powered-by footer, email attribution, README, and a
new NOTICE file. Migration renames the AATL cert identifier in encrypted
configs from docuseal_aatl to wabosign_aatl.
Removes multitenant-gated Pro upsell UI (Plans/Console/Upgrade links,
SMS/SSO/bulk-send/logo placeholders, reminder-duration restriction, the
"DocuSeal Pro" email-attribution toggle, conditions/formula/payment
pricing links) so every shipped feature is reachable on a self-hosted
deployment. Multitenant routing logic is preserved.
Drops Discord, Twitter, and ChatGPT/AI-assistant chrome. Embedding
modal keeps the upstream <docuseal-form> / @docuseal/* SDK contract so
existing embedded forms continue to work; documented in NOTICE.
REBRANDING.md captures the change inventory for future maintainers.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>