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'); }