From 98fed4bc3073b4c3b44af076d540d345b9bfec20 Mon Sep 17 00:00:00 2001 From: Cameron Date: Tue, 13 Sep 2022 09:12:14 -0400 Subject: [PATCH] {satellite/console,web/satellite}: get project salt from satellite Add getSalt to projects api. Add action, GET_SALT, on Store Projects module to make the api request and return the salt string everywhere in the web app that generates an access grant. The Wasm code which is used to create the access grant has been changed to decode the salt as a base64 encoded string. The names of the function calls in the changed Wasm code have also been changed to ensure that access grant creation fails if JS access grant worker code and Wasm code are not the same version. https://github.com/storj/storj-private/issues/64 Change-Id: Ia2bc4cbadad84b066ca1882b042a3f0bb13c783a --- cmd/satellite/testdata.go | 7 +- satellite/console/consolewasm/access.go | 18 +-- satellite/console/consolewasm/access_test.go | 129 +++++++++++++++++- .../console/consolewasm/permission_test.go | 47 +++++-- satellite/console/wasm/main.go | 4 +- web/satellite/src/api/projects.ts | 9 ++ .../accessGrants/CreateAccessModal.vue | 4 +- .../steps/CreatePassphraseStep.vue | 4 +- .../steps/EnterPassphraseStep.vue | 4 +- .../src/components/modals/OpenBucketModal.vue | 5 +- .../components/modals/ShareBucketModal.vue | 4 +- .../src/components/objects/BucketCreation.vue | 5 +- .../src/components/objects/BucketsView.vue | 7 +- .../src/components/objects/EncryptData.vue | 5 +- .../src/components/objects/UploadFile.vue | 4 +- web/satellite/src/store/modules/projects.ts | 5 + web/satellite/src/types/projects.ts | 8 ++ web/satellite/src/utils/accessGrant.worker.js | 7 +- web/satellite/tests/unit/mock/api/projects.ts | 4 + 19 files changed, 235 insertions(+), 45 deletions(-) diff --git a/cmd/satellite/testdata.go b/cmd/satellite/testdata.go index 4beae5561..d802418b5 100644 --- a/cmd/satellite/testdata.go +++ b/cmd/satellite/testdata.go @@ -5,7 +5,9 @@ package main import ( "context" + "crypto/sha256" "database/sql" + "encoding/base64" "errors" "github.com/zeebo/errs" @@ -124,7 +126,10 @@ func GetTestApiKey(satelliteId string) (string, error) { return "", errs.Wrap(err) } - accessGrant, err := consolewasm.GenAccessGrant(satelliteId, key.Serialize(), password, projectID.String()) + idHash := sha256.Sum256(projectID[:]) + base64Salt := base64.StdEncoding.EncodeToString(idHash[:]) + + accessGrant, err := consolewasm.GenAccessGrant(satelliteId, key.Serialize(), password, base64Salt) if err != nil { return "", errs.Wrap(err) } diff --git a/satellite/console/consolewasm/access.go b/satellite/console/consolewasm/access.go index a09a704c1..ffb484e07 100644 --- a/satellite/console/consolewasm/access.go +++ b/satellite/console/consolewasm/access.go @@ -4,23 +4,22 @@ package consolewasm import ( - "crypto/sha256" + "encoding/base64" "storj.io/common/encryption" "storj.io/common/grant" "storj.io/common/macaroon" "storj.io/common/storj" - "storj.io/common/uuid" ) // GenAccessGrant creates a new access grant and returns it serialized form. -func GenAccessGrant(satelliteNodeURL, apiKey, encryptionPassphrase, projectID string) (string, error) { +func GenAccessGrant(satelliteNodeURL, apiKey, encryptionPassphrase, base64EncodedSalt string) (string, error) { parsedAPIKey, err := macaroon.ParseAPIKey(apiKey) if err != nil { return "", err } - key, err := DeriveRootKey(encryptionPassphrase, projectID) + key, err := DeriveRootKey(encryptionPassphrase, base64EncodedSalt) if err != nil { return "", err } @@ -41,14 +40,11 @@ func GenAccessGrant(satelliteNodeURL, apiKey, encryptionPassphrase, projectID st } // DeriveRootKey derives the root key portion of the access grant. -func DeriveRootKey(encryptionPassphrase, projectID string) (*storj.Key, error) { - id, err := uuid.FromString(projectID) +func DeriveRootKey(encryptionPassphrase, base64EncodedSalt string) (*storj.Key, error) { + const concurrency = 8 + saltBytes, err := base64.StdEncoding.DecodeString(base64EncodedSalt) if err != nil { return nil, err } - - const concurrency = 8 - salt := sha256.Sum256(id[:]) - - return encryption.DeriveRootKey([]byte(encryptionPassphrase), salt[:], "", concurrency) + return encryption.DeriveRootKey([]byte(encryptionPassphrase), saltBytes, "", concurrency) } diff --git a/satellite/console/consolewasm/access_test.go b/satellite/console/consolewasm/access_test.go index 9dc2d0d27..b54e1cb7f 100644 --- a/satellite/console/consolewasm/access_test.go +++ b/satellite/console/consolewasm/access_test.go @@ -4,6 +4,13 @@ package consolewasm_test import ( + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/http/cookiejar" + "strings" "testing" "github.com/stretchr/testify/require" @@ -20,16 +27,27 @@ func TestGenerateAccessGrant(t *testing.T) { testplanet.Run(t, testplanet.Config{ SatelliteCount: 1, StorageNodeCount: 0, UplinkCount: 1, }, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) { + client := newTestClient(t, ctx, planet) + user := client.defaultUser() + uplinkPeer := planet.Uplinks[0] + projectID := uplinkPeer.Projects[0].ID + + client.login(user.email, user.password) + + resp, bodyString := client.request(http.MethodGet, fmt.Sprintf("/projects/%s/salt", projectID.String()), nil) + require.Equal(t, http.StatusOK, resp.StatusCode) + + var b64Salt string + require.NoError(t, json.Unmarshal([]byte(bodyString), &b64Salt)) + satellitePeer := planet.Satellites[0] satelliteNodeURL := satellitePeer.NodeURL().String() - uplinkPeer := planet.Uplinks[0] apiKeyString := uplinkPeer.Projects[0].APIKey - projectID := uplinkPeer.Projects[0].ID.String() passphrase := "supersecretpassphrase" - wasmAccessString, err := console.GenAccessGrant(satelliteNodeURL, apiKeyString, passphrase, projectID) + wasmAccessString, err := console.GenAccessGrant(satelliteNodeURL, apiKeyString, passphrase, b64Salt) require.NoError(t, err) uplinkCliAccess, err := uplinkPeer.Config.RequestAccessWithPassphrase(ctx, satelliteNodeURL, apiKeyString, passphrase) @@ -50,7 +68,7 @@ func TestDefaultAccess(t *testing.T) { satelliteNodeURL := satellitePeer.NodeURL().String() uplinkPeer := planet.Uplinks[0] APIKey := uplinkPeer.APIKey[satellitePeer.ID()] - projectID := uplinkPeer.Projects[0].ID.String() + projectID := uplinkPeer.Projects[0].ID require.Equal(t, 1, len(uplinkPeer.Projects)) passphrase := "supersecretpassphrase" @@ -58,8 +76,18 @@ func TestDefaultAccess(t *testing.T) { testfilename := "file.txt" testdata := []byte("fun data") + client := newTestClient(t, ctx, planet) + user := client.defaultUser() + client.login(user.email, user.password) + + resp, bodyString := client.request(http.MethodGet, fmt.Sprintf("/projects/%s/salt", projectID.String()), nil) + require.Equal(t, http.StatusOK, resp.StatusCode) + + var b64Salt string + require.NoError(t, json.Unmarshal([]byte(bodyString), &b64Salt)) + // Create an access with the console access grant code that allows full access. - access, err := console.GenAccessGrant(satelliteNodeURL, APIKey.Serialize(), passphrase, projectID) + access, err := console.GenAccessGrant(satelliteNodeURL, APIKey.Serialize(), passphrase, b64Salt) require.NoError(t, err) newAccess, err := uplink.ParseAccess(access) require.NoError(t, err) @@ -80,3 +108,94 @@ func TestDefaultAccess(t *testing.T) { require.NoError(t, uplinkPeer.DeleteBucket(ctx, satellitePeer, testbucket1)) }) } + +type testClient struct { + t *testing.T + ctx *testcontext.Context + planet *testplanet.Planet + client *http.Client +} + +func newTestClient(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) testClient { + jar, err := cookiejar.New(nil) + require.NoError(t, err) + return testClient{t: t, ctx: ctx, planet: planet, client: &http.Client{Jar: jar}} +} + +type registeredUser struct { + email string + password string +} + +func (testClient *testClient) request(method string, path string, data io.Reader) (resp Response, body string) { + req, err := http.NewRequestWithContext(testClient.ctx, method, testClient.url(path), data) + require.NoError(testClient.t, err) + req.Header = map[string][]string{ + "sec-ch-ua": {`" Not A;Brand";v="99"`, `"Chromium";v="90"`, `"Google Chrome";v="90"`}, + "sec-ch-ua-mobile": {"?0"}, + "User-Agent": {"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36"}, + "Content-Type": {"application/json"}, + "Accept": {"*/*"}, + } + return testClient.do(req) +} + +// Response is a wrapper for http.Request to prevent false-positive with bodyclose check. +type Response struct{ *http.Response } + +func (testClient *testClient) do(req *http.Request) (_ Response, body string) { + resp, err := testClient.client.Do(req) + require.NoError(testClient.t, err) + + data, err := ioutil.ReadAll(resp.Body) + require.NoError(testClient.t, err) + require.NoError(testClient.t, resp.Body.Close()) + return Response{resp}, string(data) +} + +func (testClient *testClient) url(suffix string) string { + return testClient.planet.Satellites[0].ConsoleURL() + "/api/v0" + suffix +} + +func (testClient *testClient) toJSON(v interface{}) io.Reader { + data, err := json.Marshal(v) + require.NoError(testClient.t, err) + return strings.NewReader(string(data)) +} + +func (testClient *testClient) defaultUser() registeredUser { + user := testClient.planet.Uplinks[0].User[testClient.planet.Satellites[0].ID()] + return registeredUser{ + email: user.Email, + password: user.Password, + } +} + +func (testClient *testClient) login(email, password string) Response { + resp, body := testClient.request( + http.MethodPost, "/auth/token", + testClient.toJSON(map[string]string{ + "email": email, + "password": password, + })) + cookie := findCookie(resp, "_tokenKey") + require.NotNil(testClient.t, cookie) + + var tokenInfo struct { + Token string `json:"token"` + } + require.NoError(testClient.t, json.Unmarshal([]byte(body), &tokenInfo)) + require.Equal(testClient.t, http.StatusOK, resp.StatusCode) + require.Equal(testClient.t, tokenInfo.Token, cookie.Value) + + return resp +} + +func findCookie(response Response, name string) *http.Cookie { + for _, c := range response.Cookies() { + if c.Name == name { + return c + } + } + return nil +} diff --git a/satellite/console/consolewasm/permission_test.go b/satellite/console/consolewasm/permission_test.go index efc14b2cd..0d146524d 100644 --- a/satellite/console/consolewasm/permission_test.go +++ b/satellite/console/consolewasm/permission_test.go @@ -4,8 +4,10 @@ package consolewasm_test import ( - "context" + "encoding/json" "errors" + "fmt" + "net/http" "testing" "time" @@ -26,7 +28,7 @@ func TestSetPermissionWithBuckets(t *testing.T) { uplinkPeer := planet.Uplinks[0] APIKey := uplinkPeer.APIKey[satellitePeer.ID()] apiKeyString := APIKey.Serialize() - projectID := uplinkPeer.Projects[0].ID.String() + projectID := uplinkPeer.Projects[0].ID require.Equal(t, 1, len(uplinkPeer.Projects)) passphrase := "supersecretpassphrase" @@ -80,7 +82,18 @@ func TestSetPermissionWithBuckets(t *testing.T) { } restrictedKey, err := consolewasm.SetPermission(apiKeyString, buckets, readOnlyPermission) require.NoError(t, err) - restrictedAccessGrant, err := consolewasm.GenAccessGrant(satelliteNodeURL, restrictedKey.Serialize(), passphrase, projectID) + + client := newTestClient(t, ctx, planet) + user := client.defaultUser() + client.login(user.email, user.password) + + resp, bodyString := client.request(http.MethodGet, fmt.Sprintf("/projects/%s/salt", projectID.String()), nil) + require.Equal(t, http.StatusOK, resp.StatusCode) + + var b64Salt string + require.NoError(t, json.Unmarshal([]byte(bodyString), &b64Salt)) + + restrictedAccessGrant, err := consolewasm.GenAccessGrant(satelliteNodeURL, restrictedKey.Serialize(), passphrase, b64Salt) require.NoError(t, err) restrictedAccess, err := uplink.ParseAccess(restrictedAccessGrant) require.NoError(t, err) @@ -126,7 +139,17 @@ func TestSetPermission_Uplink(t *testing.T) { require.NoError(t, uplinkPeer.Upload(ctx, satellitePeer, testbucket2, testfilename1, testdata)) require.NoError(t, uplinkPeer.Upload(ctx, satellitePeer, testbucket2, testfilename2, testdata)) - withAccessKey(ctx, t, planet, passphrase, "only delete", []string{}, consolewasm.Permission{AllowDelete: true}, func(t *testing.T, project *uplink.Project) { + client := newTestClient(t, ctx, planet) + user := client.defaultUser() + client.login(user.email, user.password) + + resp, bodyString := client.request(http.MethodGet, fmt.Sprintf("/projects/%s/salt", uplinkPeer.Projects[0].ID.String()), nil) + require.Equal(t, http.StatusOK, resp.StatusCode) + + var b64Salt string + require.NoError(t, json.Unmarshal([]byte(bodyString), &b64Salt)) + + withAccessKey(ctx, t, planet, passphrase, b64Salt, "only delete", []string{}, consolewasm.Permission{AllowDelete: true}, func(t *testing.T, project *uplink.Project) { // All operation except delete should be restricted _, err = project.CreateBucket(ctx, testbucket2) require.True(t, errors.Is(err, uplink.ErrPermissionDenied)) @@ -157,7 +180,7 @@ func TestSetPermission_Uplink(t *testing.T) { require.Error(t, err) }) - withAccessKey(ctx, t, planet, passphrase, "only list", []string{testbucket1}, consolewasm.Permission{AllowList: true}, func(t *testing.T, project *uplink.Project) { + withAccessKey(ctx, t, planet, passphrase, b64Salt, "only list", []string{testbucket1}, consolewasm.Permission{AllowList: true}, func(t *testing.T, project *uplink.Project) { // All operation except list inside testbucket1 should be restricted _, err = project.CreateBucket(ctx, testbucket2) require.True(t, errors.Is(err, uplink.ErrPermissionDenied)) @@ -186,7 +209,7 @@ func TestSetPermission_Uplink(t *testing.T) { require.True(t, errors.Is(err, uplink.ErrPermissionDenied)) }) - withAccessKey(ctx, t, planet, passphrase, "only upload", []string{testbucket1}, consolewasm.Permission{AllowUpload: true}, func(t *testing.T, project *uplink.Project) { + withAccessKey(ctx, t, planet, passphrase, b64Salt, "only upload", []string{testbucket1}, consolewasm.Permission{AllowUpload: true}, func(t *testing.T, project *uplink.Project) { // All operation except upload to the testbucket1 should be restricted _, err = project.CreateBucket(ctx, testbucket2) require.True(t, errors.Is(err, uplink.ErrPermissionDenied)) @@ -215,7 +238,7 @@ func TestSetPermission_Uplink(t *testing.T) { require.True(t, errors.Is(err, uplink.ErrPermissionDenied)) }) - withAccessKey(ctx, t, planet, passphrase, "only download", []string{testbucket1}, consolewasm.Permission{AllowDownload: true}, func(t *testing.T, project *uplink.Project) { + withAccessKey(ctx, t, planet, passphrase, b64Salt, "only download", []string{testbucket1}, consolewasm.Permission{AllowDownload: true}, func(t *testing.T, project *uplink.Project) { // All operation except download from testbucket1 should be restricted _, err = project.CreateBucket(ctx, testbucket2) require.True(t, errors.Is(err, uplink.ErrPermissionDenied)) @@ -244,7 +267,7 @@ func TestSetPermission_Uplink(t *testing.T) { require.True(t, errors.Is(err, uplink.ErrPermissionDenied)) }) - withAccessKey(ctx, t, planet, passphrase, "not after", []string{}, consolewasm.Permission{ + withAccessKey(ctx, t, planet, passphrase, b64Salt, "not after", []string{}, consolewasm.Permission{ AllowDownload: true, AllowUpload: true, AllowList: true, @@ -278,7 +301,7 @@ func TestSetPermission_Uplink(t *testing.T) { require.True(t, errors.Is(err, uplink.ErrPermissionDenied)) }) - withAccessKey(ctx, t, planet, passphrase, "not before", []string{}, consolewasm.Permission{ + withAccessKey(ctx, t, planet, passphrase, b64Salt, "not before", []string{}, consolewasm.Permission{ AllowDownload: true, AllowUpload: true, AllowList: true, @@ -312,7 +335,7 @@ func TestSetPermission_Uplink(t *testing.T) { require.True(t, errors.Is(err, uplink.ErrPermissionDenied)) }) - withAccessKey(ctx, t, planet, passphrase, "all", []string{}, consolewasm.Permission{ + withAccessKey(ctx, t, planet, passphrase, b64Salt, "all", []string{}, consolewasm.Permission{ AllowDownload: true, AllowUpload: true, AllowList: true, @@ -368,7 +391,7 @@ func getAllBuckets(ctx *testcontext.Context, project *uplink.Project) []*uplink. return buckets } -func withAccessKey(ctx context.Context, t *testing.T, planet *testplanet.Planet, passphrase string, testname string, bucket []string, permissions consolewasm.Permission, fn func(t *testing.T, uplink *uplink.Project)) { +func withAccessKey(ctx *testcontext.Context, t *testing.T, planet *testplanet.Planet, passphrase, salt, testname string, bucket []string, permissions consolewasm.Permission, fn func(t *testing.T, uplink *uplink.Project)) { t.Run(testname, func(t *testing.T) { upl := planet.Uplinks[0] sat := planet.Satellites[0] @@ -377,7 +400,7 @@ func withAccessKey(ctx context.Context, t *testing.T, planet *testplanet.Planet, restrictedKey, err := consolewasm.SetPermission(apikey.Serialize(), bucket, permissions) require.NoError(t, err) - restrictedGrant, err := consolewasm.GenAccessGrant(sat.NodeURL().String(), restrictedKey.Serialize(), passphrase, upl.Projects[0].ID.String()) + restrictedGrant, err := consolewasm.GenAccessGrant(sat.NodeURL().String(), restrictedKey.Serialize(), passphrase, salt) require.NoError(t, err) access, err := uplink.ParseAccess(restrictedGrant) diff --git a/satellite/console/wasm/main.go b/satellite/console/wasm/main.go index abe13425f..d0eb0d38d 100644 --- a/satellite/console/wasm/main.go +++ b/satellite/console/wasm/main.go @@ -19,8 +19,8 @@ import ( ) func main() { - js.Global().Set("deriveAndEncryptRootKey", deriveAndEncryptRootKey()) - js.Global().Set("generateAccessGrant", generateAccessGrant()) + js.Global().Set("deriveAndAESEncryptRootKey", deriveAndEncryptRootKey()) + js.Global().Set("generateNewAccessGrant", generateAccessGrant()) js.Global().Set("setAPIKeyPermission", setAPIKeyPermission()) js.Global().Set("newPermission", newPermission()) js.Global().Set("restrictGrant", restrictGrant()) diff --git a/web/satellite/src/api/projects.ts b/web/satellite/src/api/projects.ts index 99ba8088d..1589f46a0 100644 --- a/web/satellite/src/api/projects.ts +++ b/web/satellite/src/api/projects.ts @@ -234,6 +234,15 @@ export class ProjectsApiGql extends BaseGql implements ProjectsApi { throw new Error('Can not get project daily usage'); } + public async getSalt(projectId: string): Promise { + const path = `${this.ROOT_PATH}/${projectId}/salt`; + const response = await this.http.get(path); + if (response.ok) { + return await response.json(); + } + throw new Error('Can not get project salt'); + } + /** * Fetch owned projects. * diff --git a/web/satellite/src/components/accessGrants/CreateAccessModal.vue b/web/satellite/src/components/accessGrants/CreateAccessModal.vue index cfa9226f3..27abc8246 100644 --- a/web/satellite/src/components/accessGrants/CreateAccessModal.vue +++ b/web/satellite/src/components/accessGrants/CreateAccessModal.vue @@ -220,11 +220,13 @@ export default class CreateAccessModal extends Vue { // creates access credentials const satelliteNodeURL = MetaUtils.getMetaContent('satellite-nodeurl'); + const salt = await this.$store.dispatch(PROJECTS_ACTIONS.GET_SALT, this.$store.getters.selectedProject.id); + this.worker.postMessage({ 'type': 'GenerateAccess', 'apiKey': this.restrictedKey, 'passphrase': this.passphrase, - 'projectID': this.$store.getters.selectedProject.id, + 'salt': salt, 'satelliteNodeURL': satelliteNodeURL, }); diff --git a/web/satellite/src/components/accessGrants/steps/CreatePassphraseStep.vue b/web/satellite/src/components/accessGrants/steps/CreatePassphraseStep.vue index 312db7fa6..2dd1e21ec 100644 --- a/web/satellite/src/components/accessGrants/steps/CreatePassphraseStep.vue +++ b/web/satellite/src/components/accessGrants/steps/CreatePassphraseStep.vue @@ -90,6 +90,7 @@ import { RouteConfig } from '@/router'; import { MetaUtils } from '@/utils/meta'; import { AnalyticsEvent } from '@/utils/constants/analyticsEventNames'; import { AnalyticsHttpApi } from '@/api/analytics'; +import { PROJECTS_ACTIONS } from '@/store/modules/projects'; import VButton from '@/components/common/VButton.vue'; import VInput from '@/components/common/VInput.vue'; @@ -186,12 +187,13 @@ export default class CreatePassphraseStep extends Vue { await this.analytics.eventTriggered(AnalyticsEvent.PASSPHRASE_CREATED); const satelliteNodeURL: string = MetaUtils.getMetaContent('satellite-nodeurl'); + const salt = await this.$store.dispatch(PROJECTS_ACTIONS.GET_SALT, this.$store.getters.selectedProject.id); this.worker.postMessage({ 'type': 'GenerateAccess', 'apiKey': this.restrictedKey, 'passphrase': this.passphrase, - 'projectID': this.$store.getters.selectedProject.id, + 'salt': salt, 'satelliteNodeURL': satelliteNodeURL, }); diff --git a/web/satellite/src/components/accessGrants/steps/EnterPassphraseStep.vue b/web/satellite/src/components/accessGrants/steps/EnterPassphraseStep.vue index 3ed1357e5..1069bd065 100644 --- a/web/satellite/src/components/accessGrants/steps/EnterPassphraseStep.vue +++ b/web/satellite/src/components/accessGrants/steps/EnterPassphraseStep.vue @@ -29,6 +29,7 @@ import { Component, Vue } from 'vue-property-decorator'; import { RouteConfig } from '@/router'; import { MetaUtils } from '@/utils/meta'; import { AnalyticsHttpApi } from '@/api/analytics'; +import { PROJECTS_ACTIONS } from '@/store/modules/projects'; import VInput from '@/components/common/VInput.vue'; import VButton from '@/components/common/VButton.vue'; @@ -98,12 +99,13 @@ export default class EnterPassphraseStep extends Vue { this.isLoading = true; const satelliteNodeURL = MetaUtils.getMetaContent('satellite-nodeurl'); + const salt = await this.$store.dispatch(PROJECTS_ACTIONS.GET_SALT, this.$store.getters.selectedProject.id); this.worker.postMessage({ 'type': 'GenerateAccess', 'apiKey': this.restrictedKey, 'passphrase': this.passphrase, - 'projectID': this.$store.getters.selectedProject.id, + 'salt': salt, 'satelliteNodeURL': satelliteNodeURL, }); diff --git a/web/satellite/src/components/modals/OpenBucketModal.vue b/web/satellite/src/components/modals/OpenBucketModal.vue index 654751cb5..192538494 100644 --- a/web/satellite/src/components/modals/OpenBucketModal.vue +++ b/web/satellite/src/components/modals/OpenBucketModal.vue @@ -56,6 +56,7 @@ import { MetaUtils } from '@/utils/meta'; import { AccessGrant, EdgeCredentials } from '@/types/accessGrants'; import { ACCESS_GRANTS_ACTIONS } from '@/store/modules/accessGrants'; import { AnalyticsHttpApi } from '@/api/analytics'; +import { PROJECTS_ACTIONS } from '@/store/modules/projects'; import VModal from '@/components/common/VModal.vue'; import VInput from '@/components/common/VInput.vue'; @@ -147,12 +148,14 @@ export default class OpenBucketModal extends Vue { throw new Error(grantEvent.data.error); } + const salt = await this.$store.dispatch(PROJECTS_ACTIONS.GET_SALT, this.$store.getters.selectedProject.id); const satelliteNodeURL: string = MetaUtils.getMetaContent('satellite-nodeurl'); + this.worker.postMessage({ 'type': 'GenerateAccess', 'apiKey': grantEvent.data.value, 'passphrase': this.passphrase, - 'projectID': this.$store.getters.selectedProject.id, + 'salt': salt, 'satelliteNodeURL': satelliteNodeURL, }); diff --git a/web/satellite/src/components/modals/ShareBucketModal.vue b/web/satellite/src/components/modals/ShareBucketModal.vue index a144fbbda..e22c0647f 100644 --- a/web/satellite/src/components/modals/ShareBucketModal.vue +++ b/web/satellite/src/components/modals/ShareBucketModal.vue @@ -40,6 +40,7 @@ import { Component, Vue } from 'vue-property-decorator'; import { APP_STATE_MUTATIONS } from '@/store/mutationConstants'; import { ACCESS_GRANTS_ACTIONS } from '@/store/modules/accessGrants'; +import { PROJECTS_ACTIONS } from '@/store/modules/projects'; import { MetaUtils } from '@/utils/meta'; import { AccessGrant, EdgeCredentials } from '@/types/accessGrants'; @@ -104,12 +105,13 @@ export default class ShareBucketModal extends Vue { const cleanAPIKey: AccessGrant = await this.$store.dispatch(ACCESS_GRANTS_ACTIONS.CREATE, LINK_SHARING_AG_NAME); const satelliteNodeURL = MetaUtils.getMetaContent('satellite-nodeurl'); + const salt = await this.$store.dispatch(PROJECTS_ACTIONS.GET_SALT, this.$store.getters.selectedProject.id); this.worker.postMessage({ 'type': 'GenerateAccess', 'apiKey': cleanAPIKey.secret, 'passphrase': this.passphrase, - 'projectID': this.$store.getters.selectedProject.id, + 'salt': salt, 'satelliteNodeURL': satelliteNodeURL, }); diff --git a/web/satellite/src/components/objects/BucketCreation.vue b/web/satellite/src/components/objects/BucketCreation.vue index 95f74f823..0e209ef7a 100644 --- a/web/satellite/src/components/objects/BucketCreation.vue +++ b/web/satellite/src/components/objects/BucketCreation.vue @@ -25,6 +25,7 @@ import { RouteConfig } from '@/router'; import { OBJECTS_ACTIONS } from '@/store/modules/objects'; import { AccessGrant, EdgeCredentials } from '@/types/accessGrants'; import { ACCESS_GRANTS_ACTIONS } from '@/store/modules/accessGrants'; +import { PROJECTS_ACTIONS } from '@/store/modules/projects'; import { MetaUtils } from '@/utils/meta'; import { AnalyticsHttpApi } from '@/api/analytics'; import { AnalyticsEvent } from '@/utils/constants/analyticsEventNames'; @@ -169,11 +170,13 @@ export default class BucketCreation extends Vue { } const satelliteNodeURL: string = MetaUtils.getMetaContent('satellite-nodeurl'); + const salt = await this.$store.dispatch(PROJECTS_ACTIONS.GET_SALT, this.$store.getters.selectedProject.id); + this.worker.postMessage({ 'type': 'GenerateAccess', 'apiKey': grantEvent.data.value, 'passphrase': this.passphrase, - 'projectID': this.$store.getters.selectedProject.id, + 'salt': salt, 'satelliteNodeURL': satelliteNodeURL, }); diff --git a/web/satellite/src/components/objects/BucketsView.vue b/web/satellite/src/components/objects/BucketsView.vue index 79b9ab7f7..a571c6239 100644 --- a/web/satellite/src/components/objects/BucketsView.vue +++ b/web/satellite/src/components/objects/BucketsView.vue @@ -91,6 +91,7 @@ import { Component, Vue, Watch } from 'vue-property-decorator'; import { RouteConfig } from '@/router'; import { ACCESS_GRANTS_ACTIONS } from '@/store/modules/accessGrants'; import { OBJECTS_ACTIONS } from '@/store/modules/objects'; +import { PROJECTS_ACTIONS } from '@/store/modules/projects'; import { AccessGrant, EdgeCredentials } from '@/types/accessGrants'; import { MetaUtils } from '@/utils/meta'; import { Validator } from '@/utils/validation'; @@ -222,13 +223,15 @@ export default class BucketsView extends Vue { if (grantEvent.data.error) { throw new Error(grantEvent.data.error); } - + + const salt = await this.$store.dispatch(PROJECTS_ACTIONS.GET_SALT, this.$store.getters.selectedProject.id); const satelliteNodeURL: string = MetaUtils.getMetaContent('satellite-nodeurl'); + this.worker.postMessage({ 'type': 'GenerateAccess', 'apiKey': this.grantWithPermissions, 'passphrase': '', - 'projectID': this.$store.getters.selectedProject.id, + 'salt': salt, 'satelliteNodeURL': satelliteNodeURL, }); diff --git a/web/satellite/src/components/objects/EncryptData.vue b/web/satellite/src/components/objects/EncryptData.vue index 6d2324d94..99f436820 100644 --- a/web/satellite/src/components/objects/EncryptData.vue +++ b/web/satellite/src/components/objects/EncryptData.vue @@ -66,6 +66,7 @@ import { OBJECTS_ACTIONS } from '@/store/modules/objects'; import { LocalData } from '@/utils/localData'; import { EdgeCredentials } from '@/types/accessGrants'; import { ACCESS_GRANTS_ACTIONS } from '@/store/modules/accessGrants'; +import { PROJECTS_ACTIONS } from '@/store/modules/projects'; import { APP_STATE_MUTATIONS } from '@/store/mutationConstants'; import { MetaUtils } from '@/utils/meta'; import { AnalyticsHttpApi } from '@/api/analytics'; @@ -193,11 +194,13 @@ export default class EncryptData extends Vue { } const satelliteNodeURL: string = MetaUtils.getMetaContent('satellite-nodeurl'); + const salt = await this.$store.dispatch(PROJECTS_ACTIONS.GET_SALT, this.$store.getters.selectedProject.id); + this.worker.postMessage({ 'type': 'GenerateAccess', 'apiKey': grantEvent.data.value, 'passphrase': this.passphrase, - 'projectID': this.$store.getters.selectedProject.id, + 'salt': salt, 'satelliteNodeURL': satelliteNodeURL, }); diff --git a/web/satellite/src/components/objects/UploadFile.vue b/web/satellite/src/components/objects/UploadFile.vue index e924c4c00..c4f9bd444 100644 --- a/web/satellite/src/components/objects/UploadFile.vue +++ b/web/satellite/src/components/objects/UploadFile.vue @@ -22,6 +22,7 @@ import { Component, Vue } from 'vue-property-decorator'; import { AnalyticsHttpApi } from '@/api/analytics'; import { RouteConfig } from '@/router'; import { ACCESS_GRANTS_ACTIONS } from '@/store/modules/accessGrants'; +import { PROJECTS_ACTIONS } from '@/store/modules/projects'; import { AccessGrant, EdgeCredentials } from '@/types/accessGrants'; import { AnalyticsEvent } from '@/utils/constants/analyticsEventNames'; import { MetaUtils } from '@/utils/meta'; @@ -140,12 +141,13 @@ export default class UploadFile extends Vue { */ private async generateCredentials(cleanApiKey: string, path: string, areEndless: boolean): Promise { const satelliteNodeURL = MetaUtils.getMetaContent('satellite-nodeurl'); + const salt = await this.$store.dispatch(PROJECTS_ACTIONS.GET_SALT, this.$store.getters.selectedProject.id); this.worker.postMessage({ 'type': 'GenerateAccess', 'apiKey': cleanApiKey, 'passphrase': this.passphrase, - 'projectID': this.$store.getters.selectedProject.id, + 'salt': salt, 'satelliteNodeURL': satelliteNodeURL, }); diff --git a/web/satellite/src/store/modules/projects.ts b/web/satellite/src/store/modules/projects.ts index 810f60bc1..4b78b11a6 100644 --- a/web/satellite/src/store/modules/projects.ts +++ b/web/satellite/src/store/modules/projects.ts @@ -29,6 +29,7 @@ export const PROJECTS_ACTIONS = { CLEAR: 'clearProjects', GET_LIMITS: 'getProjectLimits', GET_TOTAL_LIMITS: 'getTotalLimits', + GET_SALT: 'getSalt', }; export const PROJECTS_MUTATIONS = { @@ -91,6 +92,7 @@ const { GET_LIMITS, GET_TOTAL_LIMITS, FETCH_OWNED, + GET_SALT, } = PROJECTS_ACTIONS; const { @@ -331,6 +333,9 @@ export function makeProjectsModule(api: ProjectsApi): StoreModule { + return await api.getSalt(projectID); + }, [CLEAR]: function({ commit }: ProjectsContext): void { commit(CLEAR_PROJECTS); }, diff --git a/web/satellite/src/types/projects.ts b/web/satellite/src/types/projects.ts index 7684a67ad..616f31d8c 100644 --- a/web/satellite/src/types/projects.ts +++ b/web/satellite/src/types/projects.ts @@ -45,6 +45,14 @@ export interface ProjectsApi { */ getLimits(projectId: string): Promise; + /** + * Get project salt + * + * @param projectID - project ID + * throws Error + */ + getSalt(projectID: string): Promise; + /** * Get project limits. * diff --git a/web/satellite/src/utils/accessGrant.worker.js b/web/satellite/src/utils/accessGrant.worker.js index fb6834140..7dc9e3654 100644 --- a/web/satellite/src/utils/accessGrant.worker.js +++ b/web/satellite/src/utils/accessGrant.worker.js @@ -35,7 +35,7 @@ self.onmessage = async function (event) { const projectID = data.projectID; const aesKey = data.aesKey; - result = self.deriveAndEncryptRootKey(passphrase, projectID, aesKey); + result = self.deriveAndAESEncryptRootKey(passphrase, projectID, aesKey); self.postMessage(result); } break; @@ -43,11 +43,10 @@ self.onmessage = async function (event) { { apiKey = data.apiKey; const passphrase = data.passphrase; - const projectID = data.projectID; + const salt = data.salt; const nodeURL = data.satelliteNodeURL; - result = self.generateAccessGrant(nodeURL, apiKey, passphrase, projectID); - + result = self.generateNewAccessGrant(nodeURL, apiKey, passphrase, salt); self.postMessage(result); } break; diff --git a/web/satellite/tests/unit/mock/api/projects.ts b/web/satellite/tests/unit/mock/api/projects.ts index b5ebad400..52ea81de7 100644 --- a/web/satellite/tests/unit/mock/api/projects.ts +++ b/web/satellite/tests/unit/mock/api/projects.ts @@ -55,6 +55,10 @@ export class ProjectsApiMock implements ProjectsApi { return Promise.resolve(this.mockLimits); } + getSalt(): Promise { + throw new Error('not implemented'); + } + getDailyUsage(_projectId: string, _start: Date, _end: Date): Promise { throw new Error('not implemented'); }