All files / routes/admin user-auth.routes.ts

100% Statements 42/42
100% Branches 2/2
100% Functions 5/5
100% Lines 42/42

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                                          1x     1x 2x 2x 2x   2x       2x 2x             2x       1x 1x 1x 1x 1x   1x       1x           1x       1x 1x 1x 1x   1x       1x       1x       1x 2x 2x 2x   2x       2x                   2x       1x 3x 3x 3x 3x   3x         3x         3x 2x               1x                 1x    
// Admin Routes - User Session & Account Management
import { Hono } from "hono";
import { eq, and, gt } from "drizzle-orm";
import type { Dal } from "../../dal";
import type { getDb } from "../../db";
import type { Services } from "../../services";
import * as schema from "../../db/schema";
import { success, error } from "../../lib/response";
import { logger } from "../../lib/logger";
 
type Env = {
	Bindings: CloudflareBindings;
	Variables: {
		user: { id: string; name: string; email: string } | null;
		session: unknown;
		dal: Dal;
		services: Services;
		db: ReturnType<typeof getDb>;
	};
};
 
export const userAuthRoutes = new Hono<Env>();
 
// GET /:id/sessions — List active sessions for a user
userAuthRoutes.get("/:id/sessions", async (c) => {
	const db = c.get("db");
	const adminUser = c.get("user");
	const userId = c.req.param("id");
 
	logger.info(
		`[AUDIT] Admin ${adminUser?.email} listed sessions for user ${userId}`,
	);
 
	const now = new Date();
	const sessions = await db
		.select()
		.from(schema.sessions)
		.where(
			and(eq(schema.sessions.userId, userId), gt(schema.sessions.expiresAt, now)),
		);
 
	return success(c, sessions);
});
 
// DELETE /:id/sessions/:sessionId — Revoke a specific session
userAuthRoutes.delete("/:id/sessions/:sessionId", async (c) => {
	const db = c.get("db");
	const adminUser = c.get("user");
	const userId = c.req.param("id");
	const sessionId = c.req.param("sessionId");
 
	logger.info(
		`[AUDIT] Admin ${adminUser?.email} revoked session ${sessionId} for user ${userId}`,
	);
 
	await db
		.delete(schema.sessions)
		.where(
			and(eq(schema.sessions.id, sessionId), eq(schema.sessions.userId, userId)),
		);
 
	return success(c, { message: "Session revoked successfully" });
});
 
// DELETE /:id/sessions — Revoke ALL sessions for a user
userAuthRoutes.delete("/:id/sessions", async (c) => {
	const db = c.get("db");
	const adminUser = c.get("user");
	const userId = c.req.param("id");
 
	logger.info(
		`[AUDIT] Admin ${adminUser?.email} revoked all sessions for user ${userId}`,
	);
 
	await db
		.delete(schema.sessions)
		.where(eq(schema.sessions.userId, userId));
 
	return success(c, { message: "All sessions revoked successfully" });
});
 
// GET /:id/accounts — List linked accounts for a user
userAuthRoutes.get("/:id/accounts", async (c) => {
	const db = c.get("db");
	const adminUser = c.get("user");
	const userId = c.req.param("id");
 
	logger.info(
		`[AUDIT] Admin ${adminUser?.email} listed accounts for user ${userId}`,
	);
 
	const linkedAccounts = await db
		.select({
			id: schema.accounts.id,
			providerId: schema.accounts.providerId,
			accountId: schema.accounts.accountId,
			createdAt: schema.accounts.createdAt,
		})
		.from(schema.accounts)
		.where(eq(schema.accounts.userId, userId));
 
	return success(c, linkedAccounts);
});
 
// DELETE /:id/accounts/:accountId — Unlink a social account
userAuthRoutes.delete("/:id/accounts/:accountId", async (c) => {
	const db = c.get("db");
	const adminUser = c.get("user");
	const userId = c.req.param("id");
	const accountId = c.req.param("accountId");
 
	logger.info(
		`[AUDIT] Admin ${adminUser?.email} unlinked account ${accountId} for user ${userId}`,
	);
 
	// Safety check: count accounts for this user
	const userAccounts = await db
		.select({ id: schema.accounts.id })
		.from(schema.accounts)
		.where(eq(schema.accounts.userId, userId));
 
	if (userAccounts.length <= 1) {
		return error(
			c,
			"LAST_ACCOUNT",
			"Cannot unlink the last account. The user would be locked out.",
			400,
		);
	}
 
	await db
		.delete(schema.accounts)
		.where(
			and(
				eq(schema.accounts.id, accountId),
				eq(schema.accounts.userId, userId),
			),
		);
 
	return success(c, { message: "Account unlinked successfully" });
});