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:
parent
e181f4b90e
commit
59ebb0ef27
@ -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>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user