Compare commits
2 Commits
fc035106d0
...
643d74e29d
| Author | SHA1 | Date | |
|---|---|---|---|
|
643d74e29d
|
|||
|
1526a10630
|
@@ -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>
|
||||
@@ -14,7 +14,7 @@
|
||||
<div class="col text-h6">Log in</div>
|
||||
</div>
|
||||
</q-card-section>
|
||||
<q-form>
|
||||
<q-form @keydown.enter.prevent="doTokenLogin">
|
||||
<q-card-section class="q-gutter-md">
|
||||
<q-input
|
||||
v-model="email"
|
||||
@@ -23,41 +23,36 @@
|
||||
color="darkblue"
|
||||
filled></q-input>
|
||||
<q-input
|
||||
v-model="password"
|
||||
label="Password"
|
||||
type="password"
|
||||
v-if="userId"
|
||||
v-model="token"
|
||||
label="6-digit code"
|
||||
type="number"
|
||||
color="darkblue"
|
||||
filled></q-input>
|
||||
<q-card-actions>
|
||||
<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"
|
||||
@click="doTokenLogin"
|
||||
color="primary"
|
||||
label="Login 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>
|
||||
@@ -82,16 +77,75 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { login } from 'boot/appwrite';
|
||||
import GoogleOauthComponent from 'src/components/GoogleOauthComponent.vue';
|
||||
import DiscordOauthComponent from 'src/components/DiscordOauthComponent.vue';
|
||||
import { Dialog, Notify } from 'quasar';
|
||||
import { useAuthStore } from 'src/stores/auth';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { AppwriteException } from 'appwrite';
|
||||
|
||||
const email = ref('');
|
||||
const password = ref('');
|
||||
const token = ref('');
|
||||
const userId = ref();
|
||||
|
||||
console.log('version:' + process.env.VUE_APP_VERSION);
|
||||
|
||||
const doLogin = async () => {
|
||||
login(email.value, password.value);
|
||||
const doTokenLogin = async () => {
|
||||
const authStore = useAuthStore();
|
||||
if (!userId.value) {
|
||||
try {
|
||||
const sessionToken = await authStore.createTokenSession(email.value);
|
||||
userId.value = sessionToken.userId;
|
||||
Dialog.create({ message: 'Check your e-mail for your login code.' });
|
||||
} catch (e) {
|
||||
Dialog.create({
|
||||
message: 'An error occurred. Please ask for help in Discord',
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const notification = Notify.create({
|
||||
type: 'primary',
|
||||
position: 'top',
|
||||
spinner: true,
|
||||
message: 'Logging you in...',
|
||||
timeout: 8000,
|
||||
group: false,
|
||||
});
|
||||
try {
|
||||
await authStore.tokenLogin(userId.value, token.value);
|
||||
notification({
|
||||
type: 'positive',
|
||||
message: 'Logged in!',
|
||||
timeout: 2000,
|
||||
spinner: false,
|
||||
icon: 'check_circle',
|
||||
});
|
||||
useRouter().replace({ name: 'index' });
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof AppwriteException) {
|
||||
if (error.type === 'user_session_already_exists') {
|
||||
useRouter().replace({ name: 'index' });
|
||||
notification({
|
||||
type: 'positive',
|
||||
message: 'Already Logged in!',
|
||||
timeout: 2000,
|
||||
spinner: false,
|
||||
icon: 'check_circle',
|
||||
});
|
||||
return;
|
||||
}
|
||||
Dialog.create({
|
||||
title: 'Login Error!',
|
||||
message: error.message,
|
||||
persistent: true,
|
||||
});
|
||||
}
|
||||
notification({
|
||||
type: 'negative',
|
||||
message: 'Login failed.',
|
||||
timeout: 2000,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -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>
|
||||
@@ -49,6 +49,10 @@ export default route(function (/* { store, ssrContext } */) {
|
||||
return next('/login');
|
||||
}
|
||||
|
||||
if (to.name === 'login' && currentUser) {
|
||||
return next('/');
|
||||
}
|
||||
|
||||
if (requiredRoles) {
|
||||
if (!currentUser) {
|
||||
return next('/login');
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -45,22 +45,31 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
await init();
|
||||
}
|
||||
|
||||
async function createTokenSession(email: string) {
|
||||
return await account.createEmailToken(ID.unique(), email);
|
||||
}
|
||||
|
||||
async function googleLogin() {
|
||||
account.createOAuth2Session(
|
||||
await 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();
|
||||
await init();
|
||||
}
|
||||
|
||||
async function discordLogin() {
|
||||
account.createOAuth2Session(
|
||||
await 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();
|
||||
await init();
|
||||
}
|
||||
|
||||
async function tokenLogin(userId: string, token: string) {
|
||||
await account.createSession(userId, token);
|
||||
await init();
|
||||
}
|
||||
|
||||
function getUserNameById(id: string | undefined | null): string {
|
||||
@@ -102,6 +111,8 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
login,
|
||||
googleLogin,
|
||||
discordLogin,
|
||||
createTokenSession,
|
||||
tokenLogin,
|
||||
logout,
|
||||
init,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user