Add maximum sync size enforcement
This commit is contained in:
parent
664c6fd4fa
commit
62cc02cffe
33
README.md
33
README.md
|
@ -37,18 +37,22 @@ To run the binary you just built:
|
|||
`~/go/bin/ubsserver`
|
||||
|
||||
It will start the API server listening on port 8090 and using the `data` directory under $PWD as the datastore.
|
||||
You can change the listening address using the `-listen` flag (e.g.: `-listen :9999` will listen on port 9999 on all
|
||||
addresses).
|
||||
|
||||
Similarly, you can change the datastore using the `-store` flag. The store is defined in the format
|
||||
`driver_name:driver_args` where `driver_name` is the name of the store driver while `driver_args` contains
|
||||
driver-specific settings.
|
||||
There are a few command line flags that can be used to change the behaviour of the API service:
|
||||
|
||||
Currently, only two drivers are implemented:
|
||||
- `-listen $address:$port`: changes the listening address of the built in HTTP server, for example `-listen :9999` will
|
||||
listen on port 9999 on all addresses, while `-listen 127.0.0.1:9876` will only listen on port 9876 on the loopback
|
||||
interface
|
||||
- `-store $driver_name:$driver_args`: changes the datastore driver and its configuration. `$driver_name` is the name of
|
||||
the store driver while `$driver_args` contains driver-specific settings. Currently, only two drivers are implemented:
|
||||
|
||||
- **mem**: a volatile RAM-backed datastore only useful for testing and debugging. It does not take any arguments.
|
||||
- **fs**: the default driver: it stores sync data in json files in the directory specified as the argument (default is
|
||||
`data`)
|
||||
- `mem`: a volatile RAM-backed datastore only useful for testing and debugging. It does not take any arguments
|
||||
(i.e.: it can be only used by specifying `-store mem`).
|
||||
- `fs`: the default driver: it stores sync data in json files in the directory specified as the argument (default is
|
||||
`data`)
|
||||
|
||||
- `-maxsize $size`: changes the maximum size of a sync (in bytes) that can be accepted by the API. The default is set
|
||||
to 512000.
|
||||
|
||||
If you built the container image, you can run the containerised version:
|
||||
|
||||
|
@ -62,11 +66,14 @@ where you want to expose the API service.
|
|||
There are a few missing features that I would like to add.
|
||||
|
||||
Hig priority:
|
||||
- **Enforce storage limit**: it's missing in the current implementation, but it must be implemented to prevent abuse
|
||||
|
||||
Nice to have:
|
||||
- **Rate limiting**: currently it can be enforced by a reverse proxy in front of the API server but would be nice to
|
||||
- **Enforce storage limits**: only the limit on the sync size is currently enforced: more checks must be implemented to
|
||||
prevent abuse
|
||||
|
||||
Medium priority:
|
||||
- **Rate limiting**: this can be partially enforced by a reverse proxy in front of the API server but would be nice to
|
||||
have it as part of the server itself
|
||||
|
||||
Nice to have:
|
||||
- **Front page**: serving a static HTML page on `/`, perhaps with some simple JS to show the service status, should be
|
||||
fairly straightforward and would improve the end user experience. The main blocker here is my lack of design skills
|
||||
to craft a half decent looking front page
|
||||
|
|
|
@ -13,10 +13,14 @@ import (
|
|||
"gitlab.com/mporrato/uBrowserSync/syncstore"
|
||||
)
|
||||
|
||||
const apiVersion = "1.1.13"
|
||||
const infoMessage = "Powered by uBrowserSync"
|
||||
const defaultMaxSyncSize = 512000
|
||||
|
||||
type serviceInfoResp struct {
|
||||
Version string `json:"version"`
|
||||
Message string `json:"message"`
|
||||
MaxSyncSize int32 `json:"maxSyncSize"`
|
||||
MaxSyncSize int `json:"maxSyncSize"`
|
||||
Status int `json:"status"`
|
||||
}
|
||||
|
||||
|
@ -41,6 +45,13 @@ type GetSyncVerResp struct {
|
|||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
var store syncstore.Store
|
||||
var maxSyncSize = defaultMaxSyncSize
|
||||
var serviceStatus = 1
|
||||
var listen string
|
||||
|
||||
var sidRe = regexp.MustCompile("^[[:xdigit:]]{32}$")
|
||||
|
||||
func sendJSON(w http.ResponseWriter, status int, data interface{}) {
|
||||
body, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
|
@ -76,16 +87,15 @@ func info(w http.ResponseWriter, req *http.Request) {
|
|||
sendJSONError(w, syncstore.NotImplementedError)
|
||||
} else {
|
||||
log.Println("info()")
|
||||
sendJSONOk(w, serviceInfoResp{
|
||||
Version: "1.1.13",
|
||||
Message: "Powered by uBrowserSync",
|
||||
MaxSyncSize: 2 * 1024 * 1024,
|
||||
Status: 1})
|
||||
serviceInfo := serviceInfoResp{
|
||||
Version: apiVersion,
|
||||
Message: infoMessage,
|
||||
Status: serviceStatus,
|
||||
MaxSyncSize: maxSyncSize}
|
||||
sendJSONOk(w, serviceInfo)
|
||||
}
|
||||
}
|
||||
|
||||
var store syncstore.Store
|
||||
|
||||
var invalidRequestError = syncstore.NewSyncError(
|
||||
"Invalid request",
|
||||
"Malformed request body",
|
||||
|
@ -98,6 +108,10 @@ func createSync(w http.ResponseWriter, req *http.Request) {
|
|||
sendJSONError(w, invalidRequestError)
|
||||
return
|
||||
}
|
||||
if len(body.Version) > 20 {
|
||||
sendJSONError(w, invalidRequestError)
|
||||
return
|
||||
}
|
||||
resp, err := store.Create(body.Version)
|
||||
if err != nil {
|
||||
sendJSONError(w, err)
|
||||
|
@ -140,6 +154,10 @@ func updateSync(syncId string, w http.ResponseWriter, req *http.Request) {
|
|||
sendJSONError(w, invalidRequestError)
|
||||
return
|
||||
}
|
||||
if len(body.Bookmarks) > maxSyncSize {
|
||||
sendJSONError(w, syncstore.SyncDataLimitExceededError)
|
||||
return
|
||||
}
|
||||
resp, err := store.Update(syncId, body.Bookmarks, body.LastUpdated)
|
||||
if err != nil {
|
||||
sendJSONError(w, err)
|
||||
|
@ -148,8 +166,6 @@ func updateSync(syncId string, w http.ResponseWriter, req *http.Request) {
|
|||
sendJSONOk(w, UpdateResp{LastUpdated: resp})
|
||||
}
|
||||
|
||||
var sidRe = regexp.MustCompile("^[[:xdigit:]]{32}$")
|
||||
|
||||
func bookmarks(w http.ResponseWriter, req *http.Request) {
|
||||
elements := strings.Split(strings.Trim(req.URL.Path, "/"), "/")
|
||||
|
||||
|
@ -202,8 +218,6 @@ func bookmarks(w http.ResponseWriter, req *http.Request) {
|
|||
sendJSONError(w, syncstore.NotImplementedError)
|
||||
}
|
||||
|
||||
var listen string
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
var storeFlag string
|
||||
|
@ -211,6 +225,7 @@ func init() {
|
|||
|
||||
flag.StringVar(&listen, "listen", ":8090", "listen address and port")
|
||||
flag.StringVar(&storeFlag, "store", "fs:data", "blob store driver")
|
||||
flag.IntVar(&maxSyncSize, "maxsize", defaultMaxSyncSize, "maximum size of a sync in bytes")
|
||||
flag.Parse()
|
||||
|
||||
storeFlagTokens := strings.Split(storeFlag, ":")
|
||||
|
|
|
@ -56,3 +56,8 @@ var SyncConflictError = NewSyncError(
|
|||
"SyncConflictException",
|
||||
"A sync conflict was detected",
|
||||
http.StatusConflict)
|
||||
|
||||
var SyncDataLimitExceededError = NewSyncError(
|
||||
"SyncDataLimitExceededException",
|
||||
"Sync data limit exceeded",
|
||||
http.StatusRequestEntityTooLarge)
|
||||
|
|
Loading…
Reference in New Issue