Add OIDC login flow to UI and fix dark mode datetime picker icons
- Add "Sign in with SSO" button on login page (shown when OIDC is configured) - Add /oidc/callback route to exchange authorization code for JWT tokens - Add loginWithOidcCode action to auth store - Treat issuer URI as complete discovery URL (no auto-append of .well-known) - Update admin page placeholder to show full discovery URL format - Fix datetime picker calendar icon visibility in dark mode (color-scheme) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,12 +1,31 @@
|
||||
import { type FormEvent, useState } from 'react';
|
||||
import { type FormEvent, useEffect, useState } from 'react';
|
||||
import { Navigate } from 'react-router';
|
||||
import { useAuthStore } from './auth-store';
|
||||
import { config } from '../config';
|
||||
import styles from './LoginPage.module.css';
|
||||
|
||||
interface OidcInfo {
|
||||
clientId: string;
|
||||
authorizationEndpoint: string;
|
||||
}
|
||||
|
||||
export function LoginPage() {
|
||||
const { isAuthenticated, login, loading, error } = useAuthStore();
|
||||
const [username, setUsername] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [oidc, setOidc] = useState<OidcInfo | null>(null);
|
||||
const [oidcLoading, setOidcLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
fetch(`${config.apiBaseUrl}/auth/oidc/config`)
|
||||
.then((res) => (res.ok ? res.json() : null))
|
||||
.then((data) => {
|
||||
if (data?.authorizationEndpoint && data?.clientId) {
|
||||
setOidc({ clientId: data.clientId, authorizationEndpoint: data.authorizationEndpoint });
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
}, []);
|
||||
|
||||
if (isAuthenticated) return <Navigate to="/" replace />;
|
||||
|
||||
@@ -15,6 +34,19 @@ export function LoginPage() {
|
||||
login(username, password);
|
||||
};
|
||||
|
||||
const handleOidcLogin = () => {
|
||||
if (!oidc) return;
|
||||
setOidcLoading(true);
|
||||
const redirectUri = `${window.location.origin}/oidc/callback`;
|
||||
const params = new URLSearchParams({
|
||||
response_type: 'code',
|
||||
client_id: oidc.clientId,
|
||||
redirect_uri: redirectUri,
|
||||
scope: 'openid email profile',
|
||||
});
|
||||
window.location.href = `${oidc.authorizationEndpoint}?${params}`;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.page}>
|
||||
<form className={styles.card} onSubmit={handleSubmit}>
|
||||
@@ -27,6 +59,22 @@ export function LoginPage() {
|
||||
</div>
|
||||
<div className={styles.subtitle}>Sign in to access the observability dashboard</div>
|
||||
|
||||
{oidc && (
|
||||
<>
|
||||
<button
|
||||
className={styles.ssoButton}
|
||||
type="button"
|
||||
onClick={handleOidcLogin}
|
||||
disabled={oidcLoading}
|
||||
>
|
||||
{oidcLoading ? 'Redirecting...' : 'Sign in with SSO'}
|
||||
</button>
|
||||
<div className={styles.divider}>
|
||||
<span className={styles.dividerText}>or</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className={styles.field}>
|
||||
<label className={styles.label}>Username</label>
|
||||
<input
|
||||
|
||||
Reference in New Issue
Block a user