From 36a0a2c78a2cab92d683aae7ee61a3f912970415 Mon Sep 17 00:00:00 2001 From: Project_IO Date: Sat, 22 Mar 2025 12:52:22 +0900 Subject: [PATCH] feat: private directory (not complete) --- app.go | 14 +-- go.mod | 1 + go.sum | 2 + internal/middleware/auth.go | 3 +- internal/routes/auth.go | 215 ++++++++++++++++++------------------ internal/routes/mod.go | 159 +++++++++++++------------- internal/service/auth.go | 2 +- internal/service/priv.go | 90 +++++++++++++++ 8 files changed, 295 insertions(+), 191 deletions(-) create mode 100644 internal/service/priv.go diff --git a/app.go b/app.go index 52d9581..0c3d3ee 100644 --- a/app.go +++ b/app.go @@ -46,11 +46,11 @@ func main() { // init auth module service.NewAuthService() - gin := gin.Default() - routes.New(gin, ver, apiOnly) + app := gin.Default() + routes.New(app, ver, apiOnly) fmt.Fprintf(os.Stdout, "binding server at: http://0.0.0.0:%d\n", cnf.Port) - if err := gin.Run(fmt.Sprintf(":%d", cnf.Port)); err != nil { + if err = app.Run(fmt.Sprintf(":%d", cnf.Port)); err != nil { return err } @@ -81,7 +81,7 @@ func main() { } fmt.Print("new password: ") - bytePassword, err := term.ReadPassword(int(syscall.Stdin)) + bytePassword, err := term.ReadPassword(syscall.Stdin) if err != nil { return fmt.Errorf("failed to read password: %v", err) } @@ -89,7 +89,7 @@ func main() { fmt.Println() fmt.Print("type new password one more time: ") - checkByte, err := term.ReadPassword(int(syscall.Stdin)) + checkByte, err := term.ReadPassword(syscall.Stdin) if err != nil { return fmt.Errorf("failed to read password: %v", err) } @@ -101,7 +101,7 @@ func main() { } auth := service.NewAuthService() - if err := auth.Create(&service.Account{Username: username, Password: password}); err != nil { + if err = auth.Create(&service.Account{Username: username, Password: password}); err != nil { return err } @@ -111,7 +111,7 @@ func main() { }) if err := command.Execute(); err != nil { - fmt.Fprintf(os.Stderr, "%v\n", err) + _, _ = fmt.Fprintf(os.Stderr, "%v\n", err) os.Exit(1) } } diff --git a/go.mod b/go.mod index fbfae02..767e59e 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,7 @@ require ( github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.25.0 // indirect github.com/goccy/go-json v0.10.5 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.10 // indirect github.com/kr/text v0.2.0 // indirect diff --git a/go.sum b/go.sum index 10109e1..0502d04 100644 --- a/go.sum +++ b/go.sum @@ -33,6 +33,8 @@ github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PU github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= diff --git a/internal/middleware/auth.go b/internal/middleware/auth.go index 05b092c..d82657c 100644 --- a/internal/middleware/auth.go +++ b/internal/middleware/auth.go @@ -14,11 +14,12 @@ func BasicAuth(ctx *gin.Context) { var list = []string{"/settings"} for _, i := range list { - if !strings.Contains(ctx.Request.URL.Path, i) { + if !strings.HasPrefix(ctx.Request.URL.Path, i) { continue } matches = true + break } if !matches { diff --git a/internal/routes/auth.go b/internal/routes/auth.go index 03f11c1..737bac8 100644 --- a/internal/routes/auth.go +++ b/internal/routes/auth.go @@ -9,116 +9,121 @@ import ( ) func authentication(group *gin.RouterGroup) { - group.POST("/login", func(ctx *gin.Context) { - auth := service.NewAuthService() - username := ctx.PostForm("username") - password := ctx.PostForm("password") + group.POST("/login", login) + group.GET("/read", readAcc) + group.PATCH("/update", updateAcc) + group.DELETE("/delete", deleteAcc) +} - acc, err := auth.Read(username) - if err != nil { - ctx.JSON(401, gin.H{ - "ok": 0, - "errno": "username or password not invalid", - }) - return - } +func login(ctx *gin.Context) { + auth := service.NewAuthService() + username := ctx.PostForm("username") + password := ctx.PostForm("password") - ok, err := auth.Verify(username, password) - if err != nil || !ok { - ctx.JSON(401, gin.H{ - "ok": 0, - "errno": "username or password not invalid", - }) - return - } - - ctx.JSON(200, gin.H{ - "ok": 1, - "token": auth.Token(acc.Username, acc.Password), + acc, err := auth.Read(username) + if err != nil { + ctx.JSON(401, gin.H{ + "ok": 0, + "errno": "username or password not invalid", }) - }) + return + } - group.GET("/read", func(ctx *gin.Context) { - auth := service.NewAuthService() - username, password, ok := ctx.Request.BasicAuth() - if !ok { - ctx.Status(401) - return - } - - ok, err := auth.VerifyToken(username, password) - if err != nil { - ctx.JSON(500, gin.H{ - "ok": 0, - "errno": "internal server error!", - }) - } - - if !ok { - ctx.JSON(401, gin.H{ - "ok": 0, - "errno": "unauthorized", - }) - return - } - - ctx.JSON(200, gin.H{ - "ok": 1, - "username": username, + ok, err := auth.Verify(username, password) + if err != nil || !ok { + ctx.JSON(401, gin.H{ + "ok": 0, + "errno": "username or password not invalid", }) - }) + return + } - group.PATCH("/update", func(ctx *gin.Context) { - auth := service.NewAuthService() - old := ctx.PostForm("password") - new := ctx.PostForm("new_password") - username, _, ok := ctx.Request.BasicAuth() - if !ok { - ctx.Status(401) - return - } - - ok, err := auth.Verify(username, old) - if err != nil || !ok { - ctx.Status(401) - return - } - - if err = auth.Update(username, new); err != nil { - ctx.Status(500) - _, _ = fmt.Fprintln(os.Stderr, err) - return - } - - ctx.Status(200) - }) - - group.DELETE("/delete", func(ctx *gin.Context) { - auth := service.NewAuthService() - username, password, ok := ctx.Request.BasicAuth() - if !ok { - ctx.Status(401) - return - } - - ok, err := auth.VerifyToken(username, password) - if err != nil { - ctx.Status(500) - _, _ = fmt.Fprintln(os.Stderr, err) - return - } - - if !ok { - ctx.Status(401) - return - } - - if err = auth.Delete(username); err != nil { - ctx.Status(500) - _, _ = fmt.Fprintln(os.Stderr, err) - return - } - - ctx.Status(200) + ctx.JSON(200, gin.H{ + "ok": 1, + "token": auth.Token(acc.Username, acc.Password), }) } + +func readAcc(ctx *gin.Context) { + auth := service.NewAuthService() + username, password, ok := ctx.Request.BasicAuth() + if !ok { + ctx.Status(401) + return + } + + ok, err := auth.VerifyToken(username, password) + if err != nil { + ctx.JSON(500, gin.H{ + "ok": 0, + "errno": "internal server error!", + }) + } + + if !ok { + ctx.JSON(401, gin.H{ + "ok": 0, + "errno": "unauthorized", + }) + return + } + + ctx.JSON(200, gin.H{ + "ok": 1, + "username": username, + }) +} + +func updateAcc(ctx *gin.Context) { + auth := service.NewAuthService() + old := ctx.PostForm("password") + new := ctx.PostForm("new_password") + username, _, ok := ctx.Request.BasicAuth() + if !ok { + ctx.Status(401) + return + } + + ok, err := auth.Verify(username, old) + if err != nil || !ok { + ctx.Status(401) + return + } + + if err = auth.Update(username, new); err != nil { + ctx.Status(500) + _, _ = fmt.Fprintln(os.Stderr, err) + return + } + + ctx.Status(200) +} + +func deleteAcc(ctx *gin.Context) { + auth := service.NewAuthService() + username, password, ok := ctx.Request.BasicAuth() + if !ok { + ctx.Status(401) + return + } + + ok, err := auth.VerifyToken(username, password) + if err != nil { + ctx.Status(500) + _, _ = fmt.Fprintln(os.Stderr, err) + return + } + + if !ok { + ctx.Status(401) + return + } + + if err = auth.Delete(username); err != nil { + ctx.Status(500) + _, _ = fmt.Fprintln(os.Stderr, err) + return + } + + ctx.Status(200) +} diff --git a/internal/routes/mod.go b/internal/routes/mod.go index 37cf8b4..cc26daa 100644 --- a/internal/routes/mod.go +++ b/internal/routes/mod.go @@ -16,86 +16,16 @@ func New(app *gin.Engine, version *service.Version, apiOnly bool) { app.Use(middleware.BasicAuth) api := app.Group("/api") - { - api.GET("/path/*path", func(ctx *gin.Context) { - worker := service.NewWorkerService() - path := ctx.Param("path") - data, err := worker.Read(path) - if err != nil { - _, _ = fmt.Fprintf(os.Stderr, "%v\n", err) - ctx.Status(404) - return - } + api.GET("/path/*path", readPath) + api.GET("/download/*path", downloadPath) - if !data.IsDir { - ctx.JSON(200, gin.H{ - "ok": 1, - "path": path, - "total": data.FileSize, - "is_dir": false, - "entries": nil, - }) - return - } + api.POST("/private") - raw, err := os.ReadDir(data.Path) - if err != nil { - _, _ = fmt.Fprintf(os.Stderr, "%v\n", err) - ctx.Status(500) - return - } + authentication(api.Group("/auth")) - entries := make([]service.DirEntry, 0) - for _, entry := range raw { - finfo, err := entry.Info() - if err != nil { - fmt.Fprintf(os.Stderr, "%v\n", err) - continue - } - - entries = append(entries, service.DirEntry{ - Name: entry.Name(), - Path: filepath.Join(path, entry.Name()), - Date: finfo.ModTime().Unix(), - FileSize: uint64(finfo.Size()), - IsDir: finfo.IsDir(), - }) - } - - ctx.JSON(200, gin.H{ - "ok": 1, - "path": path, - "total": data.FileSize, - "is_dir": true, - "entries": entries, - }) - }) - - api.GET("/download/*path", func(ctx *gin.Context) { - worker := service.NewWorkerService() - path := ctx.Param("path") - data, err := worker.Read(path) - if err != nil { - _, _ = fmt.Fprintf(os.Stderr, "%v\n", err) - ctx.Status(404) - return - } - - if data.IsDir { - ctx.String(400, "current path is not file") - return - } - - ctx.FileAttachment(data.Path, data.Name) - }) - - auth := api.Group("/auth") - authentication(auth) - - api.GET("/version", func(ctx *gin.Context) { - ctx.String(200, "%s", version.String()) - }) - } + api.GET("/version", func(ctx *gin.Context) { + ctx.String(200, "%s", version.String()) + }) if apiOnly { return @@ -112,3 +42,78 @@ func New(app *gin.Engine, version *service.Version, apiOnly bool) { ctx.File("/web/assets/favicon.ico") }) } + +func readPath(ctx *gin.Context) { + worker := service.NewWorkerService() + path := ctx.Param("path") + // TODO: prefix detect + // if strings.HasPrefix(path, "") + + data, err := worker.Read(path) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "%v\n", err) + ctx.Status(404) + return + } + + if !data.IsDir { + ctx.JSON(200, gin.H{ + "ok": 1, + "path": path, + "total": data.FileSize, + "is_dir": false, + "entries": nil, + }) + return + } + + raw, err := os.ReadDir(data.Path) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "%v\n", err) + ctx.Status(500) + return + } + + entries := make([]service.DirEntry, 0) + for _, entry := range raw { + finfo, err := entry.Info() + if err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + continue + } + + entries = append(entries, service.DirEntry{ + Name: entry.Name(), + Path: filepath.Join(path, entry.Name()), + Date: finfo.ModTime().Unix(), + FileSize: uint64(finfo.Size()), + IsDir: finfo.IsDir(), + }) + } + + ctx.JSON(200, gin.H{ + "ok": 1, + "path": path, + "total": data.FileSize, + "is_dir": true, + "entries": entries, + }) +} + +func downloadPath(ctx *gin.Context) { + worker := service.NewWorkerService() + path := ctx.Param("path") + data, err := worker.Read(path) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "%v\n", err) + ctx.Status(404) + return + } + + if data.IsDir { + ctx.String(400, "current path is not file") + return + } + + ctx.FileAttachment(data.Path, data.Name) +} diff --git a/internal/service/auth.go b/internal/service/auth.go index bce0fd9..a34b3ea 100644 --- a/internal/service/auth.go +++ b/internal/service/auth.go @@ -35,7 +35,7 @@ func init() { username varchar(25), password varchar(255), salt varchar(50), - primary key (username) + constraint PK_Account_ID primary key(username) ); `)) if err != nil { diff --git a/internal/service/priv.go b/internal/service/priv.go new file mode 100644 index 0000000..d97e309 --- /dev/null +++ b/internal/service/priv.go @@ -0,0 +1,90 @@ +package service + +import ( + "fmt" + "github.com/google/uuid" + "os" + "strings" +) + +type PrivDirService struct{} + +type PrivDir struct { + Id string `json:"id"` + DirName string `json:"dirname"` + Owner string `json:"owner"` +} + +func init() { + db, err := Open() + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "%v\n", err) + return + } + defer db.Close() + + stmt, err := db.Prepare(strings.TrimSpace(` + create table PrivDir( + id varchar(36), + dirname varchar(250) unique, + owner varchar(25), + constraint PK_PrivDir_ID primary key(id), + constraint FK_Owner_ID foreign key(owner) + references(Account.username) on update cascade on delete cascade + ); + `)) + if err != nil { + return + } + defer stmt.Close() + + if _, err = stmt.Exec(); err != nil { + _, _ = fmt.Fprintf(os.Stderr, "%v\n", err) + return + } +} + +func NewPrivDirService() *PrivDirService { + return &PrivDirService{} +} + +func (sv *PrivDirService) CreatePriv(dirname string, acc *Account) error { + db, err := Open() + if err != nil { + return err + } + defer db.Close() + + id := uuid.NewString() + stmt, err := db.Prepare("insert into PrivDir(id, name, owner) values (?, ?, ?);") + if err != nil { + return err + } + defer stmt.Close() + + _, err = stmt.Exec(id, dirname, acc.Username) + return nil +} + +func (sv *PrivDirService) ReadPriv(name string) (*PrivDir, error) { + db, err := Open() + if err != nil { + return nil, err + } + defer db.Close() + + stmt, err := db.Prepare("select * from PrivDir where name = ?;") + if err != nil { + return nil, err + } + defer stmt.Close() + + row := stmt.QueryRow(name) + var data PrivDir + + if err = row.Scan(&data.Id, &data.DirName, &data.Owner); err != nil { + return nil, err + } + + return &data, nil +}