deploy configuration

This commit is contained in:
Daniel Noulet 2026-10-03 04:41:31 +02:00
parent 9ba3c47d82
commit dac02c61c3
45 changed files with 1405 additions and 582 deletions

View file

@ -1,5 +1,5 @@
{
"name": "cms-ui",
"name": "go-tell-ui",
"private": true,
"version": "0.1.0",
"type": "module",

View file

@ -54,7 +54,7 @@ export async function requestSession(): Promise<SessionResponse | null> {
}
function demoAuthHeaders(): Record<string, string> {
const token = window.sessionStorage.getItem('cms-token')
const token = window.sessionStorage.getItem('go-tell-token')
return token ? {Authorization: `Bearer ${token}`} : {}
}
@ -99,7 +99,7 @@ async function contentRequest<T>(path = '', init?: RequestInit): Promise<T> {
headers: { Accept: 'application/json', ...demoAuthHeaders(), ...(init?.body ? { 'Content-Type': 'application/json' } : {}), ...init?.headers },
})
if (!response.ok) {
if (response.status === 401) throw new Error('Your SSO session is required to edit content.')
if (response.status === 401) throw new Error('You have to sign in to edit content.')
throw new Error((await response.text()) || 'Unable to load content.')
}
return (await response.json()) as T

View file

@ -1,9 +1,10 @@
import { createContext, useCallback, useContext, useEffect, useMemo, useState, type ReactNode } from 'react'
import { apiConfig, requestSession, signInWithGoogle, sudoLogin } from './api'
import { apiConfig, apiUrl, requestSession, signInWithGoogle, sudoLogin } from './api'
// There is no sign-in or sign-out: the upstream SSO proxy decides whether the
// current request carries the owner's identity. Signing in just sends the
// browser to the configured Google/IdP URL.
// Signing in is either the Go Loose browser login or a redirect to the
// identity-aware proxy, depending on what the API advertises: /api/auth/session
// returns loginUrl and logoutUrl when Go Loose is configured. The demo profile
// keeps using its own token in sessionStorage.
type AuthState = {
loading: boolean
authenticated: boolean
@ -12,6 +13,7 @@ type AuthState = {
error: string | null
refresh: () => Promise<void>
canSignIn: boolean
canSignOut: boolean
signIn: () => void
sudo: (username: string, password: string) => Promise<void>
signOut: () => void
@ -23,6 +25,8 @@ export function AuthProvider({ children }: { children: ReactNode }) {
const [authenticated, setAuthenticated] = useState(false)
const [canEdit, setCanEdit] = useState(false)
const [username, setUsername] = useState<string | undefined>()
const [loginUrl, setLoginUrl] = useState('')
const [logoutUrl, setLogoutUrl] = useState('')
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
@ -33,10 +37,12 @@ export function AuthProvider({ children }: { children: ReactNode }) {
setAuthenticated(session?.authenticated === true)
setCanEdit(session?.canEdit === true)
setUsername(session?.username)
setLoginUrl(session?.loginUrl ?? '')
setLogoutUrl(session?.logoutUrl ?? '')
} catch (err) {
setAuthenticated(false)
setCanEdit(false)
setError(err instanceof Error ? err.message : 'Unable to check your SSO session.')
setError(err instanceof Error ? err.message : 'Unable to check your session.')
} finally {
setLoading(false)
}
@ -44,25 +50,36 @@ export function AuthProvider({ children }: { children: ReactNode }) {
useEffect(() => { void refresh() }, [refresh])
const signIn = useCallback(() => { signInWithGoogle() }, [])
const signIn = useCallback(() => {
if (loginUrl) {
window.location.assign(apiUrl(loginUrl))
return
}
signInWithGoogle()
}, [loginUrl])
const sudo = useCallback(async (username: string, password: string) => {
const result = await sudoLogin(username, password)
window.sessionStorage.setItem('cms-token', result.token)
window.sessionStorage.setItem('go-tell-token', result.token)
setAuthenticated(true)
setCanEdit(true)
setUsername(result.user.username)
}, [])
const signOut = useCallback(() => {
window.sessionStorage.removeItem('cms-token')
if (logoutUrl) {
window.location.assign(apiUrl(logoutUrl))
return
}
window.sessionStorage.removeItem('go-tell-token')
setAuthenticated(false)
setCanEdit(false)
setUsername(undefined)
}, [])
const canSignIn = apiConfig.signIn !== ''
}, [logoutUrl])
const canSignIn = apiConfig.signIn !== '' || loginUrl !== ''
const canSignOut = logoutUrl !== ''
const value = useMemo(
() => ({ loading, authenticated, canEdit, username, error, refresh, canSignIn, signIn, sudo, signOut }),
[loading, authenticated, canEdit, username, error, refresh, canSignIn, signIn, sudo, signOut],
() => ({ loading, authenticated, canEdit, username, error, refresh, canSignIn, canSignOut, signIn, sudo, signOut }),
[loading, authenticated, canEdit, username, error, refresh, canSignIn, canSignOut, signIn, sudo, signOut],
)
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>
}

View file

@ -6,7 +6,7 @@ import {type DemoUser} from '../models'
import {SudoTerminal} from './SudoTerminal'
export function Shell({children}: {children: ReactNode}) {
const {authenticated, loading, canSignIn, signIn, username, sudo} = useAuth()
const {authenticated, loading, canSignIn, canSignOut, signIn, signOut, username, sudo} = useAuth()
const [sudoOpen, setSudoOpen] = useState(false)
const [helpOpen, setHelpOpen] = useState(false)
const [users, setUsers] = useState<DemoUser[]>([])
@ -24,7 +24,10 @@ export function Shell({children}: {children: ReactNode}) {
<span className="brand-cursor">_</span><small>linux · go · java · ideas</small></Link>
<div className="header-actions">
{!loading && <button className="mode-badge" type="button" onClick={() => {
if (authenticated) return
if (authenticated) {
if (canSignOut) signOut()
return
}
if (users.length > 0) setSudoOpen(true)
else if (canSignIn) signIn()
}}>

View file

@ -10,6 +10,12 @@ export type SessionResponse = {
authenticated?: boolean
canEdit?: boolean
username?: string
email?: string
displayName?: string
// Go Loose advertises its own login and logout endpoints; the UI follows them
// instead of sending the browser to a single-sign-on proxy.
loginUrl?: string
logoutUrl?: string
}
export type DemoUser = { username: string; name: string }