mailgun을 통해서유저의 이메일 주소로 메일을 발송할 수 있지만 유저의 이메일 주소가 유효한 지 인증할 수 없었기 때문에 새로운 인증 방식을 구현할 필요가 있었다.
그 동안 다른 웹사이트에서 회원가입을 할 때, 내가 받았던 이메일 주소 인증 메일에는 이메일 인증 문구와 함께 버튼이 존재하였고, 버튼을 누르면 자동으로 웹사이트에 접속하며 이메일이 인증되는 과정을 거쳤다.
우리는 이 방식을 채택하여 구현하기로 하였다.
email
을 서버로 전송하면 서버는 email
과 만료 시간을 합쳐 key
값을 생성하고 DB에 저장한 뒤, 유저에게 반환한다.new Date(Date.now() + 60 * 1000 * 5)
key
값과 email
을 다시 전송해주면 서버는 DB에 저장된 email
에 해당하는 key
와 대조하여 이메일 인증 결과를 반환한다. 하지만 유저가 재전송하는 시각이 만료 시간을 넘으면 이메일 주소는 유효하지 않다고 판단한다. 실제 구현을 위한 기본적인 설계는 완성되었고, 사이트 목적에 맞게 변형시킬 수 있어야 하였다.
key
값 생성
email
과 expiredDate
를 합쳐 고유한 key값을 만들 수 있는 해시 함수가 필요하였고 이를 위해 md5
를 채택하였다.
( https://www.npmjs.com/package/md5)
유저의 key
값 재전송
유저는 서버로부터 key
값을 전달받고, 이를 다시 서버에 전송하여 이메일 인증 여부를 요청해야 한다. 이 과정을 위해 유저의 이메일로 인증 요청 버튼을 전송하고 버튼의 링크 URL에는 key
값을 query parameter 로 담아놓은다. 결국 유저는 자신의 이메일을 기반으로 생성된 유일한 key
를 인증 버튼에 내장되어 이메일로 받게 되고, 버튼을 누름으로써 자동으로 서버에 key
를 전송하게 된다.
유저의 이메일 주소로 key
가 담긴 버튼을 전송
const emailVerifyLink = async (
params: { email: string; type: string },
connection: DbConnection
) => {
try {
const { email, type } = params
//email hash화로 key값 생성
const expiredDate = new Date(Date.now() + 60 * 1000 * 5) //만료기간 5분
const hashedEmailFull = md5(email + expiredDate)
const hashedEmail = hashedEmailFull.substring(0, 12)
// 이메일 등록 여부 파악
const getResponse = await connection.run(
`SELECT COUNT(*) AS count FROM user_info WHERE email=?`,
[email]
)
const { count } = getResponse[0]
if (count > 0) throw "E0000"
// 이메일 전송
else {
MAILGUN.senVerifyEmail(hashedEmail, email, type)
//이메일 정보 및 만료 기한 저장
await connection.run(
`INSERT INTO email_verify(type,email,email_code,expired_date) VALUES(?,?,?,?)`,
[type, email, hashedEmail, expiredDate]
)
}
} catch (e: any) {
paramsErrorHandler(e)
}
return {
status: 201,
data: {
success: true,
message: "이메일로 링크 발송 성공",
},
}
}
그리고 유저의 이메일로 발송된 버튼을 누르게 되면 이메일 검증 API가 동작한다.
const checkEmailLink = async (
params: { key: string; type: string }, //query
connection: DbConnection
) => {
let isVerified = false
try {
const { key, type } = params
// key에 해당하는 만료 기한을 가져옴
const response = await connection.run(
`SELECT expired_date FROM email_verify WHERE email_code=? AND type=?`,
[key, type]
)
// 존재하지 않을 시 에러
if (!response[0]) {
throw "E0002"
}
// 만료 기한과 현재 시각을 비교
const { expired_date: expiredDate } = response[0]
const intExpiredDate = expiredDate.getTime()
const nowDate = new Date().getTime()
isVerified = nowDate > intExpiredDate ? false : true
} catch (e: any) {
paramsErrorHandler(e)
}
return {
status: 200,
data: { isVerified },
}
}