fix: container install flow, filebrowser auth, AppCard enrichment

- Fix .198-style fresh installs: systemd service ExecStartPre creates
  /run/user/1000, enable podman.socket, chmod 644 /etc/hosts
- Filebrowser: add /data volume for database (fixes read-only crash),
  secure auth with random password via backend RPC (no more admin/admin)
- AppCard: enrich installing state with marketplace metadata (icon,
  title, description, tier badge, author, version)
- Registry: btcpayserver 1.13.5 → 1.13.7, images mirrored
- ReadWritePaths: add home container paths for rootless podman

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-27 13:32:54 +00:00
parent ef2922d909
commit 121f17e44e
14 changed files with 215 additions and 54 deletions

View File

@@ -40,19 +40,18 @@ describe('FileBrowserClient', () => {
})
describe('login', () => {
it('authenticates and stores token', async () => {
mockFetch.mockResolvedValueOnce(jsonResponse('"jwt-token-123"'))
it('authenticates via backend RPC and stores token', async () => {
mockFetch.mockResolvedValueOnce(jsonResponse({ result: { token: 'jwt-token-123' } }))
// We need a fresh instance to test login — use the exported singleton
const result = await fileBrowserClient.login('admin', 'admin')
const result = await fileBrowserClient.login()
expect(result).toBe(true)
expect(fileBrowserClient.isAuthenticated).toBe(true)
expect(mockFetch).toHaveBeenCalledWith(
expect.stringContaining('/app/filebrowser/api/login'),
'/rpc/v1',
expect.objectContaining({
method: 'POST',
body: JSON.stringify({ username: 'admin', password: 'admin' }),
body: JSON.stringify({ method: 'app.filebrowser-token' }),
}),
)
})
@@ -60,7 +59,7 @@ describe('FileBrowserClient', () => {
it('returns false on failed login', async () => {
mockFetch.mockResolvedValueOnce(jsonResponse(null, 403))
const result = await fileBrowserClient.login('admin', 'wrong')
const result = await fileBrowserClient.login()
expect(result).toBe(false)
})

View File

@@ -52,20 +52,21 @@ class FileBrowserClient {
return match ? match[1]! : null
}
async login(username = 'admin', password = 'admin'): Promise<boolean> {
async login(): Promise<boolean> {
try {
const res = await fetch(`${this.baseUrl}/api/login`, {
// Get a filebrowser JWT via the authenticated backend (no credentials exposed to browser)
const rpcRes = await fetch('/rpc/v1', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password }),
body: JSON.stringify({ method: 'app.filebrowser-token' }),
credentials: 'same-origin',
})
if (!res.ok) return false
const text = await res.text()
// FileBrowser returns the JWT as a plain string (possibly quoted)
const token = text.replace(/^"|"$/g, '')
// Store token as cookie — the only auth mechanism we use
if (!rpcRes.ok) return false
const rpcData = await rpcRes.json()
const token = rpcData?.result?.token
if (!token) return false
const expires = new Date(Date.now() + 24 * 60 * 60 * 1000).toUTCString()
// Only set Secure flag on HTTPS — on HTTP it silently prevents the cookie from being stored
const secure = window.location.protocol === 'https:' ? '; Secure' : ''
document.cookie = `auth=${token}; path=/app/filebrowser; SameSite=Lax${secure}; expires=${expires}`
this._authenticated = true