Replaced NextAuth's built-in OAuth provider (incompatible with Cloudflare Workers) with custom OAuth implementation using native fetch API. Features: - Custom OAuth flow compatible with Cloudflare Workers edge runtime - Auto-provisions users from Nextcloud based on group membership - Group-based role assignment (artists, shop_admins, admins) - Auto-creates artist profiles for users in 'artists' group - Seamless integration with existing NextAuth session management Technical changes: - Added custom OAuth routes: /api/auth/nextcloud/authorize & callback - Created Nextcloud API client for user provisioning (lib/nextcloud-client.ts) - Extended credentials provider to accept Nextcloud one-time tokens - Added user management functions to database layer - Updated signin UI to use custom OAuth flow - Added environment variables for OAuth configuration Documentation: - Comprehensive setup guide in docs/NEXTCLOUD-OAUTH-SETUP.md - Updated CLAUDE.md with new authentication architecture Fixes: NextAuth OAuth incompatibility with Cloudflare Workers (unenv https.request error) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
66 lines
2.0 KiB
TypeScript
66 lines
2.0 KiB
TypeScript
import { z } from "zod"
|
|
|
|
const envSchema = z.object({
|
|
// Database
|
|
DATABASE_URL: z.string().url(),
|
|
DIRECT_URL: z.string().url().optional(),
|
|
|
|
// Authentication
|
|
NEXTAUTH_URL: z.string().url(),
|
|
NEXTAUTH_SECRET: z.string().min(1),
|
|
|
|
// OAuth Providers (optional)
|
|
GOOGLE_CLIENT_ID: z.string().optional(),
|
|
GOOGLE_CLIENT_SECRET: z.string().optional(),
|
|
GITHUB_CLIENT_ID: z.string().optional(),
|
|
GITHUB_CLIENT_SECRET: z.string().optional(),
|
|
|
|
// File Storage (AWS S3 or Cloudflare R2)
|
|
AWS_ACCESS_KEY_ID: z.string().min(1),
|
|
AWS_SECRET_ACCESS_KEY: z.string().min(1),
|
|
AWS_REGION: z.string().min(1),
|
|
AWS_BUCKET_NAME: z.string().min(1),
|
|
AWS_ENDPOINT_URL: z.string().url().optional(), // For Cloudflare R2
|
|
|
|
// Application
|
|
NODE_ENV: z.enum(["development", "production", "test"]).default("development"),
|
|
|
|
// Optional: Email service
|
|
SMTP_HOST: z.string().optional(),
|
|
SMTP_PORT: z.string().optional(),
|
|
SMTP_USER: z.string().optional(),
|
|
SMTP_PASSWORD: z.string().optional(),
|
|
|
|
// Optional: Analytics
|
|
VERCEL_ANALYTICS_ID: z.string().optional(),
|
|
|
|
// CalDAV / Nextcloud Integration
|
|
NEXTCLOUD_BASE_URL: z.string().url().optional(),
|
|
NEXTCLOUD_USERNAME: z.string().optional(),
|
|
NEXTCLOUD_PASSWORD: z.string().optional(),
|
|
NEXTCLOUD_CALENDAR_BASE_PATH: z.string().default('/remote.php/dav/calendars'),
|
|
|
|
// Nextcloud OAuth Authentication
|
|
NEXTCLOUD_OAUTH_CLIENT_ID: z.string().optional(),
|
|
NEXTCLOUD_OAUTH_CLIENT_SECRET: z.string().optional(),
|
|
NEXTCLOUD_ARTISTS_GROUP: z.string().default('artists'),
|
|
NEXTCLOUD_ADMINS_GROUP: z.string().default('shop_admins'),
|
|
})
|
|
|
|
export type Env = z.infer<typeof envSchema>
|
|
|
|
// Validate environment variables at boot
|
|
function validateEnv(): Env {
|
|
try {
|
|
return envSchema.parse(process.env)
|
|
} catch (error) {
|
|
if (error instanceof z.ZodError) {
|
|
const missingVars = error.errors.map(err => err.path.join('.')).join(', ')
|
|
throw new Error(`Missing or invalid environment variables: ${missingVars}`)
|
|
}
|
|
throw error
|
|
}
|
|
}
|
|
|
|
export const env = validateEnv()
|