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.
102 lines
3 KiB
TypeScript
102 lines
3 KiB
TypeScript
import type { NextApiRequest, NextApiResponse } from 'next'
|
|
import crypto from 'crypto'
|
|
import { findUserByEmail } from './[...nextauth]'
|
|
import { AuthResponse, ForgotPasswordInput, TokenPayload } from '@/lib/auth/types'
|
|
import { withRateLimit } from '@/lib/auth/withAuth'
|
|
|
|
// In-memory token store (will be replaced with database in Agent 2)
|
|
const passwordResetTokens = new Map<string, TokenPayload>()
|
|
|
|
// Token expiry: 1 hour
|
|
const TOKEN_EXPIRY_MS = 60 * 60 * 1000
|
|
|
|
function generateResetToken(userId: string, email: string): string {
|
|
const token = crypto.randomBytes(32).toString('hex')
|
|
const payload: TokenPayload = {
|
|
userId,
|
|
email,
|
|
type: 'password_reset',
|
|
expiresAt: Date.now() + TOKEN_EXPIRY_MS,
|
|
}
|
|
passwordResetTokens.set(token, payload)
|
|
return token
|
|
}
|
|
|
|
export function verifyResetToken(token: string): TokenPayload | null {
|
|
const payload = passwordResetTokens.get(token)
|
|
if (!payload) return null
|
|
if (Date.now() > payload.expiresAt) {
|
|
passwordResetTokens.delete(token)
|
|
return null
|
|
}
|
|
return payload
|
|
}
|
|
|
|
export function invalidateResetToken(token: string): void {
|
|
passwordResetTokens.delete(token)
|
|
}
|
|
|
|
async function handler(
|
|
req: NextApiRequest,
|
|
res: NextApiResponse<AuthResponse>
|
|
) {
|
|
if (req.method !== 'POST') {
|
|
return res.status(405).json({
|
|
success: false,
|
|
message: 'Method not allowed',
|
|
error: 'Only POST requests are accepted',
|
|
})
|
|
}
|
|
|
|
try {
|
|
const { email }: ForgotPasswordInput = req.body
|
|
|
|
if (!email) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Email is required',
|
|
error: 'VALIDATION_ERROR',
|
|
})
|
|
}
|
|
|
|
// Always return success to prevent email enumeration attacks
|
|
const successResponse: AuthResponse = {
|
|
success: true,
|
|
message: 'If an account exists with this email, you will receive a password reset link.',
|
|
}
|
|
|
|
const user = findUserByEmail(email)
|
|
if (!user) {
|
|
// Return success even if user doesn't exist (security best practice)
|
|
return res.status(200).json(successResponse)
|
|
}
|
|
|
|
// Generate reset token
|
|
const resetToken = generateResetToken(user.id, user.email)
|
|
|
|
// Build reset URL
|
|
const baseUrl = process.env.NEXTAUTH_URL || `http://${req.headers.host}`
|
|
const resetUrl = `${baseUrl}/auth/reset-password?token=${resetToken}`
|
|
|
|
// TODO: Send email with reset link (will be implemented with Agent 8 - Notifications)
|
|
// For now, log the reset URL (in development only)
|
|
if (process.env.NODE_ENV === 'development') {
|
|
console.log(`Password reset link for ${email}: ${resetUrl}`)
|
|
}
|
|
|
|
return res.status(200).json(successResponse)
|
|
} catch (error) {
|
|
console.error('Forgot password error:', error)
|
|
return res.status(500).json({
|
|
success: false,
|
|
message: 'An error occurred processing your request',
|
|
error: 'INTERNAL_ERROR',
|
|
})
|
|
}
|
|
}
|
|
|
|
// Apply rate limiting: 3 requests per minute per IP
|
|
export default withRateLimit(handler, {
|
|
limit: 3,
|
|
windowMs: 60000,
|
|
})
|