refractor code,

This commit is contained in:
root
2026-05-21 12:35:49 -04:00
parent e74681e810
commit f009ca10c6
158 changed files with 215801 additions and 5884 deletions
+32
View File
@@ -0,0 +1,32 @@
import { Request, Response } from 'express'
/**
* Sends a uniform auth-error JSON response.
* All auth middleware must go through this function so the shape is identical
* to what the errorMiddleware produces for AppError instances.
*/
export function sendUnauthorized(res: Response, error: string, message: string) {
return res.status(401).json({ error, message, statusCode: 401 })
}
export function getCookie(req: Request, name: string): string | null {
const cookieHeader = req.headers.cookie
if (!cookieHeader) return null
for (const chunk of cookieHeader.split(';')) {
const [rawName, ...rawValue] = chunk.trim().split('=')
if (rawName === name) {
return decodeURIComponent(rawValue.join('='))
}
}
return null
}
export function sendForbidden(res: Response, error: string, message: string, extra?: Record<string, unknown>) {
return res.status(403).json({ error, message, statusCode: 403, ...extra })
}
export function sendPaymentRequired(res: Response, error: string, message: string, extra?: Record<string, unknown>) {
return res.status(402).json({ error, message, statusCode: 402, ...extra })
}
+31 -26
View File
@@ -2,6 +2,7 @@ import { Request, Response, NextFunction } from 'express'
import jwt from 'jsonwebtoken'
import { prisma } from '../lib/prisma'
import { AdminRole } from '@rentaldrivego/database'
import { sendUnauthorized, sendForbidden } from './authHelpers'
const ROLE_RANK: Record<AdminRole, number> = {
SUPER_ADMIN: 5,
@@ -11,48 +12,52 @@ const ROLE_RANK: Record<AdminRole, number> = {
VIEWER: 1,
}
/**
* Requires a valid admin Bearer token.
*
* Guarantees on success:
* req.admin — the full AdminUser record
*/
export async function requireAdminAuth(req: Request, res: Response, next: NextFunction) {
const authHeader = req.headers.authorization
const token = authHeader?.startsWith('Bearer ') ? authHeader.slice(7) : null
if (!token) {
return res.status(401).json({ error: 'unauthenticated', message: 'Admin authentication required', statusCode: 401 })
}
if (!token) return sendUnauthorized(res, 'unauthenticated', 'Admin authentication required')
let payload: { sub: string; type: string }
try {
const payload = jwt.verify(token, process.env.JWT_SECRET!) as { sub: string; type: string }
if (payload.type !== 'admin') {
return res.status(401).json({ error: 'invalid_token', message: 'Invalid token type', statusCode: 401 })
}
const admin = await prisma.adminUser.findUnique({ where: { id: payload.sub } })
if (!admin || !admin.isActive) {
return res.status(401).json({ error: 'unauthenticated', message: 'Admin account not found or deactivated', statusCode: 401 })
}
req.admin = admin
next()
payload = jwt.verify(token, process.env.JWT_SECRET!) as { sub: string; type: string }
} catch {
return res.status(401).json({ error: 'invalid_token', message: 'Invalid or expired admin token', statusCode: 401 })
return sendUnauthorized(res, 'invalid_token', 'Invalid or expired admin token')
}
if (payload.type !== 'admin') {
return sendUnauthorized(res, 'invalid_token', 'Invalid token type')
}
const admin = await prisma.adminUser.findUnique({ where: { id: payload.sub } })
if (!admin || !admin.isActive) {
return sendUnauthorized(res, 'unauthenticated', 'Admin account not found or deactivated')
}
req.admin = admin
next()
}
/**
* Requires the authenticated admin to have at least `minimumRole`.
* Must be applied after `requireAdminAuth`.
*/
export function requireAdminRole(minimumRole: AdminRole) {
return (req: Request, res: Response, next: NextFunction) => {
const admin = req.admin
if (!admin) {
return res.status(401).json({ error: 'unauthenticated', message: 'Admin authentication required', statusCode: 401 })
}
if (!admin) return sendUnauthorized(res, 'unauthenticated', 'Admin authentication required')
const rank = ROLE_RANK[admin.role] ?? 0
const required = ROLE_RANK[minimumRole] ?? 99
const rank = ROLE_RANK[admin.role] ?? 0
const required = ROLE_RANK[minimumRole] ?? 99
if (rank < required) {
return res.status(403).json({
error: 'forbidden',
message: `This action requires the ${minimumRole} role or higher`,
statusCode: 403,
})
return sendForbidden(res, 'forbidden', `This action requires the ${minimumRole} role or higher`)
}
next()
+48 -39
View File
@@ -1,45 +1,54 @@
import { Request, Response, NextFunction } from 'express'
import jwt from 'jsonwebtoken'
import { prisma } from '../lib/prisma'
import { getCookie, sendUnauthorized } from './authHelpers'
/**
* Validates a company-scoped Bearer token and loads the employee + company onto `req`.
*
* Guarantees on success:
* req.employee — full employee record (with company relation)
* req.company — the employee's company
* req.companyId — string shorthand for req.company.id
*/
async function authenticateCompanyRequest(req: Request, res: Response, next: NextFunction, allowCookie = false) {
const authHeader = req.headers.authorization
const bearerToken = authHeader?.startsWith('Bearer ') ? authHeader.slice(7) : null
const cookieToken = allowCookie ? getCookie(req, 'employee_token') : null
const token = bearerToken ?? cookieToken
if (!token) return sendUnauthorized(res, 'unauthenticated', 'Authentication required')
let payload: { sub: string; type: string }
try {
payload = jwt.verify(token, process.env.JWT_SECRET!) as { sub: string; type: string }
} catch {
return sendUnauthorized(res, 'invalid_token', 'Invalid or expired session token')
}
if (payload.type !== 'employee') {
return sendUnauthorized(res, 'invalid_token', 'Invalid token type for this endpoint')
}
const employee = await prisma.employee.findUnique({
where: { id: payload.sub },
include: { company: true },
})
if (!employee || !employee.isActive) {
return sendUnauthorized(res, 'unauthenticated', 'Employee account not found or inactive')
}
req.employee = employee
req.company = employee.company
req.companyId = employee.companyId
next()
}
export async function requireCompanyAuth(req: Request, res: Response, next: NextFunction) {
const authHeader = req.headers.authorization
const sessionToken = authHeader?.startsWith('Bearer ') ? authHeader.slice(7) : null
if (!sessionToken) {
return res.status(401).json({
error: 'unauthenticated',
message: 'Authentication required',
statusCode: 401,
})
}
try {
const payload = jwt.verify(sessionToken, process.env.JWT_SECRET!) as { sub: string; type: string }
if (payload.type === 'employee') {
const employee = await prisma.employee.findUnique({
where: { id: payload.sub },
include: { company: true },
})
if (!employee || !employee.isActive) {
return res.status(401).json({
error: 'unauthenticated',
message: 'Employee account not found or inactive',
statusCode: 401,
})
}
req.employee = employee
req.company = employee.company
req.companyId = employee.companyId
return next()
}
} catch {
return res.status(401).json({
error: 'invalid_token',
message: 'Invalid or expired session token',
statusCode: 401,
})
}
return authenticateCompanyRequest(req, res, next)
}
export async function requireCompanyDocumentAuth(req: Request, res: Response, next: NextFunction) {
return authenticateCompanyRequest(req, res, next, true)
}
+33 -22
View File
@@ -1,46 +1,57 @@
import { Request, Response, NextFunction } from 'express'
import jwt from 'jsonwebtoken'
import { prisma } from '../lib/prisma'
import { sendUnauthorized } from './authHelpers'
/**
* Requires a valid renter Bearer token.
*
* Guarantees on success:
* req.renterId — the authenticated renter's id
*/
export async function requireRenterAuth(req: Request, res: Response, next: NextFunction) {
const authHeader = req.headers.authorization
const token = authHeader?.startsWith('Bearer ') ? authHeader.slice(7) : null
if (!token) {
return res.status(401).json({ error: 'unauthenticated', message: 'Renter authentication required', statusCode: 401 })
}
if (!token) return sendUnauthorized(res, 'unauthenticated', 'Renter authentication required')
let payload: { sub: string; type: string }
try {
const payload = jwt.verify(token, process.env.JWT_SECRET!) as { sub: string; type: string }
if (payload.type !== 'renter') {
return res.status(401).json({ error: 'invalid_token', message: 'Invalid token type', statusCode: 401 })
}
const renter = await prisma.renter.findUnique({ where: { id: payload.sub } })
if (!renter || !renter.isActive) {
return res.status(401).json({ error: 'unauthenticated', message: 'Renter account not found or inactive', statusCode: 401 })
}
req.renterId = renter.id
next()
payload = jwt.verify(token, process.env.JWT_SECRET!) as { sub: string; type: string }
} catch {
return res.status(401).json({ error: 'invalid_token', message: 'Invalid or expired token', statusCode: 401 })
return sendUnauthorized(res, 'invalid_token', 'Invalid or expired token')
}
if (payload.type !== 'renter') {
return sendUnauthorized(res, 'invalid_token', 'Invalid token type')
}
const renter = await prisma.renter.findUnique({ where: { id: payload.sub } })
if (!renter || !renter.isActive) {
return sendUnauthorized(res, 'unauthenticated', 'Renter account not found or inactive')
}
req.renterId = renter.id
next()
}
export async function optionalRenterAuth(req: Request, res: Response, next: NextFunction) {
/**
* Optionally extracts renter identity from a Bearer token.
* Never blocks the request — invalid or absent tokens are silently ignored.
*
* Sets on success:
* req.renterId — the authenticated renter's id (if token is valid)
*/
export async function optionalRenterAuth(req: Request, _res: Response, next: NextFunction) {
const authHeader = req.headers.authorization
const token = authHeader?.startsWith('Bearer ') ? authHeader.slice(7) : null
if (!token) return next()
try {
const payload = jwt.verify(token, process.env.JWT_SECRET!) as { sub: string; type: string }
if (payload.type === 'renter') {
req.renterId = payload.sub
}
if (payload.type === 'renter') req.renterId = payload.sub
} catch {
// Optional — ignore invalid tokens
// Optional — silently ignore invalid tokens
}
next()
+8 -8
View File
@@ -1,5 +1,6 @@
import { Request, Response, NextFunction } from 'express'
import { EmployeeRole } from '@rentaldrivego/database'
import { sendUnauthorized, sendForbidden } from './authHelpers'
const ROLE_RANK: Record<EmployeeRole, number> = {
OWNER: 3,
@@ -7,21 +8,20 @@ const ROLE_RANK: Record<EmployeeRole, number> = {
AGENT: 1,
}
/**
* Requires the authenticated employee to have at least `minimumRole`.
* Must be applied after `requireCompanyAuth`.
*/
export function requireRole(minimumRole: EmployeeRole) {
return (req: Request, res: Response, next: NextFunction) => {
const employee = req.employee
if (!employee) {
return res.status(401).json({ error: 'unauthenticated', message: 'Authentication required', statusCode: 401 })
}
if (!employee) return sendUnauthorized(res, 'unauthenticated', 'Authentication required')
const employeeRank = ROLE_RANK[employee.role] ?? 0
const requiredRank = ROLE_RANK[minimumRole] ?? 99
const requiredRank = ROLE_RANK[minimumRole] ?? 99
if (employeeRank < requiredRank) {
return res.status(403).json({
error: 'forbidden',
message: `This action requires the ${minimumRole} role or higher`,
statusCode: 403,
return sendForbidden(res, 'forbidden', `This action requires the ${minimumRole} role or higher`, {
requiredRole: minimumRole,
yourRole: employee.role,
})
+17 -12
View File
@@ -1,23 +1,28 @@
import { Request, Response, NextFunction } from 'express'
import { sendUnauthorized, sendPaymentRequired } from './authHelpers'
const BLOCKED_STATUSES = ['SUSPENDED', 'PENDING']
/**
* Blocks requests for companies with lapsed or unactivated subscriptions.
* Must be applied after `requireTenant`.
*
* Guarantees on success:
* req.company.status is not SUSPENDED or PENDING
*/
export function requireSubscription(req: Request, res: Response, next: NextFunction) {
const company = req.company
if (!company) {
return res.status(401).json({ error: 'unauthenticated', message: 'No company context', statusCode: 401 })
}
if (!company) return sendUnauthorized(res, 'unauthenticated', 'No company context')
if (BLOCKED_STATUSES.includes(company.status)) {
return res.status(402).json({
error: 'subscription_' + company.status.toLowerCase(),
message:
company.status === 'SUSPENDED'
? 'Your account has been suspended. Please contact support or renew your subscription.'
: 'Your account is pending activation. Please complete your subscription setup.',
statusCode: 402,
billingUrl: `${process.env.NEXT_PUBLIC_DASHBOARD_URL}/billing`,
})
return sendPaymentRequired(
res,
`subscription_${company.status.toLowerCase()}`,
company.status === 'SUSPENDED'
? 'Your account has been suspended. Please contact support or renew your subscription.'
: 'Your account is pending activation. Please complete your subscription setup.',
{ billingUrl: `${process.env.NEXT_PUBLIC_DASHBOARD_URL}/billing` },
)
}
next()
+11 -10
View File
@@ -1,22 +1,23 @@
import { Request, Response, NextFunction } from 'express'
import { prisma } from '../lib/prisma'
import { sendUnauthorized } from './authHelpers'
/**
* Loads the company record and validates it exists.
* Must be applied after `requireCompanyAuth`.
*
* Guarantees on success:
* req.company — full Company record
* req.companyId — string id (already set by requireCompanyAuth)
*/
export async function requireTenant(req: Request, res: Response, next: NextFunction) {
if (!req.companyId) {
return res.status(401).json({
error: 'unauthenticated',
message: 'Tenant context missing — requireCompanyAuth must run first',
statusCode: 401,
})
return sendUnauthorized(res, 'unauthenticated', 'Tenant context missing — requireCompanyAuth must run first')
}
const company = await prisma.company.findUnique({ where: { id: req.companyId } })
if (!company) {
return res.status(401).json({
error: 'company_not_found',
message: 'Company not found',
statusCode: 401,
})
return sendUnauthorized(res, 'company_not_found', 'Company not found')
}
req.company = company