Compare commits
No commits in common. "main" and "v0.2" have entirely different histories.
|
|
@ -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.).
|
||||
|
||||
|
|
|
|||
8
TODO.md
8
TODO.md
|
|
@ -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
|
||||
|
|
@ -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"
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
: []
|
||||
|
|
|
|||
|
|
@ -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) => (
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
import pkg from "../../package.json"
|
||||
|
||||
export const VERSION = pkg.version
|
||||
|
|
@ -41,7 +41,6 @@ export interface SectionConfig {
|
|||
|
||||
export interface AuthenticationConfig {
|
||||
logout_url: string
|
||||
login_url?: string
|
||||
header_format?: string
|
||||
username?: string
|
||||
header_user?: string
|
||||
|
|
|
|||
Loading…
Reference in a new issue