Просмотр исходного кода

fix: fix oauth2 state not checking

JustSong 2 лет назад
Родитель
Сommit
39ae8075e4

+ 27 - 0
controller/github.go

@@ -79,6 +79,14 @@ func getGitHubUserInfoByCode(code string) (*GitHubUser, error) {
 
 func GitHubOAuth(c *gin.Context) {
 	session := sessions.Default(c)
+	state := c.Query("state")
+	if state == "" || session.Get("oauth_state") == nil || state != session.Get("oauth_state").(string) {
+		c.JSON(http.StatusForbidden, gin.H{
+			"success": false,
+			"message": "state is empty or not same",
+		})
+		return
+	}
 	username := session.Get("username")
 	if username != nil {
 		GitHubBind(c)
@@ -205,3 +213,22 @@ func GitHubBind(c *gin.Context) {
 	})
 	return
 }
+
+func GenerateOAuthCode(c *gin.Context) {
+	session := sessions.Default(c)
+	state := common.GetRandomString(12)
+	session.Set("oauth_state", state)
+	err := session.Save()
+	if err != nil {
+		c.JSON(http.StatusOK, gin.H{
+			"success": false,
+			"message": err.Error(),
+		})
+		return
+	}
+	c.JSON(http.StatusOK, gin.H{
+		"success": true,
+		"message": "",
+		"data":    state,
+	})
+}

+ 1 - 0
router/api-router.go

@@ -21,6 +21,7 @@ func SetApiRouter(router *gin.Engine) {
 		apiRouter.GET("/reset_password", middleware.CriticalRateLimit(), middleware.TurnstileCheck(), controller.SendPasswordResetEmail)
 		apiRouter.POST("/user/reset", middleware.CriticalRateLimit(), controller.ResetPassword)
 		apiRouter.GET("/oauth/github", middleware.CriticalRateLimit(), controller.GitHubOAuth)
+		apiRouter.GET("/oauth/state", middleware.CriticalRateLimit(), controller.GenerateOAuthCode)
 		apiRouter.GET("/oauth/wechat", middleware.CriticalRateLimit(), controller.WeChatAuth)
 		apiRouter.GET("/oauth/wechat/bind", middleware.CriticalRateLimit(), middleware.UserAuth(), controller.WeChatBind)
 		apiRouter.GET("/oauth/email/bind", middleware.CriticalRateLimit(), middleware.UserAuth(), controller.EmailBind)

+ 5 - 4
web/src/components/GitHubOAuth.js

@@ -13,8 +13,8 @@ const GitHubOAuth = () => {
 
   let navigate = useNavigate();
 
-  const sendCode = async (code, count) => {
-    const res = await API.get(`/api/oauth/github?code=${code}`);
+  const sendCode = async (code, state, count) => {
+    const res = await API.get(`/api/oauth/github?code=${code}&state=${state}`);
     const { success, message, data } = res.data;
     if (success) {
       if (message === 'bind') {
@@ -36,13 +36,14 @@ const GitHubOAuth = () => {
       count++;
       setPrompt(`出现错误,第 ${count} 次重试中...`);
       await new Promise((resolve) => setTimeout(resolve, count * 2000));
-      await sendCode(code, count);
+      await sendCode(code, state, count);
     }
   };
 
   useEffect(() => {
     let code = searchParams.get('code');
-    sendCode(code, 0).then();
+    let state = searchParams.get('state');
+    sendCode(code, state, 0).then();
   }, []);
 
   return (

+ 2 - 7
web/src/components/LoginForm.js

@@ -3,6 +3,7 @@ import { Button, Divider, Form, Grid, Header, Image, Message, Modal, Segment } f
 import { Link, useNavigate, useSearchParams } from 'react-router-dom';
 import { UserContext } from '../context/User';
 import { API, getLogo, showError, showSuccess } from '../helpers';
+import { getOAuthState, onGitHubOAuthClicked } from './utils';
 
 const LoginForm = () => {
   const [inputs, setInputs] = useState({
@@ -31,12 +32,6 @@ const LoginForm = () => {
 
   const [showWeChatLoginModal, setShowWeChatLoginModal] = useState(false);
 
-  const onGitHubOAuthClicked = () => {
-    window.open(
-      `https://github.com/login/oauth/authorize?client_id=${status.github_client_id}&scope=user:email`
-    );
-  };
-
   const onWeChatLoginClicked = () => {
     setShowWeChatLoginModal(true);
   };
@@ -131,7 +126,7 @@ const LoginForm = () => {
                 circular
                 color='black'
                 icon='github'
-                onClick={onGitHubOAuthClicked}
+                onClick={()=>onGitHubOAuthClicked(status.github_client_id)}
               />
             ) : (
               <></>

+ 2 - 7
web/src/components/PersonalSetting.js

@@ -4,6 +4,7 @@ import { Link, useNavigate } from 'react-router-dom';
 import { API, copy, showError, showInfo, showNotice, showSuccess } from '../helpers';
 import Turnstile from 'react-turnstile';
 import { UserContext } from '../context/User';
+import { onGitHubOAuthClicked } from './utils';
 
 const PersonalSetting = () => {
   const [userState, userDispatch] = useContext(UserContext);
@@ -130,12 +131,6 @@ const PersonalSetting = () => {
     }
   };
 
-  const openGitHubOAuth = () => {
-    window.open(
-      `https://github.com/login/oauth/authorize?client_id=${status.github_client_id}&scope=user:email`
-    );
-  };
-
   const sendVerificationCode = async () => {
     setDisableButton(true);
     if (inputs.email === '') return;
@@ -249,7 +244,7 @@ const PersonalSetting = () => {
       </Modal>
       {
         status.github_oauth && (
-          <Button onClick={openGitHubOAuth}>绑定 GitHub 账号</Button>
+          <Button onClick={()=>{onGitHubOAuthClicked(status.github_client_id)}}>绑定 GitHub 账号</Button>
         )
       }
       <Button

+ 20 - 0
web/src/components/utils.js

@@ -0,0 +1,20 @@
+import { API, showError } from '../helpers';
+
+export async function getOAuthState() {
+  const res = await API.get('/api/oauth/state');
+  const { success, message, data } = res.data;
+  if (success) {
+    return data;
+  } else {
+    showError(message);
+    return '';
+  }
+}
+
+export async function onGitHubOAuthClicked(github_client_id) {
+  const state = await getOAuthState();
+  if (!state) return;
+  window.open(
+    `https://github.com/login/oauth/authorize?client_id=${github_client_id}&state=${state}&scope=user:email`
+  );
+}