Fix installer pack fetch and simplify step 1 UI

The installer pulls each pack JSON from /manifest/<file>.json, but the
server was blocking every /manifest/ path. Add a strict whitelist
route that only serves /manifest/<a-zA-Z0-9_-name>.json files (no
directory listing, no path traversal, no other extensions); keep the
catch-all 404 for anything else under /manifest/.

Also strip the manifest URL input and "목록 새로고침" button from
installer step 1 — packs auto-load on page render, only the selectable
list remains.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-09 21:44:50 +09:00
parent 8fd7cfaaef
commit bda79e18eb
2 changed files with 37 additions and 22 deletions

View File

@@ -67,11 +67,7 @@ function renderStep1() {
section.className = 'page'
section.innerHTML =
'<h2>1단계. 설치할 음악퀴즈 선택</h2>' +
'<p>관리 사이트의 manifest.json에서 음악퀴즈 목록을 가져옵니다.</p>' +
'<div class="fieldset"><label>manifest URL <input id="manifestUrl" type="url" value="' +
(state.manifestUrl || 'http://127.0.0.1:3000/manifest.json') + '" /></label>' +
'<button class="secondaryBtn" id="reload">목록 새로고침</button></div>' +
'<div id="packList" class="cardChoice"></div>' +
'<div id="packList" class="cardChoice"><p class="formMessage">목록을 불러오는 중...</p></div>' +
'<div class="actionRow"><span></span><button class="primaryBtn" id="next" disabled>다음</button></div>'
pageHost.appendChild(section)
var listEl = section.querySelector('#packList')
@@ -80,7 +76,7 @@ function renderStep1() {
function renderList() {
listEl.innerHTML = ''
if (state.packs.length === 0) {
listEl.innerHTML = '<p class="formMessage">아직 음악퀴즈가 없습니다. "목록 새로고침"을 눌러 주세요.</p>'
listEl.innerHTML = '<p class="formMessage error">등록된 음악퀴즈가 없습니다.</p>'
return
}
state.packs.forEach(function (pack) {
@@ -97,18 +93,6 @@ function renderStep1() {
})
}
section.querySelector('#reload').addEventListener('click', async function () {
var manifestUrl = section.querySelector('#manifestUrl').value
state.manifestUrl = manifestUrl
try {
var packs = await installerApi.loadPacks(manifestUrl)
state.packs = packs
renderList()
} catch (err) {
alert('manifest 다운로드 실패: ' + err.message)
}
})
nextBtn.addEventListener('click', async function () {
if (!state.selectedPackKey) return
await installerApi.setSelectedPack(state.selectedPackKey)
@@ -116,7 +100,15 @@ function renderStep1() {
renderStep2()
})
renderList()
;(async function () {
try {
var packs = await installerApi.loadPacks()
state.packs = packs
renderList()
} catch (err) {
listEl.innerHTML = '<p class="formMessage error">목록을 가져오지 못했습니다: ' + err.message + '</p>'
}
})()
}
function renderStep2() {