feat(auth): add @ketopath/auth package wrapping Better Auth
- New workspace packages/auth exporting:
- auth: Better Auth server instance using Prisma adapter on @ketopath/db
- makeAuthClient: React client factory parameterised by baseURL
- readAuthEnv: Zod-validated env reader (BETTER_AUTH_SECRET min 32 chars,
BETTER_AUTH_URL, optional GOOGLE_CLIENT_ID/SECRET)
- emailAndPassword enabled with min 8 chars, requireEmailVerification: false
for MVP (re-enable in V1, see ADR 0001)
- Google provider conditionally registered when both env vars are present —
keeps the package usable before OAuth configuration is delivered
- 7-day sessions, 24h rolling refresh, cookie prefix 'ketopath'
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
79d4943c00
commit
31eea207ba
9 files changed
+110
-1
No files matched your search
@@ -0,0 +1,5 @@
|
|||||||
|
/** @type {import('eslint').Linter.Config} */
|
||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
extends: ['@ketopath/eslint-config'],
|
||||||
|
};
|
||||||
@@ -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:*"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { createAuthClient } from 'better-auth/react';
|
||||||
|
|
||||||
|
export function makeAuthClient(baseURL: string): ReturnType<typeof createAuthClient> {
|
||||||
|
return createAuthClient({ baseURL });
|
||||||
|
}
|
||||||
@@ -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<typeof authEnvSchema>;
|
||||||
|
|
||||||
|
export function readAuthEnv(source: NodeJS.ProcessEnv = process.env): AuthEnv {
|
||||||
|
return authEnvSchema.parse(source);
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export { auth, type Auth } from './server.js';
|
||||||
|
export { readAuthEnv, type AuthEnv } from './env.js';
|
||||||
@@ -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;
|
||||||
@@ -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"]
|
||||||
|
}
|
||||||
Generated
BIN
Binary file not shown.
+2
-1
@@ -3,6 +3,7 @@
|
|||||||
"references": [
|
"references": [
|
||||||
{ "path": "./packages/shared" },
|
{ "path": "./packages/shared" },
|
||||||
{ "path": "./packages/ui" },
|
{ "path": "./packages/ui" },
|
||||||
{ "path": "./packages/db" }
|
{ "path": "./packages/db" },
|
||||||
|
{ "path": "./packages/auth" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user