fix: implement critical security remediation for authentication and authorization
- Add Clerk token verification to tRPC context (server/trpc/index.ts) - Remove client-controlled authorId/reviewedById from revisions router - Require JWT_SECRET environment variable, remove hardcoded fallback - Add table name validation to prevent SQL injection in backup logic - Fix TRPCContext type to use better-sqlite3 instead of LibSQL - Update revisions router tests to use proper tRPC v11+ API - Add resetInMemoryState function for test isolation Security fixes address: - Critical: Authentication bypass via missing token verification - Critical: User impersonation via client-controlled IDs - High: Insecure WebSocket defaults with hardcoded secrets - High: SQL injection vulnerability in backup logic All tests passing (24/24).
This commit is contained in:
@@ -4,6 +4,10 @@ import { revisionsRouter } from './revisions-router';
|
||||
import type { TRPCContext } from './types';
|
||||
import type { TRPCError } from '@trpc/server';
|
||||
import { t } from './router';
|
||||
import { drizzle } from 'drizzle-orm/better-sqlite3';
|
||||
import Database from 'better-sqlite3';
|
||||
import { projects, characters, scenes, characterRelationships, sceneCharacters } from '../../src/db/schema';
|
||||
import { verifyToken } from '@clerk/backend';
|
||||
|
||||
// App router combining all routers
|
||||
export const appRouter = t.router({
|
||||
@@ -13,13 +17,42 @@ export const appRouter = t.router({
|
||||
|
||||
export type AppRouter = typeof appRouter;
|
||||
|
||||
// Database instance (shared for now - should come from config)
|
||||
let dbInstance: ReturnType<typeof drizzle> | null = null;
|
||||
|
||||
function getDb() {
|
||||
if (dbInstance) return dbInstance;
|
||||
const sqlite = new Database('./data/frenocorp.db');
|
||||
dbInstance = drizzle(sqlite);
|
||||
return dbInstance;
|
||||
}
|
||||
|
||||
// Create tRPC HTTP server
|
||||
export function createTRPCServer(port: number = 8080) {
|
||||
const server = createHTTPServer({
|
||||
router: appRouter,
|
||||
createContext: async (): Promise<TRPCContext> => {
|
||||
createContext: async ({ req }): Promise<TRPCContext> => {
|
||||
const authHeader = req.headers.authorization;
|
||||
let userId: number | undefined = undefined;
|
||||
|
||||
if (authHeader && authHeader.startsWith('Bearer ')) {
|
||||
const token = authHeader.substring(7);
|
||||
try {
|
||||
const clerkSecretKey = process.env.CLERK_SECRET_KEY;
|
||||
if (!clerkSecretKey) {
|
||||
console.warn('CLERK_SECRET_KEY not set, skipping token verification');
|
||||
} else {
|
||||
const payload = await verifyToken(token, { secretKey: clerkSecretKey });
|
||||
userId = payload.sub ? parseInt(payload.sub, 10) : undefined;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to verify Clerk token:', error);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
userId: undefined,
|
||||
userId,
|
||||
db: getDb(),
|
||||
};
|
||||
},
|
||||
onError: ({ error, path }: { error: TRPCError; path: string | undefined }) => {
|
||||
|
||||
Reference in New Issue
Block a user