feat: improve learning and platform operations
This commit is contained in:
@@ -4,6 +4,7 @@ import { hashPassword, verifyPassword } from '../auth/passwords.js';
|
||||
import { hashToken } from '../auth/account-tokens.js';
|
||||
import type { AuthUser } from '../auth/plugin.js';
|
||||
import { pool } from '../db/pool.js';
|
||||
import { config } from '../config.js';
|
||||
|
||||
const credentialsSchema = z.object({
|
||||
email: z.string().email().transform((email) => email.toLowerCase()),
|
||||
@@ -32,7 +33,27 @@ const serializeUser = (user: UserRow): AuthUser => ({
|
||||
});
|
||||
|
||||
export const authRoutes: FastifyPluginAsync = async (app) => {
|
||||
const publicAttempts = new Map<string, number[]>();
|
||||
const allowPublicAuthAttempt = (ip: string) => {
|
||||
const now = Date.now();
|
||||
const earliestAllowed = now - config.AUTH_RATE_LIMIT_WINDOW_SECONDS * 1000;
|
||||
const attempts = (publicAttempts.get(ip) || []).filter((timestamp) => timestamp > earliestAllowed);
|
||||
if (attempts.length >= config.AUTH_RATE_LIMIT_MAX) {
|
||||
publicAttempts.set(ip, attempts);
|
||||
return false;
|
||||
}
|
||||
attempts.push(now);
|
||||
publicAttempts.set(ip, attempts);
|
||||
return true;
|
||||
};
|
||||
const rejectIfRateLimited = (ip: string, reply: { code: (status: number) => { send: (payload: object) => unknown } }) => {
|
||||
if (allowPublicAuthAttempt(ip)) return false;
|
||||
reply.code(429).send({ error: 'Too many attempts. Please try again later.' });
|
||||
return true;
|
||||
};
|
||||
|
||||
app.post('/accept-invitation', async (request, reply) => {
|
||||
if (rejectIfRateLimited(request.ip, reply)) return;
|
||||
const input = tokenPasswordSchema.extend({ name: z.string().trim().min(2).max(120) }).parse(request.body);
|
||||
const client = await pool.connect();
|
||||
try {
|
||||
@@ -63,6 +84,7 @@ export const authRoutes: FastifyPluginAsync = async (app) => {
|
||||
});
|
||||
|
||||
app.post('/reset-password', async (request, reply) => {
|
||||
if (rejectIfRateLimited(request.ip, reply)) return;
|
||||
const input = tokenPasswordSchema.parse(request.body);
|
||||
const client = await pool.connect();
|
||||
try {
|
||||
@@ -87,6 +109,7 @@ export const authRoutes: FastifyPluginAsync = async (app) => {
|
||||
}
|
||||
});
|
||||
app.post('/register', async (request, reply) => {
|
||||
if (rejectIfRateLimited(request.ip, reply)) return;
|
||||
const input = registerSchema.parse(request.body);
|
||||
const passwordHash = await hashPassword(input.password);
|
||||
|
||||
@@ -109,6 +132,7 @@ export const authRoutes: FastifyPluginAsync = async (app) => {
|
||||
});
|
||||
|
||||
app.post('/login', async (request, reply) => {
|
||||
if (rejectIfRateLimited(request.ip, reply)) return;
|
||||
const input = credentialsSchema.parse(request.body);
|
||||
const result = await pool.query<UserRow>(
|
||||
`select id, email, display_name, role, password_hash, is_active from users where email = $1`,
|
||||
|
||||
Reference in New Issue
Block a user