WIP - BFF

This commit is contained in:
Francisco Gaona
2026-02-04 00:21:06 +01:00
parent f68321c802
commit 0e2f3dddbc
17 changed files with 645 additions and 254 deletions

View File

@@ -0,0 +1,81 @@
import { defineEventHandler, readBody, createError } from 'h3'
import { getSubdomainFromRequest, isCentralSubdomain } from '~/server/utils/tenant'
import { setSessionCookie, setTenantIdCookie } from '~/server/utils/session'
export default defineEventHandler(async (event) => {
const config = useRuntimeConfig()
const body = await readBody(event)
// Extract subdomain from the request
const subdomain = getSubdomainFromRequest(event)
if (!subdomain) {
throw createError({
statusCode: 400,
statusMessage: 'Unable to determine tenant from subdomain',
})
}
// Determine the backend URL based on whether this is central admin or tenant
const isCentral = isCentralSubdomain(subdomain)
const backendUrl = config.backendUrl || 'http://localhost:3000'
const loginEndpoint = isCentral ? '/api/auth/central/login' : '/api/auth/login'
try {
// Forward login request to NestJS backend with subdomain header
const response = await fetch(`${backendUrl}${loginEndpoint}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-tenant-subdomain': subdomain,
},
body: JSON.stringify(body),
})
const data = await response.json()
if (!response.ok) {
throw createError({
statusCode: response.status,
statusMessage: data.message || 'Login failed',
data: data,
})
}
// Extract token and tenant info from response
const { access_token, user, tenantId } = data
if (!access_token) {
throw createError({
statusCode: 500,
statusMessage: 'No access token received from backend',
})
}
// Set HTTP-only cookie with the JWT token
setSessionCookie(event, access_token)
// Set tenant ID cookie (readable by client for context)
if (tenantId) {
setTenantIdCookie(event, tenantId)
}
// Return user info (but NOT the token - it's in HTTP-only cookie)
return {
success: true,
user,
tenantId,
}
} catch (error: any) {
// Re-throw H3 errors
if (error.statusCode) {
throw error
}
console.error('Login proxy error:', error)
throw createError({
statusCode: 500,
statusMessage: 'Failed to connect to authentication service',
})
}
})

View File

@@ -0,0 +1,37 @@
import { defineEventHandler, createError } from 'h3'
import { getSubdomainFromRequest } from '~/server/utils/tenant'
import { getSessionToken, clearSessionCookie, clearTenantIdCookie } from '~/server/utils/session'
export default defineEventHandler(async (event) => {
const config = useRuntimeConfig()
const subdomain = getSubdomainFromRequest(event)
const token = getSessionToken(event)
const backendUrl = config.backendUrl || 'http://localhost:3000'
try {
// Call backend logout endpoint if we have a token
if (token) {
await fetch(`${backendUrl}/api/auth/logout`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
...(subdomain && { 'x-tenant-subdomain': subdomain }),
},
})
}
} catch (error) {
// Log but don't fail - we still want to clear cookies
console.error('Backend logout error:', error)
}
// Always clear cookies regardless of backend response
clearSessionCookie(event)
clearTenantIdCookie(event)
return {
success: true,
message: 'Logged out successfully',
}
})

View File

@@ -0,0 +1,60 @@
import { defineEventHandler, createError } from 'h3'
import { getSubdomainFromRequest } from '~/server/utils/tenant'
import { getSessionToken } from '~/server/utils/session'
export default defineEventHandler(async (event) => {
const config = useRuntimeConfig()
const subdomain = getSubdomainFromRequest(event)
const token = getSessionToken(event)
if (!token) {
throw createError({
statusCode: 401,
statusMessage: 'Not authenticated',
})
}
const backendUrl = config.backendUrl || 'http://localhost:3000'
try {
// Fetch current user from backend
const response = await fetch(`${backendUrl}/api/auth/me`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
...(subdomain && { 'x-tenant-subdomain': subdomain }),
},
})
if (!response.ok) {
if (response.status === 401) {
throw createError({
statusCode: 401,
statusMessage: 'Session expired',
})
}
throw createError({
statusCode: response.status,
statusMessage: 'Failed to fetch user',
})
}
const user = await response.json()
return {
authenticated: true,
user,
}
} catch (error: any) {
if (error.statusCode) {
throw error
}
console.error('Auth check error:', error)
throw createError({
statusCode: 500,
statusMessage: 'Failed to verify authentication',
})
}
})