fix: wire auth PIN to frontend — 401 on NAM select and all POST/PUT calls
CI / test (push) Has been cancelled
CI / test (push) Has been cancelled
Root cause: auth middleware (1603bfe) blocked all non-GET /api/ endpoints
requiring X-Pedal-Auth header, but the PIN was never served to the frontend.
Fix:
- Added auth_pin to /api/state response (safe GET endpoint, no auth required)
- Frontend stores pin from state on initial load
- apiPost/apiPut/apiDelete/apiUpload now include X-Pedal-Auth header
This commit is contained in:
@@ -37,6 +37,12 @@ function _ch(url) {
|
||||
}
|
||||
|
||||
// ── API helpers ────────────────────────────────────────────────
|
||||
let _authPin = '';
|
||||
|
||||
function _authHeaders() {
|
||||
return _authPin ? { 'X-Pedal-Auth': _authPin } : {};
|
||||
}
|
||||
|
||||
async function apiGet(path) {
|
||||
const r = await fetch(`${API}${_ch(path)}`);
|
||||
if (!r.ok) throw new Error(`${path} => ${r.status}`);
|
||||
@@ -45,7 +51,7 @@ async function apiGet(path) {
|
||||
|
||||
async function apiPost(path, data) {
|
||||
const r = await fetch(`${API}${_ch(path)}`, {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json', ..._authHeaders() },
|
||||
body: data != null ? JSON.stringify(data) : undefined,
|
||||
});
|
||||
if (!r.ok) throw new Error(`${path} => ${r.status}`);
|
||||
@@ -54,7 +60,7 @@ async function apiPost(path, data) {
|
||||
|
||||
async function apiPut(path, data) {
|
||||
const r = await fetch(`${API}${_ch(path)}`, {
|
||||
method: 'PUT', headers: { 'Content-Type': 'application/json' },
|
||||
method: 'PUT', headers: { 'Content-Type': 'application/json', ..._authHeaders() },
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
if (!r.ok) throw new Error(`${path} => ${r.status}`);
|
||||
@@ -62,7 +68,7 @@ async function apiPut(path, data) {
|
||||
}
|
||||
|
||||
async function apiDelete(path) {
|
||||
const r = await fetch(`${API}${_ch(path)}`, { method: 'DELETE' });
|
||||
const r = await fetch(`${API}${_ch(path)}`, { method: 'DELETE', headers: _authHeaders() });
|
||||
if (!r.ok) throw new Error(`${path} => ${r.status}`);
|
||||
return r.json();
|
||||
}
|
||||
@@ -70,7 +76,7 @@ async function apiDelete(path) {
|
||||
async function apiUpload(path, file) {
|
||||
const fd = new FormData();
|
||||
fd.append('file', file);
|
||||
const r = await fetch(`${API}${_ch(path)}`, { method: 'POST', body: fd });
|
||||
const r = await fetch(`${API}${_ch(path)}`, { method: 'POST', body: fd, headers: _authHeaders() });
|
||||
if (!r.ok) throw new Error(`${path} => ${r.status}`);
|
||||
return r.json();
|
||||
}
|
||||
@@ -84,7 +90,7 @@ function usePedalState() {
|
||||
|
||||
useEffect(() => {
|
||||
// Fetch initial state
|
||||
apiGet('/api/state').then(s => { setState(s); setConnected(true); }).catch(() => { setConnected(false); });
|
||||
apiGet('/api/state').then(s => { setState(s); setConnected(true); _authPin = s.auth_pin || ''; }).catch(() => { setConnected(false); });
|
||||
|
||||
// WebSocket for real-time updates
|
||||
let reconnectTimer;
|
||||
|
||||
Reference in New Issue
Block a user