https://www.npmjs.com/package/react-draft-wysiwyg
위지위그(WYSIWYG: What You See Is What You Get, "보는 대로 얻는다"
웹에디터
https://www.npmjs.com/package/react-quill
우리가 다룰 react-quill 에디터
https://www.npmjs.com/package/@toast-ui/editor
추가적으로 사용 가능한 toast-ui 에디터
노션처럼 화면을 꾸밀 수 있음.
다운로드 수가 높지는 않은데 국내에서 개발된 라이브러리로 사용하기 매우 좋은 라이브러리
ReactQuil의 onChange는 개발자가 만들어 놓은 커스텀 요소
이름만 같을 뿐 jsx의 onChange요소와는 전혀 다른 개념이다.
바로 임폴트해와서 접속하게 되면 document is not defined 에러가 발생된다
이럴 땐
이렇게 다이나믹을 이용하여 React-quill을 가져오게 되면
document is not defined가 발생되지 않는다.
Next.js는 기본적으로 서버사이드 렌더링을 지원하는데
서버에서 페이지를 미리 렌더링하는 단계에서는 브라우저 상이 아니기때문에
window나 document object를 선언하기 전이기에 존재하지 않는다.
그래서 발생되는 에러이다.
우리가 실행이 되길 원할때 임폴트해와서 사용 될수 있게 하는 것으로
모든 것을 임폴트해와서 바로 쓰게하면 성능이 저하되지만
사용이 될 떄를 지정하여 임폴트 해오기에 성능이 올라간다
(서버에서 페이지를 미리 렌더링하는 단계를 pre-rendering)
useForm을 가져와서 각각 인풋에 register를 연결한다.
하지만 reactQuil에는 register를 적용 할 수 없는데
set value라는 요소를 이용하면 가능하다
const { register, handleSubmit, setValue, trigger } = useForm({
mode: "onChange",
});
const onChangeContets = (value: string) => {
console.log(value);
// register로 등록하지 않고 강제로 값을 넣어주는 기능
setValue("contents", value);
// onChange 됐다고 react-hook-from에 알려주는 기능
trigger("contets");
};
setValue는 강제로 값을 집어 넣어주고
trigger는 어떤 값이 Mode의 내용이 될 때 리액트 훅 폼에 알려주는 기능이다
조건부 렌더링을 이용해서 React-Quill에 값이 입력되었다가 지워졌을 때 남는 찌꺼기 태그도 없애줍니다.
const { register, setValue } = useForm({
mode: "onChange",
});
const handleChange = (value: string) => {
console.log(value);
// register로 등록하지 않고, 강제로 값을 넣어주는 기능
setValue("contents", value === "<p><br></p>" ? "" : value);
trigger("contents")
};
const onClickSubmit = async (data: IFormValues) => {
if (!(data.writer && data.password && data.title && data.contents)) {
Modal.warning({ content: "필수 입력 사항입니다." });
return;
}
try {
const result = await createBoard({
variables: {
createBoardInput: {
writer: data.writer,
password: data.password,
title: data.title,
contents: data.contents,
},
},
});
// 완료된 페이지로 이동!!
router.push(`/27-04-web-editor-detail/${result.data?.createBoard._id}`);
} catch (error) {
if (error instanceof Error) Modal.error({ title: error.message });
}
};
return (
<form onSubmit={handleSubmit(onClickSubmit)}>
작성자: <input type="text" {...register("writer")} />
<br />
비밀번호: <input type="password" {...register("password")} />
<br />
제목: <input type="text" {...register("title")} />
<br />
내용: <ReactQuill onChange={handleChange} />
<br />
<button>등록하기</button>
</form>
);
}
라고 보드를 생성하는 창을 만들고
export default function WebEditorDetail() {
const router = useRouter();
const { data } = useQuery<Pick<IQuery, "fetchBoard">, IQueryFetchBoardArgs>(
FETCH_BOARD,
{
variables: { boardId: String(router.query.id) },
}
);
return (
<div>
<div>작성자: {data?.fetchBoard.writer}</div>
<div>제목: {data?.fetchBoard.title}</div>
<div>내용: {data?.fetchBoard.contents}</div>
</div>
);
상세페이지를 만든다
에디터로 작성한 내용은 HTML 태그가 포함된 문자열로 입력이 되기 때문에
HTML 태그들을 노출하지 않으면서 HTML 기능만 적용된 형태로 화면에 출력해야한다.
그러나 리액트는 HTML 태그를 직접 삽입 할 수 없게 설정되어있다
그럼에도 html 태그를 사용하고자 한다면 ?
angerouslySetInnerHTML를 이용해야한다.
중요한 데이터를 담아두기엔 로컬스토리지나 쿠키는 위험도가 높다
쿠키에서는 자바스크립트로 접근하지 못하게 httpOnly같은 옵션들을 넣어서 저장하곤 했다.
angerouslySetInnerHTML를 사용할떄 해커들이 공격을 하는 방법
이미지 태그에 onerror라는 이미지를 못불러올때 실행되게 하는 태그의 속성을 이용하여 스토리지 내의 토큰을 빼낼 수 있게 됨
백엔드에서 방어하는 로직도 필요하고 브라우저에서도 방어가 필요함
브라우저에서 방어하기 위한 라이브러리들이 존재하는데
우리는 DOMPurify 를 사용해볼 것
https://www.npmjs.com/package/dompurify
문제가 되는 것들은 차단이 되게 해주는 라이브러리
yarn add dompurify
yarn add -D @types/dompurify
{process.browser &&
<div
dangerouslySetInnerHTML={{
__html: Dompurify.sanitize(String(data?.fetchBoard.contents))
}}
/>
}
sanitize는 바이러스를 없앤다는 뜻
어떤 자바스크립트 요청이 있더라도 실행되지 않음
OWASP란 Open Web Application Security Project의 약자로 오픈소스 웹 애플리케이션 보안 프로젝트
3~4년동안 어떤 공격이 많았는지 보여주는 것
개발자라면 알고 있어야한다!
A01 : Broken Access Control (접근 권한 취약점)
A02 : Cryptographic Failures (암호화 오류)
A03: Injection (인젝션)
A04: Insecure Design (안전하지 않은 설계)
A05: Security Misconfiguration (보안설정오류)
A06: Vulnerable and Outdated Components (취약하고 오래된 요소)
A07: Identification and Authentication Failures (식별 및 인증 오류)
A08: Software and Data Integrity Failures(소프트웨어 및 데이터 무결성 오류)
A09: Security Logging and Monitoring Failures (보안 로깅 및 모니터링 실패)
A10: Server-Side Request Forgery (서버 측 요청 위조)
매년OWASP 상위권을 유지하는 것 중 하나가 Injection 입니다.
SQL쿼리문을 작성할때 조건을 통해 데이터를 주고 받는데
조건을 조작하여 공격하는 것. 현재는 ORM을 사용해 막는다.
return (
<div>
<div style={{color: "red"}}>작성자: {data?.fetchBoard.writer}</div>
{process.browser && (
<div style={{color: "green"}}>제목: {data?.fetchBoard.title}</div>
)}
<div style={{color: "blue"}}>내용: 반갑습니다!<div>
</div>
)
lt: 작다
gT: 크다
next.js에서는 위의 과정으로 페이지를 그리는데
이 중에 브라우저 diffing 단계에서 태그를 기준으로 비교하기 때문에
프론트엔드 서버에서 pre-rendering된 결과물과 브라우저에서 그려진 결과물의 태그 구조가 다를 경우 CSS가 코드와 다르게 적용
그렇기 때문에 브라우저에서만 렌더링되는 태그가 있을 경우, 삼항연산자를 이용해서 프론트엔드 서버에서도 빈 태그가 들어가 있도록 만들어줘야 한다
return (
<div>
<div style={{color: "red"}}>작성자: {data?.fetchBoard.writer}</div>
{process.browser ? (
<div style={{color: "green"}}>제목: {data?.fetchBoard.title}</div>
) : (
<div style={{color: "green"}} />
)}
<div style={{color: "blue"}}>내용: 반갑습니다!<div>
</div>
)
+) quill을 다운로드하여 실습하는데...?
ReactQuill'은(는) JSX 구성 요소로 사용할 수 없습니다. 해당 인스턴스 형식 'ReactQuill'은(는) 유효한 JSX 요소가 아닙니다.
'render()'에서 반환되는 형식은 해당 형식 간에 호환되지 않습니다.
'React.ReactNode' 형식은 'import("/Users/ㅇㅇㅇㅇ/node_modules/@types/react/index").ReactNode' 형식에 할당할 수 없습니다.
'{}' 형식은 'ReactNode' 형식에 할당할 수 없습니다.
'{}' 형식에 'ReactPortal' 형식의 key, children, type, props 속성이 없습니다
라는 오류가 발생 했다...
클래스형 컴포넌트에서나 나올법한 render도 있고요,,?
타입 에러 같은데