From d89ec89d51a73fd4545be9ea6d80d39891c2451b Mon Sep 17 00:00:00 2001 From: Noah Hsu Date: Tue, 28 Jun 2022 15:12:40 +0800 Subject: [PATCH] feat: sign of file --- internal/bootstrap/data/setting.go | 1 + internal/sign/sign.go | 30 +++++++++++++++++ pkg/sign/hmac.go | 52 ++++++++++++++++++++++++++++++ pkg/sign/sign.go | 15 +++++++++ server/controllers/fsget.go | 11 ++++--- server/controllers/fslist.go | 19 ++++++++--- server/controllers/setting.go | 2 ++ server/router.go | 2 +- 8 files changed, 122 insertions(+), 10 deletions(-) create mode 100644 internal/sign/sign.go create mode 100644 pkg/sign/hmac.go create mode 100644 pkg/sign/sign.go diff --git a/internal/bootstrap/data/setting.go b/internal/bootstrap/data/setting.go index a04b57fa..10de9e68 100644 --- a/internal/bootstrap/data/setting.go +++ b/internal/bootstrap/data/setting.go @@ -88,6 +88,7 @@ func initialSettings() { {Key: "global_readme", Value: "This is global readme", Type: conf.TypeText, Group: model.GLOBAL}, {Key: "customize_head", Type: conf.TypeText, Group: model.GLOBAL, Flag: model.PRIVATE}, {Key: "customize_body", Type: conf.TypeText, Group: model.GLOBAL, Flag: model.PRIVATE}, + {Key: "link_expiration", Value: "4", Type: conf.TypeNumber, Group: model.GLOBAL, Flag: model.PRIVATE}, // single settings {Key: "token", Value: token, Type: conf.TypeString, Group: model.SINGLE, Flag: model.PRIVATE}, } diff --git a/internal/sign/sign.go b/internal/sign/sign.go new file mode 100644 index 00000000..a8fb6823 --- /dev/null +++ b/internal/sign/sign.go @@ -0,0 +1,30 @@ +package sign + +import ( + "github.com/alist-org/alist/v3/internal/setting" + "github.com/alist-org/alist/v3/pkg/sign" + "sync" + "time" +) + +var once sync.Once +var instance sign.Sign + +func WithDuration(data string, d time.Duration) string { + once.Do(Instance) + return instance.Sign(data, time.Now().Add(d).Unix()) +} + +func NotExpired(data string) string { + once.Do(Instance) + return instance.Sign(data, 0) +} + +func Verify(data string, sign string) error { + once.Do(Instance) + return instance.Verify(data, sign) +} + +func Instance() { + instance = sign.NewHMACSign([]byte(setting.GetByKey("token"))) +} diff --git a/pkg/sign/hmac.go b/pkg/sign/hmac.go new file mode 100644 index 00000000..8d7f736b --- /dev/null +++ b/pkg/sign/hmac.go @@ -0,0 +1,52 @@ +package sign + +import ( + "crypto/hmac" + "crypto/sha256" + "encoding/base64" + "io" + "strconv" + "strings" + "time" +) + +type HMACSign struct { + SecretKey []byte +} + +func (s HMACSign) Sign(data string, expire int64) string { + h := hmac.New(sha256.New, s.SecretKey) + expireTimeStamp := strconv.FormatInt(expire, 10) + _, err := io.WriteString(h, data+":"+expireTimeStamp) + if err != nil { + return "" + } + + return base64.URLEncoding.EncodeToString(h.Sum(nil)) + ":" + expireTimeStamp +} + +func (s HMACSign) Verify(data, sign string) error { + signSlice := strings.Split(sign, ":") + // check whether contains expire time + if signSlice[len(signSlice)-1] == "" { + return ErrExpireMissing + } + // check whether expire time is expired + expires, err := strconv.ParseInt(signSlice[len(signSlice)-1], 10, 64) + if err != nil { + return ErrExpireInvalid + } + // if expire time is expired, return error + if expires < time.Now().Unix() && expires != 0 { + return ErrSignExpired + } + // verify sign + if s.Sign(data, expires) != sign { + return ErrSignInvalid + } + return nil +} + +func NewHMACSign(secret []byte) Sign { + return HMACSign{SecretKey: secret} +} diff --git a/pkg/sign/sign.go b/pkg/sign/sign.go new file mode 100644 index 00000000..2a286677 --- /dev/null +++ b/pkg/sign/sign.go @@ -0,0 +1,15 @@ +package sign + +import "errors" + +type Sign interface { + Sign(data string, expire int64) string + Verify(data, sign string) error +} + +var ( + ErrSignExpired = errors.New("sign expired") + ErrSignInvalid = errors.New("sign invalid") + ErrExpireInvalid = errors.New("expire invalid") + ErrExpireMissing = errors.New("expire missing") +) diff --git a/server/controllers/fsget.go b/server/controllers/fsget.go index 85c59e32..e90f28e6 100644 --- a/server/controllers/fsget.go +++ b/server/controllers/fsget.go @@ -33,17 +33,18 @@ func FsGet(c *gin.Context) { common.ErrorStrResp(c, "password is incorrect", 401) return } - data, err := fs.Get(c, req.Path) + obj, err := fs.Get(c, req.Path) if err != nil { common.ErrorResp(c, err, 500, true) return } common.SuccessResp(c, FsGetResp{ ObjResp: ObjResp{ - Name: data.GetName(), - Size: data.GetSize(), - IsDir: data.IsDir(), - Modified: data.ModTime(), + Name: obj.GetName(), + Size: obj.GetSize(), + IsDir: obj.IsDir(), + Modified: obj.ModTime(), + Sign: Sign(obj), }, // TODO: set raw url }) diff --git a/server/controllers/fslist.go b/server/controllers/fslist.go index faeab084..0683a35e 100644 --- a/server/controllers/fslist.go +++ b/server/controllers/fslist.go @@ -1,11 +1,11 @@ package controllers import ( - "fmt" "github.com/alist-org/alist/v3/internal/db" "github.com/alist-org/alist/v3/internal/fs" "github.com/alist-org/alist/v3/internal/model" "github.com/alist-org/alist/v3/internal/setting" + "github.com/alist-org/alist/v3/internal/sign" "github.com/alist-org/alist/v3/pkg/utils" "github.com/alist-org/alist/v3/server/common" "github.com/gin-gonic/gin" @@ -24,7 +24,7 @@ type ObjResp struct { Size int64 `json:"size"` IsDir bool `json:"is_dir"` Modified time.Time `json:"modified"` - URL string `json:"url"` + Sign string `json:"sign"` } type FsListResp struct { @@ -99,9 +99,20 @@ func toObjResp(objs []model.Obj, path string, baseURL string) []ObjResp { Size: obj.GetSize(), IsDir: obj.IsDir(), Modified: obj.ModTime(), - // TODO: sign url - URL: fmt.Sprintf("%s/d%s", baseURL, stdpath.Join(path, obj.GetName())), + Sign: Sign(obj), }) } return resp } + +func Sign(obj model.Obj) string { + if obj.IsDir() { + return "" + } + expire := setting.GetIntSetting("link_expiration", 0) + if expire == 0 { + return sign.NotExpired(obj.GetName()) + } else { + return sign.WithDuration(obj.GetName(), time.Duration(expire)*time.Hour) + } +} diff --git a/server/controllers/setting.go b/server/controllers/setting.go index 3582ef52..9dc7df42 100644 --- a/server/controllers/setting.go +++ b/server/controllers/setting.go @@ -4,6 +4,7 @@ import ( "github.com/alist-org/alist/v3/internal/conf" "github.com/alist-org/alist/v3/internal/db" "github.com/alist-org/alist/v3/internal/model" + "github.com/alist-org/alist/v3/internal/sign" "github.com/alist-org/alist/v3/pkg/utils/random" "github.com/alist-org/alist/v3/server/common" "github.com/gin-gonic/gin" @@ -17,6 +18,7 @@ func ResetToken(c *gin.Context) { common.ErrorResp(c, err, 500) return } + sign.Instance() common.SuccessResp(c, token) } diff --git a/server/router.go b/server/router.go index f527c646..1403a6bc 100644 --- a/server/router.go +++ b/server/router.go @@ -52,7 +52,7 @@ func Init(r *gin.Engine) { public := api.Group("/public") public.GET("/settings", controllers.PublicSettings) public.Any("/list", controllers.FsList) - public.GET("/get", controllers.FsGet) + public.Any("/get", controllers.FsGet) } func Cors(r *gin.Engine) {