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();
|
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_ENDPOINT = import.meta.env.VITE_APPWRITE_API_ENDPOINT;
|
||||||
const VITE_APPWRITE_API_PROJECT = import.meta.env.VITE_APPWRITE_API_PROJECT;
|
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
|
const pwresetUrl = process.env.DEV
|
||||||
? 'http://localhost:4000/pwreset'
|
? 'http://localhost:4000/pwreset'
|
||||||
: 'https://oys.undock.ca/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"
|
color="darkblue"
|
||||||
filled></q-input>
|
filled></q-input>
|
||||||
<q-card-actions>
|
<q-card-actions>
|
||||||
|
<q-space />
|
||||||
<q-btn
|
<q-btn
|
||||||
type="button"
|
type="button"
|
||||||
@click="doLogin"
|
@click="doLogin"
|
||||||
label="Login"
|
label="Login"
|
||||||
color="primary"></q-btn>
|
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-actions>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-form>
|
</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">
|
<div class="row justify-center q-ma-sm">
|
||||||
<GoogleOauthComponent />
|
<GoogleOauthComponent />
|
||||||
</div>
|
</div>
|
||||||
<div class="row justify-center q-ma-sm">
|
<div class="row justify-center q-ma-sm">
|
||||||
<DiscordOauthComponent />
|
<DiscordOauthComponent />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row justify-center">
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
color="secondary"
|
||||||
|
to="/pwreset"
|
||||||
|
label="Forgot Password?" />
|
||||||
|
</div>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
</q-page>
|
</q-page>
|
||||||
|
|||||||
@@ -34,58 +34,27 @@
|
|||||||
@click="resetPw"
|
@click="resetPw"
|
||||||
label="Send Reset Link"
|
label="Send Reset Link"
|
||||||
color="primary"></q-btn>
|
color="primary"></q-btn>
|
||||||
<!-- <q-btn
|
|
||||||
type="button"
|
|
||||||
@click="register"
|
|
||||||
color="secondary"
|
|
||||||
label="Register"
|
|
||||||
flat
|
|
||||||
></q-btn> -->
|
|
||||||
</q-card-actions>
|
</q-card-actions>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-form>
|
</q-form>
|
||||||
<q-form
|
<div v-else-if="validResetLink()">
|
||||||
@submit="submitNewPw"
|
<q-form
|
||||||
v-else-if="validResetLink()">
|
@submit="submitNewPw"
|
||||||
<q-card-section class="q-ma-sm">
|
@keydown.enter.prevent="resetPw">
|
||||||
<q-input
|
<NewPasswordComponent v-model="newPassword" />
|
||||||
v-model="password"
|
<q-card-actions>
|
||||||
label="New Password"
|
<q-btn
|
||||||
type="password"
|
type="submit"
|
||||||
color="darkblue"
|
label="Reset Password"
|
||||||
:rules="[validatePasswordStrength]"
|
color="primary"></q-btn>
|
||||||
lazy-rules
|
</q-card-actions>
|
||||||
filled></q-input>
|
</q-form>
|
||||||
<q-input
|
</div>
|
||||||
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>
|
|
||||||
<q-card
|
<q-card
|
||||||
v-else
|
v-else
|
||||||
class="text-center">
|
class="text-center">
|
||||||
<span class="text-h5">Invalid reset link.</span>
|
<span class="text-h5">Invalid reset link.</span>
|
||||||
</q-card>
|
</q-card>
|
||||||
<!-- <q-card-section><GoogleOauthComponent /></q-card-section> -->
|
|
||||||
</q-card>
|
</q-card>
|
||||||
</q-page>
|
</q-page>
|
||||||
</q-page-container>
|
</q-page-container>
|
||||||
@@ -112,38 +81,11 @@ import { ref } from 'vue';
|
|||||||
import { account, resetPassword } from 'boot/appwrite';
|
import { account, resetPassword } from 'boot/appwrite';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { Dialog } from 'quasar';
|
import { Dialog } from 'quasar';
|
||||||
// import GoogleOauthComponent from 'src/components/GoogleOauthComponent.vue';
|
import NewPasswordComponent from 'components/NewPasswordComponent.vue';
|
||||||
|
|
||||||
const email = ref('');
|
const email = ref('');
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const password = ref('');
|
const newPassword = 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
function validResetLink(): boolean {
|
function validResetLink(): boolean {
|
||||||
const query = router.currentRoute.value.query;
|
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() {
|
function submitNewPw() {
|
||||||
const query = router.currentRoute.value.query;
|
const query = router.currentRoute.value.query;
|
||||||
if (
|
if (newPassword.value) {
|
||||||
validatePasswordStrength(password.value) === true &&
|
|
||||||
validatePasswordsMatch(confirmPassword.value) === true
|
|
||||||
) {
|
|
||||||
account
|
account
|
||||||
.updateRecovery(
|
.updateRecovery(
|
||||||
query.userId as string,
|
query.userId as string,
|
||||||
query.secret as string,
|
query.secret as string,
|
||||||
password.value
|
newPassword.value
|
||||||
)
|
)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
Dialog.create({ message: 'Password Changed!' });
|
Dialog.create({ message: 'Password Changed!' }).onOk(() =>
|
||||||
router.replace('/login');
|
router.replace('/login')
|
||||||
|
);
|
||||||
})
|
})
|
||||||
.catch((e) =>
|
.catch((e) =>
|
||||||
Dialog.create({
|
Dialog.create({
|
||||||
message: 'Password change failed! Error: ' + e.message,
|
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,
|
publicRoute: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// {
|
{
|
||||||
// path: '/register',
|
path: '/signup',
|
||||||
// component: () => import('pages/RegisterPage.vue'),
|
component: () => import('pages/SignupPage.vue'),
|
||||||
// name: 'register'
|
name: 'signup',
|
||||||
// meta: {
|
meta: {
|
||||||
// accountRoute: true,
|
publicRoute: true,
|
||||||
// }
|
},
|
||||||
// },
|
},
|
||||||
// Always leave this as last one,
|
// Always leave this as last one,
|
||||||
// but you can also remove it
|
// but you can also remove it
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -48,8 +48,8 @@ export const useAuthStore = defineStore('auth', () => {
|
|||||||
async function googleLogin() {
|
async function googleLogin() {
|
||||||
account.createOAuth2Session(
|
account.createOAuth2Session(
|
||||||
OAuthProvider.Google,
|
OAuthProvider.Google,
|
||||||
'https://undock.ca',
|
'https://oys.undock.ca',
|
||||||
'https://undock.ca/#/login'
|
'https://oys.undock.ca/login'
|
||||||
);
|
);
|
||||||
currentUser.value = await account.get();
|
currentUser.value = await account.get();
|
||||||
}
|
}
|
||||||
@@ -57,8 +57,8 @@ export const useAuthStore = defineStore('auth', () => {
|
|||||||
async function discordLogin() {
|
async function discordLogin() {
|
||||||
account.createOAuth2Session(
|
account.createOAuth2Session(
|
||||||
OAuthProvider.Discord,
|
OAuthProvider.Discord,
|
||||||
'https://undock.ca',
|
'https://oys.undock.ca',
|
||||||
'https://undock.ca/#/login'
|
'https://oys.undock.ca/login'
|
||||||
);
|
);
|
||||||
currentUser.value = await account.get();
|
currentUser.value = await account.get();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user