Implement authentication module

This commit is contained in:
Maciej Pędzich 2023-05-25 20:59:33 +02:00
parent 9713b47b65
commit 84156909ab
8 changed files with 162 additions and 20 deletions

View File

@ -1,12 +1,26 @@
<script setup lang="ts">
import { useAuth } from './composables/useAuth';
import NavMenu from './components/ui/NavMenu.vue';
const { userLoadingFinished } = useAuth();
</script>
<template>
<v-app>
<NavMenu />
<v-main>
<RouterView />
<section
v-if="!userLoadingFinished"
class="w-100 h-100 pb-4 d-flex justify-center align-center"
>
<v-progress-circular
:size="100"
:width="7"
color="indigo"
indeterminate
></v-progress-circular>
</section>
<RouterView v-show="userLoadingFinished" />
</v-main>
</v-app>
</template>

View File

@ -1,8 +1,19 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import { useAuth } from '@/composables/useAuth';
import ThemeSwitch from './ThemeSwitch.vue';
const { isLoggedIn, logOut } = useAuth();
const router = useRouter();
const showDrawer = ref(false);
const logOutAndGoToLogIn = async () => {
await logOut();
await router.push('/log-in');
};
</script>
<template>
@ -16,25 +27,23 @@ const showDrawer = ref(false);
</v-app-bar>
<v-navigation-drawer v-model="showDrawer" temporary>
<v-list density="compact" nav>
<v-list-item
title="Home"
prepend-icon="mdi-home"
link
to="/"
></v-list-item>
<v-list-item
title="Vote"
prepend-icon="mdi-vote"
link
to="/vote"
></v-list-item>
<v-list-item
title="Log in"
prepend-icon="mdi-login"
link
to="/log-in"
></v-list-item>
<v-list-item title="Log out" prepend-icon="mdi-logout"></v-list-item>
<v-list-item title="Home" prepend-icon="mdi-home" link to="/" />
<template v-if="isLoggedIn">
<v-list-item title="Vote" prepend-icon="mdi-vote" link to="/vote" />
<v-list-item
title="Log out"
prepend-icon="mdi-logout"
@click="logOutAndGoToLogIn"
/>
</template>
<template v-else>
<v-list-item
title="Log in"
prepend-icon="mdi-login"
link
to="/log-in"
/>
</template>
</v-list>
</v-navigation-drawer>
</template>

View File

@ -0,0 +1,54 @@
import { ref, computed } from 'vue';
import { Models } from 'appwrite';
import { account } from '@/appwrite';
const user = ref<Models.User<Models.Preferences> | null>(null);
const userLoadingFinished = ref(false);
const isLoggedIn = computed(() => !!user.value);
export function useAuth() {
const logIn = (provider: 'github' | 'discord') => {
const redirectPath = localStorage.getItem('redirectPath') || '/';
const permissionScopes =
provider === 'github'
? ['read:user', 'user:email']
: ['identify', 'email'];
account.createOAuth2Session(
provider,
`${location.origin}${redirectPath}`,
`${location.origin}/log-in#oauth-error`,
permissionScopes
);
};
const loadUser = async () => {
try {
if (userLoadingFinished.value) return;
const currentUser = await account.get();
user.value = currentUser;
} catch (error) {
// TODO: Add to globaL Vue error logger and implement snackbar message bus
console.error(error);
user.value = null;
} finally {
userLoadingFinished.value = true;
}
};
const logOut = async () => {
await account.deleteSession('current');
user.value = null;
};
return {
user,
userLoadingFinished,
isLoggedIn,
logIn,
loadUser,
logOut
};
}

19
src/guards/auth.ts Normal file
View File

@ -0,0 +1,19 @@
import { RouteLocationNormalized, NavigationGuardNext } from 'vue-router';
import { useAuth } from '@/composables/useAuth';
export async function authGuard(
to: RouteLocationNormalized,
from: RouteLocationNormalized,
next: NavigationGuardNext
) {
const { isLoggedIn, loadUser } = useAuth();
await loadUser();
if (!to.meta.authRequired || isLoggedIn.value) {
return next();
} else {
localStorage.setItem('redirectPath', to.fullPath);
return next('/log-in');
}
}

View File

@ -1,4 +1,6 @@
import { createRouter, createWebHistory } from 'vue-router';
import { authGuard } from '@/guards/auth';
import Home from '@/views/Home.vue';
const routes = [
@ -6,6 +8,17 @@ const routes = [
path: '/',
name: 'Home',
component: Home
},
{
path: '/log-in',
name: 'LogIn',
component: () => import('../views/LogIn.vue')
},
{
path: '/vote',
name: 'Vote',
meta: { authRequired: true },
component: () => import('../views/Vote.vue')
}
];
@ -14,4 +27,6 @@ const router = createRouter({
routes
});
router.beforeEach(authGuard);
export default router;

7
src/types.d.ts vendored Normal file
View File

@ -0,0 +1,7 @@
import 'vue-router';
declare module 'vue-router' {
interface RouteMeta {
authRequired?: boolean;
}
}

21
src/views/LogIn.vue Normal file
View File

@ -0,0 +1,21 @@
<script lang="ts" setup>
import { useAuth } from '@/composables/useAuth';
const { logIn } = useAuth();
</script>
<template>
<section class="w-100 h-100 d-flex flex-column justify-center align-center">
<h1 class="text-h3 mb-3">Log in via:</h1>
<v-container>
<v-row align="center" justify="center">
<v-col cols="auto">
<v-btn size="large" @click="logIn('github')">GitHub</v-btn>
</v-col>
<v-col cols="auto">
<v-btn size="large" @click="logIn('discord')">Discord</v-btn>
</v-col>
</v-row>
</v-container>
</section>
</template>

3
src/views/Vote.vue Normal file
View File

@ -0,0 +1,3 @@
<template>
<h1>Vote</h1>
</template>