| 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"` |
| } |
|
|
| |
| 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) |
| } |
|
|
| |
| 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) { |
| |
| 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 |
| } |
| |
| user, err := op.GetUserByName(req.Username) |
| if err != nil { |
| common.ErrorResp(c, err, 400) |
| loginCache.Set(ip, count+1) |
| return |
| } |
| |
| if err := user.ValidatePwdStaticHash(req.Password); err != nil { |
| common.ErrorResp(c, err, 400) |
| loginCache.Set(ip, count+1) |
| return |
| } |
| |
| if user.OtpSecret != "" { |
| if !totp.Validate(req.OtpCode, user.OtpSecret) { |
| common.ErrorStrResp(c, "Invalid 2FA code", 402) |
| loginCache.Set(ip, count+1) |
| return |
| } |
| } |
| |
| 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"` |
| } |
|
|
| |
| |
| 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 |
| } |
| |
| 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) |
| } |
| } |
|
|