From 24b7a28336b903b7a8fac3588e48e821081929ac Mon Sep 17 00:00:00 2001 From: RandomChars <random@chars.jp> Date: Thu, 2 Sep 2021 12:51:36 +0900 Subject: [PATCH] private mode --- api.go | 91 ++++++++++++++++++++++++++++++++++++++++++++++++ api/paths.go | 1 + client/remote.go | 19 +++++++--- config.go | 38 +++++++++++++++----- store/store.go | 5 ++- 5 files changed, 140 insertions(+), 14 deletions(-) diff --git a/api.go b/api.go index 5d78674..74ffd6d 100644 --- a/api.go +++ b/api.go @@ -28,11 +28,23 @@ func registerAPI() { context.JSON(http.StatusOK, instance.SingleUser) }) + router.GET(api.Private, func(context *gin.Context) { + context.JSON(http.StatusOK, instance.Private) + }) + router.GET(api.User, func(context *gin.Context) { + if !privateAccessible(context) { + return + } + context.JSON(http.StatusOK, instance.Users()) }) router.PUT(api.User, func(context *gin.Context) { + if !privateAccessible(context) { + return + } + user, ok := getUser(context) if !instance.Register { if !ok { @@ -73,6 +85,10 @@ func registerAPI() { }) router.GET(api.UserField, func(context *gin.Context) { + if !privateAccessible(context) { + return + } + info := instance.User(context.Param("flake")) context.JSON(http.StatusOK, api.UserPayload{ Username: info.Username, @@ -176,6 +192,10 @@ func registerAPI() { }) router.GET(api.UsernameField, func(context *gin.Context) { + if !privateAccessible(context) { + return + } + info := instance.UserUsername(context.Param("name")) payload := api.UserPayload{ Username: info.Username, @@ -212,6 +232,7 @@ func registerAPI() { return } + // Only allow lookup if user is current user or privileged flake := context.Param("flake") if !info.Privileged && (info.Snowflake != flake) { context.JSON(http.StatusForbidden, api.Denied) @@ -229,6 +250,7 @@ func registerAPI() { return } + // Only allow set if user is current user or privileged flake := context.Param("flake") if !info.Privileged && (info.Snowflake != flake) { context.JSON(http.StatusForbidden, api.Denied) @@ -238,10 +260,18 @@ func registerAPI() { }) router.GET(api.UserImage, func(context *gin.Context) { + if !privateAccessible(context) { + return + } + context.JSON(http.StatusOK, instance.UserImages(context.Param("flake"))) }) router.GET(api.SearchField, func(context *gin.Context) { + if !privateAccessible(context) { + return + } + tagsPayload := context.Param("tags") tags := strings.Split(tagsPayload, "!") context.JSON(http.StatusOK, instance.ImageSearch(tags)) @@ -294,10 +324,18 @@ func registerAPI() { }) router.GET(api.ImagePage, func(context *gin.Context) { + if !privateAccessible(context) { + return + } + context.String(http.StatusOK, strconv.Itoa(instance.PageTotal(store.ImageRootPageVariant))) }) router.GET(api.ImagePageField, func(context *gin.Context) { + if !privateAccessible(context) { + return + } + param := context.Param("entry") entry, err := strconv.Atoi(param) if err != nil { @@ -308,6 +346,10 @@ func registerAPI() { }) router.GET(api.ImagePageImage, func(context *gin.Context) { + if !privateAccessible(context) { + return + } + param := context.Param("entry") entry, err := strconv.Atoi(param) if err != nil { @@ -318,6 +360,10 @@ func registerAPI() { }) router.GET(api.ImageField, func(context *gin.Context) { + if !privateAccessible(context) { + return + } + context.JSON(http.StatusOK, instance.ImageSnowflake(context.Param("flake"))) }) @@ -364,6 +410,10 @@ func registerAPI() { }) router.GET(api.ImageFile, func(context *gin.Context) { + if !privateAccessible(context) { + return + } + flake := context.Param("flake") image, data := instance.ImageData(instance.ImageSnowflakeHash(flake), false) if image.Snowflake != flake { @@ -374,6 +424,10 @@ func registerAPI() { }) router.GET(api.ImagePreview, func(context *gin.Context) { + if !privateAccessible(context) { + return + } + flake := context.Param("flake") image, data := instance.ImageData(instance.ImageSnowflakeHash(flake), true) if image.Snowflake != flake { @@ -384,6 +438,10 @@ func registerAPI() { }) router.GET(api.ImageTag, func(context *gin.Context) { + if !privateAccessible(context) { + return + } + context.JSON(http.StatusOK, instance.ImageTags(context.Param("flake"))) }) @@ -422,6 +480,10 @@ func registerAPI() { }) router.GET(api.Tag, func(context *gin.Context) { + if !privateAccessible(context) { + return + } + context.JSON(http.StatusOK, instance.Tags()) }) @@ -474,6 +536,10 @@ func registerAPI() { }) router.GET(api.TagPage, func(context *gin.Context) { + if !privateAccessible(context) { + return + } + tag := context.Param("tag") if !instance.MatchName(tag) { context.JSON(http.StatusBadRequest, api.Denied) @@ -483,6 +549,10 @@ func registerAPI() { }) router.GET(api.TagPageField, func(context *gin.Context) { + if !privateAccessible(context) { + return + } + tag := context.Param("tag") if !instance.MatchName(tag) { context.JSON(http.StatusBadRequest, api.Denied) @@ -499,6 +569,10 @@ func registerAPI() { }) router.GET(api.TagPageImage, func(context *gin.Context) { + if !privateAccessible(context) { + return + } + tag := context.Param("tag") if !instance.MatchName(tag) { context.JSON(http.StatusBadRequest, api.Denied) @@ -515,6 +589,10 @@ func registerAPI() { }) router.GET(api.TagInfo, func(context *gin.Context) { + if !privateAccessible(context) { + return + } + context.JSON(http.StatusOK, instance.TagInfo(context.Param("tag"))) }) @@ -541,6 +619,19 @@ func registerAPI() { }) } +func privateAccessible(context *gin.Context) bool { + if !instance.Private { + return true + } + + if _, ok := getUser(context); !ok { + context.JSON(http.StatusForbidden, api.Denied) + return false + } + + return true +} + func getUser(context *gin.Context) (store.User, bool) { if instance.SingleUser { return instance.User(instance.InitialUser), true diff --git a/api/paths.go b/api/paths.go index 13fd584..4caec4a 100644 --- a/api/paths.go +++ b/api/paths.go @@ -3,6 +3,7 @@ package api const ( Base = "/api" SingleUser = Base + "/single_user" + Private = Base + "/private" Image = Base + "/image" ImagePage = Image + "/page" ImagePageField = ImagePage + "/:entry" diff --git a/client/remote.go b/client/remote.go index 4f14410..4f3248a 100644 --- a/client/remote.go +++ b/client/remote.go @@ -9,11 +9,12 @@ import ( // Remote represents a remote image board server. type Remote struct { - url string - single bool - secret string - client *http.Client - user *api.UserPayload + url string + single bool + private bool + secret string + client *http.Client + user *api.UserPayload store.Info } @@ -33,11 +34,19 @@ func (r *Remote) SingleUser() bool { return r.single } +// Private returns whether the Remote is running in private mode. +func (r *Remote) Private() bool { + return r.private +} + // Handshake checks if the server is still online and updates Remote. func (r *Remote) Handshake() error { if err := r.fetch(http.MethodGet, api.SingleUser, &r.single, nil); err != nil { return err } + if err := r.fetch(http.MethodGet, api.Private, &r.private, nil); err != nil { + return err + } return r.fetch(http.MethodGet, api.Base, r, nil) } diff --git a/config.go b/config.go index cbe36a1..58867fa 100644 --- a/config.go +++ b/config.go @@ -5,6 +5,7 @@ import ( log "github.com/sirupsen/logrus" "github.com/spf13/viper" "random.chars.jp/git/image-board/store" + "strconv" ) var instance *store.Store @@ -23,6 +24,7 @@ func configSetup() { "loglevel": "info", "store": "./db", "single-user": true, + "private": false, }) viper.SetDefault("server", map[string]interface{}{ "host": "127.0.0.1", @@ -58,7 +60,8 @@ func configSetup() { if viper.GetStringMap("server")["unix"] != serverConfig["unix"] || viper.GetStringMap("server")["host"] != serverConfig["host"] || viper.GetStringMap("server")["port"] != serverConfig["port"] || - viper.GetStringMap("system")["single-user"] != systemConfig["single-user"] { + viper.GetStringMap("system")["single-user"] != systemConfig["single-user"] || + viper.GetStringMap("system")["private"] != systemConfig["private"] { log.Warn("Configuration change requires restart.") cleanup(true) return @@ -76,12 +79,11 @@ func configSetup() { } func openStore() { - path := viper.GetStringMap("system")["store"].(string) - single, ok := viper.GetStringMap("system")["single-user"].(bool) - if !ok { - single = false - } - instance = store.New(path, single) + path := systemConfig["store"].(string) + single := parseBool(systemConfig["single-user"]) + private := parseBool(systemConfig["private"]) + + instance = store.New(path, single, private) if instance == nil { log.Fatalf("Error initializing store.") } @@ -98,6 +100,26 @@ func openStore() { } } if single { - log.Infof("Server running in single user mode, all operations are performed as the initial user.") + log.Info("Server running in single user mode, all operations are performed as the initial user.") + } else if private { + log.Info("Server running in private mode, all operations will require authentication.") + } +} + +func parseBool(v interface{}) bool { + if s, ok := v.(bool); !ok { + var sS string + if sS, ok = v.(string); !ok { + return false + } else { + if b, err := strconv.ParseBool(sS); err != nil { + log.Warnf("Error parsing boolean value, %s", err) + return false + } else { + return b + } + } + } else { + return s } } diff --git a/store/store.go b/store/store.go index 6a5f8e1..b96489b 100644 --- a/store/store.go +++ b/store/store.go @@ -33,6 +33,7 @@ type Info struct { type Store struct { Path string SingleUser bool + Private bool Revision int Compat bool Register bool @@ -58,7 +59,7 @@ func init() { } // New initialises a new store instance. -func New(path string, single bool) *Store { +func New(path string, single, private bool) *Store { var store *Store if stat, err := os.Stat(path); err != nil { log.Infof("Initializing new store at %s.", path) @@ -66,6 +67,7 @@ func New(path string, single bool) *Store { store = &Store{ Path: path, SingleUser: single, + Private: private, Revision: revision, Compat: runtime.GOOS == "windows", Register: false, @@ -101,6 +103,7 @@ func New(path string, single bool) *Store { store = &Store{ Path: path, SingleUser: single, + Private: private, Revision: info.Revision, Compat: info.Compat, Register: info.Register, -- GitLab