From 9cf69abd2d974d4f8c0d0134861ee915c8666625 Mon Sep 17 00:00:00 2001 From: Vladimir L Date: Sat, 4 Jun 2022 03:44:52 +0200 Subject: [PATCH 1/5] added support for restricted mode of service: only owner of master token can create new baskets, small refactoring and renaming --- config.go | 10 ++++++-- handlers.go | 62 ++++++++++++++++++++++++++++++++---------------- handlers_test.go | 42 ++++++++++++++++++++++++++++++++ server.go | 10 ++++---- server_test.go | 7 ++---- 5 files changed, 98 insertions(+), 33 deletions(-) diff --git a/config.go b/config.go index 14e3050..f3d3894 100644 --- a/config.go +++ b/config.go @@ -35,6 +35,7 @@ type ServerConfig struct { DbConnection string Baskets []string PathPrefix string + Mode string } type arrayFlags []string @@ -57,10 +58,14 @@ func CreateConfig() *ServerConfig { var pageSize = flag.Int("page", defaultPageSize, "Default page size") var masterToken = flag.String("token", "", "Master token, random token is generated if not provided") var dbType = flag.String("db", defaultDatabaseType, fmt.Sprintf( - "Baskets storage type: %s - in-memory, %s - Bolt DB, %s - SQL database", DbTypeMemory, DbTypeBolt, DbTypeSQL)) + "Baskets storage type: \"%s\" - in-memory, \"%s\" - Bolt DB, \"%s\" - SQL database", + DbTypeMemory, DbTypeBolt, DbTypeSQL)) var dbFile = flag.String("file", "./baskets.db", "Database location, only applicable for file or SQL databases") var dbConnection = flag.String("conn", "", "Database connection string for SQL databases, if undefined \"file\" argument is considered") var prefix = flag.String("prefix", "", "Service URL path prefix") + var mode = flag.String("mode", ModePublic, fmt.Sprintf( + "Service mode: \"%s\" - any visitor can create a new basket, \"%s\" - baskets creation requires master token", + ModePublic, ModeRestricted)) var baskets arrayFlags flag.Var(&baskets, "basket", "Name of a basket to auto-create during service startup (can be specified multiple times)") @@ -83,7 +88,8 @@ func CreateConfig() *ServerConfig { DbFile: *dbFile, DbConnection: *dbConnection, Baskets: baskets, - PathPrefix: normalizePrefix(*prefix)} + PathPrefix: normalizePrefix(*prefix), + Mode: *mode} } func normalizePrefix(prefix string) string { diff --git a/handlers.go b/handlers.go index 7707098..343a51f 100644 --- a/handlers.go +++ b/handlers.go @@ -16,6 +16,11 @@ import ( "github.com/julienschmidt/httprouter" ) +const ( + ModePublic = "public" + ModeRestricted = "restricted" +) + var validBasketName = regexp.MustCompile(basketNamePattern) var defaultResponse = ResponseConfig{Status: http.StatusOK, Headers: http.Header{}, IsTemplate: false} var indexPageTemplate = template.Must(template.New("index").Parse(indexPageContentTemplate)) @@ -59,14 +64,14 @@ func getPage(values url.Values) (int, int) { return max, skip } -// getAuthenticatedBasket fetches basket details by name and authenticates the access to this basket, returns nil in case of failure -func getAuthenticatedBasket(w http.ResponseWriter, r *http.Request, ps httprouter.Params) (string, Basket) { +// getAuthorizedBasket fetches basket details by name and authorizes the access to this basket, returns nil in case of failure +func getAuthorizedBasket(w http.ResponseWriter, r *http.Request, ps httprouter.Params, config *ServerConfig) (string, Basket) { name := ps.ByName("basket") if !validBasketName.MatchString(name) { http.Error(w, "invalid basket name; the name does not match pattern: "+validBasketName.String(), http.StatusBadRequest) } else if basket := basketsDb.Get(name); basket != nil { // maybe custom header, e.g. basket_key, basket_token - if token := r.Header.Get("Authorization"); basket.Authorize(token) || token == serverConfig.MasterToken { + if token := r.Header.Get("Authorization"); basket.Authorize(token) || token == config.MasterToken { return name, basket } w.WriteHeader(http.StatusUnauthorized) @@ -77,6 +82,21 @@ func getAuthenticatedBasket(w http.ResponseWriter, r *http.Request, ps httproute return "", nil } +// authorizeRequest helps to authorize requests for restricted end-points and returns true in case of successful authorization +// publicAPI requires no authorization unless the server mode is set to "restricted" +func authorizeRequest(w http.ResponseWriter, r *http.Request, publicAPI bool, config *ServerConfig) bool { + if publicAPI && config.Mode != ModeRestricted { + return true + } + + if r.Header.Get("Authorization") == serverConfig.MasterToken { + return true + } + + w.WriteHeader(http.StatusUnauthorized) + return false +} + // validateBasketConfig validates basket configuration func validateBasketConfig(config *BasketConfig) error { // validate Capacity @@ -138,9 +158,7 @@ func getValidMethod(ps httprouter.Params) (string, error) { // GetBaskets handles HTTP request to get registered baskets func GetBaskets(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { - if r.Header.Get("Authorization") != serverConfig.MasterToken { - w.WriteHeader(http.StatusUnauthorized) - } else { + if authorizeRequest(w, r, false, serverConfig) { values := r.URL.Query() if query := values.Get("q"); len(query) > 0 { // find names @@ -157,9 +175,7 @@ func GetBaskets(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { // GetStats handles HTTP request to get database statistics func GetStats(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { - if r.Header.Get("Authorization") != serverConfig.MasterToken { - w.WriteHeader(http.StatusUnauthorized) - } else { + if authorizeRequest(w, r, false, serverConfig) { // get database stats max := parseInt(r.URL.Query().Get("max"), 1, 100, 5) json, err := json.Marshal(basketsDb.GetStats(max)) @@ -176,7 +192,7 @@ func GetVersion(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { // GetBasket handles HTTP request to get basket configuration func GetBasket(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { - if _, basket := getAuthenticatedBasket(w, r, ps); basket != nil { + if _, basket := getAuthorizedBasket(w, r, ps, serverConfig); basket != nil { json, err := json.Marshal(basket.Config()) writeJSON(w, http.StatusOK, json, err) } @@ -184,6 +200,10 @@ func GetBasket(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { // CreateBasket handles HTTP request to create a new basket func CreateBasket(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + if !authorizeRequest(w, r, true, serverConfig) { + return + } + name := ps.ByName("basket") if name == serviceOldAPIPath || name == serviceAPIPath || name == serviceUIPath { http.Error(w, "This basket name conflicts with reserved system path: "+name, http.StatusForbidden) @@ -228,7 +248,7 @@ func CreateBasket(w http.ResponseWriter, r *http.Request, ps httprouter.Params) // UpdateBasket handles HTTP request to update basket configuration func UpdateBasket(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { - if _, basket := getAuthenticatedBasket(w, r, ps); basket != nil { + if _, basket := getAuthorizedBasket(w, r, ps, serverConfig); basket != nil { // read config (max 2 kB) body, err := ioutil.ReadAll(io.LimitReader(r.Body, 2048)) r.Body.Close() @@ -257,7 +277,7 @@ func UpdateBasket(w http.ResponseWriter, r *http.Request, ps httprouter.Params) // DeleteBasket handles HTTP request to delete basket func DeleteBasket(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { - if name, basket := getAuthenticatedBasket(w, r, ps); basket != nil { + if name, basket := getAuthorizedBasket(w, r, ps, serverConfig); basket != nil { log.Printf("[info] deleting basket: %s", name) basketsDb.Delete(name) @@ -267,7 +287,7 @@ func DeleteBasket(w http.ResponseWriter, r *http.Request, ps httprouter.Params) // GetBasketResponse handles HTTP request to get basket response configuration func GetBasketResponse(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { - if _, basket := getAuthenticatedBasket(w, r, ps); basket != nil { + if _, basket := getAuthorizedBasket(w, r, ps, serverConfig); basket != nil { method, errm := getValidMethod(ps) if errm != nil { http.Error(w, errm.Error(), http.StatusBadRequest) @@ -285,7 +305,7 @@ func GetBasketResponse(w http.ResponseWriter, r *http.Request, ps httprouter.Par // UpdateBasketResponse handles HTTP request to update basket response configuration func UpdateBasketResponse(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { - if _, basket := getAuthenticatedBasket(w, r, ps); basket != nil { + if _, basket := getAuthorizedBasket(w, r, ps, serverConfig); basket != nil { method, errm := getValidMethod(ps) if errm != nil { http.Error(w, errm.Error(), http.StatusBadRequest) @@ -318,7 +338,7 @@ func UpdateBasketResponse(w http.ResponseWriter, r *http.Request, ps httprouter. // GetBasketRequests handles HTTP request to get requests collected by basket func GetBasketRequests(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { - if _, basket := getAuthenticatedBasket(w, r, ps); basket != nil { + if _, basket := getAuthorizedBasket(w, r, ps, serverConfig); basket != nil { values := r.URL.Query() if query := values.Get("q"); len(query) > 0 { // find requests @@ -335,7 +355,7 @@ func GetBasketRequests(w http.ResponseWriter, r *http.Request, ps httprouter.Par // ClearBasket handles HTTP request to delete all requests collected by basket func ClearBasket(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { - if _, basket := getAuthenticatedBasket(w, r, ps); basket != nil { + if _, basket := getAuthorizedBasket(w, r, ps, serverConfig); basket != nil { basket.Clear() w.WriteHeader(http.StatusNoContent) } @@ -343,7 +363,7 @@ func ClearBasket(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { // ForwardToWeb handels HTTP forwarding to /web func ForwardToWeb(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { - http.Redirect(w, r, pathPrefix+"/"+serviceUIPath, http.StatusFound) + http.Redirect(w, r, serverConfig.PathPrefix+"/"+serviceUIPath, http.StatusFound) } type TemplateData struct { @@ -356,7 +376,7 @@ type TemplateData struct { // WebIndexPage handles HTTP request to render index page func WebIndexPage(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { w.Header().Set("Content-Type", "text/html; charset=utf-8") - indexPageTemplate.Execute(w, TemplateData{Prefix: pathPrefix, Version: version}) + indexPageTemplate.Execute(w, TemplateData{Prefix: serverConfig.PathPrefix, Version: version}) } // WebBasketPage handles HTTP request to render basket details page @@ -366,9 +386,9 @@ func WebBasketPage(w http.ResponseWriter, r *http.Request, ps httprouter.Params) case serviceOldAPIPath: // admin page to access all baskets w.Header().Set("Content-Type", "text/html; charset=utf-8") - basketsPageTemplate.Execute(w, TemplateData{Prefix: pathPrefix, Version: version}) + basketsPageTemplate.Execute(w, TemplateData{Prefix: serverConfig.PathPrefix, Version: version}) default: - basketPageTemplate.Execute(w, TemplateData{Prefix: pathPrefix, Version: version, Basket: name}) + basketPageTemplate.Execute(w, TemplateData{Prefix: serverConfig.PathPrefix, Version: version, Basket: name}) } } else { http.Error(w, "Basket name does not match pattern: "+validBasketName.String(), http.StatusBadRequest) @@ -377,7 +397,7 @@ func WebBasketPage(w http.ResponseWriter, r *http.Request, ps httprouter.Params) // AcceptBasketRequests accepts and handles HTTP requests passed to different baskets func AcceptBasketRequests(w http.ResponseWriter, r *http.Request) { - name, publicErr, err := getBasketNameOfAcceptedRequest(r, pathPrefix) + name, publicErr, err := getBasketNameOfAcceptedRequest(r, serverConfig.PathPrefix) if err != nil { log.Printf("[error] %s", err) http.Error(w, publicErr, http.StatusBadRequest) diff --git a/handlers_test.go b/handlers_test.go index ec0c6e5..faf99b7 100644 --- a/handlers_test.go +++ b/handlers_test.go @@ -284,6 +284,48 @@ func TestCreateBasket_ReadTimeout(t *testing.T) { } } +func TestCreateBasket_Unauthorized(t *testing.T) { + basket := "create10" + + serverConfig.Mode = ModeRestricted + r, err := http.NewRequest("POST", "http://localhost:55555/api/baskets/"+basket, strings.NewReader("")) + if assert.NoError(t, err) { + w := httptest.NewRecorder() + ps := append(make(httprouter.Params, 0), httprouter.Param{Key: "basket", Value: basket}) + CreateBasket(w, r, ps) + + // validate response: 401 - Unauthorized + assert.Equal(t, 401, w.Code, "wrong HTTP result code") + + // validate database + assert.Nil(t, basketsDb.Get(basket), "basket '%v' should not be created", basket) + } + + serverConfig.Mode = ModePublic +} + +func TestCreateBasket_Authorized(t *testing.T) { + basket := "create11" + + serverConfig.Mode = ModeRestricted + r, err := http.NewRequest("POST", "http://localhost:55555/api/baskets/"+basket, strings.NewReader("")) + if assert.NoError(t, err) { + r.Header.Add("Authorization", serverConfig.MasterToken) + + w := httptest.NewRecorder() + ps := append(make(httprouter.Params, 0), httprouter.Param{Key: "basket", Value: basket}) + CreateBasket(w, r, ps) + + // validate response: 201 - Created + assert.Equal(t, 201, w.Code, "wrong HTTP result code") + + // validate database + assert.NotNil(t, basketsDb.Get(basket), "basket '%v' should be created", basket) + } + + serverConfig.Mode = ModePublic +} + func TestGetBasket(t *testing.T) { basket := "get01" diff --git a/server.go b/server.go index 297a6c0..1549444 100644 --- a/server.go +++ b/server.go @@ -16,7 +16,6 @@ var basketsDb BasketsDatabase var httpClient *http.Client var httpInsecureClient *http.Client var version *Version -var pathPrefix string // CreateServer creates an instance of Request Baskets server func CreateServer(config *ServerConfig) *http.Server { @@ -43,9 +42,8 @@ func CreateServer(config *ServerConfig) *http.Server { insecureTransport := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}} httpInsecureClient = &http.Client{Transport: insecureTransport} - setPathPrefix(config.PathPrefix) - // configure service HTTP router + pathPrefix := getPathPrefix(config) router := httprouter.New() //// Old API mapping //// @@ -112,11 +110,13 @@ func createBasketsDatabase(dbtype string, file string, conn string) BasketsDatab } } -func setPathPrefix(prefix string) { - pathPrefix = prefix +func getPathPrefix(config *ServerConfig) string { + pathPrefix := config.PathPrefix if len(pathPrefix) > 0 { log.Printf("[info] service path prefix: %s", pathPrefix) } + + return pathPrefix } func shutdownHook() { diff --git a/server_test.go b/server_test.go index 798934d..d209f1c 100644 --- a/server_test.go +++ b/server_test.go @@ -80,9 +80,6 @@ func TestCreateDefaultBaskets(t *testing.T) { } func TestSetPathPrefix(t *testing.T) { - setPathPrefix("/abc") - assert.Equal(t, "/abc", pathPrefix, "unexpected prefix") - - setPathPrefix("") - assert.Empty(t, pathPrefix, "prefix is not expected") + assert.Equal(t, "/abc", getPathPrefix(&ServerConfig{PathPrefix: "/abc"}), "unexpected prefix") + assert.Empty(t, getPathPrefix(&ServerConfig{}), "prefix is not expected") } From 9918801bbbb58d3d5a101ebf6b677b3eac05ceea Mon Sep 17 00:00:00 2001 From: Vladimir L Date: Wed, 8 Jun 2022 00:40:27 +0200 Subject: [PATCH 2/5] improved visualisation of 'Last Request' timestamp in case when it is undefined --- web/baskets.html | 2 +- web_baskets.html.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/web/baskets.html b/web/baskets.html index ba0c00c..d593010 100644 --- a/web/baskets.html +++ b/web/baskets.html @@ -96,7 +96,7 @@ basketsList.append("
  • " + toDisplayName(basket.name) + " - " + basket.requests_count + " (" + toDisplayInt(basket.requests_total_count) + ")
    Last Request: " - + new Date(basket.last_request_date).toISOString() + "
  • "); + + ((basket.last_request_date > 0) ? new Date(basket.last_request_date).toISOString() : "n/a") + ""); } } } diff --git a/web_baskets.html.go b/web_baskets.html.go index a9ff910..b8410a2 100644 --- a/web_baskets.html.go +++ b/web_baskets.html.go @@ -86,7 +86,7 @@ var ( basketsList.append("
  • " + toDisplayName(basket.name) + " - " + basket.requests_count + " (" + toDisplayInt(basket.requests_total_count) + ")
    Last Request: " - + new Date(basket.last_request_date).toISOString() + "
  • "); + + ((basket.last_request_date > 0) ? new Date(basket.last_request_date).toISOString() : "n/a") + ""); } } } From 9cfe8e55e3bc928f22f468ec96d0b82d819ad67f Mon Sep 17 00:00:00 2001 From: Vladimir L Date: Wed, 8 Jun 2022 00:56:39 +0200 Subject: [PATCH 3/5] UI to support 'restricted' mode and request master token if basket creation fails with 401, this feature addresses the feature request #71 --- web/index.html | 61 ++++++++++++++++++++++++++++++++++++++++++----- web_index.html.go | 61 ++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 110 insertions(+), 12 deletions(-) diff --git a/web/index.html b/web/index.html index 400f1e9..435fb0e 100644 --- a/web/index.html +++ b/web/index.html @@ -41,9 +41,13 @@ } function onAjaxError(jqXHR) { - $("#error_message_label").html("HTTP " + jqXHR.status + " - " + jqXHR.statusText); - $("#error_message_text").html(jqXHR.responseText); - $("#error_message").modal(); + if (jqXHR.status == 401) { + $("#master_token_dialog").modal({ keyboard : false }); + } else { + $("#error_message_label").html("HTTP " + jqXHR.status + " - " + jqXHR.statusText); + $("#error_message_text").html(jqXHR.responseText); + $("#error_message").modal(); + } } function addBasketName(name) { @@ -64,7 +68,13 @@ function createBasket() { var basket = $.trim($("#basket_name").val()); if (basket) { - $.post(prefix + "/api/baskets/" + basket, function(data) { + $.ajax({ + method: "POST", + url: prefix + "/api/baskets/" + basket, + headers: { + "Authorization" : sessionStorage.getItem("master_token") + } + }).done(function(data) { localStorage.setItem("basket_" + basket, data.token); $("#created_message_text").html("

    Basket '" + basket + "' is successfully created!

    Your token is: " + data.token + "

    "); @@ -73,9 +83,9 @@ // refresh addBasketName(basket); - }).fail(onAjaxError).always(function() { + }).always(function() { randomName(); - }); + }).fail(onAjaxError); } else { $("#error_message_label").html("Missing basket name"); $("#error_message_text").html("Please, provide a name of basket you would like to create"); @@ -83,6 +93,17 @@ } } + function saveMasterToken() { + var token = $("#master_token").val(); + $("#master_token").val(""); + $("#master_token_dialog").modal("hide"); + if (token) { + sessionStorage.setItem("master_token", token); + } else { + sessionStorage.removeItem("master_token"); + } + } + // Initialization $(document).ready(function() { $("#base_uri").html(window.location.protocol + "//" + window.location.host + prefix + "/"); @@ -93,6 +114,9 @@ $("#refresh").on("click", function(event) { randomName(); }); + $("#master_token_dialog").on("hidden.bs.modal", function (event) { + saveMasterToken(); + }); randomName(); showMyBaskets(); }); @@ -152,6 +176,31 @@ + + +
    diff --git a/web_index.html.go b/web_index.html.go index e51ec26..bc1b0e7 100644 --- a/web_index.html.go +++ b/web_index.html.go @@ -30,9 +30,13 @@ var ( } function onAjaxError(jqXHR) { - $("#error_message_label").html("HTTP " + jqXHR.status + " - " + jqXHR.statusText); - $("#error_message_text").html(jqXHR.responseText); - $("#error_message").modal(); + if (jqXHR.status == 401) { + $("#master_token_dialog").modal({ keyboard : false }); + } else { + $("#error_message_label").html("HTTP " + jqXHR.status + " - " + jqXHR.statusText); + $("#error_message_text").html(jqXHR.responseText); + $("#error_message").modal(); + } } function addBasketName(name) { @@ -53,7 +57,13 @@ var ( function createBasket() { var basket = $.trim($("#basket_name").val()); if (basket) { - $.post("{{.Prefix}}/api/baskets/" + basket, function(data) { + $.ajax({ + method: "POST", + url: "{{.Prefix}}/api/baskets/" + basket, + headers: { + "Authorization" : sessionStorage.getItem("master_token") + } + }).done(function(data) { localStorage.setItem("basket_" + basket, data.token); $("#created_message_text").html("

    Basket '" + basket + "' is successfully created!

    Your token is: " + data.token + "

    "); @@ -62,9 +72,9 @@ var ( // refresh addBasketName(basket); - }).fail(onAjaxError).always(function() { + }).always(function() { randomName(); - }); + }).fail(onAjaxError); } else { $("#error_message_label").html("Missing basket name"); $("#error_message_text").html("Please, provide a name of basket you would like to create"); @@ -72,6 +82,17 @@ var ( } } + function saveMasterToken() { + var token = $("#master_token").val(); + $("#master_token").val(""); + $("#master_token_dialog").modal("hide"); + if (token) { + sessionStorage.setItem("master_token", token); + } else { + sessionStorage.removeItem("master_token"); + } + } + // Initialization $(document).ready(function() { $("#base_uri").html(window.location.protocol + "//" + window.location.host + "{{.Prefix}}/"); @@ -82,6 +103,9 @@ var ( $("#refresh").on("click", function(event) { randomName(); }); + $("#master_token_dialog").on("hidden.bs.modal", function (event) { + saveMasterToken(); + }); randomName(); showMyBaskets(); }); @@ -141,6 +165,31 @@ var (
    + + +
    From 71ca79857cd9467ca9986e42ad77f879b7b9e7a8 Mon Sep 17 00:00:00 2001 From: Vladimir L Date: Wed, 8 Jun 2022 01:08:57 +0200 Subject: [PATCH 4/5] added support for service 'mode' in docker container via env var MODE --- docker/entrypoint.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index ff4d41d..98a7486 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -46,6 +46,10 @@ if [ -n "$PATHPREFIX" ]; then args="$args -prefix $PATHPREFIX" fi +if [ -n "$MODE" ]; then + args="$args -mode $MODE" +fi + cmd="/bin/rbaskets $args" echo "Executing: $cmd" exec $cmd From 0baa70616c1da43d33db6d8dfa557df75a638a91 Mon Sep 17 00:00:00 2001 From: Vladimir L Date: Wed, 8 Jun 2022 01:33:41 +0200 Subject: [PATCH 5/5] update README: added description of new parameter 'mode', list ENVVAR for docker for corresponding command line params --- README.md | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index f9eee4c..b4ae775 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ Request Baskets service supports several command line configuration parameters. $ request-baskets --help Usage of bin/request-baskets: -db string - Baskets storage type: mem - in-memory, bolt - Bolt DB, sql - SQL database (default "mem") + Baskets storage type: "mem" - in-memory, "bolt" - Bolt DB, "sql" - SQL database (default "mem") -file string Database location, only applicable for file or SQL databases (default "./baskets.db") -conn string @@ -105,20 +105,26 @@ Usage of bin/request-baskets: Name of a basket to auto-create during service startup (can be specified multiple times) -prefix string Service URL path prefix + -mode string + Service mode: "public" - any visitor can create a new basket, "restricted" - baskets creation requires master token (default "public") ``` ### Parameters - * `-p` *port* - HTTP service listener port, default value is `55555` - * `-page` *size* - default page size when retrieving collections - * `-size` *size* - default new basket capacity, applied if basket capacity is not provided during creation - * `-maxsize` *size* - maximum allowed basket capacity, basket capacity greater than this number will be rejected by service - * `-token` *token* - master token to gain control over all baskets, if not defined a random token will be generated when service is launched and printed to *stdout* - * `-db` *type* - defines baskets storage type: `mem` - in-memory storage (default), `bolt` - [bbolt](https://github.com/etcd-io/bbolt) database, `sql` - SQL database - * `-file` *location* - location of Bolt database file, only relevant if appropriate storage type is chosen - * `-conn` *connection* - database connection string for SQL databases, if undefined `-file` argument is considered - * `-basket` *value* - name of a basket to auto-create during service startup, this parameter can be specified multiple times - * `-prefix` *URL path prefix* - allows to host API and web-UI of baskets service under a sub-path instead of domain ROOT +List of comman line parameters with corresponding ENVVAR for [docker container](./docker/entrypoint.sh): + + * `-p` *port* (`PORT`) - HTTP service listener port, default value is `55555` + * `-l` *IP address* (`LISTEN`) - HTTP listener IP address, default `127.0.0.1` (docker default: `0.0.0.0`) + * `-page` *size* (`PAGE`) - default page size when retrieving collections + * `-size` *size* (`SIZE`) - default new basket capacity, applied if basket capacity is not provided during creation + * `-maxsize` *size* (`MAXSIZE`) - maximum allowed basket capacity, basket capacity greater than this number will be rejected by service + * `-token` *token* (`TOKEN`) - master token to gain control over all baskets, if not defined a random token will be generated when service is launched and printed to *stdout* + * `-db` *type* (`DB`) - defines baskets storage type: `mem` - in-memory storage (default), `bolt` - [bbolt](https://github.com/etcd-io/bbolt) database (docker default), `sql` - SQL database + * `-file` *location* (`FILE`) - location of Bolt database file, only relevant if appropriate storage type is chosen + * `-conn` *connection* (`CONN`) - database connection string for SQL databases, if undefined `-file` argument is considered + * `-basket` *value* (`BASKET`) - name of a basket to auto-create during service startup, this parameter can be specified multiple times + * `-prefix` *URL path prefix* (`PATHPREFIX`) - allows to host API and web-UI of baskets service under a sub-path instead of domain ROOT + * `-mode` *mode* (`MODE`) - defines service operation mode: `public` - when any visitor can create a new basket, or `restricted` - baskets creation requires master token ## Usage