@@ -233,9 +233,10 @@ func (h *Handler) showOIDCLoginPage(w http.ResponseWriter, r *http.Request) {
233233 appName = "your application"
234234 }
235235
236- ssoHTML := ""
237- if h .getKeytabPath () != "" {
238- ssoLink := h .url ("/login/sso" ) + "?oidc=1"
236+ ssoEnabled := h .getKeytabPath () != ""
237+ ssoLink := ""
238+ if ssoEnabled {
239+ ssoLink = h .url ("/login/sso" ) + "?oidc=1"
239240 if redirectURI != "" {
240241 ssoLink += "&redirect_uri=" + url .QueryEscape (redirectURI )
241242 }
@@ -245,11 +246,26 @@ func (h *Handler) showOIDCLoginPage(w http.ResponseWriter, r *http.Request) {
245246 if nonce != "" {
246247 ssoLink += "&nonce=" + url .QueryEscape (nonce )
247248 }
248- ssoHTML = fmt .Sprintf (`<a href="%s" class="sso-btn">Sign in with SSO</a><div class="divider"><span>or sign in with credentials</span></div>` , ssoLink )
249+ }
250+
251+ autoSSO := false
252+ if ssoEnabled && errorMsg == "" {
253+ if rs := h .runtimeSettings .get (); rs != nil && rs .AutoSSO {
254+ autoSSO = true
255+ }
256+ }
257+
258+ ssoEnabledStr := ""
259+ if ssoEnabled {
260+ ssoEnabledStr = "1"
261+ }
262+ autoSSOStr := ""
263+ if autoSSO {
264+ autoSSOStr = "1"
249265 }
250266
251267 w .Header ().Set ("Content-Type" , "text/html; charset=utf-8" )
252- fmt .Fprintf (w , oidcLoginHTML , action , h .oidcClientID (), redirectURI , state , nonce , scope , appName , errorHTML , ssoHTML )
268+ fmt .Fprintf (w , oidcLoginHTML , action , h .oidcClientID (), redirectURI , state , nonce , scope , appName , errorHTML , ssoLink , ssoEnabledStr , autoSSOStr )
253269}
254270
255271// handleOIDCToken handles the OAuth2 token endpoint.
@@ -740,6 +756,10 @@ func oidcError(w http.ResponseWriter, errorCode, description string, status int)
740756}
741757
742758// OIDC login page template
759+ // oidcLoginHTML format args:
760+ // %[1]s = form action, %[2]s = client_id, %[3]s = redirect_uri, %[4]s = state,
761+ // %[5]s = nonce, %[6]s = scope, %[7]s = appName, %[8]s = errorHTML,
762+ // %[9]s = ssoLink, %[10]s = ssoEnabled ("1"/""), %[11]s = autoSSO ("1"/"")
743763const oidcLoginHTML = `<!DOCTYPE html>
744764<html lang="en">
745765<head>
@@ -759,8 +779,8 @@ const oidcLoginHTML = `<!DOCTYPE html>
759779 --error-bg:rgba(139,21,61,0.2);--error-text:#D4A0A0;
760780}}
761781*{box-sizing:border-box;margin:0;padding:0}
762- body{font-family:'Inter',system-ui ,sans-serif;background:var(--bg);color:var(--text);min-height:100vh;display:flex;align-items:center;justify-content:center}
763- .card{width:400px ;padding:40px;background:var(--card);border:1px solid var(--border);border-radius:12px;box-shadow:0 4px 16px rgba(51,63,72,0.1)}
782+ body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto ,sans-serif;background:var(--bg);color:var(--text);min-height:100vh;display:flex;align-items:center;justify-content:center}
783+ .card{width:420px ;padding:40px;background:var(--card);border:1px solid var(--border);border-radius:12px;box-shadow:0 4px 16px rgba(51,63,72,0.1)}
764784.brand{text-align:center;margin-bottom:32px}
765785.brand h1{font-size:1.5rem;font-weight:700;margin-bottom:4px}
766786.brand p{color:var(--muted);font-size:0.875rem}
@@ -769,36 +789,75 @@ body{font-family:'Inter',system-ui,sans-serif;background:var(--bg);color:var(--t
769789label{display:block;font-size:0.875rem;font-weight:600;margin-bottom:8px}
770790input[type=text],input[type=password]{width:100%%;padding:12px 16px;background:var(--card);border:1px solid var(--border);border-radius:8px;font-size:0.875rem;font-family:inherit;color:var(--text);margin-bottom:16px}
771791input:focus{outline:none;border-color:var(--burgundy);box-shadow:0 0 0 3px rgba(139,21,61,0.15)}
772- button{width:100%%;padding:12px;background:var(--burgundy);color:#fff;border:none;border-radius:8px;font-size:0.875rem;font-weight:600;cursor:pointer;font-family:inherit}
773- button:hover{background:var(--burgundy-hover)}
792+ .btn-primary{width:100%%;padding:14px;background:var(--burgundy);color:#fff;border:none;border-radius:8px;font-size:0.95rem;font-weight:600;cursor:pointer;font-family:inherit;text-align:center;text-decoration:none;display:block}
793+ .btn-primary:hover{background:var(--burgundy-hover)}
794+ .btn-submit{width:100%%;padding:12px;background:var(--burgundy);color:#fff;border:none;border-radius:8px;font-size:0.875rem;font-weight:600;cursor:pointer;font-family:inherit}
795+ .btn-submit:hover{background:var(--burgundy-hover)}
796+ .manual-toggle{display:block;width:100%%;text-align:center;padding:10px;color:var(--muted);font-size:0.8rem;cursor:pointer;border:none;background:none;margin-top:16px;font-family:inherit}
797+ .manual-toggle:hover{color:var(--text)}
798+ .manual-form{display:none;margin-top:16px;padding-top:16px;border-top:1px solid var(--border)}
799+ .manual-form.show{display:block}
774800.app-name{font-size:0.75rem;color:var(--muted);text-align:center;margin-top:16px}
775- .sso-btn{display:block;width:100%%;padding:12px;background:var(--card);color:var(--text);border:1px solid var(--border);border-radius:8px;font-size:0.875rem;font-weight:600;text-align:center;text-decoration:none;font-family:inherit;cursor:pointer}
776- .sso-btn:hover{border-color:var(--burgundy);color:var(--burgundy)}
777- .divider{display:flex;align-items:center;margin:20px 0;gap:12px}
778- .divider::before,.divider::after{content:'';flex:1;height:1px;background:var(--border)}
779- .divider span{color:var(--muted);font-size:0.75rem;white-space:nowrap}
801+ .sso-status{text-align:center;padding:24px 0;color:var(--muted);font-size:0.9rem}
802+ .sso-status .spinner{display:inline-block;width:20px;height:20px;border:2px solid var(--border);border-top-color:var(--burgundy);border-radius:50%%;animation:spin 0.8s linear infinite;margin-right:8px;vertical-align:middle}
803+ @keyframes spin{to{transform:rotate(360deg)}}
780804</style>
781805</head>
782806<body>
783807<div class="card">
784808 <div class="brand"><h1>SimpleAuth</h1><p>Sign in to continue</p></div>
785809 <div class="gold-bar"></div>
786810 %[8]s
787- %[9]s
788- <form method="POST" action="%[1]s">
789- <input type="hidden" name="client_id" value="%[2]s">
790- <input type="hidden" name="redirect_uri" value="%[3]s">
791- <input type="hidden" name="state" value="%[4]s">
792- <input type="hidden" name="nonce" value="%[5]s">
793- <input type="hidden" name="scope" value="%[6]s">
794- <input type="hidden" name="response_type" value="code">
795- <label>Username</label>
796- <input type="text" name="username" placeholder="Enter your username" autofocus required>
797- <label>Password</label>
798- <input type="password" name="password" placeholder="Enter your password" required>
799- <button type="submit">Sign In</button>
800- </form>
811+ <div id="sso-section" style="display:none">
812+ <a href="%[9]s" class="btn-primary" id="sso-btn">Sign in with Single Sign-On</a>
813+ <button class="manual-toggle" onclick="document.getElementById('manual-form').classList.add('show');this.style.display='none'">
814+ Or sign in with username and password
815+ </button>
816+ </div>
817+ <div id="auto-sso-status" style="display:none">
818+ <div class="sso-status"><span class="spinner"></span> Attempting Single Sign-On...</div>
819+ </div>
820+ <div id="manual-form" class="manual-form">
821+ <form method="POST" action="%[1]s">
822+ <input type="hidden" name="client_id" value="%[2]s">
823+ <input type="hidden" name="redirect_uri" value="%[3]s">
824+ <input type="hidden" name="state" value="%[4]s">
825+ <input type="hidden" name="nonce" value="%[5]s">
826+ <input type="hidden" name="scope" value="%[6]s">
827+ <input type="hidden" name="response_type" value="code">
828+ <label>Username</label>
829+ <input type="text" name="username" placeholder="Enter your username" autofocus required>
830+ <label>Password</label>
831+ <input type="password" name="password" placeholder="Enter your password" required>
832+ <button type="submit" class="btn-submit">Sign In</button>
833+ </form>
834+ </div>
801835 <div class="app-name">Signing into %[7]s</div>
802836</div>
837+ <script>
838+ (function(){
839+ var ssoEnabled = "%[10]s" === "1";
840+ var autoSSO = "%[11]s" === "1";
841+ var ssoLink = "%[9]s";
842+ var hasError = document.querySelector('.error') !== null;
843+
844+ if (ssoEnabled && !hasError) {
845+ document.getElementById('sso-section').style.display = 'block';
846+ if (autoSSO && ssoLink) {
847+ document.getElementById('sso-section').style.display = 'none';
848+ document.getElementById('auto-sso-status').style.display = 'block';
849+ setTimeout(function(){ window.location.href = ssoLink; }, 500);
850+ }
851+ } else if (ssoEnabled && hasError) {
852+ document.getElementById('sso-section').style.display = 'block';
853+ document.getElementById('manual-form').classList.add('show');
854+ } else {
855+ document.getElementById('manual-form').classList.add('show');
856+ document.getElementById('manual-form').style.borderTop = 'none';
857+ document.getElementById('manual-form').style.marginTop = '0';
858+ document.getElementById('manual-form').style.paddingTop = '0';
859+ }
860+ })();
861+ </script>
803862</body>
804863</html>`
0 commit comments