Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhanced Email Validation: Domain Existence Check #15345

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
225 changes: 35 additions & 190 deletions website/client/src/components/auth/authForm.vue
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
<template>
<div class="form">
<div v-if="emailExistsError" class="input-error">{{ $t('emailDoesNotExist') }}</div>
<div class="form-group row text-center">
<div class="col-12">
<div
class="btn btn-secondary social-button"
@click="socialAuth('google')"
>
<div
class="svg-icon social-icon"
v-html="icons.googleIcon"
></div>
<div class="svg-icon social-icon" v-html="icons.googleIcon"></div>
<span>{{ registering
? $t('signUpWithSocial', {social: 'Google'})
: $t('loginWithSocial', {social: 'Google'}) }}</span>
Expand All @@ -22,24 +20,15 @@
class="btn btn-secondary social-button"
@click="socialAuth('apple')"
>
<div
class="svg-icon social-icon apple-icon"
v-html="icons.appleIcon"
></div>
<div class="svg-icon social-icon apple-icon" v-html="icons.appleIcon"></div>
<span>{{ registering
? $t('signUpWithSocial', {social: 'Apple'})
: $t('loginWithSocial', {social: 'Apple'}) }}</span>
</div>
</div>
</div>
<div
v-if="registering"
class="form-group"
>
<label
v-once
for="usernameInput"
>{{ $t('username') }}</label>
<div v-if="registering" class="form-group">
<label v-once for="usernameInput">{{ $t('username') }}</label>
<input
id="usernameInput"
v-model="username"
Expand All @@ -48,22 +37,12 @@
:placeholder="$t('usernamePlaceholder')"
:class="{'input-valid': usernameValid, 'input-invalid': usernameInvalid}"
>
<div
v-for="issue in usernameIssues"
:key="issue"
class="input-error"
>
<div v-for="issue in usernameIssues" :key="issue" class="input-error">
{{ issue }}
</div>
</div>
<div
v-if="!registering"
class="form-group"
>
<label
v-once
for="usernameInput"
>{{ $t('emailOrUsername') }}</label>
<div v-if="!registering" class="form-group">
<label v-once for="usernameInput">{{ $t('emailOrUsername') }}</label>
<input
id="usernameInput"
v-model="username"
Expand All @@ -72,34 +51,21 @@
:placeholder="$t('emailOrUsername')"
>
</div>
<div
v-if="registering"
class="form-group"
>
<label
v-once
for="emailInput"
>{{ $t('email') }}</label>
<div v-if="registering" class="form-group">
<label v-once for="emailInput">{{ $t('email') }}</label>
<input
id="emailInput"
v-model="email"
class="form-control"
type="email"
:placeholder="$t('emailPlaceholder')"
:class="{'input-invalid': emailInvalid, 'input-valid': emailValid}"
:class="{'input-invalid': emailInvalid || emailExistsError, 'input-valid': emailValid}"
@blur="checkEmailExists"
>
</div>
<div class="form-group">
<label
v-once
for="passwordInput"
>{{ $t('password') }}</label>
<a
v-if="!registering"
v-once
class="float-right forgot-password"
@click="forgotPassword = true"
>{{ $t('forgotPassword') }}</a>
<label v-once for="passwordInput">{{ $t('password') }}</label>
<a v-if="!registering" v-once class="float-right forgot-password" @click="forgotPassword = true">{{ $t('forgotPassword') }}</a>

Check failure on line 68 in website/client/src/components/auth/authForm.vue

View workflow job for this annotation

GitHub Actions / lint (21.x)

This line has a length of 133. Maximum allowed is 100

Check failure on line 68 in website/client/src/components/auth/authForm.vue

View workflow job for this annotation

GitHub Actions / production-build (21.x)

This line has a length of 133. Maximum allowed is 100
<input
id="passwordInput"
v-model="password"
Expand All @@ -108,24 +74,15 @@
:placeholder="$t(registering ? 'passwordPlaceholder' : 'password')"
:class="{
'input-valid': registering ? passwordValid : false,
'input-invalid': registering ? passwordInvalid: false,
'input-invalid': registering ? passwordInvalid : false,
}"
>
<div
v-if="passwordInvalid && registering"
class="input-error"
>
<div v-if="passwordInvalid && registering" class="input-error">
{{ $t('minPasswordLength') }}
</div>
</div>
<div
v-if="registering"
class="form-group"
>
<label
v-once
for="confirmPasswordInput"
>{{ $t('confirmPassword') }}</label>
<div v-if="registering" class="form-group">
<label v-once for="confirmPasswordInput">{{ $t('confirmPassword') }}</label>
<input
id="confirmPasswordInput"
v-model="passwordConfirm"
Expand All @@ -134,99 +91,28 @@
:placeholder="$t('confirmPasswordPlaceholder')"
:class="{'input-invalid': passwordConfirmInvalid, 'input-valid': passwordConfirmValid}"
>
<div
v-if="passwordConfirmInvalid"
class="input-error"
>
<div v-if="passwordConfirmInvalid" class="input-error">
{{ $t('passwordConfirmationMatch') }}
</div>
<small
v-once
class="form-text"
v-html="$t('termsAndAgreement')"
></small>
<small v-once class="form-text" v-html="$t('termsAndAgreement')"></small>
</div>
<div class="text-center">
<div
v-if="registering"
v-once
class="btn btn-info"
@click="register()"
>
<div v-if="registering" v-once class="btn btn-info" @click="register">
{{ $t('joinHabitica') }}
</div>
<div
v-if="!registering"
v-once
class="btn btn-info"
@click="login()"
>
<div v-if="!registering" v-once class="btn btn-info" @click="login">
{{ $t('login') }}
</div>
</div>
</div>
</template>

<style lang="scss" scoped>
@import '~@/assets/scss/colors.scss';

.form {
margin: 0 auto;
width: 100%;
padding-top: 2em;
padding-bottom: 4em;
position: relative;
z-index: 1;

.form-group {
text-align: left;
font-weight: bold;
}

.social-button {
width: 100%;
height: 100%;
white-space: inherit;
text-align: center;

.text {
display: inline-block;
min-height: 0px;
}
}

.social-icon {
margin-right: 1em;
width: 18px;
height: 18px;
display: inline-block;
vertical-align: top;
margin-top: .1em;
}

.apple-icon {
margin-top: -1px;
}

small.form-text {
text-align: center;
}

.input-error {
margin-top: 0.25em;
font-weight: normal;
font-size: 90%;
width: 100%;
}
}
</style>

<script>
import hello from 'hellojs';

Check failure on line 111 in website/client/src/components/auth/authForm.vue

View workflow job for this annotation

GitHub Actions / lint (21.x)

'hello' is defined but never used

Check failure on line 111 in website/client/src/components/auth/authForm.vue

View workflow job for this annotation

GitHub Actions / production-build (21.x)

'hello' is defined but never used
import debounce from 'lodash/debounce';

Check failure on line 112 in website/client/src/components/auth/authForm.vue

View workflow job for this annotation

GitHub Actions / lint (21.x)

'debounce' is defined but never used

Check failure on line 112 in website/client/src/components/auth/authForm.vue

View workflow job for this annotation

GitHub Actions / production-build (21.x)

'debounce' is defined but never used
import isEmail from 'validator/es/lib/isEmail';
import { MINIMUM_PASSWORD_LENGTH } from '@/../../common/script/constants';
import { setUpAxios, buildAppleAuthUrl } from '@/libs/auth';

Check failure on line 115 in website/client/src/components/auth/authForm.vue

View workflow job for this annotation

GitHub Actions / lint (21.x)

'buildAppleAuthUrl' is defined but never used

Check failure on line 115 in website/client/src/components/auth/authForm.vue

View workflow job for this annotation

GitHub Actions / production-build (21.x)

'buildAppleAuthUrl' is defined but never used
import googleIcon from '@/assets/svg/google.svg';
import appleIcon from '@/assets/svg/apple_black.svg';

Expand All @@ -240,6 +126,7 @@
password: '',
passwordConfirm: '',
usernameIssues: [],
emailExistsError: false,
};

data.icons = Object.freeze({
Expand Down Expand Up @@ -283,69 +170,27 @@
return !this.passwordConfirmValid;
},
},
watch: {
username () {
this.validateUsername(this.username);
},
},
mounted () {
hello.init({
google: process.env.GOOGLE_CLIENT_ID, // eslint-disable-line
});
},
methods: {
// eslint-disable-next-line func-names
validateUsername: debounce(function (username) {
if (username.length < 1) {
return;
}
this.$store.dispatch('auth:verifyUsername', {
username: this.username,
}).then(res => {
if (res.issues !== undefined) {
this.usernameIssues = res.issues;
} else {
this.usernameIssues = [];
}
});
}, 500),
// @TODO: Abstract hello in to action or lib
async socialAuth (network) {
if (network === 'apple') {
window.location.href = buildAppleAuthUrl();
} else {
try {
await hello(network).logout();
} catch (e) {} // eslint-disable-line

try {
const redirectUrl = `${window.location.protocol}//${window.location.host}`;
const auth = await hello(network).login({
scope: 'email',
redirect_uri: redirectUrl, // eslint-disable-line camelcase
});
async checkEmailExists () {
if (!this.emailValid) return;

await this.$store.dispatch('auth:socialAuth', {
auth,
});

await this.finishAuth();
} catch (err) {
console.error(err); // eslint-disable-line no-console
// logout the user
await hello(network).logout();
this.socialAuth(network); // login again
}
try {
const res = await this.$store.dispatch('auth:verifyEmail', { email: this.email });
this.emailExistsError = !res.exists;
} catch (e) {
console.error(e); // Handle error

Check failure on line 181 in website/client/src/components/auth/authForm.vue

View workflow job for this annotation

GitHub Actions / production-build (21.x)

Unexpected console statement
}
},
async register () {
if (this.emailExistsError || !this.emailValid) return;

if (!this.email) {
window.alert(this.$t('missingEmail')); // eslint-disable-line no-alert
window.alert(this.$t('missingEmail'));
return;
}

if (this.password !== this.passwordConfirm) {
window.alert(this.$t('passwordConfirmationMatch')); // eslint-disable-line no-alert
window.alert(this.$t('passwordConfirmationMatch'));
return;
}

Expand All @@ -361,7 +206,7 @@
} catch (e) {
if (e.response.data.data && e.response.data.data.errors) {
const message = e.response.data.data.errors.map(error => `${error.message}\n`);
window.alert(message); // eslint-disable-line no-alert
window.alert(message);
}
}
},
Expand Down
Loading
Loading