feat: Entra ID group restriction, remove redirect URI field, user delete + email edit
This commit is contained in:
@ -3,7 +3,7 @@ import { authFetch } from '../lib/auth';
|
||||
import { User } from '../types';
|
||||
import {
|
||||
Shield, Activity, Save, CheckCircle, AlertCircle, Eye, EyeOff,
|
||||
Settings2, ExternalLink, KeyRound, Globe, Clock, ChevronRight,
|
||||
Settings2, KeyRound, Globe, Clock, ChevronRight, Copy, Users,
|
||||
} from 'lucide-react';
|
||||
|
||||
const SECRET_SENTINEL = '__SET__';
|
||||
@ -13,7 +13,7 @@ interface RawSettings {
|
||||
azure_client_id: string;
|
||||
azure_tenant_id: string;
|
||||
azure_client_secret: string;
|
||||
azure_redirect_uri: string;
|
||||
azure_allowed_group: string;
|
||||
checkmk_api_url: string;
|
||||
checkmk_api_secret: string;
|
||||
checkmk_sync_interval_ms: string;
|
||||
@ -28,7 +28,7 @@ function Label({ children }: { children: React.ReactNode }) {
|
||||
}
|
||||
|
||||
function Hint({ children }: { children: React.ReactNode }) {
|
||||
return <p className="mt-1 text-[10px] text-slate-600 font-mono">{children}</p>;
|
||||
return <p className="mt-1 text-[10px] text-slate-500 font-mono leading-relaxed">{children}</p>;
|
||||
}
|
||||
|
||||
function ConfiguredBadge() {
|
||||
@ -68,7 +68,7 @@ function Input({ value, onChange, placeholder, monospace, icon }: {
|
||||
return (
|
||||
<div className="relative">
|
||||
{icon && (
|
||||
<span className="absolute left-3 top-1/2 -translate-y-1/2 text-slate-600 pointer-events-none">
|
||||
<span className="absolute left-3 top-1/2 -translate-y-1/2 text-slate-500 pointer-events-none">
|
||||
{icon}
|
||||
</span>
|
||||
)}
|
||||
@ -92,7 +92,7 @@ function SecretInput({ value, onChange, alreadySet, show, onToggleShow }: {
|
||||
}) {
|
||||
return (
|
||||
<div className="relative">
|
||||
<span className="absolute left-3 top-1/2 -translate-y-1/2 text-slate-600 pointer-events-none">
|
||||
<span className="absolute left-3 top-1/2 -translate-y-1/2 text-slate-500 pointer-events-none">
|
||||
<KeyRound className="w-3.5 h-3.5" />
|
||||
</span>
|
||||
<input
|
||||
@ -105,7 +105,7 @@ function SecretInput({ value, onChange, alreadySet, show, onToggleShow }: {
|
||||
<button
|
||||
type="button"
|
||||
onClick={onToggleShow}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-slate-600 hover:text-slate-300 transition-colors"
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-slate-500 hover:text-slate-300 transition-colors"
|
||||
tabIndex={-1}
|
||||
>
|
||||
{show ? <EyeOff className="w-3.5 h-3.5" /> : <Eye className="w-3.5 h-3.5" />}
|
||||
@ -119,7 +119,7 @@ function SectionCard({ accentColor, children }: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<div className={`bg-[#0F172A] border border-[#1E293B] rounded-2xl overflow-hidden`}>
|
||||
<div className="bg-[#0F172A] border border-[#1E293B] rounded-2xl overflow-hidden">
|
||||
<div className={`h-0.5 w-full ${accentColor}`} />
|
||||
<div className="p-6 space-y-5">
|
||||
{children}
|
||||
@ -133,6 +133,7 @@ export default function Settings({ currentUser: _currentUser }: SettingsProps) {
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const [successMsg, setSuccessMsg] = useState('');
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const [effectiveRedirectUri, setEffectiveRedirectUri] = useState('');
|
||||
|
||||
@ -141,7 +142,7 @@ export default function Settings({ currentUser: _currentUser }: SettingsProps) {
|
||||
const [azureTenantId, setAzureTenantId] = useState('');
|
||||
const [azureClientSecret, setAzureClientSecret] = useState('');
|
||||
const [azureSecretSet, setAzureSecretSet] = useState(false);
|
||||
const [azureRedirectUri, setAzureRedirectUri] = useState('');
|
||||
const [azureAllowedGroup, setAzureAllowedGroup] = useState('');
|
||||
const [showAzureSecret, setShowAzureSecret] = useState(false);
|
||||
|
||||
const [checkmkApiUrl, setCheckmkApiUrl] = useState('');
|
||||
@ -175,7 +176,7 @@ export default function Settings({ currentUser: _currentUser }: SettingsProps) {
|
||||
setAzureTenantId(data.azure_tenant_id || '');
|
||||
setAzureSecretSet(data.azure_client_secret === SECRET_SENTINEL);
|
||||
setAzureClientSecret('');
|
||||
setAzureRedirectUri(data.azure_redirect_uri || '');
|
||||
setAzureAllowedGroup(data.azure_allowed_group || '');
|
||||
setCheckmkApiUrl(data.checkmk_api_url || '');
|
||||
setCheckmkSecretSet(data.checkmk_api_secret === SECRET_SENTINEL);
|
||||
setCheckmkApiSecret('');
|
||||
@ -195,7 +196,7 @@ export default function Settings({ currentUser: _currentUser }: SettingsProps) {
|
||||
azure_enabled: azureEnabled ? 'true' : 'false',
|
||||
azure_client_id: azureClientId,
|
||||
azure_tenant_id: azureTenantId,
|
||||
azure_redirect_uri: azureRedirectUri,
|
||||
azure_allowed_group: azureAllowedGroup,
|
||||
checkmk_api_url: checkmkApiUrl,
|
||||
checkmk_sync_interval_ms: checkmkSyncInterval,
|
||||
};
|
||||
@ -222,6 +223,14 @@ export default function Settings({ currentUser: _currentUser }: SettingsProps) {
|
||||
}
|
||||
}
|
||||
|
||||
function copyRedirectUri() {
|
||||
if (!effectiveRedirectUri) return;
|
||||
navigator.clipboard.writeText(effectiveRedirectUri).then(() => {
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-64">
|
||||
@ -261,7 +270,6 @@ export default function Settings({ currentUser: _currentUser }: SettingsProps) {
|
||||
|
||||
{/* ── Microsoft Entra ID ── */}
|
||||
<SectionCard accentColor="bg-gradient-to-r from-blue-600 to-indigo-600">
|
||||
{/* Card header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-2 bg-blue-950/60 border border-blue-900/40 rounded-xl">
|
||||
@ -280,7 +288,6 @@ export default function Settings({ currentUser: _currentUser }: SettingsProps) {
|
||||
<p className="text-[11px] text-slate-500 mt-0.5">OAuth 2.0 SSO for organizational accounts</p>
|
||||
</div>
|
||||
</div>
|
||||
{/* Toggle */}
|
||||
<div className="flex items-center gap-2.5">
|
||||
<span className={`text-[10px] font-semibold font-mono ${azureEnabled ? 'text-blue-400' : 'text-slate-600'}`}>
|
||||
{azureEnabled ? 'ENABLED' : 'DISABLED'}
|
||||
@ -297,7 +304,6 @@ export default function Settings({ currentUser: _currentUser }: SettingsProps) {
|
||||
|
||||
<div className="h-px bg-slate-800/60" />
|
||||
|
||||
{/* Fields */}
|
||||
<div className={`grid grid-cols-1 sm:grid-cols-2 gap-5 transition-opacity duration-200 ${!azureEnabled ? 'opacity-40 pointer-events-none' : ''}`}>
|
||||
<FieldRow label="Tenant ID" hint="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx">
|
||||
<Input value={azureTenantId} onChange={setAzureTenantId} placeholder="Paste Tenant ID" monospace />
|
||||
@ -317,26 +323,38 @@ export default function Settings({ currentUser: _currentUser }: SettingsProps) {
|
||||
onToggleShow={() => setShowAzureSecret(v => !v)}
|
||||
/>
|
||||
</FieldRow>
|
||||
<FieldRow label="Redirect URI" hint="Leave blank to auto-derive from APP_URL">
|
||||
<FieldRow
|
||||
label="Allowed Group"
|
||||
hint="Object ID of an Azure AD group. If set, only members of this group can sign in."
|
||||
>
|
||||
<Input
|
||||
value={azureRedirectUri}
|
||||
onChange={setAzureRedirectUri}
|
||||
placeholder="https://…/api/auth/azure/callback"
|
||||
value={azureAllowedGroup}
|
||||
onChange={setAzureAllowedGroup}
|
||||
placeholder="Leave blank to allow all tenant users"
|
||||
monospace
|
||||
icon={<ExternalLink className="w-3.5 h-3.5" />}
|
||||
icon={<Users className="w-3.5 h-3.5" />}
|
||||
/>
|
||||
{effectiveRedirectUri && (
|
||||
<div className="mt-2 flex items-start gap-2 bg-amber-950/30 border border-amber-900/40 rounded-lg px-3 py-2">
|
||||
<span className="text-amber-400 mt-0.5 shrink-0">⚠</span>
|
||||
<div className="min-w-0">
|
||||
<p className="text-[10px] text-amber-300 font-semibold mb-0.5">Register this URI in Azure Portal</p>
|
||||
<p className="text-[10px] font-mono text-amber-200 break-all">{effectiveRedirectUri}</p>
|
||||
<p className="text-[10px] text-amber-400/70 mt-0.5">App registrations → Authentication → Redirect URIs</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</FieldRow>
|
||||
</div>
|
||||
|
||||
{/* Redirect URI – read-only */}
|
||||
{effectiveRedirectUri && azureEnabled && (
|
||||
<div className="flex items-start gap-3 bg-slate-900/60 border border-slate-700/60 rounded-xl px-4 py-3">
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-[10px] font-semibold text-slate-400 uppercase tracking-wide mb-1">Required Redirect URI</p>
|
||||
<p className="text-[11px] font-mono text-slate-200 break-all">{effectiveRedirectUri}</p>
|
||||
<p className="text-[10px] text-slate-500 mt-1">Register this in Azure Portal → App registrations → Authentication → Redirect URIs</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={copyRedirectUri}
|
||||
className="shrink-0 p-1.5 rounded-lg text-slate-500 hover:text-slate-200 hover:bg-slate-800 transition-all"
|
||||
title="Copy to clipboard"
|
||||
>
|
||||
{copied ? <CheckCircle className="w-4 h-4 text-emerald-400" /> : <Copy className="w-4 h-4" />}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</SectionCard>
|
||||
|
||||
{/* ── CheckMK ── */}
|
||||
@ -403,7 +421,7 @@ export default function Settings({ currentUser: _currentUser }: SettingsProps) {
|
||||
<button
|
||||
onClick={handleSave}
|
||||
disabled={saving}
|
||||
className="flex items-center gap-2 bg-cyan-600 hover:bg-cyan-500 active:bg-cyan-700 disabled:bg-slate-800 disabled:text-slate-600 disabled:border disabled:border-slate-700 text-white font-semibold text-sm px-5 py-2.5 rounded-xl transition-all shadow-lg shadow-cyan-950/50 focus:outline-none focus:ring-2 focus:ring-cyan-500/40"
|
||||
className="flex items-center gap-2 bg-cyan-600 hover:bg-cyan-500 active:bg-cyan-700 disabled:bg-slate-800 disabled:text-slate-600 disabled:border disabled:border-slate-700 text-white font-semibold text-sm px-5 py-2.5 rounded-xl transition-all shadow-lg shadow-cyan-950/50"
|
||||
>
|
||||
{saving ? (
|
||||
<span className="w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin" />
|
||||
|
||||
Reference in New Issue
Block a user