security: hash operator passwords (scrypt) + persistent session secret
- 운영자 로그인 비밀번호를 평문 비교(===)에서 Node 내장 scrypt 해시 + 상수시간
비교(verifyPassword)로 전환. 새 파일 src/server/password.ts. 기존 account.json
의 평문 비밀번호는 그대로 검증되며, 로그인 성공 시 scrypt 해시로 자동 업그레이드
후 저장(writeAccounts 추가). 외부 의존성 없음.
- 세션 시크릿을 하드코딩 폴백('...dev-secret') 대신, 환경변수 우선 → 없으면
.session-secret 파일에 영구 랜덤값 생성/보관하도록 변경(세션 위조 방지, 재시작
후에도 세션 유지). .session-secret 은 .gitignore 에 추가.
서버(사이트) 전용 변경이라 설치기 exe 는 영향 없음(재빌드 불필요).
Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
This commit is contained in:
@@ -22,8 +22,10 @@ import {
|
||||
saveTerm,
|
||||
savePackList,
|
||||
setPackPublic,
|
||||
setTermVisibility
|
||||
setTermVisibility,
|
||||
writeAccounts
|
||||
} from '../../shared/store.js'
|
||||
import { hashPassword, isHashed, verifyPassword } from '../password.js'
|
||||
import { fetchReleaseVersions } from '../../shared/mojang.js'
|
||||
import { fetchPlaylistEntries, fetchVideoMeta, YtDlpUnavailableError } from '../youtube.js'
|
||||
import { requireAuth } from '../middleware/auth.js'
|
||||
@@ -58,11 +60,20 @@ opRouter.post('/op', async (req, res, next) => {
|
||||
try {
|
||||
const password = pickFirstValue(req.body.password)
|
||||
const accounts = await readAccounts()
|
||||
const matched = accounts.find((entry) => entry.password === password)
|
||||
const matched = accounts.find((entry) => verifyPassword(password, entry.password))
|
||||
if (!matched) {
|
||||
res.status(401).render('op/login', { error: t('login.wrongPassword') })
|
||||
return
|
||||
}
|
||||
// 평문으로 저장돼 있던 비밀번호는 로그인 성공 시 scrypt 해시로 자동 업그레이드.
|
||||
if (!isHashed(matched.password)) {
|
||||
try {
|
||||
matched.password = hashPassword(password)
|
||||
await writeAccounts(accounts)
|
||||
} catch {
|
||||
// 업그레이드 실패는 로그인 자체에 영향 주지 않음.
|
||||
}
|
||||
}
|
||||
req.session.userId = matched.id
|
||||
res.redirect('/op/dashboard')
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user