113 lines
3.2 KiB
TypeScript
113 lines
3.2 KiB
TypeScript
import { betterAuth } from 'better-auth';
|
|
import { bearer } from 'better-auth/plugins/bearer';
|
|
import { Pool } from 'pg';
|
|
import { Kysely, PostgresDialect } from 'kysely';
|
|
import type { FastifyBaseLogger } from 'fastify';
|
|
|
|
export interface BetterAuthConfig {
|
|
databaseUrl: string;
|
|
pool?: Pool;
|
|
secret: string;
|
|
baseUrl: string;
|
|
trustedOrigins: string[];
|
|
logger: FastifyBaseLogger;
|
|
}
|
|
|
|
/**
|
|
* Create Better Auth instance with PostgreSQL adapter and passkey support
|
|
*/
|
|
export async function createBetterAuth(config: BetterAuthConfig) {
|
|
try {
|
|
config.logger.debug({
|
|
databaseUrl: config.databaseUrl.replace(/:[^:@]+@/, ':***@'),
|
|
baseUrl: config.baseUrl,
|
|
}, 'Creating Better Auth instance');
|
|
|
|
// Use existing pool if provided, otherwise create new one
|
|
const pool = config.pool || new Pool({
|
|
connectionString: config.databaseUrl,
|
|
});
|
|
|
|
config.logger.debug('PostgreSQL pool created');
|
|
|
|
// Test database connection first
|
|
try {
|
|
config.logger.debug('Testing database connection...');
|
|
const testClient = await pool.connect();
|
|
await testClient.query('SELECT 1');
|
|
testClient.release();
|
|
config.logger.debug('Database connection test successful');
|
|
} catch (dbError: any) {
|
|
config.logger.error({
|
|
error: dbError,
|
|
message: dbError.message,
|
|
stack: dbError.stack,
|
|
}, 'Database connection test failed');
|
|
throw new Error(`Database connection failed: ${dbError.message}`);
|
|
}
|
|
|
|
// Create Kysely instance for Better Auth
|
|
config.logger.debug('Creating Kysely database instance...');
|
|
const db = new Kysely({
|
|
dialect: new PostgresDialect({ pool }),
|
|
});
|
|
config.logger.debug('Kysely instance created');
|
|
|
|
// Better Auth v1.5.3 postgres configuration
|
|
const auth = betterAuth({
|
|
database: {
|
|
db,
|
|
type: 'postgres',
|
|
},
|
|
|
|
// Secret for JWT signing
|
|
secret: config.secret,
|
|
|
|
// Base URL for callbacks and redirects
|
|
baseURL: config.baseUrl,
|
|
|
|
// Trusted origins for CORS
|
|
trustedOrigins: config.trustedOrigins,
|
|
|
|
// Email/password authentication
|
|
emailAndPassword: {
|
|
enabled: true,
|
|
requireEmailVerification: false, // Set to true in production
|
|
sendResetPassword: async ({ user, url }) => {
|
|
// TODO: Implement email sending
|
|
config.logger.info({ userId: user.id, resetUrl: url }, 'Password reset requested');
|
|
},
|
|
},
|
|
|
|
// Session configuration
|
|
session: {
|
|
expiresIn: 60 * 60 * 24 * 7, // 7 days
|
|
updateAge: 60 * 60 * 24, // Update session every 24 hours
|
|
cookieCache: {
|
|
enabled: true,
|
|
maxAge: 5 * 60, // 5 minutes
|
|
},
|
|
},
|
|
|
|
// Plugins
|
|
plugins: [
|
|
bearer(), // Enable Bearer token authentication for API/WebSocket
|
|
],
|
|
|
|
});
|
|
|
|
config.logger.debug('Better Auth instance created');
|
|
return auth;
|
|
} catch (error: any) {
|
|
config.logger.error({
|
|
error,
|
|
message: error.message,
|
|
stack: error.stack,
|
|
cause: error.cause,
|
|
}, 'Error creating Better Auth instance');
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
export type BetterAuthInstance = Awaited<ReturnType<typeof createBetterAuth>>;
|