Skip to content

Commit

Permalink
네이버 소셜 로그인 기능 반영, OAuth 부분 리팩토링 중
Browse files Browse the repository at this point in the history
  • Loading branch information
sirini committed May 8, 2024
1 parent a8bc0cc commit ff226dc
Show file tree
Hide file tree
Showing 27 changed files with 187 additions and 64 deletions.
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
| |_\__ \ |_) | (_) | (_| | | | (_| |
\__|___/_.__/ \___/ \__,_|_| \__,_|
v0.8.22 | tsboard.dev
v0.8.23 | tsboard.dev
-->
<!doctype html>
<html lang="ko">
Expand Down
6 changes: 5 additions & 1 deletion install/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,13 @@ ADMIN_PW=#adminpw#
[email protected]
GMAIL_APP_PASSWORD=
# 구글 OAuth 클라이언트
# 구글 OAuth 클라이언트 (CLIENT_ID는 tsboard.config.ts OAUTH.GOOGLE.CLIENT_ID와 동일)
OAUTH_GOOGLE_CLIENT_ID=
OAUTH_GOOGLE_PW=
# 네이버 OAuth 클라이언트
OAUTH_NAVER_CLIENT_ID=
OAUTH_NAVER_PW=
`

const foundEnvTitle = `
Expand Down
Binary file added public/naver/btnD_icon_circle.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/naver/btnD_icon_square.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/naver/btnD_logout.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/naver/btnD_official.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/naver/btnD_short.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/naver/btnG_icon_circle.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/naver/btnG_icon_square.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/naver/btnG_logout.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/naver/btnG_official.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/naver/btnG_short.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/naver/btnW_icon_circle.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/naver/btnW_icon_square.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/naver/btnW_logout.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/naver/btnW_official.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/naver/btnW_short.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
143 changes: 129 additions & 14 deletions server/routers/auth/oauth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ import { Elysia, t } from "elysia"
import { jwt } from "@elysiajs/jwt"
import { registerUser } from "../../database/auth/signin"
import { saveTokens } from "../../database/auth/authorization"
import { AUTH, OAUTH } from "../../../tsboard.config"
import { AUTH, OAUTH, TSBOARD } from "../../../tsboard.config"
import { getUser } from "../../database/auth/myinfo"
import { INIT_USER } from "../../database/auth/const"
import { fail, success } from "../../util/tools"
import { nanoid } from "nanoid"

export const oauth = new Elysia()
.use(
Expand All @@ -20,21 +21,30 @@ export const oauth = new Elysia()
secret: process.env.JWT_SECRET_KEY!,
}),
)
.get("/google/request", async ({ set }) => {
const scope = "email profile"
const redirectURI = `${TSBOARD.API.URI}/tsapi/auth/google/callback`
const requestURI = `https://accounts.google.com/o/oauth2/v2/auth?client_id=${process.env.OAUTH_GOOGLE_CLIENT_ID}&redirect_uri=${redirectURI}&scope=${scope}&response_type=code&access_type=offline&prompt=select_account`
set.redirect = requestURI
return
})
.get(
"/google",
async ({ jwt, cookie: { refresh, googleUserInfo }, query: { code }, set }) => {
"/google/callback",
async ({ jwt, cookie: { refresh, oauthUserInfo }, query: { code }, set }) => {
set.headers["Content-Type"] = "text/html"

if (!process.env.OAUTH_GOOGLE_PW) {
return "<!DOCTYPE html><html><head><title>TSBOARD</title></head><body><h1>Failed to access Google OAuth</h1><p><button onclick='window.close()'>CLOSE</button></p></body></html>"
set.redirect = TSBOARD.API.URI
return
}

if (code.length < 1) {
return "<!DOCTYPE html><html><head><title>TSBOARD</title></head><body><h1>Failed to get a code from Google</h1><p><button onclick='window.close()'>CLOSE</button></p></body></html>"
set.redirect = TSBOARD.API.URI
return
}

let userInfo: any = null
const tokenResponse = await fetch(OAUTH.GOOGLE.TOKEN_URI, {
const tokenResponse = await fetch("https://oauth2.googleapis.com/token", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Expand All @@ -43,14 +53,14 @@ export const oauth = new Elysia()
code,
client_id: process.env.OAUTH_GOOGLE_CLIENT_ID || "",
client_secret: process.env.OAUTH_GOOGLE_PW || "",
redirect_uri: OAUTH.GOOGLE.REDIRECT_URI,
redirect_uri: `${TSBOARD.API.URI}/tsapi/auth/google/callback`,
grant_type: "authorization_code",
}),
})

const googleToken = await tokenResponse.json()
if (googleToken.access_token) {
const userInfoResponse = await fetch(OAUTH.GOOGLE.USERINFO_URI, {
const userInfoResponse = await fetch("https://www.googleapis.com/oauth2/v2/userinfo", {
headers: {
Authorization: `Bearer ${googleToken.access_token}`,
},
Expand All @@ -59,7 +69,8 @@ export const oauth = new Elysia()
}

if (userInfo === null) {
return "<!DOCTYPE html><html><head><title>TSBOARD</title></head><body><h1>Unable to get an user information from Google</h1><p><button onclick='window.close()'>CLOSE</button></p></body></html>"
set.redirect = TSBOARD.API.URI
return
}

if (userInfo.email) {
Expand All @@ -86,26 +97,130 @@ export const oauth = new Elysia()
const googleUser = await getUser(userUid)
googleUser.token = accessToken

googleUserInfo.set({
oauthUserInfo.set({
value: JSON.stringify(googleUser),
maxAge: AUTH.JWT.OAUTH_TIMEOUT * 60,
path: "/",
httpOnly: AUTH.COOKIE.HTTP_ONLY,
secure: AUTH.COOKIE.SECURE,
})
}
return `<!DOCTYPE html><html><head><script type='text/javascript'>window.opener.postMessage('${OAUTH.SUCCESS_MESSAGE}', '*');window.onload=function(){window.close();};</script><title>TSBOARD</title></head><body><h1>Login Successful</h1><p><button onclick='window.close()'>CLOSE</button></p></body></html>`

set.redirect = `${TSBOARD.API.URI}/login/oauth`
return
},
{
query: t.Object({
code: t.String(),
}),
},
)

.get("/naver/request", async ({ set }) => {
const redirectURI = `${TSBOARD.API.URI}/tsapi/auth/naver/callback`
const requestURI = `https://nid.naver.com/oauth2.0/authorize?response_type=code&client_id=${process.env.OAUTH_NAVER_CLIENT_ID}&redirect_uri=${redirectURI}&state=${nanoid()}`
set.redirect = requestURI
return
})
.get(
"/naver/callback",
async ({ jwt, cookie: { refresh, oauthUserInfo }, query: { code, state }, set }) => {
if (!process.env.OAUTH_NAVER_PW) {
set.redirect = TSBOARD.API.URI
return
}

if (code.length < 1) {
set.redirect = TSBOARD.API.URI
return
}

let userInfo: any = null
const tokenResponse = await fetch("https://nid.naver.com/oauth2.0/token", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
"X-Naver-Client-Id": process.env.OAUTH_NAVER_CLIENT_ID || "",
"X-Naver-Client-Secret": process.env.OAUTH_NAVER_PW || "",
},
body: new URLSearchParams({
grant_type: "authorization_code",
client_id: process.env.OAUTH_NAVER_CLIENT_ID || "",
client_secret: process.env.OAUTH_NAVER_PW || "",
redirect_uri: `${TSBOARD.API.URI}/tsapi/naver/callback`,
code,
state,
}),
})

const naverToken = await tokenResponse.json()
if (naverToken.access_token) {
const userInfoResponse = await fetch("https://openapi.naver.com/v1/nid/me", {
headers: {
Authorization: `Bearer ${naverToken.access_token}`,
},
})
userInfo = await userInfoResponse.json()
}

if (userInfo === null) {
set.redirect = TSBOARD.API.URI
return
}

userInfo = userInfo.response

if (userInfo.email) {
const userUid = await registerUser(
userInfo.email,
userInfo.nickname,
userInfo.profile_image,
)
const now = Math.floor(Date.now() / 1000)
const accessToken = await jwt.sign({
uid: userUid,
exp: now + AUTH.JWT.ACCESS_TIMEOUT * 60,
})
const refreshToken = await jwt.sign({
exp: now + AUTH.JWT.REFRESH_TIMEOUT * 60 * 60 * 24,
})

saveTokens(userUid, refreshToken)

refresh.set({
value: refreshToken,
maxAge: AUTH.JWT.REFRESH_TIMEOUT * 86400,
path: "/",
httpOnly: AUTH.COOKIE.HTTP_ONLY,
secure: AUTH.COOKIE.SECURE,
})

const naverUser = await getUser(userUid)
naverUser.token = accessToken

oauthUserInfo.set({
value: JSON.stringify(naverUser),
maxAge: AUTH.JWT.OAUTH_TIMEOUT * 60,
path: "/",
httpOnly: AUTH.COOKIE.HTTP_ONLY,
secure: AUTH.COOKIE.SECURE,
})
}

set.redirect = `${TSBOARD.API.URI}/login/oauth`
return
},
{
query: t.Object({
code: t.String(),
state: t.String(),
}),
},
)
.get("/google/userinfo", async ({ cookie: { googleUserInfo } }) => {
.get("/oauth/userinfo", async ({ cookie: { oauthUserInfo } }) => {
let response = INIT_USER
if (googleUserInfo.value.length < 1) {
if (oauthUserInfo.value.length < 1) {
return fail(`User information from Google is not available.`, response)
}
return success(googleUserInfo.value)
return success(oauthUserInfo.value)
})
18 changes: 9 additions & 9 deletions src/messages/mail/resetpassword.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ export const TEXT = [
직접 비밀번호를 변경하실 수 있습니다.<br />
<br />
<div style="width: 500px; background-color: #f0f0f0; border-radius: 10px; border: 2px solid #ddd; margin-top: 10px; margin-bottom: 10px; padding: 10px; line-height: 170%;">
&middot; 링크 : <a href="${TSBOARD.SITE.URL}${TSBOARD.PREFIX}/changepassword/#uid#/#code#" target="_blank">여기를 눌러 비밀번호 초기화 하기!</a>
&middot; 링크 : <a href="${TSBOARD.API.URI}${TSBOARD.PREFIX}/changepassword/#uid#/#code#" target="_blank">여기를 눌러 비밀번호 초기화 하기!</a>
</div>
<br />
From <a href="${TSBOARD.SITE.URL}" target="_blank">${TSBOARD.SITE.URL}</a> <span style="color: #888888">&middot; Powered by tsboard.dev</span>`,
From <a href="${TSBOARD.API.URI}" target="_blank">${TSBOARD.API.URI}</a> <span style="color: #888888">&middot; Powered by tsboard.dev</span>`,
CHAT: `[resetpassword] 비밀번호 초기화 요청 (회원 번호: #fromUid# / 아이디: #email#)
관리 화면에서 비밀번호를 임시로 초기화 해주세요. (${TSBOARD.SITE.URL}${TSBOARD.PREFIX}/admin/member/#fromUid#)
관리 화면에서 비밀번호를 임시로 초기화 해주세요. (${TSBOARD.API.URI}${TSBOARD.PREFIX}/admin/member/#fromUid#)
이후 초기화한 비밀번호를 #email# (으)로 전달해 주세요!
`,
},
Expand All @@ -38,12 +38,12 @@ If you did request a password reset, you can change your password using the rese
<br />
<div style="width: 500px; background-color: #f0f0f0; border-radius: 10px; border: 2px solid #ddd; margin-top: 10px; margin-bottom: 10px; padding: 10px; line-height: 170%;">
&middot; Link : <a href="${TSBOARD.SITE.URL}${TSBOARD.PREFIX}/changepassword/#uid#/#code#" target="_blank">Click here to reset your password!</a>
&middot; Link : <a href="${TSBOARD.API.URI}${TSBOARD.PREFIX}/changepassword/#uid#/#code#" target="_blank">Click here to reset your password!</a>
</div>
<br />
From <a href="${TSBOARD.SITE.URL}" target="_blank">${TSBOARD.SITE.URL}</a> <span style="color: #888888">&middot; Powered by tsboard.dev</span>`,
From <a href="${TSBOARD.API.URI}" target="_blank">${TSBOARD.API.URI}</a> <span style="color: #888888">&middot; Powered by tsboard.dev</span>`,
CHAT: `[resetpassword] Password reset request (Member ID: #fromUid# / Email: #email#)
Please temporarily reset the password from the admin panel. (${TSBOARD.SITE.URL}${TSBOARD.PREFIX}/admin/member/#fromUid#)
Please temporarily reset the password from the admin panel. (${TSBOARD.API.URI}${TSBOARD.PREFIX}/admin/member/#fromUid#)
Then, send the newly reset password to #email#!
`,
},
Expand All @@ -58,12 +58,12 @@ Then, send the newly reset password to #email#!
<br />
<div style="width: 500px; background-color: #f0f0f0; border-radius: 10px; border: 2px solid #ddd; margin-top: 10px; margin-bottom: 10px; padding: 10px; line-height: 170%;">
&middot; 链接 : <a href="${TSBOARD.SITE.URL}${TSBOARD.PREFIX}/changepassword/#uid#/#code#" target="_blank">点击此处重置您的密码!</a>
&middot; 链接 : <a href="${TSBOARD.API.URI}${TSBOARD.PREFIX}/changepassword/#uid#/#code#" target="_blank">点击此处重置您的密码!</a>
</div>
<br />
来自 <a href="${TSBOARD.SITE.URL}" target="_blank">${TSBOARD.SITE.URL}</a> <span style="color: #888888">&middot; 由 tsboard.dev 提供技术支持</span>`,
来自 <a href="${TSBOARD.API.URI}" target="_blank">${TSBOARD.API.URI}</a> <span style="color: #888888">&middot; 由 tsboard.dev 提供技术支持</span>`,
CHAT: `[resetpassword] 密码重置请求 (会员ID: #fromUid# / 电子邮箱: #email#)
请暂时从管理面板重置密码。 (${TSBOARD.SITE.URL}${TSBOARD.PREFIX}/admin/member/#fromUid#)
请暂时从管理面板重置密码。 (${TSBOARD.API.URI}${TSBOARD.PREFIX}/admin/member/#fromUid#)
然后,将新重置的密码发送给 #email#!
`,
},
Expand Down
12 changes: 6 additions & 6 deletions src/messages/mail/verification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ export const TEXT = [
<br />
<div style="width: 500px; background-color: #f0f0f0; border-radius: 10px; border: 2px solid #ddd; margin-top: 10px; margin-bottom: 10px; padding: 10px; line-height: 170%;">
&middot; 인증 코드 : <strong style="letter-spacing: 5px;">#code#</strong><br />
&middot; 코드 입력 : <a href="${TSBOARD.SITE.URL}${TSBOARD.PREFIX}/verify/#uid#" target="_blank">여기를 눌러 위의 인증 코드 입력하기!</a>
&middot; 코드 입력 : <a href="${TSBOARD.API.URI}${TSBOARD.PREFIX}/verify/#uid#" target="_blank">여기를 눌러 위의 인증 코드 입력하기!</a>
</div>
<br />
From <a href="${TSBOARD.SITE.URL}" target="_blank">${TSBOARD.SITE.URL}</a> <span style="color: #888888">&middot; Powered by tsboard.dev</span>`,
From <a href="${TSBOARD.API.URI}" target="_blank">${TSBOARD.API.URI}</a> <span style="color: #888888">&middot; Powered by tsboard.dev</span>`,
},
/* LANG.EN */ {
SUBJECT: `[${TSBOARD.SITE.NAME}] Please enter your verification code, #name#.`,
Expand All @@ -29,10 +29,10 @@ To complete your registration, please enter the 6-digit verification code at the
<div style="width: 500px; background-color: #f0f0f0; border-radius: 10px; border: 2px solid #ddd; margin-top: 10px; margin-bottom: 10px; padding: 10px; line-height: 170%;">
&middot; Verification Code: <strong style="letter-spacing: 5px;">#code#</strong><br />
&middot; Enter Code: <a href="${TSBOARD.SITE.URL}${TSBOARD.PREFIX}/verify/#uid#" target="_blank">Click here to enter your verification code!</a>
&middot; Enter Code: <a href="${TSBOARD.API.URI}${TSBOARD.PREFIX}/verify/#uid#" target="_blank">Click here to enter your verification code!</a>
</div>
<br />
From <a href="${TSBOARD.SITE.URL}" target="_blank">${TSBOARD.SITE.URL}</a> <span style="color: #888888">&middot; Powered by tsboard.dev</span>`,
From <a href="${TSBOARD.API.URI}" target="_blank">${TSBOARD.API.URI}</a> <span style="color: #888888">&middot; Powered by tsboard.dev</span>`,
},
/* LANG.CN */ {
SUBJECT: `[${TSBOARD.SITE.NAME}] 请填写您的验证码,#name#。`,
Expand All @@ -43,9 +43,9 @@ From <a href="${TSBOARD.SITE.URL}" target="_blank">${TSBOARD.SITE.URL}</a> <span
<div style="width: 500px; background-color: #f0f0f0; border-radius: 10px; border: 2px solid #ddd; margin-top: 10px; margin-bottom: 10px; padding: 10px; line-height: 170%;">
&middot; 验证码: <strong style="letter-spacing: 5px;">#code#</strong><br />
&middot; 填写验证码: <a href="${TSBOARD.SITE.URL}${TSBOARD.PREFIX}/verify/#uid#" target="_blank">点击这里填写您的验证码!</a>
&middot; 填写验证码: <a href="${TSBOARD.API.URI}${TSBOARD.PREFIX}/verify/#uid#" target="_blank">点击这里填写您的验证码!</a>
</div>
<br />
来自 <a href="${TSBOARD.SITE.URL}" target="_blank">${TSBOARD.SITE.URL}</a> <span style="color: #888888">&middot; 由 tsboard.dev 提供技术支持</span>`,
来自 <a href="${TSBOARD.API.URI}" target="_blank">${TSBOARD.API.URI}</a> <span style="color: #888888">&middot; 由 tsboard.dev 提供技术支持</span>`,
},
]
6 changes: 3 additions & 3 deletions src/messages/mail/welcome.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const TEXT = [
${TSBOARD.SITE.NAME} 에서 자주 만나뵈었으면 좋겠습니다!<br />
<br />
<br />
From <a href="${TSBOARD.SITE.URL}" target="_blank">${TSBOARD.SITE.URL}</a> <span style="color: #888888">&middot; Powered by tsboard.dev</span>`,
From <a href="${TSBOARD.API.URI}" target="_blank">${TSBOARD.API.URI}</a> <span style="color: #888888">&middot; Powered by tsboard.dev</span>`,
},
/* LANG.EN */ {
SUBJECT: `[${TSBOARD.SITE.NAME}] Welcome to your membership, #name#!`,
Expand All @@ -29,7 +29,7 @@ You can now log in to the site.<br />
We look forward to seeing you often at ${TSBOARD.SITE.NAME}!<br />
<br />
<br />
From <a href="${TSBOARD.SITE.URL}" target="_blank">${TSBOARD.SITE.URL}</a> <span style="color: #888888">&middot; Powered by tsboard.dev</span>`,
From <a href="${TSBOARD.API.URI}" target="_blank">${TSBOARD.API.URI}</a> <span style="color: #888888">&middot; Powered by tsboard.dev</span>`,
},
/* LANG.CN */ {
SUBJECT: `[${TSBOARD.SITE.NAME}] 欢迎加入会员,#name#!`,
Expand All @@ -41,6 +41,6 @@ From <a href="${TSBOARD.SITE.URL}" target="_blank">${TSBOARD.SITE.URL}</a> <span
我们期待在${TSBOARD.SITE.NAME}经常见到您!<br />
<br />
<br />
来自 <a href="${TSBOARD.SITE.URL}" target="_blank">${TSBOARD.SITE.URL}</a> <span style="color: #888888">&middot; 由 tsboard.dev 提供技术支持</span>`,
来自 <a href="${TSBOARD.API.URI}" target="_blank">${TSBOARD.API.URI}</a> <span style="color: #888888">&middot; 由 tsboard.dev 提供技术支持</span>`,
},
]
3 changes: 3 additions & 0 deletions src/messages/pages/auth/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const TEXT = [
SIGNUP: "회원가입",
LOGIN: "로그인",
GOOGLE_LOGIN_TOOLTIP: "구글 계정으로 로그인하기",
NAVER_LOGIN_TOOLTIP: "네이버 계정으로 로그인하기",
},
/* LANG.EN */ {
TITLE: "Login",
Expand All @@ -30,6 +31,7 @@ If you've forgotten your password, we will send a reset password to the email ad
SIGNUP: "Sign Up",
LOGIN: "Login",
GOOGLE_LOGIN_TOOLTIP: "Sign in with Google",
NAVER_LOGIN_TOOLTIP: "Sign in with Naver",
},
/* LANG.CN */ {
TITLE: "登录",
Expand All @@ -43,6 +45,7 @@ If you've forgotten your password, we will send a reset password to the email ad
SIGNUP: "注册",
LOGIN: "登录",
GOOGLE_LOGIN_TOOLTIP: "使用谷歌登录",
NAVER_LOGIN_TOOLTIP: "使用naver登录",
},
]
Object.freeze(TEXT)
Loading

0 comments on commit ff226dc

Please sign in to comment.