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
+133 -7
View File
@@ -36,17 +36,25 @@ beforeEach(() => {
mockFetch.mockReset();
});
const scope = {
organizationId: "org-1",
projectId: "proj-1",
environmentId: "env-1",
};
const assignedEverywhere = [{ projectId: "proj-1", environmentIds: [] }];
describe("resolveVaultReferences", () => {
it("returns input untouched and skips the db when no refs are present", async () => {
const env = "FOO=bar\nBAZ=${{project.QUX}}";
const result = await resolveVaultReferences(env, "org-1");
const result = await resolveVaultReferences(env, scope);
expect(result).toBe(env);
expect(findMany).not.toHaveBeenCalled();
});
it("returns null/empty inputs unchanged", async () => {
expect(await resolveVaultReferences(null, "org-1")).toBeNull();
expect(await resolveVaultReferences("", "org-1")).toBe("");
expect(await resolveVaultReferences(null, scope)).toBeNull();
expect(await resolveVaultReferences("", scope)).toBe("");
});
it("throws when refs exist but no organization context is given", async () => {
@@ -58,16 +66,64 @@ describe("resolveVaultReferences", () => {
it("throws for an unknown provider", async () => {
findMany.mockResolvedValue([]);
await expect(
resolveVaultReferences("FOO=${{vault.missing.SECRET}}", "org-1"),
resolveVaultReferences("FOO=${{vault.missing.SECRET}}", scope),
).rejects.toThrow('Vault provider "missing" not found');
});
it("throws when the provider is not assigned to the project", async () => {
findMany.mockResolvedValue([
{
name: "prod",
providerType: "doppler",
config: { providerType: "doppler", serviceToken: "dp.st.token" },
assignments: [{ projectId: "other-project", environmentIds: [] }],
},
]);
await expect(
resolveVaultReferences("FOO=${{vault.prod.SECRET}}", scope),
).rejects.toThrow("not enabled for this project/environment");
});
it("throws when the provider is restricted to another environment", async () => {
findMany.mockResolvedValue([
{
name: "prod",
providerType: "doppler",
config: { providerType: "doppler", serviceToken: "dp.st.token" },
assignments: [
{ projectId: "proj-1", environmentIds: ["production-env"] },
],
},
]);
await expect(
resolveVaultReferences("FOO=${{vault.prod.SECRET}}", scope),
).rejects.toThrow("not enabled for this project/environment");
});
it("allows a provider restricted to the matching environment", async () => {
findMany.mockResolvedValue([
{
name: "prod",
providerType: "doppler",
config: { providerType: "doppler", serviceToken: "dp.st.token" },
assignments: [{ projectId: "proj-1", environmentIds: ["env-1"] }],
},
]);
mockFetch.mockResolvedValue(jsonResponse({ SECRET: "value-1" }));
const result = await resolveVaultReferences(
"FOO=${{vault.prod.SECRET}}",
scope,
);
expect(result).toBe("FOO=value-1");
});
it("resolves refs through a doppler provider", async () => {
findMany.mockResolvedValue([
{
name: "doppler-prod",
providerType: "doppler",
config: { providerType: "doppler", serviceToken: "dp.st.token" },
assignments: assignedEverywhere,
},
]);
mockFetch.mockResolvedValue(
@@ -76,7 +132,7 @@ describe("resolveVaultReferences", () => {
const result = await resolveVaultReferences(
"DB_URL=${{vault.doppler-prod.DB_URL}}\nAPI_KEY=${{vault.doppler-prod.API_KEY}}",
"org-1",
scope,
);
expect(result).toBe("DB_URL=postgres://real\nAPI_KEY=key-123");
@@ -89,12 +145,13 @@ describe("resolveVaultReferences", () => {
name: "doppler-prod",
providerType: "doppler",
config: { providerType: "doppler", serviceToken: "dp.st.token" },
assignments: assignedEverywhere,
},
]);
mockFetch.mockResolvedValue(jsonResponse({ OTHER: "x" }));
await expect(
resolveVaultReferences("FOO=${{vault.doppler-prod.MISSING}}", "org-1"),
resolveVaultReferences("FOO=${{vault.doppler-prod.MISSING}}", scope),
).rejects.toThrow('secret "MISSING" not found');
});
});
@@ -106,6 +163,7 @@ describe("withResolvedVaultRefs + prepareEnvironmentVariables", () => {
name: "prod",
providerType: "doppler",
config: { providerType: "doppler", serviceToken: "dp.st.token" },
assignments: assignedEverywhere,
},
]);
mockFetch.mockResolvedValue(jsonResponse({ DB_PASSWORD: "s3cret" }));
@@ -113,8 +171,10 @@ describe("withResolvedVaultRefs + prepareEnvironmentVariables", () => {
const entity = {
env: "DATABASE_URL=postgres://user:${{project.DB_PASSWORD}}@db",
environment: {
environmentId: "env-1",
env: null,
project: {
projectId: "proj-1",
env: "DB_PASSWORD=${{vault.prod.DB_PASSWORD}}",
organizationId: "org-1",
},
@@ -137,6 +197,7 @@ describe("withResolvedVaultRefs + prepareEnvironmentVariables", () => {
name: "prod",
providerType: "doppler",
config: { providerType: "doppler", serviceToken: "dp.st.token" },
assignments: assignedEverywhere,
},
]);
mockFetch.mockResolvedValue(jsonResponse({ NPM_TOKEN: "npm-123" }));
@@ -146,8 +207,9 @@ describe("withResolvedVaultRefs + prepareEnvironmentVariables", () => {
buildArgs: "NPM_TOKEN=${{vault.prod.NPM_TOKEN}}",
buildSecrets: null,
environment: {
environmentId: "env-1",
env: null,
project: { env: null, organizationId: "org-1" },
project: { projectId: "proj-1", env: null, organizationId: "org-1" },
},
});
@@ -156,6 +218,70 @@ describe("withResolvedVaultRefs + prepareEnvironmentVariables", () => {
expect(resolved.env).toBe("FOO=bar");
});
it.each([
["service env", { env: "SECRET=${{vault.locked.X}}" }],
["environment env", { environmentEnv: "SECRET=${{vault.locked.X}}" }],
["project env", { projectEnv: "SECRET=${{vault.locked.X}}" }],
["build args", { buildArgs: "SECRET=${{vault.locked.X}}" }],
])(
"rejects a ref to an unassigned provider placed in the %s",
async (_source, overrides) => {
findMany.mockResolvedValue([
{
name: "locked",
providerType: "doppler",
config: { providerType: "doppler", serviceToken: "dp.st.token" },
assignments: [{ projectId: "other-project", environmentIds: [] }],
},
]);
const entity = {
env: (overrides as { env?: string }).env ?? "FOO=bar",
buildArgs: (overrides as { buildArgs?: string }).buildArgs ?? null,
environment: {
environmentId: "env-1",
env:
(overrides as { environmentEnv?: string }).environmentEnv ?? null,
project: {
projectId: "proj-1",
env: (overrides as { projectEnv?: string }).projectEnv ?? null,
organizationId: "org-1",
},
},
};
await expect(withResolvedVaultRefs(entity)).rejects.toThrow(
"not enabled for this project/environment",
);
expect(mockFetch).not.toHaveBeenCalled();
},
);
it("rejects a ref restricted to another environment from the environment env", async () => {
findMany.mockResolvedValue([
{
name: "prod-only",
providerType: "doppler",
config: { providerType: "doppler", serviceToken: "dp.st.token" },
assignments: [
{ projectId: "proj-1", environmentIds: ["production-env"] },
],
},
]);
await expect(
withResolvedVaultRefs({
env: null,
environment: {
environmentId: "dev-env",
env: "SECRET=${{vault.prod-only.X}}",
project: { projectId: "proj-1", env: null, organizationId: "org-1" },
},
}),
).rejects.toThrow("not enabled for this project/environment");
expect(mockFetch).not.toHaveBeenCalled();
});
it("prepareEnvironmentVariables throws on unresolved vault refs", () => {
expect(() =>
prepareEnvironmentVariables("FOO=${{vault.prod.SECRET}}", "", ""),
@@ -62,6 +62,8 @@ export const ShowEnvironment = ({ id, type }: Props) => {
const completionSource = useEnvCompletionSource({
projectEnv: data?.environment?.project?.env,
environmentEnv: data?.environment?.env,
projectId: data?.environment?.projectId,
environmentId: data?.environment?.environmentId,
});
const mutationMap = {
@@ -49,6 +49,8 @@ export const ShowEnvironment = ({ applicationId }: Props) => {
const completionSource = useEnvCompletionSource({
projectEnv: data?.environment?.project?.env,
environmentEnv: data?.environment?.env,
projectId: data?.environment?.projectId,
environmentId: data?.environment?.environmentId,
});
const form = useForm<EnvironmentSchema>({
@@ -58,6 +58,8 @@ export const EnvironmentVariables = ({ environmentId, children }: Props) => {
const completionSource = useEnvCompletionSource({
includeShared: false,
projectId: data?.projectId,
environmentId,
});
const form = useForm<UpdateEnvironment>({
defaultValues: {
@@ -58,6 +58,7 @@ export const ProjectEnvironment = ({ projectId, children }: Props) => {
const completionSource = useEnvCompletionSource({
includeShared: false,
projectId,
});
const form = useForm<UpdateProject>({
defaultValues: {
@@ -7,6 +7,7 @@ import { z } from "zod";
import { vaultProviderIcons } from "@/components/icons/vault-provider-icons";
import { AlertBlock } from "@/components/shared/alert-block";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import {
Dialog,
DialogContent,
@@ -76,6 +77,12 @@ const VaultProviderSchema = z
azureClientSecret: z.string(),
dopplerProject: z.string(),
dopplerConfig: z.string(),
assignments: z.array(
z.object({
projectId: z.string(),
environmentIds: z.array(z.string()),
}),
),
})
.superRefine((data, ctx) => {
const isValidUrl = (value: string) => {
@@ -215,6 +222,7 @@ const defaultValues: VaultProviderForm = {
azureClientSecret: "",
dopplerProject: "",
dopplerConfig: "",
assignments: [],
};
const buildConfig = (data: VaultProviderForm) => {
@@ -303,6 +311,39 @@ export const HandleVaultProvider = ({ vaultProviderId }: Props) => {
});
const providerType = form.watch("providerType");
const assignments = form.watch("assignments");
const { data: orgProjects } = api.project.all.useQuery();
const setAssignments = (
next: { projectId: string; environmentIds: string[] }[],
) => form.setValue("assignments", next, { shouldValidate: true });
const toggleProject = (targetProjectId: string) => {
const exists = assignments.some((a) => a.projectId === targetProjectId);
setAssignments(
exists
? assignments.filter((a) => a.projectId !== targetProjectId)
: [...assignments, { projectId: targetProjectId, environmentIds: [] }],
);
};
const toggleEnvironment = (
targetProjectId: string,
environmentId: string,
) => {
setAssignments(
assignments.map((a) => {
if (a.projectId !== targetProjectId) return a;
const has = a.environmentIds.includes(environmentId);
return {
...a,
environmentIds: has
? a.environmentIds.filter((e) => e !== environmentId)
: [...a.environmentIds, environmentId],
};
}),
);
};
useEffect(() => {
if (provider) {
@@ -310,6 +351,7 @@ export const HandleVaultProvider = ({ vaultProviderId }: Props) => {
...defaultValues,
name: provider.name,
providerType: provider.config.providerType,
assignments: provider.assignments ?? [],
...(provider.config.providerType === "hashicorp" && {
url: provider.config.url,
token: provider.config.token,
@@ -351,6 +393,7 @@ export const HandleVaultProvider = ({ vaultProviderId }: Props) => {
const payload: any = {
name: data.name,
config: buildConfig(data),
assignments: data.assignments,
...(vaultProviderId && { vaultProviderId }),
};
await mutateAsync(payload)
@@ -751,8 +794,8 @@ export const HandleVaultProvider = ({ vaultProviderId }: Props) => {
/>
</div>
<FormDescription>
App Registration with the "Key Vault Secrets User" role on
the vault. Reference format:{" "}
App Registration with the "Key Vault Secrets User" role on the
vault. Reference format:{" "}
<code>{"${{vault.<name>.secret-name}}"}</code>
</FormDescription>
</>
@@ -816,6 +859,71 @@ export const HandleVaultProvider = ({ vaultProviderId }: Props) => {
</>
)}
<div className="flex flex-col gap-2 rounded-lg border p-3">
<FormLabel>Access</FormLabel>
<FormDescription>
This provider can only be referenced from the selected projects.
Pick environments to narrow it further — none selected means all
environments of that project.
</FormDescription>
<div className="flex flex-col gap-2">
{orgProjects?.map((project) => {
const assignment = assignments.find(
(a) => a.projectId === project.projectId,
);
return (
<div
key={project.projectId}
className="flex flex-col gap-1.5"
>
<label className="flex flex-row items-center gap-2 text-sm cursor-pointer">
<Checkbox
checked={!!assignment}
onCheckedChange={() =>
toggleProject(project.projectId)
}
/>
{project.name}
</label>
{assignment && (
<div className="flex flex-row flex-wrap gap-3 pl-6">
{project.environments?.map((environment) => (
<label
key={environment.environmentId}
className="flex flex-row items-center gap-1.5 text-xs text-muted-foreground cursor-pointer"
>
<Checkbox
checked={assignment.environmentIds.includes(
environment.environmentId,
)}
onCheckedChange={() =>
toggleEnvironment(
project.projectId,
environment.environmentId,
)
}
/>
{environment.name}
</label>
))}
{assignment.environmentIds.length === 0 && (
<span className="text-xs text-muted-foreground italic self-center">
All environments
</span>
)}
</div>
)}
</div>
);
})}
</div>
{form.formState.errors.assignments && (
<p className="text-sm font-medium text-destructive">
{form.formState.errors.assignments.message}
</p>
)}
</div>
<DialogFooter className="flex w-full flex-row justify-between gap-2 sm:justify-between">
<Button
type="button"
@@ -80,6 +80,18 @@ export const ShowVaultProviders = () => {
{provider.name}
</span>
<div className="flex flex-row gap-2 items-center">
{provider.assignments.length === 0 ? (
<Badge variant="destructive">
Not assigned
</Badge>
) : (
<Badge variant="secondary">
{provider.assignments.length}{" "}
{provider.assignments.length === 1
? "project"
: "projects"}
</Badge>
)}
<Badge variant="outline">
{providerLabels[provider.providerType] ??
provider.providerType}
@@ -163,6 +163,11 @@ const RESOURCE_META: Record<string, { label: string; description: string }> = {
label: "Audit Logs",
description: "View the audit log of actions performed in the organization",
},
vaultProvider: {
label: "Secrets Providers",
description:
"Manage external secret managers (HashiCorp Vault, AWS, Azure, Infisical, Doppler) and where their secrets can be referenced",
},
};
/** Descriptions for each action within a resource */
@@ -419,6 +424,22 @@ const ACTION_META: Record<
auditLog: {
read: { label: "Read", description: "View the audit log history" },
},
vaultProvider: {
read: {
label: "Read",
description: "View providers and secret names for env autocomplete",
},
create: {
label: "Create",
description: "Connect new secret providers and test their connection",
},
update: {
label: "Update",
description:
"Edit provider credentials and project/environment assignments",
},
delete: { label: "Delete", description: "Remove secret providers" },
},
};
/** Resources that should be hidden from the custom role editor (better-auth internals) */
@@ -12,6 +12,8 @@ interface Options {
projectEnv?: string | null;
environmentEnv?: string | null;
includeShared?: boolean;
projectId?: string;
environmentId?: string;
}
const parseKeys = (env?: string | null) =>
@@ -55,9 +57,22 @@ export const useEnvCompletionSource = ({
projectEnv,
environmentEnv,
includeShared = true,
projectId,
environmentId,
}: Options = {}) => {
const { data: providers } = api.vaultProvider.all.useQuery();
const { data: allProviders } = api.vaultProvider.all.useQuery();
const utils = api.useUtils();
const providers = projectId
? allProviders?.filter((provider) =>
provider.assignments?.some(
(assignment) =>
assignment.projectId === projectId &&
(assignment.environmentIds.length === 0 ||
!environmentId ||
assignment.environmentIds.includes(environmentId)),
),
)
: [];
return useCallback(
async (context: CompletionContext): Promise<CompletionResult | null> => {
@@ -0,0 +1,13 @@
CREATE TYPE "public"."VaultProviderType" AS ENUM('hashicorp', 'infisical', 'aws', 'doppler', 'azure');--> statement-breakpoint
CREATE TABLE "vault_provider" (
"vaultProviderId" text PRIMARY KEY NOT NULL,
"name" text NOT NULL,
"providerType" "VaultProviderType" NOT NULL,
"config" jsonb NOT NULL,
"assignments" jsonb DEFAULT '[]'::jsonb NOT NULL,
"organizationId" text NOT NULL,
"createdAt" text NOT NULL
);
--> statement-breakpoint
ALTER TABLE "vault_provider" ADD CONSTRAINT "vault_provider_organizationId_organization_id_fk" FOREIGN KEY ("organizationId") REFERENCES "public"."organization"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
CREATE UNIQUE INDEX "vault_provider_org_name_idx" ON "vault_provider" USING btree ("organizationId","name");
File diff suppressed because it is too large Load Diff
+7
View File
@@ -1268,6 +1268,13 @@
"when": 1786132623336,
"tag": "0180_slippery_norrin_radd",
"breakpoints": true
},
{
"idx": 181,
"version": "7",
"when": 1786400712839,
"tag": "0181_dashing_havok",
"breakpoints": true
}
]
}
@@ -1,6 +1,6 @@
import {
createVaultProvider,
findVaultProviderById,
findVaultProviderInOrganization,
findVaultProvidersByOrganizationId,
listVaultProviderSecretNames,
maskVaultProviderConfig,
@@ -21,20 +21,6 @@ import {
} from "@/server/db/schema";
import { createTRPCRouter, withPermission } from "../trpc";
const findInOrganization = 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 vaultProviderRouter = createTRPCRouter({
create: withPermission("vaultProvider", "create")
.input(apiCreateVaultProvider)
@@ -55,7 +41,7 @@ export const vaultProviderRouter = createTRPCRouter({
update: withPermission("vaultProvider", "update")
.input(apiUpdateVaultProvider)
.mutation(async ({ ctx, input }) => {
await findInOrganization(
await findVaultProviderInOrganization(
input.vaultProviderId,
ctx.session.activeOrganizationId,
);
@@ -63,6 +49,7 @@ export const vaultProviderRouter = createTRPCRouter({
input.vaultProviderId,
input.name,
input.config,
input.assignments,
);
await audit(ctx, {
action: "update",
@@ -76,7 +63,7 @@ export const vaultProviderRouter = createTRPCRouter({
remove: withPermission("vaultProvider", "delete")
.input(apiRemoveVaultProvider)
.mutation(async ({ ctx, input }) => {
const provider = await findInOrganization(
const provider = await findVaultProviderInOrganization(
input.vaultProviderId,
ctx.session.activeOrganizationId,
);
@@ -103,7 +90,7 @@ export const vaultProviderRouter = createTRPCRouter({
one: withPermission("vaultProvider", "read")
.input(apiFindOneVaultProvider)
.query(async ({ ctx, input }) => {
const provider = await findInOrganization(
const provider = await findVaultProviderInOrganization(
input.vaultProviderId,
ctx.session.activeOrganizationId,
);
@@ -122,7 +109,7 @@ export const vaultProviderRouter = createTRPCRouter({
let config = input.config;
if (input.vaultProviderId) {
const provider = await findInOrganization(
const provider = await findVaultProviderInOrganization(
input.vaultProviderId,
ctx.session.activeOrganizationId,
);
@@ -138,7 +125,7 @@ export const vaultProviderRouter = createTRPCRouter({
listSecretNames: withPermission("vaultProvider", "read")
.input(apiListVaultSecretNames)
.query(async ({ ctx, input }) => {
const provider = await findInOrganization(
const provider = await findVaultProviderInOrganization(
input.vaultProviderId,
ctx.session.activeOrganizationId,
);
@@ -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.