Add typed context and middleware for oRPC procedures

Use implement(contract).$context<APIContext>() for proper type safety
in all procedure handlers. Create authMiddleware and loginRequestMiddleware
using os.middleware() and apply with .use() on routes requiring auth.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
RevIQ
2026-01-09 15:36:26 +08:00
parent 829d365e80
commit a4d1f28f3d
12 changed files with 483 additions and 334 deletions

View File

@@ -3,9 +3,6 @@
* First step in the login flow - validates email and returns available auth methods
*/
import type { APIContext } from "../../context.js";
import { implement } from "@orpc/server";
import { contract } from "@reviq/api-contract";
import {
COOKIE_DURATIONS,
COOKIE_NAMES,
@@ -19,8 +16,7 @@ import {
} from "../../utils/crypto.js";
import { getGeoInfo, getUserAgent } from "../../utils/geo.js";
import { isDeviceTrusted } from "../../utils/session.js";
const os = implement(contract);
import { os } from "../base.js";
/**
* Create login request handler
@@ -33,7 +29,6 @@ const os = implement(contract);
*/
export const createLoginRequest = os.auth.createLoginRequest.handler(
async ({ input, context }) => {
const ctx = context as APIContext;
const { email: rawEmail } = input;
// Normalize email to lowercase
@@ -41,14 +36,14 @@ export const createLoginRequest = os.auth.createLoginRequest.handler(
// Read or generate device fingerprint
let deviceFingerprint = getCookie(
ctx.reqHeaders,
context.reqHeaders,
COOKIE_NAMES.DEVICE_FINGERPRINT,
);
if (!deviceFingerprint) {
deviceFingerprint = generateDeviceFingerprint();
setCookie(
ctx.resHeaders,
context.resHeaders,
COOKIE_NAMES.DEVICE_FINGERPRINT,
deviceFingerprint,
COOKIE_OPTIONS.device,
@@ -56,7 +51,7 @@ export const createLoginRequest = os.auth.createLoginRequest.handler(
}
// Look up user by email
const user = await ctx.db
const user = await context.db
.selectFrom("users")
.select(["id", "password_hash"])
.where("email", "=", email)
@@ -70,7 +65,7 @@ export const createLoginRequest = os.auth.createLoginRequest.handler(
// Set placeholder login request token cookie
setCookie(
ctx.resHeaders,
context.resHeaders,
COOKIE_NAMES.LOGIN_REQUEST_TOKEN,
placeholderToken,
COOKIE_OPTIONS.loginRequest,
@@ -89,13 +84,13 @@ export const createLoginRequest = os.auth.createLoginRequest.handler(
// Check if device is trusted
const isTrustedDevice = await isDeviceTrusted(
ctx.db,
context.db,
userId,
deviceFingerprint,
);
// Check if user has passkey
const passkey = await ctx.db
const passkey = await context.db
.selectFrom("passkeys")
.select(["id"])
.where("user_id", "=", userId)
@@ -106,13 +101,13 @@ export const createLoginRequest = os.auth.createLoginRequest.handler(
const hasPassword = user.password_hash !== null;
// Get geo info and user agent
const geo = getGeoInfo(ctx.reqHeaders);
const userAgent = getUserAgent(ctx.reqHeaders);
const geo = getGeoInfo(context.reqHeaders);
const userAgent = getUserAgent(context.reqHeaders);
// Create login request
const expiresAt = generateExpiry(COOKIE_DURATIONS.LOGIN_REQUEST);
const loginRequest = await ctx.db
const loginRequest = await context.db
.insertInto("login_requests")
.values({
user_id: userId,
@@ -132,7 +127,7 @@ export const createLoginRequest = os.auth.createLoginRequest.handler(
// Set login request token cookie with the real login request ID
setCookie(
ctx.resHeaders,
context.resHeaders,
COOKIE_NAMES.LOGIN_REQUEST_TOKEN,
loginRequestId,
COOKIE_OPTIONS.loginRequest,