Fix auth security issues, add rate limiting, write Phase 2 audit
Security fixes: - Register route validates company exists before creating user - Rate limiting on auth routes (10 per 15min per IP) - Dev auth plugin guards against production use - Main.ts throws if JWT_SECRET missing in production Added Phase 2 audit doc (22) covering: - Built vs planning doc comparison - Security review with fixes applied - Duplicate code patterns identified - Standard POS feature gap analysis - Music-specific feature gaps 33 tests passing.
This commit is contained in:
@@ -3,11 +3,22 @@ import { eq } from 'drizzle-orm'
|
||||
import bcrypt from 'bcrypt'
|
||||
import { RegisterSchema, LoginSchema } from '@forte/shared/schemas'
|
||||
import { users } from '../../db/schema/users.js'
|
||||
import { companies } from '../../db/schema/stores.js'
|
||||
|
||||
const SALT_ROUNDS = 10
|
||||
|
||||
export const authRoutes: FastifyPluginAsync = async (app) => {
|
||||
app.post('/auth/register', async (request, reply) => {
|
||||
// Rate limit auth routes — 10 attempts per 15 minutes per IP
|
||||
const rateLimitConfig = {
|
||||
config: {
|
||||
rateLimit: {
|
||||
max: 10,
|
||||
timeWindow: '15 minutes',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
app.post('/auth/register', rateLimitConfig, async (request, reply) => {
|
||||
const parsed = RegisterSchema.safeParse(request.body)
|
||||
if (!parsed.success) {
|
||||
return reply.status(400).send({
|
||||
@@ -18,6 +29,25 @@ export const authRoutes: FastifyPluginAsync = async (app) => {
|
||||
const { email, password, firstName, lastName, role } = parsed.data
|
||||
const companyId = request.companyId
|
||||
|
||||
// Validate that the company exists
|
||||
if (!companyId) {
|
||||
return reply.status(400).send({
|
||||
error: { message: 'Company ID is required (x-company-id header)', statusCode: 400 },
|
||||
})
|
||||
}
|
||||
|
||||
const [company] = await app.db
|
||||
.select({ id: companies.id })
|
||||
.from(companies)
|
||||
.where(eq(companies.id, companyId))
|
||||
.limit(1)
|
||||
|
||||
if (!company) {
|
||||
return reply.status(400).send({
|
||||
error: { message: 'Invalid company', statusCode: 400 },
|
||||
})
|
||||
}
|
||||
|
||||
// Email is globally unique across all companies
|
||||
const existing = await app.db
|
||||
.select({ id: users.id })
|
||||
@@ -61,7 +91,7 @@ export const authRoutes: FastifyPluginAsync = async (app) => {
|
||||
return reply.status(201).send({ user, token })
|
||||
})
|
||||
|
||||
app.post('/auth/login', async (request, reply) => {
|
||||
app.post('/auth/login', rateLimitConfig, async (request, reply) => {
|
||||
const parsed = LoginSchema.safeParse(request.body)
|
||||
if (!parsed.success) {
|
||||
return reply.status(400).send({
|
||||
|
||||
Reference in New Issue
Block a user