안녕하세요

게시물 수정 기능 구현, 권한 없는 이용자 접근 제한 방법 본문

Next13 블로그 프로젝트

게시물 수정 기능 구현, 권한 없는 이용자 접근 제한 방법

sakuraop 2023. 9. 19. 00:21

목차

1. 게시물 수정 라우터, API 생성하기

  • postId를 쿼리로 GET 요청을 보내 수정할 게시물의 데이터를 불러오기
  • 수정 버튼을 클릭하면 /manage/newpost/1로 POST 요청보내기
  • DB에서 게시물 데이터 업데이트하기
  • 작성된 게시물로 redirect 하기

2. 게시물 수정 권한을 확인하고, 권한이 없다면 돌려보내기

  • 게시물 작성자의 id와 로그인 중인 user의 id가 일치하는지 확인
    + 블로그 관리자인 경우(isBlogAdmin)에도 수정 권한을 가지도록 하기
  • 수정 권한이 없는 경우에는 경고창을 띄우고 접근하려던 게시물로 redirect 하기

1. 게시물 수정 라우터, API 생성하기

게시물 작성 페이지 라우터: /manage/newpost

게시물 수정 페이지 라우터: /manage/newpost/{postId}

 

새로운 글 작성인지, 게시물 수정인지를 구분해야 합니다. postId 를 Editor의 props로 넘겨줍니다.

import Editor from "@/containers/Editor/Editor";
import { authOptions } from "@/pages/api/auth/[...nextauth]";
import { checkBlogAdmin } from "@/utils/sessionCheck/checkBlogAdmin";
import { getServerSession } from "next-auth";

const EditPostRouter = async ({ params }: any) => {
  return (
    <div>
      <Editor postId={params.postId}/>
    </div>
  );
};

 

postId를 쿼리로 GET 요청을 보내 수정할 게시물의 데이터를 불러옵니다.

"use client";

const Editor = ({ postId }: { postId?: number}) => {
  const router = useRouter(); // 작성 완료되면 게시물로 redirect

  const [title, setTitle] = useState("");
  const [subtitles, setSubtitles] = useState("");
  const [contents, setContents] = useState("");

  useEffect(() => {
  // postId가 있다면 게시물 데이터를 요청하고, state에 데이터를 저장합니다.
    if (postId) {
      (async () => {
        const result = await axios.get(`/api/manage/newpost/${postId}`);

        setTitle(result.data.title);
        setSubtitles(result.data.subtitles.join(" "));
        setContents(result.data.contents);
      })();
    }
  }, []);

 

게시물을 수정하고 수정 버튼을 클릭하면 /manage/newpost/1로 POST 요청을 보냅니다.

  // 수정하기 버튼 클릭하면,
  const handleClickEditButton = async (e: any) => {
    e.preventDefault();
    // api 요청을 보내고
    const result = await axios.post(`/api/manage/newpost/${postId}`, {
      title,
      subtitles,
      contents,
      id: postId,
    });

    // 게시물 작성이 완료되면 해당 게시물 주소로 redirect 합니다.
    router.push(`/post/${result.data.id}`);
  };

  return (
    <>...</>
  );
};

export default Editor;

 

GET요청과 POST요청에 응답할 API를 만듭니다.

 

GET요청을 받으면 postId와 일치하는 게시물을 DB에서 조회하고 필요한 데이터를 응답에 실어 보내줍니다.

import { connectDB } from "@/utils/db/db";

const handler = async (req: any, res: any) => {
  const { postId } = req.query;

  if (req.method === "GET") {
    const db = (await connectDB).db("blog");
    const postCollection = await db.collection("posts");
    const post = await postCollection.findOne({ id: Number(postId) });

    if (post) {
      const { title, subtitles, contents, email } = post;
      return res.status(200).json({ title, subtitles, contents, email });
    }

    return res.status(404).json({ message: "Not found any post" });
  }

 

POST요청을 받으면 postId와 일치하는 게시물을 DB에서 업데이트합니다.

  if (req.method === "POST") {
    // DB와 Collection 연결
    const db = (await connectDB).db("blog");
    const postCollection = await db.collection("posts");

    // 게시물 작성자 정보
    const { title, subtitles, contents, id } = req.body; // 게시물 내용

    const saveData = {
      title,
      subtitles: subtitles.split(" "),
      contents,
    };

    const result = await postCollection.updateOne({ id: Number(id) }, { $set: { ...saveData } }); // DB에 저장한 결과

    return res.status(200).json({ id }); // 응답에 게시물 id를 포함하여 redirect할 수 있도록 합니다.
  }
};

export default handler;

 

게시물 수정이 완료되었습니다.


2. 게시물 수정 권한을 확인하고, 권한이 없다면 돌려보내기

게시물 권한 확인은 라우터에서 진행을 하고, 

Editor 컴포넌트에서 alert("권한이 없습니다.") 알림과 게시물로 redirect를 수행하도록 하겠습니다.

 

Editor에 props로 게시물 번호와 권한을 전달합니다.

import Editor from "@/containers/Editor/Editor";
import { authOptions } from "@/pages/api/auth/[...nextauth]";
import { checkBlogAdmin } from "@/utils/sessionCheck/checkBlogAdmin";
import { getServerSession } from "next-auth";

const EditPostRouter = async ({ params }: any) => {
  const canEdit: boolean = await checkEditAuthor(params.postId); // 수정 권한 확인

  return (
    <div>
      <Editor postId={params.postId} canEdit={canEdit} />
    </div>
  );
};

const checkEditAuthor = async (postId: string) => {
  // 게시글을 작성한 유저를 확인합니다.
  const result = await fetch(`http://localhost:3000/api/manage/newpost/${postId}`, {
    method: "GET",
    cache: "force-cache",
  });
  const jsonData = await result.json();
  const postEmail = jsonData.email;

  // 로그인한 유저를 확인합니다.
  const token = await getServerSession(authOptions);
  const userEmail = token?.user?.email;

  // 접속 권한을 확인합니다.
  const isBlogAdmin = checkBlogAdmin(userEmail as string);
  const canEdit: boolean = postEmail === userEmail || isBlogAdmin;

  return canEdit;
};

export default EditPostRouter;
  • 게시물을 조회하여 게시물 작성자의 id와 로그인 중인 user의 id가 일치하는지 확인을 합니다.
  • 블로그 관리자인 경우(isBlogAdmin)에도 수정 권한을 가지도록 합니다.

 

수정 권한이 없는 경우에는 경고창을 띄우고 접근하려던 게시물로 redirect 합니다.

const Editor = ({ postId, canEdit }: { postId?: number; canEdit?: boolean }) => {
  const router = useRouter(); // 작성 완료되면 게시물로 redirect 할겁니다.

  // 수정 권한이 없는 경우엔 수정을 시도하려던 게시글로 이동합니다.
  useEffect(() => {
    if (postId && !canEdit) {
      window.alert("수정 권한 없음");
      router.push(`/post/${postId}`);
    }
  }, []);
  
    // ...

 

수정 권한이 없다면, 게시물 내용을 보호 + 불필요한 렌더링 방지를 합니다.

  return (
    <>
      {postId && !canEdit ? (
        <div>{null}</div>
      ) : (
        <div className={styles.container}>
 		  // ...에디터
        </div>
      )}
    </>
  );
};

export default Editor;

 

url에 직접 입력하여 게시물 수정을 시도해봅니다.

 

수정 권한이 없다는 알림이 나타납니다.

 

확인을 누르면 수정하기 위해 접근을 시도하던 게시물로 이동합니다.


DB의 데이터와 다른 문제 발생

redirect된 게시물이 업데이트 된 DB 데이터와 다릅니다.

새로고침 하여야 동기화가 됩니다.
원인을 파악하여 다시 요청을 할 수 있도록 해야합니다.

 

너무 길어져서 다음 게시물로 이어집니다.

https://sakuraop.tistory.com/599

 

게시글 수정 뒤 redirect한 게시물이 새로고침 되지 않는 문제 해결

게시물을 수정하였지만 redirect된 게시물에는 변화가 없습니다. 🔻ㅎㅎ를 지우고🔻 🔻게시물로 redirect 했더니 바로 반영이 안되는 문제🔻 업데이트한 게시물이 DB의 데이터와 다른 문제 발생 r

sakuraop.tistory.com