diff --git a/legalconsenthub/components/FormEngine.vue b/legalconsenthub/components/FormEngine.vue index 9e279fe..a722809 100644 --- a/legalconsenthub/components/FormEngine.vue +++ b/legalconsenthub/components/FormEngine.vue @@ -4,6 +4,7 @@ @@ -17,6 +18,7 @@ import { resolveComponent } from 'vue' const props = defineProps<{ modelValue: FormElementDto[] + disabled?: boolean }>() const emit = defineEmits<{ diff --git a/legalconsenthub/components/UserMenu.vue b/legalconsenthub/components/UserMenu.vue index b2eca77..6bfe1c9 100644 --- a/legalconsenthub/components/UserMenu.vue +++ b/legalconsenthub/components/UserMenu.vue @@ -60,13 +60,13 @@ const colors = [ ] const neutrals = ['slate', 'gray', 'zinc', 'neutral', 'stone'] -const { data: session } = await useSession(useFetch) +const { user: betterAuthUser, signOut } = await useAuth() const user = ref({ - name: session?.value?.user?.name, + name: betterAuthUser.value?.name, avatar: { src: '/_nuxt/public/favicon.ico', - alt: session?.value?.user?.name + alt: betterAuthUser.value?.name } }) @@ -115,7 +115,6 @@ const items = computed(() => [ type: 'checkbox', onSelect: (e) => { e.preventDefault() - appConfig.ui.colors.primary = color } })) @@ -136,7 +135,6 @@ const items = computed(() => [ checked: appConfig.ui.colors.neutral === color, onSelect: (e) => { e.preventDefault() - appConfig.ui.colors.neutral = color } })) @@ -154,7 +152,6 @@ const items = computed(() => [ checked: colorMode.value === 'light', onSelect(e: Event) { e.preventDefault() - colorMode.preference = 'light' } }, @@ -182,7 +179,6 @@ const items = computed(() => [ async onSelect(e: Event) { e.preventDefault() signOut() - await navigateTo('/login') } } ] diff --git a/legalconsenthub/composables/useAuth.ts b/legalconsenthub/composables/useAuth.ts new file mode 100644 index 0000000..2aeb195 --- /dev/null +++ b/legalconsenthub/composables/useAuth.ts @@ -0,0 +1,84 @@ +// Copied from https://github.com/atinux/nuxthub-better-auth + +import { defu } from 'defu' +import { createAuthClient } from 'better-auth/client' +import type { InferSessionFromClient, InferUserFromClient, ClientOptions } from 'better-auth/client' +import { organizationClient } from 'better-auth/client/plugins' +import type { RouteLocationRaw } from 'vue-router' + +interface RuntimeAuthConfig { + redirectUserTo: RouteLocationRaw | string + redirectGuestTo: RouteLocationRaw | string +} + +export function useAuth() { + const url = useRequestURL() + const headers = import.meta.server ? useRequestHeaders() : undefined + + const client = createAuthClient({ + baseURL: url.origin, + fetchOptions: { + headers + }, + plugins: [organizationClient()] + }) + + const options = defu(useRuntimeConfig().public.auth as Partial, { + redirectUserTo: '/', + redirectGuestTo: '/login' + }) + const session = useState | null>('auth:session', () => null) + const user = useState | null>('auth:user', () => null) + const sessionFetching = import.meta.server ? ref(false) : useState('auth:sessionFetching', () => false) + + const fetchSession = async () => { + if (sessionFetching.value) { + console.log('already fetching session') + return + } + sessionFetching.value = true + const { data } = await client.getSession({ + fetchOptions: { + headers + } + }) + session.value = data?.session || null + user.value = data?.user || null + sessionFetching.value = false + return data + } + + if (import.meta.client) { + client.$store.listen('$sessionSignal', async (signal) => { + if (!signal) return + await fetchSession() + }) + } + + async function signOut({ redirectTo }: { redirectTo?: RouteLocationRaw } = {}) { + const res = await client.signOut() + if (res.error) { + console.error('Error signing out:', res.error) + return res + } + session.value = null + user.value = null + if (redirectTo) { + await navigateTo(redirectTo) + } + return res + } + + return { + session, + user, + loggedIn: computed(() => !!session.value), + signIn: client.signIn, + signUp: client.signUp, + signOut, + organization: client.organization, + options, + fetchSession, + client + } +} diff --git a/legalconsenthub/composables/useBetterAuth.ts b/legalconsenthub/composables/useBetterAuth.ts index c5f761a..2e36229 100644 --- a/legalconsenthub/composables/useBetterAuth.ts +++ b/legalconsenthub/composables/useBetterAuth.ts @@ -3,6 +3,7 @@ const selectedOrgId = ref(undefined) export function useBetterAuth() { const toast = useToast() + const { organization } = useAuth() async function createOrganization(name: string, slug: string, logo?: string) { await organization.create( @@ -25,7 +26,7 @@ export function useBetterAuth() { } async function deleteOrganization() { - await authClient.organization.delete( + await organization.delete( { organizationId: activeOrganization.value?.id ?? '' }, { onSuccess: () => { @@ -41,7 +42,7 @@ export function useBetterAuth() { } async function getInvitation(invitationId: string): Promise { - return authClient.organization.getInvitation({ + return organization.getInvitation({ query: { id: invitationId }, fetchOptions: { throw: true, @@ -56,7 +57,7 @@ export function useBetterAuth() { } async function inviteMember(email: string, role: 'member' | 'admin') { - await authClient.organization.inviteMember({ + await organization.inviteMember({ email, role, fetchOptions: { @@ -77,7 +78,7 @@ export function useBetterAuth() { } async function acceptInvitation(invitationId: string) { - await authClient.organization.acceptInvitation({ + await organization.acceptInvitation({ invitationId, fetchOptions: { throw: true, @@ -93,7 +94,7 @@ export function useBetterAuth() { } async function rejectInvitation(invitationId: string) { - await authClient.organization.rejectInvitation({ + await organization.rejectInvitation({ invitationId, fetchOptions: { throw: true, diff --git a/legalconsenthub/middleware/auth.global.ts b/legalconsenthub/middleware/auth.global.ts new file mode 100644 index 0000000..d7ea141 --- /dev/null +++ b/legalconsenthub/middleware/auth.global.ts @@ -0,0 +1,63 @@ +// Copied from https://github.com/atinux/nuxthub-better-auth + +import { defu } from 'defu' + +type MiddlewareOptions = + | false + | { + /** + * Only apply auth middleware to guest or user + */ + only?: 'guest' | 'user' + /** + * Redirect authenticated user to this route + */ + redirectUserTo?: string + /** + * Redirect guest to this route + */ + redirectGuestTo?: string + } + +declare module '#app' { + interface PageMeta { + auth?: MiddlewareOptions + } +} + +declare module 'vue-router' { + interface RouteMeta { + auth?: MiddlewareOptions + } +} + +export default defineNuxtRouteMiddleware(async (to) => { + // If auth is disabled, skip middleware + if (to.meta?.auth === false) { + return + } + const { loggedIn, options, fetchSession } = useAuth() + const { only, redirectUserTo, redirectGuestTo } = defu(to.meta?.auth, options) + + // If guest mode, redirect if authenticated + if (only === 'guest' && loggedIn.value) { + // Avoid infinite redirect + if (to.path === redirectUserTo) { + return + } + return navigateTo(redirectUserTo) + } + + // If client-side, fetch session between each navigation + if (import.meta.client) { + await fetchSession() + } + // If not authenticated, redirect to home + if (!loggedIn.value) { + // Avoid infinite redirect + if (to.path === redirectGuestTo) { + return + } + return navigateTo(redirectGuestTo) + } +}) diff --git a/legalconsenthub/middleware/auth.ts b/legalconsenthub/middleware/auth.ts deleted file mode 100644 index 3136d33..0000000 --- a/legalconsenthub/middleware/auth.ts +++ /dev/null @@ -1,7 +0,0 @@ -export default defineNuxtRouteMiddleware(async (_to, _from) => { - const { data: session } = await useSession(useFetch) - - if (!session.value) { - return navigateTo('/login') - } -}) diff --git a/legalconsenthub/nuxt.config.ts b/legalconsenthub/nuxt.config.ts index 5c26590..9dba81d 100644 --- a/legalconsenthub/nuxt.config.ts +++ b/legalconsenthub/nuxt.config.ts @@ -6,7 +6,11 @@ export default defineNuxtConfig({ public: { clientProxyBasePath: 'NOT_SET', serverApiBaseUrl: 'NOT_SET', - serverApiBasePath: 'NOT_SET' + serverApiBasePath: 'NOT_SET', + auth: { + redirectUserTo: '/', + redirectGuestTo: '/login' + } } }, components: [ diff --git a/legalconsenthub/pages/administration.vue b/legalconsenthub/pages/administration.vue index 1873932..62f5e25 100644 --- a/legalconsenthub/pages/administration.vue +++ b/legalconsenthub/pages/administration.vue @@ -69,16 +69,16 @@

{{ member.role }}

-
+
- {{ member.user.id === session.id ? 'Leave' : 'Remove' }} + {{ member.user.id === user.id ? 'Leave' : 'Remove' }}
- +
-

{{ session?.name }}

+

{{ user?.name }}

Owner

@@ -141,7 +141,8 @@ import { useClipboard } from '@vueuse/core' const { copy, copied } = useClipboard() const toast = useToast() -const organizations = computed(() => useListOrganizations().value.data || []) +const { organization, client } = useAuth() +const organizations = computed(() => client.useListOrganizations.value?.data || []) const { deleteOrganization: betterAuthDeleteOrganization, activeOrganization, selectedOrgId } = useBetterAuth() const selectItems = computed(() => organizations.value.map((org) => ({ label: org.name, value: org.id }))) @@ -151,8 +152,7 @@ watch(selectedOrgId, async (newId) => { activeOrganization.value = data }) -const { data: sessionData } = useSession().value -const session = computed(() => sessionData?.user) +const { user } = await useAuth() const isRevoking = ref([]) diff --git a/legalconsenthub/pages/application-forms/[id].vue b/legalconsenthub/pages/application-forms/[id].vue index 7734b4f..576e306 100644 --- a/legalconsenthub/pages/application-forms/[id].vue +++ b/legalconsenthub/pages/application-forms/[id].vue @@ -22,8 +22,8 @@
- - Submit + + Submit
@@ -33,8 +33,10 @@