diff --git a/go.mod b/go.mod index 43ab7c0e4..374ff2eeb 100644 --- a/go.mod +++ b/go.mod @@ -39,8 +39,8 @@ require ( go.etcd.io/bbolt v1.3.6 // indirect go.mongodb.org/mongo-driver v1.8.1 golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b - golang.org/x/net v0.0.0-20211209124913-491a49abca63 - golang.org/x/sys v0.0.0-20211210111614-af8b64212486 // indirect + golang.org/x/net v0.0.0-20211216030914-fe4d6282115f + golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect gopkg.in/gavv/httpexpect.v2 v2.3.1 gopkg.in/sourcemap.v1 v1.0.5 // indirect ) @@ -53,6 +53,7 @@ require ( github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 // indirect github.com/fasthttp/websocket v1.4.3-rc.10 // indirect github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 // indirect + github.com/go-bindata/go-bindata/v3 v3.1.3 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect github.com/go-stack/stack v1.8.1 // indirect @@ -65,6 +66,7 @@ require ( github.com/gorilla/securecookie v1.1.1 // indirect github.com/gorilla/websocket v1.4.2 // indirect github.com/karrick/godirwalk v1.10.3 // indirect + github.com/kisielk/errcheck v1.6.0 // indirect github.com/klauspost/compress v1.13.6 // indirect github.com/labstack/echo v3.3.10+incompatible github.com/labstack/echo-contrib v0.11.0 @@ -85,6 +87,7 @@ require ( github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.31.0 // indirect github.com/valyala/fasttemplate v1.2.1 // indirect + github.com/wadey/gocovmerge v0.0.0-20160331181800-b5bfa59ec0ad // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect github.com/xdg-go/scram v1.0.2 // indirect github.com/xdg-go/stringprep v1.0.2 // indirect @@ -93,8 +96,10 @@ require ( github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect github.com/yudai/gojsondiff v1.0.0 // indirect github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect + github.com/yuin/goldmark v1.4.4 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect golang.org/x/image v0.0.0-20211028202545-6944b10bf410 // indirect + golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/tools v0.1.8 // indirect diff --git a/go.sum b/go.sum index 83e8d2c74..d5520fa7a 100644 --- a/go.sum +++ b/go.sum @@ -622,9 +622,11 @@ github.com/yudai/pp v2.0.1+incompatible h1:Q4//iY4pNF6yPLZIigmvcl7k/bPgrcTPIFIcm github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.4 h1:zNWRjYUW32G9KirMXYHQHVNFkXvMI7LpgNW2AgYAoIs= github.com/yuin/goldmark v1.4.4/go.mod h1:rmuwmfZ0+bvzB24eSC//bk1R1Zp3hM0OXYv/G2LIilg= +github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.etcd.io/bbolt v1.3.0/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= @@ -729,8 +731,11 @@ golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211209124913-491a49abca63 h1:iocB37TsdFuN6IBRZ+ry36wrkoV51/tl5vOWqkcPGvY= golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211216030914-fe4d6282115f h1:hEYJvxw1lSnWIl8X9ofsYMklzaDs90JI2az5YMd4fPM= +golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -799,11 +804,15 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211209171907-798191bca915 h1:P+8mCzuEpyszAT6T42q0sxU+eveBAF/cJ2Kp0x6/8+0= golang.org/x/sys v0.0.0-20211209171907-798191bca915/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211210111614-af8b64212486 h1:5hpz5aRr+W1erYCL5JRhSUBJRph7l9XkNveoExlrKYk= golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -854,6 +863,7 @@ golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.8 h1:P1HhGGuLW4aAclzjtmJdf0mJOjVUZUzOTqkAkWL+l6w= golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/storage/database/user.go b/storage/database/user.go index be7b357ef..f61891169 100644 --- a/storage/database/user.go +++ b/storage/database/user.go @@ -101,7 +101,13 @@ func (me *UserDB) APIKey(key string) (*model.User, error) { return nil, model.ErrAuthorityMissing } var userID string - err := me.db.Get(userApiKeyBucket, key, &userID) + tmpHashedKey := &model.ApiKey{ + Name: "", + Key: key, + } + tmpHashedKey.HideKey() + + err := me.db.Get(userApiKeyBucket, tmpHashedKey.Key, &userID) if err != nil { return nil, model.ErrAuthorityMissing } @@ -428,19 +434,21 @@ func (me *UserDB) save(u *model.User, tx db.DB) error { func (me *UserDB) updateApiKeys(u *model.User, tx db.DB) error { newKeys := make([]model.ApiKey, 0) + var existingKeys []model.ApiKey + _ = tx.Get(userApiKeysBucket, u.ID, &existingKeys) + for _, a := range u.ApiKeys { - if a.IsNew() { + if me.keyIsNew(existingKeys, a) { + a.HideKey() newKeys = append(newKeys, *a) err := tx.Set(userApiKeyBucket, a.Key, u.ID) if err != nil { return err } - a.HideKey() } } + if len(newKeys) > 0 { - var existingKeys []model.ApiKey - _ = tx.Get(userApiKeysBucket, u.ID, &existingKeys) if len(existingKeys) > 0 { newKeys = append(newKeys, existingKeys...) } @@ -452,6 +460,20 @@ func (me *UserDB) updateApiKeys(u *model.User, tx db.DB) error { return nil } +func (me *UserDB) keyIsNew(existingKeys []model.ApiKey, apiKey *model.ApiKey) bool { + exists := false + var tmp model.ApiKey + tmp.Key = apiKey.Key + tmp.HideKey() + for _, k := range existingKeys { + if k.Key == tmp.Key { + exists = true + break + } + } + return !exists +} + func (me *UserDB) setTinyUserIconBase64(item *model.User) error { if item.PhotoPath == "" { return nil diff --git a/storage/database/user_test.go b/storage/database/user_test.go index 07b86ef72..767d91b9c 100644 --- a/storage/database/user_test.go +++ b/storage/database/user_test.go @@ -59,7 +59,8 @@ func TestUser(t *testing.T) { const key = "f235122f9a1e4884123456788a2126f8dd76996b" val, err := us.CreateApiKey(dummySuperAdmin, item2.ID, key) Expect(err).To(Succeed()) - item2.ApiKeys = []*model.ApiKey{{Name: key, Key: val[:4] + "..." + val[36:]}} + item2.ApiKeys = []*model.ApiKey{{Name: key, Key: key}} + item2.ApiKeys[0].HideKey() Expect(us.APIKey(val)).To(equalJSON(item2)) Expect(us.DeleteApiKey(dummySuperAdmin, item2.ID, item2.ApiKeys[0].Key)).To(Succeed()) } diff --git a/sys/model/apikey.go b/sys/model/apikey.go index 466059a6b..146baeb1c 100644 --- a/sys/model/apikey.go +++ b/sys/model/apikey.go @@ -5,6 +5,7 @@ import ( "strings" uuid "github.com/satori/go.uuid" + "golang.org/x/crypto/bcrypt" ) const ApiKeyLength = 40 //example: f235122f9a1e4884123456788a2126f8dd76996b @@ -24,20 +25,21 @@ func NewApiKey(name, uid string) (*ApiKey, error) { return &ApiKey{Name: name, Key: k[0:16] + uid[0:8] + k[16:]}, nil } -//HideKey turns f235122f9a1e4884123456788a2126f8dd76996b into f235...996b +//HideKey hashes the key func (me *ApiKey) HideKey() { - if me.IsNew() { - me.Key = me.Key[0:4] + "..." + me.Key[len(me.Key)-4:] - } + me.Key, _ = hashString(me.Key) } -//IsNew returns true on keys like f235122f9a1e4884123456788a2126f8dd76996b -//but hidden keys like f235...996b are hidden and therefore not new anymore -func (me *ApiKey) IsNew() bool { - //if not hidden then new - return len(me.Key) > 11 +func MatchesApiKey(hiddenApiKey, apiKey string) bool { + return checkStringHash(apiKey, hiddenApiKey) } -func MatchesApiKey(hiddenApiKey, apiKey string) bool { - return strings.HasPrefix(apiKey, hiddenApiKey[0:4]) && strings.HasSuffix(apiKey, hiddenApiKey[len(hiddenApiKey)-4:]) +func hashString(str string) (string, error) { + bytes, err := bcrypt.GenerateFromPassword([]byte(str), 14) + return string(bytes), err +} + +func checkStringHash(str, hash string) bool { + err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(str)) + return err == nil } diff --git a/sys/model/user_test.go b/sys/model/user_test.go deleted file mode 100644 index 7e80add90..000000000 --- a/sys/model/user_test.go +++ /dev/null @@ -1,28 +0,0 @@ -package model - -import ( - "testing" -) - -func TestUser_HideApiKeys(t *testing.T) { - u := &User{ID: "123456789010"} - _, err := u.NewApiKey("key1") - if err != nil { - t.Error(err) - return - } - - _, err = u.NewApiKey("key2") - if err != nil { - t.Error(err) - return - } - for _, a := range u.ApiKeys { - a.HideKey() - } - for _, a := range u.ApiKeys { - if len(a.Key) != 11 { - t.Error("wrong length! key not hidden?") - } - } -} diff --git a/test/apikey_test.go b/test/apikey_test.go index c99c9eaae..c5928fccb 100644 --- a/test/apikey_test.go +++ b/test/apikey_test.go @@ -3,30 +3,36 @@ package test import ( "encoding/base64" "net/http" + + "github.com/ProxeusApp/proxeus-core/sys/model" ) func testApiKey(s *session) { u := registerTestUser(s) login(s, u) - apiKey, summary := createApiKey(s, u, "test-"+s.id) + apiKey, hashed := createApiKey(s, u, "test-"+s.id) logout(s) token := getSessionToken(s, u.username, apiKey) deleteSessionToken(s, token) login(s, u) - deleteApiKey(s, u, summary) + deleteApiKey(s, u, hashed) deleteUser(s, u) } func createApiKey(s *session, u *user, name string) (string, string) { key := s.e.GET("/api/user/create/api/key/{id}").WithPath("id", u.uuid).WithQuery("name", name).Expect().Status(http.StatusOK).Body().Raw() - summary := key[:4] + "..." + key[len(key)-4:] - s.e.GET("/api/me").Expect().Status(http.StatusOK).JSON().Path("$..Key").Array().Contains(summary) + tmpHashedKey := &model.ApiKey{ + Name: "", + Key: key, + } + tmpHashedKey.HideKey() + s.e.GET("/api/me").Expect().Status(http.StatusOK).JSON().Path("$..Key").Array().Contains(tmpHashedKey.Key) - return key, summary + return key, tmpHashedKey.Key } func getSessionToken(s *session, username, apiKey string) string { diff --git a/ui/core/src/components/user/ApiKey.vue b/ui/core/src/components/user/ApiKey.vue index 6bc06321c..2886736b6 100644 --- a/ui/core/src/components/user/ApiKey.vue +++ b/ui/core/src/components/user/ApiKey.vue @@ -41,9 +41,9 @@