diff --git a/src/App.vue b/src/App.vue index 528b09f..862baa4 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,12 +1,26 @@ diff --git a/src/components/ui/NavMenu.vue b/src/components/ui/NavMenu.vue index f340fe4..fb486da 100644 --- a/src/components/ui/NavMenu.vue +++ b/src/components/ui/NavMenu.vue @@ -1,8 +1,19 @@ diff --git a/src/composables/useAuth.ts b/src/composables/useAuth.ts new file mode 100644 index 0000000..63333c7 --- /dev/null +++ b/src/composables/useAuth.ts @@ -0,0 +1,54 @@ +import { ref, computed } from 'vue'; +import { Models } from 'appwrite'; + +import { account } from '@/appwrite'; + +const user = ref | 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 + }; +} diff --git a/src/guards/auth.ts b/src/guards/auth.ts new file mode 100644 index 0000000..2c5a1e6 --- /dev/null +++ b/src/guards/auth.ts @@ -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'); + } +} diff --git a/src/router/index.ts b/src/router/index.ts index 699436a..2748595 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -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; diff --git a/src/types.d.ts b/src/types.d.ts new file mode 100644 index 0000000..5a54ebf --- /dev/null +++ b/src/types.d.ts @@ -0,0 +1,7 @@ +import 'vue-router'; + +declare module 'vue-router' { + interface RouteMeta { + authRequired?: boolean; + } +} diff --git a/src/views/LogIn.vue b/src/views/LogIn.vue new file mode 100644 index 0000000..6af4cec --- /dev/null +++ b/src/views/LogIn.vue @@ -0,0 +1,21 @@ + + + diff --git a/src/views/Vote.vue b/src/views/Vote.vue new file mode 100644 index 0000000..b972ecf --- /dev/null +++ b/src/views/Vote.vue @@ -0,0 +1,3 @@ +