Files
gremiumhub/legalconsenthub/composables/useAuth.ts

185 lines
4.8 KiB
TypeScript

// Copied from https://github.com/atinux/nuxthub-better-auth
import { defu } from 'defu'
import { createAuthClient } from 'better-auth/vue'
import type { InferSessionFromClient, InferUserFromClient, ClientOptions } from 'better-auth/client'
import { organizationClient, jwtClient } from 'better-auth/client/plugins'
import type { RouteLocationRaw } from 'vue-router'
import type { UserDto } from '~/.api-client'
import type { RouteLocationNormalizedLoaded } from '#vue-router'
import {
accessControl,
employerRole,
worksCouncilMemberRole,
employeeRole,
adminRole,
ownerRole,
ROLES
} from '~/server/utils/permissions'
interface RuntimeAuthConfig {
redirectUserTo: RouteLocationRaw | string
redirectGuestTo: RouteLocationRaw | string
}
// TODO: Move into pinia store
const session = ref<InferSessionFromClient<ClientOptions> | null>(null)
const user = ref<InferUserFromClient<ClientOptions> | null>(null)
const sessionFetching = import.meta.server ? ref(false) : ref(false)
const jwt = ref<string | null>(null)
const organizations = ref<
{ id: string; name: string; createdAt: Date; slug: string; metadata?: any; logo?: string | null }[]
>([])
const selectedOrganization = ref<{
id: string
name: string
createdAt: Date
slug: string
metadata?: any
logo?: string | null
} | null>(null)
const activeMember = ref<{ role: string } | null>(null)
export function useAuth() {
const url = useRequestURL()
const route = useRoute()
const headers = import.meta.server ? useRequestHeaders() : undefined
const client = createAuthClient({
baseURL: url.origin,
fetchOptions: {
headers
},
user: {
deleteUser: {
enabled: true
}
},
plugins: [
organizationClient({
// Pass the same access control instance and roles to client
ac: accessControl,
roles: {
[ROLES.EMPLOYER]: employerRole,
[ROLES.WORKS_COUNCIL_MEMBER]: worksCouncilMemberRole,
[ROLES.EMPLOYEE]: employeeRole,
[ROLES.ADMIN]: adminRole,
[ROLES.OWNER]: ownerRole
}
}),
jwtClient()
]
})
const options = defu(useRuntimeConfig().public.auth as Partial<RuntimeAuthConfig>, {
redirectUserTo: '/',
redirectGuestTo: '/login'
})
async function fetchSession() {
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
// Only fetch JWT and organizations if we have a session and not on public routes
if (session.value && !isPublicRoute()) {
await fetchJwtAndOrganizations()
}
return data
}
async function fetchJwtAndOrganizations() {
// Fetch JWT
const tokenResult = await client.token()
jwt.value = tokenResult.data?.token ?? null
// Fetch organization
const orgResult = await client.organization.list({
fetchOptions: {
headers
}
})
organizations.value = orgResult.data ?? []
if (!selectedOrganization.value && organizations.value.length > 0) {
selectedOrganization.value = organizations.value[0]
}
// Fetch active member
const activeMemberResult = await client.organization.getActiveMember({
fetchOptions: {
headers
}
})
activeMember.value = activeMemberResult.data || null
}
watch(
() => selectedOrganization.value,
async (newValue) => {
if (newValue) {
await client.organization.setActive({
organizationId: newValue?.id
})
}
}
)
if (import.meta.client) {
client.$store.listen('$sessionSignal', async (signal) => {
if (!signal) return
await fetchSession()
})
}
function isPublicRoute(routeToCheck?: RouteLocationNormalizedLoaded) {
const finalRoute = routeToCheck ?? route
const publicRoutes = ['/login', '/signup', '/accept-invitation']
return publicRoutes.some((path) => finalRoute.path.startsWith(path))
}
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, { external: true })
}
return res
}
return {
session,
user,
loggedIn: computed(() => !!session.value),
signIn: client.signIn,
signUp: client.signUp,
deleteUser: client.deleteUser,
signOut,
organization: client.organization,
organizations,
selectedOrganization,
options,
fetchSession,
client,
jwt,
isPublicRoute,
activeMember
}
}