aboutsummaryrefslogtreecommitdiff
path: root/front-end/src/store
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/store
parentb564708f29cf8a709c3e3d981477b2ec8440673e (diff)
log time coming ui and lib updates
Diffstat (limited to 'front-end/src/store')
-rw-r--r--front-end/src/store/globalState.ts30
-rw-r--r--front-end/src/store/index.ts49
-rw-r--r--front-end/src/store/mfaSettingsPlugin.ts62
-rw-r--r--front-end/src/store/pageProtectionPlugin.ts79
-rw-r--r--front-end/src/store/socialMfaPlugin.ts42
-rw-r--r--front-end/src/store/userProfile.ts82
6 files changed, 344 insertions, 0 deletions
diff --git a/front-end/src/store/globalState.ts b/front-end/src/store/globalState.ts
new file mode 100644
index 0000000..9e700eb
--- /dev/null
+++ b/front-end/src/store/globalState.ts
@@ -0,0 +1,30 @@
+import 'pinia'
+import { shallowRef } from 'vue';
+import { useAutoHeartbeat } from '@vnuge/vnlib.browser';
+import { toRefs, useLocalStorage } from '@vueuse/core';
+import { PiniaPluginContext, PiniaPlugin } from 'pinia'
+import { defaults } from 'lodash-es';
+
+declare module 'pinia' {
+ export interface PiniaCustomProperties {
+ autoHeartbeat: boolean;
+ }
+}
+
+export const globalStatePlugin: PiniaPlugin = ({ store }: PiniaPluginContext) =>{
+
+ //Get shared global state storage
+ const mainState = useLocalStorage("vn-state", { ahEnabled: false });
+
+ //Set defaults to avoid undefined errors from toRefs
+ defaults(mainState.value, { ahEnabled: false })
+
+ const { ahEnabled } = toRefs(mainState)
+
+ //Setup heartbeat for 5 minutes
+ useAutoHeartbeat(shallowRef(5 * 60 * 1000), ahEnabled)
+
+ return{
+ autoHeartbeat: ahEnabled,
+ }
+} \ No newline at end of file
diff --git a/front-end/src/store/index.ts b/front-end/src/store/index.ts
new file mode 100644
index 0000000..924bf1b
--- /dev/null
+++ b/front-end/src/store/index.ts
@@ -0,0 +1,49 @@
+import { useSession } from "@vnuge/vnlib.browser";
+import { set } from "@vueuse/core";
+import { defineStore } from "pinia";
+import { computed, shallowRef } from "vue";
+
+/**
+ * Loads the main store for the application
+ */
+export const useStore = defineStore('main', () => {
+
+ const { loggedIn, isLocalAccount } = useSession();
+
+ //MANAGED STATE
+ const headerRoutes = shallowRef(Array<string>());
+ const authRoutes = shallowRef(Array<string>());
+ const siteTitle = shallowRef("");
+ const pageTitle = shallowRef("");
+ const showCookieWarning = shallowRef(!navigator.cookieEnabled); //Default to current cookie status
+
+ /**
+ * The current routes to display in the header depending on the
+ * user's login status
+ */
+ const currentRoutes = computed(() => loggedIn.value ? authRoutes.value : headerRoutes.value);
+
+ const setHeaderRouteNames = (routeNames: string[], authRouteNames: string[]) => {
+ set(headerRoutes, [...routeNames]);
+ set(authRoutes, [...authRouteNames]);
+ }
+
+ const setSiteTitle = (title: string) => set(siteTitle, title);
+ const setPageTitle = (title: string) => set(pageTitle, title);
+ const setCookieWarning = (show: boolean) => set(showCookieWarning, show);
+
+ return{
+ loggedIn,
+ isLocalAccount,
+ headerRoutes,
+ authRoutes,
+ siteTitle,
+ pageTitle,
+ showCookieWarning,
+ setCookieWarning,
+ setPageTitle,
+ currentRoutes,
+ setHeaderRouteNames,
+ setSiteTitle
+ }
+})
diff --git a/front-end/src/store/mfaSettingsPlugin.ts b/front-end/src/store/mfaSettingsPlugin.ts
new file mode 100644
index 0000000..dffafce
--- /dev/null
+++ b/front-end/src/store/mfaSettingsPlugin.ts
@@ -0,0 +1,62 @@
+import 'pinia'
+import { MaybeRef, shallowRef, watch } from 'vue';
+import { MfaMethod, PkiPublicKey, apiCall, useMfaConfig, usePkiConfig, usePkiAuth } from '@vnuge/vnlib.browser';
+import { useToggle, get } from '@vueuse/core';
+import { PiniaPluginContext, PiniaPlugin, storeToRefs } from 'pinia'
+import { includes } from 'lodash-es';
+
+declare module 'pinia' {
+ export interface PiniaCustomProperties {
+ mfaEndabledMethods: MfaMethod[]
+ mfaConfig: ReturnType<typeof useMfaConfig>
+ pkiConfig: ReturnType<typeof usePkiConfig>
+ pkiAuth: ReturnType<typeof usePkiAuth>
+ pkiPublicKeys: PkiPublicKey[]
+ mfaRefreshMethods: () => void
+ }
+}
+
+export const mfaSettingsPlugin = (mfaEndpoint: MaybeRef<string>, pkiEndpoint?:MaybeRef<string>): PiniaPlugin => {
+
+ return ({ store }: PiniaPluginContext) => {
+
+ const { loggedIn } = storeToRefs(store)
+ const mfaConfig = useMfaConfig(mfaEndpoint)
+ const pkiConfig = usePkiConfig(pkiEndpoint || '/')
+ const pkiAuth = usePkiAuth(pkiEndpoint || '/')
+ const [onRefresh, mfaRefreshMethods] = useToggle()
+
+ const mfaEndabledMethods = shallowRef<MfaMethod[]>([])
+ const pkiPublicKeys = shallowRef<PkiPublicKey[]>([])
+
+ watch([loggedIn, onRefresh], ([ li ]) => {
+ if(!li){
+ mfaEndabledMethods.value = []
+ return
+ }
+
+ //load the mfa methods if the user is logged in
+ apiCall(async () => mfaEndabledMethods.value = await mfaConfig.getMethods())
+ })
+
+ //Watch for changes to mfa methods (refresh) and update the pki keys
+ watch([mfaEndabledMethods], ([ methods ]) => {
+ if(!includes(methods, 'pki' as MfaMethod) || !get(pkiEndpoint)){
+ pkiPublicKeys.value = []
+ return
+ }
+
+ //load the pki keys if pki is enabled
+ apiCall(async () => pkiPublicKeys.value = await pkiConfig.getAllKeys())
+ })
+
+ return{
+ mfaRefreshMethods,
+ mfaEndabledMethods,
+ mfaConfig,
+ pkiConfig,
+ pkiAuth,
+ pkiPublicKeys
+ }
+ }
+} \ No newline at end of file
diff --git a/front-end/src/store/pageProtectionPlugin.ts b/front-end/src/store/pageProtectionPlugin.ts
new file mode 100644
index 0000000..9831dad
--- /dev/null
+++ b/front-end/src/store/pageProtectionPlugin.ts
@@ -0,0 +1,79 @@
+import 'pinia'
+import { watch } from 'vue';
+import { } from '@vnuge/vnlib.browser';
+import { useSessionStorage, get, set } from '@vueuse/core';
+import { PiniaPluginContext, PiniaPlugin, storeToRefs } from 'pinia'
+import { includes, startsWith } from 'lodash-es';
+import { useRouter } from 'vue-router';
+
+declare module 'pinia' {
+ export interface PiniaCustomProperties {
+
+ }
+}
+
+export const pageProtectionPlugin = (router: ReturnType<typeof useRouter>): PiniaPlugin => {
+
+ return ({ store }: PiniaPluginContext) => {
+
+ const { loggedIn, headerRoutes } = storeToRefs(store)
+
+ const lastPageStore = useSessionStorage('lastPageStore', undefined)
+
+ //Setup nav guards
+ router.beforeEach((to, from) => {
+ if(!to.name){
+ return true;
+ }
+
+ //see if to route is login route
+ if(startsWith(to.name as string, 'Login')){
+
+ //If the user is logged-in, redirect to the last page
+ if(get(loggedIn) === true){
+ const lastPath = get(lastPageStore);
+ set(lastPageStore, undefined) //Clear the last page
+
+ return lastPath ? { path: lastPath } : true;
+ }
+ else{
+ //Set last page as from page
+ set(lastPageStore, from.fullPath)
+ }
+ return true;
+ }
+
+ //See if the to route is not in allowed routes
+ if (!includes(get(headerRoutes), to.name as string)){
+ //Check if the user is logged in
+ if(get(loggedIn) === false){
+
+ //Save the last page
+ set(lastPageStore, to.fullPath)
+
+ //Redirect to login
+ return { name: 'Login' }
+ }
+ }
+
+ //Allow
+ return true;
+ })
+
+ router.afterEach(() => {
+ //scroll window back to top
+ window.scrollTo(0, 0)
+ })
+
+ watch(loggedIn, (loggedIn) => {
+ //If the user gets logged out, redirect to login
+ if(loggedIn === false && router.currentRoute.value.name !== 'Login'){
+ router.push({ name: 'Login' })
+ }
+ })
+
+ return {
+
+ }
+ }
+} \ No newline at end of file
diff --git a/front-end/src/store/socialMfaPlugin.ts b/front-end/src/store/socialMfaPlugin.ts
new file mode 100644
index 0000000..d328399
--- /dev/null
+++ b/front-end/src/store/socialMfaPlugin.ts
@@ -0,0 +1,42 @@
+
+import 'pinia'
+import { } from 'vue';
+import { useSocialOauthLogin, createSocialMethod, useUser } from '@vnuge/vnlib.browser'
+import { } from '@vueuse/core';
+import { PiniaPluginContext, PiniaPlugin, storeToRefs } from 'pinia'
+import { } from 'lodash-es';
+
+declare module 'pinia' {
+ export interface PiniaCustomProperties {
+ socialOauth: ReturnType<typeof useSocialOauthLogin>
+ }
+}
+
+export const socialMfaPlugin: PiniaPlugin = ({ store }: PiniaPluginContext) => {
+
+ const { } = storeToRefs(store)
+ const { logout } = useUser()
+
+ //Setup social providers
+ const socialOauth = useSocialOauthLogin([
+ //createSocialMethod('github', '/login/social/github'),
+ //createSocialMethod('discord', '/login/social/discord'),
+ ])
+
+ /**
+ * Override the logout function to default to a social logout,
+ * if the social logout fails, then we will logout the user
+ */
+
+ const logoutFunc = socialOauth.logout;
+
+ (socialOauth as any).logout = async () => {
+ if (await logoutFunc() === false) {
+ await logout()
+ }
+ }
+
+ return {
+ socialOauth
+ }
+} \ No newline at end of file
diff --git a/front-end/src/store/userProfile.ts b/front-end/src/store/userProfile.ts
new file mode 100644
index 0000000..a4ea469
--- /dev/null
+++ b/front-end/src/store/userProfile.ts
@@ -0,0 +1,82 @@
+import 'pinia'
+import { MaybeRef, watch } from 'vue';
+import { ServerDataBuffer, ServerObjectBuffer, UserProfile, WebMessage, apiCall, useAxios, useDataBuffer, useUser } from '@vnuge/vnlib.browser';
+import { get, useToggle } from '@vueuse/core';
+import { PiniaPlugin, PiniaPluginContext, storeToRefs } from 'pinia'
+import { defer } from 'lodash-es';
+
+export interface OAuth2Application {
+ readonly Id: string,
+ readonly name: string,
+ readonly description: string,
+ readonly permissions: string[],
+ readonly client_id: string,
+ readonly Created: Date,
+ readonly LastModified: Date,
+}
+
+export interface NewAppResponse {
+ readonly secret: string
+ readonly app: ServerDataBuffer<OAuth2Application, WebMessage<string>>
+}
+
+interface ExUserProfile extends UserProfile {
+ created: string | Date
+}
+
+declare module 'pinia' {
+ export interface PiniaCustomProperties {
+ userProfile: ServerDataBuffer<ExUserProfile, WebMessage<string>>
+ userName: string | undefined
+ refreshProfile(): void;
+ }
+}
+
+export const profilePlugin = (accountsUrl:MaybeRef<string>) :PiniaPlugin => {
+
+ return ({ store }: PiniaPluginContext) => {
+
+ const { loggedIn } = storeToRefs(store)
+ const { getProfile, userName } = useUser()
+ const axios = useAxios(null)
+
+ const [onRefresh, refreshProfile] = useToggle()
+
+ const updateUserProfile = async (profile: ServerObjectBuffer<ExUserProfile>) => {
+ // Apply the buffer to the profile
+ const { data } = await axios.post<WebMessage<string>>(get(accountsUrl), profile.buffer)
+
+ //Get the new profile from the server
+ const newProfile = await getProfile() as ExUserProfile
+
+ //Apply the new profile to the buffer
+ profile.apply(newProfile)
+
+ return data;
+ }
+
+ const userProfile = useDataBuffer({} as any, updateUserProfile)
+
+ const loadProfile = async () => {
+ //Get the user profile
+ const profile = await getProfile() as ExUserProfile
+ //Apply the profile to the buffer
+ userProfile.apply(profile)
+ }
+
+ watch([loggedIn, onRefresh], ([li]) => {
+ //If the user is logged in, load the profile buffer
+ if (li) {
+ apiCall(loadProfile)
+ }
+ })
+
+ defer(refreshProfile);
+
+ return {
+ userProfile,
+ refreshProfile,
+ userName
+ }
+ }
+} \ No newline at end of file