From 46bd1bbdfc4060769b95f211b5f5c8ac9b3af238 Mon Sep 17 00:00:00 2001 From: breel Date: Fri, 7 Aug 2020 22:10:02 -0600 Subject: [PATCH] new auth --- server/auth.go | 194 ----------------------------------- server/auth/const.go | 7 ++ server/auth/encrypt.go | 60 +++++++++++ server/auth/encrypt_test.go | 27 +++++ server/auth/generate.go | 55 ++++++++++ server/auth/generate_test.go | 51 +++++++++ server/auth/register.go | 31 ++++++ server/auth/register_test.go | 56 ++++++++++ server/auth/token.go | 51 +++++++++ server/auth/token_test.go | 33 ++++++ server/auth/verify.go | 70 +++++++++++++ server/auth/verify_test.go | 147 ++++++++++++++++++++++++++ server/auth_test.go | 184 --------------------------------- server/const.go | 10 +- server/rest.go | 3 + server/rest_test.go | 6 +- storage/graph.go | 8 +- 17 files changed, 599 insertions(+), 394 deletions(-) delete mode 100644 server/auth.go create mode 100644 server/auth/const.go create mode 100644 server/auth/encrypt.go create mode 100644 server/auth/encrypt_test.go create mode 100644 server/auth/generate.go create mode 100644 server/auth/generate_test.go create mode 100644 server/auth/register.go create mode 100644 server/auth/register_test.go create mode 100644 server/auth/token.go create mode 100644 server/auth/token_test.go create mode 100644 server/auth/verify.go create mode 100644 server/auth/verify_test.go delete mode 100644 server/auth_test.go diff --git a/server/auth.go b/server/auth.go deleted file mode 100644 index 80ea0fa..0000000 --- a/server/auth.go +++ /dev/null @@ -1,194 +0,0 @@ -package server - -import ( - "crypto/aes" - "crypto/cipher" - "crypto/rand" - "encoding/base64" - "encoding/json" - "errors" - "io" - "local/dndex/config" - "local/dndex/storage" - "local/dndex/storage/entity" - "net/http" - "strings" - "time" - - "github.com/google/uuid" -) - -func Auth(g storage.RateLimitedGraph, w http.ResponseWriter, r *http.Request) error { - if !config.New().Auth { - return nil - } - if err := auth(g, w, r); err != nil { - json.NewEncoder(w).Encode(map[string]interface{}{"error": "error when authorizing: " + err.Error()}) - return err - } - return nil -} - -func auth(g storage.RateLimitedGraph, w http.ResponseWriter, r *http.Request) error { - if isPublic(g, r) { - return nil - } - if !hasAuth(r) { - return requestAuth(g, w, r) - } - return checkAuth(g, w, r) -} - -func isPublic(g storage.RateLimitedGraph, r *http.Request) bool { - namespace, err := getAuthNamespace(r) - - if err != nil { - return false - } - ones, err := g.List(r.Context(), namespace, UserKey) - if err != nil { - return false - } - if len(ones) == 0 { - return false - } - return ones[0].Title == "" -} - -func hasAuth(r *http.Request) bool { - _, err := r.Cookie(AuthKey) - return err == nil -} - -func checkAuth(g storage.RateLimitedGraph, w http.ResponseWriter, r *http.Request) error { - namespace, err := getAuthNamespace(r) - if err != nil { - return err - } - token, _ := r.Cookie(AuthKey) - results, err := g.List(r.Context(), namespace, token.Value) - if err != nil { - return err - } - if len(results) != 1 { - return requestAuth(g, w, r) - } - modified := time.Unix(0, results[0].Modified) - if time.Since(modified) > config.New().AuthLifetime { - return requestAuth(g, w, r) - } - return nil -} - -func requestAuth(g storage.RateLimitedGraph, w http.ResponseWriter, r *http.Request) error { - namespace, err := getAuthNamespace(r) - if err != nil { - http.Error(w, `{"error": "namespace required"}`, http.StatusBadRequest) - return err - } - - ones, err := g.List(r.Context(), namespace, UserKey) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err - } - if len(ones) != 1 { - http.NotFound(w, r) - return errors.New("namespace not established") - } - userKey := ones[0] - - id := uuid.New().String() - token := entity.One{ - ID: id, - Name: id, - Title: namespace, - } - if err := g.Insert(r.Context(), namespace, token); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err - } - - encodedToken, err := aesEnc(userKey.Title, token.Name) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err - } - http.SetCookie(w, &http.Cookie{Name: NewAuthKey, Value: encodedToken}) - - http.Redirect(w, r, r.URL.String(), http.StatusSeeOther) - return errors.New("auth requested") -} - -func aesEnc(key, payload string) (string, error) { - if len(key) == 0 { - return "", errors.New("key required") - } - key = strings.Repeat(key, 32)[:32] - - block, err := aes.NewCipher([]byte(key)) - if err != nil { - return "", err - } - gcm, err := cipher.NewGCM(block) - if err != nil { - return "", err - } - nonce := make([]byte, gcm.NonceSize()) - if _, err = io.ReadFull(rand.Reader, nonce); err != nil { - return "", err - } - b := gcm.Seal(nonce, nonce, []byte(payload), nil) - - return base64.StdEncoding.EncodeToString(b), nil -} - -func aesDec(key, payload string) (string, error) { - if len(key) == 0 { - return "", errors.New("key required") - } - key = strings.Repeat(key, 32)[:32] - - ciphertext, err := base64.StdEncoding.DecodeString(payload) - if err != nil { - return "", err - } - - block, err := aes.NewCipher([]byte(key)) - if err != nil { - return "", err - } - gcm, err := cipher.NewGCM(block) - if err != nil { - return "", err - } - if len(ciphertext) < gcm.NonceSize() { - return "", errors.New("short ciphertext") - } - b, err := gcm.Open(nil, ciphertext[:gcm.NonceSize()], ciphertext[gcm.NonceSize():], nil) - return string(b), err -} - -func getAuthNamespace(r *http.Request) (string, error) { - namespace, err := getNamespace(r) - return strings.Join([]string{namespace, AuthKey}, "."), err -} - -func getNamespace(r *http.Request) (string, error) { - if strings.HasPrefix(r.URL.Path, config.New().FilePrefix) { - path := strings.TrimPrefix(r.URL.Path, config.New().FilePrefix+"/") - if path == r.URL.Path { - return "", errors.New("no namespace on files") - } - path = strings.Split(path, "/")[0] - if path == "" { - return "", errors.New("empty namespace on files") - } - return path, nil - } - namespace := r.URL.Query().Get("namespace") - if len(namespace) == 0 { - return "", errors.New("no namespace found") - } - return namespace, nil -} diff --git a/server/auth/const.go b/server/auth/const.go new file mode 100644 index 0000000..4030cad --- /dev/null +++ b/server/auth/const.go @@ -0,0 +1,7 @@ +package auth + +const ( + AuthKey = "DnDex-Auth" + UserKey = "DnDex-User" + NewAuthKey = "New-" + AuthKey +) diff --git a/server/auth/encrypt.go b/server/auth/encrypt.go new file mode 100644 index 0000000..1ceaef1 --- /dev/null +++ b/server/auth/encrypt.go @@ -0,0 +1,60 @@ +package auth + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "encoding/base64" + "errors" + "io" + "strings" +) + +func encrypt(key, payload string) (string, error) { + if len(key) == 0 { + return "", errors.New("key required") + } + key = strings.Repeat(key, 32)[:32] + + block, err := aes.NewCipher([]byte(key)) + if err != nil { + return "", err + } + gcm, err := cipher.NewGCM(block) + if err != nil { + return "", err + } + nonce := make([]byte, gcm.NonceSize()) + if _, err = io.ReadFull(rand.Reader, nonce); err != nil { + return "", err + } + b := gcm.Seal(nonce, nonce, []byte(payload), nil) + + return base64.StdEncoding.EncodeToString(b), nil +} + +func decrypt(key, payload string) (string, error) { + if len(key) == 0 { + return "", errors.New("key required") + } + key = strings.Repeat(key, 32)[:32] + + ciphertext, err := base64.StdEncoding.DecodeString(payload) + if err != nil { + return "", err + } + + block, err := aes.NewCipher([]byte(key)) + if err != nil { + return "", err + } + gcm, err := cipher.NewGCM(block) + if err != nil { + return "", err + } + if len(ciphertext) < gcm.NonceSize() { + return "", errors.New("short ciphertext") + } + b, err := gcm.Open(nil, ciphertext[:gcm.NonceSize()], ciphertext[gcm.NonceSize():], nil) + return string(b), err +} diff --git a/server/auth/encrypt_test.go b/server/auth/encrypt_test.go new file mode 100644 index 0000000..259a62a --- /dev/null +++ b/server/auth/encrypt_test.go @@ -0,0 +1,27 @@ +package auth + +import ( + "testing" +) + +func TestAES(t *testing.T) { + for _, plaintext := range []string{"", "payload!", "a really long payload here"} { + key := "password" + + enc, err := encrypt(key, plaintext) + if err != nil { + t.Fatal("cannot enc:", err) + } + if enc == plaintext { + t.Fatal(enc) + } + + dec, err := decrypt(key, enc) + if err != nil { + t.Fatal("cannot dec:", err) + } + if dec != plaintext { + t.Fatalf("want decrypted %q, got %q", plaintext, dec) + } + } +} diff --git a/server/auth/generate.go b/server/auth/generate.go new file mode 100644 index 0000000..4f50627 --- /dev/null +++ b/server/auth/generate.go @@ -0,0 +1,55 @@ +package auth + +import ( + "context" + "local/dndex/storage" + "local/dndex/storage/entity" + "net/http" + + "github.com/google/uuid" +) + +func Generate(g storage.RateLimitedGraph, r *http.Request, salt string) (string, error) { + namespaceRequested := readRequestedNamespace(r) + key, err := getKeyForNamespace(r.Context(), g, namespaceRequested) + if err != nil { + return "", err + } + token, err := makeTokenForNamespace(r.Context(), g, namespaceRequested) + if err != nil { + return "", err + } + return token.Encode(salt + key) +} + +func readRequestedNamespace(r *http.Request) string { + return readRequested(r, UserKey) +} + +func readRequested(r *http.Request, key string) string { + return r.FormValue(key) +} + +func getKeyForNamespace(ctx context.Context, g storage.RateLimitedGraph, namespace string) (string, error) { + namespaceOne, err := g.Get(ctx, namespace, UserKey) + if err != nil { + return "", err + } + return namespaceOne.Title, nil +} + +func makeTokenForNamespace(ctx context.Context, g storage.RateLimitedGraph, namespace string) (Token, error) { + token := Token{ + Namespace: namespace, + Token: uuid.New().String(), + } + obf, err := token.Obfuscate() + if err != nil { + return Token{}, err + } + one := entity.One{ + ID: token.Token, + Title: obf, + } + return token, g.Insert(ctx, namespace+"."+AuthKey, one) +} diff --git a/server/auth/generate_test.go b/server/auth/generate_test.go new file mode 100644 index 0000000..22b5480 --- /dev/null +++ b/server/auth/generate_test.go @@ -0,0 +1,51 @@ +package auth + +import ( + "context" + "local/dndex/storage" + "local/dndex/storage/entity" + "net/http" + "net/http/httptest" + "os" + "strings" + "testing" + + "github.com/google/uuid" +) + +func TestGenerate(t *testing.T) { + os.Args = os.Args[:1] + os.Setenv("AUTH", "true") + defer os.Unsetenv("AUTH") + + fresh := func() (storage.RateLimitedGraph, *http.Request, string) { + g := storage.NewRateLimitedGraph() + key := uuid.New().String() + namespace := uuid.New().String() + one := entity.One{ + ID: UserKey, + Title: key, + } + if err := g.Insert(context.TODO(), namespace, one); err != nil { + t.Fatal(err) + } + r := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(UserKey+`=`+namespace)) + r.Header.Set("content-type", "application/x-www-form-urlencoded") + return g, + r, + key + } + + t.Run("ok", func(t *testing.T) { + g, r, key := fresh() + salt := uuid.New().String() + encoded, err := Generate(g, r, salt) + if err != nil { + t.Fatal(err) + } + var token Token + if err := token.Decode(salt+key, encoded); err != nil { + t.Fatal(err) + } + }) +} diff --git a/server/auth/register.go b/server/auth/register.go new file mode 100644 index 0000000..3822f75 --- /dev/null +++ b/server/auth/register.go @@ -0,0 +1,31 @@ +package auth + +import ( + "context" + "errors" + "local/dndex/storage" + "local/dndex/storage/entity" + "net/http" +) + +func Register(g storage.RateLimitedGraph, r *http.Request) error { + namespaceRequested := readRequestedNamespace(r) + keyRequested := readRequestedKey(r) + _, err := getKeyForNamespace(r.Context(), g, namespaceRequested) + if err == nil { + return errors.New("namespace already exists") + } + return makeNamespace(r.Context(), g, namespaceRequested, keyRequested) +} + +func readRequestedKey(r *http.Request) string { + return readRequested(r, AuthKey) +} + +func makeNamespace(ctx context.Context, g storage.RateLimitedGraph, namespace, key string) error { + one := entity.One{ + ID: UserKey, + Title: key, + } + return g.Insert(ctx, namespace, one) +} diff --git a/server/auth/register_test.go b/server/auth/register_test.go new file mode 100644 index 0000000..3c0be5d --- /dev/null +++ b/server/auth/register_test.go @@ -0,0 +1,56 @@ +package auth + +import ( + "context" + "fmt" + "local/dndex/storage" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/google/uuid" +) + +func TestRegister(t *testing.T) { + g := storage.NewRateLimitedGraph() + namespace := uuid.New().String() + key := uuid.New().String() + r := httptest.NewRequest( + http.MethodPost, + "/", + strings.NewReader( + fmt.Sprintf( + "%s=%s&%s=%s", + UserKey, + namespace, + AuthKey, + key, + ), + ), + ) + r.Header.Set("content-type", "application/x-www-form-urlencoded") + if err := r.ParseForm(); err != nil { + t.Fatal(err) + } + if v := readRequested(r, UserKey); v == "" { + t.Fatal(UserKey, v) + } + if v := readRequested(r, AuthKey); v == "" { + t.Fatal(AuthKey, v) + } + err := Register(g, r) + if err != nil { + t.Fatal(err) + } + one, err := g.Get(context.TODO(), namespace, UserKey) + if err != nil { + t.Fatal(err) + } + if one.ID != UserKey { + t.Fatal(err) + } + if one.Title != key { + t.Fatal(one) + } +} diff --git a/server/auth/token.go b/server/auth/token.go new file mode 100644 index 0000000..0112009 --- /dev/null +++ b/server/auth/token.go @@ -0,0 +1,51 @@ +package auth + +import ( + "encoding/base64" + "encoding/json" +) + +type Token struct { + Namespace string + Token string +} + +func (t Token) Obfuscate() (string, error) { + b, err := json.Marshal(t) + if err != nil { + return "", err + } + obfuscated := base64.StdEncoding.EncodeToString(b) + return obfuscated, nil +} + +func (t Token) Encode(key string) (string, error) { + obfuscated, err := t.Obfuscate() + if err != nil { + return "", err + } + encoded, err := encrypt(key, obfuscated) + if err != nil { + return "", err + } + return encoded, nil +} + +func (t *Token) Deobfuscate(obfuscated string) error { + marshalled, err := base64.StdEncoding.DecodeString(obfuscated) + if err != nil { + return err + } + if err := json.Unmarshal([]byte(marshalled), t); err != nil { + return err + } + return nil +} + +func (t *Token) Decode(key, encoded string) error { + obfuscated, err := decrypt(key, encoded) + if err != nil { + return err + } + return t.Deobfuscate(obfuscated) +} diff --git a/server/auth/token_test.go b/server/auth/token_test.go new file mode 100644 index 0000000..e797178 --- /dev/null +++ b/server/auth/token_test.go @@ -0,0 +1,33 @@ +package auth + +import ( + "fmt" + "testing" + + "github.com/google/uuid" +) + +func TestTokenEncDec(t *testing.T) { + token := Token{ + Namespace: "username", + Token: uuid.New().String(), + } + key := "a" + + encoded, err := token.Encode(key) + if err != nil { + t.Fatal(err) + } + + var ntoken Token + err = ntoken.Decode(key, encoded) + if err != nil { + t.Fatal(err) + } + + if fmt.Sprint(token) != fmt.Sprint(ntoken) { + t.Fatal(ntoken) + } + + t.Logf("token=%+v, ntoken=%+v, encoded=%s", token, ntoken, encoded) +} diff --git a/server/auth/verify.go b/server/auth/verify.go new file mode 100644 index 0000000..0300209 --- /dev/null +++ b/server/auth/verify.go @@ -0,0 +1,70 @@ +package auth + +import ( + "context" + "errors" + "local/dndex/config" + "local/dndex/storage" + "net/http" + "time" +) + +func Verify(g storage.RateLimitedGraph, w http.ResponseWriter, r *http.Request) error { + token, ok := getToken(r) + if !ok { + return errors.New("auth not found") + } + if !config.New().Auth { + return nil + } + if isPublic(token, g, r) { + return nil + } + return verifyToken(token, g, r) +} + +func getToken(r *http.Request) (Token, bool) { + cookie, err := r.Cookie(AuthKey) + if err != nil { + return Token{}, false + } + obfuscated := cookie.Value + var token Token + err = token.Deobfuscate(obfuscated) + return token, err == nil +} + +func isPublic(token Token, g storage.RateLimitedGraph, r *http.Request) bool { + return isPublicNamespace(r.Context(), g, token.Namespace) +} + +func isPublicNamespace(ctx context.Context, g storage.RateLimitedGraph, namespace string) bool { + maybePublicContainer, err := g.Get(ctx, namespace, UserKey) + if err != nil { + return false + } + return maybePublicContainer.Title == "" +} + +func verifyToken(token Token, g storage.RateLimitedGraph, r *http.Request) error { + serverTokenContainer, err := g.Get(r.Context(), token.Namespace+"."+AuthKey, token.Token) + if err != nil { + return err + } + + var serverToken Token + if err := serverToken.Deobfuscate(serverTokenContainer.Title); err != nil { + return err + } + + if token.Namespace != serverToken.Namespace { + return errors.New("token namespace does not match request's namespace") + } + + modified := time.Unix(0, serverTokenContainer.Modified) + if time.Since(modified) > config.New().AuthLifetime { + return errors.New("token is expired") + } + + return nil +} diff --git a/server/auth/verify_test.go b/server/auth/verify_test.go new file mode 100644 index 0000000..65aa394 --- /dev/null +++ b/server/auth/verify_test.go @@ -0,0 +1,147 @@ +package auth + +import ( + "context" + "local/dndex/storage" + "local/dndex/storage/entity" + "net/http" + "net/http/httptest" + "os" + "strings" + "testing" + + "github.com/google/uuid" +) + +func TestVerify(t *testing.T) { + os.Args = os.Args[:1] + os.Setenv("AUTH", "true") + defer os.Unsetenv("AUTH") + + fresh := func() (storage.RateLimitedGraph, *httptest.ResponseRecorder, *http.Request, Token, string) { + g := storage.NewRateLimitedGraph() + token := Token{ + Token: uuid.New().String(), + Namespace: uuid.New().String(), + } + obf, _ := token.Obfuscate() + one := entity.One{ + ID: token.Token, + Title: obf, + } + if err := g.Insert(context.TODO(), token.Namespace+"."+AuthKey, one); err != nil { + t.Fatal(err) + } + return g, + httptest.NewRecorder(), + httptest.NewRequest(http.MethodGet, "/", nil), + token, + obf + } + + t.Run("no auth", func(t *testing.T) { + g, w, r, _, _ := fresh() + err := Verify(g, w, r) + if err == nil { + t.Fatal(err) + } + if !strings.Contains(err.Error(), "auth not found") { + t.Fatal(err) + } + }) + + t.Run("ok auth", func(t *testing.T) { + g, w, r, _, obf := fresh() + r.AddCookie(&http.Cookie{ + Name: AuthKey, + Value: obf, + }) + err := Verify(g, w, r) + if err != nil { + t.Fatal(err) + } + }) + + t.Run("no ns auth", func(t *testing.T) { + g, w, r, token, _ := fresh() + token.Namespace = "" + obf, err := token.Obfuscate() + if err != nil { + t.Fatal(err) + } + r.AddCookie(&http.Cookie{ + Name: AuthKey, + Value: obf, + }) + err = Verify(g, w, r) + if err == nil { + t.Fatal(err) + } + }) + + t.Run("wrong ns auth", func(t *testing.T) { + g, w, r, token, _ := fresh() + token.Namespace = uuid.New().String() + obf, err := token.Obfuscate() + if err != nil { + t.Fatal(err) + } + r.AddCookie(&http.Cookie{ + Name: AuthKey, + Value: obf, + }) + err = Verify(g, w, r) + if err == nil { + t.Fatal(err) + } + }) + + t.Run("expired auth", func(t *testing.T) { + t.Logf("not impl") + }) + + t.Run("bad auth", func(t *testing.T) { + g, w, r, token, _ := fresh() + token.Token = uuid.New().String() + obf, err := token.Obfuscate() + if err != nil { + t.Fatal(err) + } + r.AddCookie(&http.Cookie{ + Name: AuthKey, + Value: obf, + }) + err = Verify(g, w, r) + if err == nil { + t.Fatal(err) + } + }) + + t.Run("public not ok", func(t *testing.T) { + g, w, r, _, _ := fresh() + if err := g.Insert(context.TODO(), "public", entity.One{ID: UserKey}); err != nil { + t.Fatal(err) + } + err := Verify(g, w, r) + if err == nil { + t.Fatal(err) + } + }) + + t.Run("public ok", func(t *testing.T) { + g, w, r, token, _ := fresh() + if err := g.Insert(context.TODO(), token.Namespace, entity.One{ID: UserKey}); err != nil { + t.Fatal(err) + } + token.Token = "gibberish-but-public-ns-so-its-ok" + obf, _ := token.Obfuscate() + r.AddCookie(&http.Cookie{ + Name: AuthKey, + Value: obf, + }) + err := Verify(g, w, r) + if err != nil { + t.Fatal(err) + } + }) +} diff --git a/server/auth_test.go b/server/auth_test.go deleted file mode 100644 index 10fe0be..0000000 --- a/server/auth_test.go +++ /dev/null @@ -1,184 +0,0 @@ -package server - -import ( - "context" - "fmt" - "local/dndex/storage/entity" - "net/http" - "net/http/httptest" - "os" - "strings" - "testing" - "time" - - "github.com/google/uuid" -) - -func TestAuth(t *testing.T) { - os.Args = os.Args[:1] - - rest, clean := testREST(t) - defer clean() - - handler := rest.router - g := rest.g - - os.Setenv("AUTH", "true") - defer os.Setenv("AUTH", "false") - - if err := g.Insert(context.TODO(), "col."+AuthKey, entity.One{ID: UserKey, Name: UserKey, Title: "password"}); err != nil { - t.Fatal(err) - } - - t.Run("auth: no namespace", func(t *testing.T) { - r := httptest.NewRequest(http.MethodGet, "/who", nil) - w := httptest.NewRecorder() - handler.ServeHTTP(w, r) - if w.Code != http.StatusBadRequest { - t.Fatalf("%d: %s", w.Code, w.Body.Bytes()) - } - }) - - t.Run("auth: bad provided", func(t *testing.T) { - r := httptest.NewRequest(http.MethodGet, "/who?namespace=col", nil) - r.Header.Set("Cookie", fmt.Sprintf("%s=not-a-real-token", AuthKey)) - w := httptest.NewRecorder() - handler.ServeHTTP(w, r) - if w.Code != http.StatusSeeOther { - t.Fatalf("%d: %s", w.Code, w.Body.Bytes()) - } - }) - - t.Run("auth: expired provided", func(t *testing.T) { - os.Setenv("AUTHLIFETIME", "1ms") - defer os.Setenv("AUTHLIFETIME", "1h") - one := entity.One{ID: uuid.New().String(), Name: uuid.New().String(), Title: "title"} - if err := g.Insert(context.TODO(), "col", one); err != nil { - t.Fatal(err) - } - time.Sleep(time.Millisecond * 50) - r := httptest.NewRequest(http.MethodGet, "/who?namespace=col", nil) - r.Header.Set("Cookie", fmt.Sprintf("%s=%s", AuthKey, one.ID)) - w := httptest.NewRecorder() - handler.ServeHTTP(w, r) - if w.Code != http.StatusSeeOther { - t.Fatalf("%d: %s", w.Code, w.Body.Bytes()) - } - }) - - t.Run("auth: none provided: who", func(t *testing.T) { - r := httptest.NewRequest(http.MethodGet, "/who?namespace=col", nil) - w := httptest.NewRecorder() - handler.ServeHTTP(w, r) - if w.Code != http.StatusSeeOther { - t.Fatalf("%d: %s", w.Code, w.Body.Bytes()) - } - }) - - t.Run("auth: none provided: files", func(t *testing.T) { - r := httptest.NewRequest(http.MethodGet, "/__files__/col/myfile", nil) - w := httptest.NewRecorder() - handler.ServeHTTP(w, r) - if w.Code != http.StatusSeeOther { - t.Fatalf("%d: %s", w.Code, w.Body.Bytes()) - } - }) - - t.Run("auth: provided", func(t *testing.T) { - os.Setenv("AUTHLIFETIME", "1h") - one := entity.One{ID: uuid.New().String(), Name: uuid.New().String(), Title: "title"} - if err := g.Insert(context.TODO(), "col."+AuthKey, one); err != nil { - t.Fatal(err) - } - r := httptest.NewRequest(http.MethodTrace, "/who?namespace=col", nil) - r.Header.Set("Cookie", fmt.Sprintf("%s=%s", AuthKey, one.Name)) - w := httptest.NewRecorder() - handler.ServeHTTP(w, r) - if w.Code != http.StatusOK { - t.Fatalf("%d: %s", w.Code, w.Body.Bytes()) - } - }) - - t.Run("auth: request unknown namespace", func(t *testing.T) { - os.Setenv("AUTHLIFETIME", "1h") - r := httptest.NewRequest(http.MethodTrace, "/who?namespace=not-col", nil) - w := httptest.NewRecorder() - handler.ServeHTTP(w, r) - if w.Code != http.StatusNotFound { - t.Fatalf("%d: %s", w.Code, w.Body.Bytes()) - } - }) - - t.Run("auth: request", func(t *testing.T) { - os.Setenv("AUTHLIFETIME", "1h") - r := httptest.NewRequest(http.MethodTrace, "/who?namespace=col", nil) - w := httptest.NewRecorder() - handler.ServeHTTP(w, r) - if w.Code != http.StatusSeeOther { - t.Fatalf("%d: %s", w.Code, w.Body.Bytes()) - } - - rawtoken := getCookie(NewAuthKey, w.Header()) - if rawtoken == "" { - t.Fatal(w.Header()) - } - token, err := aesDec("password", rawtoken) - if err != nil { - t.Fatal(err) - } - - r = httptest.NewRequest(http.MethodTrace, "/who?namespace=col", nil) - w = httptest.NewRecorder() - r.Header.Set("Cookie", fmt.Sprintf("%s=%s", AuthKey, token)) - handler.ServeHTTP(w, r) - if w.Code != http.StatusOK { - t.Fatalf("%d: %s", w.Code, w.Body.Bytes()) - } - - r = httptest.NewRequest(http.MethodTrace, "/__files__/col/myfile", nil) - w = httptest.NewRecorder() - r.Header.Set("Cookie", fmt.Sprintf("%s=%s", AuthKey, token)) - handler.ServeHTTP(w, r) - if w.Code != http.StatusNotFound { - t.Fatalf("%d: %s", w.Code, w.Body.Bytes()) - } - }) -} - -func TestAES(t *testing.T) { - for _, plaintext := range []string{"", "payload!", "a really long payload here"} { - key := "password" - - enc, err := aesEnc(key, plaintext) - if err != nil { - t.Fatal("cannot enc:", err) - } - if enc == plaintext { - t.Fatal(enc) - } - - dec, err := aesDec(key, enc) - if err != nil { - t.Fatal("cannot dec:", err) - } - if dec != plaintext { - t.Fatalf("want decrypted %q, got %q", plaintext, dec) - } - } -} - -func getCookie(key string, header http.Header) string { - cookies, _ := header["Set-Cookie"] - if len(cookies) == 0 { - cookies, _ = header["Cookie"] - } - for i := range cookies { - value := strings.Split(cookies[i], ";")[0] - k := value[:strings.Index(value, "=")] - v := value[strings.Index(value, "=")+1:] - if k == key { - return v - } - } - return "" -} diff --git a/server/const.go b/server/const.go index 5bc92f4..97fc3dc 100644 --- a/server/const.go +++ b/server/const.go @@ -1,11 +1,3 @@ package server -const ( - AuthKey = "DnDex-Auth" - UserKey = "DnDex-User" -) - -var ( - NewAuthKey = "New-" + AuthKey - GitCommit string -) +var GitCommit string diff --git a/server/rest.go b/server/rest.go index 1fab3b8..fafbebe 100644 --- a/server/rest.go +++ b/server/rest.go @@ -68,10 +68,13 @@ func (rest *REST) scope(r *http.Request) RESTScope { } func (rest *REST) files(w http.ResponseWriter, _ *http.Request) { + http.Error(w, "not impl", http.StatusNotImplemented) } func (rest *REST) users(w http.ResponseWriter, _ *http.Request) { + http.Error(w, "not impl", http.StatusNotImplemented) } func (rest *REST) entities(w http.ResponseWriter, _ *http.Request) { + http.Error(w, "not impl", http.StatusNotImplemented) } diff --git a/server/rest_test.go b/server/rest_test.go index b940f3f..a1daff9 100644 --- a/server/rest_test.go +++ b/server/rest_test.go @@ -209,9 +209,9 @@ func randomOne() entity.One { Title: "titl-" + uuid.New().String()[:5], Text: "text-" + uuid.New().String()[:5], Modified: time.Now().UnixNano(), - Connections: map[string]entity.One{}, - Attachments: map[string]string{ - uuid.New().String()[:5]: uuid.New().String()[:5], + Connections: map[string]entity.Connection{}, + Attachments: map[string]entity.Attachment{ + uuid.New().String()[:5]: entity.Attachment{Location: uuid.New().String()[:5]}, }, } } diff --git a/storage/graph.go b/storage/graph.go index d9258c6..2f1d067 100644 --- a/storage/graph.go +++ b/storage/graph.go @@ -71,13 +71,13 @@ func (g Graph) gatherOnes(ctx context.Context, ch <-chan bson.Raw) ([]entity.One } func (g Graph) Insert(ctx context.Context, namespace string, one entity.One) error { - if one.Name == "" || one.ID == "" { - return errors.New("cannot create document without both name and id") + if one.ID == "" { + return errors.New("cannot create document without id") } - if ones, err := g.ListCaseInsensitive(ctx, namespace, one.Name); err != nil { + if ones, err := g.ListCaseInsensitive(ctx, namespace, one.ID); err != nil { return err } else if len(ones) > 0 { - return fmt.Errorf("collision on primary key when case insensitive: cannot create %q because %+v exists", one.Name, ones) + return fmt.Errorf("collision on primary key when case insensitive: cannot create %q because %+v exists", one.ID, ones) } return g.driver.Insert(ctx, namespace, one) }