From 4de18a6a81aee9947664f3acaa422a7220e64672 Mon Sep 17 00:00:00 2001 From: dushixiang <798148596@qq.com> Date: Tue, 12 Jan 2021 22:36:59 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BA=86=E5=8F=8C=E5=9B=A0?= =?UTF-8?q?=E7=B4=A0=E8=AE=A4=E8=AF=81=E7=9A=84=E6=B5=81=E7=A8=8B=E5=92=8C?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkg/api/account.go | 39 ++++++++++++ pkg/api/routes.go | 1 + web/src/common/request.js | 1 + web/src/components/Login.js | 102 ++++++++++++++++++++++++++------ web/src/components/user/Info.js | 47 +++++++++------ 5 files changed, 154 insertions(+), 36 deletions(-) diff --git a/pkg/api/account.go b/pkg/api/account.go index ae898c3..64bf4c0 100644 --- a/pkg/api/account.go +++ b/pkg/api/account.go @@ -48,6 +48,45 @@ func LoginEndpoint(c echo.Context) error { return Fail(c, -1, "您输入的账号或密码不正确") } + if user.TOTPSecret != "" { + return Fail(c, 0, "") + } + + token := utils.UUID() + + authorization := Authorization{ + Token: token, + Remember: loginAccount.Remember, + User: user, + } + + if authorization.Remember { + // 记住登录有效期两周 + global.Cache.Set(token, authorization, time.Hour*time.Duration(24*14)) + } else { + global.Cache.Set(token, authorization, time.Hour*time.Duration(2)) + } + + model.UpdateUserById(&model.User{Online: true}, user.ID) + + return Success(c, token) +} + +func loginWithTotpEndpoint(c echo.Context) error { + var loginAccount LoginAccount + if err := c.Bind(&loginAccount); err != nil { + return err + } + + user, err := model.FindUserByUsername(loginAccount.Username) + if err != nil { + return Fail(c, -1, "您输入的账号或密码不正确") + } + + if err := utils.Encoder.Match([]byte(user.Password), []byte(loginAccount.Password)); err != nil { + return Fail(c, -1, "您输入的账号或密码不正确") + } + if !totp.Validate(loginAccount.TOTP, user.TOTPSecret) { return Fail(c, -2, "您的TOTP不匹配") } diff --git a/pkg/api/routes.go b/pkg/api/routes.go index a95fd23..fd10274 100644 --- a/pkg/api/routes.go +++ b/pkg/api/routes.go @@ -30,6 +30,7 @@ func SetupRoutes() *echo.Echo { e.Use(Auth) e.POST("/login", LoginEndpoint) + e.POST("/loginWithTotp", loginWithTotpEndpoint) e.GET("/tunnel", TunEndpoint) e.GET("/ssh", SSHEndpoint) diff --git a/web/src/common/request.js b/web/src/common/request.js index 4c914cc..657006c 100644 --- a/web/src/common/request.js +++ b/web/src/common/request.js @@ -25,6 +25,7 @@ const handleError = (error) => { const handleResult = (result) => { if (result['code'] === 403) { window.location.href = '#/login'; + return; } } diff --git a/web/src/components/Login.js b/web/src/components/Login.js index e54a185..5a07ff3 100644 --- a/web/src/components/Login.js +++ b/web/src/components/Login.js @@ -1,18 +1,24 @@ -import React, { Component } from 'react'; -import { Button, Card, Checkbox, Form, Input, Typography } from "antd"; +import React, {Component} from 'react'; +import {Button, Card, Checkbox, Form, Input, Modal, Typography} from "antd"; import './Login.css' import request from "../common/request"; -import { message } from "antd/es"; -import { withRouter } from "react-router-dom"; -import { OneToOneOutlined, LockOutlined, UserOutlined } from '@ant-design/icons'; +import {message} from "antd/es"; +import {withRouter} from "react-router-dom"; +import {LockOutlined, OneToOneOutlined, UserOutlined} from '@ant-design/icons'; + +const {Title} = Typography; -const { Title } = Typography; class LoginForm extends Component { + formRef = React.createRef() + state = { inLogin: false, height: window.innerHeight, - width: window.innerWidth + width: window.innerWidth, + loginAccount: undefined, + totpModalVisible: false, + confirmLoading: false }; componentDidMount() { @@ -31,6 +37,15 @@ class LoginForm extends Component { try { let result = await request.post('/login', params); + + if (result.code === 0) { + // 进行双因子认证 + this.setState({ + loginAccount: params, + totpModalVisible: true + }) + return; + } if (result.code !== 1) { throw new Error(result.message); } @@ -50,35 +65,88 @@ class LoginForm extends Component { } }; + handleOk = async (values) => { + this.setState({ + confirmLoading: true + }) + let loginAccount = this.state.loginAccount; + loginAccount['totp'] = values['totp']; + try { + let result = await request.post('/loginWithTotp', loginAccount); + + if (result.code !== 1) { + throw new Error(result.message); + } + + // 跳转登录 + sessionStorage.removeItem('current'); + sessionStorage.removeItem('openKeys'); + localStorage.setItem('X-Auth-Token', result['data']); + // this.props.history.push(); + window.location.href = "/" + } catch (e) { + message.error(e.message); + } finally { + this.setState({ + confirmLoading: false + }); + } + } + + handleCancel = () => { + this.setState({ + totpModalVisible: false + }) + } + render() { return (