safeParseAsync인 spa를 사용한다.npm i bcrypt를 사용해 비밀번호를 암호화 시킨다 //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
}
})
}
}
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이 정보를 암호화하고 그걸 사용자에게 전달한다
*/
정리
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
}
})
Edge런타임에서만 실행된다.//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))
}
//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를 이용하여 이렇게 많은 처리를 한건 처음이라 머리가 터질거 같고 뭐가 뭔지 아직 잘 모르겠다.. 간단한 처리같은데 상당히 간단하지 않은 느낌..