mirror of
http://172.20.10.11:3000/gitadmin/INSIGHT-MVP.git
synced 2026-06-24 22:46:39 +02:00
feat: add SSO configuration page in admin section
New admin page at /admin/sso with: - Live SSO status indicator (active/not configured) - Step-by-step Azure App Registration guide - Required values table (Tenant ID, Client ID, Client Secret) - API permissions list (openid, profile, email, User.Read) - Redirect URI with copy button - Environment variables template - Technical reference (endpoints, security info) Accessible via sidebar: Administration → SSO-Konfiguration Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
45cf644f81
commit
eba738fdc5
3 changed files with 482 additions and 0 deletions
472
packages/frontend/src/admin/AdminSsoPage.tsx
Normal file
472
packages/frontend/src/admin/AdminSsoPage.tsx
Normal file
|
|
@ -0,0 +1,472 @@
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import api from '../api/client';
|
||||||
|
|
||||||
|
interface SsoStatus {
|
||||||
|
microsoft: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cardStyle: React.CSSProperties = {
|
||||||
|
background: 'var(--color-bg-card)',
|
||||||
|
borderRadius: 'var(--radius-md)',
|
||||||
|
boxShadow: 'var(--shadow-sm)',
|
||||||
|
border: '1px solid var(--color-border)',
|
||||||
|
padding: '1.5rem',
|
||||||
|
marginBottom: '1.5rem',
|
||||||
|
};
|
||||||
|
|
||||||
|
const codeBlockStyle: React.CSSProperties = {
|
||||||
|
background: '#1e293b',
|
||||||
|
color: '#e2e8f0',
|
||||||
|
borderRadius: 'var(--radius-sm)',
|
||||||
|
padding: '1rem 1.25rem',
|
||||||
|
fontSize: '0.8125rem',
|
||||||
|
fontFamily: "'JetBrains Mono', 'Fira Code', 'Consolas', monospace",
|
||||||
|
lineHeight: 1.7,
|
||||||
|
overflowX: 'auto',
|
||||||
|
whiteSpace: 'pre',
|
||||||
|
marginTop: '0.75rem',
|
||||||
|
};
|
||||||
|
|
||||||
|
const inlineCodeStyle: React.CSSProperties = {
|
||||||
|
background: 'var(--color-bg)',
|
||||||
|
padding: '0.125rem 0.375rem',
|
||||||
|
borderRadius: 'var(--radius-sm)',
|
||||||
|
fontSize: '0.8125rem',
|
||||||
|
fontFamily: "'JetBrains Mono', 'Fira Code', 'Consolas', monospace",
|
||||||
|
color: '#1e40af',
|
||||||
|
};
|
||||||
|
|
||||||
|
const stepStyle: React.CSSProperties = {
|
||||||
|
display: 'flex',
|
||||||
|
gap: '1rem',
|
||||||
|
marginBottom: '1.5rem',
|
||||||
|
};
|
||||||
|
|
||||||
|
const stepNumberStyle: React.CSSProperties = {
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
borderRadius: '50%',
|
||||||
|
background: 'var(--color-primary)',
|
||||||
|
color: 'white',
|
||||||
|
fontSize: '0.875rem',
|
||||||
|
fontWeight: 700,
|
||||||
|
flexShrink: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
const stepContentStyle: React.CSSProperties = {
|
||||||
|
flex: 1,
|
||||||
|
paddingTop: '0.25rem',
|
||||||
|
};
|
||||||
|
|
||||||
|
const h3Style: React.CSSProperties = {
|
||||||
|
fontSize: '1rem',
|
||||||
|
fontWeight: 600,
|
||||||
|
color: 'var(--color-text)',
|
||||||
|
marginBottom: '0.5rem',
|
||||||
|
};
|
||||||
|
|
||||||
|
const pStyle: React.CSSProperties = {
|
||||||
|
fontSize: '0.875rem',
|
||||||
|
color: 'var(--color-text-secondary)',
|
||||||
|
lineHeight: 1.6,
|
||||||
|
marginBottom: '0.5rem',
|
||||||
|
};
|
||||||
|
|
||||||
|
const tableStyle: React.CSSProperties = {
|
||||||
|
width: '100%',
|
||||||
|
borderCollapse: 'collapse',
|
||||||
|
marginTop: '0.75rem',
|
||||||
|
};
|
||||||
|
|
||||||
|
const thStyle: React.CSSProperties = {
|
||||||
|
padding: '0.625rem 0.75rem',
|
||||||
|
textAlign: 'left',
|
||||||
|
fontSize: '0.75rem',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
color: 'var(--color-text-muted)',
|
||||||
|
borderBottom: '1px solid var(--color-border)',
|
||||||
|
background: 'var(--color-bg)',
|
||||||
|
};
|
||||||
|
|
||||||
|
const tdStyle: React.CSSProperties = {
|
||||||
|
padding: '0.625rem 0.75rem',
|
||||||
|
fontSize: '0.8125rem',
|
||||||
|
borderBottom: '1px solid var(--color-border)',
|
||||||
|
verticalAlign: 'top',
|
||||||
|
};
|
||||||
|
|
||||||
|
export function AdminSsoPage() {
|
||||||
|
const { data: ssoStatus, isLoading } = useQuery<SsoStatus>({
|
||||||
|
queryKey: ['sso', 'status'],
|
||||||
|
queryFn: async () => {
|
||||||
|
const res = await api.get<SsoStatus>('/auth/sso/status');
|
||||||
|
return res.data;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const redirectUri = `${window.location.origin}/api/v1/auth/sso/microsoft/callback`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1.5rem' }}>
|
||||||
|
<h1 style={{ fontSize: '1.5rem', fontWeight: 600 }}>SSO-Konfiguration</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Status-Anzeige */}
|
||||||
|
<div style={cardStyle}>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem', marginBottom: '0.5rem' }}>
|
||||||
|
<svg width="24" height="24" viewBox="0 0 21 21" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect x="1" y="1" width="9" height="9" fill="#f25022" />
|
||||||
|
<rect x="11" y="1" width="9" height="9" fill="#7fba00" />
|
||||||
|
<rect x="1" y="11" width="9" height="9" fill="#00a4ef" />
|
||||||
|
<rect x="11" y="11" width="9" height="9" fill="#ffb900" />
|
||||||
|
</svg>
|
||||||
|
<h2 style={{ fontSize: '1.125rem', fontWeight: 600 }}>Microsoft Entra ID (Azure AD)</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '0.5rem',
|
||||||
|
padding: '0.75rem 1rem',
|
||||||
|
borderRadius: 'var(--radius-sm)',
|
||||||
|
background: ssoStatus?.microsoft ? '#f0fdf4' : '#fef2f2',
|
||||||
|
border: `1px solid ${ssoStatus?.microsoft ? '#bbf7d0' : '#fecaca'}`,
|
||||||
|
marginTop: '0.75rem',
|
||||||
|
}}>
|
||||||
|
<span style={{
|
||||||
|
display: 'inline-block',
|
||||||
|
width: 10,
|
||||||
|
height: 10,
|
||||||
|
borderRadius: '50%',
|
||||||
|
background: ssoStatus?.microsoft ? 'var(--color-success)' : 'var(--color-error)',
|
||||||
|
}} />
|
||||||
|
<span style={{
|
||||||
|
fontSize: '0.875rem',
|
||||||
|
fontWeight: 500,
|
||||||
|
color: ssoStatus?.microsoft ? '#166534' : '#991b1b',
|
||||||
|
}}>
|
||||||
|
{isLoading
|
||||||
|
? 'Status wird geladen...'
|
||||||
|
: ssoStatus?.microsoft
|
||||||
|
? 'SSO ist aktiv — Benutzer können sich mit Microsoft anmelden'
|
||||||
|
: 'SSO ist nicht konfiguriert — folge der Anleitung unten'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Einrichtungs-Anleitung */}
|
||||||
|
<div style={cardStyle}>
|
||||||
|
<h2 style={{ fontSize: '1.125rem', fontWeight: 600, marginBottom: '1.25rem' }}>
|
||||||
|
Einrichtungsanleitung
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
{/* Schritt 1 */}
|
||||||
|
<div style={stepStyle}>
|
||||||
|
<div style={stepNumberStyle}>1</div>
|
||||||
|
<div style={stepContentStyle}>
|
||||||
|
<h3 style={h3Style}>App Registration im Azure Portal anlegen</h3>
|
||||||
|
<p style={pStyle}>
|
||||||
|
Gehe zum{' '}
|
||||||
|
<a
|
||||||
|
href="https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationsListBlade"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
style={{ color: 'var(--color-primary)', textDecoration: 'underline' }}
|
||||||
|
>
|
||||||
|
Azure Portal → App registrations
|
||||||
|
</a>
|
||||||
|
{' '}und klicke auf <strong>New registration</strong>.
|
||||||
|
</p>
|
||||||
|
<table style={tableStyle}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style={thStyle}>Feld</th>
|
||||||
|
<th style={thStyle}>Wert</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style={tdStyle}><strong>Name</strong></td>
|
||||||
|
<td style={tdStyle}>INSIGHT Platform</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style={tdStyle}><strong>Supported account types</strong></td>
|
||||||
|
<td style={tdStyle}>
|
||||||
|
Accounts in this organizational directory only<br />
|
||||||
|
<span style={{ fontSize: '0.75rem', color: 'var(--color-text-muted)' }}>
|
||||||
|
(Single tenant — nur euer Azure AD)
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style={tdStyle}><strong>Redirect URI</strong></td>
|
||||||
|
<td style={tdStyle}>
|
||||||
|
Platform: <strong>Web</strong><br />
|
||||||
|
URI: <code style={inlineCodeStyle}>{redirectUri}</code>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Schritt 2 */}
|
||||||
|
<div style={stepStyle}>
|
||||||
|
<div style={stepNumberStyle}>2</div>
|
||||||
|
<div style={stepContentStyle}>
|
||||||
|
<h3 style={h3Style}>Client Secret erstellen</h3>
|
||||||
|
<p style={pStyle}>
|
||||||
|
In der App Registration → <strong>Certificates & secrets</strong> → <strong>New client secret</strong>.
|
||||||
|
</p>
|
||||||
|
<table style={tableStyle}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style={thStyle}>Feld</th>
|
||||||
|
<th style={thStyle}>Wert</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style={tdStyle}><strong>Description</strong></td>
|
||||||
|
<td style={tdStyle}>INSIGHT Platform SSO</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style={tdStyle}><strong>Expires</strong></td>
|
||||||
|
<td style={tdStyle}>
|
||||||
|
24 months (empfohlen)<br />
|
||||||
|
<span style={{ fontSize: '0.75rem', color: 'var(--color-text-muted)' }}>
|
||||||
|
Danach muss das Secret erneuert werden
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div style={{
|
||||||
|
marginTop: '0.75rem',
|
||||||
|
padding: '0.625rem 0.75rem',
|
||||||
|
background: '#fffbeb',
|
||||||
|
border: '1px solid #fde68a',
|
||||||
|
borderRadius: 'var(--radius-sm)',
|
||||||
|
fontSize: '0.8125rem',
|
||||||
|
color: '#92400e',
|
||||||
|
}}>
|
||||||
|
<strong>Wichtig:</strong> Den <strong>Value</strong> des Secrets sofort kopieren — er wird nur einmalig angezeigt!
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Schritt 3 */}
|
||||||
|
<div style={stepStyle}>
|
||||||
|
<div style={stepNumberStyle}>3</div>
|
||||||
|
<div style={stepContentStyle}>
|
||||||
|
<h3 style={h3Style}>API Permissions konfigurieren</h3>
|
||||||
|
<p style={pStyle}>
|
||||||
|
In der App Registration → <strong>API permissions</strong> → <strong>Add a permission</strong> → <strong>Microsoft Graph</strong> → <strong>Delegated permissions</strong>.
|
||||||
|
</p>
|
||||||
|
<p style={pStyle}>Folgende Berechtigungen hinzufügen:</p>
|
||||||
|
<table style={tableStyle}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style={thStyle}>Permission</th>
|
||||||
|
<th style={thStyle}>Typ</th>
|
||||||
|
<th style={thStyle}>Beschreibung</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style={tdStyle}><code style={inlineCodeStyle}>openid</code></td>
|
||||||
|
<td style={tdStyle}>Delegated</td>
|
||||||
|
<td style={tdStyle}>Sign users in</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style={tdStyle}><code style={inlineCodeStyle}>profile</code></td>
|
||||||
|
<td style={tdStyle}>Delegated</td>
|
||||||
|
<td style={tdStyle}>View users' basic profile</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style={tdStyle}><code style={inlineCodeStyle}>email</code></td>
|
||||||
|
<td style={tdStyle}>Delegated</td>
|
||||||
|
<td style={tdStyle}>View users' email address</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style={tdStyle}><code style={inlineCodeStyle}>User.Read</code></td>
|
||||||
|
<td style={tdStyle}>Delegated</td>
|
||||||
|
<td style={tdStyle}>Sign in and read user profile</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p style={{ ...pStyle, marginTop: '0.5rem' }}>
|
||||||
|
Dann <strong>Grant admin consent</strong> klicken, um die Berechtigungen für alle Benutzer freizugeben.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Schritt 4 */}
|
||||||
|
<div style={stepStyle}>
|
||||||
|
<div style={stepNumberStyle}>4</div>
|
||||||
|
<div style={stepContentStyle}>
|
||||||
|
<h3 style={h3Style}>Werte aus Azure Portal kopieren</h3>
|
||||||
|
<p style={pStyle}>
|
||||||
|
In der App Registration → <strong>Overview</strong> findest du die benötigten Werte:
|
||||||
|
</p>
|
||||||
|
<table style={tableStyle}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style={thStyle}>Azure Portal Feld</th>
|
||||||
|
<th style={thStyle}>Umgebungsvariable</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style={tdStyle}><strong>Directory (tenant) ID</strong></td>
|
||||||
|
<td style={tdStyle}><code style={inlineCodeStyle}>AZURE_TENANT_ID</code></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style={tdStyle}><strong>Application (client) ID</strong></td>
|
||||||
|
<td style={tdStyle}><code style={inlineCodeStyle}>AZURE_CLIENT_ID</code></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style={tdStyle}><strong>Client secret value</strong> (aus Schritt 2)</td>
|
||||||
|
<td style={tdStyle}><code style={inlineCodeStyle}>AZURE_CLIENT_SECRET</code></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Schritt 5 */}
|
||||||
|
<div style={stepStyle}>
|
||||||
|
<div style={stepNumberStyle}>5</div>
|
||||||
|
<div style={stepContentStyle}>
|
||||||
|
<h3 style={h3Style}>Umgebungsvariablen auf dem Server setzen</h3>
|
||||||
|
<p style={pStyle}>
|
||||||
|
Trage die Werte in die <code style={inlineCodeStyle}>.env</code>-Datei auf dem Server ein:
|
||||||
|
</p>
|
||||||
|
<div style={codeBlockStyle}>
|
||||||
|
{`AZURE_TENANT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||||
|
AZURE_CLIENT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||||
|
AZURE_CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
|
AZURE_REDIRECT_URI=${redirectUri}`}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Schritt 6 */}
|
||||||
|
<div style={stepStyle}>
|
||||||
|
<div style={stepNumberStyle}>6</div>
|
||||||
|
<div style={stepContentStyle}>
|
||||||
|
<h3 style={h3Style}>Core-Service neu starten</h3>
|
||||||
|
<p style={pStyle}>
|
||||||
|
Nach dem Setzen der Umgebungsvariablen den Backend-Service neu starten:
|
||||||
|
</p>
|
||||||
|
<div style={codeBlockStyle}>
|
||||||
|
{`docker compose restart core`}
|
||||||
|
</div>
|
||||||
|
<p style={{ ...pStyle, marginTop: '0.75rem' }}>
|
||||||
|
Nach dem Neustart sollte der Status oben auf{' '}
|
||||||
|
<strong style={{ color: '#166534' }}>aktiv</strong> wechseln und
|
||||||
|
der „Mit Microsoft anmelden"-Button auf der Login-Seite erscheinen.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Funktionsweise */}
|
||||||
|
<div style={cardStyle}>
|
||||||
|
<h2 style={{ fontSize: '1.125rem', fontWeight: 600, marginBottom: '1rem' }}>
|
||||||
|
Funktionsweise
|
||||||
|
</h2>
|
||||||
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1rem' }}>
|
||||||
|
<div style={{
|
||||||
|
padding: '1rem',
|
||||||
|
background: 'var(--color-bg)',
|
||||||
|
borderRadius: 'var(--radius-sm)',
|
||||||
|
}}>
|
||||||
|
<h3 style={{ ...h3Style, fontSize: '0.875rem' }}>Erstanmeldung</h3>
|
||||||
|
<p style={{ ...pStyle, fontSize: '0.8125rem', marginBottom: 0 }}>
|
||||||
|
Wenn ein Benutzer sich zum ersten Mal mit Microsoft anmeldet,
|
||||||
|
wird automatisch ein Konto angelegt (Rolle: Benutzer).
|
||||||
|
Existiert bereits ein Konto mit derselben E-Mail-Adresse,
|
||||||
|
wird das Microsoft-Konto automatisch verknüpft.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div style={{
|
||||||
|
padding: '1rem',
|
||||||
|
background: 'var(--color-bg)',
|
||||||
|
borderRadius: 'var(--radius-sm)',
|
||||||
|
}}>
|
||||||
|
<h3 style={{ ...h3Style, fontSize: '0.875rem' }}>Sicherheit</h3>
|
||||||
|
<p style={{ ...pStyle, fontSize: '0.8125rem', marginBottom: 0 }}>
|
||||||
|
Der SSO-Flow nutzt den OAuth2 Authorization Code Flow.
|
||||||
|
Das Client Secret verlässt nie den Server.
|
||||||
|
CSRF-Schutz via State-Parameter.
|
||||||
|
Die Multi-Faktor-Authentifizierung (MFA) von Microsoft wird unterstützt.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Technische Details */}
|
||||||
|
<div style={cardStyle}>
|
||||||
|
<h2 style={{ fontSize: '1.125rem', fontWeight: 600, marginBottom: '1rem' }}>
|
||||||
|
Technische Referenz
|
||||||
|
</h2>
|
||||||
|
<table style={tableStyle}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style={thStyle}>Endpoint</th>
|
||||||
|
<th style={thStyle}>Beschreibung</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style={tdStyle}><code style={inlineCodeStyle}>GET /api/v1/auth/sso/microsoft</code></td>
|
||||||
|
<td style={tdStyle}>Startet den SSO-Flow (Redirect zu Microsoft)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style={tdStyle}><code style={inlineCodeStyle}>GET /api/v1/auth/sso/microsoft/callback</code></td>
|
||||||
|
<td style={tdStyle}>Callback von Microsoft (Token-Exchange + User-Provisioning)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style={tdStyle}><code style={inlineCodeStyle}>GET /api/v1/auth/sso/status</code></td>
|
||||||
|
<td style={tdStyle}>Prüft ob SSO konfiguriert ist (für Login-Seite)</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h3 style={{ ...h3Style, marginTop: '1.25rem', fontSize: '0.9375rem' }}>Redirect URI</h3>
|
||||||
|
<p style={pStyle}>
|
||||||
|
Diese URI muss exakt so in der Azure App Registration eingetragen sein:
|
||||||
|
</p>
|
||||||
|
<div style={{
|
||||||
|
...codeBlockStyle,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
gap: '1rem',
|
||||||
|
}}>
|
||||||
|
<span>{redirectUri}</span>
|
||||||
|
<button
|
||||||
|
onClick={() => navigator.clipboard.writeText(redirectUri)}
|
||||||
|
style={{
|
||||||
|
padding: '0.25rem 0.5rem',
|
||||||
|
background: '#334155',
|
||||||
|
color: '#e2e8f0',
|
||||||
|
border: '1px solid #475569',
|
||||||
|
borderRadius: 'var(--radius-sm)',
|
||||||
|
fontSize: '0.75rem',
|
||||||
|
cursor: 'pointer',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Kopieren
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -6,6 +6,7 @@ import { AppLayout } from './AppLayout';
|
||||||
import { DashboardPage } from './DashboardPage';
|
import { DashboardPage } from './DashboardPage';
|
||||||
import { AdminUsersPage } from '../admin/AdminUsersPage';
|
import { AdminUsersPage } from '../admin/AdminUsersPage';
|
||||||
import { AdminTenantsPage } from '../admin/AdminTenantsPage';
|
import { AdminTenantsPage } from '../admin/AdminTenantsPage';
|
||||||
|
import { AdminSsoPage } from '../admin/AdminSsoPage';
|
||||||
import { ProfilePage } from '../profile/ProfilePage';
|
import { ProfilePage } from '../profile/ProfilePage';
|
||||||
|
|
||||||
function PrivateRoute({ children }: { children: React.ReactNode }) {
|
function PrivateRoute({ children }: { children: React.ReactNode }) {
|
||||||
|
|
@ -46,6 +47,7 @@ export function App() {
|
||||||
<Route path="profile" element={<ProfilePage />} />
|
<Route path="profile" element={<ProfilePage />} />
|
||||||
<Route path="admin/users" element={<AdminUsersPage />} />
|
<Route path="admin/users" element={<AdminUsersPage />} />
|
||||||
<Route path="admin/tenants" element={<AdminTenantsPage />} />
|
<Route path="admin/tenants" element={<AdminTenantsPage />} />
|
||||||
|
<Route path="admin/sso" element={<AdminSsoPage />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
{/* Fallback */}
|
{/* Fallback */}
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,14 @@ export function AppLayout() {
|
||||||
>
|
>
|
||||||
Mandanten
|
Mandanten
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
<NavLink
|
||||||
|
to="/admin/sso"
|
||||||
|
className={({ isActive }) =>
|
||||||
|
`${styles.navLink} ${isActive ? styles.active : ''}`
|
||||||
|
}
|
||||||
|
>
|
||||||
|
SSO-Konfiguration
|
||||||
|
</NavLink>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</nav>
|
</nav>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue