mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-09-12 19:51:00 +05:00
fix(security): enforce org-scope on move and update procedures
This commit is contained in:
parent
1572008cdf
commit
20dd8a2c37
@ -0,0 +1,77 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
// The cross-org re-parenting vector through `application.update` exists for every
|
||||
// service type: each apiUpdate* schema derives from createSchema.partial() and
|
||||
// previously only omitted serverId, leaving environmentId settable through the
|
||||
// `update` mutation with no target-env authorization. The fix omits environmentId
|
||||
// from every apiUpdate* schema. These tests pin that contract so a future
|
||||
// createSchema/.partial() refactor cannot silently re-introduce it.
|
||||
|
||||
import {
|
||||
apiUpdateCompose,
|
||||
apiUpdateLibsql,
|
||||
apiUpdateMariaDB,
|
||||
apiUpdateMongo,
|
||||
apiUpdateMySql,
|
||||
apiUpdatePostgres,
|
||||
apiUpdateRedis,
|
||||
} from "@/server/db/schema";
|
||||
|
||||
const schemas = {
|
||||
apiUpdatePostgres: {
|
||||
schema: apiUpdatePostgres,
|
||||
idField: "postgresId",
|
||||
extra: { dockerImage: "postgres:16" },
|
||||
},
|
||||
apiUpdateRedis: {
|
||||
schema: apiUpdateRedis,
|
||||
idField: "redisId",
|
||||
extra: { dockerImage: "redis:7" },
|
||||
},
|
||||
apiUpdateMariaDB: {
|
||||
schema: apiUpdateMariaDB,
|
||||
idField: "mariadbId",
|
||||
extra: { dockerImage: "mariadb:11" },
|
||||
},
|
||||
apiUpdateMySql: {
|
||||
schema: apiUpdateMySql,
|
||||
idField: "mysqlId",
|
||||
extra: { dockerImage: "mysql:8" },
|
||||
},
|
||||
apiUpdateMongo: {
|
||||
schema: apiUpdateMongo,
|
||||
idField: "mongoId",
|
||||
extra: { dockerImage: "mongo:7" },
|
||||
},
|
||||
apiUpdateLibsql: { schema: apiUpdateLibsql, idField: "libsqlId", extra: {} },
|
||||
apiUpdateCompose: {
|
||||
schema: apiUpdateCompose,
|
||||
idField: "composeId",
|
||||
extra: { composeFile: "services:\n web:\n image: nginx" },
|
||||
},
|
||||
};
|
||||
|
||||
describe("apiUpdate* schemas omit environmentId (no re-parenting via update)", () => {
|
||||
for (const [name, { schema, idField, extra }] of Object.entries(schemas)) {
|
||||
describe(name, () => {
|
||||
it("drops environmentId from the parsed payload", () => {
|
||||
const parsed = schema.parse({
|
||||
[idField]: "svc-1",
|
||||
environmentId: "foreign-env",
|
||||
} as Record<string, unknown>);
|
||||
expect(parsed).not.toHaveProperty("environmentId");
|
||||
});
|
||||
|
||||
it("still parses legitimate update fields", () => {
|
||||
const parsed = schema.parse({
|
||||
[idField]: "svc-1",
|
||||
...extra,
|
||||
} as Record<string, unknown>);
|
||||
expect(parsed).toHaveProperty(idField, "svc-1");
|
||||
for (const [k, v] of Object.entries(extra)) {
|
||||
expect(parsed).toHaveProperty(k, v);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,207 @@
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
// Bridge to the application router is exercised through tRPC's createCaller so
|
||||
// the org-scope guards that live inline in the procedure (restored by the fix)
|
||||
// are verified exactly as a real client request would hit them.
|
||||
//
|
||||
// The DB is mocked one layer down (same pattern as git-provider-idor.test.ts)
|
||||
// so the real findApplicationById / findEnvironmentById / checkServicePermissionAndAccess
|
||||
// run against controlled org/project tree data.
|
||||
|
||||
const mockDb = vi.hoisted(() => {
|
||||
const returning = vi.fn(() =>
|
||||
Promise.resolve([{ applicationId: "app-1", appName: "app-1" }]),
|
||||
);
|
||||
const set = vi.fn(() => ({ where: () => ({ returning }) }));
|
||||
const update = vi.fn(() => ({ set }));
|
||||
return {
|
||||
update,
|
||||
_ret: returning,
|
||||
_set: set,
|
||||
query: {
|
||||
member: { findFirst: vi.fn() },
|
||||
organizationRole: { findMany: vi.fn(() => Promise.resolve([])) },
|
||||
applications: { findFirst: vi.fn() },
|
||||
environments: { findFirst: vi.fn() },
|
||||
},
|
||||
};
|
||||
});
|
||||
vi.mock("@dokploy/server/db", () => ({ db: mockDb }));
|
||||
|
||||
vi.mock("@dokploy/server/services/proprietary/license-key", () => ({
|
||||
hasValidLicense: vi.fn(() => Promise.resolve(false)),
|
||||
}));
|
||||
|
||||
vi.mock("@dokploy/server/services/proprietary/audit-log", () => ({
|
||||
createAuditLog: vi.fn(() => Promise.resolve()),
|
||||
}));
|
||||
|
||||
// getAccessibleServerIds is only used by the application.update procedure when
|
||||
// buildServerId is supplied; keep it deterministic in case it is reached.
|
||||
vi.mock("@dokploy/server/services/server", () => ({
|
||||
getAccessibleServerIds: vi.fn(() => Promise.resolve(new Set())),
|
||||
}));
|
||||
|
||||
import { applicationRouter } from "@/server/api/routers/application";
|
||||
|
||||
const ORG_A = "org-a";
|
||||
const ORG_B = "org-b";
|
||||
|
||||
// tRPC's createCaller is typed against the full CreateContextOptions (db/req/res
|
||||
// + the full better-auth Session). The procedures only read `ctx.user` and
|
||||
// `ctx.session.activeOrganizationId`; typing the helper as `any` lets createCaller
|
||||
// accept the minimal ctx while the returned caller stays fully typed by the router.
|
||||
const ctx = (role: "owner" | "admin" | "member", org = ORG_A): any => ({
|
||||
user: {
|
||||
id: "user-1",
|
||||
email: "owner@example.com",
|
||||
role,
|
||||
ownerId: "owner-1",
|
||||
},
|
||||
session: { activeOrganizationId: org },
|
||||
});
|
||||
|
||||
const appRow = (orgId: string) => ({
|
||||
applicationId: "app-1",
|
||||
appName: "app-1",
|
||||
environment: { project: { organizationId: orgId } },
|
||||
});
|
||||
|
||||
const envRow = (orgId: string) => ({
|
||||
environmentId: "env-target",
|
||||
project: { organizationId: orgId },
|
||||
});
|
||||
|
||||
const memberRow = (
|
||||
role: "owner" | "admin" | "member",
|
||||
accessedServices: string[] = ["app-1"],
|
||||
) => ({
|
||||
id: "member-1",
|
||||
role,
|
||||
userId: "user-1",
|
||||
organizationId: ORG_A,
|
||||
accessedServices,
|
||||
accessedProjects: [],
|
||||
accessedEnvironments: [],
|
||||
canCreateServices: true,
|
||||
canCreateProjects: true,
|
||||
canCreateEnvironments: true,
|
||||
canDeleteServices: true,
|
||||
canDeleteProjects: true,
|
||||
canDeleteEnvironments: true,
|
||||
canAccessToTraefikFiles: true,
|
||||
canAccessToDocker: true,
|
||||
canAccessToAPI: true,
|
||||
canAccessToSSHKeys: true,
|
||||
canAccessToGitProviders: true,
|
||||
user: { id: "user-1", email: "owner@example.com" },
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockDb.query.member.findFirst.mockResolvedValue(memberRow("owner"));
|
||||
mockDb.query.applications.findFirst.mockResolvedValue(appRow(ORG_A));
|
||||
mockDb.query.environments.findFirst.mockResolvedValue(envRow(ORG_A));
|
||||
mockDb.query.organizationRole.findMany.mockResolvedValue([]);
|
||||
mockDb._ret.mockResolvedValue([{ applicationId: "app-1", appName: "app-1" }]);
|
||||
});
|
||||
|
||||
describe("application.move org-scope guards", () => {
|
||||
it("allows moving an application between environments of the same organization", async () => {
|
||||
mockDb.query.applications.findFirst.mockResolvedValue(appRow(ORG_A));
|
||||
mockDb.query.environments.findFirst.mockResolvedValue(envRow(ORG_A));
|
||||
|
||||
const caller = applicationRouter.createCaller(ctx("owner"));
|
||||
await expect(
|
||||
caller.move({
|
||||
applicationId: "app-1",
|
||||
targetEnvironmentId: "env-target",
|
||||
}),
|
||||
).resolves.toMatchObject({ applicationId: "app-1" });
|
||||
expect(mockDb.update).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("rejects moving an application that belongs to a foreign organization (source check)", async () => {
|
||||
// Headline exfiltration vector: owner of Org A pulls an Org B app into an Org A env.
|
||||
mockDb.query.applications.findFirst.mockResolvedValue(appRow(ORG_B));
|
||||
mockDb.query.environments.findFirst.mockResolvedValue(envRow(ORG_A));
|
||||
|
||||
const caller = applicationRouter.createCaller(ctx("owner"));
|
||||
await expect(
|
||||
caller.move({
|
||||
applicationId: "app-1",
|
||||
targetEnvironmentId: "env-target",
|
||||
}),
|
||||
).rejects.toMatchObject({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to move this application",
|
||||
});
|
||||
// The DB write must never run for a rejected move.
|
||||
expect(mockDb.update).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("rejects moving to a target environment that belongs to a foreign organization (target check)", async () => {
|
||||
mockDb.query.applications.findFirst.mockResolvedValue(appRow(ORG_A));
|
||||
mockDb.query.environments.findFirst.mockResolvedValue(envRow(ORG_B));
|
||||
|
||||
const caller = applicationRouter.createCaller(ctx("owner"));
|
||||
await expect(
|
||||
caller.move({
|
||||
applicationId: "app-1",
|
||||
targetEnvironmentId: "env-target",
|
||||
}),
|
||||
).rejects.toMatchObject({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to move to this environment",
|
||||
});
|
||||
expect(mockDb.update).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("still rejects the foreign-source move for an owner even when accessedServices would otherwise be skipped", async () => {
|
||||
// Owner role bypasses the accessedServices list, so the only thing standing
|
||||
// between a foreign applicationId and re-parenting is the restored source check.
|
||||
mockDb.query.applications.findFirst.mockResolvedValue(appRow(ORG_B));
|
||||
mockDb.query.environments.findFirst.mockResolvedValue(envRow(ORG_A));
|
||||
|
||||
const caller = applicationRouter.createCaller(ctx("owner"));
|
||||
await expect(
|
||||
caller.move({
|
||||
applicationId: "app-foreign",
|
||||
targetEnvironmentId: "env-target",
|
||||
}),
|
||||
).rejects.toMatchObject({ code: "UNAUTHORIZED" });
|
||||
expect(mockDb.update).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("rejects a non-owner (with access to the source app) moving to a foreign organization (orphaning path)", async () => {
|
||||
mockDb.query.member.findFirst.mockResolvedValue(
|
||||
memberRow("member", ["app-1"]),
|
||||
);
|
||||
mockDb.query.applications.findFirst.mockResolvedValue(appRow(ORG_A));
|
||||
mockDb.query.environments.findFirst.mockResolvedValue(envRow(ORG_B));
|
||||
|
||||
const caller = applicationRouter.createCaller(ctx("member"));
|
||||
await expect(
|
||||
caller.move({
|
||||
applicationId: "app-1",
|
||||
targetEnvironmentId: "env-target",
|
||||
}),
|
||||
).rejects.toMatchObject({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to move to this environment",
|
||||
});
|
||||
expect(mockDb.update).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("throws a TRPCError (so tRPC maps the HTTP status) on the source check", async () => {
|
||||
mockDb.query.applications.findFirst.mockResolvedValue(appRow(ORG_B));
|
||||
mockDb.query.environments.findFirst.mockResolvedValue(envRow(ORG_A));
|
||||
|
||||
const caller = applicationRouter.createCaller(ctx("owner"));
|
||||
const err = await caller
|
||||
.move({ applicationId: "app-1", targetEnvironmentId: "env-target" })
|
||||
.catch((e) => e);
|
||||
expect(err).toBeInstanceOf(TRPCError);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,125 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
// The `application.update` mutation previously accepted `environmentId` via the
|
||||
// `apiUpdateApplication` schema (derived from createSchema.partial()) and passed
|
||||
// it straight through to updateApplication -> db.update(applications).set(...).
|
||||
// That was a second cross-org re-parenting path with no target-env authorization.
|
||||
// The fix omits `environmentId` from apiUpdateApplication so the field can no longer
|
||||
// be set through update — environment changes must go through the guarded `move`.
|
||||
|
||||
const mockDb = vi.hoisted(() => {
|
||||
const returning = vi.fn(() =>
|
||||
Promise.resolve([{ applicationId: "app-1", appName: "app-1" }]),
|
||||
);
|
||||
const set = vi.fn((_data: Record<string, unknown>) => ({
|
||||
where: () => ({ returning }),
|
||||
}));
|
||||
const update = vi.fn(() => ({ set }));
|
||||
return {
|
||||
update,
|
||||
_set: set,
|
||||
_ret: returning,
|
||||
query: {
|
||||
member: { findFirst: vi.fn() },
|
||||
organizationRole: { findMany: vi.fn(() => Promise.resolve([])) },
|
||||
},
|
||||
};
|
||||
});
|
||||
vi.mock("@dokploy/server/db", () => ({ db: mockDb }));
|
||||
|
||||
vi.mock("@dokploy/server/services/proprietary/license-key", () => ({
|
||||
hasValidLicense: vi.fn(() => Promise.resolve(false)),
|
||||
}));
|
||||
|
||||
vi.mock("@dokploy/server/services/proprietary/audit-log", () => ({
|
||||
createAuditLog: vi.fn(() => Promise.resolve()),
|
||||
}));
|
||||
|
||||
vi.mock("@dokploy/server/services/server", () => ({
|
||||
getAccessibleServerIds: vi.fn(() => Promise.resolve(new Set())),
|
||||
}));
|
||||
|
||||
import { applicationRouter } from "@/server/api/routers/application";
|
||||
import { apiUpdateApplication } from "@/server/db/schema";
|
||||
|
||||
const ORG_A = "org-a";
|
||||
|
||||
const ctx = (): any => ({
|
||||
user: {
|
||||
id: "user-1",
|
||||
email: "owner@example.com",
|
||||
role: "owner" as const,
|
||||
ownerId: "owner-1",
|
||||
},
|
||||
session: { activeOrganizationId: ORG_A },
|
||||
});
|
||||
|
||||
const memberRow = () => ({
|
||||
id: "member-1",
|
||||
role: "owner",
|
||||
userId: "user-1",
|
||||
organizationId: ORG_A,
|
||||
accessedServices: ["app-1"],
|
||||
accessedProjects: [],
|
||||
accessedEnvironments: [],
|
||||
canCreateServices: true,
|
||||
canCreateProjects: true,
|
||||
canCreateEnvironments: true,
|
||||
canDeleteServices: true,
|
||||
canDeleteProjects: true,
|
||||
canDeleteEnvironments: true,
|
||||
canAccessToTraefikFiles: true,
|
||||
canAccessToDocker: true,
|
||||
canAccessToAPI: true,
|
||||
canAccessToSSHKeys: true,
|
||||
canAccessToGitProviders: true,
|
||||
user: { id: "user-1", email: "owner@example.com" },
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockDb.query.member.findFirst.mockResolvedValue(memberRow());
|
||||
mockDb.query.organizationRole.findMany.mockResolvedValue([]);
|
||||
mockDb._ret.mockResolvedValue([{ applicationId: "app-1", appName: "app-1" }]);
|
||||
});
|
||||
|
||||
describe("apiUpdateApplication schema (environmentId omitted)", () => {
|
||||
it("does not expose environmentId as a parsed key", () => {
|
||||
const parsed = apiUpdateApplication.parse({
|
||||
applicationId: "app-1",
|
||||
environmentId: "foreign-env",
|
||||
autoDeploy: true,
|
||||
});
|
||||
expect(parsed).not.toHaveProperty("environmentId");
|
||||
expect(parsed).toHaveProperty("applicationId", "app-1");
|
||||
expect(parsed).toHaveProperty("autoDeploy", true);
|
||||
});
|
||||
|
||||
it("silently drops environmentId supplied as an unknown key", () => {
|
||||
const parsed = apiUpdateApplication.parse({
|
||||
applicationId: "app-1",
|
||||
environmentId: "foreign-env",
|
||||
});
|
||||
expect(Object.keys(parsed)).not.toContain("environmentId");
|
||||
});
|
||||
});
|
||||
|
||||
describe("application.update does not re-parent via environmentId", () => {
|
||||
it("ignores environmentId in the mutation payload and never writes it to the db", async () => {
|
||||
const caller = applicationRouter.createCaller(ctx());
|
||||
|
||||
// A raw client could still send `environmentId` over the wire; zod now strips
|
||||
// it (it's omitted from apiUpdateApplication). Cast to simulate that payload.
|
||||
await caller.update({
|
||||
applicationId: "app-1",
|
||||
environmentId: "foreign-env",
|
||||
autoDeploy: true,
|
||||
} as any);
|
||||
|
||||
expect(mockDb._set).toHaveBeenCalledOnce();
|
||||
const setData = mockDb._set.mock.calls[0]![0];
|
||||
expect(setData).not.toHaveProperty("environmentId");
|
||||
// Sanity: the legitimate field still flows through.
|
||||
expect(setData).toHaveProperty("autoDeploy", true);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,132 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
// Representative sibling guard: the same org-scope checks restored on
|
||||
// application.move were also restored on the database-service `move` procedures
|
||||
// (postgres, redis, mariadb, mysql, mongo, libsql, compose). Postgres is exercised
|
||||
// here through tRPC's createCaller; the remaining siblings share the identical
|
||||
// procedure shape and are covered by the schema + manual verification plan.
|
||||
|
||||
const mockDb = vi.hoisted(() => {
|
||||
const returning = vi.fn(() =>
|
||||
Promise.resolve([{ postgresId: "pg-1", appName: "pg-1" }]),
|
||||
);
|
||||
const set = vi.fn(() => ({ where: () => ({ returning }) }));
|
||||
const update = vi.fn(() => ({ set }));
|
||||
return {
|
||||
update,
|
||||
_set: set,
|
||||
_ret: returning,
|
||||
query: {
|
||||
member: { findFirst: vi.fn() },
|
||||
organizationRole: { findMany: vi.fn(() => Promise.resolve([])) },
|
||||
postgres: { findFirst: vi.fn() },
|
||||
environments: { findFirst: vi.fn() },
|
||||
},
|
||||
};
|
||||
});
|
||||
vi.mock("@dokploy/server/db", () => ({ db: mockDb }));
|
||||
|
||||
vi.mock("@dokploy/server/services/proprietary/license-key", () => ({
|
||||
hasValidLicense: vi.fn(() => Promise.resolve(false)),
|
||||
}));
|
||||
|
||||
vi.mock("@dokploy/server/services/proprietary/audit-log", () => ({
|
||||
createAuditLog: vi.fn(() => Promise.resolve()),
|
||||
}));
|
||||
|
||||
import { postgresRouter } from "@/server/api/routers/postgres";
|
||||
|
||||
const ORG_A = "org-a";
|
||||
const ORG_B = "org-b";
|
||||
|
||||
const ctx = (): any => ({
|
||||
user: {
|
||||
id: "user-1",
|
||||
email: "owner@example.com",
|
||||
role: "owner" as const,
|
||||
ownerId: "owner-1",
|
||||
},
|
||||
session: { activeOrganizationId: ORG_A },
|
||||
});
|
||||
|
||||
const pgRow = (orgId: string) => ({
|
||||
postgresId: "pg-1",
|
||||
appName: "pg-1",
|
||||
environment: { project: { organizationId: orgId } },
|
||||
});
|
||||
|
||||
const envRow = (orgId: string) => ({
|
||||
environmentId: "env-target",
|
||||
project: { organizationId: orgId },
|
||||
});
|
||||
|
||||
const memberRow = () => ({
|
||||
id: "member-1",
|
||||
role: "owner",
|
||||
userId: "user-1",
|
||||
organizationId: ORG_A,
|
||||
accessedServices: ["pg-1"],
|
||||
accessedProjects: [],
|
||||
accessedEnvironments: [],
|
||||
canCreateServices: true,
|
||||
canCreateProjects: true,
|
||||
canCreateEnvironments: true,
|
||||
canDeleteServices: true,
|
||||
canDeleteProjects: true,
|
||||
canDeleteEnvironments: true,
|
||||
canAccessToTraefikFiles: true,
|
||||
canAccessToDocker: true,
|
||||
canAccessToAPI: true,
|
||||
canAccessToSSHKeys: true,
|
||||
canAccessToGitProviders: true,
|
||||
user: { id: "user-1", email: "owner@example.com" },
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockDb.query.member.findFirst.mockResolvedValue(memberRow());
|
||||
mockDb.query.postgres.findFirst.mockResolvedValue(pgRow(ORG_A));
|
||||
mockDb.query.environments.findFirst.mockResolvedValue(envRow(ORG_A));
|
||||
mockDb.query.organizationRole.findMany.mockResolvedValue([]);
|
||||
});
|
||||
|
||||
describe("postgres.move org-scope guards", () => {
|
||||
it("allows moving a postgres within the same organization", async () => {
|
||||
mockDb.query.postgres.findFirst.mockResolvedValue(pgRow(ORG_A));
|
||||
mockDb.query.environments.findFirst.mockResolvedValue(envRow(ORG_A));
|
||||
|
||||
const caller = postgresRouter.createCaller(ctx());
|
||||
await expect(
|
||||
caller.move({ postgresId: "pg-1", targetEnvironmentId: "env-target" }),
|
||||
).resolves.toMatchObject({ postgresId: "pg-1" });
|
||||
expect(mockDb.update).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("rejects moving a postgres that belongs to a foreign organization (source check)", async () => {
|
||||
mockDb.query.postgres.findFirst.mockResolvedValue(pgRow(ORG_B));
|
||||
mockDb.query.environments.findFirst.mockResolvedValue(envRow(ORG_A));
|
||||
|
||||
const caller = postgresRouter.createCaller(ctx());
|
||||
await expect(
|
||||
caller.move({ postgresId: "pg-1", targetEnvironmentId: "env-target" }),
|
||||
).rejects.toMatchObject({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to move this postgres",
|
||||
});
|
||||
expect(mockDb.update).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("rejects moving to a target environment in a foreign organization (target check)", async () => {
|
||||
mockDb.query.postgres.findFirst.mockResolvedValue(pgRow(ORG_A));
|
||||
mockDb.query.environments.findFirst.mockResolvedValue(envRow(ORG_B));
|
||||
|
||||
const caller = postgresRouter.createCaller(ctx());
|
||||
await expect(
|
||||
caller.move({ postgresId: "pg-1", targetEnvironmentId: "env-target" }),
|
||||
).rejects.toMatchObject({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to move to this environment",
|
||||
});
|
||||
expect(mockDb.update).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@ -1018,6 +1018,30 @@ export const applicationRouter = createTRPCRouter({
|
||||
service: ["create"],
|
||||
});
|
||||
|
||||
const application = await findApplicationById(input.applicationId);
|
||||
if (
|
||||
application.environment.project.organizationId !==
|
||||
ctx.session.activeOrganizationId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to move this application",
|
||||
});
|
||||
}
|
||||
|
||||
const targetEnvironment = await findEnvironmentById(
|
||||
input.targetEnvironmentId,
|
||||
);
|
||||
if (
|
||||
targetEnvironment.project.organizationId !==
|
||||
ctx.session.activeOrganizationId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to move to this environment",
|
||||
});
|
||||
}
|
||||
|
||||
const updatedApplication = await db
|
||||
.update(applications)
|
||||
.set({
|
||||
|
||||
@ -786,6 +786,30 @@ export const composeRouter = createTRPCRouter({
|
||||
service: ["create"],
|
||||
});
|
||||
|
||||
const compose = await findComposeById(input.composeId);
|
||||
if (
|
||||
compose.environment.project.organizationId !==
|
||||
ctx.session.activeOrganizationId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to move this compose",
|
||||
});
|
||||
}
|
||||
|
||||
const targetEnvironment = await findEnvironmentById(
|
||||
input.targetEnvironmentId,
|
||||
);
|
||||
if (
|
||||
targetEnvironment.project.organizationId !==
|
||||
ctx.session.activeOrganizationId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to move to this environment",
|
||||
});
|
||||
}
|
||||
|
||||
const updatedCompose = await db
|
||||
.update(composeTable)
|
||||
.set({
|
||||
|
||||
@ -433,6 +433,30 @@ export const libsqlRouter = createTRPCRouter({
|
||||
service: ["create"],
|
||||
});
|
||||
|
||||
const libsql = await findLibsqlById(input.libsqlId);
|
||||
if (
|
||||
libsql.environment.project.organizationId !==
|
||||
ctx.session.activeOrganizationId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to move this libsql",
|
||||
});
|
||||
}
|
||||
|
||||
const targetEnvironment = await findEnvironmentById(
|
||||
input.targetEnvironmentId,
|
||||
);
|
||||
if (
|
||||
targetEnvironment.project.organizationId !==
|
||||
ctx.session.activeOrganizationId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to move to this environment",
|
||||
});
|
||||
}
|
||||
|
||||
const updatedLibsql = await db
|
||||
.update(libsqlTable)
|
||||
.set({
|
||||
|
||||
@ -460,6 +460,30 @@ export const mariadbRouter = createTRPCRouter({
|
||||
service: ["create"],
|
||||
});
|
||||
|
||||
const mariadb = await findMariadbById(input.mariadbId);
|
||||
if (
|
||||
mariadb.environment.project.organizationId !==
|
||||
ctx.session.activeOrganizationId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to move this mariadb",
|
||||
});
|
||||
}
|
||||
|
||||
const targetEnvironment = await findEnvironmentById(
|
||||
input.targetEnvironmentId,
|
||||
);
|
||||
if (
|
||||
targetEnvironment.project.organizationId !==
|
||||
ctx.session.activeOrganizationId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to move to this environment",
|
||||
});
|
||||
}
|
||||
|
||||
const updatedMariadb = await db
|
||||
.update(mariadbTable)
|
||||
.set({
|
||||
|
||||
@ -475,6 +475,30 @@ export const mongoRouter = createTRPCRouter({
|
||||
service: ["create"],
|
||||
});
|
||||
|
||||
const mongo = await findMongoById(input.mongoId);
|
||||
if (
|
||||
mongo.environment.project.organizationId !==
|
||||
ctx.session.activeOrganizationId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to move this mongo",
|
||||
});
|
||||
}
|
||||
|
||||
const targetEnvironment = await findEnvironmentById(
|
||||
input.targetEnvironmentId,
|
||||
);
|
||||
if (
|
||||
targetEnvironment.project.organizationId !==
|
||||
ctx.session.activeOrganizationId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to move to this environment",
|
||||
});
|
||||
}
|
||||
|
||||
const updatedMongo = await db
|
||||
.update(mongoTable)
|
||||
.set({
|
||||
|
||||
@ -478,6 +478,30 @@ export const mysqlRouter = createTRPCRouter({
|
||||
service: ["create"],
|
||||
});
|
||||
|
||||
const mysql = await findMySqlById(input.mysqlId);
|
||||
if (
|
||||
mysql.environment.project.organizationId !==
|
||||
ctx.session.activeOrganizationId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to move this mysql",
|
||||
});
|
||||
}
|
||||
|
||||
const targetEnvironment = await findEnvironmentById(
|
||||
input.targetEnvironmentId,
|
||||
);
|
||||
if (
|
||||
targetEnvironment.project.organizationId !==
|
||||
ctx.session.activeOrganizationId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to move to this environment",
|
||||
});
|
||||
}
|
||||
|
||||
const updatedMysql = await db
|
||||
.update(mysqlTable)
|
||||
.set({
|
||||
|
||||
@ -481,6 +481,30 @@ export const postgresRouter = createTRPCRouter({
|
||||
service: ["create"],
|
||||
});
|
||||
|
||||
const postgres = await findPostgresById(input.postgresId);
|
||||
if (
|
||||
postgres.environment.project.organizationId !==
|
||||
ctx.session.activeOrganizationId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to move this postgres",
|
||||
});
|
||||
}
|
||||
|
||||
const targetEnvironment = await findEnvironmentById(
|
||||
input.targetEnvironmentId,
|
||||
);
|
||||
if (
|
||||
targetEnvironment.project.organizationId !==
|
||||
ctx.session.activeOrganizationId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to move to this environment",
|
||||
});
|
||||
}
|
||||
|
||||
const updatedPostgres = await db
|
||||
.update(postgresTable)
|
||||
.set({
|
||||
|
||||
@ -462,6 +462,30 @@ export const redisRouter = createTRPCRouter({
|
||||
service: ["create"],
|
||||
});
|
||||
|
||||
const redis = await findRedisById(input.redisId);
|
||||
if (
|
||||
redis.environment.project.organizationId !==
|
||||
ctx.session.activeOrganizationId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to move this redis",
|
||||
});
|
||||
}
|
||||
|
||||
const targetEnvironment = await findEnvironmentById(
|
||||
input.targetEnvironmentId,
|
||||
);
|
||||
if (
|
||||
targetEnvironment.project.organizationId !==
|
||||
ctx.session.activeOrganizationId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to move to this environment",
|
||||
});
|
||||
}
|
||||
|
||||
const updatedRedis = await db
|
||||
.update(redisTable)
|
||||
.set({
|
||||
|
||||
@ -551,4 +551,4 @@ export const apiUpdateApplication = createSchema
|
||||
.extend({
|
||||
applicationId: z.string().min(1),
|
||||
})
|
||||
.omit({ serverId: true });
|
||||
.omit({ serverId: true, environmentId: true });
|
||||
|
||||
@ -264,7 +264,7 @@ export const apiUpdateCompose = createSchema
|
||||
composeFile: z.string().optional(),
|
||||
command: z.string().optional(),
|
||||
})
|
||||
.omit({ serverId: true });
|
||||
.omit({ serverId: true, environmentId: true });
|
||||
|
||||
export const apiSaveEnvironmentVariablesCompose = createSchema
|
||||
.pick({
|
||||
|
||||
@ -246,7 +246,7 @@ export const apiUpdateLibsql = createSchema
|
||||
.extend({
|
||||
libsqlId: z.string().min(1),
|
||||
})
|
||||
.omit({ serverId: true });
|
||||
.omit({ serverId: true, environmentId: true });
|
||||
|
||||
export const apiRebuildLibsql = createSchema
|
||||
.pick({
|
||||
|
||||
@ -220,7 +220,7 @@ export const apiUpdateMariaDB = createSchema
|
||||
mariadbId: z.string().min(1),
|
||||
dockerImage: z.string().optional(),
|
||||
})
|
||||
.omit({ serverId: true });
|
||||
.omit({ serverId: true, environmentId: true });
|
||||
|
||||
export const apiRebuildMariadb = createSchema
|
||||
.pick({
|
||||
|
||||
@ -204,7 +204,7 @@ export const apiUpdateMongo = createSchema
|
||||
dockerImage: z.string().optional(),
|
||||
replicaSets: z.boolean().optional(),
|
||||
})
|
||||
.omit({ serverId: true });
|
||||
.omit({ serverId: true, environmentId: true });
|
||||
|
||||
export const apiResetMongo = createSchema
|
||||
.pick({
|
||||
|
||||
@ -217,7 +217,7 @@ export const apiUpdateMySql = createSchema
|
||||
mysqlId: z.string().min(1),
|
||||
dockerImage: z.string().optional(),
|
||||
})
|
||||
.omit({ serverId: true });
|
||||
.omit({ serverId: true, environmentId: true });
|
||||
|
||||
export const apiRebuildMysql = createSchema
|
||||
.pick({
|
||||
|
||||
@ -211,7 +211,7 @@ export const apiUpdatePostgres = createSchema
|
||||
postgresId: z.string().min(1),
|
||||
dockerImage: z.string().optional(),
|
||||
})
|
||||
.omit({ serverId: true });
|
||||
.omit({ serverId: true, environmentId: true });
|
||||
|
||||
export const apiRebuildPostgres = createSchema
|
||||
.pick({
|
||||
|
||||
@ -198,7 +198,7 @@ export const apiUpdateRedis = createSchema
|
||||
redisId: z.string().min(1),
|
||||
dockerImage: z.string().optional(),
|
||||
})
|
||||
.omit({ serverId: true });
|
||||
.omit({ serverId: true, environmentId: true });
|
||||
|
||||
export const apiRebuildRedis = createSchema
|
||||
.pick({
|
||||
|
||||
Loading…
Reference in New Issue
Block a user