qinglong-go/api/user/user.go

320 lines
8.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package user
import (
_ "embed"
"errors"
"fmt"
"github.com/gin-gonic/gin"
"github.com/huoxue1/qinglong-go/internal/auth"
"github.com/huoxue1/qinglong-go/internal/res"
"github.com/huoxue1/qinglong-go/service/notification"
"github.com/huoxue1/qinglong-go/service/user"
"github.com/huoxue1/qinglong-go/utils"
"os"
"path"
"time"
)
//go:embed config_sample.sh
var sample []byte
//go:embed package_sample.json
var pack []byte
func Api(group *gin.RouterGroup) {
group.GET("/", get())
group.PUT("/init", appInit())
group.POST("/login", login())
group.POST("/logout", logout())
group.PUT("/notification/init", putNotification())
group.PUT("/notification", putNotification())
group.GET("/notification", getNotification())
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}))
}
}
func logout() gin.HandlerFunc {
return func(ctx *gin.Context) {
ctx.JSON(200, res.Ok(true))
}
}
func get() gin.HandlerFunc {
return func(ctx *gin.Context) {
info, err := auth.GetAuthInfo()
if err != nil {
ctx.JSON(401, res.Err(401, err))
return
}
ctx.JSON(200, res.Ok(gin.H{"username": info.Username, "twoFactorActivated": info.IsTwoFactorChecking}))
}
}
func appInit() gin.HandlerFunc {
return func(ctx *gin.Context) {
isInit := auth.IsInit()
if isInit {
ctx.JSON(400, res.Err(400, errors.New("the app is initialized")))
return
}
_ = os.MkdirAll(path.Join("data", "config"), 0666)
_ = os.MkdirAll(path.Join("data", "deps"), 0666)
_ = os.Link(path.Join("data", "deps"), path.Join("data", "scripts", "deps"))
_ = os.MkdirAll(path.Join("data", "log"), 0666)
_ = os.MkdirAll(path.Join("data", "repo"), 0666)
_ = os.MkdirAll(path.Join("data", "scripts"), 0666)
_ = os.MkdirAll(path.Join("data", "deps"), 0666)
_ = os.MkdirAll(path.Join("data", "raw"), 0666)
_ = os.WriteFile(path.Join("data", "config", "config.sh"), sample, 0666)
_ = os.WriteFile(path.Join("data", "scripts", "package.json"), pack, 0666)
_ = os.WriteFile(path.Join("data", "config", "config.sample.sh"), sample, 0666)
type Req struct {
UserName string `json:"username"`
Password string `json:"password"`
}
r := new(Req)
err := ctx.ShouldBindJSON(r)
if err != nil {
ctx.JSON(503, res.ErrMessage(503, err.Error()))
return
}
err = auth.InitAuthInfo(r.UserName, r.Password)
if err != nil {
ctx.JSON(503, res.Err(503, err))
return
}
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 {
ctx.JSON(503, res.Err(503, err))
return
}
authInfo, err := auth.GetAuthInfo()
if err != nil {
ctx.JSON(503, res.Err(503, err))
return
}
if authInfo.IsTwoFactorChecking {
ctx.JSON(200, res.Err(420, errors.New("")))
return
}
if authInfo.Username == r.UserName && authInfo.Password == r.Password {
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 {
authInfo.Tokens.Mobile = token
authInfo.Token = token
err := auth.UpdateAuthInfo(authInfo)
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 {
authInfo.Tokens.Desktop = token
authInfo.Token = token
err := auth.UpdateAuthInfo(authInfo)
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(),
}))
}
} else {
ctx.JSON(400, res.ErrMessage(400, "账号密码错误!"))
}
}
}