rename before moving to monorepo

This commit is contained in:
wass08
2026-03-11 15:12:41 +01:00
parent 5bd9c21dbc
commit 3eeba322a8
29 changed files with 24 additions and 24 deletions
+142
View File
@@ -0,0 +1,142 @@
# @pascal-app/auth
Authentication package for Pascal Editor using Better Auth.
## Features
- **Magic Link Authentication** - Passwordless email-based authentication
- **Session Management** - Secure cookie-based sessions
- **Supabase Integration** - Uses Supabase as the database adapter
- **Type-safe** - Full TypeScript support with type inference
## Setup
### 1. Configure environment variables
Add these to `apps/editor/.env.local`:
```bash
# Better Auth
BETTER_AUTH_SECRET=<generate_with_openssl_rand_base64_32>
BETTER_AUTH_URL=http://localhost:3000
```
Generate a secret for `BETTER_AUTH_SECRET`:
```bash
openssl rand -base64 32
```
### 2. Ensure database is running
Make sure you have Supabase running with the auth tables created. See `@pascal-app/db` package for setup.
## Usage
### Server-side (API routes, server actions)
```typescript
import { auth } from '@pascal-app/auth/server'
// Get session in server component or action
const session = await auth.api.getSession({ headers: request.headers })
if (!session) {
return { error: 'Unauthorized' }
}
// Access user data
const userId = session.user.id
const email = session.user.email
```
### Client-side (React components)
```typescript
'use client'
import { authClient } from '@pascal-app/auth/client'
function SignInButton() {
const { signIn } = authClient
const handleSignIn = async (email: string) => {
await signIn.magicLink({
email,
callbackURL: '/dashboard',
})
}
return <button onClick={() => handleSignIn('user@example.com')}>Sign In</button>
}
```
### Using the auth hook
```typescript
'use client'
import { authClient } from '@pascal-app/auth/client'
function Profile() {
const { data: session, isPending } = authClient.useSession()
if (isPending) return <div>Loading...</div>
if (!session) return <div>Not signed in</div>
return <div>Signed in as {session.user.email}</div>
}
```
## API Routes
The auth package requires an API route handler in your Next.js app:
```typescript
// app/api/auth/[...all]/route.ts
import { auth } from '@pascal-app/auth/server'
import { toNextJsHandler } from 'better-auth/next-js'
export const { GET, POST } = toNextJsHandler(auth)
```
This handles all Better Auth endpoints:
- `/api/auth/sign-in/magic-link` - Send magic link
- `/api/auth/sign-in/magic-link/verify` - Verify magic link
- `/api/auth/sign-out` - Sign out
- `/api/auth/session` - Get session
- And more...
## Email Configuration
By default, magic links are logged to the console. To send actual emails, you'll need to configure an email provider in `packages/auth/src/server.ts`:
```typescript
magicLink({
sendMagicLink: async ({ email, url }) => {
// Use Resend, SendGrid, or your preferred email service
await sendEmail({
to: email,
subject: 'Sign in to Pascal Editor',
html: `Click here to sign in: <a href="${url}">${url}</a>`,
})
},
})
```
## Database Schema
The auth package requires these tables (created by `@pascal-app/db` migrations):
- `users` - User accounts
- `sessions` - Active sessions
- `accounts` - OAuth provider accounts (for future use)
- `verification_tokens` - Magic link tokens
## Security
- Session cookies are httpOnly and secure (in production)
- Sessions expire after 7 days
- Session cache is enabled for 5 minutes to reduce database queries
- Magic link tokens expire after 15 minutes
- All sensitive operations require valid session tokens
+24
View File
@@ -0,0 +1,24 @@
{
"name": "@pascal-app/auth",
"version": "0.0.0",
"type": "module",
"exports": {
"./server": {
"types": "./src/server.ts",
"default": "./src/server.ts"
},
"./client": {
"types": "./src/client.ts",
"default": "./src/client.ts"
}
},
"dependencies": {
"@pascal-app/db": "*",
"better-auth": "^1.5.2",
"resend": "^4.0.1"
},
"devDependencies": {
"@repo/typescript-config": "*",
"typescript": "5.9.3"
}
}
+61
View File
@@ -0,0 +1,61 @@
import { lastLoginMethodClient, magicLinkClient } from 'better-auth/client/plugins'
import { createAuthClient } from 'better-auth/react'
/**
* Get the auth base URL
* In development: use the editor URL (localhost:3000)
* In production: use the same origin
*/
function getAuthURL(): string {
if (typeof window !== 'undefined') {
return window.location.origin
}
// SSR fallback - detect environment from Vercel variables
const isDevelopment =
process.env.NODE_ENV === 'development' ||
process.env.NEXT_PUBLIC_VERCEL_ENV === 'development'
const isPreview = process.env.NEXT_PUBLIC_VERCEL_ENV === 'preview'
const isProduction =
process.env.NODE_ENV === 'production' || process.env.NEXT_PUBLIC_VERCEL_ENV === 'production'
if (isDevelopment) {
return process.env.NEXT_PUBLIC_APP_URL || `http://localhost:${process.env.PORT || 3000}`
}
if (isPreview && process.env.NEXT_PUBLIC_VERCEL_URL) {
return `https://${process.env.NEXT_PUBLIC_VERCEL_URL}`
}
if (isProduction) {
return (
process.env.NEXT_PUBLIC_APP_URL ||
(process.env.NEXT_PUBLIC_VERCEL_PROJECT_PRODUCTION_URL
? `https://${process.env.NEXT_PUBLIC_VERCEL_PROJECT_PRODUCTION_URL}`
: 'https://editor.pascal.app')
)
}
return 'http://localhost:3000'
}
/**
* Auth client instance
* Configured for magic link authentication
*/
export const authClient = createAuthClient({
baseURL: getAuthURL(),
plugins: [magicLinkClient(), lastLoginMethodClient()],
})
/**
* Export types for use in components
*/
export type AuthState = {
user: (typeof authClient)['$Infer']['Session']['user'] | null
session: (typeof authClient)['$Infer']['Session']['session'] | null
isLoading: boolean
}
export type User = NonNullable<AuthState['user']>
export type Session = NonNullable<AuthState['session']>
+99
View File
@@ -0,0 +1,99 @@
import type { Database } from '@pascal-app/db'
import { schema } from '@pascal-app/db'
import type { BetterAuthOptions } from 'better-auth'
import { betterAuth } from 'better-auth'
import { drizzleAdapter } from 'better-auth/adapters/drizzle'
import { lastLoginMethod, magicLink } from 'better-auth/plugins'
export interface SendMagicLinkParams {
email: string
url: string
token: string
}
export interface AuthConfig {
db: Database
appName: string
baseURL: string
secret: string
/** Google OAuth client ID */
googleClientId?: string
/** Google OAuth client secret */
googleClientSecret?: string
/** Callback to send magic link emails */
sendMagicLink?: (params: SendMagicLinkParams) => Promise<void>
/** Additional plugins to add (e.g., nextCookies for web) */
additionalPlugins?: BetterAuthOptions['plugins']
}
/**
* Creates a Better Auth instance with full configuration including:
* - Magic link authentication
* - Custom session with activePropertyId
* - Session cookie caching
*/
export function createAuth(config: AuthConfig) {
return betterAuth({
appName: config.appName,
baseURL: config.baseURL,
secret: config.secret,
basePath: '/api/auth',
database: drizzleAdapter(config.db, {
provider: 'pg',
usePlural: true,
schema,
}),
advanced: {
database: {
generateId: false, // Use our prefixed nanoid IDs from schema
},
},
session: {
// Session caching to reduce database queries
cookieCache: {
enabled: true,
maxAge: 5 * 60, // Cache duration in seconds (5 minutes)
},
additionalFields: {
// Additional fields for the session table
activePropertyId: {
type: 'string',
},
},
},
// Account linking — always enabled so magic link + Google users can share accounts
account: {
accountLinking: {
enabled: true,
trustedProviders: ['google', 'email'],
},
},
// Google OAuth provider (only enabled when credentials are provided)
...(config.googleClientId &&
config.googleClientSecret && {
socialProviders: {
google: {
clientId: config.googleClientId,
clientSecret: config.googleClientSecret,
},
},
}),
plugins: [
...(config.additionalPlugins ?? []),
// Track which login method was last used (e.g., "google", "magic-link")
lastLoginMethod(),
// Magic link authentication
...(config.sendMagicLink
? [
magicLink({
sendMagicLink: config.sendMagicLink,
expiresIn: 300, // 5 minutes
disableSignUp: false, // Allow new users to sign up via magic link
}),
]
: []),
],
})
}
export type Auth = ReturnType<typeof createAuth>
+9
View File
@@ -0,0 +1,9 @@
{
"extends": "@repo/typescript-config/base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src"
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}