Skip to content

Commit

Permalink
web/satellite/v2: add missing auth pages
Browse files Browse the repository at this point in the history
This change adds in missing authentication pages and behavior. It also
changes some auth routes to match what the console API sends to users'
emails, matching the routes in the v1 app

Change-Id: Ib2919e1b72dfc07eafebbc83365c8726fc38a50e
  • Loading branch information
wilfred-asomanii authored and Storj Robot committed Jan 17, 2024
1 parent 615cd8e commit 5c421f9
Show file tree
Hide file tree
Showing 6 changed files with 442 additions and 9 deletions.
16 changes: 13 additions & 3 deletions web/satellite/vuetify-poc/src/router/index.ts
Expand Up @@ -49,15 +49,25 @@ const routes: RouteRecordRaw[] = [
component: () => import(/* webpackChunkName: "SignupConfirmation" */ '@poc/views/SignupConfirmation.vue'),
},
{
path: '/password-reset',
name: 'Password Reset',
component: () => import(/* webpackChunkName: "PasswordReset" */ '@poc/views/PasswordReset.vue'),
path: '/forgot-password',
name: 'Forgot Password',
component: () => import(/* webpackChunkName: "ForgotPassword" */ '@poc/views/ForgotPassword.vue'),
},
{
path: '/password-reset-confirmation',
name: 'Password Reset Confirmation',
component: () => import(/* webpackChunkName: "PasswordResetConfirmation" */ '@poc/views/PasswordResetConfirmation.vue'),
},
{
path: '/password-recovery',
name: 'Password Recovery',
component: () => import(/* webpackChunkName: "PasswordRecovery" */ '@poc/views/PasswordRecovery.vue'),
},
{
path: '/activate',
name: 'Activate Account',
component: () => import(/* webpackChunkName: "ActivateAccountRequest" */ '@poc/views/ActivateAccountRequest.vue'),
},
{
path: '/password-reset-new',
name: 'Password Reset New',
Expand Down
206 changes: 206 additions & 0 deletions web/satellite/vuetify-poc/src/views/ActivateAccountRequest.vue
@@ -0,0 +1,206 @@
// Copyright (C) 2024 Storj Labs, Inc.
// See LICENSE for copying information.

<template>
<v-container class="fill-height">
<v-row justify="center">
<v-col cols="12" sm="9" md="7" lg="5" xl="4" xxl="3">
<v-card title="Verify Account" class="pa-2 pa-sm-7">
<v-card-item>
<v-alert
v-if="isActivationExpired"
variant="tonal"
color="error"
rounded="lg"
density="comfortable"
border
closable
>
<template #text>
The verification link you clicked on has expired. Request a new link.
</template>
</v-alert>
</v-card-item>
<v-card-text>
<p>If you haven’t verified your account yet, input your email to receive a new verification link. Make sure you’re signing on the right satellite.</p>
<v-form v-model="formValid" class="pt-4" @submit.prevent>
<v-select
v-model="satellite"
label="Satellite"
:items="satellites"
item-title="satellite"
:hint="satellite.hint"
persistent-hint
return-object
chips
class="mt-3 mb-2"
/>
<v-text-field
v-model="email"
label="Email address"
name="email"
type="email"
:rules="emailRules"
autofocus
clearable
required
class="my-2"
/>
<VueHcaptcha
v-if="captchaConfig.hcaptcha.enabled"
ref="captcha"
:sitekey="captchaConfig.hcaptcha.siteKey"
:re-captcha-compat="false"
size="invisible"
@verify="onCaptchaVerified"
@error="onCaptchaError"
/>
<v-btn
color="primary"
size="large"
block
:loading="isLoading"
:disabled="!formValid"
@click="onActivateClick"
>
Get Activation Link
</v-btn>
</v-form>
</v-card-text>
</v-card>
<p class="pt-6 text-center text-body-2">Go back to <router-link class="link" to="/login">login</router-link></p>
</v-col>
</v-row>
</v-container>
</template>

<script setup lang="ts">
import { computed, onMounted, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import {
VAlert,
VBtn,
VCard, VCardItem,
VCardText,
VCol,
VContainer,
VForm,
VRow,
VSelect,
VTextField,
} from 'vuetify/components';
import VueHcaptcha from '@hcaptcha/vue3-hcaptcha';
import { useConfigStore } from '@/store/modules/configStore';
import { EmailRule, RequiredRule, ValidationRule } from '@poc/types/common';
import { useLoading } from '@/composables/useLoading';
import { useNotify } from '@/utils/hooks';
import { AuthHttpApi } from '@/api/auth';
import { MultiCaptchaConfig } from '@/types/config.gen';
const auth: AuthHttpApi = new AuthHttpApi();
const configStore = useConfigStore();
const route = useRoute();
const router = useRouter();
const notify = useNotify();
const { isLoading, withLoading } = useLoading();
const email = ref<string>('');
const isActivationExpired = ref<boolean>(false);
const formValid = ref<boolean>(false);
const captchaResponseToken = ref<string>('');
const captcha = ref<VueHcaptcha>();
const satellitesHints = [
{ satellite: 'US1', hint: 'Recommended for North and South America' },
{ satellite: 'EU1', hint: 'Recommended for Europe and Africa' },
{ satellite: 'AP1', hint: 'Recommended for Asia and Australia' },
];
const emailRules: ValidationRule<string>[] = [
RequiredRule,
EmailRule,
];
/**
* This component's captcha configuration.
*/
const captchaConfig = computed((): MultiCaptchaConfig => {
return configStore.state.config.captcha.login;
});
/**
* Name of the current satellite.
*/
const satellite = computed({
get: () => {
const satName = configStore.state.config.satelliteName;
const item = satellitesHints.find(item => item.satellite === satName);
return item ?? { satellite: satName, hint: '' };
},
set: value => {
const sats = configStore.state.config.partneredSatellites ?? [];
const satellite = sats.find(sat => sat.name === value.satellite);
if (satellite) {
window.location.href = satellite.address + configStore.optionalV2Path + '/forgot-password';
}
},
});
/**
* Information about partnered satellites.
*/
const satellites = computed(() => {
const satellites = configStore.state.config.partneredSatellites ?? [];
return satellites.map(satellite => {
const item = satellitesHints.find(item => item.satellite === satellite.name);
return item ?? { satellite: satellite.name, hint: '' };
});
});
/**
* Handles captcha verification response.
*/
function onCaptchaVerified(response: string): void {
captchaResponseToken.value = response;
onActivateClick();
}
/**
* Handles captcha error.
*/
function onCaptchaError(): void {
captchaResponseToken.value = '';
notify.error('The captcha encountered an error. Please try again.', null);
}
/**
* onActivateClick validates input fields and requests resending of activation email.
*/
async function onActivateClick(): Promise<void> {
if (!formValid.value) {
return;
}
if (captcha.value && !captchaResponseToken.value) {
captcha.value.execute();
return;
}
await withLoading(async () => {
try {
await auth.resendEmail(email.value);
notify.success('Activation link sent');
router.push(`/signup-confirmation?email=${encodeURIComponent(email.value)}`);
} catch (error) {
notify.notifyError(error);
}
});
captcha.value?.reset();
captchaResponseToken.value = '';
}
onMounted((): void => {
isActivationExpired.value = route.query.expired === 'true';
});
</script>
Expand Up @@ -6,6 +6,21 @@
<v-row align="top" justify="center">
<v-col cols="12" sm="9" md="7" lg="5" xl="4" xxl="3">
<v-card title="Did you forgot your password?" class="pa-2 pa-sm-7">
<v-card-item>
<v-alert
v-if="isPasswordResetExpired"
variant="tonal"
color="error"
rounded="lg"
density="comfortable"
border
closable
>
<template #text>
The password reset link you clicked on has expired. Request a new link.
</template>
</v-alert>
</v-card-item>
<v-card-text>
<p>Select your account satellite, enter your email address, and we will send you a password reset link.</p>
<v-form v-model="formValid" class="pt-4" @submit.prevent>
Expand Down Expand Up @@ -59,11 +74,13 @@
</template>

<script setup lang="ts">
import { computed, ref } from 'vue';
import { useRouter } from 'vue-router';
import { computed, onMounted, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import {
VAlert,
VBtn,
VCard,
VCardItem,
VCardText,
VCol,
VContainer,
Expand All @@ -83,6 +100,7 @@ import { MultiCaptchaConfig } from '@/types/config.gen';
const configStore = useConfigStore();
const route = useRoute();
const router = useRouter();
const notify = useNotify();
const { isLoading, withLoading } = useLoading();
Expand All @@ -99,6 +117,7 @@ const emailRules: ValidationRule<string>[] = [
];
const formValid = ref<boolean>(false);
const isPasswordResetExpired = ref<boolean>(false);
const email = ref('');
const captcha = ref<VueHcaptcha>();
const captchaResponseToken = ref<string>('');
Expand All @@ -123,7 +142,7 @@ const satellite = computed({
const sats = configStore.state.config.partneredSatellites ?? [];
const satellite = sats.find(sat => sat.name === value.satellite);
if (satellite) {
window.location.href = satellite.address + configStore.optionalV2Path + '/password-reset';
window.location.href = satellite.address + configStore.optionalV2Path + '/forgot-password';
}
},
});
Expand Down Expand Up @@ -157,7 +176,6 @@ async function onPasswordReset(): Promise<void> {
notify.notifyError(error);
}
});
captcha.value?.reset();
captchaResponseToken.value = '';
}
Expand All @@ -177,4 +195,8 @@ function onCaptchaError(): void {
captchaResponseToken.value = '';
notify.error('The captcha encountered an error. Please try again.', null);
}
onMounted(() => {
isPasswordResetExpired.value = route.query.expired === 'true';
});
</script>
2 changes: 1 addition & 1 deletion web/satellite/vuetify-poc/src/views/Login.vue
Expand Up @@ -132,7 +132,7 @@
@expired="onCaptchaError"
@error="onCaptchaError"
/>
<p v-if="!isMFARequired" class="mt-7 text-center text-body-2">Forgot your password? <router-link class="link" to="/password-reset">Reset password</router-link></p>
<p v-if="!isMFARequired" class="mt-7 text-center text-body-2">Forgot your password? <router-link class="link" to="/forgot-password">Reset password</router-link></p>
<p class="mt-5 text-center text-body-2">Don't have an account? <router-link class="link" to="/signup">Sign Up</router-link></p>
</v-col>
</v-row>
Expand Down

0 comments on commit 5c421f9

Please sign in to comment.