2022-11-16 10:20:07 +00:00
|
|
|
|
package user
|
|
|
|
|
|
|
|
|
|
import (
|
2022-11-20 14:11:47 +00:00
|
|
|
|
_ "embed"
|
2023-04-22 10:02:50 +00:00
|
|
|
|
"errors"
|
2023-01-10 08:37:57 +00:00
|
|
|
|
"fmt"
|
2022-11-16 10:20:07 +00:00
|
|
|
|
"github.com/gin-gonic/gin"
|
2023-04-22 10:02:50 +00:00
|
|
|
|
"github.com/huoxue1/qinglong-go/internal/auth"
|
|
|
|
|
"github.com/huoxue1/qinglong-go/internal/res"
|
2023-01-10 08:37:57 +00:00
|
|
|
|
"github.com/huoxue1/qinglong-go/service/notification"
|
2022-11-16 10:20:07 +00:00
|
|
|
|
"github.com/huoxue1/qinglong-go/service/user"
|
|
|
|
|
"github.com/huoxue1/qinglong-go/utils"
|
|
|
|
|
"os"
|
|
|
|
|
"path"
|
|
|
|
|
"time"
|
|
|
|
|
)
|
|
|
|
|
|
2022-11-26 02:31:26 +00:00
|
|
|
|
//go:embed package_sample.json
|
|
|
|
|
var pack []byte
|
|
|
|
|
|
2022-11-16 10:20:07 +00:00
|
|
|
|
func Api(group *gin.RouterGroup) {
|
|
|
|
|
group.GET("/", get())
|
|
|
|
|
group.PUT("/init", appInit())
|
|
|
|
|
group.POST("/login", login())
|
2023-01-10 08:37:57 +00:00
|
|
|
|
group.POST("/logout", logout())
|
2023-01-10 12:43:28 +00:00
|
|
|
|
group.PUT("/notification/init", putNotification())
|
2023-01-10 08:37:57 +00:00
|
|
|
|
group.PUT("/notification", putNotification())
|
|
|
|
|
group.GET("/notification", getNotification())
|
2023-04-22 10:02:50 +00:00
|
|
|
|
group.GET("/two-factor/init", twoFactorInit())
|
|
|
|
|
group.PUT("/two-factor/active", twoFactorActive())
|
|
|
|
|
group.PUT("/two-factor/login", twoFactorLogin())
|
|
|
|
|
group.PUT("/two-factor/deactive", twoFactorDeactive())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func twoFactorDeactive() gin.HandlerFunc {
|
|
|
|
|
return func(ctx *gin.Context) {
|
|
|
|
|
info, err := auth.GetAuthInfo()
|
|
|
|
|
if err != nil {
|
|
|
|
|
ctx.JSON(401, res.Err(401, err))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
info.TwoFactorSecret = ""
|
|
|
|
|
info.IsTwoFactorChecking = false
|
|
|
|
|
err = auth.UpdateAuthInfo(info)
|
|
|
|
|
if err != nil {
|
|
|
|
|
ctx.JSON(500, res.Err(500, err))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
ctx.JSON(200, res.Ok(nil))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func twoFactorLogin() gin.HandlerFunc {
|
|
|
|
|
return func(ctx *gin.Context) {
|
|
|
|
|
info, err := auth.GetAuthInfo()
|
|
|
|
|
if err != nil {
|
|
|
|
|
ctx.JSON(401, res.Err(401, err))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if info.TwoFactorSecret == "" {
|
|
|
|
|
ctx.JSON(400, res.Err(400, errors.New("the two factor is not initialized")))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
type req struct {
|
|
|
|
|
UserName string `json:"username"`
|
|
|
|
|
Password string `json:"password"`
|
|
|
|
|
Code string `json:"code"`
|
|
|
|
|
}
|
|
|
|
|
var r req
|
|
|
|
|
err = ctx.ShouldBindJSON(&r)
|
|
|
|
|
if err != nil {
|
|
|
|
|
ctx.JSON(400, res.Err(400, err))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if info.Username != r.UserName || info.Password != r.Password {
|
|
|
|
|
ctx.JSON(400, res.Err(400, errors.New("the username or password is invalid")))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !auth.VerifyTOTP(info.TwoFactorSecret, r.Code) {
|
|
|
|
|
ctx.JSON(400, res.Err(400, errors.New("the two factor code is invalid")))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
token, err := utils.GenerateToken(r.UserName, 48)
|
|
|
|
|
if err != nil {
|
|
|
|
|
ctx.JSON(503, res.Err(503, err))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
ip, err := user.GetNetIp(ctx.RemoteIP())
|
|
|
|
|
if err != nil {
|
|
|
|
|
ip = new(user.Ip)
|
|
|
|
|
err = nil
|
|
|
|
|
}
|
|
|
|
|
mobile := utils.IsMobile(ctx.GetHeader("User-Agent"))
|
|
|
|
|
if mobile {
|
|
|
|
|
info.Tokens.Mobile = token
|
|
|
|
|
info.Token = token
|
|
|
|
|
err := auth.UpdateAuthInfo(info)
|
|
|
|
|
if err != nil {
|
|
|
|
|
ctx.JSON(503, res.Err(503, err))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
notification.Push.Send("登录通知", fmt.Sprintf("你于%s登录mobile端登录成功,ip地址 %s", time.Now().Format("2006-01-02 15:04:05"), ctx.ClientIP()))
|
|
|
|
|
ctx.JSON(200, res.Ok(gin.H{
|
|
|
|
|
"token": token,
|
|
|
|
|
"platform": "mobile",
|
|
|
|
|
"retries": 0,
|
|
|
|
|
"lastip": ctx.RemoteIP(),
|
|
|
|
|
"lastaddr": ip.Addr,
|
|
|
|
|
"lastlogon": time.Now().UnixNano(),
|
|
|
|
|
}))
|
|
|
|
|
} else {
|
|
|
|
|
info.Tokens.Desktop = token
|
|
|
|
|
info.Token = token
|
|
|
|
|
err := auth.UpdateAuthInfo(info)
|
|
|
|
|
if err != nil {
|
|
|
|
|
ctx.JSON(503, res.Err(503, err))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
notification.Push.Send("登录通知", fmt.Sprintf("你于%s登录pc端登录成功,ip地址 %s", time.Now().Format("2006-01-02 15:04:05"), ctx.ClientIP()))
|
|
|
|
|
ctx.JSON(200, res.Ok(gin.H{
|
|
|
|
|
"token": token,
|
|
|
|
|
"platform": "desktop",
|
|
|
|
|
"retries": 0,
|
|
|
|
|
"lastip": ctx.RemoteIP(),
|
|
|
|
|
"lastaddr": ip.Addr,
|
|
|
|
|
"lastlogon": time.Now().Unix(),
|
|
|
|
|
}))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func twoFactorActive() gin.HandlerFunc {
|
|
|
|
|
return func(ctx *gin.Context) {
|
|
|
|
|
info, err := auth.GetAuthInfo()
|
|
|
|
|
if err != nil {
|
|
|
|
|
ctx.JSON(401, res.Err(401, err))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if info.TwoFactorSecret == "" {
|
|
|
|
|
ctx.JSON(400, res.Err(400, errors.New("the two factor is not initialized")))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
type req struct {
|
|
|
|
|
Code string `json:"code"`
|
|
|
|
|
}
|
|
|
|
|
var r req
|
|
|
|
|
err = ctx.ShouldBindJSON(&r)
|
|
|
|
|
if err != nil {
|
|
|
|
|
ctx.JSON(400, res.Err(400, err))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if !auth.VerifyTOTP(info.TwoFactorSecret, r.Code) {
|
|
|
|
|
ctx.JSON(400, res.Err(400, errors.New("the two factor code is invalid")))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
info.IsTwoFactorChecking = true
|
|
|
|
|
_ = auth.UpdateAuthInfo(info)
|
|
|
|
|
ctx.JSON(200, res.Ok(true))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func twoFactorInit() gin.HandlerFunc {
|
|
|
|
|
return func(ctx *gin.Context) {
|
|
|
|
|
info, err := auth.GetAuthInfo()
|
|
|
|
|
if err != nil {
|
|
|
|
|
ctx.JSON(401, res.Err(401, err))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
//if info.TwoFactorSecret != "" {
|
|
|
|
|
// ctx.JSON(400, res.Err(400, errors.New("the two factor is initialized")))
|
|
|
|
|
// return
|
|
|
|
|
//}
|
|
|
|
|
secret, qrcode, err := auth.GenerateTOTP(info.Username, "qinglong-go")
|
|
|
|
|
if err != nil {
|
|
|
|
|
ctx.JSON(500, res.Err(500, err))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
info.TwoFactorSecret = secret
|
|
|
|
|
_ = auth.UpdateAuthInfo(info)
|
|
|
|
|
ctx.JSON(200, res.Ok(gin.H{"secret": secret, "url": qrcode}))
|
|
|
|
|
|
|
|
|
|
}
|
2023-01-10 08:37:57 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func logout() gin.HandlerFunc {
|
|
|
|
|
return func(ctx *gin.Context) {
|
|
|
|
|
ctx.JSON(200, res.Ok(true))
|
|
|
|
|
}
|
2022-11-16 10:20:07 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func get() gin.HandlerFunc {
|
|
|
|
|
return func(ctx *gin.Context) {
|
2023-04-22 10:02:50 +00:00
|
|
|
|
info, err := auth.GetAuthInfo()
|
2022-11-16 10:20:07 +00:00
|
|
|
|
if err != nil {
|
2023-04-22 10:02:50 +00:00
|
|
|
|
ctx.JSON(401, res.Err(401, err))
|
2022-11-16 10:20:07 +00:00
|
|
|
|
return
|
|
|
|
|
}
|
2023-04-22 10:02:50 +00:00
|
|
|
|
ctx.JSON(200, res.Ok(gin.H{"username": info.Username, "twoFactorActivated": info.IsTwoFactorChecking}))
|
2022-11-16 10:20:07 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func appInit() gin.HandlerFunc {
|
|
|
|
|
return func(ctx *gin.Context) {
|
2023-04-22 10:02:50 +00:00
|
|
|
|
isInit := auth.IsInit()
|
|
|
|
|
if isInit {
|
|
|
|
|
ctx.JSON(400, res.Err(400, errors.New("the app is initialized")))
|
2022-11-16 10:20:07 +00:00
|
|
|
|
return
|
|
|
|
|
}
|
2023-02-03 04:43:37 +00:00
|
|
|
|
_ = os.MkdirAll(path.Join("data", "deps"), 0666)
|
2023-04-22 10:02:50 +00:00
|
|
|
|
_ = os.Link(path.Join("data", "deps"), path.Join("data", "scripts", "deps"))
|
2022-11-20 14:11:47 +00:00
|
|
|
|
_ = os.MkdirAll(path.Join("data", "log"), 0666)
|
|
|
|
|
_ = os.MkdirAll(path.Join("data", "repo"), 0666)
|
|
|
|
|
_ = os.MkdirAll(path.Join("data", "scripts"), 0666)
|
2022-11-26 02:31:26 +00:00
|
|
|
|
_ = os.MkdirAll(path.Join("data", "deps"), 0666)
|
|
|
|
|
_ = os.MkdirAll(path.Join("data", "raw"), 0666)
|
|
|
|
|
_ = os.WriteFile(path.Join("data", "scripts", "package.json"), pack, 0666)
|
2023-04-22 14:17:05 +00:00
|
|
|
|
|
2022-11-16 10:20:07 +00:00
|
|
|
|
type Req struct {
|
|
|
|
|
UserName string `json:"username"`
|
|
|
|
|
Password string `json:"password"`
|
|
|
|
|
}
|
|
|
|
|
r := new(Req)
|
2023-04-22 10:02:50 +00:00
|
|
|
|
err := ctx.ShouldBindJSON(r)
|
2022-11-16 10:20:07 +00:00
|
|
|
|
if err != nil {
|
2023-01-10 12:43:28 +00:00
|
|
|
|
ctx.JSON(503, res.ErrMessage(503, err.Error()))
|
2022-11-16 10:20:07 +00:00
|
|
|
|
return
|
|
|
|
|
}
|
2023-04-22 10:02:50 +00:00
|
|
|
|
err = auth.InitAuthInfo(r.UserName, r.Password)
|
|
|
|
|
if err != nil {
|
|
|
|
|
ctx.JSON(503, res.Err(503, err))
|
|
|
|
|
return
|
|
|
|
|
}
|
2022-11-16 10:20:07 +00:00
|
|
|
|
ctx.JSON(200, res.Ok(true))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func login() gin.HandlerFunc {
|
|
|
|
|
return func(ctx *gin.Context) {
|
|
|
|
|
type Req struct {
|
|
|
|
|
UserName string `json:"username"`
|
|
|
|
|
Password string `json:"password"`
|
|
|
|
|
}
|
|
|
|
|
r := new(Req)
|
|
|
|
|
err := ctx.ShouldBindJSON(r)
|
|
|
|
|
if err != nil {
|
2023-01-10 12:43:28 +00:00
|
|
|
|
ctx.JSON(503, res.Err(503, err))
|
2022-11-16 10:20:07 +00:00
|
|
|
|
return
|
|
|
|
|
}
|
2023-04-22 10:02:50 +00:00
|
|
|
|
authInfo, err := auth.GetAuthInfo()
|
2022-11-16 10:20:07 +00:00
|
|
|
|
if err != nil {
|
2023-04-22 10:02:50 +00:00
|
|
|
|
ctx.JSON(503, res.Err(503, err))
|
2022-11-16 10:20:07 +00:00
|
|
|
|
return
|
|
|
|
|
}
|
2023-04-22 10:02:50 +00:00
|
|
|
|
|
|
|
|
|
if authInfo.IsTwoFactorChecking {
|
|
|
|
|
ctx.JSON(200, res.Err(420, errors.New("")))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if authInfo.Username == r.UserName && authInfo.Password == r.Password {
|
2022-11-20 14:11:47 +00:00
|
|
|
|
token, err := utils.GenerateToken(r.UserName, 48)
|
2022-11-16 10:20:07 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
ctx.JSON(503, res.Err(503, err))
|
|
|
|
|
return
|
|
|
|
|
}
|
2023-01-12 03:21:57 +00:00
|
|
|
|
ip, err := user.GetNetIp(ctx.RemoteIP())
|
|
|
|
|
if err != nil {
|
|
|
|
|
ip = new(user.Ip)
|
|
|
|
|
err = nil
|
|
|
|
|
}
|
2022-11-16 10:20:07 +00:00
|
|
|
|
mobile := utils.IsMobile(ctx.GetHeader("User-Agent"))
|
|
|
|
|
if mobile {
|
2023-04-22 10:02:50 +00:00
|
|
|
|
authInfo.Tokens.Mobile = token
|
|
|
|
|
authInfo.Token = token
|
|
|
|
|
err := auth.UpdateAuthInfo(authInfo)
|
2022-11-16 10:20:07 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
ctx.JSON(503, res.Err(503, err))
|
|
|
|
|
return
|
|
|
|
|
}
|
2023-01-10 08:37:57 +00:00
|
|
|
|
notification.Push.Send("登录通知", fmt.Sprintf("你于%s登录mobile端登录成功,ip地址 %s", time.Now().Format("2006-01-02 15:04:05"), ctx.ClientIP()))
|
2022-11-16 10:20:07 +00:00
|
|
|
|
ctx.JSON(200, res.Ok(gin.H{
|
|
|
|
|
"token": token,
|
|
|
|
|
"platform": "mobile",
|
|
|
|
|
"retries": 0,
|
2023-01-12 03:21:57 +00:00
|
|
|
|
"lastip": ctx.RemoteIP(),
|
|
|
|
|
"lastaddr": ip.Addr,
|
2022-11-16 10:20:07 +00:00
|
|
|
|
"lastlogon": time.Now().UnixNano(),
|
|
|
|
|
}))
|
|
|
|
|
} else {
|
2023-04-22 10:02:50 +00:00
|
|
|
|
authInfo.Tokens.Desktop = token
|
|
|
|
|
authInfo.Token = token
|
|
|
|
|
err := auth.UpdateAuthInfo(authInfo)
|
2022-11-16 10:20:07 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
ctx.JSON(503, res.Err(503, err))
|
|
|
|
|
return
|
|
|
|
|
}
|
2023-01-10 08:37:57 +00:00
|
|
|
|
notification.Push.Send("登录通知", fmt.Sprintf("你于%s登录pc端登录成功,ip地址 %s", time.Now().Format("2006-01-02 15:04:05"), ctx.ClientIP()))
|
2022-11-16 10:20:07 +00:00
|
|
|
|
ctx.JSON(200, res.Ok(gin.H{
|
|
|
|
|
"token": token,
|
|
|
|
|
"platform": "desktop",
|
|
|
|
|
"retries": 0,
|
2023-01-12 03:21:57 +00:00
|
|
|
|
"lastip": ctx.RemoteIP(),
|
|
|
|
|
"lastaddr": ip.Addr,
|
|
|
|
|
"lastlogon": time.Now().Unix(),
|
2022-11-16 10:20:07 +00:00
|
|
|
|
}))
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
ctx.JSON(400, res.ErrMessage(400, "账号密码错误!"))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|