web/satellite: create project passphrase modal

Added create project passphrase modal.
User can select between generated and entered passphrase.

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

Change-Id: I69887562230c7c8002e9bc763ac24c551a0caa1d
This commit is contained in:
Vitalii 2022-11-29 15:54:57 +02:00 committed by Storj Robot
parent 3fe6aee786
commit b5aadff0f7
17 changed files with 911 additions and 261 deletions

View File

@ -185,6 +185,7 @@ export default defineComponent({
justify-content: center;
background-color: var(--c-blue-3);
cursor: pointer;
box-sizing: border-box;
.trash-icon {
margin-right: 5px;

View File

@ -7,7 +7,6 @@
<CreateProjectModal v-if="isCreateProjectModal" />
<AddPaymentMethodModal v-if="isAddPMModal" />
<OpenBucketModal v-if="isOpenBucketModal" />
<SetEncryptionPassphrase v-if="isSetEncryptionPassphraseModalShown" />
<MFARecoveryCodesModal v-if="isMFARecoveryModal" />
<EnableMFAModal v-if="isEnableMFAModal" />
<DisableMFAModal v-if="isDisableMFAModal" />
@ -22,6 +21,7 @@
<DeleteBucketModal v-if="isDeleteBucketModal" />
<AddCouponCodeModal v-if="isAddCouponModal" />
<NewBillingAddCouponCodeModal v-if="isNewBillingAddCouponModal" />
<CreateProjectPassphraseModal v-if="isCreateProjectPassphraseModal" />
</div>
</template>
@ -32,7 +32,6 @@ import CreateProjectPromptModal from '@/components/modals/CreateProjectPromptMod
import CreateProjectModal from '@/components/modals/CreateProjectModal.vue';
import AddPaymentMethodModal from '@/components/modals/AddPaymentMethodModal.vue';
import OpenBucketModal from '@/components/modals/OpenBucketModal.vue';
import SetEncryptionPassphrase from '@/components/modals/SetEncryptionPassphrase.vue';
import MFARecoveryCodesModal from '@/components/modals/MFARecoveryCodesModal.vue';
import EnableMFAModal from '@/components/modals/EnableMFAModal.vue';
import DisableMFAModal from '@/components/modals/DisableMFAModal.vue';
@ -47,10 +46,12 @@ import ShareObjectModal from '@/components/modals/ShareObjectModal.vue';
import DeleteBucketModal from '@/components/modals/DeleteBucketModal.vue';
import AddCouponCodeModal from '@/components/modals/AddCouponCodeModal.vue';
import NewBillingAddCouponCodeModal from '@/components/modals/NewBillingAddCouponCodeModal.vue';
import CreateProjectPassphraseModal from '@/components/modals/createProjectPassphrase/CreateProjectPassphraseModal.vue';
// @vue/component
@Component({
components: {
CreateProjectPassphraseModal,
DeleteBucketModal,
CreateProjectPromptModal,
CreateProjectModal,
@ -69,7 +70,6 @@ import NewBillingAddCouponCodeModal from '@/components/modals/NewBillingAddCoupo
NewFolderModal,
AddCouponCodeModal,
NewBillingAddCouponCodeModal,
SetEncryptionPassphrase,
},
})
export default class AllModals extends Vue {
@ -88,13 +88,6 @@ export default class AllModals extends Vue {
return this.$store.state.appStateModule.appState.isCreateProjectModalShown;
}
/**
* Indicates if set encryption passphrase modal is shown.
*/
public get isSetEncryptionPassphraseModalShown(): boolean {
return this.$store.state.appStateModule.appState.isSetEncryptionPassphraseModalShown;
}
/**
* Indicates if add payment method modal is shown.
*/
@ -206,5 +199,12 @@ export default class AllModals extends Vue {
public get isNewBillingAddCouponModal(): boolean {
return this.$store.state.appStateModule.appState.isNewBillingAddCouponModalShown;
}
/**
* Indicates if create project passphrase modal is shown.
*/
public get isCreateProjectPassphraseModal(): boolean {
return this.$store.state.appStateModule.appState.isCreateProjectPassphraseModalShown;
}
}
</script>

View File

@ -1,246 +0,0 @@
// Copyright (C) 2022 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<VModal :on-close="closeModal">
<template #content>
<div class="modal">
<Icon />
<h1 class="modal__title">Set/Update Project Encryption Passphrase</h1>
<p class="modal__info">
TODO(moby)
</p>
<VInput
label="Encryption Passphrase"
placeholder="Enter a passphrase here"
:error="enterError"
role-description="passphrase"
is-password
:disabled="isLoading"
@setData="setPassphrase"
/>
<div class="modal__buttons">
<VButton
label="Cancel"
height="48px"
:is-transparent="true"
:on-press="closeModal"
:is-disabled="isLoading"
/>
<VButton
label="Continue ->"
height="48px"
:on-press="onContinue"
:is-disabled="isLoading"
/>
</div>
</div>
</template>
</VModal>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { APP_STATE_MUTATIONS } from '@/store/mutationConstants';
import { OBJECTS_ACTIONS, OBJECTS_MUTATIONS } from '@/store/modules/objects';
import { MetaUtils } from '@/utils/meta';
import { AccessGrant, EdgeCredentials } from '@/types/accessGrants';
import { ACCESS_GRANTS_ACTIONS } from '@/store/modules/accessGrants';
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
import VModal from '@/components/common/VModal.vue';
import VInput from '@/components/common/VInput.vue';
import VButton from '@/components/common/VButton.vue';
import Icon from '@/../static/images/objects/openBucket.svg';
// @vue/component
@Component({
components: {
VInput,
VModal,
Icon,
VButton,
},
})
export default class OpenBucketModal extends Vue {
private worker: Worker;
private readonly FILE_BROWSER_AG_NAME: string = 'Web file browser API key';
public enterError = '';
public passphrase = '';
public isLoading = false;
/**
* Lifecycle hook after initial render.
* Sets local worker.
*/
public mounted(): void {
this.setWorker();
}
/**
* Sets access and navigates to object browser.
*/
public async onContinue(): Promise<void> {
if (this.isLoading) return;
if (!this.passphrase) {
this.enterError = 'Passphrase can\'t be empty';
return;
}
this.isLoading = true;
try {
await this.setAccess();
this.isLoading = false;
this.closeModal();
await this.$notify.success('Successfully set project passphrase');
} catch (e) {
await this.$notify.error(e.message);
this.isLoading = false;
}
}
/**
* Sets access to S3 client.
*/
public async setAccess(): Promise<void> {
if (!this.apiKey) {
await this.$store.dispatch(ACCESS_GRANTS_ACTIONS.DELETE_BY_NAME_AND_PROJECT_ID, this.FILE_BROWSER_AG_NAME);
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 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': [],
'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 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,
'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);
await this.$store.dispatch(OBJECTS_ACTIONS.SET_S3_CLIENT);
await this.$store.commit(OBJECTS_MUTATIONS.SET_PROMPT_FOR_PASSPHRASE, false);
}
/**
* 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);
};
}
/**
* Closes open bucket modal.
*/
public closeModal(): void {
if (this.isLoading) return;
this.$store.commit(APP_STATE_MUTATIONS.TOGGLE_SET_ENCRYPTION_PASSPHRASE_MODAL_SHOWN);
}
/**
* Sets passphrase from child component.
*/
public setPassphrase(passphrase: string): void {
if (this.enterError) this.enterError = '';
this.passphrase = passphrase;
this.$store.dispatch(OBJECTS_ACTIONS.SET_PASSPHRASE, this.passphrase);
}
/**
* Returns apiKey from store.
*/
private get apiKey(): string {
return this.$store.state.objectsModule.apiKey;
}
}
</script>
<style scoped lang="scss">
.modal {
font-family: 'font_regular', sans-serif;
display: flex;
flex-direction: column;
align-items: center;
padding: 62px 62px 54px;
max-width: 500px;
@media screen and (max-width: 600px) {
padding: 62px 24px 54px;
}
&__title {
font-family: 'font_bold', sans-serif;
font-size: 26px;
line-height: 31px;
color: #131621;
margin: 30px 0 15px;
}
&__info {
font-size: 16px;
line-height: 21px;
text-align: center;
color: #354049;
margin-bottom: 32px;
}
&__input {
margin-bottom: 21px;
}
&__buttons {
display: flex;
column-gap: 20px;
margin-top: 31px;
width: 100%;
@media screen and (max-width: 500px) {
flex-direction: column-reverse;
column-gap: unset;
row-gap: 20px;
}
}
}
</style>

View File

@ -0,0 +1,401 @@
// Copyright (C) 2022 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<VModal :on-close="closeModal">
<template #content>
<div class="modal">
<SelectPassphraseModeStep
v-if="activeStep === CreateProjectPassphraseStep.SelectMode"
:is-generate="selectedOption === CreatePassphraseOption.Generate"
:set-generate="() => setOption(CreatePassphraseOption.Generate)"
:set-enter="() => setOption(CreatePassphraseOption.Enter)"
/>
<PassphraseGeneratedStep
v-if="activeStep === CreateProjectPassphraseStep.PassphraseGenerated"
:passphrase="passphrase"
/>
<EnterPassphraseStep
v-if="activeStep === CreateProjectPassphraseStep.EnterPassphrase"
:set-passphrase="setPassphrase"
:enter-error="enterError"
/>
<SuccessStep v-if="activeStep === CreateProjectPassphraseStep.Success" />
<div v-if="isCheckVisible" class="modal__save-container" @click="toggleSaved">
<div class="modal__save-container__check" :class="{checked: passphraseSaved}">
<CheckIcon />
</div>
<div class="modal__save-container__info">
<h2 class="modal__save-container__info__title">
Yes I understand and saved the passphrase.
</h2>
<p class="modal__save-container__info__msg">
Check the box to continue.
</p>
</div>
</div>
<div class="modal__buttons">
<VButton
v-if="activeStep !== CreateProjectPassphraseStep.Success"
:label="activeStep === CreateProjectPassphraseStep.SelectMode ? 'Cancel' : 'Back'"
width="100%"
height="48px"
:is-white="true"
:on-press="onCancelOrBack"
/>
<VButton
label="Continue ->"
:width="activeStep === CreateProjectPassphraseStep.Success ? '200px' : '100%'"
height="48px"
:on-press="onContinue"
:is-disabled="continueButtonDisabled"
/>
</div>
</div>
</template>
</VModal>
</template>
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue';
import { generateMnemonic } from 'bip39';
import { useNotify, useStore } from '@/utils/hooks';
import { APP_STATE_MUTATIONS } from '@/store/mutationConstants';
import { ACCESS_GRANTS_ACTIONS } from '@/store/modules/accessGrants';
import { AccessGrant, EdgeCredentials } from '@/types/accessGrants';
import { OBJECTS_ACTIONS, OBJECTS_MUTATIONS } from '@/store/modules/objects';
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
import { MetaUtils } from '@/utils/meta';
import VModal from '@/components/common/VModal.vue';
import VButton from '@/components/common/VButton.vue';
import SelectPassphraseModeStep from '@/components/modals/createProjectPassphrase/SelectPassphraseModeStep.vue';
import PassphraseGeneratedStep from '@/components/modals/createProjectPassphrase/PassphraseGeneratedStep.vue';
import EnterPassphraseStep from '@/components/modals/createProjectPassphrase/EnterPassphraseStep.vue';
import SuccessStep from '@/components/modals/createProjectPassphrase/SuccessStep.vue';
import CheckIcon from '@/../static/images/projectPassphrase/check.svg';
enum CreateProjectPassphraseStep {
SelectMode = 'SelectMode',
PassphraseGenerated = 'PassphraseGenerated',
EnterPassphrase = 'EnterPassphrase',
Success = 'Success',
}
enum CreatePassphraseOption {
Generate = 'Generate',
Enter = 'Enter',
}
const FILE_BROWSER_AG_NAME = 'Web file browser API key';
const store = useStore();
const notify = useNotify();
const selectedOption = ref<CreatePassphraseOption>(CreatePassphraseOption.Generate);
const activeStep = ref<CreateProjectPassphraseStep>(CreateProjectPassphraseStep.SelectMode);
const passphrase = ref<string>('');
const enterError = ref<string>('');
const isLoading = ref<boolean>(false);
const passphraseSaved = ref<boolean>(false);
const worker = ref<Worker | null>(null);
/**
* Indicates if save passphrase checkbox container is shown.
*/
const isCheckVisible = computed((): boolean => {
return activeStep.value === CreateProjectPassphraseStep.PassphraseGenerated ||
activeStep.value === CreateProjectPassphraseStep.EnterPassphrase;
});
/**
* Indicates if continue button is disabled.
*/
const continueButtonDisabled = computed((): boolean => {
return (isCheckVisible.value && !passphraseSaved.value) || isLoading.value;
});
/**
* Returns web file browser api key from vuex state.
*/
const apiKey = computed((): string => {
return store.state.objectsModule.apiKey;
});
/**
* Lifecycle hook after initial render.
* Sets local worker.
*/
onMounted(() => {
setWorker();
});
/**
* Sets passphrase input value to local variable.
* Resets error is present.
* @param value
*/
function setPassphrase(value: string): void {
if (enterError.value) {
enterError.value = '';
}
passphrase.value = value;
}
/**
* Sets create passphrase option (generated or entered).
* @param option
*/
function setOption(option: CreatePassphraseOption): void {
selectedOption.value = option;
}
/**
* Toggles save passphrase checkbox.
*/
function toggleSaved(): void {
passphraseSaved.value = !passphraseSaved.value;
}
/**
* Closes modal.
*/
function closeModal(): void {
store.commit(APP_STATE_MUTATIONS.TOGGLE_CREATE_PROJECT_PASSPHRASE_MODAL_SHOWN);
}
/**
* Sets local worker with worker instantiated in store.
*/
function setWorker(): void {
worker.value = store.state.accessGrantsModule.accessGrantsWebWorker;
if (worker.value) {
worker.value.onerror = (error: ErrorEvent) => {
notify.error(error.message);
};
}
}
/**
* Generates s3 credentials from provided passphrase and stores it in vuex state to be reused.
*/
async function setAccess(): Promise<void> {
if (!worker.value) {
notify.error('Worker is not defined');
return;
}
if (!apiKey.value) {
await store.dispatch(ACCESS_GRANTS_ACTIONS.DELETE_BY_NAME_AND_PROJECT_ID, FILE_BROWSER_AG_NAME);
const cleanAPIKey: AccessGrant = await store.dispatch(ACCESS_GRANTS_ACTIONS.CREATE, FILE_BROWSER_AG_NAME);
await store.dispatch(OBJECTS_ACTIONS.SET_API_KEY, cleanAPIKey.secret);
}
const now = new Date();
const inThreeDays = new Date(now.setDate(now.getDate() + 3));
await worker.value.postMessage({
'type': 'SetPermission',
'isDownload': true,
'isUpload': true,
'isList': true,
'isDelete': true,
'notAfter': inThreeDays.toISOString(),
'buckets': [],
'apiKey': apiKey.value,
});
const grantEvent: MessageEvent = await new Promise(resolve => {
if (worker.value) {
worker.value.onmessage = resolve;
}
});
if (grantEvent.data.error) {
throw new Error(grantEvent.data.error);
}
const salt = await store.dispatch(PROJECTS_ACTIONS.GET_SALT, store.getters.selectedProject.id);
const satelliteNodeURL: string = MetaUtils.getMetaContent('satellite-nodeurl');
worker.value.postMessage({
'type': 'GenerateAccess',
'apiKey': grantEvent.data.value,
'passphrase': passphrase.value,
'salt': salt,
'satelliteNodeURL': satelliteNodeURL,
});
const accessGrantEvent: MessageEvent = await new Promise(resolve => {
if (worker.value) {
worker.value.onmessage = resolve;
}
});
if (accessGrantEvent.data.error) {
throw new Error(accessGrantEvent.data.error);
}
const accessGrant = accessGrantEvent.data.value;
const gatewayCredentials: EdgeCredentials = await store.dispatch(ACCESS_GRANTS_ACTIONS.GET_GATEWAY_CREDENTIALS, { accessGrant });
await store.dispatch(OBJECTS_ACTIONS.SET_GATEWAY_CREDENTIALS, gatewayCredentials);
await store.dispatch(OBJECTS_ACTIONS.SET_S3_CLIENT);
await store.commit(OBJECTS_MUTATIONS.SET_PROMPT_FOR_PASSPHRASE, false);
}
/**
* Holds on continue button click logic.
* Navigates further through flow.
*/
async function onContinue(): Promise<void> {
if (activeStep.value === CreateProjectPassphraseStep.SelectMode) {
if (selectedOption.value === CreatePassphraseOption.Generate) {
if (passphrase.value) {
passphrase.value = '';
}
passphrase.value = generateMnemonic();
activeStep.value = CreateProjectPassphraseStep.PassphraseGenerated;
return;
}
if (selectedOption.value === CreatePassphraseOption.Enter) {
if (passphrase.value) {
passphrase.value = '';
}
activeStep.value = CreateProjectPassphraseStep.EnterPassphrase;
return;
}
}
if (
activeStep.value === CreateProjectPassphraseStep.PassphraseGenerated ||
activeStep.value === CreateProjectPassphraseStep.EnterPassphrase
) {
if (isLoading.value) return;
if (!passphrase.value) {
enterError.value = 'Passphrase can\'t be empty';
return;
}
isLoading.value = true;
try {
await setAccess();
store.dispatch(OBJECTS_ACTIONS.SET_PASSPHRASE, passphrase.value);
activeStep.value = CreateProjectPassphraseStep.Success;
} catch (e) {
await notify.error(e.message);
} finally {
isLoading.value = false;
}
return;
}
if (activeStep.value === CreateProjectPassphraseStep.Success) {
closeModal();
}
}
/**
* Holds on cancel/back button click logic.
* Navigates backwards through flow.
*/
function onCancelOrBack(): void {
if (activeStep.value === CreateProjectPassphraseStep.SelectMode) {
closeModal();
return;
}
if (
activeStep.value === CreateProjectPassphraseStep.PassphraseGenerated ||
activeStep.value === CreateProjectPassphraseStep.EnterPassphrase
) {
passphrase.value = '';
if (passphraseSaved.value) {
passphraseSaved.value = false;
}
activeStep.value = CreateProjectPassphraseStep.SelectMode;
return;
}
}
</script>
<style scoped lang="scss">
.modal {
padding: 43px 60px 53px;
font-family: 'font_regular', sans-serif;
@media screen and (max-width: 615px) {
padding: 30px 20px;
}
&__buttons {
display: flex;
align-items: center;
justify-content: center;
column-gap: 33px;
margin-top: 20px;
@media screen and (max-width: 530px) {
column-gap: unset;
flex-direction: column-reverse;
row-gap: 15px;
}
}
&__save-container {
padding: 14px 20px;
display: flex;
align-items: center;
cursor: pointer;
margin-top: 16px;
background: #fafafb;
border: 1px solid #c8d3de;
border-radius: 10px;
&__check {
background: #fff;
border: 1px solid #c8d3de;
border-radius: 8px;
min-width: 32px;
min-height: 32px;
display: flex;
align-items: center;
justify-content: center;
}
&__info {
margin-left: 12px;
&__title {
font-family: 'font_bold', sans-serif;
font-size: 14px;
line-height: 20px;
color: #091c45;
margin-bottom: 8px;
text-align: left;
}
&__msg {
font-size: 12px;
line-height: 18px;
color: #091c45;
text-align: left;
}
}
}
}
.checked {
background: #00ac26;
border-color: #00ac26;
}
</style>

View File

@ -0,0 +1,61 @@
// Copyright (C) 2022 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="enter-step">
<LockIcon />
<h1 class="enter-step__title">Enter Passphrase</h1>
<p class="enter-step__info">
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. Save your passphrase and keep it safe.
</p>
<VInput
label="Encryption Passphrase"
:is-password="true"
width="100%"
height="56px"
placeholder="Enter Encryption Passphrase"
:error="enterError"
@setData="setPassphrase"
/>
</div>
</template>
<script setup lang="ts">
import VInput from '@/components/common/VInput.vue';
import LockIcon from '@/../static/images/projectPassphrase/lock.svg';
const props = withDefaults(defineProps<{
setPassphrase?: (v: string) => void,
enterError?: string,
}>(), {
setPassphrase: () => () => {},
enterError: '',
});
</script>
<style scoped lang="scss">
.enter-step {
display: flex;
flex-direction: column;
align-items: center;
font-family: 'font_regular', sans-serif;
max-width: 433px;
&__title {
font-family: 'font_bold', sans-serif;
font-size: 32px;
line-height: 39px;
color: #1b2533;
margin: 14px 0;
}
&__info {
font-size: 14px;
line-height: 19px;
color: #354049;
margin-bottom: 24px;
}
}
</style>

View File

@ -0,0 +1,174 @@
// Copyright (C) 2022 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="generated-step">
<GeneratedIcon />
<h1 class="generated-step__title">Passphrase Generated</h1>
<p class="generated-step__info">
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. Save your passphrase and keep it safe.
</p>
<div class="generated-step__mnemonic">
<p class="generated-step__mnemonic__value">
{{ passphrase }}
</p>
<div class="generated-step__mnemonic__buttons">
<v-button
class="copy-button"
:label="isPassphraseCopied ? 'Copied' : 'Copy to clipboard'"
width="156px"
height="40px"
:is-white="!isPassphraseCopied"
:is-white-green="isPassphraseCopied"
font-size="13px"
:on-press="onCopyPassphraseClick"
>
<template v-if="!isPassphraseCopied" #icon>
<copy-icon class="copy-icon" />
</template>
</v-button>
<v-button
:label="isPassphraseDownloaded ? 'Downloaded' : 'Download'"
font-size="13px"
width="100%"
height="40px"
:is-green-white="isPassphraseDownloaded"
:on-press="downloadPassphrase"
>
<template v-if="!isPassphraseDownloaded" #icon>
<download-icon class="button-icon" />
</template>
</v-button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
import { AnalyticsHttpApi } from '@/api/analytics';
import { useCopy, useNotify } from '@/utils/hooks';
import { Download } from '@/utils/download';
import VButton from '@/components/common/VButton.vue';
import GeneratedIcon from '@/../static/images/projectPassphrase/generated.svg';
import CopyIcon from '@/../static/images/common/copy.svg';
import DownloadIcon from '@/../static/images/common/download.svg';
const props = withDefaults(defineProps<{
passphrase?: string,
}>(), {
passphrase: '',
});
const notify = useNotify();
const copy = useCopy();
const currentDate = new Date().toISOString();
const isPassphraseCopied = ref<boolean>(false);
const isPassphraseDownloaded = ref<boolean>(false);
const analytics = new AnalyticsHttpApi();
/**
* Copies passphrase to clipboard.
*/
function onCopyPassphraseClick(): void {
copy(props.passphrase);
isPassphraseCopied.value = true;
analytics.eventTriggered(AnalyticsEvent.COPY_TO_CLIPBOARD_CLICKED);
notify.success(`Passphrase was copied successfully`);
}
/**
* Downloads passphrase to .txt file.
*/
function downloadPassphrase(): void {
isPassphraseDownloaded.value = true;
Download.file(props.passphrase, `passphrase-${currentDate}.txt`);
analytics.eventTriggered(AnalyticsEvent.DOWNLOAD_TXT_CLICKED);
}
</script>
<style scoped lang="scss">
.generated-step {
display: flex;
flex-direction: column;
align-items: center;
font-family: 'font_regular', sans-serif;
max-width: 433px;
&__title {
font-family: 'font_bold', sans-serif;
font-size: 32px;
line-height: 39px;
color: #1b2533;
margin: 14px 0;
}
&__info {
font-size: 14px;
line-height: 19px;
color: #354049;
margin-bottom: 24px;
}
&__mnemonic {
display: flex;
align-items: center;
background: #ebeef1;
border: 1px solid #d8dee3;
border-radius: 10px;
padding: 10px 15px 10px 23px;
@media screen and (max-width: 530px) {
flex-direction: column;
}
&__value {
font-size: 16px;
line-height: 26px;
letter-spacing: -0.02em;
color: #091c45;
text-align: justify;
}
&__buttons {
display: flex;
flex-direction: column;
row-gap: 8px;
margin-left: 14px;
@media screen and (max-width: 530px) {
margin: 15px 0 0;
}
}
}
}
.copy-button {
background-color: #fff !important;
}
.copy-icon {
margin-right: 5px;
:deep(path),
:deep(rect) {
stroke: #354049;
}
}
.button-icon {
margin-right: 5px;
:deep(path),
:deep(rect) {
stroke: white;
}
}
</style>

View File

@ -0,0 +1,156 @@
// Copyright (C) 2022 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="passphrase-mode">
<LockIcon />
<h1 class="passphrase-mode__title">Encryption Passphrase</h1>
<p class="passphrase-mode__info">
The encryption passphrase will be used to encrypt all the files you upload in this project. We encourage
you to generate the encryption passphrase. You can also enter your own passphrase.
</p>
<div
class="passphrase-mode__option"
:class="{selected: isGenerate}"
@click="setGenerate"
>
<div
class="passphrase-mode__option__check"
:class="{'selected-check': isGenerate}"
>
<CheckIcon />
</div>
<div class="passphrase-mode__option__info">
<h2 class="passphrase-mode__option__info__title">
Generate passphrase
</h2>
<p class="passphrase-mode__option__info__msg">
Automatically generate 12-word passphrase.
</p>
</div>
</div>
<div
class="passphrase-mode__option"
:class="{selected: !isGenerate}"
@click="setEnter"
>
<div
class="passphrase-mode__option__check"
:class="{'selected-check': !isGenerate}"
>
<CheckIcon />
</div>
<div class="passphrase-mode__option__info">
<h2 class="passphrase-mode__option__info__title">
Enter passphrase
</h2>
<p class="passphrase-mode__option__info__msg">
You can also enter your own passphrase.
</p>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import LockIcon from '@/../static/images/projectPassphrase/lock.svg';
import CheckIcon from '@/../static/images/projectPassphrase/check.svg';
const props = withDefaults(defineProps<{
isGenerate?: boolean,
setGenerate?: () => void
setEnter?: () => void
}>(), {
isGenerate: true,
setGenerate: () => () => {},
setEnter: () => () => {},
});
</script>
<style scoped lang="scss">
.passphrase-mode {
display: flex;
flex-direction: column;
align-items: center;
font-family: 'font_regular', sans-serif;
max-width: 433px;
&__title {
font-family: 'font_bold', sans-serif;
font-size: 32px;
line-height: 39px;
color: #1b2533;
margin: 14px 0;
}
&__info {
font-size: 14px;
line-height: 19px;
color: #354049;
margin-bottom: 24px;
}
&__option {
padding: 14px 20px;
display: flex;
align-items: center;
border: 1px solid #d8dee3;
background-color: #fafafb;
border-radius: 10px;
margin-bottom: 9px;
width: calc(100% - 40px);
cursor: pointer;
&__check {
min-width: 32px;
min-height: 32px;
background-color: #fff;
border: 1px solid #c8d3de;
border-radius: 16px;
display: flex;
align-items: center;
justify-content: center;
}
&__info {
margin-left: 16px;
align-items: flex-start;
&__title {
font-family: 'font_bold', sans-serif;
font-size: 14px;
line-height: 20px;
color: #091c45;
text-align: left;
margin-bottom: 8px;
}
&__msg {
font-size: 12px;
line-height: 18px;
color: #091c45;
text-align: left;
}
}
&:hover {
background-color: #fff;
border-color: #0149ff;
}
}
}
.selected {
background-color: #fff;
border-color: #929fb1;
&:hover {
border-color: #929fb1;
}
}
.selected-check {
border-color: #00ac26;
background-color: #00ac26;
}
</style>

View File

@ -0,0 +1,60 @@
// Copyright (C) 2022 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="success-step">
<SuccessIcon class="success-step__big-icon" />
<SmallSuccessIcon class="success-step__small-icon" />
<h1 class="success-step__title">Success</h1>
<p class="success-step__info">
Your encryption passphrase is ready to use. Now you can upload files into your buckets securely using an
encryption only you know.
</p>
</div>
</template>
<script setup lang="ts">
import SuccessIcon from '@/../static/images/projectPassphrase/success.svg';
import SmallSuccessIcon from '@/../static/images/projectPassphrase/smallSuccess.svg';
</script>
<style scoped lang="scss">
.success-step {
display: flex;
flex-direction: column;
align-items: center;
font-family: 'font_regular', sans-serif;
max-width: 433px;
&__small-icon {
display: none;
@media screen and (max-width: 530px) {
display: block;
}
}
&__title {
font-family: 'font_bold', sans-serif;
font-size: 32px;
line-height: 39px;
color: #1b2533;
margin: 14px 0;
}
&__info {
font-size: 14px;
line-height: 19px;
color: #354049;
margin-bottom: 24px;
max-width: 376px;
}
}
@media screen and (max-width: 530px) {
.success-step__big-icon {
display: none;
}
}
</style>

View File

@ -252,7 +252,7 @@ export default class NavigationArea extends Vue {
* Toggles switch passphrase modal.
*/
public toggleEncryptionPassphraseShown(): void {
this.$store.commit(APP_STATE_MUTATIONS.TOGGLE_SET_ENCRYPTION_PASSPHRASE_MODAL_SHOWN);
this.$store.commit(APP_STATE_MUTATIONS.TOGGLE_CREATE_PROJECT_PASSPHRASE_MODAL_SHOWN);
}
public debugShowAccess(): void {

View File

@ -33,7 +33,6 @@ class ViewsState {
public isCreateProjectModalShown = false,
public isAddPMModalShown = false,
public isOpenBucketModalShown = false,
public isSetEncryptionPassphraseModalShown = false,
public isMFARecoveryModalShown = false,
public isEnableMFAModalShown = false,
public isDisableMFAModalShown = false,
@ -42,6 +41,7 @@ class ViewsState {
public isShareObjectModalShown = false,
public isDeleteBucketModalShown = false,
public isNewFolderModalShown = false,
public isCreateProjectPassphraseModalShown = false,
public isObjectDetailsModalShown = false,
public isAddCouponModalShown = false,
public isNewBillingAddCouponModalShown = false,
@ -144,8 +144,8 @@ export const appStateModule = {
[APP_STATE_MUTATIONS.TOGGLE_OPEN_BUCKET_MODAL_SHOWN](state: State): void {
state.appState.isOpenBucketModalShown = !state.appState.isOpenBucketModalShown;
},
[APP_STATE_MUTATIONS.TOGGLE_SET_ENCRYPTION_PASSPHRASE_MODAL_SHOWN](state: State): void {
state.appState.isSetEncryptionPassphraseModalShown = !state.appState.isSetEncryptionPassphraseModalShown;
[APP_STATE_MUTATIONS.TOGGLE_CREATE_PROJECT_PASSPHRASE_MODAL_SHOWN](state: State): void {
state.appState.isCreateProjectPassphraseModalShown = !state.appState.isCreateProjectPassphraseModalShown;
},
[APP_STATE_MUTATIONS.TOGGLE_MFA_RECOVERY_MODAL_SHOWN](state: State): void {
state.appState.isMFARecoveryModalShown = !state.appState.isMFARecoveryModalShown;

View File

@ -27,7 +27,6 @@ export const APP_STATE_MUTATIONS = {
TOGGLE_SUCCESSFUL_PASSWORD_RESET: 'TOGGLE_SUCCESSFUL_PASSWORD_RESET',
TOGGLE_SUCCESSFUL_PROJECT_CREATION_POPUP: 'TOGGLE_SUCCESSFUL_PROJECT_CREATION_POPUP',
TOGGLE_EDIT_PROFILE_MODAL_SHOWN: 'TOGGLE_EDIT_PROFILE_MODAL_SHOWN',
TOGGLE_SET_ENCRYPTION_PASSPHRASE_MODAL_SHOWN: 'TOGGLE_SET_ENCRYPTION_PASSPHRASE_MODAL_SHOWN',
TOGGLE_CHANGE_PASSWORD_MODAL_SHOWN: 'TOGGLE_CHANGE_PASSWORD_MODAL_SHOWN',
TOGGLE_UPLOAD_CANCEL_POPUP: 'TOGGLE_UPLOAD_CANCEL_POPUP',
TOGGLE_CREATE_PROJECT_PROMPT_POPUP: 'TOGGLE_CREATE_PROJECT_PROMPT_POPUP',
@ -45,6 +44,7 @@ export const APP_STATE_MUTATIONS = {
TOGGLE_ADD_COUPON_MODAL_SHOWN: 'TOGGLE_ADD_COUPON_MODAL_SHOWN',
TOGGLE_NEW_BILLING_ADD_COUPON_MODAL_SHOWN: 'TOGGLE_NEW_BILLING_ADD_COUPON_MODAL_SHOWN',
TOGGLE_NEW_FOLDER_MODAL_SHOWN: 'TOGGLE_NEW_FOLDER_MODAL_SHOWN',
TOGGLE_CREATE_PROJECT_PASSPHRASE_MODAL_SHOWN: 'TOGGLE_CREATE_PROJECT_PASSPHRASE_MODAL_SHOWN',
SHOW_DELETE_PAYMENT_METHOD_POPUP: 'SHOW_DELETE_PAYMENT_METHOD_POPUP',
SHOW_SET_DEFAULT_PAYMENT_METHOD_POPUP: 'SHOW_SET_DEFAULT_PAYMENT_METHOD_POPUP',
CLOSE_ALL: 'CLOSE_ALL',

View File

@ -23,3 +23,12 @@ export function useStore() {
export function useNotify() {
return getCurrentInstance()?.proxy.$notify || {} as Notificator;
}
export function useCopy() {
return getCurrentInstance()?.proxy.$copyText || {} as (text: string, container?: object | HTMLElement) => Promise<{
action: string,
text: string,
trigger: string | HTMLElement | HTMLCollection | NodeList,
clearSelection: () => void
}>;
}

View File

@ -0,0 +1,3 @@
<svg width="17" height="12" viewBox="0 0 17 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.6626 0.438444C16.2482 1.02345 16.2486 1.97241 15.6636 2.558L6.67014 11.5605C6.38912 11.8418 6.00783 11.9999 5.61021 12C5.21259 12.0001 4.83122 11.8422 4.55006 11.561L0.438974 7.44994C-0.146325 6.86464 -0.146325 5.91568 0.438974 5.33039C1.02427 4.74509 1.97323 4.74509 2.55853 5.33039L5.60931 8.38117L13.543 0.439504C14.128 -0.146087 15.077 -0.146562 15.6626 0.438444Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 544 B

View File

@ -0,0 +1,7 @@
<svg width="112" height="112" viewBox="0 0 112 112" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M46.0385 0H65.3697C80.8738 0 87.0253 1.71842 93.0589 4.94527C99.0926 8.17211 103.828 12.9074 107.055 18.941L107.3 19.4056C110.328 25.2188 111.957 31.3599 112 46.0385V65.3698C112 80.8738 110.282 87.0253 107.055 93.059C103.828 99.0926 99.0926 103.828 93.0589 107.055L92.5944 107.3C86.7812 110.328 80.6401 111.957 65.9615 112H46.6302C31.1262 112 24.9747 110.282 18.941 107.055C12.9074 103.828 8.17211 99.0926 4.94527 93.059L4.70003 92.5944C1.67181 86.7812 0.0431812 80.6401 0 65.9615V46.6303C0 31.1262 1.71842 24.9747 4.94527 18.941C8.17211 12.9074 12.9074 8.17211 18.941 4.94527L19.4056 4.70003C25.2188 1.67181 31.3599 0.0431812 46.0385 0Z" fill="#00AC26"/>
<path d="M56.4626 76.3289C68.2203 76.3289 77.7519 66.7975 77.7519 55.0399C77.7519 43.2824 68.2203 33.751 56.4626 33.751C44.7049 33.751 35.1733 43.2824 35.1733 55.0399C35.1733 66.7975 44.7049 76.3289 56.4626 76.3289Z" fill="#00AC26"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M55.9994 28.1968C71.5912 28.1968 84.2309 40.629 84.2309 55.9648C84.2309 71.3007 71.5912 83.7329 55.9994 83.7329C41.3473 83.7329 29.3022 72.7541 27.9036 58.7039C28.1179 53.787 28.5845 50.0463 29.2911 46.9459C33.0972 36.0371 43.6174 28.1968 55.9994 28.1968ZM55.9994 36.5272C44.9779 36.5272 36.0985 45.2608 36.0985 55.9648C36.0985 66.6688 44.9779 75.4025 55.9994 75.4025C67.0209 75.4025 75.9003 66.6688 75.9003 55.9648C75.9003 45.2608 67.0209 36.5272 55.9994 36.5272Z" fill="#00AC26"/>
<rect x="18.5703" y="19.5112" width="73.8262" height="73.8262" rx="36.9131" fill="#00AC26"/>
<path d="M55.4835 23.2026C73.8314 23.2026 88.7053 38.0765 88.7053 56.4244C88.7053 74.7723 73.8314 89.6462 55.4835 89.6462C37.1356 89.6462 22.2617 74.7723 22.2617 56.4244C22.2617 38.0765 37.1356 23.2026 55.4835 23.2026ZM55.4835 29.2933C40.4994 29.2933 28.3524 41.4403 28.3524 56.4244C28.3524 71.4085 40.4994 83.5555 55.4835 83.5555C70.4676 83.5555 82.6146 71.4085 82.6146 56.4244C82.6146 41.4403 70.4676 29.2933 55.4835 29.2933ZM69.6538 47.2132C70.814 48.3734 70.8423 50.237 69.7387 51.4316L69.6538 51.5199L54.6698 66.5038C54.2226 66.9511 53.6733 67.2363 53.096 67.3582C52.1033 67.6899 50.9649 67.4806 50.1469 66.7153L50.1114 66.6815L41.2136 57.7843C40.0243 56.595 40.0243 54.6668 41.2136 53.4775C42.3738 52.3172 44.2374 52.2889 45.432 53.3926L45.5203 53.4775L52.3019 60.2583L65.347 47.2132C66.5363 46.0239 68.4645 46.0239 69.6538 47.2132Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -0,0 +1,7 @@
<svg width="112" height="113" viewBox="0 0 112 113" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M46.0385 0H65.3698C80.8738 0 87.0253 1.73377 93.059 4.98942C99.0926 8.24507 103.828 13.0226 107.055 19.1102L107.3 19.5788C110.328 25.444 111.957 31.6399 112 46.4496V65.9534C112 81.5959 110.282 87.8023 107.055 93.8898C103.828 99.9774 99.0926 104.755 93.059 108.011L92.5944 108.258C86.7812 111.313 80.6401 112.956 65.9615 113H46.6303C31.1262 113 24.9747 111.266 18.941 108.011C12.9074 104.755 8.17211 99.9774 4.94527 93.8898L4.70003 93.4212C1.67181 87.556 0.0431812 81.3601 0 66.5504V47.0466C0 31.4041 1.71842 25.1977 4.94527 19.1102C8.17211 13.0226 12.9074 8.24507 18.941 4.98942L19.4056 4.74199C25.2188 1.68673 31.3599 0.0435667 46.0385 0Z" fill="#EBEEF1"/>
<path d="M56.4626 69.107C68.2203 69.107 77.7519 59.4905 77.7519 47.628C77.7519 35.7654 68.2203 26.1489 56.4626 26.1489C44.7049 26.1489 35.1733 35.7654 35.1733 47.628C35.1733 59.4905 44.7049 69.107 56.4626 69.107Z" fill="#FFC600"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M55.9998 20.5452C71.5917 20.5452 84.2313 33.0884 84.2313 48.5612C84.2313 64.034 71.5917 76.5771 55.9998 76.5771C41.3477 76.5771 29.3027 65.5003 27.904 51.3247C28.1184 46.3639 28.5849 42.5898 29.2915 39.4617C33.0976 28.4555 43.6178 20.5452 55.9998 20.5452ZM55.9998 28.95C44.9784 28.95 36.099 37.7616 36.099 48.5612C36.099 59.3607 44.9784 68.1723 55.9998 68.1723C67.0213 68.1723 75.9007 59.3607 75.9007 48.5612C75.9007 37.7616 67.0213 28.95 55.9998 28.95Z" fill="#FF458B"/>
<path d="M84.2313 48.5625H27.7688V91.5204H84.2313V48.5625Z" fill="#0149FF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M56.4627 59.7683C59.5299 59.7683 62.0164 62.1107 62.0164 65.0002C62.0164 67.1873 60.5919 69.061 58.5692 69.8427L58.5693 75.6445H54.3562L54.3563 69.8427C52.3336 69.061 50.9091 67.1873 50.9091 65.0002C50.9091 62.1107 53.3955 59.7683 56.4627 59.7683Z" fill="#0218A7"/>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1,4 @@
<svg width="113" height="113" viewBox="0 0 113 113" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="113" height="113" rx="56.5" fill="#00AC26"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M83.5283 37.4363C85.7359 39.6417 85.7377 43.219 83.5323 45.4266L49.6285 79.3643C48.5691 80.4247 47.1318 81.0207 45.6328 81.0211C44.1338 81.0215 42.6962 80.4262 41.6362 79.3663L26.1382 63.8683C23.9318 61.6618 23.9318 58.0844 26.1382 55.878C28.3447 53.6715 31.9221 53.6715 34.1286 55.878L45.6294 67.3788L75.538 37.4403C77.7434 35.2327 81.3207 35.2309 83.5283 37.4363Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 604 B

View File

@ -0,0 +1,13 @@
<svg width="437" height="140" viewBox="0 0 437 140" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="156" y="27" width="113" height="113" rx="56.5" fill="#00AC26"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M239.528 64.4363C241.736 66.6417 241.738 70.219 239.532 72.4266L205.629 106.364C204.569 107.425 203.132 108.021 201.633 108.021C200.134 108.021 198.696 107.426 197.636 106.366L182.138 90.8683C179.932 88.6618 179.932 85.0844 182.138 82.878C184.345 80.6715 187.922 80.6715 190.129 82.878L201.629 94.3788L231.538 64.4403C233.743 62.2327 237.321 62.2309 239.528 64.4363Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M314.247 63.5977H337.161V86.5115H314.247V63.5977Z" fill="#00E567"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M150.577 0H166.944V16.367H150.577V0Z" fill="#FF458B"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M342.304 29.9287H352.124V39.7489H342.304V29.9287Z" fill="#FF458B"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M76.6919 30.3955H86.5121V40.2157H76.6919V30.3955Z" fill="#00E567"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M427.18 16.3672H437V26.1874H427.18V16.3672Z" fill="#0149FF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 83.4717H9.82022V93.2919H0V83.4717Z" fill="#0149FF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M392.576 54.2451H402.396V64.0653H392.576V54.2451Z" fill="#FFC600"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M103.346 78.3284H132.806V107.789H103.346V78.3284Z" fill="#FFC600"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M268.886 13.0938H291.8V36.0076H268.886V13.0938Z" fill="#0149FF"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB