diff --git a/.env b/.env index af5fec9..245a102 100644 --- a/.env +++ b/.env @@ -1 +1,2 @@ -VITE_R2_BASE_URL=https://pub-e115c4e749734702abd09206cba74257.r2.dev/ \ No newline at end of file +VITE_R2_BASE_URL=https://pub-e115c4e749734702abd09206cba74257.r2.dev/ +VITE_HCAPTCHA_SITEKEY=a7340f48-b55e-4c56-8d96-2e70ce3423e0 diff --git a/functions/api/avatars/index.js b/functions/api/avatars/index.js index 58e7401..8552635 100644 --- a/functions/api/avatars/index.js +++ b/functions/api/avatars/index.js @@ -1,50 +1,63 @@ import { verifyJWT } from '../../middleware/auth'; import { createErrorResponse, createSuccessResponse } from '../../utils'; import { fileTypeFromBuffer } from 'file-type'; +import hCaptchaPlugin from "@cloudflare/pages-plugin-hcaptcha"; -export async function onRequestPut(context) { - try { +export const onRequestPut = [ + async (context) => { + return hCaptchaPlugin({ + secret: context.env.hcaptcha_secret_key, + sitekey: context.env.hcaptcha_site_key, + onError: (context) => { + console.error("hCaptcha error:", context.error); + return createErrorResponse("hCaptcha verification failed", 403); + } + })(context); + }, + async (context) => { const { request, env } = context; - // Verify the JWT token - const authResult = await verifyJWT(context); - if (authResult) { - return authResult; // Return the error response from the middleware + try { + // Verify the JWT token + const authResult = await verifyJWT(context); + if (authResult) { + return authResult; // Return the error response from the middleware + } + + const formData = await request.formData(); + const avatar = formData.get('avatar'); + + if (!avatar) { + return createErrorResponse("Missing avatar", 400); + } + + if (avatar.size > 1 * 1024 * 1024) { + // Entity too large + return createErrorResponse("Avatar must be less than 1MB", 413); + } + + const buffer = await avatar.arrayBuffer(); + const fileTypeResult = await fileTypeFromBuffer(buffer); + + if (!fileTypeResult) { + return createErrorResponse("Unsupported file type", 400); + } + + if (fileTypeResult.mime !== 'image/jpeg' && fileTypeResult.mime !== 'image/png') { + return createErrorResponse("Avatar must be a JPG or PNG image", 400); + } + + // Upload the avatar to R2 + const objectName = `avatars/${context.user.userId}`; + await env.MY_BUCKET.put(objectName, buffer); + + // Store the filename in D1 + await env.DB.prepare("UPDATE users SET avatar = ? WHERE id = ?").bind(objectName, context.user.userId).run(); + + return createSuccessResponse({ message: "Avatar uploaded successfully" }); + } catch (error) { + console.error("Avatar upload error:", error); + return createErrorResponse("Avatar upload failed", 500); } - - const formData = await request.formData(); - const avatar = formData.get('avatar'); - - if (!avatar) { - return createErrorResponse("Missing avatar", 400); - } - - if (avatar.size > 1 * 1024 * 1024) { - // Entity too large - return createErrorResponse("Avatar must be less than 1MB", 413); - } - - const buffer = await avatar.arrayBuffer(); - const fileTypeResult = await fileTypeFromBuffer(buffer); - - if (!fileTypeResult) { - return createErrorResponse("Unsupported file type", 400); - } - - if (fileTypeResult.mime !== 'image/jpeg' && fileTypeResult.mime !== 'image/png') { - return createErrorResponse("Avatar must be a JPG or PNG image", 400); - } - - // Upload the avatar to R2 - const objectName = `avatars/${context.user.userId}`; - await env.MY_BUCKET.put(objectName, buffer); - - // Store the filename in D1 - await env.DB.prepare("UPDATE users SET avatar = ? WHERE id = ?").bind(objectName, context.user.userId).run(); - - return createSuccessResponse({ message: "Avatar uploaded successfully" }); - } catch (error) { - console.error("Avatar upload error:", error); - return createErrorResponse("Avatar upload failed", 500); - } -} + }, +]; diff --git a/functions/api/login.js b/functions/api/login.js index fad8420..f8b43de 100644 --- a/functions/api/login.js +++ b/functions/api/login.js @@ -1,58 +1,80 @@ import { SignJWT } from 'jose'; import { createSuccessResponse, createErrorResponse } from "../utils"; +import hCaptchaPlugin from "@cloudflare/pages-plugin-hcaptcha"; -export async function onRequestPost(context) { - try { - const { request, env } = context; +export const onRequestPost = [ + async (context) => { + return hCaptchaPlugin({ + secret: context.env.hcaptcha_secret_key, + sitekey: context.env.hcaptcha_site_key, + onError: (context) => { + console.error("hCaptcha error:", context.error); + return createErrorResponse("hCaptcha verification failed", 403); + } + })(context); + }, + async (context) => { + try { + const { request, env } = context; + let payload; - const { username, password } = await request.json(); + try { + const formData = await request.formData(); + payload = JSON.parse(formData.get('payload')); + } catch (e) { + console.error("Payload parsing error:", e); + return createErrorResponse("Invalid payload", 400); + } - if (!username || !password) { - return createErrorResponse("Missing username or password", 400); - } + const { username, password } = payload; - if (username.length < 3) { - return createErrorResponse("Username must be at least 3 characters", 400); - } + if (!username || !password) { + return createErrorResponse("Missing username or password", 400); + } - if (password.length < 8) { - return createErrorResponse("Password must be at least 8 characters", 400); - } + if (username.length < 3) { + return createErrorResponse("Username must be at least 3 characters", 400); + } - if (!/^[a-zA-Z0-9]+$/.test(username)) { - return createErrorResponse("Username must be alphanumeric", 400); - } + if (password.length < 8) { + return createErrorResponse("Password must be at least 8 characters", 400); + } - // Get the stored password from D1 - const { results } = await env.DB.prepare("SELECT password FROM users WHERE username = ?").bind(username).all(); + if (!/^[a-zA-Z0-9]+$/.test(username)) { + return createErrorResponse("Username must be alphanumeric", 400); + } - if (!results || results.length === 0) { - return new Response(JSON.stringify({"error": "Invalid username or password"}), { status: 403, headers: { 'Content-Type': 'application/json' } }); - } + // Get the stored password from D1 + const { results } = await env.DB.prepare("SELECT password FROM users WHERE username = ?").bind(username).all(); - const storedPassword = results[0].password; + if (!results || results.length === 0) { + return new Response(JSON.stringify({"error": "Invalid username or password"}), { status: 403, headers: { 'Content-Type': 'application/json' } }); + } - // Compare the password to the stored password - if (password !== storedPassword) { - return new Response(JSON.stringify({"error": "Invalid username or password"}), { status: 403, headers: { 'Content-Type': 'application/json' } }); - } + const storedPassword = results[0].password; - // Get the user ID - const { results: userResults } = await env.DB.prepare("SELECT * FROM users WHERE username = ?").bind(username).all(); - const jwtPayload = (({ id, username }) => ({ id, username }))(userResults[0]); + // Compare the password to the stored password + if (password !== storedPassword) { + return new Response(JSON.stringify({"error": "Invalid username or password"}), { status: 403, headers: { 'Content-Type': 'application/json' } }); + } - // Generate a JWT token - const jwt = await new SignJWT(jwtPayload) - .setProtectedHeader({ alg: 'HS256' }) - .setIssuedAt() - .setIssuer('urn:example:issuer') - .setAudience('urn:example:audience') - .setExpirationTime('2h') - .sign(new TextEncoder().encode(env.JWT_SECRET)); + // Get the user ID + const { results: userResults } = await env.DB.prepare("SELECT * FROM users WHERE username = ?").bind(username).all(); + const jwtPayload = (({ id, username }) => ({ id, username }))(userResults[0]); - return createSuccessResponse({ jwt }); + // Generate a JWT token + const jwt = await new SignJWT(jwtPayload) + .setProtectedHeader({ alg: 'HS256' }) + .setIssuedAt() + .setIssuer('urn:example:issuer') + .setAudience('urn:example:audience') + .setExpirationTime('2h') + .sign(new TextEncoder().encode(env.JWT_SECRET)); + + return createSuccessResponse({ jwt }); } catch (error) { console.error("Login error:", error); return createErrorResponse("Login failed", 500); } -} + }, +]; diff --git a/functions/api/messages.js b/functions/api/messages.js index ab349b9..14aa798 100644 --- a/functions/api/messages.js +++ b/functions/api/messages.js @@ -1,5 +1,6 @@ import { verifyJWT } from '../middleware/auth'; import { createErrorResponse, createSuccessResponse } from '../utils'; +import hCaptchaPlugin from "@cloudflare/pages-plugin-hcaptcha"; export async function onRequestGet(context) { try { @@ -15,42 +16,57 @@ export async function onRequestGet(context) { } } -export async function onRequestPost(context) { - try { - const { request, env } = context; +export const onRequestPost = [ + async (context) => { + return hCaptchaPlugin({ + secret: context.env.hcaptcha_secret_key, + sitekey: context.env.hcaptcha_site_key, + onError: (context) => { + console.error("hCaptcha error:", context.error); + return createErrorResponse("hCaptcha verification failed", 403); + } + })(context); + }, + async (context) => { + try { + const { request, env } = context; + let payload; - // Verify the JWT token - const authResult = await verifyJWT(context); - if (authResult) { - return authResult; // Return the error response from the middleware - } + try { + const formData = await request.formData(); + payload = JSON.parse(formData.get('payload')); + } catch (e) { + console.error("Payload parsing error:", e); + return createErrorResponse("Invalid payload", 400); + } - const { message } = await request.json(); + const { message } = payload; - if (!message) { - return createErrorResponse("Empty message", 400); - } + if (!message) { + return createErrorResponse("Empty message", 400); + } - if (message.length > 200) { - return createErrorResponse("Message too long", 400); - } + if (message.length > 200) { + return createErrorResponse("Message too long", 400); + } - // Generate a unique ID for the message - const messageId = crypto.randomUUID(); + // Generate a unique ID for the message + const messageId = crypto.randomUUID(); - // Store the message in D1 - await env.DB.prepare("INSERT INTO messages (id, userId, message) VALUES (?, ?, ?)") - .bind(messageId, context.user.userId, message) - .run(); + // Store the message in D1 + await env.DB.prepare("INSERT INTO messages (id, userId, message) VALUES (?, ?, ?)") + .bind(messageId, context.user.userId, message) + .run(); - return new Response(JSON.stringify({ id: messageId, username: context.user.username, message }), { - headers: { 'Content-Type': 'application/json' }, - }); + return new Response(JSON.stringify({ id: messageId, username: context.user.username, message }), { + headers: { 'Content-Type': 'application/json' }, + }); } catch (error) { console.error("Message posting error:", error); return createErrorResponse("Message posting failed", 500); } -} + }, +]; export async function onRequestDelete(context) { try { diff --git a/functions/api/motto.js b/functions/api/motto.js index e4a4bd9..5df5835 100644 --- a/functions/api/motto.js +++ b/functions/api/motto.js @@ -1,25 +1,35 @@ import { verifyJWT } from '../middleware/auth'; import { createErrorResponse, createSuccessResponse } from '../utils'; +import hCaptchaPlugin from "@cloudflare/pages-plugin-hcaptcha"; -export async function onRequestGet(context) { - try { - // Verify the JWT token - const authResult = await verifyJWT(context); - if (authResult) { - return authResult; // Return the error response from the middleware +export const onRequestPost = [ + async (context) => { + return hCaptchaPlugin({ + secret: context.env.hcaptcha_secret_key, + sitekey: context.env.hcaptcha_site_key, + onError: (context) => { + console.error("hCaptcha error:", context.error); + return createErrorResponse("hCaptcha verification failed", 403); + } + })(context); + }, + async (context) => { + try { + // Verify the JWT token + const authResult = await verifyJWT(context); + if (authResult) { + return authResult; // Return the error response from the middleware + } + + const input = { prompt: '用繁體中文生成一句名言佳句' }; + + const response = await context.env.AI.run('@cf/meta/llama-3.2-1b-instruct', input); + const motto = response.response; + + return createSuccessResponse({ motto }); + } catch (error) { + console.error("Gen motto error:", error); + return createErrorResponse("Get motto failed", 500); } - - // Use Cloudflare AI Gateway to proxy the request to Google AI Studio - const ai = context.env.AI; - - const input = { prompt: '用繁體中文生成一句名言佳句' }; - - const response = await ai.run('@cf/meta/llama-3.2-1b-instruct', input); - const motto = response.response; - - return createSuccessResponse({ motto }); - } catch (error) { - console.error("Gen motto error:", error); - return createErrorResponse("Get motto failed", 500); - } -} + }, +]; diff --git a/functions/api/register.js b/functions/api/register.js index dd14ac2..fd97646 100644 --- a/functions/api/register.js +++ b/functions/api/register.js @@ -1,52 +1,62 @@ -import { createErrorResponse } from '../utils'; +import { createErrorResponse, createSuccessResponse } from '../utils'; +import hCaptchaPlugin from "@cloudflare/pages-plugin-hcaptcha"; -export async function onRequestPost(context) { - try { - const { request, env } = context; - - const { username, password } = await request.json(); - - if (!username || !password) { - return createErrorResponse("Missing username or password", 400); - } - - if (username.length < 3) { - return createErrorResponse("Username must be at least 3 characters", 400); - } - - if (password.length < 8) { - return createErrorResponse("Password must be at least 8 characters", 400); - } - - if (!/^[a-zA-Z0-9]+$/.test(username)) { - return createErrorResponse("Username must be alphanumeric", 400); - } - - // Check if the username already exists - const { results: existingUsers } = await env.DB.prepare("SELECT id FROM users WHERE username = ?").bind(username).all(); - if (existingUsers.length > 0) { - return createErrorResponse("Username already exists", 400); - } - - // Store the username and password in D1 - await env.DB.prepare("INSERT INTO users (username, password, avatar) VALUES (?, ?, ?)").bind(username, password, "avatars/default.png").run(); - - // Get the user ID - const { results } = await env.DB.prepare("SELECT id FROM users WHERE username = ?").bind(username).all(); - const userId = results[0].id; - - // Registration successful, return success response - return new Response( - JSON.stringify({ - success: true, - message: "Registration successful. Please login.", - }), - { - headers: { "Content-Type": "application/json" }, +export const onRequestPost = [ + async (context) => { + return hCaptchaPlugin({ + secret: context.env.hcaptcha_secret_key, + sitekey: context.env.hcaptcha_site_key, + onError: (context) => { + console.error("hCaptcha error:", context.error); + return createErrorResponse("hCaptcha verification failed", 403); } - ); - } catch (error) { - console.error("Registration error:", error); - return createErrorResponse("Server Error", 500); - } -} + })(context); + }, + async (context) => { + try { + const { request, env } = context; + let payload; + + try { + const formData = await request.formData(); + payload = JSON.parse(formData.get('payload')); + } catch (e) { + console.error("Payload parsing error:", e); + return createErrorResponse("Invalid payload", 400); + } + + const { username, password } = payload; + + if (!username || !password) { + return createErrorResponse("Missing username or password", 400); + } + + if (username.length < 3) { + return createErrorResponse("Username must be at least 3 characters", 400); + } + + if (password.length < 8) { + return createErrorResponse("Password must be at least 8 characters", 400); + } + + if (!/^[a-zA-Z0-9]+$/.test(username)) { + return createErrorResponse("Username must be alphanumeric", 400); + } + + // Check if the username already exists + const { results: existingUsers } = await env.DB.prepare("SELECT id FROM users WHERE username = ?").bind(username).all(); + if (existingUsers.length > 0) { + return createErrorResponse("Username already exists", 400); + } + + // Store the username and password in D1 + await env.DB.prepare("INSERT INTO users (username, password, avatar) VALUES (?, ?, ?)").bind(username, password, "avatars/default.png").run(); + + // Registration successful, return success response + return createSuccessResponse("Registration successful", 201); + } catch (error) { + console.error("Registration error:", error); + return createErrorResponse("Server Error", 500); + } + }, +]; diff --git a/index.html b/index.html index 3fbc713..a33c0ac 100644 --- a/index.html +++ b/index.html @@ -2,7 +2,7 @@
- +Powered By Cloudflare Workers AI
- +每日金句:
+{{ motto }}