Spaces:
Running
Running
package handles | |
import ( | |
"bytes" | |
"encoding/base64" | |
"image/png" | |
"time" | |
"github.com/Xhofe/go-cache" | |
"github.com/alist-org/alist/v3/internal/model" | |
"github.com/alist-org/alist/v3/internal/op" | |
"github.com/alist-org/alist/v3/server/common" | |
"github.com/gin-gonic/gin" | |
"github.com/pquerna/otp/totp" | |
) | |
var loginCache = cache.NewMemCache[int]() | |
var ( | |
defaultDuration = time.Minute * 5 | |
defaultTimes = 5 | |
) | |
type LoginReq struct { | |
Username string `json:"username" binding:"required"` | |
Password string `json:"password"` | |
OtpCode string `json:"otp_code"` | |
} | |
// Login Deprecated | |
func Login(c *gin.Context) { | |
var req LoginReq | |
if err := c.ShouldBind(&req); err != nil { | |
common.ErrorResp(c, err, 400) | |
return | |
} | |
req.Password = model.StaticHash(req.Password) | |
loginHash(c, &req) | |
} | |
// LoginHash login with password hashed by sha256 | |
func LoginHash(c *gin.Context) { | |
var req LoginReq | |
if err := c.ShouldBind(&req); err != nil { | |
common.ErrorResp(c, err, 400) | |
return | |
} | |
loginHash(c, &req) | |
} | |
func loginHash(c *gin.Context, req *LoginReq) { | |
// check count of login | |
ip := c.ClientIP() | |
count, ok := loginCache.Get(ip) | |
if ok && count >= defaultTimes { | |
common.ErrorStrResp(c, "Too many unsuccessful sign-in attempts have been made using an incorrect username or password, Try again later.", 429) | |
loginCache.Expire(ip, defaultDuration) | |
return | |
} | |
// check username | |
user, err := op.GetUserByName(req.Username) | |
if err != nil { | |
common.ErrorResp(c, err, 400) | |
loginCache.Set(ip, count+1) | |
return | |
} | |
// validate password hash | |
if err := user.ValidatePwdStaticHash(req.Password); err != nil { | |
common.ErrorResp(c, err, 400) | |
loginCache.Set(ip, count+1) | |
return | |
} | |
// check 2FA | |
if user.OtpSecret != "" { | |
if !totp.Validate(req.OtpCode, user.OtpSecret) { | |
common.ErrorStrResp(c, "Invalid 2FA code", 402) | |
loginCache.Set(ip, count+1) | |
return | |
} | |
} | |
// generate token | |
token, err := common.GenerateToken(user) | |
if err != nil { | |
common.ErrorResp(c, err, 400, true) | |
return | |
} | |
common.SuccessResp(c, gin.H{"token": token}) | |
loginCache.Del(ip) | |
} | |
type UserResp struct { | |
model.User | |
Otp bool `json:"otp"` | |
} | |
// CurrentUser get current user by token | |
// if token is empty, return guest user | |
func CurrentUser(c *gin.Context) { | |
user := c.MustGet("user").(*model.User) | |
userResp := UserResp{ | |
User: *user, | |
} | |
userResp.Password = "" | |
if userResp.OtpSecret != "" { | |
userResp.Otp = true | |
} | |
common.SuccessResp(c, userResp) | |
} | |
func UpdateCurrent(c *gin.Context) { | |
var req model.User | |
if err := c.ShouldBind(&req); err != nil { | |
common.ErrorResp(c, err, 400) | |
return | |
} | |
user := c.MustGet("user").(*model.User) | |
user.Username = req.Username | |
if req.Password != "" { | |
user.SetPassword(req.Password) | |
} | |
user.SsoID = req.SsoID | |
if err := op.UpdateUser(user); err != nil { | |
common.ErrorResp(c, err, 500) | |
} else { | |
common.SuccessResp(c) | |
} | |
} | |
func Generate2FA(c *gin.Context) { | |
user := c.MustGet("user").(*model.User) | |
if user.IsGuest() { | |
common.ErrorStrResp(c, "Guest user can not generate 2FA code", 403) | |
return | |
} | |
key, err := totp.Generate(totp.GenerateOpts{ | |
Issuer: "Alist", | |
AccountName: user.Username, | |
}) | |
if err != nil { | |
common.ErrorResp(c, err, 500) | |
return | |
} | |
img, err := key.Image(400, 400) | |
if err != nil { | |
common.ErrorResp(c, err, 500) | |
return | |
} | |
// to base64 | |
var buf bytes.Buffer | |
png.Encode(&buf, img) | |
b64 := base64.StdEncoding.EncodeToString(buf.Bytes()) | |
common.SuccessResp(c, gin.H{ | |
"qr": "data:image/png;base64," + b64, | |
"secret": key.Secret(), | |
}) | |
} | |
type Verify2FAReq struct { | |
Code string `json:"code" binding:"required"` | |
Secret string `json:"secret" binding:"required"` | |
} | |
func Verify2FA(c *gin.Context) { | |
var req Verify2FAReq | |
if err := c.ShouldBind(&req); err != nil { | |
common.ErrorResp(c, err, 400) | |
return | |
} | |
user := c.MustGet("user").(*model.User) | |
if user.IsGuest() { | |
common.ErrorStrResp(c, "Guest user can not generate 2FA code", 403) | |
return | |
} | |
if !totp.Validate(req.Code, req.Secret) { | |
common.ErrorStrResp(c, "Invalid 2FA code", 400) | |
return | |
} | |
user.OtpSecret = req.Secret | |
if err := op.UpdateUser(user); err != nil { | |
common.ErrorResp(c, err, 500) | |
} else { | |
common.SuccessResp(c) | |
} | |
} | |