web/satellite: import file browser component

WHAT:
import and instantiate file browser component

WHY:
to operate over folders and objects

Change-Id: Ib6fb4fdc2668d2f274df3d1b23f8cc0bb6a361ea
This commit is contained in:
Vitalii Shpital 2021-03-23 22:50:34 +02:00
parent 444b1f4757
commit 6ae2351389
21 changed files with 1893 additions and 2897 deletions

View File

@ -88,6 +88,7 @@ type Config struct {
BetaSatelliteSupportURL string `help:"url link for for beta satellite support" default:""` BetaSatelliteSupportURL string `help:"url link for for beta satellite support" default:""`
DocumentationURL string `help:"url link to documentation" devDefault:"https://documentation.storj.io/" releaseDefault:"https://documentation.tardigrade.io/"` DocumentationURL string `help:"url link to documentation" devDefault:"https://documentation.storj.io/" releaseDefault:"https://documentation.tardigrade.io/"`
CouponCodeUIEnabled bool `help:"indicates if user is allowed to add coupon codes to account" default:"false"` CouponCodeUIEnabled bool `help:"indicates if user is allowed to add coupon codes to account" default:"false"`
FileBrowserFlowDisabled bool `help:"indicates if file browser flow is disabled" default:"true"`
RateLimit web.IPRateLimiterConfig RateLimit web.IPRateLimiterConfig
@ -311,6 +312,7 @@ func (server *Server) appHandler(w http.ResponseWriter, r *http.Request) {
BetaSatelliteSupportURL string BetaSatelliteSupportURL string
DocumentationURL string DocumentationURL string
CouponCodeUIEnabled bool CouponCodeUIEnabled bool
FileBrowserFlowDisabled bool
} }
data.ExternalAddress = server.config.ExternalAddress data.ExternalAddress = server.config.ExternalAddress
@ -330,6 +332,7 @@ func (server *Server) appHandler(w http.ResponseWriter, r *http.Request) {
data.BetaSatelliteSupportURL = server.config.BetaSatelliteSupportURL data.BetaSatelliteSupportURL = server.config.BetaSatelliteSupportURL
data.DocumentationURL = server.config.DocumentationURL data.DocumentationURL = server.config.DocumentationURL
data.CouponCodeUIEnabled = server.config.CouponCodeUIEnabled data.CouponCodeUIEnabled = server.config.CouponCodeUIEnabled
data.FileBrowserFlowDisabled = server.config.FileBrowserFlowDisabled
if server.templates.index == nil { if server.templates.index == nil {
server.log.Error("index template is not set") server.log.Error("index template is not set")

View File

@ -103,6 +103,9 @@ compensation.withheld-percents: 75,75,75,50,50,50,25,25,25,0,0,0,0,0,0
# external endpoint of the satellite if hosted # external endpoint of the satellite if hosted
# console.external-address: "" # console.external-address: ""
# indicates if file browser flow is disabled
# console.file-browser-flow-disabled: true
# allow domains to embed the satellite in a frame, space separated # allow domains to embed the satellite in a frame, space separated
# console.frame-ancestors: tardigrade.io # console.frame-ancestors: tardigrade.io

View File

@ -20,6 +20,7 @@
<meta name="beta-satellite-support-url" content="{{ .BetaSatelliteSupportURL }}"> <meta name="beta-satellite-support-url" content="{{ .BetaSatelliteSupportURL }}">
<meta name="documentation-url" content="{{ .DocumentationURL }}"> <meta name="documentation-url" content="{{ .DocumentationURL }}">
<meta name="coupon-code-ui-enabled" content="{{ .CouponCodeUIEnabled }}"> <meta name="coupon-code-ui-enabled" content="{{ .CouponCodeUIEnabled }}">
<meta name="file-browser-flow-disabled" content="{{ .FileBrowserFlowDisabled }}">
<title>{{ .SatelliteName }}</title> <title>{{ .SatelliteName }}</title>
<link rel="shortcut icon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAABYlAAAWJQFJUiTwAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAL8SURBVHgBxVcxTFNRFL01JvylrRsmNWETWWSybg5I3NTQBCdRFyfrDuoKMgMyOQgdTcDAJmrUrWWCgQIbhJKy0TJQpsc73P/S9/tfP+8/GnqSn1fo77/3nnPufe8nhAR1ETeoy7hJjpj93/ycu0+UuUVuEA5Y2xaifirEVpX/nvknnBFbgpMGUfmIKOkRLRT578oxXy6IJcFCialH0EyaaPoZBy7tEQ3NEY1IKd4/iidHwqYLijLA559cuY6dT0RjBU5AAYm9fiivLFnBKMGBTyeqQ4BXhXDwdqjUiKZkskOzREsbzeeBNRMCEiDgr12uYl1WNbnW/oc2iUys8jrQyyxhHRkM3hdgAMFBHQyGG/GDqyDlsSeS/npQC99jlEBpOnyX2XCF8sGhZLbeMLMZkCDbJ1nYYTfDeMP9fMH5y5vmIKYE8RxUjBXPedDH1Zu6I9QFSzLQxErz4Xn5oNwg+2NSmuv3Lkvz4QlTi8rupDlBmA6tqQLrnYNCvoxSNAOtUEaakwzMv+ALidTP2OlKKiSK75Cs6hy9NYFkjzmG1SBCIuUq0Za8pgydge8R9E+e10qNrGE1ikH5435mo11bQgr4B9LEgVUC0Npm1o+vcuvBxB1NYFsaaeC2XUuW/Xs7msC9Xqa+MMa9jQr1KtXAQoKYHakeskbIhDrVasdTbbVY4s8ZYld/9PWuyeTSHksFBjBFcZ+aH/j/yZk5gcAcgImgIX6MNsKKhKBta1sB2A3HV5pD6iJQIzw/MICwoohc1F6ALBH03XemFYPl+VdzcBNUh6j5gZZEcP341opAAnX/AXl/A0FlrrshgMRR+YUvPPN8CHgAxlqWVYuEdH7V/ZilA6cosFDa53EcmUDKC+7X+IwxHEVhO0DK6aeXH88uHcWQA8xE7Yg69M6xgdWZUEFtNNDyx1s2KnyDIxu22zdZTjgWhANm/vL6clGIsnw3+Fbk94RreS8AMGrBxvwoT0lMPnSNC2JJoAPdgnMBJLjKq5lzAp1C19+OzwFiYzAU5f7eeQAAAABJRU5ErkJggg==" type="image/x-icon"> <link rel="shortcut icon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAABYlAAAWJQFJUiTwAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAL8SURBVHgBxVcxTFNRFL01JvylrRsmNWETWWSybg5I3NTQBCdRFyfrDuoKMgMyOQgdTcDAJmrUrWWCgQIbhJKy0TJQpsc73P/S9/tfP+8/GnqSn1fo77/3nnPufe8nhAR1ETeoy7hJjpj93/ycu0+UuUVuEA5Y2xaifirEVpX/nvknnBFbgpMGUfmIKOkRLRT578oxXy6IJcFCialH0EyaaPoZBy7tEQ3NEY1IKd4/iidHwqYLijLA559cuY6dT0RjBU5AAYm9fiivLFnBKMGBTyeqQ4BXhXDwdqjUiKZkskOzREsbzeeBNRMCEiDgr12uYl1WNbnW/oc2iUys8jrQyyxhHRkM3hdgAMFBHQyGG/GDqyDlsSeS/npQC99jlEBpOnyX2XCF8sGhZLbeMLMZkCDbJ1nYYTfDeMP9fMH5y5vmIKYE8RxUjBXPedDH1Zu6I9QFSzLQxErz4Xn5oNwg+2NSmuv3Lkvz4QlTi8rupDlBmA6tqQLrnYNCvoxSNAOtUEaakwzMv+ALidTP2OlKKiSK75Cs6hy9NYFkjzmG1SBCIuUq0Za8pgydge8R9E+e10qNrGE1ikH5435mo11bQgr4B9LEgVUC0Npm1o+vcuvBxB1NYFsaaeC2XUuW/Xs7msC9Xqa+MMa9jQr1KtXAQoKYHakeskbIhDrVasdTbbVY4s8ZYld/9PWuyeTSHksFBjBFcZ+aH/j/yZk5gcAcgImgIX6MNsKKhKBta1sB2A3HV5pD6iJQIzw/MICwoohc1F6ALBH03XemFYPl+VdzcBNUh6j5gZZEcP341opAAnX/AXl/A0FlrrshgMRR+YUvPPN8CHgAxlqWVYuEdH7V/ZilA6cosFDa53EcmUDKC+7X+IwxHEVhO0DK6aeXH88uHcWQA8xE7Yg69M6xgdWZUEFtNNDyx1s2KnyDIxu22zdZTjgWhANm/vL6clGIsnw3+Fbk94RreS8AMGrBxvwoT0lMPnSNC2JJoAPdgnMBJLjKq5lzAp1C19+OzwFiYzAU5f7eeQAAAABJRU5ErkJggg==" type="image/x-icon">
<link rel="dns-prefetch" href="https://js.stripe.com"> <link rel="dns-prefetch" href="https://js.stripe.com">

File diff suppressed because it is too large Load Diff

View File

@ -10,7 +10,8 @@
"dev": "vue-cli-service build --mode development" "dev": "vue-cli-service build --mode development"
}, },
"dependencies": { "dependencies": {
"@aws-sdk/client-s3": "3.8.1", "aws-sdk": "2.853.0",
"browser": "git+https://github.com/storj/browser#7e16121e9070e671e3c68557048ced234c61943b",
"apollo-cache-inmemory": "1.6.6", "apollo-cache-inmemory": "1.6.6",
"apollo-client": "2.6.10", "apollo-client": "2.6.10",
"apollo-link": "1.2.14", "apollo-link": "1.2.14",

View File

@ -157,16 +157,17 @@ export class AccessGrantsApiGql extends BaseGql implements AccessGrantsApi {
* *
* @param accessGrant - generated access grant * @param accessGrant - generated access grant
* @param optionalURL - optional requestURL * @param optionalURL - optional requestURL
* @param isPublic - optional status
* @throws Error * @throws Error
*/ */
public async getGatewayCredentials(accessGrant: string, optionalURL?: string): Promise<GatewayCredentials> { public async getGatewayCredentials(accessGrant: string, optionalURL?: string, isPublic?: boolean): Promise<GatewayCredentials> {
const requestURL: string = optionalURL || MetaUtils.getMetaContent('gateway-credentials-request-url'); const requestURL: string = optionalURL || MetaUtils.getMetaContent('gateway-credentials-request-url');
if (!requestURL) throw new Error('Cannot get gateway credentials: request URL is not provided'); if (!requestURL) throw new Error('Cannot get gateway credentials: request URL is not provided');
const path = `${requestURL}/v1/access`; const path = `${requestURL}/v1/access`;
const body = { const body = {
access_grant: accessGrant, access_grant: accessGrant,
public: false, public: isPublic || false,
}; };
const response = await this.client.post(path, JSON.stringify(body)); const response = await this.client.post(path, JSON.stringify(body));
if (!response.ok) { if (!response.ok) {

View File

@ -173,6 +173,7 @@ export default class ConfirmDeletePopup extends Vue {
&__item { &__item {
padding: 25px; padding: 25px;
width: calc(100% - 50px); width: calc(100% - 50px);
max-width: calc(100% - 50px);
background: rgba(245, 246, 250, 0.6); background: rgba(245, 246, 250, 0.6);
&__name { &__name {
@ -182,6 +183,7 @@ export default class ConfirmDeletePopup extends Vue {
font-size: 14px; font-size: 14px;
line-height: 19px; line-height: 19px;
color: #1b2533; color: #1b2533;
overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }

View File

@ -22,6 +22,7 @@ import { AuthHttpApi } from '@/api/auth';
import { RouteConfig } from '@/router'; import { RouteConfig } from '@/router';
import { ACCESS_GRANTS_ACTIONS } from '@/store/modules/accessGrants'; import { ACCESS_GRANTS_ACTIONS } from '@/store/modules/accessGrants';
import { BUCKET_ACTIONS } from '@/store/modules/buckets'; import { BUCKET_ACTIONS } from '@/store/modules/buckets';
import { OBJECTS_ACTIONS } from '@/store/modules/objects';
import { PROJECTS_ACTIONS } from '@/store/modules/projects'; import { PROJECTS_ACTIONS } from '@/store/modules/projects';
import { USER_ACTIONS } from '@/store/modules/users'; import { USER_ACTIONS } from '@/store/modules/users';
import { import {
@ -63,6 +64,7 @@ export default class AccountDropdown extends Vue {
await this.$store.dispatch(ACCESS_GRANTS_ACTIONS.STOP_ACCESS_GRANTS_WEB_WORKER); await this.$store.dispatch(ACCESS_GRANTS_ACTIONS.STOP_ACCESS_GRANTS_WEB_WORKER);
await this.$store.dispatch(NOTIFICATION_ACTIONS.CLEAR); await this.$store.dispatch(NOTIFICATION_ACTIONS.CLEAR);
await this.$store.dispatch(BUCKET_ACTIONS.CLEAR); await this.$store.dispatch(BUCKET_ACTIONS.CLEAR);
await this.$store.dispatch(OBJECTS_ACTIONS.CLEAR);
await this.$store.dispatch(APP_STATE_ACTIONS.CLOSE_POPUPS); await this.$store.dispatch(APP_STATE_ACTIONS.CLOSE_POPUPS);
LocalData.removeUserId(); LocalData.removeUserId();

View File

@ -31,6 +31,7 @@ import TeamIcon from '@/../static/images/navigation/team.svg';
import { RouteConfig } from '@/router'; import { RouteConfig } from '@/router';
import { NavigationLink } from '@/types/navigation'; import { NavigationLink } from '@/types/navigation';
import { MetaUtils } from '@/utils/meta';
@Component({ @Component({
components: { components: {
@ -42,16 +43,30 @@ import { NavigationLink } from '@/types/navigation';
}, },
}) })
export default class NavigationArea extends Vue { export default class NavigationArea extends Vue {
public navigation: NavigationLink[] = [];
/** /**
* Array of navigation links with icons. * Lifecycle hook before initial render.
* Sets navigation side bar list.
*/ */
public readonly navigation: NavigationLink[] = [ public async beforeMount(): Promise<void> {
RouteConfig.ProjectDashboard.withIcon(DashboardIcon), if (await JSON.parse(MetaUtils.getMetaContent('file-browser-flow-disabled'))) {
// TODO: enable when the flow will be finished this.navigation = [
// RouteConfig.Objects.withIcon(ObjectsIcon), RouteConfig.ProjectDashboard.withIcon(DashboardIcon),
RouteConfig.AccessGrants.withIcon(AccessGrantsIcon), RouteConfig.AccessGrants.withIcon(AccessGrantsIcon),
RouteConfig.Users.withIcon(TeamIcon), RouteConfig.Users.withIcon(TeamIcon),
]; ];
return;
}
this.navigation = [
RouteConfig.ProjectDashboard.withIcon(DashboardIcon),
RouteConfig.Objects.withIcon(ObjectsIcon),
RouteConfig.AccessGrants.withIcon(AccessGrantsIcon),
RouteConfig.Users.withIcon(TeamIcon),
];
}
/** /**
* Indicates if navigation side bar is hidden. * Indicates if navigation side bar is hidden.

View File

@ -21,6 +21,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { Bucket } from 'aws-sdk/clients/s3';
import { Component, Prop, Vue } from 'vue-property-decorator'; import { Component, Prop, Vue } from 'vue-property-decorator';
import ObjectsPopup from '@/components/objects/ObjectsPopup.vue'; import ObjectsPopup from '@/components/objects/ObjectsPopup.vue';
@ -29,8 +30,6 @@ import BucketIcon from '@/../static/images/objects/bucketItem.svg';
import DeleteIcon from '@/../static/images/objects/delete.svg'; import DeleteIcon from '@/../static/images/objects/delete.svg';
import DotsIcon from '@/../static/images/objects/dots.svg'; import DotsIcon from '@/../static/images/objects/dots.svg';
import { Bucket } from '@aws-sdk/client-s3';
@Component({ @Component({
components: { components: {
BucketIcon, BucketIcon,

View File

@ -11,14 +11,14 @@
</div> </div>
</div> </div>
<div class="buckets-view__loader" v-if="isLoading"/> <div class="buckets-view__loader" v-if="isLoading"/>
<p class="buckets-view__no-buckets" v-if="!(isLoading || buckets.length)">No Buckets</p> <p class="buckets-view__no-buckets" v-if="!(isLoading || bucketsList.length)">No Buckets</p>
<div class="buckets-view__list" v-if="!isLoading && buckets.length"> <div class="buckets-view__list" v-if="!isLoading && bucketsList.length">
<div class="buckets-view__list__sorting-header"> <div class="buckets-view__list__sorting-header">
<p class="buckets-view__list__sorting-header__name">Name</p> <p class="buckets-view__list__sorting-header__name">Name</p>
<p class="buckets-view__list__sorting-header__date">Date Added</p> <p class="buckets-view__list__sorting-header__date">Date Added</p>
<p class="buckets-view__list__sorting-header__empty"/> <p class="buckets-view__list__sorting-header__empty"/>
</div> </div>
<div class="buckets-view__list__item" v-for="(bucket, key) in buckets" :key="key" @click.stop="openBucket"> <div class="buckets-view__list__item" v-for="(bucket, key) in bucketsList" :key="key" @click.stop="openBucket(bucket.Name)">
<BucketItem <BucketItem
:item-data="bucket" :item-data="bucket"
:show-delete-bucket-popup="showDeleteBucketPopup" :show-delete-bucket-popup="showDeleteBucketPopup"
@ -55,6 +55,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { Bucket } from 'aws-sdk/clients/s3';
import { Component, Vue } from 'vue-property-decorator'; import { Component, Vue } from 'vue-property-decorator';
import BucketItem from '@/components/objects/BucketItem.vue'; import BucketItem from '@/components/objects/BucketItem.vue';
@ -67,7 +68,6 @@ import { ACCESS_GRANTS_ACTIONS } from '@/store/modules/accessGrants';
import { OBJECTS_ACTIONS } from '@/store/modules/objects'; import { OBJECTS_ACTIONS } from '@/store/modules/objects';
import { AccessGrant, GatewayCredentials } from '@/types/accessGrants'; import { AccessGrant, GatewayCredentials } from '@/types/accessGrants';
import { MetaUtils } from '@/utils/meta'; import { MetaUtils } from '@/utils/meta';
import { Bucket } from '@aws-sdk/client-s3';
@Component({ @Component({
components: { components: {
@ -102,85 +102,88 @@ export default class BucketsView extends Vue {
return; return;
} }
await this.removeTemporaryAccessGrant();
try { try {
const cleanAPIKey: AccessGrant = await this.$store.dispatch(ACCESS_GRANTS_ACTIONS.CREATE, this.FILE_BROWSER_AG_NAME); if (!this.accessGrantFromStore) {
await this.setWorker();
// This is done just in case old temporary access grant still exists.
await this.removeTemporaryAccessGrant();
await this.setAccess();
await this.fetchBuckets();
}
this.worker = this.$store.state.accessGrantsModule.accessGrantsWebWorker; this.isLoading = false;
this.worker.onmessage = (event: MessageEvent) => {
const data = event.data;
if (data.error) {
throw new Error(data.error);
}
this.grantWithPermissions = data.value; if (!this.bucketsList.length) this.showCreateBucketPopup();
};
const now = new Date();
const inADay = new Date(now.setDate(now.getDate() + 1));
await this.worker.postMessage({
'type': 'SetPermission',
'isDownload': true,
'isUpload': true,
'isList': true,
'isDelete': true,
'buckets': [],
'apiKey': cleanAPIKey.secret,
'notBefore': now.toISOString(),
'notAfter': inADay.toISOString(),
});
// Timeout is used to give some time for web worker to return value.
setTimeout(() => {
this.worker.onmessage = (event: MessageEvent) => {
const data = event.data;
if (data.error) {
throw new Error(data.error);
}
this.accessGrant = data.value;
};
const satelliteNodeURL: string = MetaUtils.getMetaContent('satellite-nodeurl');
this.worker.postMessage({
'type': 'GenerateAccess',
'apiKey': this.grantWithPermissions,
'passphrase': this.$route.params.passphrase,
'projectID': this.$store.getters.selectedProject.id,
'satelliteNodeURL': satelliteNodeURL,
});
// Timeout is used to give some time for web worker to return value.
setTimeout(async () => {
await this.$store.dispatch(OBJECTS_ACTIONS.SET_ACCESS_GRANT, this.accessGrant);
// TODO: use this value until all the satellites will have this URL set.
const gatewayURL = 'https://auth.tardigradeshare.io';
const gatewayCredentials: GatewayCredentials = await this.$store.dispatch(ACCESS_GRANTS_ACTIONS.GET_GATEWAY_CREDENTIALS, {accessGrant: this.accessGrant, optionalURL: gatewayURL});
await this.$store.dispatch(OBJECTS_ACTIONS.SET_GATEWAY_CREDENTIALS, gatewayCredentials);
await this.$store.dispatch(OBJECTS_ACTIONS.SET_S3_CLIENT);
await this.$store.dispatch(OBJECTS_ACTIONS.FETCH_BUCKETS);
this.isLoading = false;
if (!this.buckets.length) this.showCreateBucketPopup();
}, 1000);
}, 1000);
} catch (error) { } catch (error) {
await this.$notify.error(`Failed to setup Objects view. ${error.message}`); await this.$notify.error(`Failed to setup Buckets view. ${error.message}`);
return;
} }
} }
/** /**
* Lifecycle hook before component destroying. * Sets access to S3 client.
* Remove temporary created access grant.
*/ */
public async beforeDestroy(): Promise<void> { public async setAccess(): Promise<void> {
await this.removeTemporaryAccessGrant(); const cleanAPIKey: AccessGrant = await this.$store.dispatch(ACCESS_GRANTS_ACTIONS.CREATE, this.FILE_BROWSER_AG_NAME);
await this.$store.dispatch(OBJECTS_ACTIONS.SET_API_KEY, cleanAPIKey.secret);
const now = new Date();
const inADay = new Date(now.setDate(now.getDate() + 1));
await this.worker.postMessage({
'type': 'SetPermission',
'isDownload': true,
'isUpload': true,
'isList': true,
'isDelete': true,
'buckets': [],
'apiKey': cleanAPIKey.secret,
'notBefore': new Date().toISOString(),
'notAfter': inADay.toISOString(),
});
const grantEvent: MessageEvent = await new Promise(resolve => this.worker.onmessage = resolve);
this.grantWithPermissions = grantEvent.data.value;
if (grantEvent.data.error) {
throw new Error(grantEvent.data.error);
}
const satelliteNodeURL: string = MetaUtils.getMetaContent('satellite-nodeurl');
this.worker.postMessage({
'type': 'GenerateAccess',
'apiKey': this.grantWithPermissions,
'passphrase': this.passphrase,
'projectID': this.$store.getters.selectedProject.id,
'satelliteNodeURL': satelliteNodeURL,
});
const accessGrantEvent: MessageEvent = await new Promise(resolve => this.worker.onmessage = resolve);
this.accessGrant = accessGrantEvent.data.value;
if (accessGrantEvent.data.error) {
throw new Error(accessGrantEvent.data.error);
}
await this.$store.dispatch(OBJECTS_ACTIONS.SET_ACCESS_GRANT, this.accessGrant);
const gatewayCredentials: GatewayCredentials = await this.$store.dispatch(ACCESS_GRANTS_ACTIONS.GET_GATEWAY_CREDENTIALS, {accessGrant: this.accessGrant});
await this.$store.dispatch(OBJECTS_ACTIONS.SET_GATEWAY_CREDENTIALS, gatewayCredentials);
await this.$store.dispatch(OBJECTS_ACTIONS.SET_S3_CLIENT);
}
/**
* Fetches bucket using S3 client.
*/
public async fetchBuckets(): Promise<void> {
await this.$store.dispatch(OBJECTS_ACTIONS.FETCH_BUCKETS);
}
/**
* Sets local worker with worker instantiated in store.
*/
public setWorker(): void {
this.worker = this.$store.state.accessGrantsModule.accessGrantsWebWorker;
this.worker.onerror = (error: ErrorEvent) => {
this.$notify.error(error.message);
};
} }
/** /**
@ -235,6 +238,12 @@ export default class BucketsView extends Vue {
* Opens utils dropdown. * Opens utils dropdown.
*/ */
public openDropdown(key: number): void { public openDropdown(key: number): void {
if (this.activeDropdown === key) {
this.activeDropdown = -1;
return;
}
this.activeDropdown = key; this.activeDropdown = key;
} }
@ -294,15 +303,33 @@ export default class BucketsView extends Vue {
} }
} }
public openBucket(): void { /**
* Holds on bucket click. Proceeds to file browser.
*/
public openBucket(bucketName: string): void {
this.$store.dispatch(OBJECTS_ACTIONS.SET_FILE_COMPONENT_BUCKET_NAME, bucketName);
this.$router.push(RouteConfig.Objects.with(RouteConfig.UploadFile).path); this.$router.push(RouteConfig.Objects.with(RouteConfig.UploadFile).path);
} }
/** /**
* Returns fetched buckets from store. * Returns fetched buckets from store.
*/ */
public get buckets(): Bucket[] { public get bucketsList(): Bucket[] {
return this.$store.state.objectsModule.buckets; return this.$store.state.objectsModule.bucketsList;
}
/**
* Returns passphrase from store.
*/
private get passphrase(): string {
return this.$store.state.objectsModule.passphrase;
}
/**
* Returns access grant from store.
*/
private get accessGrantFromStore(): string {
return this.$store.state.objectsModule.accessGrant;
} }
} }
</script> </script>

View File

@ -22,7 +22,7 @@ import GeneratePassphrase from '@/components/common/GeneratePassphrase.vue';
import { RouteConfig } from '@/router'; import { RouteConfig } from '@/router';
import { OBJECTS_ACTIONS } from '@/store/modules/objects'; import { OBJECTS_ACTIONS } from '@/store/modules/objects';
import { LocalData } from '@/utils/localData'; import { LocalData, UserIDPassSalt } from '@/utils/localData';
@Component({ @Component({
components: { components: {
@ -31,9 +31,21 @@ import { LocalData } from '@/utils/localData';
}) })
export default class CreatePassphrase extends Vue { export default class CreatePassphrase extends Vue {
private isLoading: boolean = false; private isLoading: boolean = false;
private keyToBeStored: string = '';
public passphrase: string = ''; public passphrase: string = '';
/**
* Lifecycle hook after initial render.
* Chooses correct route.
*/
public mounted(): void {
const idPassSalt: UserIDPassSalt | null = LocalData.getUserIDPassSalt();
if (idPassSalt && idPassSalt.userId === this.$store.getters.user.id) {
this.$router.push({name: RouteConfig.EnterPassphrase.name});
}
}
/** /**
* Sets passphrase from child component. * Sets passphrase from child component.
*/ */
@ -44,22 +56,43 @@ export default class CreatePassphrase extends Vue {
/** /**
* Holds on next button click logic. * Holds on next button click logic.
*/ */
public onNextClick(): void { public async onNextClick(): Promise<void> {
if (this.isLoading) return; if (this.isLoading) return;
this.isLoading = true; this.isLoading = true;
const SALT = 'storj-unique-salt'; const SALT = 'storj-unique-salt';
pbkdf2.pbkdf2(this.passphrase, SALT, 1, 64, (error, key) => {
if (error) return this.$notify.error(error.message);
LocalData.setUserIDPassSalt(this.$store.getters.user.id, key.toString('hex'), SALT); const result: Buffer | Error = await this.pbkdf2Async(SALT);
});
if (result instanceof Error) {
await this.$notify.error(result.message);
return;
}
this.keyToBeStored = await result.toString('hex');
await LocalData.setUserIDPassSalt(this.$store.getters.user.id, this.keyToBeStored, SALT);
await this.$store.dispatch(OBJECTS_ACTIONS.SET_PASSPHRASE, this.passphrase);
this.isLoading = false; this.isLoading = false;
this.$store.dispatch(OBJECTS_ACTIONS.SET_PASSPHRASE, this.passphrase); await this.$router.push({name: RouteConfig.EnterPassphrase.name});
this.$router.push({name: RouteConfig.BucketsManagement.name}); }
/**
* Generates passphrase fingerprint asynchronously.
*/
private pbkdf2Async(salt: string): Promise<Buffer | Error> {
const ITERATIONS = 1;
const KEY_LENGTH = 64;
return new Promise((response, reject) => {
pbkdf2.pbkdf2(this.passphrase, salt, ITERATIONS, KEY_LENGTH, (error, key) => {
error ? reject(error) : response(key);
});
});
} }
} }
</script> </script>

View File

@ -13,14 +13,6 @@
</div> </div>
<p class="enter-pass__container__warning__message"> <p class="enter-pass__container__warning__message">
Entering your encryption passphrase here will share encryption data with your browser. Entering your encryption passphrase here will share encryption data with your browser.
<a
class="enter-pass__container__warning__message__link"
:href="docsLink"
target="_blank"
rel="noopener norefferer"
>
Learn More
</a>
</p> </p>
</div> </div>
<label class="enter-pass__container__textarea" for="enter-pass-textarea"> <label class="enter-pass__container__textarea" for="enter-pass-textarea">
@ -38,8 +30,9 @@
<div class="enter-pass__container__error" v-if="isError"> <div class="enter-pass__container__error" v-if="isError">
<h2 class="enter-pass__container__error__title">Encryption Passphrase Does not Match</h2> <h2 class="enter-pass__container__error__title">Encryption Passphrase Does not Match</h2>
<p class="enter-pass__container__error__message"> <p class="enter-pass__container__error__message">
This passphrase hasnt yet been used in the browser. Please ensure this is the encryption passphrase A previous fingerprint of a passphrase-based-key-derivation-function created in this browser doesn't
used in libulink or the Uplink CLI. match the passphrase you just entered. Entering a passphrase not previously created will result in
the creation of a new passphrase.
</p> </p>
<label class="enter-pass__container__error__check-area" :class="{ error: isCheckboxError }" for="error-checkbox"> <label class="enter-pass__container__error__check-area" :class="{ error: isCheckboxError }" for="error-checkbox">
<input <input
@ -84,11 +77,24 @@ import { MetaUtils } from '@/utils/meta';
}, },
}) })
export default class EnterPassphrase extends Vue { export default class EnterPassphrase extends Vue {
private hashFromInput: string = '';
public passphrase: string = ''; public passphrase: string = '';
public isError: boolean = false; public isError: boolean = false;
public isCheckboxChecked: boolean = false; public isCheckboxChecked: boolean = false;
public isCheckboxError: boolean = false; public isCheckboxError: boolean = false;
/**
* Lifecycle hook after initial render.
* Chooses correct route.
*/
public mounted(): void {
const idPassSalt: UserIDPassSalt | null = LocalData.getUserIDPassSalt();
if (!idPassSalt) {
this.$router.push({name: RouteConfig.CreatePassphrase.name});
}
}
/** /**
* Returns docs link from config. * Returns docs link from config.
*/ */
@ -99,38 +105,43 @@ export default class EnterPassphrase extends Vue {
/** /**
* Holds on access data button click logic. * Holds on access data button click logic.
*/ */
public onAccessDataClick(): void { public async onAccessDataClick(): Promise<void> {
if (!this.passphrase) return; if (!this.passphrase) return;
const hashFromStorage: UserIDPassSalt | null = LocalData.getUserIDPassSalt(); const hashFromStorage: UserIDPassSalt | null = LocalData.getUserIDPassSalt();
if (!hashFromStorage) return; if (!hashFromStorage) return;
pbkdf2.pbkdf2(this.passphrase, hashFromStorage.salt, 1, 64, (error, key) => { const result: Buffer | Error = await this.pbkdf2Async(hashFromStorage.salt);
if (error) return this.$notify.error(error.message);
const hashFromInput: string = key.toString('hex'); if (result instanceof Error) {
const areHashesEqual = () => { await this.$notify.error(result.message);
return hashFromStorage.passwordHash === hashFromInput;
};
switch (true) { return;
case areHashesEqual() || }
!areHashesEqual() && this.isError && this.isCheckboxChecked:
this.$store.dispatch(OBJECTS_ACTIONS.SET_PASSPHRASE, this.passphrase);
this.$router.push({name: RouteConfig.BucketsManagement.name});
return; this.hashFromInput = await result.toString('hex');
case !areHashesEqual() && this.isError && !this.isCheckboxChecked:
this.isCheckboxError = true;
return; const areHashesEqual = () => {
case !areHashesEqual(): return hashFromStorage.passwordHash === this.hashFromInput;
this.isError = true; };
return; switch (true) {
default: case areHashesEqual() ||
} !areHashesEqual() && this.isError && this.isCheckboxChecked:
}); await this.$store.dispatch(OBJECTS_ACTIONS.SET_PASSPHRASE, this.passphrase);
await this.$router.push({name: RouteConfig.BucketsManagement.name});
return;
case !areHashesEqual() && this.isError && !this.isCheckboxChecked:
this.isCheckboxError = true;
return;
case !areHashesEqual():
this.isError = true;
return;
default:
}
} }
/** /**
@ -141,6 +152,20 @@ export default class EnterPassphrase extends Vue {
this.isCheckboxChecked = false; this.isCheckboxChecked = false;
this.isError = false; this.isError = false;
} }
/**
* Generates passphrase fingerprint asynchronously.
*/
private pbkdf2Async(salt: string): Promise<Buffer | Error> {
const ITERATIONS = 1;
const KEY_LENGTH = 64;
return new Promise((response, reject) => {
pbkdf2.pbkdf2(this.passphrase, salt, ITERATIONS, KEY_LENGTH, (error, key) => {
error ? reject(error) : response(key);
});
});
}
} }
</script> </script>
@ -212,12 +237,6 @@ export default class EnterPassphrase extends Vue {
line-height: 19px; line-height: 19px;
color: #1b2533; color: #1b2533;
margin: 10px 0 0 0; margin: 10px 0 0 0;
&__link {
font-family: 'font_medium', sans-serif;
color: #0068dc;
text-decoration: underline;
}
} }
} }

View File

@ -11,39 +11,17 @@
import { Component, Vue } from 'vue-property-decorator'; import { Component, Vue } from 'vue-property-decorator';
import { RouteConfig } from '@/router'; import { RouteConfig } from '@/router';
import { LocalData, UserIDPassSalt } from '@/utils/localData'; import { MetaUtils } from '@/utils/meta';
@Component @Component
export default class ObjectsArea extends Vue { export default class ObjectsArea extends Vue {
/** /**
* Lifecycle hook after initial render. * Lifecycle hook after initial render.
* Chooses correct route. * Redirects if flow is disabled.
*/ */
public async mounted(): Promise<void> { public async mounted(): Promise<void> {
const DUPLICATE_NAV_ERROR: string = 'NavigationDuplicated'; if (await JSON.parse(MetaUtils.getMetaContent('file-browser-flow-disabled'))) {
const idPassSalt: UserIDPassSalt | null = LocalData.getUserIDPassSalt(); await this.$router.push(RouteConfig.ProjectDashboard.path);
if (idPassSalt && idPassSalt.userId === this.$store.getters.user.id) {
try {
await this.$router.push(RouteConfig.Objects.with(RouteConfig.EnterPassphrase).path);
return;
} catch (error) {
if (error.name === DUPLICATE_NAV_ERROR) {
return;
}
await this.$notify.error(error.message);
}
}
try {
await this.$router.push(RouteConfig.Objects.with(RouteConfig.CreatePassphrase).path);
} catch (error) {
if (error.name === DUPLICATE_NAV_ERROR) {
return;
}
await this.$notify.error(error.message);
} }
} }
} }

View File

@ -1,9 +1,199 @@
// Copyright (C) 2021 Storj Labs, Inc. // Copyright (C) 2021 Storj Labs, Inc.
// See LICENSE for copying information. // See LICENSE for copying information.
<template>
<div class="file-browser">
<FileBrowser></FileBrowser>
</div>
</template>
<script lang="ts"> <script lang="ts">
import { FileBrowser } from 'browser';
import { Component, Vue } from 'vue-property-decorator'; import { Component, Vue } from 'vue-property-decorator';
@Component import { RouteConfig } from '@/router';
export default class UploadFile extends Vue {} import { ACCESS_GRANTS_ACTIONS } from '@/store/modules/accessGrants';
import { AccessGrant, GatewayCredentials } from '@/types/accessGrants';
import { MetaUtils } from '@/utils/meta';
@Component({
components: {
FileBrowser,
},
})
export default class UploadFile extends Vue {
private worker: Worker;
/**
* Lifecycle hook before initial render.
* Checks if bucket is chosen.
*/
public beforeMount(): void {
if (!this.bucket) {
this.$router.push(RouteConfig.Objects.with(RouteConfig.EnterPassphrase).path);
}
}
/**
* Lifecycle hook after initial render.
* Checks if bucket is chosen.
* Sets local worker.
*/
public mounted(): void {
if (!this.bucket) {
this.$router.push(RouteConfig.Objects.with(RouteConfig.EnterPassphrase).path);
return;
}
this.setWorker();
}
/**
* Lifecycle hook after vue instance was created.
* Initiates file browser.
*/
public created(): void {
if (!this.bucket) {
return;
}
this.$store.commit('files/init', {
endpoint: this.$store.state.objectsModule.gatewayCredentials.endpoint,
accessKey: this.$store.state.objectsModule.gatewayCredentials.accessKeyId,
secretKey: this.$store.state.objectsModule.gatewayCredentials.secretKey,
bucket: this.bucket,
browserRoot: RouteConfig.Objects.with(RouteConfig.UploadFile).path,
getObjectMapUrl: async (path: string) => await this.generateObjectMapUrl(path),
getSharedLink: async (path: string) => await this.generateShareLinkUrl(path),
});
}
/**
* Generates a URL for an object map.
*/
public async generateObjectMapUrl(path: string): Promise<string> {
path = `${this.bucket}/${path}`;
const now = new Date();
const inADay = new Date(now.setDate(now.getDate() + 1));
try {
const key: string = await this.accessKey(this.apiKey, inADay, path);
return `https://link.tardigradeshare.io/s/${key}/${path}?map=1`;
} catch (error) {
await this.$notify.error(error.message);
return '';
}
}
/**
* Generates a URL for a link sharing service.
*/
public async generateShareLinkUrl(path: string): Promise<string> {
path = `${this.bucket}/${path}`;
const now = new Date();
const notAfter = new Date(now.setFullYear(now.getFullYear() + 100));
const LINK_SHARING_AG_NAME = `${path}_shared-object_${now.toISOString()}`;
const cleanAPIKey: AccessGrant = await this.$store.dispatch(ACCESS_GRANTS_ACTIONS.CREATE, LINK_SHARING_AG_NAME);
try {
const key: string = await this.accessKey(cleanAPIKey.secret, notAfter, path);
return `https://link.tardigradeshare.io/${key}/${path}`;
} catch (error) {
await this.$notify.error(error.message);
return '';
}
}
/**
* Sets local worker with worker instantiated in store.
*/
public setWorker(): void {
this.worker = this.$store.state.accessGrantsModule.accessGrantsWebWorker;
this.worker.onerror = (error: ErrorEvent) => {
this.$notify.error(error.message);
};
}
/**
* Generates public access key.
*/
private async accessKey(cleanApiKey: string, notAfter: Date, path: string): Promise<string> {
const satelliteNodeURL = MetaUtils.getMetaContent('satellite-nodeurl');
this.worker.postMessage({
'type': 'GenerateAccess',
'apiKey': cleanApiKey,
'passphrase': this.passphrase,
'projectID': this.$store.getters.selectedProject.id,
'satelliteNodeURL': satelliteNodeURL,
});
const grantEvent: MessageEvent = await new Promise(resolve => this.worker.onmessage = resolve);
const grantData = grantEvent.data;
if (grantData.error) {
await this.$notify.error(grantData.error);
return '';
}
this.worker.postMessage({
'type': 'RestrictGrant',
'isDownload': true,
'isUpload': true,
'isList': true,
'isDelete': true,
'paths': [path],
'grant': grantData.value,
'notBefore': new Date().toISOString(),
'notAfter': notAfter.toISOString(),
});
const event: MessageEvent = await new Promise(resolve => this.worker.onmessage = resolve);
const data = event.data;
if (data.error) {
await this.$notify.error(data.error);
return '';
}
const gatewayCredentials: GatewayCredentials = await this.$store.dispatch(ACCESS_GRANTS_ACTIONS.GET_GATEWAY_CREDENTIALS, {accessGrant: data.value, isPublic: true});
return gatewayCredentials.accessKeyId;
}
/**
* Returns passphrase from store.
*/
private get passphrase(): string {
return this.$store.state.objectsModule.passphrase;
}
/**
* Returns apiKey from store.
*/
private get apiKey(): string {
return this.$store.state.objectsModule.apiKey;
}
/**
* Returns bucket name from store.
*/
private get bucket(): string {
return this.$store.state.objectsModule.fileComponentBucketName;
}
}
</script> </script>
<style scoped>
@import '../../../node_modules/browser/dist/browser.css';
.file-browser {
font-family: 'font_regular', sans-serif;
padding-bottom: 200px;
}
</style>

View File

@ -388,6 +388,12 @@ router.beforeEach((to, from, next) => {
return; return;
} }
if (navigateToDefaultSubTab(to.matched, RouteConfig.Objects)) {
next(RouteConfig.Objects.with(RouteConfig.CreatePassphrase).path);
return;
}
if (to.name === 'default') { if (to.name === 'default') {
next(RouteConfig.ProjectDashboard.path); next(RouteConfig.ProjectDashboard.path);

View File

@ -1,6 +1,7 @@
// Copyright (C) 2019 Storj Labs, Inc. // Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information. // See LICENSE for copying information.
import { files } from 'browser';
import Vue from 'vue'; import Vue from 'vue';
import Vuex from 'vuex'; import Vuex from 'vuex';
@ -62,6 +63,7 @@ export const store = new Vuex.Store<ModulesState>({
projectsModule: makeProjectsModule(projectsApi), projectsModule: makeProjectsModule(projectsApi),
bucketUsageModule: makeBucketsModule(bucketsApi), bucketUsageModule: makeBucketsModule(bucketsApi),
objectsModule: makeObjectsModule(), objectsModule: makeObjectsModule(),
files,
}, },
}); });

View File

@ -215,7 +215,7 @@ export function makeAccessGrantsModule(api: AccessGrantsApi): StoreModule<Access
await api.deleteByNameAndProjectID(name, rootGetters.selectedProject.id); await api.deleteByNameAndProjectID(name, rootGetters.selectedProject.id);
}, },
getGatewayCredentials: async function({commit}: any, payload): Promise<GatewayCredentials> { getGatewayCredentials: async function({commit}: any, payload): Promise<GatewayCredentials> {
const credentials: GatewayCredentials = await api.getGatewayCredentials(payload.accessGrant, payload.optionalURL); const credentials: GatewayCredentials = await api.getGatewayCredentials(payload.accessGrant, payload.optionalURL, payload.isPublic);
commit(SET_GATEWAY_CREDENTIALS, credentials); commit(SET_GATEWAY_CREDENTIALS, credentials);

View File

@ -1,23 +1,19 @@
// Copyright (C) 2021 Storj Labs, Inc. // Copyright (C) 2021 Storj Labs, Inc.
// See LICENSE for copying information. // See LICENSE for copying information.
import S3, { Bucket } from 'aws-sdk/clients/s3';
import { StoreModule } from '@/store'; import { StoreModule } from '@/store';
import { GatewayCredentials } from '@/types/accessGrants'; import { GatewayCredentials } from '@/types/accessGrants';
import {
Bucket,
CreateBucketCommand,
DeleteBucketCommand,
ListBucketsCommand,
ListBucketsOutput,
S3Client,
} from '@aws-sdk/client-s3';
export const OBJECTS_ACTIONS = { export const OBJECTS_ACTIONS = {
CLEAR: 'clearObjects', CLEAR: 'clearObjects',
SET_GATEWAY_CREDENTIALS: 'setGatewayCredentials', SET_GATEWAY_CREDENTIALS: 'setGatewayCredentials',
SET_API_KEY: 'setApiKey',
SET_ACCESS_GRANT: 'setAccessGrant', SET_ACCESS_GRANT: 'setAccessGrant',
SET_S3_CLIENT: 'setS3Client', SET_S3_CLIENT: 'setS3Client',
SET_PASSPHRASE: 'setPassphrase', SET_PASSPHRASE: 'setPassphrase',
SET_FILE_COMPONENT_BUCKET_NAME: 'setFileComponentBucketName',
FETCH_BUCKETS: 'fetchBuckets', FETCH_BUCKETS: 'fetchBuckets',
CREATE_BUCKET: 'createBucket', CREATE_BUCKET: 'createBucket',
DELETE_BUCKET: 'deleteBucket', DELETE_BUCKET: 'deleteBucket',
@ -25,28 +21,34 @@ export const OBJECTS_ACTIONS = {
export const OBJECTS_MUTATIONS = { export const OBJECTS_MUTATIONS = {
SET_GATEWAY_CREDENTIALS: 'setGatewayCredentials', SET_GATEWAY_CREDENTIALS: 'setGatewayCredentials',
SET_API_KEY: 'setApiKey',
SET_ACCESS_GRANT: 'setAccessGrant', SET_ACCESS_GRANT: 'setAccessGrant',
CLEAR: 'clearObjects', CLEAR: 'clearObjects',
SET_S3_CLIENT: 'setS3Client', SET_S3_CLIENT: 'setS3Client',
SET_BUCKETS: 'setBuckets', SET_BUCKETS: 'setBuckets',
SET_FILE_COMPONENT_BUCKET_NAME: 'setFileComponentBucketName',
SET_PASSPHRASE: 'setPassphrase', SET_PASSPHRASE: 'setPassphrase',
}; };
const { const {
CLEAR, CLEAR,
SET_API_KEY,
SET_ACCESS_GRANT, SET_ACCESS_GRANT,
SET_GATEWAY_CREDENTIALS, SET_GATEWAY_CREDENTIALS,
SET_S3_CLIENT, SET_S3_CLIENT,
SET_BUCKETS, SET_BUCKETS,
SET_PASSPHRASE, SET_PASSPHRASE,
SET_FILE_COMPONENT_BUCKET_NAME,
} = OBJECTS_MUTATIONS; } = OBJECTS_MUTATIONS;
export class ObjectsState { export class ObjectsState {
public apiKey: string = '';
public accessGrant: string = ''; public accessGrant: string = '';
public gatewayCredentials: GatewayCredentials = new GatewayCredentials(); public gatewayCredentials: GatewayCredentials = new GatewayCredentials();
public s3Client: S3Client = new S3Client({}); public s3Client: S3 = new S3({});
public buckets: Bucket[] = []; public bucketsList: Bucket[] = [];
public passphrase: string = ''; public passphrase: string = '';
public fileComponentBucketName: string = '';
} }
/** /**
@ -56,6 +58,9 @@ export function makeObjectsModule(): StoreModule<ObjectsState> {
return { return {
state: new ObjectsState(), state: new ObjectsState(),
mutations: { mutations: {
[SET_API_KEY](state: ObjectsState, apiKey: string) {
state.apiKey = apiKey;
},
[SET_ACCESS_GRANT](state: ObjectsState, accessGrant: string) { [SET_ACCESS_GRANT](state: ObjectsState, accessGrant: string) {
state.accessGrant = accessGrant; state.accessGrant = accessGrant;
}, },
@ -63,36 +68,37 @@ export function makeObjectsModule(): StoreModule<ObjectsState> {
state.gatewayCredentials = credentials; state.gatewayCredentials = credentials;
}, },
[SET_S3_CLIENT](state: ObjectsState) { [SET_S3_CLIENT](state: ObjectsState) {
// TODO: use this for local testing. Remove after final implementation.
// state.gatewayCredentials.accessKeyId = 'jwitszrc76z4amjcrinv4zjpnlia';
// state.gatewayCredentials.secretKey = 'jyjufay7ddmwj6tlboyuj23yy4lqigfqa2ie25y526qmjj65khxzw';
// state.gatewayCredentials.endpoint = 'https://gateway.tardigradeshare.io';
const s3Config = { const s3Config = {
credentials: { accessKeyId: state.gatewayCredentials.accessKeyId,
accessKeyId: state.gatewayCredentials.accessKeyId, secretAccessKey: state.gatewayCredentials.secretKey,
secretAccessKey: state.gatewayCredentials.secretKey,
},
endpoint: state.gatewayCredentials.endpoint, endpoint: state.gatewayCredentials.endpoint,
s3ForcePathStyle: true,
signatureVersion: 'v4',
region: 'REGION',
}; };
state.s3Client = new S3Client(s3Config); state.s3Client = new S3(s3Config);
}, },
[SET_BUCKETS](state: ObjectsState, buckets: Bucket[]) { [SET_BUCKETS](state: ObjectsState, buckets: Bucket[]) {
state.buckets = buckets; state.bucketsList = buckets;
}, },
[SET_PASSPHRASE](state: ObjectsState, passphrase: string) { [SET_PASSPHRASE](state: ObjectsState, passphrase: string) {
state.passphrase = passphrase; state.passphrase = passphrase;
}, },
[SET_FILE_COMPONENT_BUCKET_NAME](state: ObjectsState, bucketName: string) {
state.fileComponentBucketName = bucketName;
},
[CLEAR](state: ObjectsState) { [CLEAR](state: ObjectsState) {
state.apiKey = '';
state.passphrase = '';
state.accessGrant = ''; state.accessGrant = '';
state.gatewayCredentials = new GatewayCredentials(); state.gatewayCredentials = new GatewayCredentials();
state.s3Client = new S3({});
state.bucketsList = [];
state.fileComponentBucketName = '';
}, },
}, },
actions: { actions: {
setApiKey: function({commit}: any, apiKey: string): void {
commit(SET_API_KEY, apiKey);
},
setAccessGrant: function({commit}: any, accessGrant: string): void { setAccessGrant: function({commit}: any, accessGrant: string): void {
commit(SET_ACCESS_GRANT, accessGrant); commit(SET_ACCESS_GRANT, accessGrant);
}, },
@ -105,25 +111,23 @@ export function makeObjectsModule(): StoreModule<ObjectsState> {
setPassphrase: function({commit}: any, passphrase: string): void { setPassphrase: function({commit}: any, passphrase: string): void {
commit(SET_PASSPHRASE, passphrase); commit(SET_PASSPHRASE, passphrase);
}, },
setFileComponentBucketName: function({commit}: any, bucketName: string): void {
commit(SET_FILE_COMPONENT_BUCKET_NAME, bucketName);
},
fetchBuckets: async function(ctx): Promise<void> { fetchBuckets: async function(ctx): Promise<void> {
const bucketsOutput: ListBucketsOutput = await ctx.state.s3Client.send(new ListBucketsCommand({ const result = await ctx.state.s3Client.listBuckets().promise();
credentials: {
accessKeyId: ctx.state.gatewayCredentials.accessKeyId,
secretAccessKey: ctx.state.gatewayCredentials.secretKey,
},
}));
ctx.commit(SET_BUCKETS, bucketsOutput.Buckets); ctx.commit(SET_BUCKETS, result.Buckets);
}, },
createBucket: async function(ctx, name: string): Promise<void> { createBucket: async function(ctx, name: string): Promise<void> {
await ctx.state.s3Client.send(new CreateBucketCommand({ await ctx.state.s3Client.createBucket({
Bucket: name, Bucket: name,
})); }).promise();
}, },
deleteBucket: async function(ctx, name: string): Promise<void> { deleteBucket: async function(ctx, name: string): Promise<void> {
await ctx.state.s3Client.send(new DeleteBucketCommand({ await ctx.state.s3Client.deleteBucket({
Bucket: name, Bucket: name,
})); }).promise();
}, },
clearObjects: function ({commit}: any): void { clearObjects: function ({commit}: any): void {
commit(CLEAR); commit(CLEAR);

View File

@ -47,7 +47,7 @@ export interface AccessGrantsApi {
* @returns GatewayCredentials * @returns GatewayCredentials
* @throws Error * @throws Error
*/ */
getGatewayCredentials(accessGrant: string, optionalURL?: string): Promise<GatewayCredentials>; getGatewayCredentials(accessGrant: string, optionalURL?: string, isPublic?: boolean): Promise<GatewayCredentials>;
} }
/** /**

View File

@ -53,7 +53,7 @@ self.onmessage = function (event) {
permission.NotBefore = notBefore; permission.NotBefore = notBefore;
permission.NotAfter = notAfter; permission.NotAfter = notAfter;
if (data.type == "SetPermission") { if (data.type === "SetPermission") {
const buckets = data.buckets; const buckets = data.buckets;
apiKey = data.apiKey; apiKey = data.apiKey;
result = self.setAPIKeyPermission(apiKey, buckets, permission); result = self.setAPIKeyPermission(apiKey, buckets, permission);