185 lines
4.8 KiB
TypeScript
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
|
|
}
|
|
}
|