[TIL]Prisma ,DB Authentication, middleware

ohoho·2024년 11월 28일

슬기로운스터디

목록 보기
51/54

오늘 공부한것 & 기억할 내용

Prisma와 DB를 사용한 Authentication

  • db에서 유효성 검사를 하는 함수를 만들었기 때문에 formSchema에서 데이터를 검증할때 safeParse대신 safeParseAsyncspa를 사용한다.
  • npm i bcrypt를 사용해 비밀번호를 암호화 시킨다
  • bcrypt의 hash는 단방향이다 (비밀번호를 암호화 시킬 수 있지만, 암호화된 비밀번호를 원래의 비밀번호로 바꾸지 못한다)
  • bcrypt의 compare는 암호화된 비밀번호를 원래의 비밀번호와 일치하는지 확인해준다.
//action.ts
//1. db에서 아이디, 이메일 중복검사 함수를 만든다.
const checkUniqueUsername = async(username:string) => {
    const user = await db.user.findUnique({
        where:{
            username
        },
        select:{
            id:true
        }
    })
    // if(user){
    //     return false
    // }else{
    //     return true
    // }
    return !Boolean(user) //위의 코드와 작동 똑같음
}
const checkUniqueEmail = async (email:string) => {
    const user = await db.user.findUnique({
        where:{email},
        select:{id:true}
    })
    return !Boolean(user)
}

//2. formSchema안에 위에서 만들어둔 함수를 refine을 통해 실행시킨다
const formSchema = z.object({
  username:z.string({
    invalid_type_error:"user name must be a string",
    required_error:"Where is my username?"
  }).min(5,"Way too short").max(10,"That is too long")
  .refine(username => !username.includes('hi'),"No hi allowed")
  .refine(checkUniqueUsername,'This Username is already taken'),
})

//3. createAccount함수 안에서 모든 유효성 검사를 통과 했을 시 새로운 user 데이터를 저장한다
export async function createAccount(prev:any,data:FormData) {
  //db에서 유효성 검사를 하기 때문에 formSchema에서 데이터를 검증할때 safeParse대신 safeParseAsync인 spa를 사용한다.
const result = await formSchema.spa(formData)
    if(!result.success){
        console.log(result.error.flatten())
        return result.error.flatten()
    }else{
        //password를 보안을 위해 bcrypt의 hash를 사용하여 암호화 시킨다
      //hash안의 두번째 숫자는 알고리즘을 몇번 실행시킬것인지에 대한 숫자를 적는다
        const hashedPassword = await bcrypt.hash(result.data.password,12)
        //db에 새로운 user data저장
        const user = await db.user.create({
            data:{
                username:result.data.username,
                email:result.data.email,
              //보안을 위해 password에는 hash로 암호화된 비밀번호를 넣는다
                password:hashedPassword
            },
          //user의 모든 정보를 받아올 필요가 없기에 id만 받아온다
            select:{
                id:true
            }
        })
    }
}

iron-session

  • 사용자를 로그인 시키기 위해 사용자에게 쿠키를 줘야한다.(사용자에게 쿠키를 주지만 암호화해서 준다)
  • npm i iron-session을 사용해 session cookie를 생성하고 정보를 저장한다.
//action.ts createAccount함수안의 유효성 검사 성공 했을때 안의 코드에 작성
//최신 NextJS에선 cookies()이렇게만 하면 쿠키를 불러올 수 있음
const cookie = await getIronSession(await cookies(),{
  cookieName:"delicious-karrot",
  password:process.env.COOKIE_PASSWORD!
})
//@ts-ignore
cookie.id = user.id
await cookie.save()

/*
- getIronSession함수는 nextJs에서 주는 cookies()를 받은 다음 cookie에서 cookieName이 똑같은걸 찾은 후 찾지 못하면 새로 생성한다.
- 쿠키에 넣을 정보는 암호화 할것이기 때문에 iron session이 비밀번호를 요구한다.
- 비밀번호를 가지고 데이터를 암호화 할 수 있기에 env파일에 비밀번호를 적는다.
- 그래서 const cookie = await getIronSession 코드는 쿠키를 가져오거나 쿠키가 없으면 생성하는 역할을 한다.
- cookie.id는 새로 생성된 user의 id값을 저장한다.
- 그리고 await cookie.save() 로 쿠키를 저장하면 iron session이 정보를 암호화하고 그걸 사용자에게 전달한다 
*/

정리

  • getIronSession():
    암호화된 안전한 세션 쿠키를 생성/관리하는 함수
    쿠키 내용을 암호화하여 보안을 강화
    서버 사이드에서 사용자 정보를 안전하게 저장할 수 있게 해줌
  • await cookies():
    Next.js의 쿠키 객체를 가져오는 메서드
    현재 요청/응답의 쿠키를 참조
  • 옵션:
    cookieName: 쿠키의 이름 ("delicious-karrot")
    password: 쿠키 암호화에 사용되는 비밀번호 (환경변수에서 로드)
  • cookie.id = user.id:
    사용자 ID를 세션 쿠키에 추가
    //@ts-ignore는 TypeScript 타입 체크를 일시적으로 무시하는 주석
  • await cookie.save():
    변경된 쿠키 정보를 저장
    브라우저에 암호화된 세션 쿠키로 전달

superRefine

  • db에서 유효성 검사를 하기위해 유효성 검사 함수를 여러개 만들고 formSchema에 여러개를 넣어줄경우 개수마다 db와 통신하기에 효율성이 좋지 않다. 그걸 방지 하기 위해 superRefine을 사용한다.
  • 한개의 유효성 검사가 완료 되기전까지 다른것들의 유효성 검사를 진행하지 않는다
const formSchema = z.object({
}).superRefine(async ({username},ctx) => {
    const user = await db.user.findUnique({
        where:{
            username
        },
        select:{
            id:true
        }
    })
    if(user){
        //zod에서 유효성 검사 후 에러메시지 쓸때 만드는 코드
        ctx.addIssue({
            code:'custom',
            message:'This username is already taken',
            path:['username'],
          //위의 유효성 검사가 완료 되기전까지 다른것들의 유효성 검사를 진행하지 않는다
          //superRefine을 다른 refine보다 앞에 놔야 검사 진행하지 않음
            fatal:true
        })
        //반환값은 사용되지는 않지만 타입을 위해서 반환한다
        return z.NEVER
    }
})

middleware

  • 미들웨어를 사용하면 request가 완료되기 전에 코드를 실행할 수 있다 .(profile페이지로 이동시키기전에 중간에서 실행될 함수를 만들 수 있다)
  • 이미지, CSS, JS, Favicon 요청 등 웹 사이트의 모든 단일 request 하나하나마다 미들웨어가 실행된다.
  • nodeJs에서 실행되는것들은 사용할 수 없으며, Edge런타임에서만 실행된다.
    (Edge는 웹 표준에 최적화된, 제한된 런타임 환경 Node.js의 일부 기능을 지원하지 않고, 더 제한적인 API를 제공, javascript,cookie등 작은 코드들만 실행가능)
//middleware.ts app과 동급 위치
//midleware의 함수 이름은 항상 middleware여야한다.
export function middleware(request:NextRequest){
    //middleware가 request를 가로채서 profile 페이지로 가려는 request를 중단시킨다
    if(request.nextUrl.pathname === '/profile'){
        return Response.json({
            error:"you are not allowed here!"
        })
    }
}

if(request.nextUrl.pathname === '/profile'){
  //일반적인 redirect는 사용하지 못하기에 new URL을 만들어서 이동시킨다
  return Response.redirect(new URL("/",request.url))
}

config

  • middleware에서 특정 페이지에서 실행시키거나 실행시키고 싶지 않을때 사용하는 object
  • matcher를 사용하면 matcher에 지정한 특정 경로들에서만 미들웨어가 실행
//midleware.ts
//이름은 항상 config여야한다.
export const config = {
  	//특정 페이지에서만 보여지도록 
  	// /user/:path*는 user로 시작하는 모든 페이지에서 보여지도록
    matcher:['/','/user/:path*']
  
  //특정 파일 제외 모두 실행
  //matcher: ["/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)"]
}

배운점 & 느낀점

db를 사용해 유효성 검사를 하고 유효성 검사가 통과된 데이터는 db에 저장하고, 로그인 유무를 알기위해 iron-session을 사용하여 쿠키를 만들고 사용자 id를 쿠키에 추가한 다음 그걸 통해 보여질 페이지와 안보여질 페이지를 미들웨어를 통해 구분한다.
db를 이용하여 이렇게 많은 처리를 한건 처음이라 머리가 터질거 같고 뭐가 뭔지 아직 잘 모르겠다.. 간단한 처리같은데 상당히 간단하지 않은 느낌..

0개의 댓글