diff --git a/apps/api/.env.example b/apps/api/.env.example index 5e4cc4a..3bd8fbb 100644 --- a/apps/api/.env.example +++ b/apps/api/.env.example @@ -7,3 +7,11 @@ LOG_LEVEL=info CORS_ORIGINS=http://localhost:3000 DATABASE_URL=postgresql://luciano:luciano%40@localhost:5432/ketopath?schema=public + +# Better Auth — DEVE coincidere con apps/web/.env (sessione condivisa) +BETTER_AUTH_SECRET= +BETTER_AUTH_URL=http://localhost:3000 + +# Google OAuth (configurato a fine progetto) +# GOOGLE_CLIENT_ID= +# GOOGLE_CLIENT_SECRET= diff --git a/apps/api/package.json b/apps/api/package.json index acb69d1..440bdc6 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -15,8 +15,10 @@ "@fastify/helmet": "^11.1.1", "@fastify/rate-limit": "^9.1.0", "@fastify/sensible": "^5.6.0", + "@ketopath/auth": "workspace:*", "@ketopath/db": "workspace:*", "@ketopath/shared": "workspace:*", + "better-auth": "^1.0.21", "fastify": "^4.28.1", "fastify-plugin": "^4.5.1", "zod": "^3.23.8" diff --git a/apps/api/src/app.ts b/apps/api/src/app.ts index adbea94..b393115 100644 --- a/apps/api/src/app.ts +++ b/apps/api/src/app.ts @@ -7,6 +7,8 @@ import Fastify, { type FastifyInstance } from 'fastify'; import { env } from './config/env.js'; import { dbRoutes } from './modules/db/db.routes.js'; import { healthRoutes } from './modules/health/health.routes.js'; +import { meRoutes } from './modules/me/me.routes.js'; +import { authPlugin } from './plugins/auth.js'; import { prismaPlugin } from './plugins/prisma.js'; export async function buildApp(): Promise { @@ -36,9 +38,11 @@ export async function buildApp(): Promise { }); await app.register(sensible); await app.register(prismaPlugin); + await app.register(authPlugin); await app.register(healthRoutes); await app.register(dbRoutes); + await app.register(meRoutes); return app; } diff --git a/apps/api/src/config/env.ts b/apps/api/src/config/env.ts index 7abd932..52adf2f 100644 --- a/apps/api/src/config/env.ts +++ b/apps/api/src/config/env.ts @@ -15,6 +15,8 @@ const envSchema = z.object({ .filter(Boolean), ), DATABASE_URL: z.string().url(), + BETTER_AUTH_SECRET: z.string().min(32), + BETTER_AUTH_URL: z.string().url(), }); export type Env = z.infer; diff --git a/apps/api/src/modules/me/me.routes.ts b/apps/api/src/modules/me/me.routes.ts new file mode 100644 index 0000000..948d1d5 --- /dev/null +++ b/apps/api/src/modules/me/me.routes.ts @@ -0,0 +1,10 @@ +import type { FastifyPluginAsync } from 'fastify'; + +import { requireAuth } from '../../plugins/auth.js'; + +export const meRoutes: FastifyPluginAsync = async (fastify) => { + fastify.get('/me', { preHandler: requireAuth() }, async (request) => ({ + user: request.user, + session: request.session, + })); +}; diff --git a/apps/api/src/plugins/auth.ts b/apps/api/src/plugins/auth.ts new file mode 100644 index 0000000..47ebe37 --- /dev/null +++ b/apps/api/src/plugins/auth.ts @@ -0,0 +1,39 @@ +import { auth } from '@ketopath/auth'; +import type { FastifyReply, FastifyRequest } from 'fastify'; +import fp from 'fastify-plugin'; + +type SessionPayload = Awaited>; +type User = NonNullable['user']; +type Session = NonNullable['session']; + +declare module 'fastify' { + interface FastifyRequest { + user: User | null; + session: Session | null; + } +} + +export const authPlugin = fp(async (app) => { + app.decorateRequest('user', null); + app.decorateRequest('session', null); + + app.addHook('preHandler', async (request) => { + const headers = new Headers(); + for (const [key, value] of Object.entries(request.headers)) { + if (typeof value === 'string') headers.set(key, value); + else if (Array.isArray(value)) headers.set(key, value.join(', ')); + } + + const result = await auth.api.getSession({ headers }); + request.user = result?.user ?? null; + request.session = result?.session ?? null; + }); +}); + +export function requireAuth() { + return async (request: FastifyRequest, reply: FastifyReply): Promise => { + if (!request.user || !request.session) { + return reply.code(401).send({ error: 'unauthorized' }); + } + }; +} diff --git a/apps/web/next.config.mjs b/apps/web/next.config.mjs index ec6068c..caa3379 100644 --- a/apps/web/next.config.mjs +++ b/apps/web/next.config.mjs @@ -5,10 +5,19 @@ const withNextIntl = createNextIntlPlugin('./src/i18n.ts'); /** @type {import('next').NextConfig} */ const nextConfig = { reactStrictMode: true, - transpilePackages: ['@ketopath/auth', '@ketopath/shared', '@ketopath/ui'], + transpilePackages: ['@ketopath/auth', '@ketopath/db', '@ketopath/shared', '@ketopath/ui'], experimental: { typedRoutes: true, }, + webpack(config) { + // I package interni usano .js nelle import (NodeNext). Webpack non lo + // risolve di default per i file TS: gli diciamo di provare anche .ts/.tsx. + config.resolve.extensionAlias = { + ...config.resolve.extensionAlias, + '.js': ['.ts', '.tsx', '.js', '.jsx'], + }; + return config; + }, }; export default withNextIntl(nextConfig); diff --git a/packages/auth/src/index.ts b/packages/auth/src/index.ts index 8e8e3b7..54f39cc 100644 --- a/packages/auth/src/index.ts +++ b/packages/auth/src/index.ts @@ -1,2 +1,2 @@ -export { auth, type Auth } from './server'; -export { readAuthEnv, type AuthEnv } from './env'; +export { auth, type Auth } from './server.js'; +export { readAuthEnv, type AuthEnv } from './env.js'; diff --git a/packages/auth/src/server.ts b/packages/auth/src/server.ts index 9d72257..f1cd296 100644 --- a/packages/auth/src/server.ts +++ b/packages/auth/src/server.ts @@ -2,7 +2,7 @@ import { prisma } from '@ketopath/db'; import { betterAuth } from 'better-auth'; import { prismaAdapter } from 'better-auth/adapters/prisma'; -import { readAuthEnv } from './env'; +import { readAuthEnv } from './env.js'; const env = readAuthEnv(); diff --git a/packages/auth/tsconfig.json b/packages/auth/tsconfig.json index 79c7de1..be069dd 100644 --- a/packages/auth/tsconfig.json +++ b/packages/auth/tsconfig.json @@ -1,11 +1,10 @@ { - "extends": "@ketopath/tsconfig/base.json", + "extends": "@ketopath/tsconfig/node.json", "compilerOptions": { "outDir": "dist", "rootDir": "src", "composite": true, - "lib": ["DOM", "ES2022"], - "types": ["node"] + "lib": ["DOM", "ES2022"] }, "include": ["src/**/*"], "exclude": ["node_modules", "dist"] diff --git a/packages/db/src/index.ts b/packages/db/src/index.ts index 198cde3..cf10914 100644 --- a/packages/db/src/index.ts +++ b/packages/db/src/index.ts @@ -1,4 +1,4 @@ -export { prisma } from './client'; +export { prisma } from './client.js'; export { ActivityLevel, CookingTime, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c456f34..2ef8663 100644 Binary files a/pnpm-lock.yaml and b/pnpm-lock.yaml differ