Add site-wide login gate protecting the whole app

Chat/memory/logs/models APIs were reachable without any auth even
though the app is deployed publicly behind Caddy HTTPS; only the
settings screen had a password. Add an optional SITE_PASSWORD-backed
session cookie gate in front of everything (settings included, on
top of its existing SETTINGS_PASSWORD), with a matching login screen
on the frontend. Stays fully backward compatible: with SITE_PASSWORD
unset the app behaves exactly as before.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-18 23:04:01 +09:00
co-authored by Claude Sonnet 5
parent 002282a8b7
commit 1ad8b130c7
12 changed files with 319 additions and 38 deletions
+24
View File
@@ -34,6 +34,7 @@ async function parseJson<T>(res: Response, label: string): Promise<T> {
throw new Error(`${label}: バックエンドに接続できません (${res.status} ${res.statusText})`)
}
if (!res.ok) {
if (res.status === 401) window.dispatchEvent(new Event('playchat:unauthorized'))
const body = await res.json().catch(() => ({}))
throw new Error(`${label}: ${body.detail ?? res.statusText}`)
}
@@ -81,6 +82,29 @@ export async function deleteLog(logId: number): Promise<void> {
await fetch(`${BASE}/logs/${logId}`, { method: 'DELETE' })
}
export interface AuthStatus {
required: boolean
authenticated: boolean
}
export async function fetchAuthStatus(): Promise<AuthStatus> {
const res = await fetch(`${BASE}/api/auth/status`)
return parseJson<AuthStatus>(res, '認証状態取得')
}
export async function login(password: string): Promise<AuthStatus> {
const res = await fetch(`${BASE}/api/auth/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ password }),
})
return parseJson<AuthStatus>(res, 'ログイン')
}
export async function logout(): Promise<void> {
await fetch(`${BASE}/api/auth/logout`, { method: 'POST' })
}
export interface SettingEntry {
key: string
is_set: boolean