'use strict'; // ============================================================================ // 비밀번호 규칙 (v1.3.17) // // 서비스 계정(빌더 사용자·관리자)과 사이트 관리자 계정의 비밀번호 규칙을 한곳에서 정한다. // 관리자 콘솔 › 설정 › "비밀번호 규칙" 에서 바꾸며(settings.passwordPolicy), 기본값은 1.3.16 까지의 규칙 // "6자 이상" 그대로다(기존 사용자·화면 동작이 바뀌지 않게). 규칙은 새로 정하는 비밀번호(가입·변경·재설정)에만 적용하고, // 이미 쓰고 있는 비밀번호로 로그인하는 것은 막지 않는다. // ============================================================================ const DEFAULT = { minLength: 6, maxLength: 256, requireLetter: false, requireDigit: false, requireSymbol: false, requireMixedCase: false, forbidId: false }; const httpError = (m, s = 400) => Object.assign(new Error(m), { status: s }); /** 지금 규칙 (설정 파일 값 + 기본값). settings 를 늦게 불러 순환 의존을 피한다 */ function current() { let p = {}; try { p = require('./settings').load().passwordPolicy || {}; } catch (_) {} return normalize(p); } /** 관리자 입력 정리: 최소 길이 4~64, 최대 길이 최소 길이 이상 256 이하, 나머지는 켜기/끄기 */ function normalize(p) { p = p || {}; const on = (v) => v === true || v === 'true' || v === 'on' || v === 1 || v === '1'; const min = Math.round(Number(p.minLength)); const max = Math.round(Number(p.maxLength)); const out = { ...DEFAULT }; if (Number.isFinite(min)) { if (min < 4 || min > 64) throw httpError('비밀번호 최소 길이는 4~64 사이로 정하세요'); out.minLength = min; } if (Number.isFinite(max)) { if (max < out.minLength || max > 256) throw httpError('비밀번호 최대 길이는 최소 길이 이상 256 이하로 정하세요'); out.maxLength = max; } for (const k of ['requireLetter', 'requireDigit', 'requireSymbol', 'requireMixedCase', 'forbidId']) if (p[k] !== undefined) out[k] = on(p[k]); return out; } /** 규칙 설명 문장 (오류 메시지·화면 안내) */ function describe(pol = current()) { const parts = [`${pol.minLength}자 이상`]; if (pol.maxLength < 256) parts.push(`${pol.maxLength}자 이하`); if (pol.requireLetter) parts.push('영문자 포함'); if (pol.requireMixedCase) parts.push('대문자·소문자 모두 포함'); if (pol.requireDigit) parts.push('숫자 포함'); if (pol.requireSymbol) parts.push('특수문자 포함'); if (pol.forbidId) parts.push('아이디를 포함하지 않음'); return parts.join(', '); } /** * 새 비밀번호 검사. 맞지 않으면 어떤 규칙인지 적은 오류를 던진다. * @param {string} pw * @param {object} [ctx] { id: 아이디(forbidId 검사), label: 오류 문장 앞말('비밀번호'·'새 비밀번호') } */ function check(pw, ctx = {}) { const pol = current(); const s = String(pw || ''); const label = ctx.label || '비밀번호'; const fail = () => { throw httpError(`${label}는 ${describe(pol)} 이어야 합니다`); }; if (s.length < pol.minLength || s.length > pol.maxLength) fail(); if (pol.requireLetter && !/[A-Za-z]/.test(s)) fail(); if (pol.requireMixedCase && !(/[a-z]/.test(s) && /[A-Z]/.test(s))) fail(); if (pol.requireDigit && !/\d/.test(s)) fail(); if (pol.requireSymbol && !/[^A-Za-z0-9]/.test(s)) fail(); if (pol.forbidId && ctx.id && s.toLowerCase().includes(String(ctx.id).toLowerCase())) fail(); return true; } module.exports = { DEFAULT, current, normalize, describe, check };