feat(frontend): Get project running again after move to Mac Mini

This commit is contained in:
2025-05-31 10:14:36 +02:00
parent d553668893
commit 075847f0ee
10 changed files with 86 additions and 28 deletions

2
.vscode/launch.json vendored
View File

@@ -7,7 +7,7 @@
"type": "chrome", "type": "chrome",
"request": "launch", "request": "launch",
"name": "client: chrome", "name": "client: chrome",
"url": "http://192.168.178.105:3001", "url": "http://192.168.178.114:3001",
"webRoot": "${workspaceFolder}/legalconsenthub" "webRoot": "${workspaceFolder}/legalconsenthub"
}, },
{ {

View File

@@ -36,7 +36,7 @@ class SecurityConfig {
@Bean @Bean
fun jwtDecoder(): JwtDecoder { fun jwtDecoder(): JwtDecoder {
return NimbusJwtDecoder.withJwkSetUri("http://192.168.178.105:3001/api/auth/jwks") return NimbusJwtDecoder.withJwkSetUri("http://192.168.178.114:3001/api/auth/jwks")
.jwsAlgorithm(SignatureAlgorithm.ES512).build() .jwsAlgorithm(SignatureAlgorithm.ES512).build()
} }
} }

View File

@@ -7,8 +7,8 @@
BETTER_AUTH_URL=http://localhost:3000 BETTER_AUTH_URL=http://localhost:3000
BETTER_AUTH_SECRET=YOUR_SECRET BETTER_AUTH_SECRET=YOUR_SECRET
``` ```
2. Generate database schema: `npx @better-auth/cli generate` 2. Generate database schema: `pnpm dlx @better-auth/cli generate`
3. Migrate schema: `npx @better-auth/cli migrate` 3. Migrate schema: `pnpm dlx @better-auth/cli migrate`
## Common errors ## Common errors
@@ -31,3 +31,9 @@ rm -fr node_modules; pnpm store prune
``` ```
https://github.com/elizaOS/eliza/pull/665 https://github.com/elizaOS/eliza/pull/665
### Unauthorized /token and /organization/list endpoints
User needs to be logged in to access these endpoints.
https://www.better-auth.com/docs/plugins/organization#accept-invitation

View File

@@ -6,6 +6,8 @@ create table "account" ("id" text not null primary key, "accountId" text not nul
create table "verification" ("id" text not null primary key, "identifier" text not null, "value" text not null, "expiresAt" date not null, "createdAt" date, "updatedAt" date); create table "verification" ("id" text not null primary key, "identifier" text not null, "value" text not null, "expiresAt" date not null, "createdAt" date, "updatedAt" date);
create table "jwks" ("id" text not null primary key, "publicKey" text not null, "privateKey" text not null, "createdAt" date not null);
create table "organization" ("id" text not null primary key, "name" text not null, "slug" text not null unique, "logo" text, "createdAt" date not null, "metadata" text); create table "organization" ("id" text not null primary key, "name" text not null, "slug" text not null unique, "logo" text, "createdAt" date not null, "metadata" text);
create table "member" ("id" text not null primary key, "organizationId" text not null references "organization" ("id"), "userId" text not null references "user" ("id"), "role" text not null, "createdAt" date not null); create table "member" ("id" text not null primary key, "organizationId" text not null references "organization" ("id"), "userId" text not null references "user" ("id"), "role" text not null, "createdAt" date not null);

View File

@@ -178,7 +178,7 @@ const items = computed<DropdownMenuItem[][]>(() => [
icon: 'i-lucide-log-out', icon: 'i-lucide-log-out',
async onSelect(e: Event) { async onSelect(e: Event) {
e.preventDefault() e.preventDefault()
signOut() await signOut({ redirectTo: '/' })
} }
} }
] ]

View File

@@ -1,11 +1,12 @@
// Copied from https://github.com/atinux/nuxthub-better-auth // Copied from https://github.com/atinux/nuxthub-better-auth
import { defu } from 'defu' import { defu } from 'defu'
import { createAuthClient } from 'better-auth/client' import { createAuthClient } from 'better-auth/vue'
import type { InferSessionFromClient, InferUserFromClient, ClientOptions } from 'better-auth/client' import type { InferSessionFromClient, InferUserFromClient, ClientOptions } from 'better-auth/client'
import { organizationClient, jwtClient } from 'better-auth/client/plugins' import { organizationClient, jwtClient } from 'better-auth/client/plugins'
import type { RouteLocationRaw } from 'vue-router' import type { RouteLocationRaw } from 'vue-router'
import type { UserDto } from '~/.api-client' import type { UserDto } from '~/.api-client'
import type { RouteLocationNormalizedLoaded } from '#vue-router'
interface RuntimeAuthConfig { interface RuntimeAuthConfig {
redirectUserTo: RouteLocationRaw | string redirectUserTo: RouteLocationRaw | string
@@ -30,6 +31,7 @@ const selectedOrganization = ref<{
export function useAuth() { export function useAuth() {
const url = useRequestURL() const url = useRequestURL()
const route = useRoute()
const headers = import.meta.server ? useRequestHeaders() : undefined const headers = import.meta.server ? useRequestHeaders() : undefined
const client = createAuthClient({ const client = createAuthClient({
@@ -60,18 +62,32 @@ export function useAuth() {
user.value = data?.user || null user.value = data?.user || null
sessionFetching.value = false sessionFetching.value = false
// Fetch JWT - workaround for not working extraction of JWT out of session (https://github.com/better-auth/better-auth/issues/1835) // Only fetch JWT and organizations if we have a session and not on public routes
jwt.value = (await client.token()).data?.token ?? null if (session.value && !isPublicRoute()) {
await fetchJwtAndOrganizations()
// Fetch organization
organizations.value = (await client.organization.list()).data ?? []
if (!selectedOrganization.value && organizations.value.length > 0) {
selectedOrganization.value = organizations.value[0]
} }
return data 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]
}
}
watch( watch(
() => selectedOrganization.value, () => selectedOrganization.value,
async (newValue) => { async (newValue) => {
@@ -90,6 +106,12 @@ export function useAuth() {
}) })
} }
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 } = {}) { async function signOut({ redirectTo }: { redirectTo?: RouteLocationRaw } = {}) {
const res = await client.signOut() const res = await client.signOut()
if (res.error) { if (res.error) {
@@ -99,7 +121,7 @@ export function useAuth() {
session.value = null session.value = null
user.value = null user.value = null
if (redirectTo) { if (redirectTo) {
await navigateTo(redirectTo) await navigateTo(redirectTo, { external: true })
} }
return res return res
} }
@@ -122,7 +144,9 @@ export function useAuth() {
selectedOrganization, selectedOrganization,
options, options,
fetchSession, fetchSession,
fetchJwtAndOrganizations,
client, client,
jwt jwt,
isPublicRoute
} }
} }

View File

@@ -1,6 +1,7 @@
// Copied from https://github.com/atinux/nuxthub-better-auth // Copied from https://github.com/atinux/nuxthub-better-auth
import { defu } from 'defu' import { defu } from 'defu'
import type { RouteLocationNormalized } from '#vue-router'
type MiddlewareOptions = type MiddlewareOptions =
| false | false
@@ -31,33 +32,44 @@ declare module 'vue-router' {
} }
} }
export default defineNuxtRouteMiddleware(async (to) => { export default defineNuxtRouteMiddleware(async (to: RouteLocationNormalized) => {
// If auth is disabled, skip middleware // 1. If auth is disabled, skip middleware
if (to.meta?.auth === false) { if (to.meta?.auth === false) {
console.log('[1] Auth middleware disabled for this route:', to.path)
return return
} }
const { loggedIn, options, fetchSession } = useAuth() const { loggedIn, options, fetchSession, isPublicRoute } = useAuth()
const { only, redirectUserTo, redirectGuestTo } = defu(to.meta?.auth, options) const { only, redirectUserTo, redirectGuestTo } = defu(to.meta?.auth, options)
// If guest mode, redirect if authenticated // 2. If guest mode, redirect if authenticated
if (only === 'guest' && loggedIn.value) { if (only === 'guest' && loggedIn.value) {
// Avoid infinite redirect console.log('[2] Guest mode: user is authenticated, redirecting to', redirectUserTo)
if (to.path === redirectUserTo) { if (to.path === redirectUserTo) {
console.log('[2.1] Already at redirectUserTo:', redirectUserTo)
return return
} }
return navigateTo(redirectUserTo) return navigateTo(redirectUserTo)
} }
// If client-side, fetch session between each navigation // 3. If client-side, fetch session between each navigation
if (import.meta.client) { if (import.meta.client) {
await fetchSession() console.log('[3] Client-side navigation, fetching session')
try {
await fetchSession()
} catch (e) {
console.error(e)
}
} }
// If not authenticated, redirect to home
// 4. If not authenticated, redirect to home or guest route
if (!loggedIn.value) { if (!loggedIn.value) {
// Avoid infinite redirect if (isPublicRoute(to)) {
if (to.path === redirectGuestTo) { console.log('[4] Not authenticated, but route is public:', to.path)
// Continue navigating to the public route
return return
} }
// No public route, redirect to guest route
console.log('[4.1] Not authenticated, redirecting to guest route:', redirectGuestTo)
return navigateTo(redirectGuestTo) return navigateTo(redirectGuestTo)
} }
}) })

View File

@@ -7,13 +7,14 @@
"dev": "nuxt dev --port 3001 --host", "dev": "nuxt dev --port 3001 --host",
"generate": "nuxt generate", "generate": "nuxt generate",
"preview": "nuxt preview", "preview": "nuxt preview",
"postinstall": "nuxt prepare && pnpm run fix:bettersqlite", "postinstall": "nuxt prepare && pnpm run fix:bettersqlite && pnpm run api:generate",
"format": "prettier . --write", "format": "prettier . --write",
"type-check": "nuxi typecheck", "type-check": "nuxi typecheck",
"lint": "eslint .", "lint": "eslint .",
"lint:fix": "eslint . --fix", "lint:fix": "eslint . --fix",
"api:generate": "openapi-generator-cli generate -i ../legalconsenthub-backend/api/legalconsenthub.yml -g typescript-fetch -o .api-client", "api:generate": "openapi-generator-cli generate -i ../legalconsenthub-backend/api/legalconsenthub.yml -g typescript-fetch -o .api-client",
"fix:bettersqlite": "cd node_modules/better-sqlite3 && pnpm dlx node-gyp rebuild && cd ../..", "fix:bettersqlite": "cd node_modules/better-sqlite3 && pnpm dlx node-gyp rebuild && cd ../..",
"generate:betterauth": "pnpm dlx @better-auth/cli generate --config server/utils/auth.ts",
"migrate:betterauth": "pnpm dlx @better-auth/cli migrate --config server/utils/auth.ts" "migrate:betterauth": "pnpm dlx @better-auth/cli migrate --config server/utils/auth.ts"
}, },
"dependencies": { "dependencies": {
@@ -35,5 +36,9 @@
"prettier": "3.5.1", "prettier": "3.5.1",
"typescript": "5.7.3", "typescript": "5.7.3",
"vue-tsc": "2.2.2" "vue-tsc": "2.2.2"
},
"volta": {
"node": "22.16.0",
"pnpm": "10.11.0"
} }
} }

View File

@@ -0,0 +1,9 @@
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.hook('vue:error', (error, instance, info) => {
console.error('Vue error:', error, 'Instance:', instance, 'Info:', info)
})
nuxtApp.hook('app:error', (error) => {
console.error('App error:', error)
})
})

View File

@@ -9,7 +9,7 @@ export const auth = betterAuth({
plugins: [ plugins: [
jwt({ jwt({
jwt: { jwt: {
issuer: 'http://192.168.178.105:3001', issuer: 'http://192.168.178.114:3001',
expirationTime: '48h' expirationTime: '48h'
}, },
jwks: { jwks: {
@@ -22,7 +22,7 @@ export const auth = betterAuth({
organization({ organization({
async sendInvitationEmail(data) { async sendInvitationEmail(data) {
console.log('Sending invitation email', data) console.log('Sending invitation email', data)
const inviteLink = `http://192.168.178.105:3001/accept-invitation/${data.id}` const inviteLink = `http://192.168.178.114:3001/accept-invitation/${data.id}`
await resend.emails.send({ await resend.emails.send({
from: 'Acme <onboarding@resend.dev>', from: 'Acme <onboarding@resend.dev>',
to: data.email, to: data.email,