5.9 KiB
5.9 KiB
11. tRPC Foundation — Auth Context, Middleware, and Protected Procedures
meta: id: shieldai-unified-restructure-11 feature: shieldai-unified-restructure priority: P0 depends_on: [shieldai-unified-restructure-10] tags: [backend, trpc, auth, api]
objective:
- Establish the tRPC foundation in the unified monolith: router factory, context builder with auth, middleware for protected routes, and error handling. Replace the stub tRPC setup with a production-ready auth-aware API layer.
deliverables:
web/src/server/api/utils.ts— tRPC initialization and helpers:tobject frominitTRPC.context<typeof createTRPCContext>().create()createTRPCRouterfactorypublicProcedure— unauthenticatedprotectedProcedure— requires valid JWT/sessionadminProcedure— requires admin rolerateLimitedProcedure— with basic rate limiting middleware
web/src/server/api/trpc.ts— Context builder:createTRPCContext({ req, res })that extracts auth from:- Session cookie (SolidStart session)
Authorization: Bearer <jwt>headerx-api-keyheader (for service-to-service or extension calls)
- Returns
{ db, user, session, apiKey }in context - Uses Drizzle
dbinstance fromweb/src/server/db/index.ts
web/src/server/api/root.ts— Root router:- Imports all sub-routers
- Exports
appRouterandAppRoutertype
web/src/routes/api/trpc/[trpc].ts— tRPC API handler:- SolidStart API route that handles all tRPC requests
- Uses
fetchRequestHandlerfrom@trpc/server/adapters/fetch - Wires
createTRPCContext
web/src/lib/api.ts— tRPC client:- Updates existing file to use correct
AppRoutertype - Adds auth token injection from localStorage/cookie
- Handles unauthorized responses (401 → redirect to login)
- Updates existing file to use correct
- Auth utilities:
web/src/server/auth/jwt.ts— JWT sign/verify usingjoseorjsonwebtokenweb/src/server/auth/session.ts— Session creation and validationweb/src/server/auth/password.ts— Password hashing withbcryptorargon2
steps:
- Install dependencies in
web/:@trpc/server,@trpc/client(already present, verify versions match)jose(modern JWT library, works in Edge runtime)bcryptjsorargon2for password hashingzodfor input validation (already present via valibot, but standardize on one — recommendzodfor wider ecosystem compatibility)
- Create
web/src/server/auth/jwt.ts:signJWT(payload, secret, expiresIn)verifyJWT(token, secret)- Use
josefor Edge compatibility
- Create
web/src/server/auth/password.ts:hashPassword(password)→ async hashverifyPassword(password, hash)→ boolean
- Create
web/src/server/api/trpc.ts(context builder):- Parse cookies from request headers
- Try session cookie first (SolidStart session)
- Fall back to Bearer JWT
- Fall back to x-api-key
- Look up user in database via Drizzle
- Return context with
user,session,db,apiKey
- Update
web/src/server/api/utils.ts:- Initialize tRPC with context type
- Define
publicProcedure - Define
protectedProcedureusing middleware that checksctx.userand throwsTRPCError.UNAUTHORIZEDif missing - Define
adminProcedurethat checksctx.user.role === 'admin'
- Update
web/src/server/api/root.ts:- Keep
exampleRouterfor now - Add placeholder imports for future routers
- Keep
- Update
web/src/routes/api/trpc/[trpc].ts:- Create SolidStart API route
- Use
fetchRequestHandlerwithrouter: appRouter,createContext: createTRPCContext
- Update
web/src/lib/api.ts:- Import
AppRouterfrom~/server/api/root - Add
httpBatchLinkwithheadersfunction that reads auth token from cookie/localStorage - Add error link that handles 401 by redirecting
- Import
- Test with a temporary protected route that returns
ctx.user.
steps:
- Unit: Context builder returns anonymous user for unauthenticated requests
- Unit: Context builder returns full user for valid JWT
- Unit:
protectedProcedurerejects unauthenticated requests with 401 - Unit:
adminProcedurerejects non-admin users with 403 - Integration: tRPC client can call public and protected endpoints
- E2E: Login flow sets cookie/JWT and subsequent requests are authenticated
acceptance_criteria:
createTRPCContextcorrectly builds context from session, JWT, or API keypublicProcedureallows unauthenticated accessprotectedProcedurerequires authentication and providesctx.useradminProcedurerequires admin role- tRPC API handler responds correctly at
/api/trpc - Client library (
web/src/lib/api.ts) connects and handles auth automatically - JWT sign/verify works with
joselibrary - Password hashing uses bcrypt or argon2
validation:
- Create a temporary tRPC router with public and protected queries
- Call public query from browser → should succeed
- Call protected query without auth → should return 401
- Call protected query with valid JWT → should return user data
- Run
cd web && pnpm testto verify auth unit tests
notes:
- The legacy Fastify auth middleware (
packages/api/src/middleware/auth.middleware.ts) is the reference for auth logic. Port its JWT verification and API key handling. - SolidStart sessions are cookie-based. The tRPC context should read the SolidStart session cookie and validate it.
- For the extension (task 27), API key auth will be important. Ensure
x-api-keyhandling is robust. - Consider using
lucia-authornext-authSolidStart integration for session management, but a custom JWT approach is fine if it matches the legacy system. - Standardize on
zodfor input validation across all tRPC routers. The current setup uses valibot — migrate to zod for consistency. - Keep auth utilities in
web/src/server/auth/separate from tRPC to allow reuse by other server code (e.g., background jobs).