feat: authentication with logout button and username display
This commit is contained in:
parent
59d49d0299
commit
1c8284e79d
|
|
@ -7,6 +7,8 @@ site:
|
|||
# Informations du site
|
||||
theme:
|
||||
# Apparence
|
||||
authentication:
|
||||
# Authentification (optionnel)
|
||||
sections:
|
||||
# Liste des sections et liens
|
||||
```
|
||||
|
|
@ -46,6 +48,24 @@ theme:
|
|||
background: "gradient"
|
||||
```
|
||||
|
||||
## `authentication`
|
||||
|
||||
Configuration optionnelle pour l'authentification via un reverse proxy (Authelia, Authentik, etc.).
|
||||
|
||||
| Champ | Requis | Type | Défaut | Description |
|
||||
|---|---|---|---|---|
|
||||
| `logout_url` | Oui | chaîne (URL) | — | URL de déconnexion (ex: `"https://auth.example.com/logout"`) |
|
||||
| `header_format` | Non | chaîne | `"Remote-User"` | Nom de l'en-tête HTTP indiquant que l'utilisateur est connecté |
|
||||
| `username` | Non | chaîne | — | Nom de l'en-tête HTTP contenant le nom d'utilisateur (ex: `"Remote-User"`). Affiche le nom dans le header quand renseigné. |
|
||||
|
||||
Quand l'en-tête défini par `header_format` est présent dans la requête, un bouton de déconnexion apparaît à droite du bouton thème.
|
||||
|
||||
```yaml
|
||||
authentication:
|
||||
logout_url: "https://auth.example.com/logout"
|
||||
header_format: "Remote-User"
|
||||
```
|
||||
|
||||
## `sections`
|
||||
|
||||
Tableau de sections. Chaque section contient :
|
||||
|
|
@ -124,6 +144,10 @@ theme:
|
|||
accent: "#8b5cf6"
|
||||
background: "gradient"
|
||||
|
||||
authentication:
|
||||
logout_url: "https://auth.example.com/logout"
|
||||
header_format: "Remote-User"
|
||||
|
||||
sections:
|
||||
- id: "services"
|
||||
title: "Services"
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
site:
|
||||
name: "Soch Family"
|
||||
title: "Les applications de la famille"
|
||||
#logoUrl: ""
|
||||
title: " "
|
||||
#title: "Les applications de la famille"
|
||||
logoUrl: "https://immich.socheleau.fr/api/assets/e010423f-ca95-45a4-a9ee-9bb1a5a48688/thumbnail?slug=us_6&size=preview&c=IhgKJYiHg4ZfhXh6h5aJp5eAdQlJ&edited=true"
|
||||
#description: "Accès rapide à nos services"
|
||||
showFooter: false
|
||||
|
||||
|
|
@ -10,10 +11,16 @@ theme:
|
|||
accent: "#8b5cf6"
|
||||
background: "gradient"
|
||||
|
||||
authentication:
|
||||
logout_url: "https://auth.socheleau.fr/logout"
|
||||
header_format: "Remote-User"
|
||||
username: "Remote-User"
|
||||
|
||||
sections:
|
||||
- id: "global"
|
||||
title: "Global"
|
||||
description: "Services accessibles à toute la famille"
|
||||
cardLayout: "horizontal"
|
||||
links:
|
||||
- id: "nextcloud"
|
||||
name: "Nextcloud"
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { headers } from "next/headers"
|
||||
import { getConfig } from "@/lib/config"
|
||||
import { Dashboard } from "@/components/dashboard/Dashboard"
|
||||
import { ErrorDisplay } from "@/components/ui/ErrorDisplay"
|
||||
|
|
@ -18,12 +19,25 @@ function getConfigResult() {
|
|||
}
|
||||
}
|
||||
|
||||
export default function Home() {
|
||||
export default async function Home() {
|
||||
const result = getConfigResult()
|
||||
|
||||
if (!result.ok) {
|
||||
return <ErrorDisplay message={result.message} />
|
||||
}
|
||||
|
||||
return <Dashboard config={result.config} />
|
||||
const { config } = result
|
||||
const authHeader = config.authentication?.header_format ?? "Remote-User"
|
||||
const usernameHeader = config.authentication?.username
|
||||
const headersList = await headers()
|
||||
const isAuthenticated = config.authentication != null && headersList.has(authHeader)
|
||||
const username = isAuthenticated && usernameHeader ? headersList.get(usernameHeader) ?? null : null
|
||||
|
||||
return (
|
||||
<Dashboard
|
||||
config={config}
|
||||
isAuthenticated={isAuthenticated}
|
||||
username={username}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,12 +4,20 @@ import type { DashboardConfig } from "@/types/config"
|
|||
|
||||
interface DashboardProps {
|
||||
config: DashboardConfig
|
||||
isAuthenticated?: boolean
|
||||
username?: string | null
|
||||
}
|
||||
|
||||
export function Dashboard({ config }: DashboardProps) {
|
||||
export function Dashboard({ config, isAuthenticated = false, username = null }: DashboardProps) {
|
||||
return (
|
||||
<div className="dashboard-root mx-auto flex w-full max-w-7xl flex-col px-4 py-8 sm:px-6 lg:px-8">
|
||||
<DashboardHeader site={config.site} initialTheme={config.theme.mode} />
|
||||
<DashboardHeader
|
||||
site={config.site}
|
||||
initialTheme={config.theme.mode}
|
||||
authentication={config.authentication}
|
||||
isAuthenticated={isAuthenticated}
|
||||
username={username}
|
||||
/>
|
||||
<main className="flex flex-col gap-12">
|
||||
{config.sections.map((section, index) => (
|
||||
<DashboardSection key={section.id} section={section} sectionIndex={index} />
|
||||
|
|
|
|||
|
|
@ -1,14 +1,17 @@
|
|||
"use client"
|
||||
|
||||
import { useState, useEffect } from "react"
|
||||
import type { SiteConfig } from "@/types/config"
|
||||
import type { SiteConfig, AuthenticationConfig } from "@/types/config"
|
||||
|
||||
interface DashboardHeaderProps {
|
||||
site: SiteConfig
|
||||
initialTheme: "dark" | "light"
|
||||
authentication?: AuthenticationConfig
|
||||
isAuthenticated?: boolean
|
||||
username?: string | null
|
||||
}
|
||||
|
||||
export function DashboardHeader({ site, initialTheme }: DashboardHeaderProps) {
|
||||
export function DashboardHeader({ site, initialTheme, authentication, isAuthenticated = false, username = null }: DashboardHeaderProps) {
|
||||
const [isLight, setIsLight] = useState(initialTheme === "light")
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -47,10 +50,10 @@ export function DashboardHeader({ site, initialTheme }: DashboardHeaderProps) {
|
|||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
{site.description && (
|
||||
<p className="hidden text-sm text-[var(--text-tertiary)] lg:block">
|
||||
{site.description}
|
||||
</p>
|
||||
{isAuthenticated && username && (
|
||||
<span className="hidden text-sm text-[var(--text-secondary)] lg:block">
|
||||
{username}
|
||||
</span>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
|
|
@ -103,11 +106,40 @@ export function DashboardHeader({ site, initialTheme }: DashboardHeaderProps) {
|
|||
</svg>
|
||||
)}
|
||||
</button>
|
||||
{isAuthenticated && authentication && (
|
||||
<a
|
||||
href={authentication.logout_url}
|
||||
className="flex size-10 items-center justify-center rounded-lg border transition-colors hover:opacity-80 active:opacity-60 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--focus-ring)] sm:size-9"
|
||||
style={{
|
||||
backgroundColor: "rgba(128,128,128,0.15)",
|
||||
borderColor: "rgba(128,128,128,0.25)",
|
||||
color: "var(--text-tertiary)",
|
||||
}}
|
||||
aria-label="Déconnexion"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" />
|
||||
<polyline points="16 17 21 12 16 7" />
|
||||
<line x1="21" x2="9" y1="12" y2="12" />
|
||||
</svg>
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{site.description && (
|
||||
<p className="text-sm text-[var(--text-tertiary)] lg:hidden">
|
||||
{site.description}
|
||||
{isAuthenticated && username && (
|
||||
<p className="text-sm text-[var(--text-secondary)] lg:hidden">
|
||||
{username}
|
||||
</p>
|
||||
)}
|
||||
</header>
|
||||
|
|
|
|||
|
|
@ -39,6 +39,12 @@ export const siteSchema = z.object({
|
|||
showFooter: z.boolean().optional().default(true),
|
||||
})
|
||||
|
||||
export const authenticationSchema = z.object({
|
||||
logout_url: z.string().url("Invalid logout URL").min(1, "Logout URL is required"),
|
||||
header_format: z.string().optional().default("Remote-User"),
|
||||
username: z.string().optional(),
|
||||
})
|
||||
|
||||
export const dashboardConfigSchema = z.object({
|
||||
site: siteSchema,
|
||||
theme: themeSchema.optional().default({
|
||||
|
|
@ -46,5 +52,6 @@ export const dashboardConfigSchema = z.object({
|
|||
accent: "#8b5cf6",
|
||||
background: "gradient",
|
||||
}),
|
||||
authentication: authenticationSchema.optional(),
|
||||
sections: z.array(sectionSchema).default([]),
|
||||
})
|
||||
|
|
|
|||
|
|
@ -32,8 +32,15 @@ export interface SectionConfig {
|
|||
links: LinkConfig[]
|
||||
}
|
||||
|
||||
export interface AuthenticationConfig {
|
||||
logout_url: string
|
||||
header_format?: string
|
||||
username?: string
|
||||
}
|
||||
|
||||
export interface DashboardConfig {
|
||||
site: SiteConfig
|
||||
theme: ThemeConfig
|
||||
authentication?: AuthenticationConfig
|
||||
sections: SectionConfig[]
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue