Enhance application structure: Add runtime configuration for environment variables, integrate new dependencies for Cloudinary and UI components, and refactor member management features including improved forms and member dashboard. Update styles and layout for better user experience.
This commit is contained in:
parent
6e7e27ac4e
commit
e4a0a9ab0f
61 changed files with 7902 additions and 950 deletions
|
|
@ -1,32 +1,76 @@
|
|||
// server/api/auth/login.post.js
|
||||
import jwt from 'jsonwebtoken'
|
||||
import Member from '../../models/member'
|
||||
import { Resend } from 'resend'
|
||||
import Member from '../../models/member.js'
|
||||
import { connectDB } from '../../utils/mongoose.js'
|
||||
|
||||
const resend = new Resend(process.env.RESEND_API_KEY)
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
// Connect to database
|
||||
await connectDB()
|
||||
|
||||
const { email } = await readBody(event)
|
||||
|
||||
if (!email) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: 'Email is required'
|
||||
})
|
||||
}
|
||||
|
||||
const member = await Member.findOne({ email })
|
||||
if (!member) {
|
||||
throw createError({ statusCode: 404 })
|
||||
throw createError({
|
||||
statusCode: 404,
|
||||
statusMessage: 'No account found with that email address'
|
||||
})
|
||||
}
|
||||
|
||||
// Send magic link via Resend
|
||||
// Generate magic link token
|
||||
const token = jwt.sign(
|
||||
{ memberId: member._id },
|
||||
process.env.JWT_SECRET,
|
||||
{ expiresIn: '7d' }
|
||||
{ expiresIn: '15m' } // Shorter expiry for security
|
||||
)
|
||||
|
||||
await resend.emails.send({
|
||||
from: 'Ghost Guild <noreply@ghostguild.org>',
|
||||
to: email,
|
||||
subject: 'Your Ghost Guild login link',
|
||||
html: `
|
||||
<a href="https://ghostguild.org/auth/verify?token=${token}">
|
||||
Click here to log in
|
||||
</a>
|
||||
`
|
||||
})
|
||||
// Get the base URL for the magic link
|
||||
const headers = getHeaders(event)
|
||||
const baseUrl = process.env.BASE_URL || `${headers.host?.includes('localhost') ? 'http' : 'https'}://${headers.host}`
|
||||
|
||||
return { success: true }
|
||||
// Send magic link via Resend
|
||||
try {
|
||||
await resend.emails.send({
|
||||
from: 'Ghost Guild <noreply@ghostguild.org>',
|
||||
to: email,
|
||||
subject: 'Your Ghost Guild login link',
|
||||
html: `
|
||||
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
|
||||
<h2 style="color: #2563eb;">Welcome back to Ghost Guild!</h2>
|
||||
<p>Click the button below to sign in to your account:</p>
|
||||
<div style="text-align: center; margin: 30px 0;">
|
||||
<a href="${baseUrl}/api/auth/verify?token=${token}"
|
||||
style="background-color: #2563eb; color: white; padding: 12px 24px; text-decoration: none; border-radius: 6px; display: inline-block;">
|
||||
Sign In to Ghost Guild
|
||||
</a>
|
||||
</div>
|
||||
<p style="color: #666; font-size: 14px;">
|
||||
This link will expire in 15 minutes for security. If you didn't request this login link, you can safely ignore this email.
|
||||
</p>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Login link sent to your email'
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to send email:', error)
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: 'Failed to send login email. Please try again.'
|
||||
})
|
||||
}
|
||||
})
|
||||
11
server/api/auth/logout.post.js
Normal file
11
server/api/auth/logout.post.js
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
export default defineEventHandler(async (event) => {
|
||||
// Clear the auth token cookie
|
||||
setCookie(event, 'auth-token', '', {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
sameSite: 'strict',
|
||||
maxAge: 0 // Expire immediately
|
||||
})
|
||||
|
||||
return { message: 'Logged out successfully' }
|
||||
})
|
||||
57
server/api/auth/verify.get.js
Normal file
57
server/api/auth/verify.get.js
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
// server/api/auth/verify.get.js
|
||||
import jwt from 'jsonwebtoken'
|
||||
import Member from '../../models/member.js'
|
||||
import { connectDB } from '../../utils/mongoose.js'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
// Connect to database
|
||||
await connectDB()
|
||||
|
||||
const query = getQuery(event)
|
||||
const { token } = query
|
||||
|
||||
if (!token) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: 'Token is required'
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
// Verify the JWT token
|
||||
const decoded = jwt.verify(token, process.env.JWT_SECRET)
|
||||
const member = await Member.findById(decoded.memberId)
|
||||
|
||||
if (!member) {
|
||||
throw createError({
|
||||
statusCode: 404,
|
||||
statusMessage: 'Member not found'
|
||||
})
|
||||
}
|
||||
|
||||
// Create a new session token for the authenticated user
|
||||
const sessionToken = jwt.sign(
|
||||
{ memberId: member._id, email: member.email },
|
||||
process.env.JWT_SECRET,
|
||||
{ expiresIn: '30d' }
|
||||
)
|
||||
|
||||
// Set the session cookie
|
||||
setCookie(event, 'auth-token', sessionToken, {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
sameSite: 'lax',
|
||||
maxAge: 60 * 60 * 24 * 30 // 30 days
|
||||
})
|
||||
|
||||
// Redirect to the members dashboard or home page
|
||||
await sendRedirect(event, '/members', 302)
|
||||
|
||||
} catch (err) {
|
||||
console.error('Token verification error:', err)
|
||||
throw createError({
|
||||
statusCode: 401,
|
||||
statusMessage: 'Invalid or expired token'
|
||||
})
|
||||
}
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue