optional API path prefix
parent
25a43c8a0b
commit
ec5223d530
|
|
@ -13,6 +13,7 @@ type Config struct {
|
|||
Database string
|
||||
Driver []string
|
||||
FilePrefix string
|
||||
APIPrefix string
|
||||
FileRoot string
|
||||
Auth bool
|
||||
AuthLifetime time.Duration
|
||||
|
|
@ -33,6 +34,7 @@ func New() Config {
|
|||
|
||||
as.Append(args.INT, "p", "port to listen on", 18114)
|
||||
as.Append(args.STRING, "fileprefix", "path prefix for file service", "/__files__")
|
||||
as.Append(args.STRING, "api-prefix", "path prefix for api", "api")
|
||||
as.Append(args.STRING, "fileroot", "path to file hosting root", "/tmp/")
|
||||
as.Append(args.STRING, "database", "database name to use", "db")
|
||||
as.Append(args.STRING, "driver", "database driver args to use, like [local/storage.Type,arg1,arg2...] or [/path/to/boltdb]", "map")
|
||||
|
|
@ -60,5 +62,6 @@ func New() Config {
|
|||
MaxFileSize: int64(as.GetInt("max-file-size")),
|
||||
RPS: as.GetInt("rps"),
|
||||
SysRPS: as.GetInt("sys-rps"),
|
||||
APIPrefix: strings.TrimPrefix(as.GetString("api-prefix"), "/"),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"local/dndex/config"
|
||||
"local/dndex/server/auth"
|
||||
"local/dndex/storage/entity"
|
||||
"net/http"
|
||||
|
|
@ -27,6 +28,8 @@ func Test(t *testing.T) {
|
|||
s := httptest.NewServer(http.HandlerFunc(nil))
|
||||
s.Close()
|
||||
p := strings.Split(s.URL, ":")[2]
|
||||
os.Args = []string{"a"}
|
||||
s.URL = s.URL + "/" + config.New().APIPrefix
|
||||
os.Args = strings.Split(fmt.Sprintf(`dndex -auth=true -database db -delay 5ms -driver map -fileprefix /files -fileroot %s -p %v -rps 50 -sys-rps 40`, d, p), " ")
|
||||
|
||||
go main()
|
||||
|
|
|
|||
|
|
@ -1,13 +1,19 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"local/dndex/storage"
|
||||
"local/dndex/storage/entity"
|
||||
"net/http"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gopkg.in/mgo.v2/bson"
|
||||
)
|
||||
|
||||
func GeneratePlain(g storage.RateLimitedGraph, r *http.Request) (string, error) {
|
||||
|
|
@ -47,7 +53,26 @@ func readRequestedNamespace(r *http.Request) string {
|
|||
}
|
||||
|
||||
func readRequested(r *http.Request, key string) string {
|
||||
return r.FormValue(key)
|
||||
switch r.Header.Get("Content-Type") {
|
||||
case "application/json":
|
||||
b, _ := ioutil.ReadAll(r.Body)
|
||||
r.Body = struct {
|
||||
io.Reader
|
||||
io.Closer
|
||||
}{
|
||||
Reader: bytes.NewReader(b),
|
||||
Closer: r.Body,
|
||||
}
|
||||
m := bson.M{}
|
||||
json.Unmarshal(b, &m)
|
||||
v, ok := m[key]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprint(v)
|
||||
default:
|
||||
return r.FormValue(key)
|
||||
}
|
||||
}
|
||||
|
||||
func getKeyForNamespace(ctx context.Context, g storage.RateLimitedGraph, namespace string) (string, error) {
|
||||
|
|
|
|||
|
|
@ -95,3 +95,30 @@ func TestGenerate(t *testing.T) {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestReadRequested(t *testing.T) {
|
||||
t.Run("form: ignore query params", func(t *testing.T) {
|
||||
r := httptest.NewRequest(http.MethodPost, "/a=c", nil)
|
||||
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
if got := readRequested(r, "a"); got != "" {
|
||||
t.Fatal(got)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("form: body beats query params", func(t *testing.T) {
|
||||
r := httptest.NewRequest(http.MethodPost, "/a=c", strings.NewReader(`a=b`))
|
||||
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
if got := readRequested(r, "a"); got != "b" {
|
||||
t.Fatal(got)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("json: OK", func(t *testing.T) {
|
||||
r := httptest.NewRequest(http.MethodPost, "/a=c", strings.NewReader(`{"a": "b"}`))
|
||||
r.Header.Set("Content-Type", "application/json")
|
||||
if got := readRequested(r, "a"); got != "b" {
|
||||
t.Fatal(got)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"local/dndex/server/auth"
|
||||
"local/gziphttp"
|
||||
"net/http"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
|
@ -69,3 +70,11 @@ func (rest *REST) shift(foo http.HandlerFunc) http.HandlerFunc {
|
|||
foo(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
func (rest *REST) deprefix(foo http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
newpath := strings.TrimPrefix(r.URL.Path, path.Join("/", config.New().APIPrefix))
|
||||
r.URL.Path = newpath
|
||||
foo(w, r)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -131,3 +131,20 @@ func TestMiddlewareShift(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMiddlewareDeprefix(t *testing.T) {
|
||||
resolved := ""
|
||||
bar := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
resolved = r.URL.Path
|
||||
})
|
||||
|
||||
os.Args = []string{"a"}
|
||||
os.Setenv("API_PREFIX", "myprefix")
|
||||
r := httptest.NewRequest(http.MethodGet, "/myprefix/abc", nil)
|
||||
rest := &REST{}
|
||||
rest.deprefix(bar)(nil, r)
|
||||
|
||||
if resolved != "/abc" {
|
||||
t.Fatal(resolved)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"local/dndex/storage"
|
||||
"local/router"
|
||||
"net/http"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
|
|
@ -42,11 +43,11 @@ func NewREST(g storage.RateLimitedGraph) (*REST, error) {
|
|||
fmt.Sprintf("dump"): rest.dump,
|
||||
}
|
||||
|
||||
for path, foo := range paths {
|
||||
for urlpath, foo := range paths {
|
||||
bar := foo
|
||||
bar = rest.shift(bar)
|
||||
bar = rest.scoped(bar)
|
||||
switch strings.Split(path, "/")[0] {
|
||||
switch strings.Split(urlpath, "/")[0] {
|
||||
case "users":
|
||||
case "version":
|
||||
default:
|
||||
|
|
@ -54,7 +55,9 @@ func NewREST(g storage.RateLimitedGraph) (*REST, error) {
|
|||
}
|
||||
bar = rest.defend(bar)
|
||||
bar = rest.delay(bar)
|
||||
if err := rest.router.Add(path, bar); err != nil {
|
||||
bar = rest.deprefix(bar)
|
||||
routerpath := path.Join("/", config.New().APIPrefix, urlpath)
|
||||
if err := rest.router.Add(routerpath, bar); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -163,10 +163,10 @@ func TestRESTRouter(t *testing.T) {
|
|||
|
||||
for name, d := range cases {
|
||||
c := d
|
||||
path := name
|
||||
urlpath := path.Join("/", config.New().APIPrefix, name)
|
||||
rest, setAuth, clean := testREST(t)
|
||||
defer clean()
|
||||
r := httptest.NewRequest(c.method, path, strings.NewReader(``))
|
||||
r := httptest.NewRequest(c.method, urlpath, strings.NewReader(``))
|
||||
setAuth(r)
|
||||
w := httptest.NewRecorder()
|
||||
rest.router.ServeHTTP(w, r)
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ func (rest *REST) users(w http.ResponseWriter, r *http.Request) {
|
|||
rest.respNotFound(w)
|
||||
return
|
||||
}
|
||||
r.Header.Set("Application-Type", "application/x-www-form-urlencoded")
|
||||
rest.usersContentType(r)
|
||||
switch r.URL.Path {
|
||||
case "/register":
|
||||
rest.usersRegister(w, r)
|
||||
|
|
@ -28,6 +28,7 @@ func (rest *REST) users(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
func (rest *REST) usersRegister(w http.ResponseWriter, r *http.Request) {
|
||||
rest.usersContentType(r)
|
||||
err := auth.Register(rest.g, r)
|
||||
if err != nil {
|
||||
rest.respError(w, err)
|
||||
|
|
@ -37,6 +38,7 @@ func (rest *REST) usersRegister(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
func (rest *REST) usersLogin(w http.ResponseWriter, r *http.Request) {
|
||||
rest.usersContentType(r)
|
||||
salt := uuid.New().String()[:5]
|
||||
var token string
|
||||
var err error
|
||||
|
|
@ -56,3 +58,9 @@ func (rest *REST) usersLogin(w http.ResponseWriter, r *http.Request) {
|
|||
"salt": salt,
|
||||
})
|
||||
}
|
||||
|
||||
func (rest *REST) usersContentType(r *http.Request) {
|
||||
if r.Header.Get("Application-Type") == "" {
|
||||
r.Header.Set("Application-Type", "application/x-www-form-urlencoded")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,9 +17,11 @@ func TestUsersRegister(t *testing.T) {
|
|||
defer clean()
|
||||
|
||||
t.Run("register ok", func(t *testing.T) {
|
||||
user := uuid.New().String()[:5]
|
||||
pwd := uuid.New().String()[:5]
|
||||
testRegisterOK(t, rest, user, pwd)
|
||||
for _, json := range []bool{false, true} {
|
||||
user := uuid.New().String()[:5]
|
||||
pwd := uuid.New().String()[:5]
|
||||
testRegisterOK(t, rest, user, pwd, json)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("register 400: nil body", func(t *testing.T) {
|
||||
|
|
@ -76,36 +78,54 @@ func TestUsersLogin(t *testing.T) {
|
|||
defer clean()
|
||||
|
||||
t.Run("login ok", func(t *testing.T) {
|
||||
user := uuid.New().String()[:5]
|
||||
pwd := uuid.New().String()[:5]
|
||||
testRegisterOK(t, rest, user, pwd)
|
||||
testLoginOK(t, rest, user, pwd)
|
||||
for _, json := range []bool{false, true} {
|
||||
user := uuid.New().String()[:5]
|
||||
pwd := uuid.New().String()[:5]
|
||||
testRegisterOK(t, rest, user, pwd, json)
|
||||
testLoginOK(t, rest, user, pwd, json)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("login 404 user", func(t *testing.T) {
|
||||
pwd := uuid.New().String()[:5]
|
||||
testLoginNotOK(t, rest, "bad", pwd)
|
||||
for _, json := range []bool{false, true} {
|
||||
pwd := uuid.New().String()[:5]
|
||||
testLoginNotOK(t, rest, "bad", pwd, json)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("login bad user", func(t *testing.T) {
|
||||
user := uuid.New().String()[:5]
|
||||
pwd := uuid.New().String()[:5]
|
||||
testRegisterOK(t, rest, user, pwd)
|
||||
testLoginNotOK(t, rest, "bad", pwd)
|
||||
for _, json := range []bool{false, true} {
|
||||
user := uuid.New().String()[:5]
|
||||
pwd := uuid.New().String()[:5]
|
||||
testRegisterOK(t, rest, user, pwd, json)
|
||||
testLoginNotOK(t, rest, "bad", pwd, json)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("login bad pwd", func(t *testing.T) {
|
||||
user := uuid.New().String()[:5]
|
||||
pwd := uuid.New().String()[:5]
|
||||
testRegisterOK(t, rest, user, pwd)
|
||||
testLoginNotOK(t, rest, user, "bad")
|
||||
for _, json := range []bool{false, true} {
|
||||
user := uuid.New().String()[:5]
|
||||
pwd := uuid.New().String()[:5]
|
||||
testRegisterOK(t, rest, user, pwd, json)
|
||||
testLoginNotOK(t, rest, user, "bad", json)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func testRegisterOK(t *testing.T, rest *REST, user, pwd string) {
|
||||
func testRegisterOK(t *testing.T, rest *REST, user, pwd string, useJSON bool) {
|
||||
body := fmt.Sprintf(`%s=%s&%s=%s`, auth.UserKey, user, auth.AuthKey, pwd)
|
||||
if useJSON {
|
||||
s, _ := json.Marshal(map[string]string{
|
||||
auth.UserKey: user,
|
||||
auth.AuthKey: pwd,
|
||||
})
|
||||
body = string(s)
|
||||
}
|
||||
r := httptest.NewRequest(http.MethodPost, "/register", strings.NewReader(body))
|
||||
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
if useJSON {
|
||||
r.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
w := httptest.NewRecorder()
|
||||
rest.users(w, r)
|
||||
if w.Code != http.StatusOK {
|
||||
|
|
@ -113,10 +133,19 @@ func testRegisterOK(t *testing.T, rest *REST, user, pwd string) {
|
|||
}
|
||||
}
|
||||
|
||||
func testLoginNotOK(t *testing.T, rest *REST, user, pwd string) {
|
||||
func testLoginNotOK(t *testing.T, rest *REST, user, pwd string, useJSON bool) {
|
||||
body := fmt.Sprintf(`%s=%s`, auth.UserKey, user)
|
||||
if useJSON {
|
||||
s, _ := json.Marshal(map[string]string{
|
||||
auth.UserKey: user,
|
||||
})
|
||||
body = string(s)
|
||||
}
|
||||
r := httptest.NewRequest(http.MethodPost, "/login", strings.NewReader(body))
|
||||
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
if useJSON {
|
||||
r.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
w := httptest.NewRecorder()
|
||||
rest.users(w, r)
|
||||
if w.Code < http.StatusBadRequest {
|
||||
|
|
@ -136,10 +165,19 @@ func testLoginNotOK(t *testing.T, rest *REST, user, pwd string) {
|
|||
}
|
||||
}
|
||||
|
||||
func testLoginOK(t *testing.T, rest *REST, user, pwd string) string {
|
||||
func testLoginOK(t *testing.T, rest *REST, user, pwd string, useJSON bool) string {
|
||||
body := fmt.Sprintf(`%s=%s`, auth.UserKey, user)
|
||||
if useJSON {
|
||||
s, _ := json.Marshal(map[string]string{
|
||||
auth.UserKey: user,
|
||||
})
|
||||
body = string(s)
|
||||
}
|
||||
r := httptest.NewRequest(http.MethodPost, "/login", strings.NewReader(body))
|
||||
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
if useJSON {
|
||||
r.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
w := httptest.NewRecorder()
|
||||
rest.users(w, r)
|
||||
if w.Code != http.StatusOK {
|
||||
|
|
|
|||
Loading…
Reference in New Issue