localgreenchain/pages/api/auth/verify-email.ts
Claude 39b6081baa
Implement comprehensive authentication system (Agent 1)
Add complete user authentication with NextAuth.js supporting:
- Email/password credentials authentication
- OAuth providers (GitHub, Google) with optional configuration
- JWT-based session management with 30-day expiry
- Role-based access control (USER, GROWER, FARM_MANAGER, ADMIN)
- Permission system with granular access control
- Secure password hashing with bcrypt (12 rounds)
- Rate limiting on auth endpoints
- Password reset flow with secure tokens
- Email verification system

Files added:
- lib/auth/: Core auth library (types, permissions, context, hooks, middleware)
- pages/api/auth/: Auth API routes (NextAuth, register, forgot-password, verify-email)
- pages/auth/: Auth pages (signin, signup, forgot-password, reset-password, verify-email)
- components/auth/: Reusable auth components (LoginForm, RegisterForm, AuthGuard, etc.)

Updated _app.tsx to include SessionProvider for auth state management.
2025-11-23 03:52:09 +00:00

151 lines
4.1 KiB
TypeScript

import type { NextApiRequest, NextApiResponse } from 'next'
import crypto from 'crypto'
import { findUserByEmail, getUserById } from './[...nextauth]'
import { AuthResponse, VerifyEmailInput, TokenPayload } from '@/lib/auth/types'
import { withRateLimit } from '@/lib/auth/withAuth'
// In-memory token store (will be replaced with database in Agent 2)
const emailVerificationTokens = new Map<string, TokenPayload>()
// Token expiry: 24 hours
const TOKEN_EXPIRY_MS = 24 * 60 * 60 * 1000
export function generateVerificationToken(userId: string, email: string): string {
const token = crypto.randomBytes(32).toString('hex')
const payload: TokenPayload = {
userId,
email,
type: 'email_verification',
expiresAt: Date.now() + TOKEN_EXPIRY_MS,
}
emailVerificationTokens.set(token, payload)
return token
}
function verifyToken(token: string): TokenPayload | null {
const payload = emailVerificationTokens.get(token)
if (!payload) return null
if (Date.now() > payload.expiresAt) {
emailVerificationTokens.delete(token)
return null
}
return payload
}
function invalidateToken(token: string): void {
emailVerificationTokens.delete(token)
}
async function handler(
req: NextApiRequest,
res: NextApiResponse<AuthResponse>
) {
// Handle GET request (verify token from email link)
if (req.method === 'GET') {
const { token } = req.query
if (!token || typeof token !== 'string') {
return res.status(400).json({
success: false,
message: 'Verification token is required',
error: 'VALIDATION_ERROR',
})
}
const payload = verifyToken(token)
if (!payload) {
return res.status(400).json({
success: false,
message: 'Invalid or expired verification token',
error: 'INVALID_TOKEN',
})
}
const user = getUserById(payload.userId)
if (!user) {
return res.status(400).json({
success: false,
message: 'User not found',
error: 'USER_NOT_FOUND',
})
}
// Mark email as verified
user.emailVerified = new Date()
// Invalidate the used token
invalidateToken(token)
console.log(`Email verified for user: ${user.email}`)
// Redirect to success page or return success response
if (req.headers.accept?.includes('text/html')) {
res.redirect(302, '/auth/email-verified')
return
}
return res.status(200).json({
success: true,
message: 'Email verified successfully',
})
}
// Handle POST request (resend verification email)
if (req.method === 'POST') {
const { email } = req.body
if (!email) {
return res.status(400).json({
success: false,
message: 'Email is required',
error: 'VALIDATION_ERROR',
})
}
const user = findUserByEmail(email)
if (!user) {
// Return success to prevent email enumeration
return res.status(200).json({
success: true,
message: 'If an account exists with this email, a verification link has been sent.',
})
}
if (user.emailVerified) {
return res.status(400).json({
success: false,
message: 'Email is already verified',
error: 'ALREADY_VERIFIED',
})
}
// Generate new verification token
const verificationToken = generateVerificationToken(user.id, user.email)
// Build verification URL
const baseUrl = process.env.NEXTAUTH_URL || `http://${req.headers.host}`
const verifyUrl = `${baseUrl}/api/auth/verify-email?token=${verificationToken}`
// TODO: Send verification email (will be implemented with Agent 8 - Notifications)
if (process.env.NODE_ENV === 'development') {
console.log(`Email verification link for ${email}: ${verifyUrl}`)
}
return res.status(200).json({
success: true,
message: 'If an account exists with this email, a verification link has been sent.',
})
}
return res.status(405).json({
success: false,
message: 'Method not allowed',
error: 'Only GET and POST requests are accepted',
})
}
// Apply rate limiting: 3 requests per minute per IP
export default withRateLimit(handler, {
limit: 3,
windowMs: 60000,
})