diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..d984e85 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,34 @@ +run: + tests: false +linters: + enable: + - deadcode + - errcheck + - gosimple + - govet + - ineffassign + - staticcheck + - structcheck + - typecheck + - unused + - varcheck + - gosec + - asciicheck + - bidichk + - bodyclose + - gomnd + - ifshort + - misspell + - prealloc + - tagliatelle + - ireturn + - gocritic + - whitespace + - wsl + - stylecheck + - exportloopref + - godot +linters-settings: + gomnd: + ignored-functions: + - "strconv.ParseUint" \ No newline at end of file diff --git a/.woodpecker.yml b/.woodpecker.yml index 408651c..54f09d2 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -10,6 +10,13 @@ pipeline: branch: master event: [push, pull_request, tag, deployment] + lint: + image: golangci/golangci-lint:latest + commands: + - golangci-lint run + when: + event: [push, pull_request] + image-latest: image: plugins/docker settings: diff --git a/api/http.go b/api/http.go index c539636..ac53d30 100644 --- a/api/http.go +++ b/api/http.go @@ -18,6 +18,8 @@ import ( "go.uber.org/zap" ) +const multipartMaxMemory = 1024 * 1024 * 100 + type HTTPServer struct { Files files.FileStore Users users.UserStore @@ -41,8 +43,8 @@ func NewHTTPServer(cfg *gpaste.ServerConfig) *HTTPServer { // Create initial user // TODO: Do properly user := &users.User{Username: "admin", Role: users.RoleAdmin} - user.SetPassword("admin") - srv.Users.Store(user) + _ = user.SetPassword("admin") + _ = srv.Users.Store(user) r := chi.NewRouter() r.Use(middleware.RealIP) @@ -82,9 +84,12 @@ func (s *HTTPServer) HandlerAPIFilePost(w http.ResponseWriter, r *http.Request) if err != nil { w.WriteHeader(http.StatusInternalServerError) s.Logger.Warnw("Error storing file.", "req_id", reqID, "error", err, "id", f.ID, "remote_addr", r.RemoteAddr) + return } + s.Logger.Infow("Stored file.", "req_id", reqID, "id", f.ID, "remote_addr", r.RemoteAddr) + fileURL := path.Join(s.config.URL, "/api/file", f.ID) resp := &ResponseAPIFilePost{ Message: "OK", @@ -95,7 +100,9 @@ func (s *HTTPServer) HandlerAPIFilePost(w http.ResponseWriter, r *http.Request) }, }, } + w.WriteHeader(http.StatusAccepted) + encoder := json.NewEncoder(w) if err := encoder.Encode(&resp); err != nil { s.Logger.Warnw("Error encoding response to client.", "req_id", reqID, "error", err, "remote_addr", r.RemoteAddr) @@ -117,6 +124,7 @@ func (s *HTTPServer) HandlerAPIFileGet(w http.ResponseWriter, r *http.Request) { } w.WriteHeader(http.StatusOK) + if _, err := io.Copy(w, f.Body); err != nil { reqID := middleware.GetReqID(r.Context()) s.Logger.Warnw("Error writing file to client.", "req_id", reqID, "error", err, "remote_addr", r.RemoteAddr) @@ -136,6 +144,7 @@ func (s *HTTPServer) HandlerAPIFileDelete(w http.ResponseWriter, r *http.Request w.WriteHeader(http.StatusBadRequest) return } + reqID := middleware.GetReqID(r.Context()) s.Logger.Infow("Deleted file", "id", id, "req_id", reqID) } @@ -145,15 +154,17 @@ func (s *HTTPServer) processMultiPartFormUpload(w http.ResponseWriter, r *http.R var resp ResponseAPIFilePost - if err := r.ParseMultipartForm(1024 * 1024 * 10); err != nil { + if err := r.ParseMultipartForm(multipartMaxMemory); err != nil { s.Logger.Warnw("Error parsing multipart form.", "req_id", reqID, "err", err) } + for k := range r.MultipartForm.File { ff, fh, err := r.FormFile(k) if err != nil { s.Logger.Warnw("Error reading file from multipart form.", "req_id", reqID, "error", err) return } + f := fileFromParams(r) f.ID = uuid.NewString() f.OriginalFilename = fh.Filename @@ -162,18 +173,20 @@ func (s *HTTPServer) processMultiPartFormUpload(w http.ResponseWriter, r *http.R if err := s.Files.Store(f); err != nil { w.WriteHeader(http.StatusInternalServerError) s.Logger.Warnw("Error storing file.", "req_id", reqID, "error", err, "id", f.ID, "remote_addr", r.RemoteAddr) + return } + s.Logger.Infow("Stored file.", "req_id", reqID, "id", f.ID, "filename", f.OriginalFilename, "remote_addr", r.RemoteAddr) fileURL := path.Join(s.config.URL, "/api/file", f.ID) fileResponse := ResponseAPIFilePostFiles{ID: f.ID, URL: fileURL} resp.Files = append(resp.Files, fileResponse) - } w.WriteHeader(http.StatusAccepted) encoder := json.NewEncoder(w) + if err := encoder.Encode(&resp); err != nil { s.Logger.Warnw("Error encoding response to client.", "req_id", reqID, "error", err, "remote_addr", r.RemoteAddr) } @@ -181,9 +194,12 @@ func (s *HTTPServer) processMultiPartFormUpload(w http.ResponseWriter, r *http.R func (s *HTTPServer) HandlerAPILogin(w http.ResponseWriter, r *http.Request) { reqID := middleware.GetReqID(r.Context()) + var expectedRequest RequestAPILogin + decoder := json.NewDecoder(r.Body) defer r.Body.Close() + if err := decoder.Decode(&expectedRequest); err != nil { w.WriteHeader(http.StatusBadRequest) return @@ -218,10 +234,12 @@ func (s *HTTPServer) HandlerAPIUserCreate(w http.ResponseWriter, r *http.Request } var req RequestAPIUserCreate + decoder := json.NewDecoder(r.Body) if err := decoder.Decode(&req); err != nil { s.Logger.Debugw("Error parsing request.", "req_id", reqID, "error", err, "remote_addr", r.RemoteAddr) w.WriteHeader(http.StatusBadRequest) + return } @@ -230,14 +248,17 @@ func (s *HTTPServer) HandlerAPIUserCreate(w http.ResponseWriter, r *http.Request if err := user.SetPassword(req.Password); err != nil { s.Logger.Warnw("Error setting user password.", "req_id", reqID, "error", err, "remote_addr", r.RemoteAddr) w.WriteHeader(http.StatusBadRequest) + return } if err := s.Users.Store(user); err != nil { s.Logger.Warnw("Error setting user password.", "req_id", reqID, "error", err, "remote_addr", r.RemoteAddr) w.WriteHeader(http.StatusInternalServerError) + return } + w.WriteHeader(http.StatusAccepted) s.Logger.Infow("Created user.", "req_id", reqID, "remote_addr", r.RemoteAddr, "username", req.Username) } @@ -249,6 +270,7 @@ func (s *HTTPServer) HandlerAPIUserList(w http.ResponseWriter, r *http.Request) if err != nil { s.Logger.Warnw("Error listing users.", "req_id", reqID, "error", err) w.WriteHeader(http.StatusInternalServerError) + return } @@ -263,12 +285,13 @@ func fileFromParams(r *http.Request) *files.File { keyMaxViews = "max_views" keyExpiresOn = "exp" ) + var f files.File q := r.URL.Query() if q.Has(keyMaxViews) { - views, err := strconv.ParseUint(q.Get(keyMaxViews), 10, 64) + views, err := strconv.ParseUint(q.Get(keyMaxViews), 10, 64) // nolint: gomnd if err == nil { f.MaxViews = uint(views) } diff --git a/api/middleware.go b/api/middleware.go index 193cbce..de94064 100644 --- a/api/middleware.go +++ b/api/middleware.go @@ -39,31 +39,37 @@ func (s *HTTPServer) MiddlewareAccessLogger(next http.Handler) http.Handler { next.ServeHTTP(ww, r) } + return http.HandlerFunc(fn) } func (s *HTTPServer) MiddlewareAuthentication(next http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { reqID := middleware.GetReqID(r.Context()) + header := r.Header.Get("Authorization") if header == "" { s.Logger.Debugw("Request has no auth header.", "req_id", reqID) next.ServeHTTP(w, r) + return } splitHeader := strings.Split(header, "Bearer ") - if len(splitHeader) != 2 { + if len(splitHeader) != 2 { // nolint: gomnd s.Logger.Debugw("Request has invalid token.", "req_id", reqID) next.ServeHTTP(w, r) + return } + token := splitHeader[1] claims, err := s.Auth.ValidateToken(token) if err != nil { s.Logger.Debugw("Request has invalid token.", "req_id", reqID) next.ServeHTTP(w, r) + return } @@ -71,6 +77,7 @@ func (s *HTTPServer) MiddlewareAuthentication(next http.Handler) http.Handler { ctx = context.WithValue(ctx, authCtxAuthLevel, claims.Role) ctx = context.WithValue(ctx, authCtxClaims, claims) withCtx := r.WithContext(ctx) + s.Logger.Debugw("Request is authenticated.", "req_id", reqID, "username", claims.Subject, "role", claims.Role) next.ServeHTTP(w, withCtx) @@ -84,10 +91,12 @@ func UsernameFromRequest(r *http.Request) (string, error) { if rawUsername == nil { return "", fmt.Errorf("no username") } + username, ok := rawUsername.(string) if !ok { return "", fmt.Errorf("no username") } + return username, nil } @@ -96,10 +105,12 @@ func RoleFromRequest(r *http.Request) (users.Role, error) { if rawLevel == nil { return users.RoleUnset, fmt.Errorf("no username") } + level, ok := rawLevel.(users.Role) if !ok { return users.RoleUnset, fmt.Errorf("no username") } + return level, nil } @@ -108,9 +119,11 @@ func ClaimsFromRequest(r *http.Request) *gpaste.Claims { if rawClaims == nil { return nil } + claims, ok := rawClaims.(*gpaste.Claims) if !ok { return nil } + return claims } diff --git a/auth.go b/auth.go index 4b6cab6..f4e403b 100644 --- a/auth.go +++ b/auth.go @@ -44,6 +44,7 @@ func (as *AuthService) Login(username, password string) (string, error) { claims.Role = user.Role token := jwt.NewWithClaims(jwt.GetSigningMethod("HS256"), claims) + signed, err := token.SignedString(as.hmacSecret) if err != nil { return "", err @@ -57,9 +58,11 @@ func (as *AuthService) ValidateToken(rawToken string) (*Claims, error) { token, err := jwt.ParseWithClaims(rawToken, claims, func(t *jwt.Token) (interface{}, error) { return as.hmacSecret, nil }) + if err != nil { return nil, err } + if !token.Valid { return nil, fmt.Errorf("invalid token") } diff --git a/client/client.go b/client/client.go index c564554..18ba019 100644 --- a/client/client.go +++ b/client/client.go @@ -18,9 +18,11 @@ import ( "github.com/kirsle/configdir" ) +const defaultTimeout = 10 * time.Second + type Client struct { - BaseURL string `json:"base_url"` - AuthToken string `json:"auth_token"` + BaseURL string `json:"baseUrl"` + AuthToken string `json:"authToken"` httpClient http.Client } @@ -36,7 +38,9 @@ func (c *Client) WriteConfig() error { if err != nil { return err } + path := filepath.Join(dir, "client.json") + f, err := os.Create(path) if err != nil { return err @@ -49,6 +53,7 @@ func (c *Client) WriteConfig() error { func (c *Client) LoadConfig() error { dir := configdir.LocalCache("gpaste") path := filepath.Join(dir, "client.json") + f, err := os.Open(path) if err != nil { return err @@ -66,7 +71,7 @@ func (c *Client) LoadConfigFromReader(r io.Reader) error { func (c *Client) Login(ctx context.Context, username, password string) error { url := fmt.Sprintf("%s/api/login", c.BaseURL) // TODO: Change timeout - ctx, cancel := context.WithTimeout(ctx, 10*time.Second) + ctx, cancel := context.WithTimeout(ctx, defaultTimeout) defer cancel() body := new(bytes.Buffer) @@ -74,10 +79,12 @@ func (c *Client) Login(ctx context.Context, username, password string) error { Username: username, Password: password, } + encoder := json.NewEncoder(body) if err := encoder.Encode(&requestData); err != nil { return fmt.Errorf("error encoding response: %w", err) } + req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, body) if err != nil { return fmt.Errorf("error creating request: %w", err) @@ -99,6 +106,7 @@ func (c *Client) Login(ctx context.Context, username, password string) error { if err := decoder.Decode(&responseData); err != nil { return fmt.Errorf("unable to parse response: %s", err) } + c.AuthToken = responseData.Token return nil @@ -112,6 +120,7 @@ func (c *Client) UserCreate(ctx context.Context, username, password string) erro Username: username, Password: password, } + encoder := json.NewEncoder(body) if err := encoder.Encode(requestData); err != nil { return fmt.Errorf("error encoding response: %w", err) @@ -164,9 +173,6 @@ func (c *Client) Upload(ctx context.Context, files ...*files.File) (*api.Respons client := &http.Client{} // TODO: Change timeout - ctx, cancel := context.WithTimeout(ctx, 10*time.Minute) - defer cancel() - // TODO: Improve buffering buf := &bytes.Buffer{} mw := multipart.NewWriter(buf) @@ -176,16 +182,21 @@ func (c *Client) Upload(ctx context.Context, files ...*files.File) (*api.Respons if err != nil { return nil, err } + if _, err := io.Copy(fw, file.Body); err != nil { return nil, err } + file.Body.Close() } + mw.Close() + req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, buf) if err != nil { return nil, err } + req.Header.Add("Content-Type", mw.FormDataContentType()) resp, err := client.Do(req) @@ -213,10 +224,12 @@ func (c *Client) Delete(ctx context.Context, id string) error { } req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.AuthToken)) + resp, err := c.httpClient.Do(req) if err != nil { return fmt.Errorf("unable to perform request: %s", err) } + defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return fmt.Errorf("got non-ok response from server: %s", resp.Status) diff --git a/client/client_test.go b/client/client_test.go index 4b63369..e2c3058 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -166,7 +166,7 @@ func TestClient(t *testing.T) { }) t.Run("Save", func(t *testing.T) { c := client.Client{BaseURL: "http://example.org/gpaste", AuthToken: "tokenpls"} - expectedConfig := "{\"base_url\":\"http://example.org/gpaste\",\"auth_token\":\"tokenpls\"}\n" + expectedConfig := "{\"baseUrl\":\"http://example.org/gpaste\",\"authToken\":\"tokenpls\"}\n" buf := new(bytes.Buffer) err := c.WriteConfigToWriter(buf) if err != nil { @@ -179,7 +179,7 @@ func TestClient(t *testing.T) { }) t.Run("Load", func(t *testing.T) { c := client.Client{} - config := "{\"base_url\":\"http://pasta.example.org\",\"auth_token\":\"tokenpls\"}\n" + config := "{\"baseUrl\":\"http://pasta.example.org\",\"authToken\":\"tokenpls\"}\n" expectedClient := client.Client{BaseURL: "http://pasta.example.org", AuthToken: "tokenpls"} sr := strings.NewReader(config) if err := c.LoadConfigFromReader(sr); err != nil { diff --git a/cmd/client/actions/actions.go b/cmd/client/actions/actions.go index 4eb43a0..80dadd7 100644 --- a/cmd/client/actions/actions.go +++ b/cmd/client/actions/actions.go @@ -15,27 +15,34 @@ import ( "golang.org/x/term" ) +const defaultTimeout = 10 * time.Second + func ActionUpload(c *cli.Context) error { clnt := client.Client{ BaseURL: c.String("url"), } + for _, arg := range c.Args().Slice() { f, err := os.Open(arg) if err != nil { return err } defer f.Close() + file := &files.File{ OriginalFilename: arg, Body: f, } + resp, err := clnt.Upload(c.Context, file) if err != nil { errmsg := fmt.Sprintf("Error uploading file: %s", err) return cli.Exit(errmsg, 1) } + fmt.Printf("Uploaded file %s - %s", file.OriginalFilename, resp.Files[0].URL) } + return nil } @@ -43,15 +50,19 @@ func ActionDelete(c *cli.Context) error { clnt := client.Client{ BaseURL: c.String("url"), } + for _, arg := range c.Args().Slice() { - ctx, cancel := context.WithTimeout(c.Context, 5*time.Second) + ctx, cancel := context.WithTimeout(c.Context, defaultTimeout) defer cancel() + if err := clnt.Delete(ctx, arg); err != nil { fmt.Printf("Error deleting file %s\n", arg) fmt.Printf("%s\n", err) } + fmt.Printf("Deleted %s\n", arg) } + return nil } @@ -60,6 +71,7 @@ func ActionLogin(c *cli.Context) error { if username == "" { return cli.Exit("USERNAME not supplied.", 1) } + password, err := readPassword() if err != nil { return fmt.Errorf("error reading password: %w", err) @@ -72,6 +84,7 @@ func ActionLogin(c *cli.Context) error { errmsg := fmt.Sprintf("Error logging in: %s", err) return cli.Exit(errmsg, 1) } + if err := clnt.WriteConfig(); err != nil { errMsg := fmt.Sprintf("Failed to write config: %s", err) return cli.Exit(errMsg, 1) @@ -85,7 +98,9 @@ func ActionLogin(c *cli.Context) error { func ActionUserCreate(c *cli.Context) error { // TODO: Needs to supply auth token to actually work fmt.Println("Need to be logged in to create user") + username := readString("Enter username: ") + password, err := readPassword() if err != nil { return fmt.Errorf("error reading password: %w", err) @@ -94,7 +109,8 @@ func ActionUserCreate(c *cli.Context) error { clnt := client.Client{ BaseURL: c.String("url"), } - ctx, cancel := context.WithTimeout(c.Context, 10*time.Second) + + ctx, cancel := context.WithTimeout(c.Context, defaultTimeout) defer cancel() if err := clnt.Login(ctx, username, password); err != nil { @@ -103,7 +119,9 @@ func ActionUserCreate(c *cli.Context) error { } fmt.Println("User to create:") + username = readString("Enter username: ") + password, err = readPassword() if err != nil { return fmt.Errorf("error reading password: %w", err) @@ -121,20 +139,24 @@ func ActionUserCreate(c *cli.Context) error { func readPassword() (string, error) { fmt.Print("Enter Password: ") + bytePassword, err := term.ReadPassword(int(syscall.Stdin)) if err != nil { return "", err } password := string(bytePassword) + return strings.TrimSpace(password), nil } func readString(prompt string) string { fmt.Print(prompt) + scanner := bufio.NewScanner(os.Stdin) for scanner.Scan() { return scanner.Text() } + return "" } diff --git a/cmd/client/client.go b/cmd/client/client.go index 35e62c1..c82ffa4 100644 --- a/cmd/client/client.go +++ b/cmd/client/client.go @@ -64,5 +64,5 @@ func main() { }, } - app.Run(os.Args) + _ = app.Run(os.Args) } diff --git a/cmd/server/actions/actions.go b/cmd/server/actions/actions.go index c70f5f2..bc2d72a 100644 --- a/cmd/server/actions/actions.go +++ b/cmd/server/actions/actions.go @@ -22,8 +22,11 @@ func ActionServe(c *cli.Context) error { configPath = c.String("config") } - var cfg *gpaste.ServerConfig - var r io.ReadCloser + var ( + cfg *gpaste.ServerConfig + r io.ReadCloser + ) + r, err := os.Open(configPath) if err != nil { cfg = &gpaste.ServerConfig{ @@ -52,8 +55,10 @@ func ActionServe(c *cli.Context) error { // Setup contexts for clean shutdown rootCtx, rootCancel := signal.NotifyContext(context.Background(), os.Interrupt) defer rootCancel() + httpCtx, httpCancel := context.WithCancel(rootCtx) defer httpCancel() + httpShutdownCtx, httpShutdownCancel := context.WithCancel(context.Background()) defer httpShutdownCancel() @@ -66,14 +71,18 @@ func ActionServe(c *cli.Context) error { // Wait for cancel go func() { <-httpCtx.Done() - timeoutCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + + timeoutCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second) // nolint: gomnd defer cancel() - srv.Shutdown(timeoutCtx) + + _ = srv.Shutdown(timeoutCtx) }() serverLogger.Infow("Starting HTTP server.", "addr", cfg.ListenAddr) + if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { serverLogger.Errorw("Error during shutdown.", "error", err) } + serverLogger.Infow("HTTP server shutdown complete.", "addr", cfg.ListenAddr) httpShutdownCancel() }() diff --git a/cmd/server/server.go b/cmd/server/server.go index d4d5c73..2624ee1 100644 --- a/cmd/server/server.go +++ b/cmd/server/server.go @@ -29,5 +29,5 @@ func main() { Action: actions.ActionServe, } - app.Run(os.Args) + _ = app.Run(os.Args) } diff --git a/config.go b/config.go index 1db0688..417929e 100644 --- a/config.go +++ b/config.go @@ -33,6 +33,7 @@ func ServerConfigFromReader(r io.Reader) (*ServerConfig, error) { FS: &ServerStoreFSStoreConfig{}, }, } + if err := decoder.Decode(&c); err != nil { return nil, fmt.Errorf("error decoding server config: %w", err) } diff --git a/files/filestore.go b/files/filestore.go index 5ed0ddc..454ba12 100644 --- a/files/filestore.go +++ b/files/filestore.go @@ -7,13 +7,13 @@ import ( type File struct { ID string `json:"id"` - OriginalFilename string `json:"original_filename"` - MaxViews uint `json:"max_views"` - ExpiresOn time.Time `json:"expires_on"` + OriginalFilename string `json:"originalFilename"` + MaxViews uint `json:"maxViews"` + ExpiresOn time.Time `json:"expiresOn"` Body io.ReadCloser - FileSize int64 `json:"file_size"` + FileSize int64 `json:"fileSize"` } type FileStore interface { diff --git a/files/filestore_fs.go b/files/filestore_fs.go index 5bf6e6c..0c9491b 100644 --- a/files/filestore_fs.go +++ b/files/filestore_fs.go @@ -34,6 +34,7 @@ func (s *FSFileStore) Store(f *File) error { } path := filepath.Join(s.dir, f.ID) + dst, err := os.Create(path) if err != nil { return err @@ -44,12 +45,15 @@ func (s *FSFileStore) Store(f *File) error { if err != nil { return err } + s.metadata[f.ID] = metadata s.metadata[f.ID].FileSize = n + if err := s.writeMetadata(); err != nil { delete(s.metadata, f.ID) return err } + return nil } @@ -60,11 +64,14 @@ func (s *FSFileStore) Get(id string) (*File, error) { } path := filepath.Join(s.dir, id) + f, err := os.Open(path) if err != nil { return nil, err } + metadata.Body = f + return metadata, nil } @@ -73,20 +80,24 @@ func (s *FSFileStore) Delete(id string) error { if err := os.Remove(path); err != nil { return err } + delete(s.metadata, id) + return nil } func (s *FSFileStore) List() ([]string, error) { - var results []string + results := make([]string, 0, len(s.metadata)) for k := range s.metadata { results = append(results, k) } + return results, nil } func (s *FSFileStore) writeMetadata() error { path := filepath.Join(s.dir, "metadata.json") + f, err := os.Create(path) if err != nil { return err @@ -97,11 +108,13 @@ func (s *FSFileStore) writeMetadata() error { if err := encoder.Encode(s.metadata); err != nil { return err } + return nil } func (s *FSFileStore) readMetadata() error { path := filepath.Join(s.dir, "metadata.json") + f, err := os.Open(path) if err != nil { // TODO: Handle errors better @@ -113,5 +126,6 @@ func (s *FSFileStore) readMetadata() error { if err := decoder.Decode(&s.metadata); err != nil { return err } + return nil } diff --git a/files/filestore_memory.go b/files/filestore_memory.go index ee9ac51..9788d20 100644 --- a/files/filestore_memory.go +++ b/files/filestore_memory.go @@ -29,7 +29,6 @@ func NewMemoryFileStore() *MemoryFileStore { } func (s *MemoryFileStore) Store(f *File) error { - data := &fileData{ ID: f.ID, MaxViews: f.MaxViews, @@ -45,6 +44,7 @@ func (s *MemoryFileStore) Store(f *File) error { defer s.lock.Unlock() s.data[f.ID] = data + return err } @@ -56,6 +56,7 @@ func (s *MemoryFileStore) Get(id string) (*File, error) { if !ok { return nil, fmt.Errorf("no such item") } + f := &File{ ID: fd.ID, MaxViews: fd.MaxViews, @@ -70,17 +71,21 @@ func (s *MemoryFileStore) Get(id string) (*File, error) { func (s *MemoryFileStore) Delete(id string) error { s.lock.Lock() defer s.lock.Unlock() + delete(s.data, id) + return nil } func (s *MemoryFileStore) List() ([]string, error) { - var ids []string + ids := make([]string, 0, len(s.data)) s.lock.RLock() defer s.lock.RUnlock() + for id := range s.data { ids = append(ids, id) } + return ids, nil } diff --git a/users/user.go b/users/user.go index b2c2a27..4b31160 100644 --- a/users/user.go +++ b/users/user.go @@ -16,7 +16,7 @@ const ( type User struct { Username string `json:"username"` - HashedPassword []byte `json:"hashed_password"` + HashedPassword []byte `json:"hashedPassword"` Role Role `json:"role"` } @@ -38,6 +38,8 @@ func (u *User) SetPassword(password string) error { if err != nil { return err } + u.HashedPassword = hashed + return nil } diff --git a/users/userstore_bolt.go b/users/userstore_bolt.go index 908ac94..c67b704 100644 --- a/users/userstore_bolt.go +++ b/users/userstore_bolt.go @@ -15,7 +15,7 @@ type BoltUserStore struct { } func NewBoltUserStore(path string) (*BoltUserStore, error) { - db, err := bbolt.Open(path, 0666, nil) + db, err := bbolt.Open(path, 0666, nil) // nolint: gomnd if err != nil { return nil, err } @@ -36,6 +36,7 @@ func (s *BoltUserStore) Close() error { func (s *BoltUserStore) Get(username string) (*User, error) { var user User + err := s.db.View(func(tx *bbolt.Tx) error { bkt := tx.Bucket(keyUsers) rawUser := bkt.Get([]byte(username)) @@ -44,9 +45,11 @@ func (s *BoltUserStore) Get(username string) (*User, error) { } return nil }) + if err != nil { return nil, err } + return &user, nil } @@ -86,5 +89,6 @@ func (s *BoltUserStore) List() ([]string, error) { if err != nil { return nil, err } + return ids, nil } diff --git a/users/userstore_memory.go b/users/userstore_memory.go index 8980afd..f21bc6c 100644 --- a/users/userstore_memory.go +++ b/users/userstore_memory.go @@ -18,7 +18,9 @@ func NewMemoryUserStore() *MemoryUserStore { func (s *MemoryUserStore) Get(username string) (*User, error) { s.lock.Lock() defer s.lock.Unlock() + user, ok := s.users[username] + if !ok { return nil, fmt.Errorf("no such user: %s", username) } @@ -29,21 +31,25 @@ func (s *MemoryUserStore) Get(username string) (*User, error) { func (s *MemoryUserStore) Store(user *User) error { s.lock.Lock() defer s.lock.Unlock() + s.users[user.Username] = user + return nil } func (s *MemoryUserStore) Delete(username string) error { s.lock.Lock() defer s.lock.Unlock() + delete(s.users, username) + return nil } func (s *MemoryUserStore) List() ([]string, error) { s.lock.Lock() defer s.lock.Unlock() - var ids []string + ids := make([]string, 0, len(s.users)) for k := range s.users { ids = append(ids, k) }