feat(frontend): Get project running again after move to Mac Mini
This commit is contained in:
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
@@ -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"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
@@ -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: '/' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
console.log('[3] Client-side navigation, fetching session')
|
||||||
|
try {
|
||||||
await fetchSession()
|
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)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
9
legalconsenthub/plugins/error-handler.ts
Normal file
9
legalconsenthub/plugins/error-handler.ts
Normal 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)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user