feat: (auth) Add ability to signup with e-mail
This commit is contained in:
@@ -14,7 +14,6 @@ import type { Router } from 'vue-router';
|
||||
|
||||
const client = new Client();
|
||||
|
||||
console.log(import.meta.env);
|
||||
const VITE_APPWRITE_API_ENDPOINT = import.meta.env.VITE_APPWRITE_API_ENDPOINT;
|
||||
const VITE_APPWRITE_API_PROJECT = import.meta.env.VITE_APPWRITE_API_PROJECT;
|
||||
|
||||
@@ -28,7 +27,6 @@ if (VITE_APPWRITE_API_ENDPOINT && VITE_APPWRITE_API_PROJECT) {
|
||||
);
|
||||
}
|
||||
|
||||
console.log(process.env);
|
||||
const pwresetUrl = process.env.DEV
|
||||
? 'http://localhost:4000/pwreset'
|
||||
: 'https://oys.undock.ca/pwreset';
|
||||
|
||||
62
src/components/NewPasswordComponent.vue
Normal file
62
src/components/NewPasswordComponent.vue
Normal file
@@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<q-card-section class="q-ma-sm">
|
||||
<q-input
|
||||
v-model="password"
|
||||
label="New Password"
|
||||
type="password"
|
||||
color="darkblue"
|
||||
:rules="[validatePasswordStrength]"
|
||||
lazy-rules
|
||||
filled></q-input>
|
||||
<q-input
|
||||
v-model="confirmPassword"
|
||||
label="Confirm New Password"
|
||||
type="password"
|
||||
color="darkblue"
|
||||
:rules="[validatePasswordStrength]"
|
||||
lazy-rules
|
||||
filled></q-input>
|
||||
<div class="text-caption q-py-md">Enter a new password.</div>
|
||||
</q-card-section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
const password = ref('');
|
||||
const confirmPassword = ref('');
|
||||
|
||||
const newPassword = defineModel();
|
||||
|
||||
const validatePasswordStrength = (val: string) => {
|
||||
const hasUpperCase = /[A-Z]/.test(val);
|
||||
const hasLowerCase = /[a-z]/.test(val);
|
||||
const hasNumbers = /[0-9]/.test(val);
|
||||
const hasNonAlphas = /[\W_]/.test(val);
|
||||
const isValidLength = val.length >= 8;
|
||||
|
||||
return (
|
||||
(hasUpperCase &&
|
||||
hasLowerCase &&
|
||||
hasNumbers &&
|
||||
hasNonAlphas &&
|
||||
isValidLength) ||
|
||||
'Password must be at least 8 characters long and include uppercase, lowercase, number, and special character.'
|
||||
);
|
||||
};
|
||||
|
||||
const validatePasswordsMatch = (val: string) => {
|
||||
return val === password.value || 'Passwords do not match.';
|
||||
};
|
||||
|
||||
watch([password, confirmPassword], ([newpw, newpw1]) => {
|
||||
if (
|
||||
validatePasswordStrength(newpw) === true &&
|
||||
validatePasswordsMatch(newpw1) === true
|
||||
) {
|
||||
newPassword.value = newpw;
|
||||
} else {
|
||||
newPassword.value = '';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -29,35 +29,37 @@
|
||||
color="darkblue"
|
||||
filled></q-input>
|
||||
<q-card-actions>
|
||||
<q-space />
|
||||
<q-btn
|
||||
type="button"
|
||||
@click="doLogin"
|
||||
label="Login"
|
||||
color="primary"></q-btn>
|
||||
<q-space />
|
||||
<q-btn
|
||||
flat
|
||||
color="secondary"
|
||||
to="/pwreset">
|
||||
Reset password
|
||||
</q-btn>
|
||||
<!-- <q-btn
|
||||
type="button"
|
||||
@click="register"
|
||||
color="secondary"
|
||||
label="Register"
|
||||
flat
|
||||
></q-btn> -->
|
||||
</q-card-actions>
|
||||
</q-card-section>
|
||||
</q-form>
|
||||
<q-card-section>
|
||||
<q-card-section class="q-pa-none">
|
||||
<div class="row justify-center q-ma-sm">
|
||||
<q-btn
|
||||
type="button"
|
||||
:to="{ name: 'signup' }"
|
||||
color="primary"
|
||||
label="SignUp with E-mail"
|
||||
style="width: 300px" />
|
||||
</div>
|
||||
<div class="row justify-center q-ma-sm">
|
||||
<GoogleOauthComponent />
|
||||
</div>
|
||||
<div class="row justify-center q-ma-sm">
|
||||
<DiscordOauthComponent />
|
||||
</div>
|
||||
<div class="row justify-center">
|
||||
<q-btn
|
||||
flat
|
||||
color="secondary"
|
||||
to="/pwreset"
|
||||
label="Forgot Password?" />
|
||||
</div>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</q-page>
|
||||
|
||||
@@ -34,58 +34,27 @@
|
||||
@click="resetPw"
|
||||
label="Send Reset Link"
|
||||
color="primary"></q-btn>
|
||||
<!-- <q-btn
|
||||
type="button"
|
||||
@click="register"
|
||||
color="secondary"
|
||||
label="Register"
|
||||
flat
|
||||
></q-btn> -->
|
||||
</q-card-actions>
|
||||
</q-card-section>
|
||||
</q-form>
|
||||
<q-form
|
||||
@submit="submitNewPw"
|
||||
v-else-if="validResetLink()">
|
||||
<q-card-section class="q-ma-sm">
|
||||
<q-input
|
||||
v-model="password"
|
||||
label="New Password"
|
||||
type="password"
|
||||
color="darkblue"
|
||||
:rules="[validatePasswordStrength]"
|
||||
lazy-rules
|
||||
filled></q-input>
|
||||
<q-input
|
||||
v-model="confirmPassword"
|
||||
label="Confirm New Password"
|
||||
type="password"
|
||||
color="darkblue"
|
||||
:rules="[validatePasswordStrength]"
|
||||
lazy-rules
|
||||
filled></q-input>
|
||||
<div class="text-caption q-py-md">Enter a new password.</div>
|
||||
</q-card-section>
|
||||
<q-card-actions>
|
||||
<q-btn
|
||||
type="submit"
|
||||
label="Reset Password"
|
||||
color="primary"></q-btn>
|
||||
<!-- <q-btn
|
||||
type="button"
|
||||
@click="register"
|
||||
color="secondary"
|
||||
label="Register"
|
||||
flat
|
||||
></q-btn> -->
|
||||
</q-card-actions>
|
||||
</q-form>
|
||||
<div v-else-if="validResetLink()">
|
||||
<q-form
|
||||
@submit="submitNewPw"
|
||||
@keydown.enter.prevent="resetPw">
|
||||
<NewPasswordComponent v-model="newPassword" />
|
||||
<q-card-actions>
|
||||
<q-btn
|
||||
type="submit"
|
||||
label="Reset Password"
|
||||
color="primary"></q-btn>
|
||||
</q-card-actions>
|
||||
</q-form>
|
||||
</div>
|
||||
<q-card
|
||||
v-else
|
||||
class="text-center">
|
||||
<span class="text-h5">Invalid reset link.</span>
|
||||
</q-card>
|
||||
<!-- <q-card-section><GoogleOauthComponent /></q-card-section> -->
|
||||
</q-card>
|
||||
</q-page>
|
||||
</q-page-container>
|
||||
@@ -112,38 +81,11 @@ import { ref } from 'vue';
|
||||
import { account, resetPassword } from 'boot/appwrite';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { Dialog } from 'quasar';
|
||||
// import GoogleOauthComponent from 'src/components/GoogleOauthComponent.vue';
|
||||
import NewPasswordComponent from 'components/NewPasswordComponent.vue';
|
||||
|
||||
const email = ref('');
|
||||
const router = useRouter();
|
||||
const password = ref('');
|
||||
const confirmPassword = ref('');
|
||||
|
||||
const validatePasswordStrength = (val: string) => {
|
||||
const hasUpperCase = /[A-Z]/.test(val);
|
||||
const hasLowerCase = /[a-z]/.test(val);
|
||||
const hasNumbers = /[0-9]/.test(val);
|
||||
const hasNonAlphas = /[\W_]/.test(val);
|
||||
const isValidLength = val.length >= 8;
|
||||
|
||||
return (
|
||||
(hasUpperCase &&
|
||||
hasLowerCase &&
|
||||
hasNumbers &&
|
||||
hasNonAlphas &&
|
||||
isValidLength) ||
|
||||
'Password must be at least 8 characters long and include uppercase, lowercase, number, and special character.'
|
||||
);
|
||||
};
|
||||
|
||||
const validatePasswordsMatch = (val: string) => {
|
||||
return val === password.value || 'Passwords do not match.';
|
||||
};
|
||||
|
||||
function isPasswordResetLink() {
|
||||
const query = router.currentRoute.value.query;
|
||||
return query && query.secret && query.userId && query.expire;
|
||||
}
|
||||
const newPassword = ref();
|
||||
|
||||
function validResetLink(): boolean {
|
||||
const query = router.currentRoute.value.query;
|
||||
@@ -153,27 +95,34 @@ function validResetLink(): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
function isPasswordResetLink() {
|
||||
const query = router.currentRoute.value.query;
|
||||
return query && query.secret && query.userId && query.expire;
|
||||
}
|
||||
|
||||
function submitNewPw() {
|
||||
const query = router.currentRoute.value.query;
|
||||
if (
|
||||
validatePasswordStrength(password.value) === true &&
|
||||
validatePasswordsMatch(confirmPassword.value) === true
|
||||
) {
|
||||
if (newPassword.value) {
|
||||
account
|
||||
.updateRecovery(
|
||||
query.userId as string,
|
||||
query.secret as string,
|
||||
password.value
|
||||
newPassword.value
|
||||
)
|
||||
.then(() => {
|
||||
Dialog.create({ message: 'Password Changed!' });
|
||||
router.replace('/login');
|
||||
Dialog.create({ message: 'Password Changed!' }).onOk(() =>
|
||||
router.replace('/login')
|
||||
);
|
||||
})
|
||||
.catch((e) =>
|
||||
Dialog.create({
|
||||
message: 'Password change failed! Error: ' + e.message,
|
||||
})
|
||||
);
|
||||
} else {
|
||||
Dialog.create({
|
||||
message: 'Invalid password. Try again',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
86
src/pages/SignupPage.vue
Normal file
86
src/pages/SignupPage.vue
Normal file
@@ -0,0 +1,86 @@
|
||||
<template>
|
||||
<q-layout>
|
||||
<q-page-container>
|
||||
<q-page class="flex bg-image flex-center">
|
||||
<q-card
|
||||
v-bind:style="$q.screen.lt.sm ? { width: '80%' } : { width: '30%' }">
|
||||
<q-card-section>
|
||||
<q-img
|
||||
fit="scale-down"
|
||||
src="~assets/oysqn_logo.png" />
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<div class="text-center q-pt-sm">
|
||||
<div class="col text-h6">Sign Up</div>
|
||||
</div>
|
||||
</q-card-section>
|
||||
<q-form>
|
||||
<q-card-section class="q-gutter-md">
|
||||
<q-input
|
||||
v-model="email"
|
||||
label="E-Mail"
|
||||
type="email"
|
||||
color="darkblue"
|
||||
:rules="['email']"
|
||||
filled></q-input>
|
||||
<NewPasswordComponent v-model="password" />
|
||||
<q-card-actions>
|
||||
<q-space />
|
||||
<q-btn
|
||||
type="button"
|
||||
@click="doRegister"
|
||||
label="Sign Up"
|
||||
color="primary"></q-btn>
|
||||
</q-card-actions>
|
||||
</q-card-section>
|
||||
</q-form>
|
||||
</q-card>
|
||||
</q-page>
|
||||
</q-page-container>
|
||||
</q-layout>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.bg-image {
|
||||
background-image: url('/src/assets/oys_lighthouse.jpg');
|
||||
background-repeat: no-repeat;
|
||||
background-position-x: center;
|
||||
background-size: cover;
|
||||
/* background-image: linear-gradient(
|
||||
135deg,
|
||||
#ed232a 0%,
|
||||
#ffffff 75%,
|
||||
#14539a 100%
|
||||
); */
|
||||
}
|
||||
</style>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { useAuthStore } from 'src/stores/auth';
|
||||
import NewPasswordComponent from 'src/components/NewPasswordComponent.vue';
|
||||
import { Dialog } from 'quasar';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const email = ref('');
|
||||
const password = ref('');
|
||||
const router = useRouter();
|
||||
|
||||
console.log('version:' + process.env.VUE_APP_VERSION);
|
||||
|
||||
const doRegister = async () => {
|
||||
if (email.value && password.value) {
|
||||
try {
|
||||
await useAuthStore().register(email.value, password.value);
|
||||
Dialog.create({
|
||||
message: 'Account Created! Now log-in with your e-mail / password.',
|
||||
}).onOk(() => router.replace('/login'));
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
Dialog.create({
|
||||
message: 'An error occurred. Please ask for support in Discord',
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -168,14 +168,14 @@ const routes: RouteRecordRaw[] = [
|
||||
publicRoute: true,
|
||||
},
|
||||
},
|
||||
// {
|
||||
// path: '/register',
|
||||
// component: () => import('pages/RegisterPage.vue'),
|
||||
// name: 'register'
|
||||
// meta: {
|
||||
// accountRoute: true,
|
||||
// }
|
||||
// },
|
||||
{
|
||||
path: '/signup',
|
||||
component: () => import('pages/SignupPage.vue'),
|
||||
name: 'signup',
|
||||
meta: {
|
||||
publicRoute: true,
|
||||
},
|
||||
},
|
||||
// Always leave this as last one,
|
||||
// but you can also remove it
|
||||
{
|
||||
|
||||
@@ -48,8 +48,8 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
async function googleLogin() {
|
||||
account.createOAuth2Session(
|
||||
OAuthProvider.Google,
|
||||
'https://undock.ca',
|
||||
'https://undock.ca/#/login'
|
||||
'https://oys.undock.ca',
|
||||
'https://oys.undock.ca/login'
|
||||
);
|
||||
currentUser.value = await account.get();
|
||||
}
|
||||
@@ -57,8 +57,8 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
async function discordLogin() {
|
||||
account.createOAuth2Session(
|
||||
OAuthProvider.Discord,
|
||||
'https://undock.ca',
|
||||
'https://undock.ca/#/login'
|
||||
'https://oys.undock.ca',
|
||||
'https://oys.undock.ca/login'
|
||||
);
|
||||
currentUser.value = await account.get();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user