satellite/{web, console}: removed old object flow

Removed old flow along with a feature flag

Issue:
https://github.com/storj/storj/issues/5166

Change-Id: I85cfabbf94e910c9d5efb01ef850f6b888a13f71
This commit is contained in:
Vitalii 2022-10-20 16:26:28 +03:00
parent 58a9c55f36
commit 149b59069f
18 changed files with 8 additions and 935 deletions

View File

@ -92,7 +92,6 @@ type Config struct {
LinksharingURL string `help:"url link for linksharing requests" default:"https://link.storjshare.io" devDefault:"http://localhost:8001"` LinksharingURL string `help:"url link for linksharing requests" default:"https://link.storjshare.io" devDefault:"http://localhost:8001"`
PathwayOverviewEnabled bool `help:"indicates if the overview onboarding step should render with pathways" default:"true"` PathwayOverviewEnabled bool `help:"indicates if the overview onboarding step should render with pathways" default:"true"`
NewProjectDashboard bool `help:"indicates if new project dashboard should be used" default:"true"` NewProjectDashboard bool `help:"indicates if new project dashboard should be used" default:"true"`
NewObjectsFlow bool `help:"indicates if new objects flow should be used" default:"true"`
NewAccessGrantFlow bool `help:"indicates if new access grant flow should be used" default:"true"` NewAccessGrantFlow bool `help:"indicates if new access grant flow should be used" default:"true"`
NewBillingScreen bool `help:"indicates if new billing screens should be used" default:"false"` NewBillingScreen bool `help:"indicates if new billing screens should be used" default:"false"`
GeneratedAPIEnabled bool `help:"indicates if generated console api should be used" default:"false"` GeneratedAPIEnabled bool `help:"indicates if generated console api should be used" default:"false"`
@ -454,7 +453,6 @@ func (server *Server) appHandler(w http.ResponseWriter, r *http.Request) {
NewProjectDashboard bool NewProjectDashboard bool
DefaultPaidStorageLimit memory.Size DefaultPaidStorageLimit memory.Size
DefaultPaidBandwidthLimit memory.Size DefaultPaidBandwidthLimit memory.Size
NewObjectsFlow bool
NewAccessGrantFlow bool NewAccessGrantFlow bool
NewBillingScreen bool NewBillingScreen bool
InactivityTimerEnabled bool InactivityTimerEnabled bool
@ -499,7 +497,6 @@ func (server *Server) appHandler(w http.ResponseWriter, r *http.Request) {
data.LoginHcaptchaEnabled = server.config.Captcha.Login.Hcaptcha.Enabled data.LoginHcaptchaEnabled = server.config.Captcha.Login.Hcaptcha.Enabled
data.LoginHcaptchaSiteKey = server.config.Captcha.Login.Hcaptcha.SiteKey data.LoginHcaptchaSiteKey = server.config.Captcha.Login.Hcaptcha.SiteKey
data.NewProjectDashboard = server.config.NewProjectDashboard data.NewProjectDashboard = server.config.NewProjectDashboard
data.NewObjectsFlow = server.config.NewObjectsFlow
data.NewAccessGrantFlow = server.config.NewAccessGrantFlow data.NewAccessGrantFlow = server.config.NewAccessGrantFlow
data.NewBillingScreen = server.config.NewBillingScreen data.NewBillingScreen = server.config.NewBillingScreen
data.InactivityTimerEnabled = server.config.Session.InactivityTimerEnabled data.InactivityTimerEnabled = server.config.Session.InactivityTimerEnabled

View File

@ -208,9 +208,6 @@ compensation.withheld-percents: 75,75,75,50,50,50,25,25,25,0,0,0,0,0,0
# indicates if new billing screens should be used # indicates if new billing screens should be used
# console.new-billing-screen: false # console.new-billing-screen: false
# indicates if new objects flow should be used
# console.new-objects-flow: true
# indicates if new project dashboard should be used # indicates if new project dashboard should be used
# console.new-project-dashboard: true # console.new-project-dashboard: true

View File

@ -34,7 +34,6 @@ func configureSatellite(log *zap.Logger, index int, config *satellite.Config) {
if dir := os.Getenv("STORJ_TEST_SATELLITE_WEB"); dir != "" { if dir := os.Getenv("STORJ_TEST_SATELLITE_WEB"); dir != "" {
config.Console.StaticDir = dir config.Console.StaticDir = dir
} }
config.Console.NewObjectsFlow = true
config.Console.NewAccessGrantFlow = true config.Console.NewAccessGrantFlow = true
config.Console.NewProjectDashboard = true config.Console.NewProjectDashboard = true
config.Console.CouponCodeBillingUIEnabled = true config.Console.CouponCodeBillingUIEnabled = true

View File

@ -34,7 +34,6 @@
<meta name="new-project-dashboard" content="{{ .NewProjectDashboard }}"> <meta name="new-project-dashboard" content="{{ .NewProjectDashboard }}">
<meta name="default-paid-storage-limit" content="{{ .DefaultPaidStorageLimit }}"> <meta name="default-paid-storage-limit" content="{{ .DefaultPaidStorageLimit }}">
<meta name="default-paid-bandwidth-limit" content="{{ .DefaultPaidBandwidthLimit }}"> <meta name="default-paid-bandwidth-limit" content="{{ .DefaultPaidBandwidthLimit }}">
<meta name="new-objects-flow" content="{{ .NewObjectsFlow }}">
<meta name="new-access-grant-flow" content="{{ .NewAccessGrantFlow }}"> <meta name="new-access-grant-flow" content="{{ .NewAccessGrantFlow }}">
<meta name="new-billing-screen" content="{{ .NewBillingScreen }}"> <meta name="new-billing-screen" content="{{ .NewBillingScreen }}">
<meta name="inactivity-timer-enabled" content="{{ .InactivityTimerEnabled }}"> <meta name="inactivity-timer-enabled" content="{{ .InactivityTimerEnabled }}">

View File

@ -40,7 +40,6 @@ export default class App extends Vue {
const couponCodeBillingUIEnabled = MetaUtils.getMetaContent('coupon-code-billing-ui-enabled'); const couponCodeBillingUIEnabled = MetaUtils.getMetaContent('coupon-code-billing-ui-enabled');
const couponCodeSignupUIEnabled = MetaUtils.getMetaContent('coupon-code-signup-ui-enabled'); const couponCodeSignupUIEnabled = MetaUtils.getMetaContent('coupon-code-signup-ui-enabled');
const isNewProjectDashboard = MetaUtils.getMetaContent('new-project-dashboard'); const isNewProjectDashboard = MetaUtils.getMetaContent('new-project-dashboard');
const isNewObjectsFlow = MetaUtils.getMetaContent('new-objects-flow');
if (satelliteName) { if (satelliteName) {
this.$store.dispatch(APP_STATE_ACTIONS.SET_SATELLITE_NAME, satelliteName); this.$store.dispatch(APP_STATE_ACTIONS.SET_SATELLITE_NAME, satelliteName);
@ -72,10 +71,6 @@ export default class App extends Vue {
if (isNewProjectDashboard) { if (isNewProjectDashboard) {
this.$store.dispatch(APP_STATE_ACTIONS.SET_PROJECT_DASHBOARD_STATUS, isNewProjectDashboard === 'true'); this.$store.dispatch(APP_STATE_ACTIONS.SET_PROJECT_DASHBOARD_STATUS, isNewProjectDashboard === 'true');
} }
if (isNewObjectsFlow) {
this.$store.dispatch(APP_STATE_ACTIONS.SET_OBJECTS_FLOW_STATUS, isNewObjectsFlow === 'true');
}
} }
} }
</script> </script>

View File

@ -357,13 +357,6 @@ export default class FileBrowser extends Vue {
return this.$store.state.objectsModule.fileComponentBucketName; return this.$store.state.objectsModule.fileComponentBucketName;
} }
/**
* Returns objects flow status from store.
*/
private get isNewObjectsFlow(): string {
return this.$store.state.appStateModule.isNewObjectsFlow;
}
/** /**
* Return a boolean signifying whether the upload display is allowed to be shown. * Return a boolean signifying whether the upload display is allowed to be shown.
*/ */
@ -391,8 +384,7 @@ export default class FileBrowser extends Vue {
*/ */
public async created(): Promise<void> { public async created(): Promise<void> {
if (!this.bucket) { if (!this.bucket) {
const path = this.isNewObjectsFlow ? RouteConfig.Buckets.with(RouteConfig.BucketsManagement).path : const path = RouteConfig.Buckets.with(RouteConfig.BucketsManagement).path;
RouteConfig.Buckets.with(RouteConfig.EncryptData).path;
this.analytics.pageVisit(path); this.analytics.pageVisit(path);
await this.$router.push(path); await this.$router.push(path);

View File

@ -1,408 +0,0 @@
// Copyright (C) 2021 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="encrypt-container">
<EncryptIcon />
<h1 class="encrypt-container__title" aria-roledescription="enc-title">Encryption passphrase</h1>
<p class="encrypt-container__info">
The encryption passphrase is used to encrypt and access the data that you upload to Storj.
</p>
<div class="encrypt-container__functional">
<div class="encrypt-container__functional__header">
<p class="encrypt-container__functional__header__gen" :class="{ active: isGenerate }" @click="setToGenerate">
Generate a new passphrase
</p>
<div class="encrypt-container__functional__header__right" :class="{ active: !isGenerate }">
<p
class="encrypt-container__functional__header__right__enter"
:class="{ active: !isGenerate }"
aria-roledescription="enter-passphrase-label"
@click="setToEnter"
>
Enter your own passphrase
</p>
<VInfo class="encrypt-container__functional__header__right__info-button">
<template #icon>
<InfoIcon class="encrypt-container__functional__header__right__info-button__image" :class="{ active: !isGenerate }" />
</template>
<template #message>
<p class="encrypt-container__functional__header__right__info-button__message">
We strongly encourage you to use a mnemonic phrase, which is automatically generated on
the client-side for you. Alternatively, you can enter your own passphrase.
</p>
</template>
</VInfo>
</div>
</div>
<div v-if="isGenerate" class="encrypt-container__functional__generate">
<p class="encrypt-container__functional__generate__value">{{ passphrase }}</p>
<VButton
class="encrypt-container__functional__generate__button"
label="Copy"
width="66px"
height="30px"
:is-blue-white="true"
:is-uppercase="true"
:on-press="onCopyClick"
/>
</div>
<div v-else class="encrypt-container__functional__enter">
<VInput
label="Your Passphrase"
placeholder="Enter a passphrase here..."
:error="enterError"
role-description="passphrase"
is-password
:disabled="isLoading"
@setData="setPassphrase"
/>
</div>
<h2 class="encrypt-container__functional__warning-title" aria-roledescription="warning-title">
Save your encryption passphrase
</h2>
<p class="encrypt-container__functional__warning-msg">
Please note that Storj does not know or store your encryption passphrase. If you lose it, you will not
be able to recover your files.
</p>
<p class="encrypt-container__functional__download" @click="onDownloadClick">Download as a text file</p>
<VCheckbox
class="encrypt-container__functional__checkbox"
label="I understand, and I have saved the passphrase."
:is-checkbox-error="isCheckboxError"
@setData="setSavingConfirmation"
/>
</div>
<div class="encrypt-container__buttons">
<VButton
v-if="isNewObjectsFlow"
class="encrypt-container__buttons__back"
label="< Back"
height="64px"
border-radius="62px"
:is-blue-white="true"
:on-press="onBackClick"
:is-disabled="isLoading"
/>
<VButton
label="Next >"
height="64px"
border-radius="62px"
:on-press="onNextButtonClick"
:is-disabled="isLoading || !isSavingConfirmed"
/>
</div>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import { generateMnemonic } from 'bip39';
import { LocalData, UserIDPassSalt } from '@/utils/localData';
import { Download } from '@/utils/download';
import VButton from '@/components/common/VButton.vue';
import VInfo from '@/components/common/VInfo.vue';
import VInput from '@/components/common/VInput.vue';
import VCheckbox from '@/components/common/VCheckbox.vue';
import EncryptIcon from '@/../static/images/objects/encrypt.svg';
import InfoIcon from '@/../static/images/common/smallGreyInfo.svg';
// @vue/component
@Component({
components: {
EncryptIcon,
InfoIcon,
VInfo,
VButton,
VInput,
VCheckbox,
},
})
export default class GeneratePassphrase extends Vue {
@Prop({ default: () => () => null })
public readonly onNextClick: () => unknown;
@Prop({ default: () => () => null })
public readonly onBackClick: () => unknown;
@Prop({ default: () => () => null })
public readonly setParentPassphrase: (passphrase: string) => void;
@Prop({ default: false })
public readonly isLoading: boolean;
public isGenerate = true;
public enterError = '';
public passphrase = '';
public isSavingConfirmed = false;
public isCheckboxError = false;
/**
* Lifecycle hook after initial render.
* Chooses correct state and generates mnemonic.
*/
public mounted(): void {
const idPassSalt: UserIDPassSalt | null = LocalData.getUserIDPassSalt();
if (idPassSalt && idPassSalt.userId === this.$store.getters.user.id) {
this.isGenerate = false;
return;
}
this.passphrase = generateMnemonic();
this.setParentPassphrase(this.passphrase);
}
public setSavingConfirmation(value: boolean): void {
this.isSavingConfirmed = value;
}
/**
* Holds on copy button click logic.
* Copies passphrase to clipboard.
*/
public onCopyClick(): void {
this.$copyText(this.passphrase);
this.$notify.success('Passphrase was copied successfully');
}
/**
* Holds on download button click logic.
* Downloads encryption passphrase as a txt file.
*/
public onDownloadClick(): void {
if (!this.passphrase) {
this.enterError = 'Can\'t be empty!';
return;
}
const fileName = 'StorjEncryptionPassphrase.txt';
Download.file(this.passphrase, fileName);
}
/**
* Sets passphrase from child component.
*/
public setPassphrase(passphrase: string): void {
if (this.enterError) this.enterError = '';
this.passphrase = passphrase;
this.setParentPassphrase(this.passphrase);
}
/**
* Sets view state to enter passphrase.
*/
public setToEnter(): void {
this.passphrase = '';
this.setParentPassphrase(this.passphrase);
this.isGenerate = false;
}
/**
* Sets view state to generate passphrase.
*/
public setToGenerate(): void {
if (this.enterError) this.enterError = '';
this.passphrase = generateMnemonic();
this.setParentPassphrase(this.passphrase);
this.isGenerate = true;
}
/**
* Holds on next button click logic.
*/
public async onNextButtonClick(): Promise<void> {
if (!this.passphrase) {
this.enterError = 'Can\'t be empty!';
return;
}
if (!this.isSavingConfirmed) {
this.isCheckboxError = true;
return;
}
await this.onNextClick();
}
/**
* Returns objects flow status from store.
*/
public get isNewObjectsFlow(): string {
return this.$store.state.appStateModule.isNewObjectsFlow;
}
}
</script>
<style scoped lang="scss">
.encrypt-container {
font-family: 'font_regular', sans-serif;
padding: 40px 60px 60px;
max-width: 500px;
background: #fcfcfc;
box-shadow: 0 0 32px rgb(0 0 0 / 4%);
border-radius: 20px;
margin: 30px auto 0;
display: flex;
flex-direction: column;
align-items: center;
&__title {
font-family: 'font_bold', sans-serif;
font-size: 36px;
line-height: 56px;
letter-spacing: 1px;
color: #14142b;
margin: 10px 0;
}
&__info {
font-size: 16px;
line-height: 28px;
letter-spacing: 0.75px;
color: #1b2533;
margin-bottom: 20px;
text-align: center;
max-width: 420px;
}
&__functional {
border: 1px solid #e6e9ef;
border-radius: 10px;
padding: 20px 0;
&__header {
width: calc(100% - 50px);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 25px;
border-bottom: 1px solid #e6e9ef;
&__gen {
font-family: 'font_medium', sans-serif;
font-size: 14px;
line-height: 17px;
color: #a9b5c1;
padding-bottom: 20px;
border-bottom: 4px solid #fff;
cursor: pointer;
white-space: nowrap;
}
&__right {
display: flex;
align-items: flex-start;
padding-bottom: 20px;
border-bottom: 4px solid #fff;
cursor: pointer;
&__enter {
font-family: 'font_medium', sans-serif;
font-size: 14px;
line-height: 17px;
color: #a9b5c1;
margin-right: 10px;
white-space: nowrap;
}
&__info-button {
&__image {
cursor: pointer;
}
&__message {
color: #586c86;
font-size: 16px;
line-height: 21px;
}
}
}
}
&__generate {
display: flex;
align-items: center;
padding: 16px 22px;
background: #eff0f7;
border-radius: 10px;
margin: 25px 25px 0;
&__value {
font-size: 14px;
line-height: 25px;
color: #384b65;
}
&__button {
margin-left: 32px;
min-width: 66px;
}
}
&__enter {
margin: 25px 25px 0;
}
&__download {
font-family: 'font_bold', sans-serif;
font-size: 16px;
line-height: 19px;
color: #0068dc;
cursor: pointer;
margin: 20px 25px;
display: inline-block;
}
&__warning-title {
font-family: 'font_bold', sans-serif;
font-size: 16px;
line-height: 19px;
color: #1b2533;
margin: 25px 25px 10px;
}
&__warning-msg {
font-size: 14px;
line-height: 20px;
color: #1b2533;
margin: 0 25px;
}
&__checkbox {
margin: 0 25px;
}
}
&__buttons {
width: 100%;
display: flex;
align-items: center;
margin-top: 30px;
&__back {
margin-right: 24px;
}
}
}
.active {
color: var(--c-blue-3);
border-color: var(--c-blue-3);
}
.active svg rect {
fill: var(--c-blue-3);
}
:deep(.info__box__message) {
min-width: 440px;
}
</style>

View File

@ -348,7 +348,7 @@ export default class MobileNavigation extends Vue {
private get isBucketsView(): boolean { private get isBucketsView(): boolean {
const currentRoute = this.$route.path; const currentRoute = this.$route.path;
return currentRoute.includes(RouteConfig.BucketsManagement.path) || currentRoute.includes(RouteConfig.EncryptData.path); return currentRoute.includes(RouteConfig.BucketsManagement.path);
} }
/** /**

View File

@ -285,7 +285,7 @@ export default class ProjectSelection extends Vue {
private get isBucketsView(): boolean { private get isBucketsView(): boolean {
const currentRoute = this.$route.path; const currentRoute = this.$route.path;
return currentRoute.includes(RouteConfig.BucketsManagement.path) || currentRoute.includes(RouteConfig.EncryptData.path); return currentRoute.includes(RouteConfig.BucketsManagement.path);
} }
} }
</script> </script>

View File

@ -87,9 +87,7 @@ export default class BucketDetails extends Vue {
this.$store.dispatch(OBJECTS_ACTIONS.SET_FILE_COMPONENT_BUCKET_NAME, this.bucket?.name); this.$store.dispatch(OBJECTS_ACTIONS.SET_FILE_COMPONENT_BUCKET_NAME, this.bucket?.name);
if (this.$route.params.backRoute === RouteConfig.BucketsManagement.name) { if (this.$route.params.backRoute === RouteConfig.BucketsManagement.name) {
this.isNewObjectsFlow this.$store.commit(APP_STATE_MUTATIONS.TOGGLE_OPEN_BUCKET_MODAL_SHOWN);
? this.$store.commit(APP_STATE_MUTATIONS.TOGGLE_OPEN_BUCKET_MODAL_SHOWN)
: this.$router.push(RouteConfig.Buckets.with(RouteConfig.EncryptData).path);
return; return;
} }
@ -97,13 +95,6 @@ export default class BucketDetails extends Vue {
this.analytics.pageVisit(RouteConfig.Buckets.with(RouteConfig.UploadFile).path); this.analytics.pageVisit(RouteConfig.Buckets.with(RouteConfig.UploadFile).path);
this.$router.push(RouteConfig.Buckets.with(RouteConfig.UploadFile).path); this.$router.push(RouteConfig.Buckets.with(RouteConfig.UploadFile).path);
} }
/**
* Returns objects flow status from store.
*/
private get isNewObjectsFlow(): string {
return this.$store.state.appStateModule.isNewObjectsFlow;
}
} }
</script> </script>

View File

@ -60,17 +60,6 @@
</template> </template>
</v-table> </v-table>
<EncryptionBanner v-if="!isServerSideEncryptionBannerHidden" :hide="hideBanner" /> <EncryptionBanner v-if="!isServerSideEncryptionBannerHidden" :hide="hideBanner" />
<ObjectsPopup
v-if="isCreatePopupVisible"
:on-click="onCreateBucketClick"
title="Create Bucket"
sub-title="Buckets are simply containers that store objects and their metadata within a project."
button-label="Create Bucket"
:error-message="errorMessage"
:is-loading="isRequestProcessing"
@setName="setCreateBucketName"
@close="hideCreateBucketPopup"
/>
<ObjectsPopup <ObjectsPopup
v-if="isDeletePopupVisible" v-if="isDeletePopupVisible"
:on-click="onDeleteBucketClick" :on-click="onDeleteBucketClick"
@ -127,11 +116,9 @@ export default class BucketsView extends Vue {
private readonly FILE_BROWSER_AG_NAME: string = 'Web file browser API key'; private readonly FILE_BROWSER_AG_NAME: string = 'Web file browser API key';
private worker: Worker; private worker: Worker;
private grantWithPermissions = ''; private grantWithPermissions = '';
private createBucketName = '';
private deleteBucketName = ''; private deleteBucketName = '';
public isLoading = true; public isLoading = true;
public isCreatePopupVisible = false;
public isDeletePopupVisible = false; public isDeletePopupVisible = false;
public isRequestProcessing = false; public isRequestProcessing = false;
public errorMessage = ''; public errorMessage = '';
@ -182,13 +169,8 @@ export default class BucketsView extends Vue {
} }
if (!this.bucketsPage.buckets.length && !wasDemoBucketCreated) { if (!this.bucketsPage.buckets.length && !wasDemoBucketCreated) {
if (this.isNewObjectsFlow) { this.analytics.pageVisit(RouteConfig.Buckets.with(RouteConfig.BucketCreation).path);
this.analytics.pageVisit(RouteConfig.Buckets.with(RouteConfig.BucketCreation).path); await this.$router.push(RouteConfig.Buckets.with(RouteConfig.BucketCreation).path);
await this.$router.push(RouteConfig.Buckets.with(RouteConfig.BucketCreation).path);
return;
}
await this.createDemoBucket();
} }
} catch (error) { } catch (error) {
await this.$notify.error(`Failed to setup Buckets view. ${error.message}`); await this.$notify.error(`Failed to setup Buckets view. ${error.message}`);
@ -270,40 +252,7 @@ export default class BucketsView extends Vue {
public onNewBucketButtonClick(): void { public onNewBucketButtonClick(): void {
this.analytics.pageVisit(RouteConfig.Buckets.with(RouteConfig.BucketCreation).path); this.analytics.pageVisit(RouteConfig.Buckets.with(RouteConfig.BucketCreation).path);
this.isNewObjectsFlow this.$router.push(RouteConfig.Buckets.with(RouteConfig.BucketCreation).path);
? this.$router.push(RouteConfig.Buckets.with(RouteConfig.BucketCreation).path)
: this.showCreateBucketPopup();
}
/**
* Holds create bucket click logic.
*/
public async onCreateBucketClick(): Promise<void> {
if (this.isRequestProcessing) return;
if (!this.isBucketNameValid(this.createBucketName)) return;
this.isRequestProcessing = true;
try {
if (!this.edgeCredentials.accessKeyId) {
await this.setAccess();
}
await this.$store.dispatch(OBJECTS_ACTIONS.CREATE_BUCKET, this.createBucketName);
await this.fetchBuckets();
this.createBucketName = '';
this.hideCreateBucketPopup();
} catch (error) {
const BUCKET_ALREADY_EXISTS_ERROR = 'BucketAlreadyExists';
if (error.name === BUCKET_ALREADY_EXISTS_ERROR) {
await this.$notify.error('Bucket with provided name already exists.');
} else {
await this.$notify.error(error.message);
}
} finally {
this.isRequestProcessing = false;
}
} }
/** /**
@ -404,30 +353,6 @@ export default class BucketsView extends Vue {
this.deleteBucketName = name; this.deleteBucketName = name;
} }
/**
* Makes create bucket popup visible.
*/
public showCreateBucketPopup(): void {
this.createBucketName = '';
this.isCreatePopupVisible = true;
}
/**
* Hides create bucket popup.
*/
public hideCreateBucketPopup(): void {
this.errorMessage = '';
this.isCreatePopupVisible = false;
}
/**
* Set create bucket name form input.
*/
public setCreateBucketName(name: string): void {
this.errorMessage = '';
this.createBucketName = name;
}
/** /**
* Hides server-side encryption banner. * Hides server-side encryption banner.
*/ */
@ -441,10 +366,7 @@ export default class BucketsView extends Vue {
*/ */
public openBucket(bucketName: string): void { public openBucket(bucketName: string): void {
this.$store.dispatch(OBJECTS_ACTIONS.SET_FILE_COMPONENT_BUCKET_NAME, bucketName); this.$store.dispatch(OBJECTS_ACTIONS.SET_FILE_COMPONENT_BUCKET_NAME, bucketName);
this.analytics.pageVisit(RouteConfig.Buckets.with(RouteConfig.EncryptData).path); this.$store.commit(APP_STATE_MUTATIONS.TOGGLE_OPEN_BUCKET_MODAL_SHOWN);
this.isNewObjectsFlow
? this.$store.commit(APP_STATE_MUTATIONS.TOGGLE_OPEN_BUCKET_MODAL_SHOWN)
: this.$router.push(RouteConfig.Buckets.with(RouteConfig.EncryptData).path);
} }
/** /**
@ -454,13 +376,6 @@ export default class BucketsView extends Vue {
return this.$store.state.bucketUsageModule.page; return this.$store.state.bucketUsageModule.page;
} }
/**
* Returns objects flow status from store.
*/
private get isNewObjectsFlow(): string {
return this.$store.state.appStateModule.isNewObjectsFlow;
}
/** /**
* Returns selected project id from store. * Returns selected project id from store.
*/ */

View File

@ -1,317 +0,0 @@
// Copyright (C) 2021 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="encrypt">
<div class="encrypt__msg-container">
<p class="encrypt__msg-container__title" aria-roledescription="objects-title">
The object browser uses
<a
class="encrypt__msg-container__title__link"
href="https://docs.storj.io/dcs/concepts/encryption-key/design-decision-server-side-encryption"
target="_blank"
rel="noopener noreferrer"
>
server side encryption.
</a>
</p>
<p class="encrypt__msg-container__text">
If you want to use our product with only end-to-end encryption, you may want to use our
<span class="encrypt__msg-container__text__link" @click="navigateToCLIFlow">command line solution.</span>
</p>
</div>
<GeneratePassphrase
:on-next-click="onNextClick"
:on-back-click="onBackClick"
:set-parent-passphrase="setPassphrase"
:is-loading="isLoading"
/>
<div class="encrypt__faq">
<h2 class="encrypt__faq__title">FAQ</h2>
<FAQBullet
title="Why do I need a passphrase to upload?"
text="One very important design consideration is that data stored on Storj is encrypted. That means
only you have the encryption passphrase for your data. The service doesn't ever have access to or store
your encryption passphrase. If you lose your passphrase, you will be unable to recover your data."
/>
<FAQBullet
title="What if I enter a wrong passphrase?"
text="There is no wrong passphrase because every passphrase can have access to different files. Entering
a new or different passphrase wont have any effect on the existing files. If you enter your passphrase
and dont see the existing files, its most likely you entered a new passphrase and thats why you cant
see the encrypted data stored with a different passphrase."
/>
<FAQBullet
title="Why I have to enter a passphrase for every bucket?"
text="In general, the best practice is to use one encryption passphrase per bucket. If an object with
the same path and object name uploaded by two uplinks with encryption keys derived from the same
encryption passphrase, the most recent upload will over-write the older object."
/>
<FAQBullet
title="Why there is no “remember passphrase”?"
text="There is no wrong passphrase because every passphrase can have access to different files. Entering
a new or different passphrase wont have any effect on the existing files. If you enter a passphrase and
dont see your existing files, try to enter your passphrase again."
/>
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import pbkdf2 from 'pbkdf2';
import { RouteConfig } from '@/router';
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';
import GeneratePassphrase from '@/components/common/GeneratePassphrase.vue';
import FAQBullet from '@/components/objects/FAQBullet.vue';
// @vue/component
@Component({
components: {
GeneratePassphrase,
FAQBullet,
},
})
export default class EncryptData extends Vue {
private worker: Worker;
public isLoading = false;
public passphrase = '';
public readonly analytics: AnalyticsHttpApi = new AnalyticsHttpApi();
/**
* Sets passphrase from child component.
*/
public navigateToCLIFlow(): void {
this.$store.commit(APP_STATE_MUTATIONS.SET_ONB_AG_NAME_STEP_BACK_ROUTE, this.$route.path);
this.analytics.pageVisit(RouteConfig.OnboardingTour.with(RouteConfig.OnbCLIStep.with(RouteConfig.AGName)).path);
this.$router.push({ name: RouteConfig.AGName.name });
}
/**
* Lifecycle hook after initial render.
* Sets local worker if new flow is used.
*/
public mounted(): void {
if (!this.apiKey) {
this.analytics.pageVisit(RouteConfig.Buckets.with(RouteConfig.BucketsManagement).path);
this.$router.push(RouteConfig.Buckets.with(RouteConfig.BucketsManagement).path);
}
this.setWorker();
}
/**
* Sets passphrase from child component.
*/
public setPassphrase(passphrase: string): void {
this.passphrase = passphrase;
}
/**
* 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);
};
}
/**
* Holds on next button click logic.
*/
public async onNextClick(): Promise<void> {
if (this.isLoading) return;
this.isLoading = true;
const SALT = 'storj-unique-salt';
const result: Buffer | Error = await this.pbkdf2Async(SALT);
if (result instanceof Error) {
await this.$notify.error(result.message);
return;
}
const keyToBeStored = result.toString('hex');
await LocalData.setUserIDPassSalt(this.$store.getters.user.id, keyToBeStored, SALT);
await this.$store.dispatch(OBJECTS_ACTIONS.SET_PASSPHRASE, this.passphrase);
try {
await this.setAccess();
this.analytics.pageVisit(RouteConfig.UploadFile.path);
await this.$router.push(RouteConfig.UploadFile.path);
} catch (e) {
await this.$notify.error(e.message);
} finally {
this.isLoading = false;
}
}
/**
* Holds on back button click logic.
*/
public onBackClick(): void {
this.analytics.pageVisit(RouteConfig.BucketsManagement.path);
this.$router.push(RouteConfig.BucketsManagement.path);
}
/**
* Sets access to S3 client.
*/
public async setAccess(): Promise<void> {
const now = new Date();
const inThreeDays = new Date(now.setDate(now.getDate() + 3));
await this.worker.postMessage({
'type': 'SetPermission',
'isDownload': true,
'isUpload': true,
'isList': true,
'isDelete': true,
'notAfter': inThreeDays.toISOString(),
'buckets': this.bucket ? [this.bucket] : [],
'apiKey': this.apiKey,
});
const grantEvent: MessageEvent = await new Promise(resolve => this.worker.onmessage = resolve);
if (grantEvent.data.error) {
throw new Error(grantEvent.data.error);
}
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,
'salt': salt,
'satelliteNodeURL': satelliteNodeURL,
});
const accessGrantEvent: MessageEvent = await new Promise(resolve => this.worker.onmessage = resolve);
if (accessGrantEvent.data.error) {
throw new Error(accessGrantEvent.data.error);
}
const accessGrant = accessGrantEvent.data.value;
const gatewayCredentials: EdgeCredentials = await this.$store.dispatch(ACCESS_GRANTS_ACTIONS.GET_GATEWAY_CREDENTIALS, { accessGrant });
await this.$store.dispatch(OBJECTS_ACTIONS.SET_GATEWAY_CREDENTIALS, gatewayCredentials);
}
/**
* 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);
});
});
}
/**
* 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>
<style scoped lang="scss">
.encrypt {
font-family: 'font_regular', sans-serif;
padding-bottom: 30px;
&__msg-container {
margin: -20px auto 40px;
max-width: 620px;
background-color: var(--c-yellow-2);
display: flex;
flex-direction: column;
align-items: center;
border-radius: 0 0 10px 10px;
padding: 20px 0;
&__title {
font-family: 'font_bold', sans-serif;
font-size: 16px;
line-height: 19px;
text-align: center;
color: #000;
margin-bottom: 10px;
&__link {
color: #000;
text-decoration: underline !important;
text-underline-position: under;
&:visited {
color: #000;
}
}
}
&__text {
font-size: 14px;
line-height: 20px;
text-align: center;
color: #1b2533;
max-width: 400px;
&__link {
color: #1b2533;
text-decoration: underline;
text-underline-position: under;
cursor: pointer;
}
}
}
&__faq {
max-width: 620px;
// display: flex; revert this when FAQ content will be confirmed
display: none;
flex-direction: column;
align-items: center;
margin: 0 auto;
&__title {
font-family: 'font_bold', sans-serif;
font-size: 36px;
line-height: 56px;
letter-spacing: 1px;
color: #14142b;
margin: 75px 0 30px;
}
}
}
</style>

View File

@ -29,7 +29,6 @@ import CreditsHistory from '@/components/account/billing/coupons/CouponArea.vue'
import SettingsArea from '@/components/account/SettingsArea.vue'; import SettingsArea from '@/components/account/SettingsArea.vue';
import Page404 from '@/components/errors/Page404.vue'; import Page404 from '@/components/errors/Page404.vue';
import BucketsView from '@/components/objects/BucketsView.vue'; import BucketsView from '@/components/objects/BucketsView.vue';
import EncryptData from '@/components/objects/EncryptData.vue';
import ObjectsArea from '@/components/objects/ObjectsArea.vue'; import ObjectsArea from '@/components/objects/ObjectsArea.vue';
import UploadFile from '@/components/objects/UploadFile.vue'; import UploadFile from '@/components/objects/UploadFile.vue';
import OnboardingTourArea from '@/components/onboardingTour/OnboardingTourArea.vue'; import OnboardingTourArea from '@/components/onboardingTour/OnboardingTourArea.vue';
@ -129,7 +128,6 @@ export abstract class RouteConfig {
public static SuccessScreen = new NavigationLink('success', 'Onboarding Success Screen'); public static SuccessScreen = new NavigationLink('success', 'Onboarding Success Screen');
// objects child paths. // objects child paths.
public static EncryptData = new NavigationLink('encrypt-data', 'Objects Encrypt Data');
public static BucketsManagement = new NavigationLink('management', 'Buckets Management'); public static BucketsManagement = new NavigationLink('management', 'Buckets Management');
public static BucketsDetails = new NavigationLink('details', 'Bucket Details'); public static BucketsDetails = new NavigationLink('details', 'Bucket Details');
public static UploadFile = new NavigationLink('upload/', 'Objects Upload'); public static UploadFile = new NavigationLink('upload/', 'Objects Upload');
@ -439,11 +437,6 @@ export const router = new Router({
name: RouteConfig.Buckets.name, name: RouteConfig.Buckets.name,
component: ObjectsArea, component: ObjectsArea,
children: [ children: [
{
path: RouteConfig.EncryptData.path,
name: RouteConfig.EncryptData.name,
component: EncryptData,
},
{ {
path: RouteConfig.BucketsManagement.path, path: RouteConfig.BucketsManagement.path,
name: RouteConfig.BucketsManagement.name, name: RouteConfig.BucketsManagement.name,

View File

@ -61,7 +61,6 @@ class State {
public couponCodeBillingUIEnabled = false, public couponCodeBillingUIEnabled = false,
public couponCodeSignupUIEnabled = false, public couponCodeSignupUIEnabled = false,
public isNewProjectDashboard = false, public isNewProjectDashboard = false,
public isNewObjectsFlow = false,
){} ){}
} }
@ -215,9 +214,6 @@ export const appStateModule = {
[APP_STATE_MUTATIONS.SET_ONB_CLEAN_API_KEY](state: State, apiKey: string): void { [APP_STATE_MUTATIONS.SET_ONB_CLEAN_API_KEY](state: State, apiKey: string): void {
state.appState.onbCleanApiKey = apiKey; state.appState.onbCleanApiKey = apiKey;
}, },
[APP_STATE_MUTATIONS.SET_OBJECTS_FLOW_STATUS](state: State, isNewObjectsFlow: boolean): void {
state.isNewObjectsFlow = isNewObjectsFlow;
},
[APP_STATE_MUTATIONS.SET_COUPON_CODE_BILLING_UI_STATUS](state: State, couponCodeBillingUIEnabled: boolean): void { [APP_STATE_MUTATIONS.SET_COUPON_CODE_BILLING_UI_STATUS](state: State, couponCodeBillingUIEnabled: boolean): void {
state.couponCodeBillingUIEnabled = couponCodeBillingUIEnabled; state.couponCodeBillingUIEnabled = couponCodeBillingUIEnabled;
}, },
@ -369,9 +365,6 @@ export const appStateModule = {
[APP_STATE_ACTIONS.SET_PROJECT_DASHBOARD_STATUS]: function ({ commit }: AppContext, isNewProjectDashboard: boolean): void { [APP_STATE_ACTIONS.SET_PROJECT_DASHBOARD_STATUS]: function ({ commit }: AppContext, isNewProjectDashboard: boolean): void {
commit(APP_STATE_MUTATIONS.SET_PROJECT_DASHBOARD_STATUS, isNewProjectDashboard); commit(APP_STATE_MUTATIONS.SET_PROJECT_DASHBOARD_STATUS, isNewProjectDashboard);
}, },
[APP_STATE_ACTIONS.SET_OBJECTS_FLOW_STATUS]: function ({ commit }: AppContext, isNewObjectsFlow: boolean): void {
commit(APP_STATE_MUTATIONS.SET_OBJECTS_FLOW_STATUS, isNewObjectsFlow);
},
[APP_STATE_ACTIONS.SET_COUPON_CODE_BILLING_UI_STATUS]: function ({ commit }: AppContext, couponCodeBillingUIEnabled: boolean): void { [APP_STATE_ACTIONS.SET_COUPON_CODE_BILLING_UI_STATUS]: function ({ commit }: AppContext, couponCodeBillingUIEnabled: boolean): void {
commit(APP_STATE_MUTATIONS.SET_COUPON_CODE_BILLING_UI_STATUS, couponCodeBillingUIEnabled); commit(APP_STATE_MUTATIONS.SET_COUPON_CODE_BILLING_UI_STATUS, couponCodeBillingUIEnabled);
}, },

View File

@ -56,6 +56,5 @@ export const APP_STATE_MUTATIONS = {
SET_ONB_API_KEY_STEP_BACK_ROUTE: 'SET_ONB_API_KEY_STEP_BACK_ROUTE', SET_ONB_API_KEY_STEP_BACK_ROUTE: 'SET_ONB_API_KEY_STEP_BACK_ROUTE',
SET_ONB_API_KEY: 'SET_ONB_API_KEY', SET_ONB_API_KEY: 'SET_ONB_API_KEY',
SET_ONB_CLEAN_API_KEY: 'SET_ONB_CLEAN_API_KEY', SET_ONB_CLEAN_API_KEY: 'SET_ONB_CLEAN_API_KEY',
SET_OBJECTS_FLOW_STATUS: 'SET_OBJECTS_FLOW_STATUS',
SET_ONB_OS: 'SET_ONB_OS', SET_ONB_OS: 'SET_ONB_OS',
}; };

View File

@ -31,7 +31,6 @@ export const APP_STATE_ACTIONS = {
SET_COUPON_CODE_BILLING_UI_STATUS: 'SET_COUPON_CODE_BILLING_UI_STATUS', SET_COUPON_CODE_BILLING_UI_STATUS: 'SET_COUPON_CODE_BILLING_UI_STATUS',
SET_COUPON_CODE_SIGNUP_UI_STATUS: 'SET_COUPON_CODE_SIGNUP_UI_STATUS', SET_COUPON_CODE_SIGNUP_UI_STATUS: 'SET_COUPON_CODE_SIGNUP_UI_STATUS',
SET_PROJECT_DASHBOARD_STATUS: 'SET_PROJECT_DASHBOARD_STATUS', SET_PROJECT_DASHBOARD_STATUS: 'SET_PROJECT_DASHBOARD_STATUS',
SET_OBJECTS_FLOW_STATUS: 'SET_OBJECTS_FLOW_STATUS',
}; };
export const NOTIFICATION_ACTIONS = { export const NOTIFICATION_ACTIONS = {

View File

@ -1,30 +0,0 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
import { createLocalVue, shallowMount } from '@vue/test-utils';
import Vuex from 'vuex';
import { router } from '@/router';
import { appStateModule } from '@/store/modules/appState';
import GeneratePassphrase from '@/components/common/GeneratePassphrase.vue';
const localVue = createLocalVue();
localVue.use(Vuex);
const store = new Vuex.Store({ modules: {
appStateModule,
} });
describe('GeneratePassphrase.vue', () => {
it('renders correctly with default props', () => {
const wrapper = shallowMount<GeneratePassphrase>(GeneratePassphrase, {
localVue,
router,
store,
});
expect(wrapper.vm.passphrase).toBeTruthy();
expect(wrapper).toMatchSnapshot();
});
});

View File

@ -1,41 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`GeneratePassphrase.vue renders correctly with default props 1`] = `
<div class="encrypt-container">
<encrypticon-stub></encrypticon-stub>
<h1 aria-roledescription="enc-title" class="encrypt-container__title">Encryption passphrase</h1>
<p class="encrypt-container__info">
The encryption passphrase is used to encrypt and access the data that you upload to Storj.
</p>
<div class="encrypt-container__functional">
<div class="encrypt-container__functional__header">
<p class="encrypt-container__functional__header__gen active">
Generate a new passphrase
</p>
<div class="encrypt-container__functional__header__right">
<p aria-roledescription="enter-passphrase-label" class="encrypt-container__functional__header__right__enter">
Enter your own passphrase
</p>
<vinfo-stub title="" buttonlabel="" onbuttonclick="[Function]" class="encrypt-container__functional__header__right__info-button"></vinfo-stub>
</div>
</div>
<div class="encrypt-container__functional__generate">
<p class="encrypt-container__functional__generate__value"></p>
<vbutton-stub label="Copy" width="66px" height="30px" fontsize="16px" borderradius="6px" icon="none" isbluewhite="true" isuppercase="true" onpress="[Function]" class="encrypt-container__functional__generate__button"></vbutton-stub>
</div>
<h2 aria-roledescription="warning-title" class="encrypt-container__functional__warning-title">
Save your encryption passphrase
</h2>
<p class="encrypt-container__functional__warning-msg">
Please note that Storj does not know or store your encryption passphrase. If you lose it, you will not
be able to recover your files.
</p>
<p class="encrypt-container__functional__download">Download as a text file</p>
<vcheckbox-stub label="I understand, and I have saved the passphrase." class="encrypt-container__functional__checkbox"></vcheckbox-stub>
</div>
<div class="encrypt-container__buttons">
<!---->
<vbutton-stub label="Next >" width="inherit" height="64px" fontsize="16px" borderradius="62px" icon="none" isdisabled="true" onpress="[Function]"></vbutton-stub>
</div>
</div>
`;