feat: enhance vault provider management and environment variable resolution

- Introduced support for project and environment assignments for vault providers, allowing for more granular access control.
- Updated the vault provider schema to include assignments and modified related components to handle these changes.
- Enhanced UI to display assignment status and allow users to manage project and environment associations effectively.
- Added validation to ensure assignments reference valid projects and environments within the organization.
- Updated tests to cover new functionality related to vault provider assignments and environment variable resolution.
This commit is contained in:
Mauricio Siu
2026-08-10 16:25:39 -06:00
parent b8822a1fde
commit f6622428f7
17 changed files with 9427 additions and 35 deletions
@@ -63,6 +63,15 @@ export const vaultProviderConfigSchema = z.discriminatedUnion("providerType", [
export type VaultProviderConfig = z.infer<typeof vaultProviderConfigSchema>;
export const vaultProviderAssignmentSchema = z.object({
projectId: z.string().min(1),
environmentIds: z.array(z.string().min(1)).default([]),
});
export type VaultProviderAssignment = z.infer<
typeof vaultProviderAssignmentSchema
>;
export const vaultProvider = pgTable(
"vault_provider",
{
@@ -73,6 +82,10 @@ export const vaultProvider = pgTable(
name: text("name").notNull(),
providerType: vaultProviderType("providerType").notNull(),
config: jsonb("config").$type<VaultProviderConfig>().notNull(),
assignments: jsonb("assignments")
.$type<VaultProviderAssignment[]>()
.notNull()
.default([]),
organizationId: text("organizationId")
.notNull()
.references(() => organization.id, { onDelete: "cascade" }),
@@ -102,12 +115,14 @@ const createSchema = createInsertSchema(vaultProvider);
export const apiCreateVaultProvider = createSchema.pick({}).extend({
name: vaultProviderNameSchema,
config: vaultProviderConfigSchema,
assignments: z.array(vaultProviderAssignmentSchema),
});
export const apiUpdateVaultProvider = createSchema.pick({}).extend({
vaultProviderId: z.string().min(1),
name: vaultProviderNameSchema,
config: vaultProviderConfigSchema,
assignments: z.array(vaultProviderAssignmentSchema),
});
export const apiFindOneVaultProvider = z.object({
@@ -1,6 +1,8 @@
import { db } from "@dokploy/server/db";
import {
type apiCreateVaultProvider,
projects,
type VaultProviderAssignment,
type VaultProviderConfig,
vaultProvider,
} from "@dokploy/server/db/schema";
@@ -58,10 +60,44 @@ const isUniqueNameViolation = (error: unknown) =>
error instanceof Error &&
error.message.includes("vault_provider_org_name_idx");
const validateAssignments = async (
assignments: VaultProviderAssignment[],
organizationId: string,
) => {
const orgProjects = await db.query.projects.findMany({
where: eq(projects.organizationId, organizationId),
with: { environments: true },
});
for (const assignment of assignments) {
const project = orgProjects.find(
(p) => p.projectId === assignment.projectId,
);
if (!project) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Assignment references a project outside this organization",
});
}
const environmentIds = new Set(
project.environments.map((e) => e.environmentId),
);
for (const environmentId of assignment.environmentIds) {
if (!environmentIds.has(environmentId)) {
throw new TRPCError({
code: "BAD_REQUEST",
message:
"Assignment references an environment outside the selected project",
});
}
}
}
};
export const createVaultProvider = async (
input: z.infer<typeof apiCreateVaultProvider>,
organizationId: string,
) => {
await validateAssignments(input.assignments, organizationId);
try {
const newProvider = await db
.insert(vaultProvider)
@@ -69,6 +105,7 @@ export const createVaultProvider = async (
name: input.name,
providerType: input.config.providerType,
config: input.config,
assignments: input.assignments,
organizationId,
})
.returning()
@@ -105,6 +142,20 @@ export const findVaultProviderById = async (vaultProviderId: string) => {
return provider;
};
export const findVaultProviderInOrganization = async (
vaultProviderId: string,
organizationId: string,
) => {
const provider = await findVaultProviderById(vaultProviderId);
if (provider.organizationId !== organizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to access this vault provider",
});
}
return provider;
};
export const findVaultProvidersByOrganizationId = async (
organizationId: string,
) => {
@@ -118,9 +169,11 @@ export const updateVaultProvider = async (
vaultProviderId: string,
name: string,
config: VaultProviderConfig,
assignments: VaultProviderAssignment[],
) => {
const existing = await findVaultProviderById(vaultProviderId);
const mergedConfig = mergeVaultProviderConfig(config, existing.config);
await validateAssignments(assignments, existing.organizationId);
try {
const updated = await db
@@ -129,6 +182,7 @@ export const updateVaultProvider = async (
name,
providerType: mergedConfig.providerType,
config: mergedConfig,
assignments,
})
.where(eq(vaultProvider.vaultProviderId, vaultProviderId))
.returning()
+1 -5
View File
@@ -42,11 +42,7 @@ const getAccessToken = async (config: AzureConfig) => {
return data.access_token;
};
const readSecret = async (
config: AzureConfig,
token: string,
name: string,
) => {
const readSecret = async (config: AzureConfig, token: string, name: string) => {
const response = await vaultFetch(
`${baseUrl(config)}/secrets/${encodeURIComponent(name)}?api-version=${API_VERSION}`,
{ headers: { Authorization: `Bearer ${token}` } },
Binary file not shown.