Compare commits

..

No commits in common. "main" and "v0.2" have entirely different histories.
main ... v0.2

12 changed files with 9 additions and 79 deletions

View file

@ -11,7 +11,7 @@ Le bouton de bascule thème clair/sombre ne répond pas au toucher sur Firefox i
- Tester avec un bouton complètement isolé (hors du header, hors des composants dashboard)
- Vérifier si le problème persiste dans une page vide sans CSS gradient
## Filtrage par en-têtes HTTP (ex: Authelia) — ✅ fait (v0.2)
## Filtrage par en-têtes HTTP (ex: Authelia)
Masquer/afficher des sections ou des liens en fonction des en-têtes HTTP envoyés par un reverse proxy ou un provider d'authentification (Authelia, Authentik, etc.).

View file

@ -1,8 +0,0 @@
# TODO
## Sécurité
- Retirer le token Forgejo de l'URL du remote git dans `.git/config`
(actuellement en clair : `https://maxsoch:<token>@git.socheleau.fr/...`)
→ utiliser un credential helper ou SSH à la place
- Vérifier que le token n'apparaît pas ailleurs dans le repo

View file

@ -55,18 +55,16 @@ Configuration optionnelle pour l'authentification via un reverse proxy (Authelia
| Champ | Requis | Type | Défaut | Description |
|---|---|---|---|---|
| `logout_url` | Oui | chaîne (URL) | — | URL de déconnexion (ex: `"https://auth.example.com/logout"`) |
| `login_url` | Non | chaîne (URL) | — | URL de connexion. Un bouton login remplace le bouton logout quand l'utilisateur n'est pas connecté (ex: `"https://auth.example.com"`) |
| `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-Name"`). Affiche "Hi {valeur}" dans le header. |
| `header_user` | Non | chaîne | — | Nom de l'en-tête HTTP pour filtrer par utilisateur (ex: `"Remote-User"`) |
| `header_group` | Non | chaîne | — | Nom de l'en-tête HTTP pour filtrer par groupe (ex: `"Remote-Groups"`) |
Quand l'en-tête défini par `header_format` est présent **avec une valeur valide** (non vide, sans placeholder non résolu), un bouton de déconnexion apparaît à droite du bouton thème. Sinon, si `login_url` est défini, un bouton de connexion est affiché à la place.
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"
login_url: "https://auth.example.com"
header_format: "Remote-User"
```

View file

@ -12,7 +12,6 @@ theme:
authentication:
logout_url: "https://auth.socheleau.fr/logout"
login_url: "https://auth.socheleau.fr"
header_format: "Remote-User"
username: "Remote-Name"
header_user: "Remote-User"

View file

@ -1,6 +1,6 @@
{
"name": "sochboard",
"version": "0.3.0",
"version": "0.2.0",
"private": true,
"scripts": {
"dev": "node node_modules/next/dist/bin/next dev",

View file

@ -33,16 +33,12 @@ html.theme-light {
--focus-ring-offset: #f8fafc;
}
body {
.dashboard-root {
min-height: 100vh;
background: var(--bg-gradient);
color: var(--text-primary);
}
.dashboard-root {
min-height: 100vh;
}
@keyframes fade-in {
from {
opacity: 0;

View file

@ -5,14 +5,6 @@ import { ErrorDisplay } from "@/components/ui/ErrorDisplay"
export const dynamic = "force-dynamic"
function isValidHeaderValue(value: string | null | undefined): value is string {
if (!value) return false
const trimmed = value.trim()
if (trimmed.length === 0) return false
if (trimmed.includes("{") || trimmed.includes("}")) return false
return true
}
function getConfigResult() {
try {
return { ok: true as const, config: getConfig() }
@ -38,17 +30,12 @@ export default async function Home() {
const authHeader = config.authentication?.header_format ?? "Remote-User"
const usernameHeader = config.authentication?.username
const headersList = await headers()
const authValue = config.authentication ? headersList.get(authHeader) : null
const isAuthenticated = isValidHeaderValue(authValue)
const username = isAuthenticated && usernameHeader && isValidHeaderValue(headersList.get(usernameHeader))
? headersList.get(usernameHeader)!.trim()
: null
const isAuthenticated = config.authentication != null && headersList.has(authHeader)
const username = isAuthenticated && usernameHeader ? headersList.get(usernameHeader) ?? null : null
const userHeader = config.authentication?.header_user
const groupHeader = config.authentication?.header_group
const currentUser = isAuthenticated && userHeader && isValidHeaderValue(headersList.get(userHeader))
? headersList.get(userHeader)!.trim()
: null
const currentUser = isAuthenticated && userHeader ? headersList.get(userHeader) ?? null : null
const userGroups = isAuthenticated && groupHeader
? (headersList.get(groupHeader) ?? "").split(",").map((g) => g.trim()).filter(Boolean)
: []

View file

@ -1,6 +1,5 @@
import { DashboardHeader } from "./DashboardHeader"
import { DashboardSection } from "./DashboardSection"
import { VERSION } from "@/lib/version"
import type { DashboardConfig } from "@/types/config"
interface DashboardProps {
@ -21,7 +20,6 @@ export function Dashboard({ config, isAuthenticated = false, username = null, cu
isAuthenticated={isAuthenticated}
username={username}
accent={config.theme.accent}
version={VERSION}
/>
<main className="flex flex-col gap-12">
{config.sections.map((section, index) => (

View file

@ -11,10 +11,9 @@ interface DashboardHeaderProps {
isAuthenticated?: boolean
username?: string | null
accent?: string
version?: string
}
export function DashboardHeader({ site, initialTheme, authentication, isAuthenticated = false, username = null, accent, version }: DashboardHeaderProps) {
export function DashboardHeader({ site, initialTheme, authentication, isAuthenticated = false, username = null, accent }: DashboardHeaderProps) {
const [isLight, setIsLight] = useState(initialTheme === "light")
useEffect(() => {
@ -45,9 +44,6 @@ export function DashboardHeader({ site, initialTheme, authentication, isAuthenti
<p className="mt-0.5 text-sm text-[var(--text-secondary)]">
{site.title}
</p>
{version && (
<p className="text-xs text-[var(--text-secondary)]">v{version}</p>
)}
</div>
</div>
<div className="flex items-center gap-3">
@ -107,7 +103,7 @@ export function DashboardHeader({ site, initialTheme, authentication, isAuthenti
</svg>
)}
</button>
{isAuthenticated && authentication ? (
{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"
@ -135,37 +131,6 @@ export function DashboardHeader({ site, initialTheme, authentication, isAuthenti
<line x1="21" x2="9" y1="12" y2="12" />
</svg>
</a>
) : (
!isAuthenticated &&
authentication?.login_url && (
<a
href={authentication.login_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="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="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4" />
<polyline points="10 17 15 12 10 7" />
<line x1="15" x2="3" y1="12" y2="12" />
</svg>
</a>
)
)}
</div>
</div>

View file

@ -46,7 +46,6 @@ export const siteSchema = z.object({
export const authenticationSchema = z.object({
logout_url: z.string().url("Invalid logout URL").min(1, "Logout URL is required"),
login_url: z.string().url("Invalid login URL").optional(),
header_format: z.string().optional().default("Remote-User"),
username: z.string().optional(),
header_user: z.string().optional(),

View file

@ -1,3 +0,0 @@
import pkg from "../../package.json"
export const VERSION = pkg.version

View file

@ -41,7 +41,6 @@ export interface SectionConfig {
export interface AuthenticationConfig {
logout_url: string
login_url?: string
header_format?: string
username?: string
header_user?: string