diff --git a/apps/dashboard/lib/audit.ts b/apps/dashboard/lib/audit.ts index 7d51fd508f..453af817fc 100644 --- a/apps/dashboard/lib/audit.ts +++ b/apps/dashboard/lib/audit.ts @@ -102,11 +102,30 @@ export async function insertAuditLogs( const logs = Array.isArray(logOrLogs) ? logOrLogs : [logOrLogs]; if (logs.length === 0) { + console.info("No audit logs to insert"); return Promise.resolve(); } + console.info({ + message: "Inserting audit logs", + count: logs.length, + events: logs.map((log) => log.event), + workspaceIds: [...new Set(logs.map((log) => log.workspaceId))], + }); + for (const log of logs) { const auditLogId = newId("auditLog"); + + console.info({ + message: "Inserting audit log entry", + auditLogId, + workspaceId: log.workspaceId, + event: log.event, + actorType: log.actor.type, + actorId: log.actor.id, + resourceCount: log.resources.length, + }); + await db.insert(schema.auditLog).values({ id: auditLogId, workspaceId: log.workspaceId, @@ -122,7 +141,15 @@ export async function insertAuditLogs( actorName: log.actor.name, actorMeta: log.actor.meta, }); + if (log.resources.length > 0) { + console.info({ + message: "Inserting audit log resources", + auditLogId, + resourceCount: log.resources.length, + resourceTypes: log.resources.map((r) => r.type), + }); + await db.insert(schema.auditLogTarget).values( log.resources.map((r) => ({ workspaceId: log.workspaceId, @@ -138,4 +165,9 @@ export async function insertAuditLogs( ); } } + + console.info({ + message: "Successfully inserted all audit logs", + count: logs.length, + }); } diff --git a/apps/dashboard/lib/trpc/routers/key/delete.ts b/apps/dashboard/lib/trpc/routers/key/delete.ts index 8fface1682..2da49c03f9 100644 --- a/apps/dashboard/lib/trpc/routers/key/delete.ts +++ b/apps/dashboard/lib/trpc/routers/key/delete.ts @@ -9,78 +9,176 @@ export const deleteKeys = t.procedure .use(requireWorkspace) .input( z.object({ - keyIds: z.array(z.string()), + keyIds: z.array(z.string()).min(1, "At least one key ID must be provided"), }), ) .mutation(async ({ ctx, input }) => { - const workspace = await db.query.workspaces - .findFirst({ + const { keyIds } = input; + const userId = ctx.user.id; + const tenantId = ctx.tenant.id; + + console.info({ + message: "Attempting to delete keys", + userId, + tenantId, + keyCount: keyIds.length, + keyIds, + }); + + if (keyIds.length === 0) { + console.warn({ + message: "No key IDs provided for deletion", + userId, + tenantId, + }); + throw new TRPCError({ + code: "BAD_REQUEST", + message: "No keys were provided for deletion", + }); + } + + try { + const workspace = await db.query.workspaces.findFirst({ where: (table, { and, eq, isNull }) => - and(eq(table.orgId, ctx.tenant.id), isNull(table.deletedAtM)), + and(eq(table.orgId, tenantId), isNull(table.deletedAtM)), with: { keys: { where: (table, { and, inArray, isNull }) => - and(isNull(table.deletedAtM), inArray(table.id, input.keyIds)), + and(isNull(table.deletedAtM), inArray(table.id, keyIds)), columns: { id: true, }, }, }, - }) - .catch((_err) => { + }); + + if (!workspace) { + console.error({ + message: "Workspace not found", + userId, + tenantId, + }); throw new TRPCError({ - code: "INTERNAL_SERVER_ERROR", - message: - "We were unable to delete this key. Please try again or contact support@unkey.dev.", + code: "NOT_FOUND", + message: "Workspace not found. Please verify your workspace selection and try again.", }); - }); - if (!workspace) { - throw new TRPCError({ - code: "NOT_FOUND", - message: - "We are unable to find the correct workspace. Please try again or contact support@unkey.dev.", - }); - } + } - await db - .transaction(async (tx) => { - await tx - .update(schema.keys) - .set({ deletedAtM: Date.now() }) - .where( - and( - eq(schema.keys.workspaceId, workspace.id), - inArray( - schema.keys.id, - workspace.keys.map((k) => k.id), - ), - ), - ); - insertAuditLogs( - tx, - workspace.keys.map((key) => ({ + // Check if keys were found in the workspace + if (workspace.keys.length === 0) { + console.warn({ + message: "No keys found for deletion", + userId, + tenantId, + keyIds, + }); + throw new TRPCError({ + code: "NOT_FOUND", + message: "None of the specified keys could be found in your workspace.", + }); + } + + try { + await db.transaction(async (tx) => { + console.info({ + message: "Starting key deletion transaction", + userId, + tenantId, workspaceId: workspace.id, - actor: { type: "user", id: ctx.user.id }, - event: "key.delete", - description: `Deleted ${key.id}`, - resources: [ - { - type: "key", - id: key.id, + keysToDelete: workspace.keys.map((k) => k.id), + }); + + await tx + .update(schema.keys) + .set({ deletedAtM: Date.now() }) + .where( + and( + eq(schema.keys.workspaceId, workspace.id), + inArray( + schema.keys.id, + workspace.keys.map((k) => k.id), + ), + ), + ); + + await insertAuditLogs( + tx, + workspace.keys.map((key) => ({ + workspaceId: workspace.id, + actor: { type: "user", id: userId }, + event: "key.delete", + description: `Deleted key ${key.id}`, + resources: [ + { + type: "key", + id: key.id, + }, + ], + context: { + location: ctx.audit.location, + userAgent: ctx.audit.userAgent, }, - ], - context: { - location: ctx.audit.location, - userAgent: ctx.audit.userAgent, - }, - })), - ); - }) - .catch((err) => { - console.error(err); - throw new TRPCError({ - code: "INTERNAL_SERVER_ERROR", - message: "We are unable to delete the key. Please try again or contact support@unkey.dev", + })), + ); + }); + } catch (txErr) { + console.error({ + message: "Transaction failed during key deletion", + userId, + tenantId, + workspaceId: workspace.id, + error: txErr instanceof Error ? txErr.message : String(txErr), + stack: txErr instanceof Error ? txErr.stack : undefined, }); + + // Check for specific error types + const errorMessage = String(txErr); + if (errorMessage.includes("Forbidden")) { + throw new TRPCError({ + code: "FORBIDDEN", + message: + "Permission denied. The system doesn't have sufficient access to complete this operation.", + }); + } + if (errorMessage.includes("auditLog")) { + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: "Unable to create audit logs. Please contact support@unkey.dev.", + }); + } + throw txErr; // Re-throw to be caught by outer catch + } + + console.info({ + message: "Keys deleted successfully", + userId, + tenantId, + keyCount: workspace.keys.length, + deletedKeyIds: workspace.keys.map((k) => k.id), + }); + + return { + deletedKeyIds: workspace.keys.map((k) => k.id), + totalDeleted: workspace.keys.length, + }; + } catch (err) { + if (err instanceof TRPCError) { + // Re-throw if it's already a TRPC error + throw err; + } + + console.error({ + message: "Error during key deletion", + userId, + tenantId, + keyIds, + error: err instanceof Error ? err.message : String(err), + stack: err instanceof Error ? err.stack : undefined, }); + + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: "Failed to delete keys. Our team has been notified of this issue.", + }); + } });