All files / middleware auth.middleware.ts

100% Statements 71/71
100% Branches 45/45
100% Functions 5/5
100% Lines 71/71

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198                                                                                    2x 2x   2x 1x     1x                   2x 5x   5x 2x     3x 5x 5x 5x   3x 2x     1x                 2x 3x   3x 1x     2x 3x 3x 3x   2x 1x     1x                   2x 5x   5x 1x     4x 4x 1x     3x 5x 5x     5x     3x 1x 1x 1x 1x       2x 2x 1x           1x 1x 1x                 2x 6x   6x 1x     5x 5x 1x     4x 6x 6x     6x     4x 1x 1x 1x 1x       3x 3x 1x           2x 2x 2x    
// Authentication and Authorization Middleware
import type { MiddlewareHandler } from "hono";
import { UserTenantRolesDal } from "../dal/user-tenant-roles.dal";
import { getDb } from "../db";
import { ForbiddenError, UnauthorizedError } from "../lib/errors";
import { handleError } from "../lib/response";
import { createDualCache, type DualCache } from "../lib/cache";
import {
	getCachedUserRoles,
	isPlatformAdminFromRoles,
	isSuperAdminFromRoles,
	getProRoleFromRoles,
} from "../lib/role-cache";
 
type AuthUser = {
	id: string;
	name: string;
	email: string;
	phoneNumber: string | null;
};
 
type AuthSession = {
	id: string;
	userId: string;
	token: string;
	expiresAt: Date;
};
 
type AuthVariables = {
	user: AuthUser | null;
	session: AuthSession | null;
	db: ReturnType<typeof getDb>;
	cache: DualCache;
};
 
/**
 * Require authenticated user
 * Use this middleware to protect routes that require authentication
 */
export const requireAuth: MiddlewareHandler<{
	Bindings: CloudflareBindings;
	Variables: AuthVariables;
}> = async (c, next) => {
	const user = c.get("user");
 
	if (!user) {
		return handleError(c, new UnauthorizedError("Authentication required"));
	}
 
	await next();
};
 
/**
 * Require platform admin role (admin or super_admin)
 * Use this middleware to protect admin-only routes
 */
export const requirePlatformAdmin: MiddlewareHandler<{
	Bindings: CloudflareBindings;
	Variables: AuthVariables;
}> = async (c, next) => {
	const user = c.get("user");
 
	if (!user) {
		return handleError(c, new UnauthorizedError("Authentication required"));
	}
 
	const db = c.get("db") || getDb(c.env.DB);
	const rolesDal = new UserTenantRolesDal(db);
	const cache = c.get("cache") || createDualCache(c.env.KV_CACHE);
	const roles = await getCachedUserRoles(cache, rolesDal, user.id);
 
	if (!isPlatformAdminFromRoles(roles)) {
		return handleError(c, new ForbiddenError("Platform admin access required"));
	}
 
	await next();
};
 
/**
 * Require super admin role only
 */
export const requireSuperAdmin: MiddlewareHandler<{
	Bindings: CloudflareBindings;
	Variables: AuthVariables;
}> = async (c, next) => {
	const user = c.get("user");
 
	if (!user) {
		return handleError(c, new UnauthorizedError("Authentication required"));
	}
 
	const db = c.get("db") || getDb(c.env.DB);
	const rolesDal = new UserTenantRolesDal(db);
	const cache = c.get("cache") || createDualCache(c.env.KV_CACHE);
	const roles = await getCachedUserRoles(cache, rolesDal, user.id);
 
	if (!isSuperAdminFromRoles(roles)) {
		return handleError(c, new ForbiddenError("Super admin access required"));
	}
 
	await next();
};
 
/**
 * Require pro access (user must have a role for the pro)
 * This middleware expects proId in route params
 */
export const requireProAccess: MiddlewareHandler<{
	Bindings: CloudflareBindings;
	Variables: AuthVariables & { proId: string; proRole: string };
}> = async (c, next) => {
	const user = c.get("user");
 
	if (!user) {
		return handleError(c, new UnauthorizedError("Authentication required"));
	}
 
	const proId = c.req.param("proId");
	if (!proId) {
		return handleError(c, new ForbiddenError("Pro ID required"));
	}
 
	const db = c.get("db") || getDb(c.env.DB);
	const rolesDal = new UserTenantRolesDal(db);
	const cache = c.get("cache") || createDualCache(c.env.KV_CACHE);
 
	// Single cached query replaces 2 parallel DB calls
	const roles = await getCachedUserRoles(cache, rolesDal, user.id);
 
	// Platform admins have access to all pros
	if (isPlatformAdminFromRoles(roles)) {
		c.set("proId", proId);
		c.set("proRole", "admin");
		await next();
		return;
	}
 
	// Check if user has a role for this pro
	const proRole = getProRoleFromRoles(roles, proId);
	if (!proRole) {
		return handleError(
			c,
			new ForbiddenError("You do not have access to this pro"),
		);
	}
 
	c.set("proId", proId);
	c.set("proRole", proRole);
	await next();
};
 
/**
 * Require pro owner or manager role
 */
export const requireProManager: MiddlewareHandler<{
	Bindings: CloudflareBindings;
	Variables: AuthVariables & { proId: string; proRole: string };
}> = async (c, next) => {
	const user = c.get("user");
 
	if (!user) {
		return handleError(c, new UnauthorizedError("Authentication required"));
	}
 
	const proId = c.req.param("proId");
	if (!proId) {
		return handleError(c, new ForbiddenError("Pro ID required"));
	}
 
	const db = c.get("db") || getDb(c.env.DB);
	const rolesDal = new UserTenantRolesDal(db);
	const cache = c.get("cache") || createDualCache(c.env.KV_CACHE);
 
	// Single cached query replaces 2 parallel DB calls
	const roles = await getCachedUserRoles(cache, rolesDal, user.id);
 
	// Platform admins have access
	if (isPlatformAdminFromRoles(roles)) {
		c.set("proId", proId);
		c.set("proRole", "admin");
		await next();
		return;
	}
 
	// Check if user has owner or manager role
	const proRole = getProRoleFromRoles(roles, proId);
	if (!proRole || (proRole !== "owner" && proRole !== "manager")) {
		return handleError(
			c,
			new ForbiddenError("Pro owner or manager access required"),
		);
	}
 
	c.set("proId", proId);
	c.set("proRole", proRole);
	await next();
};