44import com .dbaagent .repository .*;
55import com .dbaagent .security .EncryptionService ;
66import com .dbaagent .util .SecurityHashUtil ;
7+ import jakarta .annotation .PostConstruct ;
78import lombok .Builder ;
89import lombok .RequiredArgsConstructor ;
10+ import lombok .extern .slf4j .Slf4j ;
911import org .springframework .beans .factory .annotation .Value ;
1012import org .springframework .http .HttpStatus ;
1113import org .springframework .http .MediaType ;
2527
2628@ Service
2729@ RequiredArgsConstructor
30+ @ Slf4j
2831public class PasswordlessAuthService {
2932 private static final SecureRandom RANDOM = new SecureRandom ();
3033
@@ -72,6 +75,32 @@ public class PasswordlessAuthService {
7275 @ Value ("${security.google.enabled:false}" )
7376 private boolean googleEnabled ;
7477
78+ /**
79+ * Whether email + password sign-in is accepted at all.
80+ *
81+ * <p>Defaults to {@code true} so existing installs are unaffected. Set it to
82+ * false on deployments that front DeepSQL with Google Workspace SSO: enabling
83+ * SSO does NOT by itself close the password path, so without this flag
84+ * {@code /auth/login} stays open to every local account even when every human
85+ * signs in through Google.
86+ *
87+ * <p>Turning this off while {@code security.google.enabled} is also off leaves
88+ * no way to sign in — {@link #warnIfNoAuthMethodEnabled()} shouts about that at
89+ * startup rather than letting it be discovered at the login screen.
90+ */
91+ @ Value ("${security.password.enabled:true}" )
92+ private boolean passwordLoginEnabled ;
93+
94+ @ PostConstruct
95+ void warnIfNoAuthMethodEnabled () {
96+ if (!passwordLoginEnabled && !googleEnabled ) {
97+ log .error ("security.password.enabled=false AND security.google.enabled=false — "
98+ + "no sign-in method is available and nobody can log in. Enable one of them." );
99+ } else if (!passwordLoginEnabled ) {
100+ log .info ("Password sign-in is DISABLED (security.password.enabled=false); Google SSO only." );
101+ }
102+ }
103+
75104 @ Value ("${security.google.client-id:}" )
76105 private String googleClientId ;
77106
@@ -87,6 +116,23 @@ public class PasswordlessAuthService {
87116 @ Transactional
88117 public AuthFlowResult loginWithPassword (String email , String password , String clientIp , String userAgent , String requestId ) {
89118 String normalizedEmail = normalizeEmail (email );
119+
120+ // Checked before the rate limiter and before any credential comparison:
121+ // when the password path is closed there is nothing to rate-limit and no
122+ // secret to compare, and we must not leak whether the account exists.
123+ if (!passwordLoginEnabled ) {
124+ securityEventService .log (SecurityEventService .EventRequest .builder ()
125+ .eventType (SecurityEventType .PASSWORD_LOGIN_FAILURE )
126+ .outcome (SecurityEventOutcome .FAILURE )
127+ .email (normalizedEmail )
128+ .clientIp (clientIp )
129+ .userAgent (userAgent )
130+ .requestId (requestId )
131+ .metadata (Map .of ("reason" , "password_login_disabled" ))
132+ .build ());
133+ return AuthFlowResult .invalid ("Password sign-in is disabled. Please sign in with Google." );
134+ }
135+
90136 if (rateLimitEnabled ) enforcePasswordRateLimit (normalizedEmail , clientIp );
91137
92138 User user = normalizedEmail == null ? null : userRepository .findByEmailIgnoreCase (normalizedEmail ).orElse (null );
0 commit comments