Skip to content

Commit c7b0a31

Browse files
committed
feat: surface Google SSO on the login page
Google Workspace SSO is fully implemented server-side — /auth/google/start and /auth/google/callback exist, are permitAll in SecurityConfig, and are gated by a google_workspace_domain allowlist that verifies the OIDC `hd` claim. The client helper authAPI.getGoogleStartUrl() exists too. Nothing ever called it. Login.jsx had no Google reference at all, so on an install with SECURITY_GOOGLE_ENABLED=true the only way to sign in via SSO was to type /api/auth/google/start into the address bar. The login page is unauthenticated, so it cannot read security.google.enabled to decide whether to offer the button. Rather than add an endpoint, this extends GET /setup/status — already public, and already fetched by Login.jsx on mount — with a googleEnabled flag. Installs that never configured Google see no button and are unaffected. - SetupController: expose googleEnabled on the public status response. Field is non-final by necessity; @requiredargsconstructor would otherwise pull it into the constructor, which Spring cannot satisfy. - Login.jsx: render a "Sign in with Google" button below the password form when the flag is set. Plain <a>, not a submit button — the endpoint 302s to Google, so it is a full-page navigation and must not post the form.
1 parent b99d1b2 commit c7b0a31

2 files changed

Lines changed: 56 additions & 2 deletions

File tree

backend/src/main/java/com/dbaagent/controller/SetupController.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import com.dbaagent.service.SystemConfigService;
77
import lombok.RequiredArgsConstructor;
88
import lombok.extern.slf4j.Slf4j;
9+
import org.springframework.beans.factory.annotation.Value;
910
import org.springframework.http.ResponseEntity;
1011
import org.springframework.web.bind.annotation.*;
1112
import org.springframework.web.client.RestClient;
@@ -34,6 +35,17 @@ public class SetupController {
3435
private final CredentialRepository credentialRepository;
3536
private final LlmConfigResolver llmConfigResolver;
3637

38+
/**
39+
* Mirrors {@code security.google.enabled}. Surfaced on the public status
40+
* endpoint so the login page can decide whether to offer Google sign-in —
41+
* it is otherwise unauthenticated and has no way to know the server was
42+
* configured for SSO. Deliberately NOT final: {@code @RequiredArgsConstructor}
43+
* would pull a final field into the constructor and Spring has no bean to
44+
* satisfy it.
45+
*/
46+
@Value("${security.google.enabled:false}")
47+
private boolean googleEnabled;
48+
3749
// ── GET /setup/status ─────────────────────────────────────────────────────
3850

3951
/** Returns setup completion state. Public endpoint — no auth required. */
@@ -53,7 +65,8 @@ public SetupStatusResponse getStatus() {
5365
setupComplete,
5466
hasOrgInfo,
5567
hasConnections,
56-
hasLlmConfig
68+
hasLlmConfig,
69+
googleEnabled
5770
);
5871
}
5972

@@ -278,7 +291,9 @@ public record SetupStatusResponse(
278291
boolean setupComplete,
279292
boolean hasOrganizationInfo,
280293
boolean hasConnections,
281-
boolean hasLlmConfig
294+
boolean hasLlmConfig,
295+
/** Whether Google Workspace SSO is configured; drives the login page's SSO button. */
296+
boolean googleEnabled
282297
) {}
283298

284299
public record InitializeRequest(String orgName, String adminUsername, String adminEmail, String adminPassword) {}

src/pages/Login.jsx

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ export default function Login() {
9999

100100

101101
const renderLoginStep = () => (
102+
<>
102103
<form onSubmit={handlePasswordLogin} className="space-y-5">
103104
<div>
104105
<label htmlFor="email" className="text-xs font-semibold text-gray-500 uppercase tracking-wider block mb-1.5">
@@ -147,6 +148,44 @@ export default function Login() {
147148
{loading ? 'Signing in…' : 'Sign In'}
148149
</button>
149150
</form>
151+
152+
{/*
153+
Only rendered when the server reports security.google.enabled. The login
154+
page is unauthenticated, so /setup/status (already public, already fetched
155+
above) is how it learns SSO exists — otherwise this button would 500 for
156+
every install that never configured Google.
157+
158+
A plain <a>, not a button: /api/auth/google/start issues a 302 to Google,
159+
so this is a full-page navigation and must not submit the form above.
160+
*/}
161+
{setupStatus?.googleEnabled && (
162+
<div className="mt-6">
163+
<div className="relative">
164+
<div className="absolute inset-0 flex items-center" aria-hidden="true">
165+
<div className="w-full border-t border-gray-200" />
166+
</div>
167+
<div className="relative flex justify-center">
168+
<span className="bg-white px-3 text-xs font-medium uppercase tracking-wider text-gray-400">
169+
or
170+
</span>
171+
</div>
172+
</div>
173+
174+
<a
175+
href={authAPI.getGoogleStartUrl()}
176+
className="mt-6 w-full min-h-[48px] flex items-center justify-center gap-3 bg-white border border-gray-300 hover:bg-gray-50 text-gray-700 font-semibold py-3 rounded-full shadow-sm transition-all active:scale-[0.98]"
177+
>
178+
<svg className="h-5 w-5" viewBox="0 0 24 24" aria-hidden="true">
179+
<path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 0 1-2.2 3.32v2.76h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" />
180+
<path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.76c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84A11 11 0 0 0 12 23z" />
181+
<path fill="#FBBC05" d="M5.84 14.11a6.6 6.6 0 0 1 0-4.22V7.05H2.18a11 11 0 0 0 0 9.9l3.66-2.84z" />
182+
<path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1a11 11 0 0 0-9.82 6.05l3.66 2.84c.87-2.6 3.3-4.51 6.16-4.51z" />
183+
</svg>
184+
Sign in with Google
185+
</a>
186+
</div>
187+
)}
188+
</>
150189
)
151190

152191
const renderOtpStep = () => (

0 commit comments

Comments
 (0)