Extracted domain-specific sub-routers from monolithic `server/routes/admin.ts` and `server/routes/auth.ts` into isolated modules within `server/routes/admin/` and `server/routes/auth/` respectively. The original entry routers were updated to import and assemble these sub-routers without breaking their current HTTP interface or rate limiting/authorization middleware. Testing and linting were run ensuring perfect functionality and 100% test passing score. Co-authored-by: mrteye <1945243+mrteye@users.noreply.github.com>
229 lines
7.2 KiB
TypeScript
229 lines
7.2 KiB
TypeScript
import { Hono } from "jsr:@hono/hono@4";
|
|
import { encodeBase64Url } from "jsr:@std/encoding@1/base64url";
|
|
import { sqlWrapper } from "../../db.ts";
|
|
import { valkey } from "../../valkey.ts";
|
|
import { auditWrapper } from "../../audit.ts";
|
|
import { getAuthenticatedUser } from "../../auth-session.ts";
|
|
import { getClientIp } from "../../middleware.ts";
|
|
|
|
export const usersAdminRoutes = new Hono();
|
|
|
|
usersAdminRoutes.get("/", async (c) => {
|
|
const auth = await getAuthenticatedUser(c);
|
|
if (!auth) return c.json({ error: "Unauthorized" }, 401);
|
|
|
|
const users = await sqlWrapper.sql`
|
|
SELECT id, username, display_name, account_status
|
|
FROM users
|
|
ORDER BY username ASC
|
|
`;
|
|
|
|
return c.json({ users });
|
|
});
|
|
|
|
usersAdminRoutes.post("/:id/status", async (c) => {
|
|
const auth = await getAuthenticatedUser(c);
|
|
if (!auth) return c.json({ error: "Unauthorized" }, 401);
|
|
|
|
const targetUserId = c.req.param("id");
|
|
const { status } = await c.req.json();
|
|
|
|
if (!["active", "pending", "suspended"].includes(status)) {
|
|
return c.json({ error: "Invalid status" }, 400);
|
|
}
|
|
|
|
const targetUser = await sqlWrapper
|
|
.sql`UPDATE users SET account_status = ${status} WHERE id = ${targetUserId} RETURNING id`
|
|
.then((res: any) => res[0]);
|
|
|
|
if (!targetUser) {
|
|
return c.json({ error: "User not found" }, 404);
|
|
}
|
|
|
|
auditWrapper.auditLog(auth.userId, "user_status_changed", targetUserId, {
|
|
newStatus: status,
|
|
}, getClientIp(c));
|
|
|
|
return c.json({ success: true });
|
|
});
|
|
|
|
usersAdminRoutes.post("/:id/profile", async (c) => {
|
|
const auth = await getAuthenticatedUser(c);
|
|
if (!auth) return c.json({ error: "Unauthorized" }, 401);
|
|
|
|
const targetUserId = c.req.param("id");
|
|
const { displayName } = await c.req.json();
|
|
|
|
const targetUser = await sqlWrapper
|
|
.sql`UPDATE users SET display_name = ${
|
|
displayName?.trim() || null
|
|
} WHERE id = ${targetUserId} RETURNING id, username, display_name`
|
|
.then((res: any) => res[0]);
|
|
|
|
if (!targetUser) return c.json({ error: "User not found" }, 404);
|
|
|
|
auditWrapper.auditLog(auth.userId, "user_profile_updated", targetUserId, {
|
|
display_name: targetUser.display_name,
|
|
}, getClientIp(c));
|
|
|
|
return c.json({ success: true, user: targetUser });
|
|
});
|
|
|
|
usersAdminRoutes.get("/:id/grants", async (c) => {
|
|
const auth = await getAuthenticatedUser(c);
|
|
if (!auth) return c.json({ error: "Unauthorized" }, 401);
|
|
|
|
const targetUserId = c.req.param("id");
|
|
const grants = await sqlWrapper.sql`
|
|
SELECT g.id, g.app_id, g.role, g.created_at, a.name AS app_name, a.spiffe_id
|
|
FROM grants g
|
|
JOIN apps a ON g.app_id = a.id
|
|
WHERE g.user_id = ${targetUserId}
|
|
ORDER BY a.name ASC
|
|
`;
|
|
return c.json({ grants });
|
|
});
|
|
|
|
usersAdminRoutes.post("/:id/grants", async (c) => {
|
|
const auth = await getAuthenticatedUser(c);
|
|
if (!auth) return c.json({ error: "Unauthorized" }, 401);
|
|
|
|
const targetUserId = c.req.param("id");
|
|
const { appId, role } = await c.req.json();
|
|
|
|
if (!appId || !role) {
|
|
return c.json({ error: "appId and role are required" }, 400);
|
|
}
|
|
|
|
const app = await sqlWrapper
|
|
.sql`SELECT id, name FROM apps WHERE id = ${appId}`.then(
|
|
(res: any) => res[0],
|
|
);
|
|
if (!app) return c.json({ error: "Application not found" }, 404);
|
|
|
|
const targetUser = await sqlWrapper
|
|
.sql`SELECT id, username FROM users WHERE id = ${targetUserId}`.then(
|
|
(res: any) => res[0],
|
|
);
|
|
if (!targetUser) return c.json({ error: "User not found" }, 404);
|
|
|
|
await sqlWrapper.sql`
|
|
INSERT INTO grants (user_id, app_id, role)
|
|
VALUES (${targetUserId}, ${appId}, ${role})
|
|
ON CONFLICT (user_id, app_id) DO UPDATE SET role = ${role}
|
|
`;
|
|
|
|
auditWrapper.auditLog(
|
|
auth.userId,
|
|
"user_grant_assigned",
|
|
targetUserId,
|
|
{ app_id: appId, app_name: app.name, role },
|
|
getClientIp(c),
|
|
);
|
|
|
|
return c.json({ success: true });
|
|
});
|
|
|
|
usersAdminRoutes.delete("/:id/grants/:appId", async (c) => {
|
|
const auth = await getAuthenticatedUser(c);
|
|
if (!auth) return c.json({ error: "Unauthorized" }, 401);
|
|
|
|
const { id: targetUserId, appId } = c.req.param();
|
|
|
|
const grant = await sqlWrapper.sql`
|
|
DELETE FROM grants
|
|
WHERE user_id = ${targetUserId} AND app_id = ${appId}
|
|
RETURNING id
|
|
`.then((res: any) => res[0]);
|
|
|
|
if (grant) {
|
|
auditWrapper.auditLog(
|
|
auth.userId,
|
|
"user_grant_revoked",
|
|
targetUserId,
|
|
{ app_id: appId },
|
|
getClientIp(c),
|
|
);
|
|
return c.json({ success: true });
|
|
}
|
|
|
|
return c.json({ error: "Grant not found" }, 404);
|
|
});
|
|
|
|
usersAdminRoutes.get("/:id", async (c) => {
|
|
const auth = await getAuthenticatedUser(c);
|
|
if (!auth) return c.json({ error: "Unauthorized" }, 401);
|
|
const targetUserId = c.req.param("id");
|
|
const user = await sqlWrapper
|
|
.sql`SELECT id, username, display_name, account_status FROM users WHERE id = ${targetUserId}`
|
|
.then((res: any) => res[0]);
|
|
if (!user) return c.json({ error: "User not found" }, 404);
|
|
const sessions = await sqlWrapper
|
|
.sql`SELECT id, created_at, expires_at FROM sessions WHERE user_id = ${targetUserId} ORDER BY created_at DESC`;
|
|
const passkeys = await sqlWrapper
|
|
.sql`SELECT id, credential_id, counter FROM passkeys WHERE user_id = ${targetUserId}`;
|
|
return c.json({ user, sessions, passkeys });
|
|
});
|
|
|
|
usersAdminRoutes.delete("/:id/sessions", async (c) => {
|
|
const auth = await getAuthenticatedUser(c);
|
|
if (!auth) return c.json({ error: "Unauthorized" }, 401);
|
|
const targetUserId = c.req.param("id");
|
|
const sessions = await sqlWrapper
|
|
.sql`DELETE FROM sessions WHERE user_id = ${targetUserId} RETURNING id`;
|
|
for (const session of sessions) {
|
|
try {
|
|
await valkey.del(session.id);
|
|
} catch (_err) {}
|
|
}
|
|
auditWrapper.auditLog(
|
|
auth.userId,
|
|
"admin_all_sessions_revoked",
|
|
targetUserId,
|
|
null,
|
|
getClientIp(c),
|
|
);
|
|
return c.json({ success: true });
|
|
});
|
|
|
|
usersAdminRoutes.delete("/:userId/passkeys/:passkeyId", async (c) => {
|
|
const auth = await getAuthenticatedUser(c);
|
|
if (!auth) return c.json({ error: "Unauthorized" }, 401);
|
|
const { userId, passkeyId } = c.req.param();
|
|
const passkey = await sqlWrapper
|
|
.sql`DELETE FROM passkeys WHERE id = ${passkeyId} AND user_id = ${userId} RETURNING id`
|
|
.then((res: any) => res[0]);
|
|
if (passkey) {
|
|
auditWrapper.auditLog(auth.userId, "admin_passkey_revoked", userId, {
|
|
passkey_id: passkey.id,
|
|
}, getClientIp(c));
|
|
return c.json({ success: true });
|
|
}
|
|
return c.json({ error: "Passkey not found" }, 404);
|
|
});
|
|
|
|
usersAdminRoutes.post("/:id/recovery", async (c) => {
|
|
const auth = await getAuthenticatedUser(c);
|
|
if (!auth) return c.json({ error: "Unauthorized" }, 401);
|
|
const targetUserId = c.req.param("id");
|
|
const targetUser = await sqlWrapper
|
|
.sql`SELECT id FROM users WHERE id = ${targetUserId}`
|
|
.then((res: any) => res[0]);
|
|
if (!targetUser) return c.json({ error: "User not found" }, 404);
|
|
const recoveryCode = encodeBase64Url(
|
|
crypto.getRandomValues(new Uint8Array(24)),
|
|
);
|
|
const expiresAt = new Date();
|
|
expiresAt.setDate(expiresAt.getDate() + 1);
|
|
await sqlWrapper
|
|
.sql`INSERT INTO recovery_links (code, user_id, created_by, expires_at) VALUES (${recoveryCode}, ${targetUserId}, ${auth.userId}, ${expiresAt})`;
|
|
auditWrapper.auditLog(
|
|
auth.userId,
|
|
"recovery_link_created",
|
|
targetUserId,
|
|
null,
|
|
getClientIp(c),
|
|
);
|
|
return c.json({ success: true, recoveryCode, expiresAt });
|
|
});
|