web/satellite: move login to composition api

This change updates LoginArea.vue to use the composition API.

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

Change-Id: I34d748da1224805ed179bd2cf37e61d496d0811c
This commit is contained in:
Wilfred Asomani 2023-03-13 14:45:19 +00:00 committed by Storj Robot
parent e181f4b90e
commit 59ebb0ef27

View File

@ -158,15 +158,14 @@
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
<script setup lang="ts">
import VueRecaptcha from 'vue-recaptcha';
import VueHcaptcha from '@hcaptcha/vue-hcaptcha';
import { computed, onMounted, ref } from 'vue';
import { AuthHttpApi } from '@/api/auth';
import { ErrorMFARequired } from '@/api/errors/ErrorMFARequired';
import { RouteConfig } from '@/router';
import { PartneredSatellite } from '@/types/common';
import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
import { AppState } from '@/utils/constants/appStateEnum';
import { Validator } from '@/utils/validation';
@ -177,6 +176,8 @@ import { AnalyticsHttpApi } from '@/api/analytics';
import { USER_ACTIONS } from '@/store/modules/users';
import { TokenInfo } from '@/types/users';
import { LocalData } from '@/utils/localData';
import { useNotify, useRoute, useRouter, useStore } from '@/utils/hooks';
import { PartneredSatellite } from '@/types/common';
import VButton from '@/components/common/VButton.vue';
import VInput from '@/components/common/VInput.vue';
@ -193,301 +194,281 @@ interface ClearInput {
clearInput(): void;
}
// @vue/component
@Component({
components: {
VInput,
VButton,
BottomArrowIcon,
SelectedCheckIcon,
LogoIcon,
WarningIcon,
GreyWarningIcon,
ErrorIcon,
ConfirmMFAInput,
VueRecaptcha,
VueHcaptcha,
},
})
export default class Login extends Vue {
private email = '';
private password = '';
private passcode = '';
private recoveryCode = '';
private isLoading = false;
private emailError = '';
private passwordError = '';
private captchaError = false;
private captchaResponseToken = '';
const email = ref('');
const password = ref('');
const passcode = ref('');
const recoveryCode = ref('');
const isLoading = ref(false);
const emailError = ref('');
const passwordError = ref('');
const captchaError = ref(false);
const captchaResponseToken = ref('');
const isActivatedBannerShown = ref(false);
const isActivatedError = ref(false);
const isMFARequired = ref(false);
const isMFAError = ref(false);
const isRecoveryCodeState = ref(false);
const isBadLoginMessageShown = ref(false);
const isDropdownShown = ref(false);
private readonly recaptchaEnabled: boolean = MetaUtils.getMetaContent('login-recaptcha-enabled') === 'true';
private readonly recaptchaSiteKey: string = MetaUtils.getMetaContent('login-recaptcha-site-key');
private readonly hcaptchaEnabled: boolean = MetaUtils.getMetaContent('login-hcaptcha-enabled') === 'true';
private readonly hcaptchaSiteKey: string = MetaUtils.getMetaContent('login-hcaptcha-site-key');
const returnURL = ref(RouteConfig.ProjectDashboard.path);
private readonly auth: AuthHttpApi = new AuthHttpApi();
const recaptcha = ref<VueRecaptcha | null>(null);
const hcaptcha = ref<VueHcaptcha | null>(null);
const mfaInput = ref<ConfirmMFAInput & ClearInput | null>(null);
public readonly forgotPasswordPath: string = RouteConfig.ForgotPassword.path;
public returnURL: string = RouteConfig.ProjectDashboard.path;
public isActivatedBannerShown = false;
public isActivatedError = false;
public isMFARequired = false;
public isMFAError = false;
public isRecoveryCodeState = false;
public isBadLoginMessageShown = false;
const recaptchaEnabled = MetaUtils.getMetaContent('login-recaptcha-enabled') === 'true';
const recaptchaSiteKey = MetaUtils.getMetaContent('login-recaptcha-site-key');
const hcaptchaEnabled = MetaUtils.getMetaContent('login-hcaptcha-enabled') === 'true';
const hcaptchaSiteKey = MetaUtils.getMetaContent('login-hcaptcha-site-key');
const forgotPasswordPath: string = RouteConfig.ForgotPassword.path;
const registerPath: string = RouteConfig.Register.path;
public readonly analytics: AnalyticsHttpApi = new AnalyticsHttpApi();
const auth = new AuthHttpApi();
const notify = useNotify();
const analytics = new AnalyticsHttpApi();
const router = useRouter();
const store = useStore();
const route = useRoute();
// Tardigrade logic
public isDropdownShown = false;
/**
* Name of the current satellite.
*/
const satelliteName = computed((): string => {
return store.state.appStateModule.satelliteName;
});
public readonly registerPath: string = RouteConfig.Register.path;
/**
* Information about partnered satellites, including name and signup link.
*/
const partneredSatellites = computed((): PartneredSatellite[] => {
return store.state.appStateModule.partneredSatellites;
});
public $refs!: {
recaptcha: VueRecaptcha;
hcaptcha: VueHcaptcha;
mfaInput: ConfirmMFAInput & ClearInput;
};
/**
* Lifecycle hook after initial render.
* Makes activated banner visible on successful account activation.
*/
onMounted(() => {
isActivatedBannerShown.value = !!route.query.activated;
isActivatedError.value = route.query.activated === 'false';
/**
* Clears confirm MFA input.
*/
public clearConfirmMFAInput(): void {
this.$refs.mfaInput.clearInput();
if (store.state.appStateModule.isAllProjectsDashboard) {
returnURL.value = RouteConfig.AllProjectsDashboard.path;
}
/**
* Lifecycle hook after initial render.
* Makes activated banner visible on successful account activation.
*/
public mounted(): void {
this.isActivatedBannerShown = !!this.$route.query.activated;
this.isActivatedError = this.$route.query.activated === 'false';
returnURL.value = route.query.return_url as string || returnURL.value;
});
if (this.$store.state.appStateModule.isAllProjectsDashboard) {
this.returnURL = RouteConfig.AllProjectsDashboard.path;
/**
* Clears confirm MFA input.
*/
function clearConfirmMFAInput(): void {
mfaInput.value?.clearInput();
}
/**
* Redirects to storj.io homepage.
*/
function onLogoClick(): void {
const homepageURL = MetaUtils.getMetaContent('homepage-url');
if (homepageURL) window.location.href = homepageURL;
}
/**
* Sets page to recovery code state.
*/
function setRecoveryCodeState(): void {
isMFAError.value = false;
passcode.value = '';
clearConfirmMFAInput();
isRecoveryCodeState.value = true;
}
/**
* Cancels MFA passcode input state.
*/
function onMFACancelClick(): void {
isMFARequired.value = false;
isRecoveryCodeState.value = false;
isMFAError.value = false;
passcode.value = '';
recoveryCode.value = '';
}
/**
* Sets confirmation passcode value from input.
*/
function onConfirmInput(value: string): void {
isMFAError.value = false;
isRecoveryCodeState.value ? recoveryCode.value = value.trim() : passcode.value = value.trim();
}
/**
* Sets email string on change.
*/
function setEmail(value: string): void {
email.value = value.trim();
emailError.value = '';
}
/**
* Sets password string on change.
*/
function setPassword(value: string): void {
password.value = value;
passwordError.value = '';
}
/**
* Redirects to chosen satellite.
*/
function clickSatellite(address): void {
window.location.href = address + '/login';
}
/**
* Toggles satellite selection dropdown visibility (Tardigrade).
*/
function toggleDropdown(): void {
isDropdownShown.value = !isDropdownShown.value;
}
/**
* Closes satellite selection dropdown (Tardigrade).
*/
function closeDropdown(): void {
isDropdownShown.value = false;
}
/**
* Handles captcha verification response.
*/
function onCaptchaVerified(response: string): void {
captchaResponseToken.value = response;
captchaError.value = false;
login();
}
/**
* Handles captcha error and expiry.
*/
function onCaptchaError(): void {
captchaResponseToken.value = '';
captchaError.value = true;
}
/**
* Holds on login button click logic.
*/
async function onLoginClick(): Promise<void> {
if (isLoading.value && !isDropdownShown.value) {
return;
}
let activeElement = document.activeElement;
if (activeElement && activeElement.id === 'loginDropdown') return;
if (isDropdownShown.value) {
isDropdownShown.value = false;
return;
}
isLoading.value = true;
if (recaptcha.value && !captchaResponseToken.value) {
recaptcha.value?.execute();
return;
}
if (hcaptcha.value && !captchaResponseToken.value) {
hcaptcha.value?.execute();
return;
}
await login();
}
/**
* Performs login action.
* Then changes location to project dashboard page.
*/
async function login(): Promise<void> {
if (!validateFields()) {
isLoading.value = false;
return;
}
try {
const tokenInfo: TokenInfo = await auth.token(email.value, password.value, captchaResponseToken.value, passcode.value, recoveryCode.value);
LocalData.setSessionExpirationDate(tokenInfo.expiresAt);
} catch (error) {
if (recaptcha.value) {
recaptcha.value?.reset();
captchaResponseToken.value = '';
}
if (hcaptcha.value) {
hcaptcha.value?.reset();
captchaResponseToken.value = '';
}
this.returnURL = this.$route.query.return_url as string || this.returnURL;
}
if (error instanceof ErrorMFARequired) {
if (isMFARequired.value) isMFAError.value = true;
/**
* Redirects to storj.io homepage.
*/
public onLogoClick(): void {
const homepageURL = MetaUtils.getMetaContent('homepage-url');
if (homepageURL) window.location.href = homepageURL;
}
/**
* Sets page to recovery code state.
*/
public setRecoveryCodeState(): void {
this.isMFAError = false;
this.passcode = '';
this.clearConfirmMFAInput();
this.isRecoveryCodeState = true;
}
/**
* Cancels MFA passcode input state.
*/
public onMFACancelClick(): void {
this.isMFARequired = false;
this.isRecoveryCodeState = false;
this.isMFAError = false;
this.passcode = '';
this.recoveryCode = '';
}
/**
* Sets confirmation passcode value from input.
*/
public onConfirmInput(value: string): void {
this.isMFAError = false;
this.isRecoveryCodeState ? this.recoveryCode = value.trim() : this.passcode = value.trim();
}
/**
* Sets email string on change.
*/
public setEmail(value: string): void {
this.email = value.trim();
this.emailError = '';
}
/**
* Sets password string on change.
*/
public setPassword(value: string): void {
this.password = value;
this.passwordError = '';
}
/**
* Name of the current satellite.
*/
public get satelliteName(): string {
return this.$store.state.appStateModule.satelliteName;
}
/**
* Information about partnered satellites, including name and signup link.
*/
public get partneredSatellites(): PartneredSatellite[] {
return this.$store.state.appStateModule.partneredSatellites;
}
/**
* Redirects to chosen satellite.
*/
public clickSatellite(address): void {
window.location.href = address + '/login';
}
/**
* Toggles satellite selection dropdown visibility (Tardigrade).
*/
public toggleDropdown(): void {
this.isDropdownShown = !this.isDropdownShown;
}
/**
* Closes satellite selection dropdown (Tardigrade).
*/
public closeDropdown(): void {
this.isDropdownShown = false;
}
/**
* Handles captcha verification response.
*/
public onCaptchaVerified(response: string): void {
this.captchaResponseToken = response;
this.captchaError = false;
this.login();
}
/**
* Handles captcha error and expiry.
*/
public onCaptchaError(): void {
this.captchaResponseToken = '';
this.captchaError = true;
}
/**
* Holds on login button click logic.
*/
public async onLoginClick(): Promise<void> {
if (this.isLoading && !this.isDropdownShown) {
isMFARequired.value = true;
isLoading.value = false;
return;
}
let activeElement = document.activeElement;
if (activeElement && activeElement.id === 'loginDropdown') return;
if (this.isDropdownShown) {
this.isDropdownShown = false;
return;
}
this.isLoading = true;
if (this.$refs.recaptcha && !this.captchaResponseToken) {
this.$refs.recaptcha.execute();
return;
} if (this.$refs.hcaptcha && !this.captchaResponseToken) {
this.$refs.hcaptcha.execute();
return;
}
await this.login();
}
/**
* Performs login action.
* Then changes location to project dashboard page.
*/
public async login(): Promise<void> {
if (!this.validateFields()) {
this.isLoading = false;
return;
}
try {
const tokenInfo: TokenInfo = await this.auth.token(this.email, this.password, this.captchaResponseToken, this.passcode, this.recoveryCode);
LocalData.setSessionExpirationDate(tokenInfo.expiresAt);
} catch (error) {
if (this.$refs.recaptcha) {
this.$refs.recaptcha.reset();
this.captchaResponseToken = '';
}
if (this.$refs.hcaptcha) {
this.$refs.hcaptcha.reset();
this.captchaResponseToken = '';
if (isMFARequired.value) {
if (error instanceof ErrorBadRequest || error instanceof ErrorUnauthorized) {
await notify.error(error.message, null);
}
if (error instanceof ErrorMFARequired) {
if (this.isMFARequired) this.isMFAError = true;
this.isMFARequired = true;
this.isLoading = false;
return;
}
if (this.isMFARequired) {
if (error instanceof ErrorBadRequest || error instanceof ErrorUnauthorized) {
await this.$notify.error(error.message, null);
}
this.isMFAError = true;
this.isLoading = false;
return;
}
if (error instanceof ErrorUnauthorized) {
this.isBadLoginMessageShown = true;
this.isLoading = false;
return;
}
await this.$notify.error(error.message, null);
this.isLoading = false;
isMFAError.value = true;
isLoading.value = false;
return;
}
await this.$store.dispatch(USER_ACTIONS.LOGIN);
await this.$store.dispatch(APP_STATE_ACTIONS.CHANGE_STATE, AppState.LOADING);
this.isLoading = false;
LocalData.setServerSideEncryptionBannerHidden(false);
this.analytics.pageVisit(this.returnURL);
await this.$router.push(this.returnURL);
}
/**
* Validates email and password input strings.
*/
private validateFields(): boolean {
let isNoErrors = true;
if (!Validator.email(this.email)) {
this.emailError = 'Invalid Email';
isNoErrors = false;
if (error instanceof ErrorUnauthorized) {
isBadLoginMessageShown.value = true;
isLoading.value = false;
return;
}
if (this.password.length < Validator.PASS_MIN_LENGTH) {
this.passwordError = 'Invalid Password';
isNoErrors = false;
}
return isNoErrors;
await notify.error(error.message, null);
isLoading.value = false;
return;
}
await store.dispatch(USER_ACTIONS.LOGIN);
await store.dispatch(APP_STATE_ACTIONS.CHANGE_STATE, AppState.LOADING);
isLoading.value = false;
LocalData.setServerSideEncryptionBannerHidden(false);
analytics.pageVisit(returnURL.value);
await router.push(returnURL.value);
}
/**
* Validates email and password input strings.
*/
function validateFields(): boolean {
let isNoErrors = true;
if (!Validator.email(email.value)) {
emailError.value = 'Invalid Email';
isNoErrors = false;
}
if (password.value.length < Validator.PASS_MIN_LENGTH) {
passwordError.value = 'Invalid Password';
isNoErrors = false;
}
return isNoErrors;
}
</script>