feat(auth): replace cookie/api-key auth with JWT Bearer tokens, separate UI from API
Publish Metadata Agent Image / build-and-push-image (push) Successful in 6m3s
Publish Node Player Image / build-and-push-image (push) Failing after 58s
Publish Web Player Image / build-and-push-image (push) Has been cancelled

- Add JWT Bearer token validation to Rust API via OIDC provider JWKS
  with automatic key rotation and 1-hour cache
- Remove x-api-key auth support and built-in web UI from furumi-web-player,
  leaving it as a pure API server
- Add /auth/token endpoint to Node player server to expose OIDC access
  tokens to the frontend
- Move Node player auth endpoints from /api/* to /auth/* to avoid
  path conflicts with Rust API
- Add static file serving to Node Express server for production
  single-container deployment
- Fix SameSite=Strict cookie issue breaking OIDC redirect flow (use Lax)
- Add Dockerfile.node-player with multi-stage Node.js build
- Add CI workflows for node-player Docker image (dev + release)
- Optimize Rust Dockerfiles with dependency caching layer
- Update docker-compose with OIDC env vars and OLLAMA_MODEL support
- Cherry-pick agent LLM client fixes from DEV branch

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ultradesu
2026-04-08 14:51:52 +01:00
parent 94d14e8fc8
commit e99cacae8b
20 changed files with 515 additions and 161 deletions
+1 -2
View File
@@ -1,2 +1 @@
VITE_API_BASE_URL=http://localhost:8085
VITE_API_KEY=
VITE_FURUMI_API_URL=http://localhost:8085
+25 -6
View File
@@ -1,5 +1,6 @@
import { useEffect, useMemo, useState } from 'react'
import { useEffect, useState } from 'react'
import { FurumiPlayer } from './FurumiPlayer'
import { setAuthToken, clearAuthToken } from './furumiApi'
import './App.css'
type UserProfile = {
@@ -22,7 +23,7 @@ function App() {
}
})
const apiBase = useMemo(() => import.meta.env.VITE_API_BASE_URL ?? '', [])
const apiBase = ''
useEffect(() => {
if (runWithoutAuth) {
@@ -34,12 +35,13 @@ function App() {
const loadMe = async () => {
try {
const response = await fetch(`${apiBase}/api/me`, {
const response = await fetch(`${apiBase}/auth/me`, {
credentials: 'include',
})
if (response.status === 401) {
setUser(null)
clearAuthToken()
return
}
@@ -49,6 +51,23 @@ function App() {
const data = await response.json()
setUser(data.user ?? null)
// Fetch OIDC access token for Rust API Bearer auth
if (data.user) {
try {
const tokenRes = await fetch(`${apiBase}/auth/token`, {
credentials: 'include',
})
if (tokenRes.ok) {
const tokenData = await tokenRes.json()
if (tokenData.access_token) {
setAuthToken(tokenData.access_token)
}
}
} catch {
// Token fetch failed — API calls will fall back to other auth methods
}
}
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to load session')
} finally {
@@ -57,10 +76,10 @@ function App() {
}
void loadMe()
}, [apiBase, runWithoutAuth])
}, [runWithoutAuth])
const loginUrl = `${apiBase}/api/login`
const logoutUrl = `${apiBase}/api/logout`
const loginUrl = `${apiBase}/auth/login`
const logoutUrl = `${apiBase}/auth/logout`
return (
<>
+10 -5
View File
@@ -1,16 +1,21 @@
import axios from 'axios'
import type { Album, Artist, SearchResult, Track, TrackDetail } from './types'
const API_BASE = import.meta.env.VITE_API_BASE_URL ?? ''
export const API_ROOT = `${API_BASE}/api`
const API_KEY = import.meta.env.VITE_API_KEY
const FURUMI_API_BASE = import.meta.env.VITE_FURUMI_API_URL ?? ''
export const API_ROOT = `${FURUMI_API_BASE}/api`
export const furumiApi = axios.create({
baseURL: API_ROOT,
headers: API_KEY ? { 'x-api-key': API_KEY } : {},
})
export function setAuthToken(token: string) {
furumiApi.defaults.headers.common['Authorization'] = `Bearer ${token}`
}
export function clearAuthToken() {
delete furumiApi.defaults.headers.common['Authorization']
}
export async function getArtists(): Promise<Artist[] | null> {
const res = await furumiApi.get<Artist[]>('/artists').catch(() => null)
return res?.data ?? null
+5 -1
View File
@@ -6,7 +6,11 @@ export default defineConfig({
plugins: [react()],
server: {
proxy: {
'/api': {
'/auth': {
target: 'http://localhost:3001',
changeOrigin: true,
},
'/callback': {
target: 'http://localhost:3001',
changeOrigin: true,
},