mirror of https://github.com/docusealco/docuseal
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>
pull/687/head
parent
07f6883a37
commit
dacefffd24
@ -1,30 +0,0 @@
|
|||||||
<% if Wabosign.google_sso_enabled? %>
|
|
||||||
<div class="alert alert-success">
|
|
||||||
<%= svg_icon('discount_check_filled', class: 'w-6 h-6') %>
|
|
||||||
<div>
|
|
||||||
<p class="font-bold">Google SSO is enabled</p>
|
|
||||||
<p class="text-gray-700">
|
|
||||||
Configured via environment variables.
|
|
||||||
<% if Wabosign::GOOGLE_ALLOWED_DOMAINS.any? %>
|
|
||||||
Allowed Workspace domain<%= 's' if Wabosign::GOOGLE_ALLOWED_DOMAINS.size > 1 %>:
|
|
||||||
<code><%= Wabosign::GOOGLE_ALLOWED_DOMAINS.join(', ') %></code>.
|
|
||||||
<% else %>
|
|
||||||
<strong>Warning:</strong> no domain allowlist set — any Google account may sign in. Set <code>GOOGLE_ALLOWED_DOMAINS</code> to restrict.
|
|
||||||
<% end %>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<% else %>
|
|
||||||
<div class="alert">
|
|
||||||
<%= svg_icon('info_circle', class: 'w-6 h-6') %>
|
|
||||||
<div>
|
|
||||||
<p class="font-bold">Google SSO is not configured</p>
|
|
||||||
<p class="text-gray-700">
|
|
||||||
Set <code>GOOGLE_CLIENT_ID</code>, <code>GOOGLE_CLIENT_SECRET</code>, and <code>GOOGLE_ALLOWED_DOMAINS</code> (comma-separated) and restart the app. The OAuth redirect URI to register in Google Cloud Console is <code><%= "#{root_url}users/auth/google_oauth2/callback" rescue '/users/auth/google_oauth2/callback' %></code>.
|
|
||||||
</p>
|
|
||||||
<p class="text-gray-700 mt-2">
|
|
||||||
SAML 2.0 SSO is not bundled with this open-source edition. To enable it, add <code>ruby-saml</code> and <code>devise-saml-authenticatable</code> and wire the ACS/SLO/metadata routes; encrypted config is stored under the <code>saml_configs</code> key on the account.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
@ -1,8 +1,77 @@
|
|||||||
<div class="flex flex-wrap space-y-4 md:flex-nowrap md:space-y-0">
|
<div class="flex flex-wrap space-y-4 md:flex-nowrap md:space-y-0">
|
||||||
<%= render 'shared/settings_nav' %>
|
<%= render 'shared/settings_nav' %>
|
||||||
<div class="flex-grow max-w-xl mx-auto">
|
<div class="flex-grow max-w-xl mx-auto">
|
||||||
<h1 class="text-4xl font-bold mb-4">SAML SSO</h1>
|
<h1 class="text-4xl font-bold mb-4">Google SSO</h1>
|
||||||
<%= render 'placeholder' %>
|
|
||||||
|
<% creds = Wabosign.google_sso_credentials %>
|
||||||
|
<% value = @encrypted_config.value || {} %>
|
||||||
|
|
||||||
|
<% if creds[:source] == :env %>
|
||||||
|
<div class="alert mb-4">
|
||||||
|
<%= svg_icon('info_circle', class: 'w-6 h-6') %>
|
||||||
|
<div>
|
||||||
|
<p class="font-bold">Google SSO is configured via environment variables</p>
|
||||||
|
<p class="text-gray-700">
|
||||||
|
<code>GOOGLE_CLIENT_ID</code> and <code>GOOGLE_CLIENT_SECRET</code> 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.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<% elsif creds[:source] == :db %>
|
||||||
|
<div class="alert alert-success mb-4">
|
||||||
|
<%= svg_icon('discount_check_filled', class: 'w-6 h-6') %>
|
||||||
|
<div>
|
||||||
|
<p class="font-bold">Google SSO is enabled</p>
|
||||||
|
<p class="text-gray-700">
|
||||||
|
<% if creds[:allowed_domains].any? %>
|
||||||
|
Allowed Workspace domain<%= 's' if creds[:allowed_domains].size > 1 %>: <code><%= creds[:allowed_domains].join(', ') %></code>.
|
||||||
|
<% else %>
|
||||||
|
<strong>Warning:</strong> no domain allowlist is set. Any Google account can sign in.
|
||||||
|
<% end %>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<% else %>
|
||||||
|
<div class="alert mb-4">
|
||||||
|
<%= svg_icon('info_circle', class: 'w-6 h-6') %>
|
||||||
|
<div>
|
||||||
|
<p class="font-bold">Google SSO is not configured</p>
|
||||||
|
<p class="text-gray-700">
|
||||||
|
Fill in your Google Cloud OAuth client details below. The OAuth redirect URI to register in <a href="https://console.cloud.google.com/apis/credentials" target="_blank" rel="noopener" class="link">Google Cloud Console</a> is
|
||||||
|
<code><%= "#{root_url}auth/google_oauth2/callback" rescue '/auth/google_oauth2/callback' %></code>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<% 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| %>
|
||||||
|
<div class="form-control">
|
||||||
|
<label class="label cursor-pointer" for="encrypted_config_value_enabled">
|
||||||
|
<span class="label-text font-medium">Enable Google SSO</span>
|
||||||
|
<%= ff.check_box :enabled, { class: 'toggle', checked: value['enabled'] == true }, '1', '0' %>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-control">
|
||||||
|
<%= 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' %>
|
||||||
|
</div>
|
||||||
|
<div class="form-control">
|
||||||
|
<%= 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? %>
|
||||||
|
<span class="label-text-alt mt-1 opacity-70">Leave blank to keep the saved secret.</span>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<div class="form-control">
|
||||||
|
<%= 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' %>
|
||||||
|
<span class="label-text-alt mt-1 opacity-70">Comma-separated. Only Google accounts whose Workspace <code>hd</code> claim matches one of these domains can sign in. Leave blank to allow any Google account (not recommended).</span>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<div class="form-control pt-2">
|
||||||
|
<%= f.button button_title(title: t('save'), disabled_with: t('saving')), class: 'base-button' %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-0 md:w-52"></div>
|
<div class="w-0 md:w-52"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
Reference in new issue