add vote uniqueness, public key caching, relative poll timeframe, auth/rate limiting, modern UI styling, and error handling
This commit is contained in:
82
server/api/users/[id].ts
Normal file
82
server/api/users/[id].ts
Normal file
@@ -0,0 +1,82 @@
|
||||
// server/api/users/[id].ts
|
||||
|
||||
// Simple in-memory rate limiter
|
||||
const rateLimitMap = new Map<string, { count: number; resetTime: number }>();
|
||||
const RATE_LIMIT_WINDOW = 60000; // 1 minute
|
||||
const RATE_LIMIT_MAX = 10; // 10 requests per minute per admin
|
||||
|
||||
function checkRateLimit(adminToken: string): boolean {
|
||||
const now = Date.now();
|
||||
const limit = rateLimitMap.get(adminToken);
|
||||
|
||||
if (!limit || now > limit.resetTime) {
|
||||
rateLimitMap.set(adminToken, { count: 1, resetTime: now + RATE_LIMIT_WINDOW });
|
||||
return true;
|
||||
}
|
||||
|
||||
if (limit.count >= RATE_LIMIT_MAX) {
|
||||
return false;
|
||||
}
|
||||
|
||||
limit.count++;
|
||||
return true;
|
||||
}
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const method = event.node.req.method;
|
||||
const userId = getRouterParam(event, 'id');
|
||||
|
||||
// We use Nitro's built-in storage.
|
||||
// 'polls' is the storage namespace.
|
||||
const storage = useStorage('users');
|
||||
|
||||
if (!userId) {
|
||||
throw createError({ statusCode: 400, statusMessage: 'User ID required' });
|
||||
}
|
||||
|
||||
// GET: Fetch the saved Yjs document state
|
||||
if (method === 'GET') {
|
||||
const data = await storage.getItem(`user:${userId}`);
|
||||
// Return the array of numbers (or null if it doesn't exist yet)
|
||||
return { public_key: data };
|
||||
}
|
||||
|
||||
// POST: Save a new Yjs document state
|
||||
if (method === 'POST') {
|
||||
// Check for authentication
|
||||
const authHeader = getHeader(event, 'authorization');
|
||||
const adminApiKey = process.env.ADMIN_API_KEY || 'default-admin-key-change-in-production';
|
||||
|
||||
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
||||
throw createError({ statusCode: 401, statusMessage: 'Authorization header required' });
|
||||
}
|
||||
|
||||
const token = authHeader.replace('Bearer ', '');
|
||||
if (token !== adminApiKey) {
|
||||
throw createError({ statusCode: 403, statusMessage: 'Invalid or expired token' });
|
||||
}
|
||||
|
||||
// Check rate limiting
|
||||
if (!checkRateLimit(token)) {
|
||||
throw createError({ statusCode: 429, statusMessage: 'Rate limit exceeded. Try again later.' });
|
||||
}
|
||||
|
||||
const body = await readBody(event);
|
||||
|
||||
if (body.public_key) {
|
||||
const data = await storage.getItem(`user:${userId}`);
|
||||
|
||||
if (data == undefined || data == null) {
|
||||
// Save the binary update (sent as an array of numbers) to storage
|
||||
await storage.setItem(`user:${userId}`, body.public_key);
|
||||
console.log("New User created: " + userId)
|
||||
console.log("Public Key: " + body.public_key);
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
throw createError({ statusCode: 400, statusMessage: 'User already exists.' });
|
||||
}
|
||||
|
||||
throw createError({ statusCode: 400, statusMessage: 'Invalid update payload' });
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user