aboutsummaryrefslogtreecommitdiff
path: root/front-end/src/views
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2023-12-13 17:58:51 -0500
committerLibravatar vnugent <public@vaughnnugent.com>2023-12-13 17:58:51 -0500
commit4b8ae76132d2342f40cec703b3d5145ea075c451 (patch)
tree62b942b6181261566cd3245ee35cd15a138aabf2 /front-end/src/views
parentb564708f29cf8a709c3e3d981477b2ec8440673e (diff)
log time coming ui and lib updates
Diffstat (limited to 'front-end/src/views')
-rw-r--r--front-end/src/views/Account/[comp].vue30
-rw-r--r--front-end/src/views/Account/components/profile/Profile.vue55
-rw-r--r--front-end/src/views/Account/components/settings/Fido.vue4
-rw-r--r--front-end/src/views/Account/components/settings/PasswordReset.vue16
-rw-r--r--front-end/src/views/Account/components/settings/Pki.vue46
-rw-r--r--front-end/src/views/Account/components/settings/Security.vue31
-rw-r--r--front-end/src/views/Account/components/settings/Settings.vue2
-rw-r--r--front-end/src/views/Account/components/settings/TotpSettings.vue40
-rw-r--r--front-end/src/views/Blog/components/Content/ContentEditor.vue48
-rw-r--r--front-end/src/views/Blog/index.vue7
-rw-r--r--front-end/src/views/Login/components/Social.vue55
-rw-r--r--front-end/src/views/Login/components/Totp.vue29
-rw-r--r--front-end/src/views/Login/components/UserPass.vue98
-rw-r--r--front-end/src/views/Login/index.vue120
-rw-r--r--front-end/src/views/Login/pki/index.vue13
-rw-r--r--front-end/src/views/Login/social/[type].vue90
-rw-r--r--front-end/src/views/[...all].vue5
-rw-r--r--front-end/src/views/index.vue6
18 files changed, 315 insertions, 380 deletions
diff --git a/front-end/src/views/Account/[comp].vue b/front-end/src/views/Account/[comp].vue
index d854e2e..d4f1c4d 100644
--- a/front-end/src/views/Account/[comp].vue
+++ b/front-end/src/views/Account/[comp].vue
@@ -1,6 +1,6 @@
<template>
<div id="account-template" class="app-component-entry">
- <TabGroup :selectedIndex="tabId" @change="onTabChange" as="div" class="container h-full m-auto mt-0 mb-10 duration-150 ease-linear">
+ <TabGroup :selectedIndex="tabId" @change="onTabChange" as="div" class="container h-full m-auto mt-0 mb-10 duration-150 ease-linear text-color-foreground">
<div class="flex w-full py-2 xl:w-auto lg:pt-4 xl:fixed">
@@ -39,25 +39,24 @@
<script setup lang="ts">
import { computed } from 'vue'
-import { usePageGuard, useTitle } from '@vnuge/vnlib.browser'
import { useRouteParams } from '@vueuse/router'
import { TabGroup, TabList, Tab, TabPanels, TabPanel } from '@headlessui/vue'
+import { useStore } from '../../store'
import Settings from './components/settings/Settings.vue'
import Profile from './components/profile/Profile.vue'
-usePageGuard()
-useTitle('Account')
+const { setPageTitle } = useStore()
+setPageTitle('Account')
enum ComponentType{
Profile = 'profile',
- Oauth = 'oauth',
Settings = 'settings'
}
-const comp = useRouteParams<ComponentType>('comp')
+const comp = useRouteParams<ComponentType>('comp', '')
-const tabId = computed<number>(() =>{
- switch (comp.value) {
+const tabId = computed<number>(() => {
+ switch (comp.value) {
case ComponentType.Settings:
return 1
case ComponentType.Profile:
@@ -81,10 +80,7 @@ const onTabChange = (tabid : number) =>{
</script>
<style lang="scss">
-#account-template{
- p{
- @apply text-gray-700 dark:text-gray-400;
- }
+#account-template{
.page-link{
font-size: 1.1rem;
@@ -103,13 +99,21 @@ const onTabChange = (tabid : number) =>{
}
+ .text-color-foreground{
+ @apply dark:text-white text-black;
+ }
+
+ .text-color-background{
+ @apply text-gray-500;
+ }
+
.panel-container .panel-header{
@apply flex flex-row px-2;
}
.panel-container .panel-content{
@apply bg-white dark:bg-dark-800 border-transparent dark:border-dark-500;
- @apply m-auto max-w-3xl border sm:rounded-md shadow-md sm:p-4 p-3 sm:my-3 my-2;
+ @apply m-auto max-w-3xl border sm:rounded shadow-md sm:p-4 p-3 sm:my-3 my-2;
}
.panel-container .panel-header .panel-title{
diff --git a/front-end/src/views/Account/components/profile/Profile.vue b/front-end/src/views/Account/components/profile/Profile.vue
index 0b0a6dd..0db7192 100644
--- a/front-end/src/views/Account/components/profile/Profile.vue
+++ b/front-end/src/views/Account/components/profile/Profile.vue
@@ -2,10 +2,10 @@
<div id="account-profile" class="acnt-content-container panel-container">
<div class="acnt-content profile-container panel-content">
- <div id="profile-control-container" class="flex flex-row" :modified="modified">
+ <div id="profile-control-container" class="flex flex-row" :modified="store.userProfile.modified">
<div class="m-0">
<div class="flex rounded-full w-14 h-14 bg-primary-500 dark:bg-primary-600">
- <div class="m-auto text-white dark:text-dark-400">
+ <div class="m-auto text-white dark:text-dark-500">
<fa-icon :icon="['fas', 'user']" size="2xl" />
</div>
</div>
@@ -28,7 +28,7 @@
<div>
- <p class="profile-text">
+ <p class="profile-text text-color-background">
You may set or change your profile information here. All fields are optional,
but some features may not work without some information.
</p>
@@ -36,7 +36,7 @@
<div class="locked-info">
<div class="mx-auto my-1 sm:mx-0 sm:my-2">
<span class="pr-2">Email:</span>
- <span class="">{{ data.email }}</span>
+ <span class="">{{ store.userProfile.data.email }}</span>
</div>
<div class="mx-auto my-1 sm:mx-0 sm:my-2">
<span class="pr-2">Created:</span>
@@ -62,43 +62,28 @@ import { defaultTo } from 'lodash-es'
import useVuelidate from '@vuelidate/core'
import { ref, computed, watch } from 'vue'
import { Rules, FormSchema } from './profile-schema.ts'
-import { apiCall, useMessage, useWait, useDataBuffer, useUser, useVuelidateWrapper, IUserProfile } from '@vnuge/vnlib.browser'
-
-const ACCOUNT_URL = '/account/profile'
-
-interface UserProfile extends IUserProfile{
- created : string | Date
-}
+import { apiCall, useMessage, useWait, useVuelidateWrapper, WebMessage } from '@vnuge/vnlib.browser'
+import { useStore } from '../../../../store'
const { waiting } = useWait()
-const { getProfile } = useUser()
const { onInput, clearMessage } = useMessage()
-const { data, buffer, apply, revert, modified } = useDataBuffer<UserProfile>({} as UserProfile)
+
+const store = useStore()
const editMode = ref(false)
// Create validator based on the profile buffer as a data model
-const v$ = useVuelidate(Rules, buffer, { $lazy:true, $autoDirty:false })
+const v$ = useVuelidate(Rules, store.userProfile.buffer, { $lazy:true })
// Setup the validator wrapper
const { validate } = useVuelidateWrapper(v$);
//const modified = computed(() => profile.value.Modified)
-const createdTime = computed(() => defaultTo(data.created?.toLocaleString(), ''))
-
-const loadProfileData = async () => {
- await apiCall(async () => {
- // Get the user's profile
- const profile = await getProfile<UserProfile>()
- profile.created = new Date(profile.created)
- //Apply the profile to the buffer
- apply(profile)
- })
-}
+const createdTime = computed(() => defaultTo(store.userProfile.data.created?.toLocaleString(), ''))
const revertProfile = () => {
//Revert the buffer
- revert()
+ store.userProfile.revert()
clearMessage()
editMode.value = false
}
@@ -112,32 +97,24 @@ const onSubmit = async () => {
return
}
// Init the api call
- await apiCall(async ({ axios, toaster }) => {
- // Apply the buffer to the profile
- const response = await axios.post(ACCOUNT_URL, buffer)
-
- if(!response.data.success){
- throw { response }
- }
+ await apiCall(async ({ toaster }) => {
+ const res = await store.userProfile.update();
+ const successm = (res as WebMessage<string>).getResultOrThrow();
+
//No longer in edit mode
editMode.value = false
//Show success message
toaster.general.success({
title: 'Update successful',
- text: response.data.result,
+ text: successm,
})
-
- //reload the profile data
- loadProfileData()
})
}
watch(editMode, () => v$.value.$reset())
-//Inital profile data load, dont await
-loadProfileData()
</script>
diff --git a/front-end/src/views/Account/components/settings/Fido.vue b/front-end/src/views/Account/components/settings/Fido.vue
index f319cd3..d453378 100644
--- a/front-end/src/views/Account/components/settings/Fido.vue
+++ b/front-end/src/views/Account/components/settings/Fido.vue
@@ -18,13 +18,13 @@
</button>
</div>
<div v-else>
- <button class="btn primary xs" @click.prevent="Setup">
+ <button class="btn primary xs" disabled="true" @click.prevent="Setup">
<fa-icon icon="plus" />
<span class="pl-2">Setup</span>
</button>
</div>
</div>
- <p class="p-1 pt-3 text-sm text-gray-600">
+ <p class="p-1 pt-3 text-sm text-color-background">
WebAuthN/FIDO is not yet supported, due to complexity and browser support.
</p>
</div>
diff --git a/front-end/src/views/Account/components/settings/PasswordReset.vue b/front-end/src/views/Account/components/settings/PasswordReset.vue
index f90bce8..24dced6 100644
--- a/front-end/src/views/Account/components/settings/PasswordReset.vue
+++ b/front-end/src/views/Account/components/settings/PasswordReset.vue
@@ -2,24 +2,22 @@
<div id="pwreset-settings" class="container">
<div class="panel-content">
- <h5>Password Reset</h5>
-
- <div v-if="!pwResetShow" class="py-2">
+ <div v-if="!pwResetShow" class="">
<div class="flex flex-wrap items-center justify-between">
-
- <div class="my-auto">
- Click to reset
+
+ <div class="">
+ <h5>Password Reset</h5>
</div>
<div class="flex justify-end">
- <button class="btn red xs" @click="showForm">
+ <button class="btn xs" @click="showForm">
<fa-icon icon="sync" />
<span class="pl-2">Reset Password</span>
</button>
</div>
</div>
- <p class="mt-3 text-sm">
+ <p class="mt-3 text-sm text-color-background">
You may only reset your password if you have an internal user account. If you exclusivly use an external
authentication provider (like GitHub or Discord), you will need to reset your password externally.
</p>
@@ -61,7 +59,7 @@
<script setup lang="ts">
import { isEmpty, toSafeInteger } from 'lodash-es';
-import useVuelidate from '@vuelidate/core'
+import { useVuelidate } from '@vuelidate/core'
import { required, maxLength, minLength, helpers } from '@vuelidate/validators'
import { useUser, apiCall, useMessage, useWait, useConfirm, useVuelidateWrapper } from '@vnuge/vnlib.browser'
import { computed, reactive, ref, toRefs, watch } from 'vue'
diff --git a/front-end/src/views/Account/components/settings/Pki.vue b/front-end/src/views/Account/components/settings/Pki.vue
index 1b169e2..afe606f 100644
--- a/front-end/src/views/Account/components/settings/Pki.vue
+++ b/front-end/src/views/Account/components/settings/Pki.vue
@@ -1,12 +1,12 @@
<template>
- <div id="pki-settings" v-show="pkiEnabled" class="container">
+ <div id="pki-settings" class="container">
<div class="panel-content">
<div class="flex flex-row flex-wrap justify-between">
<h5>PKI Authentication</h5>
<div class="">
- <div v-if="enabled" class="button-group">
- <button class="btn yellow xs" @click.prevent="setIsOpen(true)">
+ <div v-if="pkiEnabled" class="button-group">
+ <button class="btn xs" @click.prevent="setIsOpen(true)">
<fa-icon icon="plus" />
<span class="pl-2">Add Key</span>
</button>
@@ -23,7 +23,7 @@
</div>
</div>
- <div v-if="pubKeys && pubKeys.length > 0" class="w-full mt-4">
+ <div v-if="store.pkiPublicKeys && store.pkiPublicKeys.length > 0" class="w-full mt-4">
<table class="min-w-full text-sm divide-y-2 divide-gray-200 dark:divide-dark-500">
<thead class="text-left">
<tr>
@@ -41,7 +41,7 @@
</thead>
<tbody class="divide-y divide-gray-200 dark:divide-dark-500">
- <tr v-for="key in pubKeys">
+ <tr v-for="key in store.pkiPublicKeys">
<td class="p-2 t font-medium truncate max-w-[8rem] whitespace-nowrap dark:text-white">
{{ key.kid }}
</td>
@@ -62,11 +62,10 @@
</table>
</div>
- <p v-else class="p-1 pt-3 text-sm text-gray-600">
+ <p v-else class="p-1 pt-3 text-sm text-color-background">
PKI authentication is a method of authenticating your user account with signed messages and a shared public key. This method implementation
uses client signed Json Web Tokens to authenticate user generated outside this website as a One Time Password (OTP). This allows for you to
use your favorite hardware or software tools, to generate said OTPs to authenticate your user.
-
</p>
</div>
</div>
@@ -75,7 +74,7 @@
<div class="fixed inset-0 bg-black/30" aria-hidden="true" />
<div class="fixed inset-0 flex justify-center">
- <DialogPanel class="w-full max-w-lg p-4 m-auto mt-24 bg-white rounded dark:bg-dark-600 dark:text-gray-300">
+ <DialogPanel class="w-full max-w-lg p-4 m-auto mt-24 bg-white rounded dark:bg-dark-700 dark:text-gray-300">
<h4>Configure your authentication key</h4>
<p class="mt-2 text-sm">
Please paste your authenticator's public key as a Json Web Key (JWK) object. Your JWK must include a kid (key id) and a kty (key type) field.
@@ -85,7 +84,7 @@
</div>
<div class="flex justify-end gap-2 mt-4">
<button class="rounded btn sm primary" @click.prevent="onSubmitKeys">Submit</button>
- <button class="rounded btn sm red" @click.prevent="setIsOpen(false)">Cancel</button>
+ <button class="rounded btn sm" @click.prevent="setIsOpen(false)">Cancel</button>
</div>
</DialogPanel>
</div>
@@ -93,24 +92,19 @@
</template>
<script setup lang="ts">
-import { isEmpty, isNil } from 'lodash-es'
-import { apiCall, useConfirm, useSession, debugLog, useFormToaster, PkiApi, PkiPublicKey } from '@vnuge/vnlib.browser'
+import { includes, isEmpty } from 'lodash-es'
+import { apiCall, useConfirm, useSession, debugLog, useFormToaster, PkiPublicKey } from '@vnuge/vnlib.browser'
import { computed, ref, watch } from 'vue'
-import { asyncComputed } from '@vueuse/core'
import { Dialog, DialogPanel } from '@headlessui/vue'
+import { useStore } from '../../../../store'
+import { } from 'pinia'
-const props = defineProps<{
- pkaiApi: PkiApi
-}>()
-
+const store = useStore()
const { reveal } = useConfirm()
const { isLocalAccount } = useSession()
const { error } = useFormToaster()
-const pkiEnabled = computed(() => isLocalAccount.value && !isNil(import.meta.env.VITE_PKI_ENDPOINT) && window.crypto.subtle)
-const { enabled, refresh } = props.pkaiApi
-
-const pubKeys = asyncComputed(() => pkiEnabled.value ? apiCall(props.pkaiApi.getAllKeys) : [], [])
+const pkiEnabled = computed(() => isLocalAccount.value && includes(store.mfaEndabledMethods, "pki") && window.crypto.subtle)
const isOpen = ref(false)
const keyData = ref('')
@@ -122,7 +116,7 @@ watch(isOpen, () =>{
pemFormat.value = false
explicitCurve.value = ""
//Reload status
- refresh()
+ store.mfaRefreshMethods()
})
const setIsOpen = (value : boolean) => isOpen.value = value
@@ -140,7 +134,7 @@ const onRemoveKey = async (single: PkiPublicKey) =>{
await apiCall(async ({ toaster }) => {
//TODO: require password or some upgrade to disable
- const { success } = await props.pkaiApi.removeKey(single.kid);
+ const { success } = await store.pkiConfig.removeKey(single.kid);
if (success) {
toaster.general.success({
@@ -156,7 +150,7 @@ const onRemoveKey = async (single: PkiPublicKey) =>{
}
//Refresh the status
- props.pkaiApi.refresh();
+ store.mfaRefreshMethods()
});
}
@@ -174,7 +168,7 @@ const onDisable = async () => {
//Disable pki
//TODO: require password or some upgrade to disable
- const { success } = await props.pkaiApi.disable();
+ const { success } = await store.pkiConfig.disable();
if(success){
toaster.general.success({
@@ -190,7 +184,7 @@ const onDisable = async () => {
}
//Refresh the status
- props.pkaiApi.refresh();
+ store.mfaRefreshMethods()
});
}
@@ -232,7 +226,7 @@ const onSubmitKeys = async () =>{
//init/update the key
//TODO: require password or some upgrade to disable
- const { getResultOrThrow } = await props.pkaiApi.addOrUpdate(jwk);
+ const { getResultOrThrow } = await store.pkiConfig.addOrUpdate(jwk);
const result = getResultOrThrow();
diff --git a/front-end/src/views/Account/components/settings/Security.vue b/front-end/src/views/Account/components/settings/Security.vue
index 1f2d06d..e6075f9 100644
--- a/front-end/src/views/Account/components/settings/Security.vue
+++ b/front-end/src/views/Account/components/settings/Security.vue
@@ -12,38 +12,38 @@
<div id="account-mfa-settings" class="panel-content">
<h5>Multi Factor Authentication</h5>
- <div class="py-2 border-b-2 border-gray-200 dark:border-dark-400">
- <TotpSettings :mfa="mfaApi" />
+ <div class="py-2 border-b-2 border-gray-200 dark:border-dark-500">
+ <TotpSettings />
</div>
<div class="py-2">
<Fido :fido-enabled="fidoEnabled"/>
</div>
</div>
- <Pki :pkai-api="pkiApi" />
+ <Pki />
<div id="browser-poll-settings" class="panel-content" >
<div class="flex justify-between">
<h5>Keep me logged in</h5>
<div class="pl-1">
<Switch
- v-model="enabled"
- :class="enabled ? 'bg-primary-500 dark:bg-primary-600' : 'bg-gray-200 dark:bg-dark-400'"
+ v-model="autoHeartbeat"
+ :class="autoHeartbeat ? 'bg-primary-500 dark:bg-primary-600' : 'bg-gray-200 dark:bg-dark-500'"
class="relative inline-flex items-center h-6 rounded-full w-11"
>
<span class="sr-only">Enable auto heartbeat</span>
<span
- :class="enabled ? 'translate-x-6' : 'translate-x-1'"
+ :class="autoHeartbeat ? 'translate-x-6' : 'translate-x-1'"
class="inline-block w-4 h-4 transition transform bg-white rounded-full"
/>
</Switch>
</div>
</div>
- <p class="p-1 text-sm">
+ <p class="p-1 text-sm text-color-background">
When enabled, continuously regenerates your login credentials to keep you logged in. The longer you are logged in,
the easier session fixation attacks become. If disabled, you will need to log when your credentials have expired.
- It is recommneded that you leave this disabled <span class="text-yellow-500">Disabled</span>
+ It is recommneded that you leave this <strong>off</strong>.
</p>
</div>
@@ -52,22 +52,25 @@
</template>
<script setup lang="ts">
-import { useAutoHeartbeat, useMfaConfig, MfaMethod, usePkiConfig } from '@vnuge/vnlib.browser'
+import { MfaMethod } from '@vnuge/vnlib.browser'
import { computed } from 'vue'
import { Switch } from '@headlessui/vue'
import { includes } from 'lodash-es'
+import { storeToRefs } from 'pinia'
+import { useStore } from '../../../../store'
import Fido from './Fido.vue'
import Pki from './Pki.vue'
import TotpSettings from './TotpSettings.vue'
import PasswordReset from './PasswordReset.vue'
-const { enabled } = useAutoHeartbeat()
+const store = useStore();
+const { autoHeartbeat } = storeToRefs(store);
-const mfaApi = useMfaConfig('/account/mfa')
-const pkiApi = usePkiConfig(import.meta.env.VITE_PKI_ENDPOINT, mfaApi)
+//Load mfa methods
+store.mfaRefreshMethods();
-const fidoEnabled = computed(() => includes(mfaApi.enabledMethods.value, 'fido' as MfaMethod))
-const totpEnabled = computed(() => includes(mfaApi.enabledMethods.value, MfaMethod.TOTP))
+const fidoEnabled = computed(() => includes(store.mfaEndabledMethods, 'fido' as MfaMethod))
+const totpEnabled = computed(() => includes(store.mfaEndabledMethods, MfaMethod.TOTP))
</script>
diff --git a/front-end/src/views/Account/components/settings/Settings.vue b/front-end/src/views/Account/components/settings/Settings.vue
index cd2ab48..fb86951 100644
--- a/front-end/src/views/Account/components/settings/Settings.vue
+++ b/front-end/src/views/Account/components/settings/Settings.vue
@@ -7,7 +7,7 @@
</template>
<script setup lang="ts">
-import Security from './security.vue'
+import Security from './Security.vue'
</script>
diff --git a/front-end/src/views/Account/components/settings/TotpSettings.vue b/front-end/src/views/Account/components/settings/TotpSettings.vue
index 9760806..0fcfe31 100644
--- a/front-end/src/views/Account/components/settings/TotpSettings.vue
+++ b/front-end/src/views/Account/components/settings/TotpSettings.vue
@@ -24,13 +24,13 @@
Your secret, if your application requires it.
</p>
- <p class="flex flex-row flex-wrap justify-center p-2 bg-gray-200 border border-gray-300 dark:bg-dark-800 dark:border-dark-300">
+ <p class="flex flex-row flex-wrap justify-center p-2 bg-gray-200 border border-gray-300 dark:bg-dark-800 dark:border-dark-500">
<span v-for="code in secretSegments" :key="code" class="px-2 font-mono tracking-wider" >
{{ code }}
</span>
</p>
- <p class="py-2">
+ <p class="py-2 text-color-background">
Please enter your code from your authenticator app to continue.
</p>
@@ -58,7 +58,7 @@
<h6>TOTP Authenticator App</h6>
<div v-if="totpEnabled" class="button-group">
- <button class="btn yellow xs" @click.prevent="regenTotp">
+ <button class="btn xs" @click.prevent="regenTotp">
<fa-icon icon="sync" />
<span class="pl-2">Regenerate</span>
</button>
@@ -74,7 +74,7 @@
<span class="pl-2">Setup</span>
</button>
</div>
- <p class="p-1 pt-3 text-sm text-gray-600">
+ <p class="p-1 pt-3 text-sm text-color-background">
TOTP is a time based one time password. You can use it as a form of Multi Factor Authentication when
using another device such as a smart phone or TOTP hardware device. You can use TOTP with your smart
phone
@@ -91,21 +91,21 @@
<script setup lang="ts">
import { isNil, chunk, defaultTo, includes, map, join } from 'lodash-es'
import { TOTP } from 'otpauth'
+import { computed, ref, defineAsyncComponent } from 'vue'
import base32Encode from 'base32-encode'
-import VueQrcode from '@chenfengyuan/vue-qrcode'
-import VOtpInput from "vue3-otp-input";
-import { computed, ref } from 'vue'
import {
- useSessionUtils,
useSession,
- useUser,
useMessage,
useConfirm,
usePassConfirm,
useFormToaster,
- MfaApi,
MfaMethod
} from '@vnuge/vnlib.browser'
+import { useStore } from '../../../../store';
+import { storeToRefs } from 'pinia';
+
+const VueQrcode = defineAsyncComponent(() => import('@chenfengyuan/vue-qrcode'))
+const VOtpInput = defineAsyncComponent(() => import('vue3-otp-input'));
interface TotpConfig{
secret: string;
@@ -115,19 +115,15 @@ interface TotpConfig{
readonly period?: number;
}
-const props = defineProps<{
- mfa: MfaApi
-}>()
+const store = useStore();
+const { userName, isLocalAccount, mfaEndabledMethods } = storeToRefs(store);
-const { isLocalAccount } = useSession()
-const { KeyStore } = useSessionUtils()
-const { userName } = useUser()
+const { KeyStore } = useSession()
const { reveal } = useConfirm()
const { elevatedApiCall } = usePassConfirm()
const { onInput, setMessage } = useMessage()
-const { enabledMethods, disableMethod, initOrUpdateMethod, refreshMethods } = props.mfa;
-const totpEnabled = computed(() => includes(enabledMethods.value, MfaMethod.TOTP))
+const totpEnabled = computed(() => includes(mfaEndabledMethods.value, MfaMethod.TOTP))
const totpMessage = ref<TotpConfig>()
const showSubmitButton = ref(false)
@@ -164,7 +160,7 @@ const ProcessAddOrUpdate = async () => {
await elevatedApiCall(async ({ password }) => {
// Init or update the totp method and get the encrypted totp message
- const res = await initOrUpdateMethod<TotpConfig>(MfaMethod.TOTP, password);
+ const res = await store.mfaConfig.initOrUpdateMethod<TotpConfig>(MfaMethod.TOTP, password);
//Get the encrypted totp message
const totp = res.getResultOrThrow()
@@ -220,10 +216,10 @@ const disable = async () => {
await elevatedApiCall(async ({ password }) => {
// Disable the totp method
- const res = await disableMethod(MfaMethod.TOTP, password)
+ const res = await store.mfaConfig.disableMethod(MfaMethod.TOTP, password)
res.getResultOrThrow()
- refreshMethods()
+ store.mfaRefreshMethods()
})
}
@@ -250,7 +246,7 @@ const CloseQrWindow = () => {
totpMessage.value = undefined
//Fresh methods
- refreshMethods()
+ store.mfaRefreshMethods()
}
</script>
diff --git a/front-end/src/views/Blog/components/Content/ContentEditor.vue b/front-end/src/views/Blog/components/Content/ContentEditor.vue
index 756cec3..608cd1b 100644
--- a/front-end/src/views/Blog/components/Content/ContentEditor.vue
+++ b/front-end/src/views/Blog/components/Content/ContentEditor.vue
@@ -26,6 +26,20 @@
v-model="v$.name.$model"
:class="{'invalid':v$.name.$invalid && v$.name.$dirty}"
/>
+ <div v-if="isNewUpload"
+ id="file-drop-zone"
+ ref="newFileDropZone"
+ class="py-16 mt-3 transition-all duration-150 ease-linear border-2 border-dashed rounded cursor-pointer dark:border-dark-500"
+ :class="{'border-primary-500 dark:border-primary-500':isOverDropZone}"
+ @click.prevent="open()"
+ >
+ <div class="flex flex-col items-center justify-center">
+ <fa-icon icon="file-upload" class="text-4xl" />
+ <p class="mt-2 text-sm text-center">
+ Drop file here or click to select file
+ </p>
+ </div>
+ </div>
</div>
<div v-if="editFile?.id" class="mt-3">
<div class="p-3 py-0.5">
@@ -68,17 +82,17 @@
Content-Type: {{ editFile.content_type }}
</div>
</div>
- </div>
- <div v-if="!uploadedFile.name" class="m-auto mt-5 w-fit">
- <button class="btn" @click.prevent="open()">
- {{ editFile?.id ? 'Overwrite file' : 'Select File' }}
- </button>
+ <div class="m-auto mt-5 w-fit">
+ <button class="btn" @click.prevent="open()">
+ Overwrite file
+ </button>
+ </div>
</div>
</div>
</fieldset>
</form>
</div>
- <div class="mt-4">
+ <div v-if="!isNewUpload" class="mt-4">
<div class="mx-auto w-fit">
<button class="btn red" @click="onDelete">Delete Forever</button>
</div>
@@ -87,13 +101,13 @@
</template>
<script setup lang="ts">
-import { computed, ref } from 'vue';
-import { reactiveComputed, useFileDialog } from '@vueuse/core';
+import { computed, ref, watch } from 'vue';
+import { reactiveComputed, useFileDialog, useDropZone } from '@vueuse/core';
import { ContentMeta } from '@vnuge/cmnext-admin';
import { useConfirm, useVuelidateWrapper, useFormToaster, useWait } from '@vnuge/vnlib.browser';
import { defaultTo, first, isEmpty, round } from 'lodash-es';
import { required, helpers, maxLength } from '@vuelidate/validators'
-import useVuelidate from '@vuelidate/core';
+import { useVuelidate } from '@vuelidate/core';
import { BlogState } from '../../blog-api';
const emit = defineEmits(['close', 'submit', 'delete']);
@@ -105,11 +119,13 @@ const { reveal } = useConfirm();
const { waiting } = useWait();
const { content, channels } = props.blog;
+const newFileDropZone = ref<HTMLElement>();
const selectedId = computed(() => content.selectedId.value);
const selectedContent = computed<ContentMeta>(() => defaultTo(content.selectedItem.value, {} as ContentMeta));
const metaBuffer = reactiveComputed<ContentMeta>(() => ({ ...selectedContent.value}));
const isChannelSelected = computed(() => channels.selectedItem.value?.id?.length ?? 0 > 0);
+const isNewUpload = computed(() => selectedId.value === 'new');
const v$ = useVuelidate({
name: {
@@ -122,11 +138,15 @@ const v$ = useVuelidate({
const { validate } = useVuelidateWrapper(v$);
const file = ref<File | undefined>();
-const { files, open, reset, onChange } = useFileDialog({ accept: '*' })
+//set the file name when a file is selected
+watch(file, f => v$.value.name.$model = f?.name);
+
+const { open, reset, onChange } = useFileDialog({ accept: '*' })
//update the file buffer when a user selects a file to upload
-onChange(() => {
- file.value = first(files.value)
- v$.value.name.$model = file.value?.name;
+onChange((f) => onFileUploaded(first(f)))
+
+const { isOverDropZone } = useDropZone(newFileDropZone, {
+ onDrop: (files) => onFileUploaded(first(files))
})
const editFile = computed<ContentMeta | undefined>(() => selectedContent.value);
@@ -143,6 +163,8 @@ const getSizeinKb = (value : number | undefined) => {
return `${size} ${value > 1024 ? 'KB' : 'B'}`;
}
+const onFileUploaded = (f: File | undefined) => file.value = f
+
const onSubmit = async () => {
const { error } = useFormToaster()
diff --git a/front-end/src/views/Blog/index.vue b/front-end/src/views/Blog/index.vue
index ea35ad6..b4aa47d 100644
--- a/front-end/src/views/Blog/index.vue
+++ b/front-end/src/views/Blog/index.vue
@@ -115,16 +115,17 @@ import { AxiosProgressEvent } from 'axios';
import { TabGroup, TabList, Tab, TabPanels, TabPanel, Switch } from '@headlessui/vue'
import { first } from 'lodash-es';
import { useRoute, useRouter } from 'vue-router';
-import { usePageGuard, useUser, useTitle, useAxios } from '@vnuge/vnlib.browser';
+import { useUser, useAxios } from '@vnuge/vnlib.browser';
import { createBlogContext, useComputedChannels, useComputedPosts, useComputedContent, SortType } from '@vnuge/cmnext-admin';
import { BlogState } from './blog-api';
+import { useStore } from '../../store';
import Channels from './components/Channels.vue';
import Posts from './components/Posts.vue';
import Content from './components/Content.vue';
//Protect page
-usePageGuard();
-useTitle('CMNext Admin')
+const store = useStore()
+store.setPageTitle('Blog Admin')
if(!window.CKEDITOR){
//Load scripts
diff --git a/front-end/src/views/Login/components/Social.vue b/front-end/src/views/Login/components/Social.vue
index 5824226..2cea930 100644
--- a/front-end/src/views/Login/components/Social.vue
+++ b/front-end/src/views/Login/components/Social.vue
@@ -1,47 +1,30 @@
<template>
-
- <form class="w-full" @submit.prevent="SocalLogin('/login/social/github')">
- <button type="submit" class="btn social-button" :disabled="waiting">
- <fa-icon :icon="['fab','github']" size="xl" />
- Login with Github
- </button>
- </form>
- <form class="mt-4" @submit.prevent="SocalLogin('/login/social/discord')">
- <button type="submit" class="btn social-button" :disabled="waiting">
- <fa-icon :icon="['fab','discord']" size="xl" />
- Login with Discord
- </button>
- </form>
+ <div class="flex flex-col gap-3">
+ <div v-for="method in store.socialOauth.methods" :key="method.Id" class="">
+ <button
+ type="submit"
+ class="btn social-button"
+ :disabled="waiting"
+ @click.prevent="submitLogin(method)"
+ >
+ <fa-icon :icon="['fab', method.Id]" size="xl" />
+ Login with {{ capitalize(method.Id) }}
+ </button>
+ </div>
+ </div>
</template>
<script setup lang="ts">
-import { apiCall, useWait, useSessionUtils, WebMessage, useUser } from '@vnuge/vnlib.browser'
+import { apiCall, useWait, type OAuthMethod } from '@vnuge/vnlib.browser'
+import { capitalize } from 'lodash-es';
+import { useStore } from '../../../store';
const { waiting } = useWait()
-const { KeyStore } = useSessionUtils()
-const { prepareLogin } = useUser()
+const store = useStore()
-const SocalLogin = async (url:string) => {
- await apiCall(async ({ axios }) => {
-
- //Prepare the login claim
- const claim = await prepareLogin()
- const { data } = await axios.put<WebMessage<string>>(url, claim)
-
- const encDat = data.getResultOrThrow()
- // Decrypt the result which should be a redirect url
- const result = await KeyStore.decryptDataAsync(encDat)
- // get utf8 text
- const text = new TextDecoder('utf-8').decode(result)
- // Recover url
- const redirect = new URL(text)
- // Force https
- redirect.protocol = 'https:'
- // redirect to the url
- window.location.href = redirect.href
- })
-}
+//Invoke login wrapped in api call
+const submitLogin = (method: OAuthMethod) => apiCall(() => store.socialOauth.beginLoginFlow(method))
</script> \ No newline at end of file
diff --git a/front-end/src/views/Login/components/Totp.vue b/front-end/src/views/Login/components/Totp.vue
index 43c05d8..2ba1314 100644
--- a/front-end/src/views/Login/components/Totp.vue
+++ b/front-end/src/views/Login/components/Totp.vue
@@ -23,25 +23,40 @@
</template>
<script setup lang="ts">
-import { useMessage, useWait } from '@vnuge/vnlib.browser';
+import { toRefs, defineAsyncComponent } from 'vue';
+import { IMfaFlowContinuiation, apiCall, useMessage, useWait } from '@vnuge/vnlib.browser';
import { toSafeInteger } from 'lodash-es';
-import VOtpInput from "vue3-otp-input";
+const VOtpInput = defineAsyncComponent(() => import('vue3-otp-input'))
-const emit = defineEmits(['submit'])
+const emit = defineEmits(['clear'])
+const props = defineProps<{
+ upgrade: IMfaFlowContinuiation
+}>()
+
+const { upgrade } = toRefs(props)
const { waiting } = useWait();
const { onInput } = useMessage();
-const SubimitTotp = async (code : string) => {
+const SubimitTotp = (code : string) => {
//If a request is still pending, do nothing
if (waiting.value) {
return
}
- //Submit a mfa upgrade result
- emit('submit', {
- code: toSafeInteger(code)
+ apiCall(async ({ toaster }) => {
+ //Submit totp code
+ const res = await upgrade.value.submit({ code: toSafeInteger(code) })
+ res.getResultOrThrow()
+
+ emit('clear')
+
+ // Push a new toast message
+ toaster.general.success({
+ title: 'Success',
+ text: 'You have been logged in',
+ })
})
}
diff --git a/front-end/src/views/Login/components/UserPass.vue b/front-end/src/views/Login/components/UserPass.vue
index e218cb8..442abb1 100644
--- a/front-end/src/views/Login/components/UserPass.vue
+++ b/front-end/src/views/Login/components/UserPass.vue
@@ -1,7 +1,12 @@
<template>
<div class="">
<h3>Login</h3>
- <form id="user-pass-submit-form" method="post" action="/login" @submit.prevent="SubmitLogin">
+
+ <div v-if="mfaUpgrade?.type === MfaMethod.TOTP">
+ <Totp @clear="totpClear" :upgrade="mfaUpgrade" />
+ </div>
+
+ <form v-else id="user-pass-submit-form" method="post" action="/login" @submit.prevent="SubmitLogin">
<fieldset class="" :disabled="waiting" >
<div>
<div class="float-label">
@@ -17,7 +22,7 @@
<label for="username">Email</label>
</div>
</div>
- <div class="py-3">
+ <div class="py-3">
<div class="mb-2 float-label">
<input
id="password"
@@ -37,29 +42,45 @@
<fa-icon :class="{'animate-spin':waiting}" :icon="waiting ? 'spinner' : 'sign-in-alt'"/>
Log-in
</button>
+ <div class="flex flex-row justify-between gap-3 pt-3 pb-2 form-links">
+ <router-link to="/pwreset">
+ Forgot password
+ </router-link>
+ <router-link to="/register">
+ Register a new account
+ </router-link>
+ </div>
</form>
- <div class="flex flex-row justify-between gap-3 pt-3 pb-2 form-links">
- <router-link to="/pwreset">
- Forgot password
- </router-link>
- <router-link to="/register">
- Register a new account
- </router-link>
- </div>
</div>
</template>
<script setup lang="ts">
-import { reactive } from 'vue'
-import useVuelidate from '@vuelidate/core'
+import { ref, shallowRef, reactive, defineAsyncComponent, type Ref } from 'vue'
+import { useTimeoutFn, set } from '@vueuse/core'
+import { useVuelidate } from '@vuelidate/core'
+import { isEqual } from 'lodash-es'
import { required, maxLength, minLength, email, helpers } from '@vuelidate/validators'
-import { useMessage, useVuelidateWrapper, useWait } from '@vnuge/vnlib.browser'
-
-const emit = defineEmits(['login'])
+import {
+ useVuelidateWrapper, useMfaLogin, totpMfaProcessor, IMfaFlowContinuiation, MfaMethod,
+ apiCall, useMessage, useWait, debugLog, WebMessage
+} from '@vnuge/vnlib.browser'
+const Totp = defineAsyncComponent(() => import('./Totp.vue'))
-const { onInput } = useMessage();
+const { onInput, setMessage } = useMessage();
const { waiting } = useWait();
+//Setup mfa login with TOTP support
+const { login } = useMfaLogin([ totpMfaProcessor() ])
+
+const mfaUpgrade = shallowRef<IMfaFlowContinuiation>();
+
+const mfaTimeout = ref<number>(600 * 1000);
+const mfaTimer = useTimeoutFn(() => {
+ //Clear upgrade message
+ mfaUpgrade.value = undefined;
+ setMessage('Your TOTP request has expired')
+}, mfaTimeout, { immediate: false })
+
const vState = reactive({ username: '', password: '' })
const rules = {
@@ -84,9 +105,50 @@ const SubmitLogin = async () => {
if (!await validate()) {
return
}
+
+ // Run login in an apicall wrapper
+ await apiCall(async ({ toaster }) => {
+
+ //Attempt to login
+ const response = await login(
+ v$.value.username.$model,
+ v$.value.password.$model
+ );
+
+ debugLog('Mfa-login', response);
+
+ //See if the response is a web message
+ if(response.getResultOrThrow){
+ (response as WebMessage).getResultOrThrow();
+ }
+
+ //Try to get response as a flow continuation
+ const mfa = response as IMfaFlowContinuiation
+
+ // Response is a totp upgrade request
+ if (isEqual(mfa.type, MfaMethod.TOTP)) {
+ //Store the upgrade message
+ set(mfaUpgrade, mfa);
+ //Setup timeout timer
+ set(mfaTimeout, mfa.expires! * 1000);
+ mfaTimer.start();
+ }
+ //If login without mfa was successful
+ else if (response.success) {
+ // Push a new toast message
+ toaster.general.success({
+ title: 'Success',
+ text: 'You have been logged in',
+ })
+ }
+ })
+}
- //Emit login and pass the username and password
- emit('login', { username: v$.value.username.$model, password: v$.value.password.$model });
+const totpClear = () => {
+ //Clear timer
+ mfaTimer.stop();
+ //Clear upgrade message
+ set(mfaUpgrade, undefined);
}
</script> \ No newline at end of file
diff --git a/front-end/src/views/Login/index.vue b/front-end/src/views/Login/index.vue
index fea02d4..5d8f298 100644
--- a/front-end/src/views/Login/index.vue
+++ b/front-end/src/views/Login/index.vue
@@ -2,12 +2,8 @@
<div id="login-template" class="app-component-entry">
<div class="login-container">
- <div v-if="showTotp">
- <Totp @submit="totpSubmit" />
- </div>
-
- <div v-else-if="!loggedIn">
- <UserPass @login="submitLogin" />
+ <div v-if="!loggedIn">
+ <UserPass/>
</div>
<div v-else>
@@ -24,12 +20,12 @@
</div>
</div>
- <div v-if="!(loggedIn || showTotp)" class="w-full mt-6">
-
+ <div v-if="!loggedIn" class="w-full mt-6">
+
<Social />
<!-- pki button, forward to the pki route -->
- <div v-if="pkiEnabled" class="mt-4">
+ <div v-if="pkiEnabled" class="mt-3">
<router-link to="/login/pki">
<button type="submit" class="btn red social-button" :disabled="waiting">
<fa-icon :icon="['fa','certificate']" size="xl" />
@@ -44,50 +40,29 @@
</template>
<script setup lang="ts">
-import { computed, ref } from 'vue'
-import Totp from './components/Totp.vue'
+import { } from 'vue'
+import { apiCall, useWait } from '@vnuge/vnlib.browser'
+import { isNil } from 'lodash-es'
+import { useStore } from '../../store'
+import { storeToRefs } from 'pinia'
import UserPass from './components/UserPass.vue'
import Social from './components/Social.vue'
-import {
- useMfaLogin, totpMfaProcessor, IMfaFlowContinuiation, MfaMethod, apiCall,
- useMessage, useWait, useUser, useSession, useLastPage, useTitle, debugLog
-} from '@vnuge/vnlib.browser'
-import { useTimeoutFn } from '@vueuse/core'
-import { isNil } from 'lodash-es'
-
-useTitle('Login')
//pki enabled flag from env
-const pkiEnabled = !isNil(import.meta.env.VITE_PKI_ENDPOINT);
+const pkiEnabled = !isNil(import.meta.env.VITE_PKI_ENABLED);
-const { waiting } = useWait()
-const { setMessage } = useMessage()
-const { logout } = useUser();
-const { loggedIn } = useSession()
-
-//Setup mfa login
-const { login } = useMfaLogin([ totpMfaProcessor() ])
-
-//If logged in re-route to the last page the user
-//was on but delayed to the session has time to be set
-const { gotoLastPage } = useLastPage()
-useTimeoutFn(() => loggedIn.value ? gotoLastPage() : null, 500)
+const store = useStore();
+const { loggedIn } = storeToRefs(store)
-const mfaUpgrade = ref<IMfaFlowContinuiation>();
-const mfaTimeout = ref<number>(600 * 1000);
-const { start, stop } = useTimeoutFn(() => {
- //Clear upgrade message
- mfaUpgrade.value = undefined;
- setMessage('Your TOTP request has expired')
-}, mfaTimeout, { immediate: false })
+store.setPageTitle('Login')
-const showTotp = computed(() => mfaUpgrade.value?.type === MfaMethod.TOTP)
+const { waiting } = useWait()
const submitLogout = async () => {
//Submit logout request
await apiCall(async ({ toaster }) => {
- // Attempt to login
- await logout()
+ // Attempt to logout
+ await store.socialOauth.logout()
// Push a new toast message
toaster.general.success({
id: 'logout-success',
@@ -98,67 +73,6 @@ const submitLogout = async () => {
})
}
-const submitLogin = async ({username, password} : { username: string, password:string }) => {
- // Run login in an apicall wrapper
- await apiCall(async ({ toaster }) => {
- // Attempt to login
- const response = await login(username, password);
-
- debugLog('Mfa-login', response)
-
- if(response.success == false){
- setMessage(response.result)
- return;
- }
-
- //Try to get response as a flow continuation
- const mfa = response as IMfaFlowContinuiation
-
- // Response is a totp upgrade request
- if (mfa.type === MfaMethod.TOTP) {
-
- //Store the upgrade message
- mfaUpgrade.value = mfa;
- //Setup timeout timer
- mfaTimeout.value = mfa.expires! * 1000;
- start();
- }
- //If login without mfa was successful
- else if (response.success) {
- // Push a new toast message
- toaster.general.success({
- title: 'Success',
- text: 'You have been logged in',
- })
- }
- })
-}
-
-const totpSubmit = ({ code } : {code:number}) =>{
- apiCall(async ({ toaster }) =>{
-
- if (!mfaUpgrade.value){
- return;
- }
-
- //Submit totp code
- const res = await mfaUpgrade.value.submit({ code })
- res.getResultOrThrow()
-
- //Clear timer
- stop();
-
- //Clear upgrade message
- mfaUpgrade.value = undefined;
-
- // Push a new toast message
- toaster.general.success({
- title: 'Success',
- text: 'You have been logged in',
- })
- })
-}
-
</script>
<style lang="scss">
diff --git a/front-end/src/views/Login/pki/index.vue b/front-end/src/views/Login/pki/index.vue
index cd0113e..585942a 100644
--- a/front-end/src/views/Login/pki/index.vue
+++ b/front-end/src/views/Login/pki/index.vue
@@ -12,7 +12,7 @@
<div class="flex justify-between mt-4">
<div class="text-sm">
- <a class="link" target="_blank" href="https://github.com/VnUgE/Plugins.Essentials/tree/master/plugins/VNLib.Plugins.Essentials.Accounts">
+ <a class="link" target="_blank" href="https://www.vaughnnugent.com/resources/software/articles?tags=docs,_VNLib.Plugins.Essentials.Accounts">
Goto OTP spec
<fa-icon icon="arrow-right" class="ml-1" />
</a>
@@ -33,16 +33,17 @@
<script setup lang="ts">
import { isEmpty } from 'lodash-es';
-import { apiCall, debugLog, useMessage, usePkiAuth } from '@vnuge/vnlib.browser';
+import { apiCall, debugLog, useMessage } from '@vnuge/vnlib.browser';
import { ref } from 'vue'
import { decodeJwt } from 'jose'
import { useRouter } from 'vue-router';
-
-const otp = ref('')
+import { useStore } from '../../../store';
const { setMessage } = useMessage()
const { push } = useRouter()
-const { login } = usePkiAuth(import.meta.env.VITE_PKI_ENDPOINT)
+const store = useStore()
+
+const otp = ref('')
const submit = () =>{
@@ -56,7 +57,7 @@ const submit = () =>{
const jwt = decodeJwt(otp.value)
debugLog(jwt)
- await login(otp.value)
+ await store.pkiAuth.login(otp.value)
//Go back to login page
push({ name: 'Login' })
diff --git a/front-end/src/views/Login/social/[type].vue b/front-end/src/views/Login/social/[type].vue
index 217e89c..68e8b77 100644
--- a/front-end/src/views/Login/social/[type].vue
+++ b/front-end/src/views/Login/social/[type].vue
@@ -32,84 +32,52 @@
</template>
<script setup lang="ts">
-import { isEqual } from 'lodash-es'
-import { useRouteParams, useRouteQuery } from '@vueuse/router'
-import { useSession, useWait, useUser, useTitle, configureApiCall } from '@vnuge/vnlib.browser'
+import { defer } from 'lodash-es'
+import { set, tryOnMounted } from '@vueuse/core'
+import { useWait, configureApiCall } from '@vnuge/vnlib.browser'
import { useRouter } from 'vue-router';
import { ref } from 'vue'
-import { ITokenResponse } from '@vnuge/vnlib.browser/dist/session';
+import { storeToRefs } from 'pinia';
+import { useStore } from '../../../store';
-useTitle('Social Login')
-
-const { loggedIn } = useSession()
-const { prepareLogin } = useUser()
+const store = useStore();
+const { loggedIn } = storeToRefs(store)
const { waiting } = useWait()
-const type = useRouteParams('type')
-const result = useRouteQuery('result', '');
-const nonce = useRouteQuery('nonce', '');
const router = useRouter()
-
const message = ref('')
//Override the message handler to capture the error message and display it
const { apiCall } = configureApiCall(m => message.value = m)
-//If logged-in redirect to login page
-if (loggedIn.value) {
- router.push({ name: 'Login' })
-}
-
+//Set the page title
+store.setPageTitle('Social Login')
-const run = async () => {
- if (isEqual(result.value, 'authorized')) {
+tryOnMounted(() => defer(() => {
- let loginUrl : string = ''
+ //If logged-in redirect to login page
+ if (loggedIn.value) {
+ router.push({ name: 'Login' })
+ }
- switch (type.value) {
- case 'github':
- loginUrl = '/login/social/github';
- break;
- case 'discord':
- loginUrl = '/login/social/discord';
- break;
- default:
+ //try to complete an oauth login
+ apiCall(async ({ toaster }) => {
+ try{
+ //Complete the login
+ await store.socialOauth.completeLogin();
+
+ toaster.general.success({
+ title:'Login Successful',
+ text: 'You have successfully logged in.'
+ })
+
router.push({ name: 'Login' })
- break;
}
-
- // If nonce is set, then we can proceed with finalization
- await apiCall(async ({ axios }) => {
- const preppedLogin = await prepareLogin()
- // Send the login request
- const { data } = await axios.post<ITokenResponse>(loginUrl, { nonce: nonce.value })
-
- data.getResultOrThrow()
-
- // Finalize the login
- await preppedLogin.finalize(data)
-
- // If the login was successful, then we can redirect to the login page
- router.push({ name: 'Login' })
- })
-
- } else {
- switch (result.value) {
- case 'invalid':
- message.value = 'The request was invalid, and you could not be logged in. Please try again.'
- break
- case 'expired':
- message.value = 'The request has expired. Please try again.'
- break
- default:
- message.value = 'There was an error processing the request. Please try again.'
- break
+ catch(err: any){
+ set(message, err.message)
}
- }
-}
-
-//Run without awaiting
-run()
+ })
+}))
</script>
diff --git a/front-end/src/views/[...all].vue b/front-end/src/views/[...all].vue
index 1f439fc..37439d1 100644
--- a/front-end/src/views/[...all].vue
+++ b/front-end/src/views/[...all].vue
@@ -11,9 +11,10 @@
</template>
<script setup lang="ts">
+import { useStore } from '../store';
-import { useTitle } from '@vnuge/vnlib.browser'
-useTitle('404 - Resource not found')
+const { setPageTitle } = useStore()
+setPageTitle('404 - Resource not found')
</script>
diff --git a/front-end/src/views/index.vue b/front-end/src/views/index.vue
index 07b0a09..ff4046e 100644
--- a/front-end/src/views/index.vue
+++ b/front-end/src/views/index.vue
@@ -11,8 +11,4 @@ import { useRouter } from 'vue-router';
const { push } = useRouter();
push('/blog')
-</script>
-
-<style lang="scss">
-
-</style> \ No newline at end of file
+</script> \ No newline at end of file