feat: secret variables in environments (#3779)
Co-authored-by: jamesgeorge007 <jamesgeorge998001@gmail.com>
This commit is contained in:
@@ -2,22 +2,38 @@ import * as E from "fp-ts/Either"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import { InferredEntity, createVersionedEntity } from "verzod"
|
||||
|
||||
import { z } from "zod"
|
||||
|
||||
import V0_VERSION from "./v/0"
|
||||
import V1_VERSION from "./v/1"
|
||||
|
||||
const versionedObject = z.object({
|
||||
v: z.number(),
|
||||
})
|
||||
|
||||
export const Environment = createVersionedEntity({
|
||||
latestVersion: 0,
|
||||
latestVersion: 1,
|
||||
versionMap: {
|
||||
0: V0_VERSION
|
||||
0: V0_VERSION,
|
||||
1: V1_VERSION,
|
||||
},
|
||||
getVersion(data) {
|
||||
const versionCheck = versionedObject.safeParse(data)
|
||||
|
||||
if (versionCheck.success) return versionCheck.data.v
|
||||
|
||||
// For V0 we have to check the schema
|
||||
const result = V0_VERSION.schema.safeParse(data)
|
||||
return result.success ? 0 : null
|
||||
},
|
||||
getVersion(x) {
|
||||
return V0_VERSION.schema.safeParse(x).success
|
||||
? 0
|
||||
: null
|
||||
}
|
||||
})
|
||||
|
||||
export type Environment = InferredEntity<typeof Environment>
|
||||
|
||||
export type EnvironmentVariable = InferredEntity<
|
||||
typeof Environment
|
||||
>["variables"][number]
|
||||
|
||||
const REGEX_ENV_VAR = /<<([^>]*)>>/g // "<<myVariable>>"
|
||||
|
||||
/**
|
||||
@@ -31,6 +47,8 @@ const ENV_MAX_EXPAND_LIMIT = 10
|
||||
*/
|
||||
const ENV_EXPAND_LOOP = "ENV_EXPAND_LOOP" as const
|
||||
|
||||
export const EnvironmentSchemaVersion = 1
|
||||
|
||||
export function parseBodyEnvVariablesE(
|
||||
body: string,
|
||||
env: Environment["variables"]
|
||||
@@ -43,7 +61,11 @@ export function parseBodyEnvVariablesE(
|
||||
const found = env.find(
|
||||
(envVar) => envVar.key === key.replace(/[<>]/g, "")
|
||||
)
|
||||
return found ? found.value : key
|
||||
|
||||
if (found && "value" in found) {
|
||||
return found.value
|
||||
}
|
||||
return key
|
||||
})
|
||||
|
||||
depth++
|
||||
@@ -68,7 +90,10 @@ export const parseBodyEnvVariables = (
|
||||
|
||||
export function parseTemplateStringE(
|
||||
str: string,
|
||||
variables: Environment["variables"]
|
||||
variables:
|
||||
| Environment["variables"]
|
||||
| { secret: true; value: string; key: string }[],
|
||||
maskValue = false
|
||||
) {
|
||||
if (!variables || !str) {
|
||||
return E.right(str)
|
||||
@@ -78,10 +103,21 @@ export function parseTemplateStringE(
|
||||
let depth = 0
|
||||
|
||||
while (result.match(REGEX_ENV_VAR) != null && depth <= ENV_MAX_EXPAND_LIMIT) {
|
||||
result = decodeURI(encodeURI(result)).replace(
|
||||
REGEX_ENV_VAR,
|
||||
(_, p1) => variables.find((x) => x.key === p1)?.value || ""
|
||||
)
|
||||
result = decodeURI(encodeURI(result)).replace(REGEX_ENV_VAR, (_, p1) => {
|
||||
const variable = variables.find((x) => x && x.key === p1)
|
||||
if (variable && "value" in variable) {
|
||||
// Mask the value if it is a secret and explicitly specified
|
||||
if (variable.secret && maskValue) {
|
||||
return "*".repeat(
|
||||
(variable as { secret: true; value: string; key: string }).value
|
||||
.length
|
||||
)
|
||||
}
|
||||
return variable.value
|
||||
}
|
||||
|
||||
return ""
|
||||
})
|
||||
depth++
|
||||
}
|
||||
|
||||
@@ -90,14 +126,52 @@ export function parseTemplateStringE(
|
||||
: E.right(result)
|
||||
}
|
||||
|
||||
export type NonSecretEnvironmentVariable = Extract<
|
||||
EnvironmentVariable,
|
||||
{ secret: false }
|
||||
>
|
||||
|
||||
export type NonSecretEnvironment = Omit<Environment, "variables"> & {
|
||||
variables: NonSecretEnvironmentVariable[]
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use `parseTemplateStringE` instead
|
||||
*/
|
||||
export const parseTemplateString = (
|
||||
str: string,
|
||||
variables: Environment["variables"]
|
||||
variables:
|
||||
| Environment["variables"]
|
||||
| { secret: true; value: string; key: string }[],
|
||||
maskValue = false
|
||||
) =>
|
||||
pipe(
|
||||
parseTemplateStringE(str, variables),
|
||||
parseTemplateStringE(str, variables, maskValue),
|
||||
E.getOrElse(() => str)
|
||||
)
|
||||
|
||||
export const translateToNewEnvironmentVariables = (
|
||||
x: any
|
||||
): Environment["variables"][number] => {
|
||||
return {
|
||||
key: x.key,
|
||||
value: x.value,
|
||||
secret: false,
|
||||
}
|
||||
}
|
||||
|
||||
export const translateToNewEnvironment = (x: any): Environment => {
|
||||
if (x.v && x.v === EnvironmentSchemaVersion) return x
|
||||
|
||||
// Legacy
|
||||
const id = x.id ?? ""
|
||||
const name = x.name ?? "Untitled"
|
||||
const variables = (x.variables ?? []).map(translateToNewEnvironmentVariables)
|
||||
|
||||
return {
|
||||
v: EnvironmentSchemaVersion,
|
||||
id,
|
||||
name,
|
||||
variables,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,10 +9,10 @@ export const V0_SCHEMA = z.object({
|
||||
key: z.string(),
|
||||
value: z.string(),
|
||||
})
|
||||
)
|
||||
),
|
||||
})
|
||||
|
||||
export default defineVersion({
|
||||
initial: true,
|
||||
schema: V0_SCHEMA
|
||||
schema: V0_SCHEMA,
|
||||
})
|
||||
|
||||
42
packages/hoppscotch-data/src/environment/v/1.ts
Normal file
42
packages/hoppscotch-data/src/environment/v/1.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { z } from "zod"
|
||||
import { defineVersion } from "verzod"
|
||||
import { V0_SCHEMA } from "./0"
|
||||
|
||||
export const V1_SCHEMA = z.object({
|
||||
v: z.literal(1),
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
variables: z.array(
|
||||
z.union([
|
||||
z.object({
|
||||
key: z.string(),
|
||||
secret: z.literal(true),
|
||||
}),
|
||||
z.object({
|
||||
key: z.string(),
|
||||
value: z.string(),
|
||||
secret: z.literal(false),
|
||||
}),
|
||||
])
|
||||
),
|
||||
})
|
||||
|
||||
export default defineVersion({
|
||||
initial: false,
|
||||
schema: V1_SCHEMA,
|
||||
up(old: z.infer<typeof V0_SCHEMA>) {
|
||||
const result: z.infer<typeof V1_SCHEMA> = {
|
||||
...old,
|
||||
v: 1,
|
||||
id: old.id ?? "",
|
||||
variables: old.variables.map((variable) => {
|
||||
return {
|
||||
...variable,
|
||||
secret: false,
|
||||
}
|
||||
}),
|
||||
}
|
||||
|
||||
return result
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user