feat(database): reject soft-deleted databases in single-row lookups

This commit is contained in:
charles-gauthereau
2026-07-05 18:31:52 +02:00
parent 8f2d6f0b01
commit 20636bf6f2
5 changed files with 24 additions and 9 deletions
@@ -2,7 +2,7 @@
import {userAction} from "@/lib/safe-actions/actions"; import {userAction} from "@/lib/safe-actions/actions";
import {z} from "zod"; import {z} from "zod";
import {db} from "@/db"; import {db} from "@/db";
import {eq} from "drizzle-orm"; import {and, eq, isNull} from "drizzle-orm";
import * as drizzleDb from "@/db"; import * as drizzleDb from "@/db";
import {BackupWith, RestorationWith} from "@/db/schema/07_database"; import {BackupWith, RestorationWith} from "@/db/schema/07_database";
import {getOrganizationChannels} from "@/db/services/notification-channel"; import {getOrganizationChannels} from "@/db/services/notification-channel";
@@ -19,7 +19,10 @@ export const getDatabaseDataAction = userAction
const {databaseId} = parsedInput; const {databaseId} = parsedInput;
const database = await db.query.database.findFirst({ const database = await db.query.database.findFirst({
where: eq(drizzleDb.schemas.database.id, databaseId), where: and(
eq(drizzleDb.schemas.database.id, databaseId),
isNull(drizzleDb.schemas.database.deletedAt),
),
with: { with: {
project: true, project: true,
retentionPolicy: true, retentionPolicy: true,
@@ -5,7 +5,7 @@ import {db} from "@/db";
import * as drizzleDb from "@/db"; import * as drizzleDb from "@/db";
import {Backup} from "@/db/schema/07_database"; import {Backup} from "@/db/schema/07_database";
import {v4 as uuidv4} from "uuid"; import {v4 as uuidv4} from "uuid";
import {eq} from "drizzle-orm"; import {and, eq, isNull} from "drizzle-orm";
import {z} from "zod"; import {z} from "zod";
import {storeBackupFiles} from "@/features/storages/utils/storages.helpers"; import {storeBackupFiles} from "@/features/storages/utils/storages.helpers";
import {getFileExtension} from "@/utils/common"; import {getFileExtension} from "@/utils/common";
@@ -19,7 +19,10 @@ export const uploadBackupAction = userAction
const databaseId = formData.get("databaseId") as string; const databaseId = formData.get("databaseId") as string;
const database = await db.query.database.findFirst({ const database = await db.query.database.findFirst({
where: eq(drizzleDb.schemas.database.id, databaseId), where: and(
eq(drizzleDb.schemas.database.id, databaseId),
isNull(drizzleDb.schemas.database.deletedAt),
),
with: { with: {
project: true, project: true,
alertPolicies: true, alertPolicies: true,
@@ -4,7 +4,7 @@ import {ServerActionResult} from "@/types/action-type";
import {z} from "zod"; import {z} from "zod";
import {db} from "@/db"; import {db} from "@/db";
import * as drizzleDb from "@/db"; import * as drizzleDb from "@/db";
import {eq, inArray} from "drizzle-orm"; import {and, eq, inArray, isNull} from "drizzle-orm";
import {dispatchStorage} from "@/features/storages/utils/storages.dispatch"; import {dispatchStorage} from "@/features/storages/utils/storages.dispatch";
import {v4 as uuidv4} from "uuid"; import {v4 as uuidv4} from "uuid";
import {getTodayISODate} from "@/utils/date-formatting"; import {getTodayISODate} from "@/utils/date-formatting";
@@ -24,7 +24,10 @@ export const migrationAction = userAction.schema(
try { try {
const targetDatabase = await db.query.database.findFirst({ const targetDatabase = await db.query.database.findFirst({
where: eq(drizzleDb.schemas.database.id, targetDatabaseId), where: and(
eq(drizzleDb.schemas.database.id, targetDatabaseId),
isNull(drizzleDb.schemas.database.deletedAt),
),
with: { with: {
project: true, project: true,
retentionPolicy: true, retentionPolicy: true,
+5 -2
View File
@@ -1,14 +1,17 @@
import {ApiKeyContextUser} from "@/lib/api-v1/types"; import {ApiKeyContextUser} from "@/lib/api-v1/types";
import {getAccessibleDatabaseIds} from "@/lib/api-v1/services/databases"; import {getAccessibleDatabaseIds} from "@/lib/api-v1/services/databases";
import {db} from "@/db"; import {db} from "@/db";
import {eq} from "drizzle-orm"; import {and, eq, isNull} from "drizzle-orm";
import * as drizzleDb from "@/db"; import * as drizzleDb from "@/db";
export async function resolveDatabaseAccess(id: string, user: ApiKeyContextUser) { export async function resolveDatabaseAccess(id: string, user: ApiKeyContextUser) {
const accessibleIds = await getAccessibleDatabaseIds(user); const accessibleIds = await getAccessibleDatabaseIds(user);
if (accessibleIds.includes(id)) return "ok"; if (accessibleIds.includes(id)) return "ok";
const exists = await db.query.database.findFirst({ const exists = await db.query.database.findFirst({
where: eq(drizzleDb.schemas.database.id, id), where: and(
eq(drizzleDb.schemas.database.id, id),
isNull(drizzleDb.schemas.database.deletedAt),
),
columns: { id: true }, columns: { id: true },
}); });
return exists ? "forbidden" : "not_found"; return exists ? "forbidden" : "not_found";
+4 -1
View File
@@ -44,7 +44,10 @@ export const backupCleanTask = async () => {
try { try {
const database = await db.query.database.findFirst({ const database = await db.query.database.findFirst({
where: eq(drizzleDb.schemas.database.id, backup.databaseId), where: and(
eq(drizzleDb.schemas.database.id, backup.databaseId),
isNull(drizzleDb.schemas.database.deletedAt),
),
with: {alertPolicies: true}, with: {alertPolicies: true},
}); });
if (database) { if (database) {