feat: 3-System 분리 프로젝트 초기 코드 작성
TK-FB(공장관리+신고)와 M-Project(부적합관리)를 3개 독립 시스템으로 분리하기 위한 전체 코드 구조 작성. - SSO 인증 서비스 (bcrypt + pbkdf2 이중 해시 지원) - System 1: 공장관리 (TK-FB 기반, 신고 코드 제거) - System 2: 신고 (TK-FB에서 workIssue 코드 추출) - System 3: 부적합관리 (M-Project 기반) - Gateway 포털 (path-based 라우팅) - 통합 docker-compose.yml 및 배포 스크립트 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
173
system1-factory/api/utils/passwordValidator.js
Normal file
173
system1-factory/api/utils/passwordValidator.js
Normal file
@@ -0,0 +1,173 @@
|
||||
/**
|
||||
* Password Validator - 비밀번호 정책 검증
|
||||
*
|
||||
* 강력한 비밀번호 정책:
|
||||
* - 최소 12자 이상
|
||||
* - 대문자 포함
|
||||
* - 소문자 포함
|
||||
* - 숫자 포함
|
||||
* - 특수문자 포함
|
||||
*
|
||||
* @author TK-FB-Project
|
||||
* @since 2026-02-04
|
||||
*/
|
||||
|
||||
/**
|
||||
* 비밀번호 강도 검증
|
||||
*
|
||||
* @param {string} password - 검증할 비밀번호
|
||||
* @param {Object} options - 옵션 (기본값 사용 권장)
|
||||
* @returns {Object} { valid: boolean, errors: string[], strength: string }
|
||||
*/
|
||||
const validatePassword = (password, options = {}) => {
|
||||
const config = {
|
||||
minLength: options.minLength || 12,
|
||||
requireUppercase: options.requireUppercase !== false,
|
||||
requireLowercase: options.requireLowercase !== false,
|
||||
requireNumbers: options.requireNumbers !== false,
|
||||
requireSpecialChars: options.requireSpecialChars !== false,
|
||||
maxLength: options.maxLength || 128
|
||||
};
|
||||
|
||||
const errors = [];
|
||||
let strength = 0;
|
||||
|
||||
// 필수 검증
|
||||
if (!password || typeof password !== 'string') {
|
||||
return {
|
||||
valid: false,
|
||||
errors: ['비밀번호를 입력해주세요.'],
|
||||
strength: 'invalid'
|
||||
};
|
||||
}
|
||||
|
||||
// 길이 검증
|
||||
if (password.length < config.minLength) {
|
||||
errors.push(`비밀번호는 최소 ${config.minLength}자 이상이어야 합니다.`);
|
||||
} else {
|
||||
strength += 1;
|
||||
}
|
||||
|
||||
if (password.length > config.maxLength) {
|
||||
errors.push(`비밀번호는 ${config.maxLength}자를 초과할 수 없습니다.`);
|
||||
}
|
||||
|
||||
// 대문자 검증
|
||||
if (config.requireUppercase && !/[A-Z]/.test(password)) {
|
||||
errors.push('대문자를 1개 이상 포함해야 합니다.');
|
||||
} else if (/[A-Z]/.test(password)) {
|
||||
strength += 1;
|
||||
}
|
||||
|
||||
// 소문자 검증
|
||||
if (config.requireLowercase && !/[a-z]/.test(password)) {
|
||||
errors.push('소문자를 1개 이상 포함해야 합니다.');
|
||||
} else if (/[a-z]/.test(password)) {
|
||||
strength += 1;
|
||||
}
|
||||
|
||||
// 숫자 검증
|
||||
if (config.requireNumbers && !/\d/.test(password)) {
|
||||
errors.push('숫자를 1개 이상 포함해야 합니다.');
|
||||
} else if (/\d/.test(password)) {
|
||||
strength += 1;
|
||||
}
|
||||
|
||||
// 특수문자 검증
|
||||
const specialChars = /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/;
|
||||
if (config.requireSpecialChars && !specialChars.test(password)) {
|
||||
errors.push('특수문자를 1개 이상 포함해야 합니다. (!@#$%^&*()_+-=[]{};\':"|,.<>/?)');
|
||||
} else if (specialChars.test(password)) {
|
||||
strength += 1;
|
||||
}
|
||||
|
||||
// 공백 검증
|
||||
if (/\s/.test(password)) {
|
||||
errors.push('비밀번호에 공백을 포함할 수 없습니다.');
|
||||
}
|
||||
|
||||
// 연속된 문자 검증 (선택적)
|
||||
if (/(.)\1{2,}/.test(password)) {
|
||||
errors.push('동일한 문자를 3회 이상 연속 사용할 수 없습니다.');
|
||||
}
|
||||
|
||||
// 강도 계산
|
||||
let strengthLabel;
|
||||
if (strength <= 2) {
|
||||
strengthLabel = 'weak';
|
||||
} else if (strength <= 3) {
|
||||
strengthLabel = 'medium';
|
||||
} else if (strength <= 4) {
|
||||
strengthLabel = 'strong';
|
||||
} else {
|
||||
strengthLabel = 'very_strong';
|
||||
}
|
||||
|
||||
return {
|
||||
valid: errors.length === 0,
|
||||
errors,
|
||||
strength: strengthLabel,
|
||||
score: strength
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 간단한 비밀번호 검증 (기존 호환용)
|
||||
* 모든 조건을 만족하면 true, 아니면 false
|
||||
*
|
||||
* @param {string} password - 검증할 비밀번호
|
||||
* @returns {boolean} 유효 여부
|
||||
*/
|
||||
const isValidPassword = (password) => {
|
||||
return validatePassword(password).valid;
|
||||
};
|
||||
|
||||
/**
|
||||
* 비밀번호 검증 결과를 한국어 메시지로 반환
|
||||
*
|
||||
* @param {string} password - 검증할 비밀번호
|
||||
* @returns {string|null} 오류 메시지 (유효하면 null)
|
||||
*/
|
||||
const getPasswordError = (password) => {
|
||||
const result = validatePassword(password);
|
||||
if (result.valid) {
|
||||
return null;
|
||||
}
|
||||
return result.errors.join(' ');
|
||||
};
|
||||
|
||||
/**
|
||||
* Express 미들웨어: 요청 body의 password 또는 newPassword 필드 검증
|
||||
*
|
||||
* @param {string} fieldName - 검증할 필드명 (기본: 'password')
|
||||
* @returns {Function} Express 미들웨어
|
||||
*/
|
||||
const validatePasswordMiddleware = (fieldName = 'password') => {
|
||||
return (req, res, next) => {
|
||||
const password = req.body[fieldName] || req.body.newPassword;
|
||||
|
||||
if (!password) {
|
||||
return next(); // 비밀번호 필드가 없으면 다음 미들웨어로
|
||||
}
|
||||
|
||||
const result = validatePassword(password);
|
||||
|
||||
if (!result.valid) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: '비밀번호가 보안 요구사항을 충족하지 않습니다.',
|
||||
details: result.errors,
|
||||
code: 'WEAK_PASSWORD'
|
||||
});
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
validatePassword,
|
||||
isValidPassword,
|
||||
getPasswordError,
|
||||
validatePasswordMiddleware
|
||||
};
|
||||
Reference in New Issue
Block a user