diff --git a/main.go b/main.go index ef0561e..8c46d89 100644 --- a/main.go +++ b/main.go @@ -10,15 +10,15 @@ import ( "strconv" "time" - "git.1e99.eu/1e99/passed/routes" - "git.1e99.eu/1e99/passed/storage" + "git.1e99.eu/1e99/passed/password" + "git.1e99.eu/1e99/passed/route" ) //go:embed static/* var embedFS embed.FS func run() error { - store, err := newStore() + storage, err := newStorage() if err != nil { return err } @@ -39,12 +39,12 @@ func run() error { handler := http.Handler(mux) mux.Handle("GET /", http.FileServerFS(staticFS)) - mux.Handle("POST /api/password", routes.CreatePassword(store, maxPasswordLength)) - mux.Handle("GET /api/password/{id}", routes.GetPassword(store)) - mux.Handle("HEAD /api/password/{id}", routes.HasPassword(store)) + mux.Handle("POST /api/password", route.CreatePassword(storage, maxPasswordLength)) + mux.Handle("GET /api/password/{id}", route.GetPassword(storage)) + mux.Handle("HEAD /api/password/{id}", route.HasPassword(storage)) if logRequests { - handler = routes.Logger(handler) + handler = route.Logger(handler) } log.Printf("Listening on %s.", address) @@ -76,15 +76,15 @@ func newStaticFS() (sfs fs.FS, err error) { } } -func newStore() (store storage.Store, err error) { - var storeType string +func newStorage() (storage password.Storage, err error) { + var storageType string var clearInterval int - env("PASSED_STORE_TYPE", &storeType, "ram") + env("PASSED_STORE_TYPE", &storageType, "ram") env("PASSED_STORE_CLEAR_INTERVAL", &clearInterval, "30") - switch storeType { + switch storageType { case "ram": - store = storage.NewRamStore() + storage = password.NewRamStorage() case "dir", "directory": var path string env("PASSED_STORE_DIR_PATH", &path, "passwords") @@ -94,7 +94,7 @@ func newStore() (store storage.Store, err error) { return } - store = storage.NewDirStore(path) + storage = password.NewDirStorage(path) default: err = errors.New("unknown storage type") return @@ -105,7 +105,7 @@ func newStore() (store storage.Store, err error) { for { <-ticker - err := store.ClearExpired() + err := storage.ClearExpired() if err != nil { log.Printf("Failed to clear expired passwords: %s", err) continue diff --git a/storage/dir.go b/password/dir.go similarity index 66% rename from storage/dir.go rename to password/dir.go index 745bb4c..00ec319 100644 --- a/storage/dir.go +++ b/password/dir.go @@ -1,4 +1,4 @@ -package storage +package password import ( "encoding/gob" @@ -7,12 +7,12 @@ import ( "time" ) -func NewDirStore(path string) Store { - store := &dir{ +func NewDirStorage(path string) Storage { + storage := &dir{ path: path, } - return store + return storage } type dir struct { @@ -24,10 +24,10 @@ type dirEntry struct { ExpiresAt time.Time } -func (store *dir) Create(password []byte, expiresAt time.Time) (string, error) { +func (s *dir) Create(password []byte, expiresAt time.Time) (string, error) { for range 1000 { id := generateId(24) - path := store.getPath(id) + path := s.getPath(id) file, err := os.OpenFile( path, @@ -59,8 +59,8 @@ func (store *dir) Create(password []byte, expiresAt time.Time) (string, error) { return "", ErrFull } -func (store *dir) Get(id string) ([]byte, error) { - entry, err := store.getEntry(id) +func (s *dir) Get(id string) ([]byte, error) { + entry, err := s.getEntry(id) switch { case os.IsNotExist(err): return nil, ErrNotFound @@ -71,8 +71,8 @@ func (store *dir) Get(id string) ([]byte, error) { return entry.Password, nil } -func (store *dir) Delete(id string) error { - path := store.getPath(id) +func (s *dir) Delete(id string) error { + path := s.getPath(id) err := os.Remove(path) if err != nil { return nil @@ -81,23 +81,23 @@ func (store *dir) Delete(id string) error { return nil } -func (store *dir) ClearExpired() error { +func (s *dir) ClearExpired() error { now := time.Now() - entries, err := os.ReadDir(store.path) + entries, err := os.ReadDir(s.path) if err != nil { return err } for _, file := range entries { id := file.Name() - entry, err := store.getEntry(id) + entry, err := s.getEntry(id) if err != nil { return err } if now.After(entry.ExpiresAt) { - err = store.Delete(id) + err = s.Delete(id) if err != nil { return err } @@ -107,8 +107,8 @@ func (store *dir) ClearExpired() error { return nil } -func (store *dir) getEntry(id string) (dirEntry, error) { - path := store.getPath(id) +func (s *dir) getEntry(id string) (dirEntry, error) { + path := s.getPath(id) file, err := os.OpenFile( path, os.O_RDONLY, @@ -129,6 +129,6 @@ func (store *dir) getEntry(id string) (dirEntry, error) { return entry, nil } -func (store *dir) getPath(id string) string { - return path.Join(store.path, id) +func (s *dir) getPath(id string) string { + return path.Join(s.path, id) } diff --git a/password/ram.go b/password/ram.go new file mode 100644 index 0000000..d0c00ba --- /dev/null +++ b/password/ram.go @@ -0,0 +1,81 @@ +package password + +import ( + "sync" + "time" +) + +func NewRamStorage() Storage { + storage := &ram{ + passwords: make(map[string]ramEntry), + lock: sync.Mutex{}, + } + + return storage +} + +type ram struct { + passwords map[string]ramEntry + lock sync.Mutex +} + +type ramEntry struct { + Password []byte + ExpiresAt time.Time +} + +func (s *ram) Create(password []byte, expiresAt time.Time) (string, error) { + s.lock.Lock() + defer s.lock.Unlock() + + for range 1000 { + id := generateId(24) + _, found := s.passwords[id] + if found { + continue + } + + s.passwords[id] = ramEntry{ + Password: password, + ExpiresAt: expiresAt, + } + + return id, nil + } + + return "", ErrFull +} + +func (s *ram) Get(id string) ([]byte, error) { + s.lock.Lock() + defer s.lock.Unlock() + + password, found := s.passwords[id] + if !found { + return nil, ErrNotFound + } + + return password.Password, nil +} + +func (s *ram) Delete(id string) error { + delete(s.passwords, id) + return nil +} + +func (s *ram) ClearExpired() error { + s.lock.Lock() + defer s.lock.Unlock() + time := time.Now() + + for id, password := range s.passwords { + if time.After(password.ExpiresAt) { + err := s.Delete(id) + if err != nil { + return err + } + } + } + + return nil +} diff --git a/storage/storage.go b/password/storage.go similarity index 92% rename from storage/storage.go rename to password/storage.go index 00e509d..878a068 100644 --- a/storage/storage.go +++ b/password/storage.go @@ -1,4 +1,4 @@ -package storage +package password import ( "errors" @@ -11,7 +11,7 @@ var ( ErrFull = errors.New("storage is filled") ) -type Store interface { +type Storage interface { Create(password []byte, expiresAt time.Time) (string, error) Get(id string) ([]byte, error) Delete(id string) error diff --git a/routes/create_password.go b/route/create_password.go similarity index 86% rename from routes/create_password.go rename to route/create_password.go index 5e032eb..c4d5888 100644 --- a/routes/create_password.go +++ b/route/create_password.go @@ -1,14 +1,14 @@ -package routes +package route import ( "encoding/json" "net/http" "time" - "git.1e99.eu/1e99/passed/storage" + "git.1e99.eu/1e99/passed/password" ) -func CreatePassword(store storage.Store, maxLength int) http.HandlerFunc { +func CreatePassword(storage password.Storage, maxLength int) http.HandlerFunc { return func(res http.ResponseWriter, req *http.Request) { var reqBody struct { // Go automatically decodes byte arrays using Base64 @@ -37,12 +37,12 @@ func CreatePassword(store storage.Store, maxLength int) http.HandlerFunc { return } - id, err := store.Create( + id, err := storage.Create( reqBody.Password, time.Now().Add(expiresIn), ) switch { - case err == storage.ErrFull: + case err == password.ErrFull: http.Error(res, "Insufficient storage", http.StatusInsufficientStorage) return case err != nil: diff --git a/routes/get_password.go b/route/get_password.go similarity index 74% rename from routes/get_password.go rename to route/get_password.go index e61bce9..842c484 100644 --- a/routes/get_password.go +++ b/route/get_password.go @@ -1,18 +1,18 @@ -package routes +package route import ( "encoding/json" "net/http" - "git.1e99.eu/1e99/passed/storage" + "git.1e99.eu/1e99/passed/password" ) -func GetPassword(store storage.Store) http.HandlerFunc { +func GetPassword(storage password.Storage) http.HandlerFunc { return func(res http.ResponseWriter, req *http.Request) { id := req.PathValue("id") - password, err := store.Get(id) + passwd, err := storage.Get(id) switch { - case err == storage.ErrNotFound: + case err == password.ErrNotFound: http.Error(res, "Password not found", http.StatusNotFound) return case err != nil: @@ -20,7 +20,7 @@ func GetPassword(store storage.Store) http.HandlerFunc { return } - err = store.Delete(id) + err = storage.Delete(id) if err != nil { http.Error(res, "", http.StatusInternalServerError) return @@ -30,7 +30,7 @@ func GetPassword(store storage.Store) http.HandlerFunc { // Go automatically encodes byte arrays using Base64 Password []byte `json:"password"` }{ - Password: password, + Password: passwd, } err = json.NewEncoder(res).Encode(&resBody) if err != nil { diff --git a/routes/has_password.go b/route/has_password.go similarity index 63% rename from routes/has_password.go rename to route/has_password.go index 34e8d4b..ae43d4b 100644 --- a/routes/has_password.go +++ b/route/has_password.go @@ -1,17 +1,17 @@ -package routes +package route import ( "net/http" - "git.1e99.eu/1e99/passed/storage" + "git.1e99.eu/1e99/passed/password" ) -func HasPassword(store storage.Store) http.HandlerFunc { +func HasPassword(storage password.Storage) http.HandlerFunc { return func(res http.ResponseWriter, req *http.Request) { id := req.PathValue("id") - _, err := store.Get(id) + _, err := storage.Get(id) switch { - case err == storage.ErrNotFound: + case err == password.ErrNotFound: http.Error(res, "", http.StatusNotFound) return case err != nil: diff --git a/routes/logger.go b/route/logger.go similarity index 93% rename from routes/logger.go rename to route/logger.go index 44b3a42..af05e51 100644 --- a/routes/logger.go +++ b/route/logger.go @@ -1,4 +1,4 @@ -package routes +package route import ( "log" diff --git a/storage/ram.go b/storage/ram.go deleted file mode 100644 index b9f3c9a..0000000 --- a/storage/ram.go +++ /dev/null @@ -1,81 +0,0 @@ -package storage - -import ( - "sync" - "time" -) - -func NewRamStore() Store { - store := &ram{ - passwords: make(map[string]ramEntry), - lock: sync.Mutex{}, - } - - return store -} - -type ram struct { - passwords map[string]ramEntry - lock sync.Mutex -} - -type ramEntry struct { - Password []byte - ExpiresAt time.Time -} - -func (store *ram) Create(password []byte, expiresAt time.Time) (string, error) { - store.lock.Lock() - defer store.lock.Unlock() - - for range 1000 { - id := generateId(24) - _, found := store.passwords[id] - if found { - continue - } - - store.passwords[id] = ramEntry{ - Password: password, - ExpiresAt: expiresAt, - } - - return id, nil - } - - return "", ErrFull -} - -func (store *ram) Get(id string) ([]byte, error) { - store.lock.Lock() - defer store.lock.Unlock() - - password, found := store.passwords[id] - if !found { - return nil, ErrNotFound - } - - return password.Password, nil -} - -func (store *ram) Delete(id string) error { - delete(store.passwords, id) - return nil -} - -func (store *ram) ClearExpired() error { - store.lock.Lock() - defer store.lock.Unlock() - time := time.Now() - - for id, password := range store.passwords { - if time.After(password.ExpiresAt) { - err := store.Delete(id) - if err != nil { - return err - } - } - } - - return nil -}