diff --git a/packages/auth/.eslintrc.cjs b/packages/auth/.eslintrc.cjs new file mode 100644 index 0000000..2aed780 --- /dev/null +++ b/packages/auth/.eslintrc.cjs @@ -0,0 +1,5 @@ +/** @type {import('eslint').Linter.Config} */ +module.exports = { + root: true, + extends: ['@ketopath/eslint-config'], +}; diff --git a/packages/auth/package.json b/packages/auth/package.json new file mode 100644 index 0000000..acd5921 --- /dev/null +++ b/packages/auth/package.json @@ -0,0 +1,25 @@ +{ + "name": "@ketopath/auth", + "version": "0.0.0", + "private": true, + "type": "module", + "main": "./src/index.ts", + "types": "./src/index.ts", + "exports": { + ".": "./src/index.ts", + "./client": "./src/client.ts" + }, + "scripts": { + "typecheck": "tsc --noEmit", + "lint": "eslint src --max-warnings=0" + }, + "dependencies": { + "@ketopath/db": "workspace:*", + "better-auth": "^1.0.21", + "zod": "^3.23.8" + }, + "devDependencies": { + "@ketopath/eslint-config": "workspace:*", + "@ketopath/tsconfig": "workspace:*" + } +} diff --git a/packages/auth/src/client.ts b/packages/auth/src/client.ts new file mode 100644 index 0000000..969b94e --- /dev/null +++ b/packages/auth/src/client.ts @@ -0,0 +1,5 @@ +import { createAuthClient } from 'better-auth/react'; + +export function makeAuthClient(baseURL: string): ReturnType { + return createAuthClient({ baseURL }); +} diff --git a/packages/auth/src/env.ts b/packages/auth/src/env.ts new file mode 100644 index 0000000..154017d --- /dev/null +++ b/packages/auth/src/env.ts @@ -0,0 +1,19 @@ +import { z } from 'zod'; + +// Le env vars sono lette dal processo che importa @ketopath/auth (apps/web, +// apps/api, ecc.). Vengono validate al momento della creazione dell'istanza, +// così errori di configurazione emergono al boot e non a runtime. +const authEnvSchema = z.object({ + BETTER_AUTH_SECRET: z + .string() + .min(32, 'BETTER_AUTH_SECRET deve essere lungo almeno 32 caratteri'), + BETTER_AUTH_URL: z.string().url(), + GOOGLE_CLIENT_ID: z.string().optional(), + GOOGLE_CLIENT_SECRET: z.string().optional(), +}); + +export type AuthEnv = z.infer; + +export function readAuthEnv(source: NodeJS.ProcessEnv = process.env): AuthEnv { + return authEnvSchema.parse(source); +} diff --git a/packages/auth/src/index.ts b/packages/auth/src/index.ts new file mode 100644 index 0000000..54f39cc --- /dev/null +++ b/packages/auth/src/index.ts @@ -0,0 +1,2 @@ +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 new file mode 100644 index 0000000..f1cd296 --- /dev/null +++ b/packages/auth/src/server.ts @@ -0,0 +1,40 @@ +import { prisma } from '@ketopath/db'; +import { betterAuth } from 'better-auth'; +import { prismaAdapter } from 'better-auth/adapters/prisma'; + +import { readAuthEnv } from './env.js'; + +const env = readAuthEnv(); + +const googleProvider = + env.GOOGLE_CLIENT_ID && env.GOOGLE_CLIENT_SECRET + ? { + google: { + clientId: env.GOOGLE_CLIENT_ID, + clientSecret: env.GOOGLE_CLIENT_SECRET, + }, + } + : undefined; + +export const auth = betterAuth({ + database: prismaAdapter(prisma, { provider: 'postgresql' }), + secret: env.BETTER_AUTH_SECRET, + baseURL: env.BETTER_AUTH_URL, + emailAndPassword: { + enabled: true, + // MVP: nessuna verifica email per non bloccare l'onboarding (vedi + // docs/decisions/0001-auth-provider.md). Verrà attivata in V1. + requireEmailVerification: false, + minPasswordLength: 8, + }, + socialProviders: googleProvider, + session: { + expiresIn: 60 * 60 * 24 * 7, // 7 giorni + updateAge: 60 * 60 * 24, // refresh expiresAt ogni 24h se la session è attiva + }, + advanced: { + cookiePrefix: 'ketopath', + }, +}); + +export type Auth = typeof auth; diff --git a/packages/auth/tsconfig.json b/packages/auth/tsconfig.json new file mode 100644 index 0000000..79c7de1 --- /dev/null +++ b/packages/auth/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "@ketopath/tsconfig/base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "composite": true, + "lib": ["DOM", "ES2022"], + "types": ["node"] + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 116cec0..8cf2904 100644 Binary files a/pnpm-lock.yaml and b/pnpm-lock.yaml differ diff --git a/tsconfig.json b/tsconfig.json index 2425989..ff7b12f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,6 +3,7 @@ "references": [ { "path": "./packages/shared" }, { "path": "./packages/ui" }, - { "path": "./packages/db" } + { "path": "./packages/db" }, + { "path": "./packages/auth" } ] }