diff --git a/.gitignore b/.gitignore
index f21c85c41..f2a23324b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,3 @@
-.direnv/
-
# backend
node_modules
.env
@@ -28,6 +26,8 @@ node_modules
/.pnp
.pnp.js
+.env
+
# testing
coverage
reports
@@ -63,12 +63,10 @@ yarn-error.log*
# Editor specific
.vscode/*
-**/.idea/*
+.idea/*
frontend-build
-# cli
-.go/
*.tgz
cli/infisical-merge
cli/test/infisical-merge
diff --git a/backend/src/@types/fastify-request-context.d.ts b/backend/src/@types/fastify-request-context.d.ts
deleted file mode 100644
index fc8d94e07..000000000
--- a/backend/src/@types/fastify-request-context.d.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import "@fastify/request-context";
-
-declare module "@fastify/request-context" {
- interface RequestContextData {
- reqId: string;
- }
-}
diff --git a/backend/src/@types/fastify.d.ts b/backend/src/@types/fastify.d.ts
index 85a19b676..99c7c4184 100644
--- a/backend/src/@types/fastify.d.ts
+++ b/backend/src/@types/fastify.d.ts
@@ -100,6 +100,12 @@ import { TWorkflowIntegrationServiceFactory } from "@app/services/workflow-integ
declare module "@fastify/request-context" {
interface RequestContextData {
reqId: string;
+ identityAuthInfo?: {
+ identityId: string;
+ oidc?: {
+ claims: Record;
+ };
+ };
}
}
diff --git a/backend/src/db/migrations/20250212191958_create-gateway.ts b/backend/src/db/migrations/20250212191958_create-gateway.ts
index 7738da5cc..14c498ca9 100644
--- a/backend/src/db/migrations/20250212191958_create-gateway.ts
+++ b/backend/src/db/migrations/20250212191958_create-gateway.ts
@@ -85,7 +85,7 @@ export async function up(knex: Knex): Promise {
}
if (await knex.schema.hasTable(TableName.DynamicSecret)) {
- const doesGatewayColExist = await knex.schema.hasColumn(TableName.DynamicSecret, "gatewayId");
+ const doesGatewayColExist = await knex.schema.hasColumn(TableName.DynamicSecret, "projectGatewayId");
await knex.schema.alterTable(TableName.DynamicSecret, (t) => {
// not setting a foreign constraint so that cascade effects are not triggered
if (!doesGatewayColExist) {
diff --git a/backend/src/db/migrations/20250314145202_identity-oidc-claim-mapping.ts b/backend/src/db/migrations/20250314145202_identity-oidc-claim-mapping.ts
new file mode 100644
index 000000000..482c5c4eb
--- /dev/null
+++ b/backend/src/db/migrations/20250314145202_identity-oidc-claim-mapping.ts
@@ -0,0 +1,21 @@
+import { Knex } from "knex";
+
+import { TableName } from "../schemas";
+
+export async function up(knex: Knex): Promise {
+ const hasMappingField = await knex.schema.hasColumn(TableName.IdentityOidcAuth, "claimMetadataMapping");
+ if (!hasMappingField) {
+ await knex.schema.alterTable(TableName.IdentityOidcAuth, (t) => {
+ t.jsonb("claimMetadataMapping");
+ });
+ }
+}
+
+export async function down(knex: Knex): Promise {
+ const hasMappingField = await knex.schema.hasColumn(TableName.IdentityOidcAuth, "claimMetadataMapping");
+ if (hasMappingField) {
+ await knex.schema.alterTable(TableName.IdentityOidcAuth, (t) => {
+ t.dropColumn("claimMetadataMapping");
+ });
+ }
+}
diff --git a/backend/src/db/schemas/identity-oidc-auths.ts b/backend/src/db/schemas/identity-oidc-auths.ts
index ebde5e7dc..03bfcf40a 100644
--- a/backend/src/db/schemas/identity-oidc-auths.ts
+++ b/backend/src/db/schemas/identity-oidc-auths.ts
@@ -26,7 +26,8 @@ export const IdentityOidcAuthsSchema = z.object({
boundSubject: z.string().nullable().optional(),
createdAt: z.date(),
updatedAt: z.date(),
- encryptedCaCertificate: zodBuffer.nullable().optional()
+ encryptedCaCertificate: zodBuffer.nullable().optional(),
+ claimMetadataMapping: z.unknown().nullable().optional()
});
export type TIdentityOidcAuths = z.infer;
diff --git a/backend/src/db/schemas/secret-sharing.ts b/backend/src/db/schemas/secret-sharing.ts
index 3406b4d63..24ea26677 100644
--- a/backend/src/db/schemas/secret-sharing.ts
+++ b/backend/src/db/schemas/secret-sharing.ts
@@ -12,7 +12,6 @@ import { TImmutableDBKeys } from "./models";
export const SecretSharingSchema = z.object({
id: z.string().uuid(),
encryptedValue: z.string().nullable().optional(),
- type: z.string(),
iv: z.string().nullable().optional(),
tag: z.string().nullable().optional(),
hashedHex: z.string().nullable().optional(),
@@ -27,7 +26,8 @@ export const SecretSharingSchema = z.object({
lastViewedAt: z.date().nullable().optional(),
password: z.string().nullable().optional(),
encryptedSecret: zodBuffer.nullable().optional(),
- identifier: z.string().nullable().optional()
+ identifier: z.string().nullable().optional(),
+ type: z.string().default("share")
});
export type TSecretSharing = z.infer;
diff --git a/backend/src/ee/services/audit-log/audit-log-types.ts b/backend/src/ee/services/audit-log/audit-log-types.ts
index a8f96bb73..9244e569e 100644
--- a/backend/src/ee/services/audit-log/audit-log-types.ts
+++ b/backend/src/ee/services/audit-log/audit-log-types.ts
@@ -978,6 +978,7 @@ interface AddIdentityOidcAuthEvent {
boundIssuer: string;
boundAudiences: string;
boundClaims: Record;
+ claimMetadataMapping: Record;
boundSubject: string;
accessTokenTTL: number;
accessTokenMaxTTL: number;
@@ -1002,6 +1003,7 @@ interface UpdateIdentityOidcAuthEvent {
boundIssuer?: string;
boundAudiences?: string;
boundClaims?: Record;
+ claimMetadataMapping?: Record;
boundSubject?: string;
accessTokenTTL?: number;
accessTokenMaxTTL?: number;
diff --git a/backend/src/ee/services/permission/permission-service.ts b/backend/src/ee/services/permission/permission-service.ts
index ddaf55d9d..6b36234ca 100644
--- a/backend/src/ee/services/permission/permission-service.ts
+++ b/backend/src/ee/services/permission/permission-service.ts
@@ -1,5 +1,6 @@
import { createMongoAbility, MongoAbility, RawRuleOf } from "@casl/ability";
import { PackRule, unpackRules } from "@casl/ability/extra";
+import { requestContext } from "@fastify/request-context";
import { MongoQuery } from "@ucast/mongo2js";
import handlebars from "handlebars";
@@ -317,14 +318,17 @@ export const permissionServiceFactory = ({
const rules = buildProjectPermissionRules(rolePermissions.concat(additionalPrivileges));
const templatedRules = handlebars.compile(JSON.stringify(rules), { data: false });
- const metadataKeyValuePair = escapeHandlebarsMissingMetadata(
- objectify(
- identityProjectPermission.metadata,
- (i) => i.key,
- (i) => i.value
- )
- );
-
+ const identityAuthInfo = requestContext.get("identityAuthInfo");
+ const unescapedMetadata = objectify(
+ identityProjectPermission.metadata,
+ (i) => i.key,
+ (i) => i.value
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ ) as Record;
+ if (identityAuthInfo?.identityId === identityId && identityAuthInfo) {
+ unescapedMetadata.auth = identityAuthInfo;
+ }
+ const metadataKeyValuePair = escapeHandlebarsMissingMetadata(unescapedMetadata);
const interpolateRules = templatedRules(
{
identity: {
diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts
index 35ef4e091..d5a0857f2 100644
--- a/backend/src/lib/api-docs/constants.ts
+++ b/backend/src/lib/api-docs/constants.ts
@@ -329,6 +329,7 @@ export const OIDC_AUTH = {
boundIssuer: "The unique identifier of the identity provider issuing the JWT.",
boundAudiences: "The list of intended recipients.",
boundClaims: "The attributes that should be present in the JWT for it to be valid.",
+ claimMetadataMapping: "The attributes that should be present in the permission metadata from the JWT.",
boundSubject: "The expected principal that is the subject of the JWT.",
accessTokenTrustedIps: "The IPs or CIDR ranges that access tokens can be used from.",
accessTokenTTL: "The lifetime for an access token in seconds.",
@@ -342,6 +343,7 @@ export const OIDC_AUTH = {
boundIssuer: "The new unique identifier of the identity provider issuing the JWT.",
boundAudiences: "The new list of intended recipients.",
boundClaims: "The new attributes that should be present in the JWT for it to be valid.",
+ claimMetadataMapping: "The new attributes that should be present in the permission metadata from the JWT.",
boundSubject: "The new expected principal that is the subject of the JWT.",
accessTokenTrustedIps: "The new IPs or CIDR ranges that access tokens can be used from.",
accessTokenTTL: "The new lifetime for an access token in seconds.",
@@ -1726,7 +1728,8 @@ export const SecretSyncs = {
SYNC_OPTIONS: (destination: SecretSync) => {
const destinationName = SECRET_SYNC_NAME_MAP[destination];
return {
- initialSyncBehavior: `Specify how Infisical should resolve the initial sync to the ${destinationName} destination.`
+ initialSyncBehavior: `Specify how Infisical should resolve the initial sync to the ${destinationName} destination.`,
+ disableSecretDeletion: `Enable this flag to prevent removal of secrets from the ${destinationName} destination when syncing.`
};
},
ADDITIONAL_SYNC_OPTIONS: {
diff --git a/backend/src/lib/template/dot-access.ts b/backend/src/lib/template/dot-access.ts
new file mode 100644
index 000000000..ec3208feb
--- /dev/null
+++ b/backend/src/lib/template/dot-access.ts
@@ -0,0 +1,34 @@
+/**
+ * Safely retrieves a value from a nested object using dot notation path
+ */
+export const getStringValueByDot = (
+ obj: Record | null | undefined,
+ path: string,
+ defaultValue?: string
+): string | undefined => {
+ // Handle null or undefined input
+ if (!obj) {
+ return defaultValue;
+ }
+
+ const parts = path.split(".");
+ let current: unknown = obj;
+
+ for (const part of parts) {
+ const isObject = typeof current === "object" && !Array.isArray(current) && current !== null;
+ if (!isObject) {
+ return defaultValue;
+ }
+ if (!Object.hasOwn(current as object, part)) {
+ // Check if the property exists as an own property
+ return defaultValue;
+ }
+ current = (current as Record)[part];
+ }
+
+ if (typeof current !== "string") {
+ return defaultValue;
+ }
+
+ return current;
+};
diff --git a/backend/src/server/plugins/auth/inject-identity.ts b/backend/src/server/plugins/auth/inject-identity.ts
index 9d239a405..5779b39f7 100644
--- a/backend/src/server/plugins/auth/inject-identity.ts
+++ b/backend/src/server/plugins/auth/inject-identity.ts
@@ -1,3 +1,4 @@
+import { requestContext } from "@fastify/request-context";
import { FastifyRequest } from "fastify";
import fp from "fastify-plugin";
import jwt, { JwtPayload } from "jsonwebtoken";
@@ -137,6 +138,12 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => {
identityName: identity.name,
authMethod: null
};
+ if (token?.identityAuth?.oidc) {
+ requestContext.set("identityAuthInfo", {
+ identityId: identity.identityId,
+ oidc: token?.identityAuth?.oidc
+ });
+ }
break;
}
case AuthMode.SERVICE_TOKEN: {
diff --git a/backend/src/server/routes/v1/identity-oidc-auth-router.ts b/backend/src/server/routes/v1/identity-oidc-auth-router.ts
index 7ce0b05b7..f3098c851 100644
--- a/backend/src/server/routes/v1/identity-oidc-auth-router.ts
+++ b/backend/src/server/routes/v1/identity-oidc-auth-router.ts
@@ -23,6 +23,7 @@ const IdentityOidcAuthResponseSchema = IdentityOidcAuthsSchema.pick({
boundIssuer: true,
boundAudiences: true,
boundClaims: true,
+ claimMetadataMapping: true,
boundSubject: true,
createdAt: true,
updatedAt: true
@@ -104,6 +105,7 @@ export const registerIdentityOidcAuthRouter = async (server: FastifyZodProvider)
boundIssuer: z.string().min(1).describe(OIDC_AUTH.ATTACH.boundIssuer),
boundAudiences: validateOidcAuthAudiencesField.describe(OIDC_AUTH.ATTACH.boundAudiences),
boundClaims: validateOidcBoundClaimsField.describe(OIDC_AUTH.ATTACH.boundClaims),
+ claimMetadataMapping: validateOidcBoundClaimsField.describe(OIDC_AUTH.ATTACH.claimMetadataMapping).optional(),
boundSubject: z.string().optional().default("").describe(OIDC_AUTH.ATTACH.boundSubject),
accessTokenTrustedIps: z
.object({
@@ -161,6 +163,7 @@ export const registerIdentityOidcAuthRouter = async (server: FastifyZodProvider)
boundIssuer: identityOidcAuth.boundIssuer,
boundAudiences: identityOidcAuth.boundAudiences,
boundClaims: identityOidcAuth.boundClaims as Record,
+ claimMetadataMapping: identityOidcAuth.claimMetadataMapping as Record,
boundSubject: identityOidcAuth.boundSubject as string,
accessTokenTTL: identityOidcAuth.accessTokenTTL,
accessTokenMaxTTL: identityOidcAuth.accessTokenMaxTTL,
@@ -200,6 +203,7 @@ export const registerIdentityOidcAuthRouter = async (server: FastifyZodProvider)
boundIssuer: z.string().min(1).describe(OIDC_AUTH.UPDATE.boundIssuer),
boundAudiences: validateOidcAuthAudiencesField.describe(OIDC_AUTH.UPDATE.boundAudiences),
boundClaims: validateOidcBoundClaimsField.describe(OIDC_AUTH.UPDATE.boundClaims),
+ claimMetadataMapping: validateOidcBoundClaimsField.describe(OIDC_AUTH.UPDATE.claimMetadataMapping).optional(),
boundSubject: z.string().optional().default("").describe(OIDC_AUTH.UPDATE.boundSubject),
accessTokenTrustedIps: z
.object({
@@ -258,6 +262,7 @@ export const registerIdentityOidcAuthRouter = async (server: FastifyZodProvider)
boundIssuer: identityOidcAuth.boundIssuer,
boundAudiences: identityOidcAuth.boundAudiences,
boundClaims: identityOidcAuth.boundClaims as Record,
+ claimMetadataMapping: identityOidcAuth.claimMetadataMapping as Record,
boundSubject: identityOidcAuth.boundSubject as string,
accessTokenTTL: identityOidcAuth.accessTokenTTL,
accessTokenMaxTTL: identityOidcAuth.accessTokenMaxTTL,
diff --git a/backend/src/services/identity-access-token/identity-access-token-types.ts b/backend/src/services/identity-access-token/identity-access-token-types.ts
index 86967df76..c97d2f40a 100644
--- a/backend/src/services/identity-access-token/identity-access-token-types.ts
+++ b/backend/src/services/identity-access-token/identity-access-token-types.ts
@@ -7,4 +7,9 @@ export type TIdentityAccessTokenJwtPayload = {
clientSecretId: string;
identityAccessTokenId: string;
authTokenType: string;
+ identityAuth: {
+ oidc?: {
+ claims: Record;
+ };
+ };
};
diff --git a/backend/src/services/identity-jwt-auth/identity-jwt-auth-service.ts b/backend/src/services/identity-jwt-auth/identity-jwt-auth-service.ts
index 555dc00a6..9844b337f 100644
--- a/backend/src/services/identity-jwt-auth/identity-jwt-auth-service.ts
+++ b/backend/src/services/identity-jwt-auth/identity-jwt-auth-service.ts
@@ -11,6 +11,7 @@ import { validatePermissionBoundary } from "@app/lib/casl/boundary";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
+import { getStringValueByDot } from "@app/lib/template/dot-access";
import { ActorType, AuthTokenType } from "../auth/auth-type";
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
@@ -177,8 +178,9 @@ export const identityJwtAuthServiceFactory = ({
if (identityJwtAuth.boundClaims) {
Object.keys(identityJwtAuth.boundClaims).forEach((claimKey) => {
const claimValue = (identityJwtAuth.boundClaims as Record)[claimKey];
+ const value = getStringValueByDot(tokenData, claimKey) || "";
- if (!tokenData[claimKey]) {
+ if (!value) {
throw new UnauthorizedError({
message: `Access denied: token has no ${claimKey} field`
});
diff --git a/backend/src/services/identity-oidc-auth/identity-oidc-auth-service.ts b/backend/src/services/identity-oidc-auth/identity-oidc-auth-service.ts
index 3c947f504..cb641af4d 100644
--- a/backend/src/services/identity-oidc-auth/identity-oidc-auth-service.ts
+++ b/backend/src/services/identity-oidc-auth/identity-oidc-auth-service.ts
@@ -12,6 +12,7 @@ import { validatePermissionBoundary } from "@app/lib/casl/boundary";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
+import { getStringValueByDot } from "@app/lib/template/dot-access";
import { ActorType, AuthTokenType } from "../auth/auth-type";
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
@@ -77,7 +78,7 @@ export const identityOidcAuthServiceFactory = ({
const { data: discoveryDoc } = await axios.get<{ jwks_uri: string }>(
`${identityOidcAuth.oidcDiscoveryUrl}/.well-known/openid-configuration`,
{
- httpsAgent: requestAgent
+ httpsAgent: identityOidcAuth.oidcDiscoveryUrl.includes("https") ? requestAgent : undefined
}
);
const jwksUri = discoveryDoc.jwks_uri;
@@ -91,7 +92,7 @@ export const identityOidcAuthServiceFactory = ({
const client = new JwksClient({
jwksUri,
- requestAgent
+ requestAgent: identityOidcAuth.oidcDiscoveryUrl.includes("https") ? requestAgent : undefined
});
const { kid } = decodedToken.header;
@@ -108,7 +109,6 @@ export const identityOidcAuthServiceFactory = ({
message: `Access denied: ${error.message}`
});
}
-
throw error;
}
@@ -135,10 +135,16 @@ export const identityOidcAuthServiceFactory = ({
if (identityOidcAuth.boundClaims) {
Object.keys(identityOidcAuth.boundClaims).forEach((claimKey) => {
const claimValue = (identityOidcAuth.boundClaims as Record)[claimKey];
+ const value = getStringValueByDot(tokenData, claimKey) || "";
+
+ if (!value) {
+ throw new UnauthorizedError({
+ message: `Access denied: token has no ${claimKey} field`
+ });
+ }
+
// handle both single and multi-valued claims
- if (
- !claimValue.split(", ").some((claimEntry) => doesFieldValueMatchOidcPolicy(tokenData[claimKey], claimEntry))
- ) {
+ if (!claimValue.split(", ").some((claimEntry) => doesFieldValueMatchOidcPolicy(value, claimEntry))) {
throw new UnauthorizedError({
message: "Access denied: OIDC claim not allowed."
});
@@ -146,6 +152,20 @@ export const identityOidcAuthServiceFactory = ({
});
}
+ const filteredClaims: Record = {};
+ if (identityOidcAuth.claimMetadataMapping) {
+ Object.keys(identityOidcAuth.claimMetadataMapping).forEach((permissionKey) => {
+ const claimKey = (identityOidcAuth.claimMetadataMapping as Record)[permissionKey];
+ const value = getStringValueByDot(tokenData, claimKey) || "";
+ if (!value) {
+ throw new UnauthorizedError({
+ message: `Access denied: token has no ${claimKey} field`
+ });
+ }
+ filteredClaims[permissionKey] = value;
+ });
+ }
+
const identityAccessToken = await identityOidcAuthDAL.transaction(async (tx) => {
const newToken = await identityAccessTokenDAL.create(
{
@@ -167,7 +187,12 @@ export const identityOidcAuthServiceFactory = ({
{
identityId: identityOidcAuth.identityId,
identityAccessTokenId: identityAccessToken.id,
- authTokenType: AuthTokenType.IDENTITY_ACCESS_TOKEN
+ authTokenType: AuthTokenType.IDENTITY_ACCESS_TOKEN,
+ identityAuth: {
+ oidc: {
+ claims: filteredClaims
+ }
+ }
} as TIdentityAccessTokenJwtPayload,
appCfg.AUTH_SECRET,
// akhilmhdh: for non-expiry tokens you should not even set the value, including undefined. Even for undefined jsonwebtoken throws error
@@ -188,6 +213,7 @@ export const identityOidcAuthServiceFactory = ({
boundIssuer,
boundAudiences,
boundClaims,
+ claimMetadataMapping,
boundSubject,
accessTokenTTL,
accessTokenMaxTTL,
@@ -254,6 +280,7 @@ export const identityOidcAuthServiceFactory = ({
boundIssuer,
boundAudiences,
boundClaims,
+ claimMetadataMapping,
boundSubject,
accessTokenMaxTTL,
accessTokenTTL,
@@ -274,6 +301,7 @@ export const identityOidcAuthServiceFactory = ({
boundIssuer,
boundAudiences,
boundClaims,
+ claimMetadataMapping,
boundSubject,
accessTokenTTL,
accessTokenMaxTTL,
@@ -335,6 +363,7 @@ export const identityOidcAuthServiceFactory = ({
boundIssuer,
boundAudiences,
boundClaims,
+ claimMetadataMapping,
boundSubject,
accessTokenMaxTTL,
accessTokenTTL,
diff --git a/backend/src/services/identity-oidc-auth/identity-oidc-auth-types.ts b/backend/src/services/identity-oidc-auth/identity-oidc-auth-types.ts
index 761f68aa7..9727285e7 100644
--- a/backend/src/services/identity-oidc-auth/identity-oidc-auth-types.ts
+++ b/backend/src/services/identity-oidc-auth/identity-oidc-auth-types.ts
@@ -7,6 +7,7 @@ export type TAttachOidcAuthDTO = {
boundIssuer: string;
boundAudiences: string;
boundClaims: Record;
+ claimMetadataMapping?: Record;
boundSubject: string;
accessTokenTTL: number;
accessTokenMaxTTL: number;
@@ -21,6 +22,7 @@ export type TUpdateOidcAuthDTO = {
boundIssuer?: string;
boundAudiences?: string;
boundClaims?: Record;
+ claimMetadataMapping?: Record;
boundSubject?: string;
accessTokenTTL?: number;
accessTokenMaxTTL?: number;
diff --git a/backend/src/services/secret-sync/aws-parameter-store/aws-parameter-store-sync-fns.ts b/backend/src/services/secret-sync/aws-parameter-store/aws-parameter-store-sync-fns.ts
index 9ca035774..7e77bd256 100644
--- a/backend/src/services/secret-sync/aws-parameter-store/aws-parameter-store-sync-fns.ts
+++ b/backend/src/services/secret-sync/aws-parameter-store/aws-parameter-store-sync-fns.ts
@@ -382,6 +382,8 @@ export const AwsParameterStoreSyncFns = {
}
}
+ if (syncOptions.disableSecretDeletion) return;
+
const parametersToDelete: AWS.SSM.Parameter[] = [];
for (const entry of Object.entries(awsParameterStoreSecretsRecord)) {
diff --git a/backend/src/services/secret-sync/aws-secrets-manager/aws-secrets-manager-sync-fns.ts b/backend/src/services/secret-sync/aws-secrets-manager/aws-secrets-manager-sync-fns.ts
index d4f272475..7cea12d1b 100644
--- a/backend/src/services/secret-sync/aws-secrets-manager/aws-secrets-manager-sync-fns.ts
+++ b/backend/src/services/secret-sync/aws-secrets-manager/aws-secrets-manager-sync-fns.ts
@@ -396,6 +396,8 @@ export const AwsSecretsManagerSyncFns = {
}
}
+ if (syncOptions.disableSecretDeletion) return;
+
for await (const secretKey of Object.keys(awsSecretsRecord)) {
if (!(secretKey in secretMap) || !secretMap[secretKey].value) {
try {
diff --git a/backend/src/services/secret-sync/azure-app-configuration/azure-app-configuration-sync-fns.ts b/backend/src/services/secret-sync/azure-app-configuration/azure-app-configuration-sync-fns.ts
index 8c0587599..64d82c125 100644
--- a/backend/src/services/secret-sync/azure-app-configuration/azure-app-configuration-sync-fns.ts
+++ b/backend/src/services/secret-sync/azure-app-configuration/azure-app-configuration-sync-fns.ts
@@ -136,6 +136,8 @@ export const azureAppConfigurationSyncFactory = ({
}
}
+ if (secretSync.syncOptions.disableSecretDeletion) return;
+
for await (const key of Object.keys(azureAppConfigSecrets)) {
const azureSecret = azureAppConfigSecrets[key];
if (
diff --git a/backend/src/services/secret-sync/azure-key-vault/azure-key-vault-sync-fns.ts b/backend/src/services/secret-sync/azure-key-vault/azure-key-vault-sync-fns.ts
index 21fef297a..b50d78bb2 100644
--- a/backend/src/services/secret-sync/azure-key-vault/azure-key-vault-sync-fns.ts
+++ b/backend/src/services/secret-sync/azure-key-vault/azure-key-vault-sync-fns.ts
@@ -189,6 +189,8 @@ export const azureKeyVaultSyncFactory = ({ kmsService, appConnectionDAL }: TAzur
});
}
+ if (secretSync.syncOptions.disableSecretDeletion) return;
+
for await (const deleteSecretKey of deleteSecrets.filter(
(secret) => !setSecrets.find((setSecret) => setSecret.key === secret)
)) {
diff --git a/backend/src/services/secret-sync/databricks/databricks-sync-fns.ts b/backend/src/services/secret-sync/databricks/databricks-sync-fns.ts
index 2d450b2b1..2ee7977a4 100644
--- a/backend/src/services/secret-sync/databricks/databricks-sync-fns.ts
+++ b/backend/src/services/secret-sync/databricks/databricks-sync-fns.ts
@@ -112,6 +112,8 @@ export const databricksSyncFactory = ({ kmsService, appConnectionDAL }: TDatabri
accessToken
});
+ if (secretSync.syncOptions.disableSecretDeletion) return;
+
for await (const secret of databricksSecretKeys) {
if (!(secret.key in secretMap)) {
await deleteDatabricksSecrets({
diff --git a/backend/src/services/secret-sync/gcp/gcp-sync-fns.ts b/backend/src/services/secret-sync/gcp/gcp-sync-fns.ts
index f0eb3ce88..a71e29ae4 100644
--- a/backend/src/services/secret-sync/gcp/gcp-sync-fns.ts
+++ b/backend/src/services/secret-sync/gcp/gcp-sync-fns.ts
@@ -71,8 +71,16 @@ const getGcpSecrets = async (accessToken: string, secretSync: TGcpSyncWithCreden
res[key] = Buffer.from(secretLatest.payload.data, "base64").toString("utf-8");
} catch (error) {
- // when a secret in GCP has no versions, we treat it as if it's a blank value
- if (error instanceof AxiosError && error.response?.status === 404) {
+ // when a secret in GCP has no versions, or is disabled/destroyed, we treat it as if it's a blank value
+ if (
+ error instanceof AxiosError &&
+ (error.response?.status === 404 ||
+ (error.response?.status === 400 &&
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
+ error.response.data.error.status === "FAILED_PRECONDITION" &&
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-call
+ error.response.data.error.message.match(/(?:disabled|destroyed)/i)))
+ ) {
res[key] = "";
} else {
throw new SecretSyncError({
@@ -147,6 +155,9 @@ export const GcpSyncFns = {
for await (const key of Object.keys(gcpSecrets)) {
try {
if (!(key in secretMap) || !secretMap[key].value) {
+ // eslint-disable-next-line no-continue
+ if (secretSync.syncOptions.disableSecretDeletion) continue;
+
// case: delete secret
await request.delete(
`${IntegrationUrls.GCP_SECRET_MANAGER_URL}/v1/projects/${destinationConfig.projectId}/secrets/${key}`,
diff --git a/backend/src/services/secret-sync/github/github-sync-fns.ts b/backend/src/services/secret-sync/github/github-sync-fns.ts
index a09a41163..1fe922de5 100644
--- a/backend/src/services/secret-sync/github/github-sync-fns.ts
+++ b/backend/src/services/secret-sync/github/github-sync-fns.ts
@@ -192,12 +192,6 @@ export const GithubSyncFns = {
const publicKey = await getPublicKey(client, secretSync);
- for await (const encryptedSecret of encryptedSecrets) {
- if (!(encryptedSecret.name in secretMap)) {
- await deleteSecret(client, secretSync, encryptedSecret);
- }
- }
-
await sodium.ready.then(async () => {
for await (const key of Object.keys(secretMap)) {
// convert secret & base64 key to Uint8Array.
@@ -224,6 +218,14 @@ export const GithubSyncFns = {
}
}
});
+
+ if (secretSync.syncOptions.disableSecretDeletion) return;
+
+ for await (const encryptedSecret of encryptedSecrets) {
+ if (!(encryptedSecret.name in secretMap)) {
+ await deleteSecret(client, secretSync, encryptedSecret);
+ }
+ }
},
getSecrets: async (secretSync: TGitHubSyncWithCredentials) => {
throw new Error(`${SECRET_SYNC_NAME_MAP[secretSync.destination]} does not support importing secrets.`);
diff --git a/backend/src/services/secret-sync/humanitec/humanitec-sync-fns.ts b/backend/src/services/secret-sync/humanitec/humanitec-sync-fns.ts
index a07d2d0b5..5fa0a3d63 100644
--- a/backend/src/services/secret-sync/humanitec/humanitec-sync-fns.ts
+++ b/backend/src/services/secret-sync/humanitec/humanitec-sync-fns.ts
@@ -196,6 +196,8 @@ export const HumanitecSyncFns = {
}
}
+ if (secretSync.syncOptions.disableSecretDeletion) return;
+
for await (const humanitecSecret of humanitecSecrets) {
if (!secretMap[humanitecSecret.key]) {
await deleteSecret(secretSync, humanitecSecret);
diff --git a/backend/src/services/secret-sync/secret-sync-schemas.ts b/backend/src/services/secret-sync/secret-sync-schemas.ts
index 8ea3a4f0b..50ff3f307 100644
--- a/backend/src/services/secret-sync/secret-sync-schemas.ts
+++ b/backend/src/services/secret-sync/secret-sync-schemas.ts
@@ -23,7 +23,8 @@ const BaseSyncOptionsSchema = ({
initialSyncBehavior: (canImportSecrets
? z.nativeEnum(SecretSyncInitialSyncBehavior)
: z.literal(SecretSyncInitialSyncBehavior.OverwriteDestination)
- ).describe(SecretSyncs.SYNC_OPTIONS(destination).initialSyncBehavior)
+ ).describe(SecretSyncs.SYNC_OPTIONS(destination).initialSyncBehavior),
+ disableSecretDeletion: z.boolean().optional().describe(SecretSyncs.SYNC_OPTIONS(destination).disableSecretDeletion)
});
const schema = merge ? baseSchema.merge(merge) : baseSchema;
diff --git a/cli/packages/api/model.go b/cli/packages/api/model.go
index 1eb936f24..72dbbc97b 100644
--- a/cli/packages/api/model.go
+++ b/cli/packages/api/model.go
@@ -2,12 +2,6 @@ package api
import "time"
-type Environment struct {
- Name string `json:"name"`
- Slug string `json:"slug"`
- ID string `json:"id"`
-}
-
// Stores info for login one
type LoginOneRequest struct {
Email string `json:"email"`
@@ -20,6 +14,7 @@ type LoginOneResponse struct {
}
// Stores info for login two
+
type LoginTwoRequest struct {
Email string `json:"email"`
ClientProof string `json:"clientProof"`
@@ -173,10 +168,9 @@ type Secret struct {
}
type Project struct {
- ID string `json:"id"`
- Name string `json:"name"`
- Slug string `json:"slug"`
- Environments []Environment `json:"environments"`
+ ID string `json:"id"`
+ Name string `json:"name"`
+ Slug string `json:"slug"`
}
type RawSecret struct {
diff --git a/cli/packages/cmd/run.go b/cli/packages/cmd/run.go
index 388a4861d..7f11a3f95 100644
--- a/cli/packages/cmd/run.go
+++ b/cli/packages/cmd/run.go
@@ -15,9 +15,6 @@ import (
"syscall"
"time"
- "github.com/Infisical/infisical-merge/packages/api"
- "github.com/go-resty/resty/v2"
-
"github.com/Infisical/infisical-merge/packages/models"
"github.com/Infisical/infisical-merge/packages/util"
"github.com/fatih/color"
@@ -62,11 +59,11 @@ var runCmd = &cobra.Command{
return nil
},
Run: func(cmd *cobra.Command, args []string) {
- environmentSlug, _ := cmd.Flags().GetString("env")
+ environmentName, _ := cmd.Flags().GetString("env")
if !cmd.Flags().Changed("env") {
environmentFromWorkspace := util.GetEnvFromWorkspaceFile()
if environmentFromWorkspace != "" {
- environmentSlug = environmentFromWorkspace
+ environmentName = environmentFromWorkspace
}
}
@@ -139,20 +136,8 @@ var runCmd = &cobra.Command{
util.HandleError(err, "Unable to parse flag")
}
- log.Debug().Msgf("Confirming selected environment is valid: %s", environmentSlug)
-
- hasEnvironment, err := confirmProjectHasEnvironment(environmentSlug, projectId, token)
- if err != nil {
- util.HandleError(err, "Could not confirm project has environment")
- }
- if !hasEnvironment {
- util.HandleError(fmt.Errorf("project does not have environment '%s'", environmentSlug))
- }
-
- log.Debug().Msgf("Project '%s' has environment '%s'", projectId, environmentSlug)
-
request := models.GetAllSecretsParameters{
- Environment: environmentSlug,
+ Environment: environmentName,
WorkspaceId: projectId,
TagSlugs: tagSlugs,
SecretsPath: secretsPath,
@@ -323,6 +308,7 @@ func waitForExitCommand(cmd *exec.Cmd) (int, error) {
}
func executeCommandWithWatchMode(commandFlag string, args []string, watchModeInterval int, request models.GetAllSecretsParameters, projectConfigDir string, secretOverriding bool, token *models.TokenDetails) {
+
var cmd *exec.Cmd
var err error
var lastSecretsFetch time.Time
@@ -453,53 +439,8 @@ func executeCommandWithWatchMode(commandFlag string, args []string, watchModeInt
}
}
-func confirmProjectHasEnvironment(environmentSlug, projectId string, token *models.TokenDetails) (bool, error) {
- var accessToken string
-
- if token != nil && (token.Type == util.SERVICE_TOKEN_IDENTIFIER || token.Type == util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER) {
- accessToken = token.Token
- } else {
- util.RequireLogin()
- util.RequireLocalWorkspaceFile()
-
- loggedInUserDetails, err := util.GetCurrentLoggedInUserDetails(true)
- if err != nil {
- util.HandleError(err, "Unable to authenticate")
- }
-
- if loggedInUserDetails.LoginExpired {
- util.PrintErrorMessageAndExit("Your login session has expired, please run [infisical login] and try again")
- }
- accessToken = loggedInUserDetails.UserCredentials.JTWToken
- }
-
- if projectId == "" {
- workspaceFile, err := util.GetWorkSpaceFromFile()
- if err != nil {
- util.HandleError(err, "Unable to get local project details")
- }
-
- projectId = workspaceFile.WorkspaceId
- }
-
- httpClient := resty.New()
- httpClient.SetAuthToken(accessToken).
- SetHeader("Accept", "application/json")
-
- project, err := api.CallGetProjectById(httpClient, projectId)
- if err != nil {
- return false, err
- }
-
- for _, env := range project.Environments {
- if env.Slug == environmentSlug {
- return true, nil
- }
- }
- return false, nil
-}
-
func fetchAndFormatSecretsForShell(request models.GetAllSecretsParameters, projectConfigDir string, secretOverriding bool, token *models.TokenDetails) (models.InjectableEnvironmentResult, error) {
+
if token != nil && token.Type == util.SERVICE_TOKEN_IDENTIFIER {
request.InfisicalToken = token.Token
} else if token != nil && token.Type == util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER {
diff --git a/cli/packages/util/secrets.go b/cli/packages/util/secrets.go
index 0e26e03b0..5a2a0ec24 100644
--- a/cli/packages/util/secrets.go
+++ b/cli/packages/util/secrets.go
@@ -232,6 +232,7 @@ func FilterSecretsByTag(plainTextSecrets []models.SingleEnvironmentVariable, tag
func GetAllEnvironmentVariables(params models.GetAllSecretsParameters, projectConfigFilePath string) ([]models.SingleEnvironmentVariable, error) {
var secretsToReturn []models.SingleEnvironmentVariable
+ // var serviceTokenDetails api.GetServiceTokenDetailsResponse
var errorToReturn error
if params.InfisicalToken == "" && params.UniversalAuthAccessToken == "" {
diff --git a/cli/test/secrets_test.go b/cli/test/secrets_test.go
index 642d33277..f7f0f13ff 100644
--- a/cli/test/secrets_test.go
+++ b/cli/test/secrets_test.go
@@ -76,6 +76,7 @@ func TestUniversalAuth_SecretsGetWrongEnvironment(t *testing.T) {
if err != nil {
t.Fatalf("snapshot failed: %v", err)
}
+
}
func TestUserAuth_SecretsGetAll(t *testing.T) {
diff --git a/docs/integrations/secret-syncs/aws-parameter-store.mdx b/docs/integrations/secret-syncs/aws-parameter-store.mdx
index 165998841..fad37265a 100644
--- a/docs/integrations/secret-syncs/aws-parameter-store.mdx
+++ b/docs/integrations/secret-syncs/aws-parameter-store.mdx
@@ -43,8 +43,11 @@ description: "Learn how to configure an AWS Parameter Store Sync for Infisical."
- **KMS Key**: The AWS KMS key ID or alias to encrypt parameters with.
- **Tags**: Optional resource tags to add to parameters synced by Infisical.
- **Sync Secret Metadata as Resource Tags**: If enabled, metadata attached to secrets will be added as resource tags to parameters synced by Infisical.
- Manually configured tags from the **Tags** field will take precedence over secret metadata when tag keys conflict.
+
+ Manually configured tags from the **Tags** field will take precedence over secret metadata when tag keys conflict.
+
- **Auto-Sync Enabled**: If enabled, secrets will automatically be synced from the source location when changes occur. Disable to enforce manual syncing only.
+ - **Disable Secret Deletion**: If enabled, Infisical will not remove secrets from the sync destination. Enable this option if you intend to manage some secrets manually outside of Infisical.
6. Configure the **Details** of your Parameter Store Sync, then click **Next**.

diff --git a/docs/integrations/secret-syncs/aws-secrets-manager.mdx b/docs/integrations/secret-syncs/aws-secrets-manager.mdx
index b8df0e8b3..8ed85be25 100644
--- a/docs/integrations/secret-syncs/aws-secrets-manager.mdx
+++ b/docs/integrations/secret-syncs/aws-secrets-manager.mdx
@@ -46,7 +46,11 @@ description: "Learn how to configure an AWS Secrets Manager Sync for Infisical."
- **KMS Key**: The AWS KMS key ID or alias to encrypt secrets with.
- **Tags**: Optional tags to add to secrets synced by Infisical.
- **Sync Secret Metadata as Tags**: If enabled, metadata attached to secrets will be added as tags to secrets synced by Infisical.
+
+ Manually configured tags from the **Tags** field will take precedence over secret metadata when tag keys conflict.
+
- **Auto-Sync Enabled**: If enabled, secrets will automatically be synced from the source location when changes occur. Disable to enforce manual syncing only.
+ - **Disable Secret Deletion**: If enabled, Infisical will not remove secrets from the sync destination. Enable this option if you intend to manage some secrets manually outside of Infisical.
6. Configure the **Details** of your Secrets Manager Sync, then click **Next**.

diff --git a/docs/integrations/secret-syncs/azure-app-configuration.mdx b/docs/integrations/secret-syncs/azure-app-configuration.mdx
index 3b443c672..35a577872 100644
--- a/docs/integrations/secret-syncs/azure-app-configuration.mdx
+++ b/docs/integrations/secret-syncs/azure-app-configuration.mdx
@@ -6,7 +6,7 @@ description: "Learn how to configure an Azure App Configuration Sync for Infisic
**Prerequisites:**
- Set up and add secrets to [Infisical Cloud](https://app.infisical.com)
- - Create a [Azure Connection](/integrations/app-connections/azure), configured for Azure App Configuration.
+ - Create an [Azure App Configuration Connection](/integrations/app-connections/azure-app-configuration)
The Azure App Configuration Secret Sync requires the following permissions to be set on the user / service principal
@@ -50,6 +50,7 @@ description: "Learn how to configure an Azure App Configuration Sync for Infisic
- **Import Secrets (Prioritize Azure App Configuration)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Secrets Manager over Infisical when keys conflict.
- **Auto-Sync Enabled**: If enabled, secrets will automatically be synced from the source location when changes occur. Disable to enforce manual syncing only.
+ - **Disable Secret Deletion**: If enabled, Infisical will not remove secrets from the sync destination. Enable this option if you intend to manage some secrets manually outside of Infisical.
6. Configure the **Details** of your Azure App Configuration Sync, then click **Next**.

diff --git a/docs/integrations/secret-syncs/azure-key-vault.mdx b/docs/integrations/secret-syncs/azure-key-vault.mdx
index 9b28f841f..5f55a73ae 100644
--- a/docs/integrations/secret-syncs/azure-key-vault.mdx
+++ b/docs/integrations/secret-syncs/azure-key-vault.mdx
@@ -6,7 +6,7 @@ description: "Learn how to configure a Azure Key Vault Sync for Infisical."
**Prerequisites:**
- Set up and add secrets to [Infisical Cloud](https://app.infisical.com)
- - Create a [Azure Connection](/integrations/app-connections/azure), configured for Azure Key Vault.
+ - Create an [Azure Key Vault Connection](/integrations/app-connections/azure-key-vault)
The Azure Key Vault Secret Sync requires the following secrets permissions to be set on the user / service principal
@@ -52,6 +52,7 @@ description: "Learn how to configure a Azure Key Vault Sync for Infisical."
- **Import Secrets (Prioritize Infisical)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Infisical over Secrets Manager when keys conflict.
- **Import Secrets (Prioritize Azure Key Vault)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Secrets Manager over Infisical when keys conflict.
- **Auto-Sync Enabled**: If enabled, secrets will automatically be synced from the source location when changes occur. Disable to enforce manual syncing only.
+ - **Disable Secret Deletion**: If enabled, Infisical will not remove secrets from the sync destination. Enable this option if you intend to manage some secrets manually outside of Infisical.
6. Configure the **Details** of your Azure Key Vault Sync, then click **Next**.

diff --git a/docs/integrations/secret-syncs/databricks.mdx b/docs/integrations/secret-syncs/databricks.mdx
index 148542708..c9db5f88a 100644
--- a/docs/integrations/secret-syncs/databricks.mdx
+++ b/docs/integrations/secret-syncs/databricks.mdx
@@ -47,6 +47,7 @@ description: "Learn how to configure a Databricks Sync for Infisical."
Databricks does not support importing secrets.
- **Auto-Sync Enabled**: If enabled, secrets will automatically be synced from the source location when changes occur. Disable to enforce manual syncing only.
+ - **Disable Secret Deletion**: If enabled, Infisical will not remove secrets from the sync destination. Enable this option if you intend to manage some secrets manually outside of Infisical.
6. Configure the **Details** of your Databricks Sync, then click **Next**.

diff --git a/docs/integrations/secret-syncs/gcp-secret-manager.mdx b/docs/integrations/secret-syncs/gcp-secret-manager.mdx
index bcb3b87bd..72c932116 100644
--- a/docs/integrations/secret-syncs/gcp-secret-manager.mdx
+++ b/docs/integrations/secret-syncs/gcp-secret-manager.mdx
@@ -43,6 +43,7 @@ description: "Learn how to configure a GCP Secret Manager Sync for Infisical."
- **Import Secrets (Prioritize Infisical)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Infisical over GCP Secret Manager when keys conflict.
- **Import Secrets (Prioritize GCP Secret Manager)**: Imports secrets from the destination endpoint before syncing, prioritizing values from GCP Secret Manager over Infisical when keys conflict.
- **Auto-Sync Enabled**: If enabled, secrets will automatically be synced from the source location when changes occur. Disable to enforce manual syncing only.
+ - **Disable Secret Deletion**: If enabled, Infisical will not remove secrets from the sync destination. Enable this option if you intend to manage some secrets manually outside of Infisical.
6. Configure the **Details** of your GCP Secret Manager Sync, then click **Next**.

diff --git a/docs/integrations/secret-syncs/github.mdx b/docs/integrations/secret-syncs/github.mdx
index d7d7afd86..d55ec3d0b 100644
--- a/docs/integrations/secret-syncs/github.mdx
+++ b/docs/integrations/secret-syncs/github.mdx
@@ -63,6 +63,7 @@ description: "Learn how to configure a GitHub Sync for Infisical."
GitHub does not support importing secrets.
- **Auto-Sync Enabled**: If enabled, secrets will automatically be synced from the source location when changes occur. Disable to enforce manual syncing only.
+ - **Disable Secret Deletion**: If enabled, Infisical will not remove secrets from the sync destination. Enable this option if you intend to manage some secrets manually outside of Infisical.
6. Configure the **Details** of your GitHub Sync, then click **Next**.

diff --git a/docs/integrations/secret-syncs/humanitec.mdx b/docs/integrations/secret-syncs/humanitec.mdx
index 0edc2098e..e8cd7eafc 100644
--- a/docs/integrations/secret-syncs/humanitec.mdx
+++ b/docs/integrations/secret-syncs/humanitec.mdx
@@ -56,6 +56,7 @@ description: "Learn how to configure a Humanitec Sync for Infisical."
Humanitec does not support importing secrets.
- **Auto-Sync Enabled**: If enabled, secrets will automatically be synced from the source location when changes occur. Disable to enforce manual syncing only.
+ - **Disable Secret Deletion**: If enabled, Infisical will not remove secrets from the sync destination. Enable this option if you intend to manage some secrets manually outside of Infisical.
6. Configure the **Details** of your Humanitec Sync, then click **Next**.

diff --git a/flake.nix b/flake.nix
index cc4cf8d65..094cfedc6 100644
--- a/flake.nix
+++ b/flake.nix
@@ -14,21 +14,11 @@
git
lazygit
- go
python312Full
nodejs_20
nodePackages.prettier
infisical
];
-
- env = {
- GOROOT = "${pkgs.go}/share/go";
- };
-
- shellHook = ''
- export GOPATH="$(pwd)/.go"
- mkdir -p "$GOPATH"
- '';
};
};
}
diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/SecretSyncOptionsFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/SecretSyncOptionsFields.tsx
index d3213caf7..b14bce809 100644
--- a/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/SecretSyncOptionsFields.tsx
+++ b/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/SecretSyncOptionsFields.tsx
@@ -1,9 +1,9 @@
import { ReactNode } from "react";
import { Controller, useFormContext } from "react-hook-form";
-import { faTriangleExclamation } from "@fortawesome/free-solid-svg-icons";
+import { faQuestionCircle, faTriangleExclamation } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { FormControl, Select, SelectItem } from "@app/components/v2";
+import { FormControl, Select, SelectItem, Switch, Tooltip } from "@app/components/v2";
import { SECRET_SYNC_INITIAL_SYNC_BEHAVIOR_MAP, SECRET_SYNC_MAP } from "@app/helpers/secretSyncs";
import { SecretSync, useSecretSyncOption } from "@app/hooks/api/secretSyncs";
@@ -116,6 +116,44 @@ export const SecretSyncOptionsFields = ({ hideInitialSync }: Props) => {
>
)}
{AdditionalSyncOptionsFieldsComponent}
+ {
+ return (
+
+
+
+ Disable Secret Deletion{" "}
+
+
+ When enabled, Infisical will not{" "}
+ remove secrets from the destination during a sync.
+
+
+ Enable this option if you intend to manage some secrets manually outside
+ of Infisical.
+
+ >
+ }
+ >
+
+
+
+
+
+ );
+ }}
+ />
{/* (
{
secretPath,
syncOptions: {
// appendSuffix, prependPrefix,
+ disableSecretDeletion,
initialSyncBehavior
},
destination,
@@ -111,6 +112,11 @@ export const SecretSyncReviewFields = () => {
{/* {prependPrefix}
{appendSuffix} */}
{AdditionalSyncOptionsFieldsComponent}
+ {disableSecretDeletion && (
+
+ Disabled
+
+ )}
diff --git a/frontend/src/components/secret-syncs/forms/schemas/base-secret-sync-schema.ts b/frontend/src/components/secret-syncs/forms/schemas/base-secret-sync-schema.ts
index b898fe91f..bf72321ce 100644
--- a/frontend/src/components/secret-syncs/forms/schemas/base-secret-sync-schema.ts
+++ b/frontend/src/components/secret-syncs/forms/schemas/base-secret-sync-schema.ts
@@ -7,7 +7,8 @@ export const BaseSecretSyncSchema = {
const baseSyncOptionsSchema = z.object({
- initialSyncBehavior: z.nativeEnum(SecretSyncInitialSyncBehavior)
+ initialSyncBehavior: z.nativeEnum(SecretSyncInitialSyncBehavior),
+ disableSecretDeletion: z.boolean().optional().default(false)
// scott: removed temporarily for evaluation of template formatting
// prependPrefix: z
// .string()
diff --git a/frontend/src/hooks/api/admin/queries.ts b/frontend/src/hooks/api/admin/queries.ts
index 496990abe..b24841dbd 100644
--- a/frontend/src/hooks/api/admin/queries.ts
+++ b/frontend/src/hooks/api/admin/queries.ts
@@ -1,6 +1,7 @@
import { useInfiniteQuery, useQuery, UseQueryOptions } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
+import { Identity } from "@app/hooks/api/identities/types";
import { User } from "../types";
import {
@@ -10,7 +11,6 @@ import {
TGetServerRootKmsEncryptionDetails,
TServerConfig
} from "./types";
-import { Identity } from "@app/hooks/api/identities/types";
export const adminStandaloneKeys = {
getUsers: "get-users",
diff --git a/frontend/src/hooks/api/identities/mutations.tsx b/frontend/src/hooks/api/identities/mutations.tsx
index 3e38d9067..d68595ad5 100644
--- a/frontend/src/hooks/api/identities/mutations.tsx
+++ b/frontend/src/hooks/api/identities/mutations.tsx
@@ -462,6 +462,7 @@ export const useUpdateIdentityOidcAuth = () => {
boundIssuer,
boundAudiences,
boundClaims,
+ claimMetadataMapping,
boundSubject
}) => {
const {
@@ -478,7 +479,8 @@ export const useUpdateIdentityOidcAuth = () => {
accessTokenTTL,
accessTokenMaxTTL,
accessTokenNumUsesLimit,
- accessTokenTrustedIps
+ accessTokenTrustedIps,
+ claimMetadataMapping
}
);
@@ -504,6 +506,7 @@ export const useAddIdentityOidcAuth = () => {
boundIssuer,
boundAudiences,
boundClaims,
+ claimMetadataMapping,
boundSubject,
accessTokenTTL,
accessTokenMaxTTL,
@@ -524,7 +527,8 @@ export const useAddIdentityOidcAuth = () => {
accessTokenTTL,
accessTokenMaxTTL,
accessTokenNumUsesLimit,
- accessTokenTrustedIps
+ accessTokenTrustedIps,
+ claimMetadataMapping
}
);
diff --git a/frontend/src/hooks/api/identities/types.ts b/frontend/src/hooks/api/identities/types.ts
index 8d344e7f7..86d35e086 100644
--- a/frontend/src/hooks/api/identities/types.ts
+++ b/frontend/src/hooks/api/identities/types.ts
@@ -193,6 +193,7 @@ export type IdentityOidcAuth = {
boundIssuer: string;
boundAudiences: string;
boundClaims: Record;
+ claimMetadataMapping?: Record;
boundSubject: string;
accessTokenTTL: number;
accessTokenMaxTTL: number;
@@ -208,6 +209,7 @@ export type AddIdentityOidcAuthDTO = {
boundIssuer: string;
boundAudiences: string;
boundClaims: Record;
+ claimMetadataMapping?: Record;
boundSubject: string;
accessTokenTTL: number;
accessTokenMaxTTL: number;
@@ -225,6 +227,7 @@ export type UpdateIdentityOidcAuthDTO = {
boundIssuer?: string;
boundAudiences?: string;
boundClaims?: Record;
+ claimMetadataMapping?: Record;
boundSubject?: string;
accessTokenTTL?: number;
accessTokenMaxTTL?: number;
diff --git a/frontend/src/hooks/api/secretSyncs/types/root-sync.ts b/frontend/src/hooks/api/secretSyncs/types/root-sync.ts
index 49cf9c978..dfbbcd063 100644
--- a/frontend/src/hooks/api/secretSyncs/types/root-sync.ts
+++ b/frontend/src/hooks/api/secretSyncs/types/root-sync.ts
@@ -3,6 +3,7 @@ import { SecretSyncInitialSyncBehavior, SecretSyncStatus } from "@app/hooks/api/
export type RootSyncOptions = {
initialSyncBehavior: SecretSyncInitialSyncBehavior;
+ disableSecretDeletion?: boolean;
// prependPrefix?: string;
// appendSuffix?: string;
};
diff --git a/frontend/src/pages/admin/OverviewPage/OverviewPage.tsx b/frontend/src/pages/admin/OverviewPage/OverviewPage.tsx
index 776c5e20f..8d1e25bc5 100644
--- a/frontend/src/pages/admin/OverviewPage/OverviewPage.tsx
+++ b/frontend/src/pages/admin/OverviewPage/OverviewPage.tsx
@@ -28,13 +28,13 @@ import {
useGetServerRootKmsEncryptionDetails,
useUpdateServerConfig
} from "@app/hooks/api";
+import { IdentityPanel } from "@app/pages/admin/OverviewPage/components/IdentityPanel";
import { AuthPanel } from "./components/AuthPanel";
import { EncryptionPanel } from "./components/EncryptionPanel";
import { IntegrationPanel } from "./components/IntegrationPanel";
import { RateLimitPanel } from "./components/RateLimitPanel";
import { UserPanel } from "./components/UserPanel";
-import { IdentityPanel } from "@app/pages/admin/OverviewPage/components/IdentityPanel";
enum TabSections {
Settings = "settings",
diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityOidcAuthForm.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityOidcAuthForm.tsx
index b1b0739cf..21440457a 100644
--- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityOidcAuthForm.tsx
+++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityOidcAuthForm.tsx
@@ -52,6 +52,15 @@ const schema = z.object({
value: z.string()
})
),
+ claimMetadataMapping: z
+ .array(
+ z.object({
+ key: z.string(),
+ value: z.string()
+ })
+ )
+ .optional()
+ .nullable(),
boundSubject: z.string().optional().default("")
});
@@ -99,7 +108,6 @@ export const IdentityOidcAuthForm = ({
accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }]
}
});
-
const {
fields: boundClaimsFields,
append: appendBoundClaimField,
@@ -109,6 +117,15 @@ export const IdentityOidcAuthForm = ({
name: "boundClaims"
});
+ const {
+ fields: claimMetadataMappingFields,
+ append: appendClaimMetadataMappingField,
+ remove: removeClaimMetadataMappingField
+ } = useFieldArray({
+ control,
+ name: "claimMetadataMapping"
+ });
+
const {
fields: accessTokenTrustedIpsFields,
append: appendAccessTokenTrustedIp,
@@ -126,6 +143,12 @@ export const IdentityOidcAuthForm = ({
key,
value
})),
+ claimMetadataMapping: data?.claimMetadataMapping
+ ? Object.entries(data.claimMetadataMapping).map(([key, value]) => ({
+ key,
+ value
+ }))
+ : undefined,
boundSubject: data.boundSubject,
accessTokenTTL: String(data.accessTokenTTL),
accessTokenMaxTTL: String(data.accessTokenMaxTTL),
@@ -164,6 +187,7 @@ export const IdentityOidcAuthForm = ({
boundIssuer,
boundAudiences,
boundClaims,
+ claimMetadataMapping,
boundSubject
}: FormData) => {
try {
@@ -180,6 +204,9 @@ export const IdentityOidcAuthForm = ({
boundIssuer,
boundAudiences,
boundClaims: Object.fromEntries(boundClaims.map((entry) => [entry.key, entry.value])),
+ claimMetadataMapping: claimMetadataMapping
+ ? Object.fromEntries(claimMetadataMapping.map((entry) => [entry.key, entry.value]))
+ : undefined,
boundSubject,
accessTokenTTL: Number(accessTokenTTL),
accessTokenMaxTTL: Number(accessTokenMaxTTL),
@@ -194,6 +221,9 @@ export const IdentityOidcAuthForm = ({
boundIssuer,
boundAudiences,
boundClaims: Object.fromEntries(boundClaims.map((entry) => [entry.key, entry.value])),
+ claimMetadataMapping: claimMetadataMapping
+ ? Object.fromEntries(claimMetadataMapping.map((entry) => [entry.key, entry.value]))
+ : undefined,
boundSubject,
organizationId: orgId,
accessTokenTTL: Number(accessTokenTTL),
@@ -449,6 +479,84 @@ export const IdentityOidcAuthForm = ({
Add Claims
+ {claimMetadataMappingFields.map(({ id }, index) => (
+
+ {
+ return (
+
+
+
+ ) : undefined
+ }
+ isError={Boolean(error)}
+ errorText={error?.message}
+ >
+ field.onChange(e)}
+ placeholder="property"
+ />
+
+ );
+ }}
+ />
+ {
+ return (
+
+ field.onChange(e)}
+ placeholder="key1.nested-key2"
+ />
+
+ );
+ }}
+ />
+ removeClaimMetadataMappingField(index)}
+ size="lg"
+ colorSchema="danger"
+ variant="plain"
+ ariaLabel="update"
+ className="p-3"
+ >
+
+
+
+ ))}
+
+
+
{accessTokenTrustedIpsFields.map(({ id }, index) => (
)}
+
+ {data.claimMetadataMapping && Object.keys(data.claimMetadataMapping).length && (
+
+ {JSON.stringify(data.claimMetadataMapping, null, 2)}
+
+ }
+ >
+
+
+
+ Reveal
+
+
+
+ )}
+
);
};
diff --git a/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx b/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx
index 94ab8c26d..8fb90243b 100644
--- a/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx
+++ b/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx
@@ -20,6 +20,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link, useNavigate, useRouter, useSearch } from "@tanstack/react-router";
import { twMerge } from "tailwind-merge";
+import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
import { createNotification } from "@app/components/notifications";
import { ProjectPermissionCan } from "@app/components/permissions";
import {
@@ -50,8 +51,10 @@ import {
import { ROUTE_PATHS } from "@app/const/routes";
import {
ProjectPermissionActions,
+ ProjectPermissionDynamicSecretActions,
ProjectPermissionSub,
useProjectPermission,
+ useSubscription,
useWorkspace
} from "@app/context";
import { useDebounce, usePagination, usePopUp, useResetPageHelper } from "@app/hooks";
@@ -72,6 +75,7 @@ import { SecretType, SecretV3RawSanitized, TSecretFolder } from "@app/hooks/api/
import { ProjectType, ProjectVersion } from "@app/hooks/api/workspace/types";
import { useDynamicSecretOverview, useFolderOverview, useSecretOverview } from "@app/hooks/utils";
+import { CreateDynamicSecretForm } from "../SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm";
import { FolderForm } from "../SecretDashboardPage/components/ActionBar/FolderForm";
import { CreateSecretForm } from "./components/CreateSecretForm";
import { FolderBreadCrumbs } from "./components/FolderBreadCrumbs";
@@ -135,6 +139,7 @@ export const OverviewPage = () => {
const [searchFilter, setSearchFilter] = useState("");
const [debouncedSearchFilter, setDebouncedSearchFilter] = useDebounce(searchFilter);
const secretPath = (routerSearch?.secretPath as string) || "/";
+ const { subscription } = useSubscription();
const [filter, setFilter] = useState(DEFAULT_FILTER_STATE);
const [filterHistory, setFilterHistory] = useState<
@@ -182,6 +187,15 @@ export const OverviewPage = () => {
}, []);
const userAvailableEnvs = currentWorkspace?.environments || [];
+ const userAvailableDynamicSecretEnvs = userAvailableEnvs.filter((env) =>
+ permission.can(
+ ProjectPermissionDynamicSecretActions.CreateRootCredential,
+ subject(ProjectPermissionSub.DynamicSecrets, {
+ environment: env.slug,
+ secretPath
+ })
+ )
+ );
const [visibleEnvs, setVisibleEnvs] = useState(userAvailableEnvs);
@@ -268,7 +282,9 @@ export const OverviewPage = () => {
"addSecretsInAllEnvs",
"addFolder",
"misc",
- "updateFolder"
+ "updateFolder",
+ "addDynamicSecret",
+ "upgradePlan"
] as const);
const handleFolderCreate = async (folderName: string, description: string | null) => {
@@ -882,20 +898,43 @@ export const OverviewPage = () => {
>
{(isAllowed) => (
}
+ leftIcon={}
onClick={() => {
handlePopUpOpen("addFolder");
handlePopUpClose("misc");
}}
isDisabled={!isAllowed}
variant="outline_bg"
- className="h-10"
+ className="h-10 text-left"
isFullWidth
>
Add Folder
)}
+
+ }
+ onClick={() => {
+ if (subscription?.dynamicSecret) {
+ handlePopUpOpen("addDynamicSecret");
+ handlePopUpClose("misc");
+ return;
+ }
+ handlePopUpOpen("upgradePlan");
+ }}
+ isDisabled={userAvailableDynamicSecretEnvs.length === 0}
+ variant="outline_bg"
+ className="h-10 text-left"
+ isFullWidth
+ >
+ Add Dynamic Secret
+
+
@@ -1214,6 +1253,24 @@ export const OverviewPage = () => {
/>
+ handlePopUpToggle("addDynamicSecret", isOpen)}
+ projectSlug={projectSlug}
+ environments={userAvailableDynamicSecretEnvs}
+ secretPath={secretPath}
+ />
+ {subscription && (
+ handlePopUpToggle("upgradePlan", isOpen)}
+ text={
+ subscription.slug === null
+ ? "You can perform this action under an Enterprise license"
+ : "You can perform this action if you switch to Infisical's Team plan"
+ }
+ />
+ )}
);
};
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx
index d545ba748..681ad99e3 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx
@@ -655,8 +655,9 @@ export const ActionBar = ({
isOpen={popUp.addDynamicSecret.isOpen}
onToggle={(isOpen) => handlePopUpToggle("addDynamicSecret", isOpen)}
projectSlug={projectSlug}
- environment={environment}
+ environments={[{ slug: environment, name: environment, id: "not-used" }]}
secretPath={secretPath}
+ isSingleEnvironmentMode
/>
24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
}),
- name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase")
+ name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"),
+ environment: z.object({ name: z.string(), slug: z.string() })
});
type TForm = z.infer;
@@ -59,15 +62,17 @@ type Props = {
onCancel: () => void;
secretPath: string;
projectSlug: string;
- environment: string;
+ environments: WorkspaceEnv[];
+ isSingleEnvironmentMode?: boolean;
};
export const AwsElastiCacheInputForm = ({
onCompleted,
onCancel,
- environment,
+ environments,
secretPath,
- projectSlug
+ projectSlug,
+ isSingleEnvironmentMode
}: Props) => {
const {
control,
@@ -87,13 +92,20 @@ export const AwsElastiCacheInputForm = ({
revocationStatement: `{
"UserId": "{{username}}"
}`
- }
+ },
+ environment: isSingleEnvironmentMode ? environments[0] : undefined
}
});
const createDynamicSecret = useCreateDynamicSecret();
- const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => {
+ const handleCreateDynamicSecret = async ({
+ name,
+ maxTTL,
+ provider,
+ defaultTTL,
+ environment
+ }: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
try {
@@ -104,7 +116,7 @@ export const AwsElastiCacheInputForm = ({
path: secretPath,
defaultTTL,
projectSlug,
- environmentSlug: environment
+ environmentSlug: environment.slug
});
onCompleted();
} catch {
@@ -299,6 +311,28 @@ export const AwsElastiCacheInputForm = ({
+ {!isSingleEnvironmentMode && (
+ (
+
+ option.name}
+ getOptionValue={(option) => option.slug}
+ />
+
+ )}
+ />
+ )}
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsIamInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsIamInputForm.tsx
index 1acb24e8f..f2458bbf5 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsIamInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsIamInputForm.tsx
@@ -5,9 +5,10 @@ import { z } from "zod";
import { TtlFormLabel } from "@app/components/features";
import { createNotification } from "@app/components/notifications";
-import { Button, FormControl, Input, TextArea } from "@app/components/v2";
+import { Button, FilterableSelect, FormControl, Input, TextArea } from "@app/components/v2";
import { useCreateDynamicSecret } from "@app/hooks/api";
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
+import { WorkspaceEnv } from "@app/hooks/api/types";
const formSchema = z.object({
provider: z.object({
@@ -40,7 +41,8 @@ const formSchema = z.object({
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
}),
- name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase")
+ name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"),
+ environment: z.object({ name: z.string(), slug: z.string() })
});
type TForm = z.infer;
@@ -49,29 +51,41 @@ type Props = {
onCancel: () => void;
secretPath: string;
projectSlug: string;
- environment: string;
+ environments: WorkspaceEnv[];
+ isSingleEnvironmentMode?: boolean;
};
export const AwsIamInputForm = ({
onCompleted,
onCancel,
- environment,
+ environments,
secretPath,
- projectSlug
+ projectSlug,
+ isSingleEnvironmentMode
}: Props) => {
const {
control,
formState: { isSubmitting },
handleSubmit
} = useForm({
- resolver: zodResolver(formSchema)
+ resolver: zodResolver(formSchema),
+ defaultValues: {
+ environment: isSingleEnvironmentMode ? environments[0] : undefined
+ }
});
const createDynamicSecret = useCreateDynamicSecret();
- const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => {
+ const handleCreateDynamicSecret = async ({
+ name,
+ maxTTL,
+ provider,
+ defaultTTL,
+ environment
+ }: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
+
try {
await createDynamicSecret.mutateAsync({
provider: { type: DynamicSecretProviders.AwsIam, inputs: provider },
@@ -80,7 +94,7 @@ export const AwsIamInputForm = ({
path: secretPath,
defaultTTL,
projectSlug,
- environmentSlug: environment
+ environmentSlug: environment.slug
});
onCompleted();
} catch {
@@ -286,6 +300,29 @@ export const AwsIamInputForm = ({
)}
/>
+ {!isSingleEnvironmentMode && (
+ (
+
+ option.name}
+ getOptionValue={(option) => option.slug}
+ menuPlacement="top"
+ />
+
+ )}
+ />
+ )}
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AzureEntraIdInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AzureEntraIdInputForm.tsx
index 55cebe173..2fb68f399 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AzureEntraIdInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AzureEntraIdInputForm.tsx
@@ -12,7 +12,7 @@ import { z } from "zod";
import { TtlFormLabel } from "@app/components/features";
import { createNotification } from "@app/components/notifications";
-import { Button, FormControl, Input } from "@app/components/v2";
+import { Button, FilterableSelect, FormControl, Input } from "@app/components/v2";
import {
DropdownMenu,
DropdownMenuContent,
@@ -23,6 +23,7 @@ import { Tooltip } from "@app/components/v2/Tooltip";
import { useCreateDynamicSecret } from "@app/hooks/api";
import { useGetDynamicSecretProviderData } from "@app/hooks/api/dynamicSecret/queries";
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
+import { WorkspaceEnv } from "@app/hooks/api/types";
const formSchema = z.object({
selectedUsers: z.array(
@@ -60,7 +61,8 @@ const formSchema = z.object({
name: z
.string()
.min(1)
- .refine((val) => val.toLowerCase() === val, "Must be lowercase")
+ .refine((val) => val.toLowerCase() === val, "Must be lowercase"),
+ environment: z.object({ name: z.string(), slug: z.string() })
});
type TForm = z.infer;
@@ -69,15 +71,17 @@ type Props = {
onCancel: () => void;
secretPath: string;
projectSlug: string;
- environment: string;
+ environments: WorkspaceEnv[];
+ isSingleEnvironmentMode?: boolean;
};
export const AzureEntraIdInputForm = ({
onCompleted,
onCancel,
- environment,
+ environments,
secretPath,
- projectSlug
+ projectSlug,
+ isSingleEnvironmentMode
}: Props) => {
const {
control,
@@ -85,7 +89,10 @@ export const AzureEntraIdInputForm = ({
watch,
handleSubmit
} = useForm({
- resolver: zodResolver(formSchema)
+ resolver: zodResolver(formSchema),
+ defaultValues: {
+ environment: isSingleEnvironmentMode ? environments[0] : undefined
+ }
});
const tenantId = watch("provider.tenantId");
const applicationId = watch("provider.applicationId");
@@ -107,7 +114,8 @@ export const AzureEntraIdInputForm = ({
selectedUsers,
provider,
maxTTL,
- defaultTTL
+ defaultTTL,
+ environment
}: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
@@ -129,7 +137,7 @@ export const AzureEntraIdInputForm = ({
path: secretPath,
defaultTTL,
projectSlug,
- environmentSlug: environment
+ environmentSlug: environment.slug
});
});
onCompleted();
@@ -373,6 +381,29 @@ export const AzureEntraIdInputForm = ({
+ {!isSingleEnvironmentMode && (
+ (
+
+ option.name}
+ getOptionValue={(option) => option.slug}
+ menuPlacement="top"
+ />
+
+ )}
+ />
+ )}
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CreateDynamicSecretForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CreateDynamicSecretForm.tsx
index c8389fb38..106a658d4 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CreateDynamicSecretForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CreateDynamicSecretForm.tsx
@@ -17,6 +17,7 @@ import { AnimatePresence, motion } from "framer-motion";
import { Modal, ModalContent } from "@app/components/v2";
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
+import { WorkspaceEnv } from "@app/hooks/api/types";
import { AwsElastiCacheInputForm } from "./AwsElastiCacheInputForm";
import { AwsIamInputForm } from "./AwsIamInputForm";
@@ -38,8 +39,9 @@ type Props = {
isOpen?: boolean;
onToggle: (isOpen: boolean) => void;
projectSlug: string;
- environment: string;
+ environments: WorkspaceEnv[];
secretPath: string;
+ isSingleEnvironmentMode?: boolean;
};
enum WizardSteps {
@@ -129,8 +131,9 @@ export const CreateDynamicSecretForm = ({
isOpen,
onToggle,
projectSlug,
- environment,
- secretPath
+ environments,
+ secretPath,
+ isSingleEnvironmentMode
}: Props) => {
const [wizardStep, setWizardStep] = useState(WizardSteps.SelectProvider);
const [selectedProvider, setSelectedProvider] = useState(null);
@@ -197,7 +200,8 @@ export const CreateDynamicSecretForm = ({
onCancel={handleFormReset}
projectSlug={projectSlug}
secretPath={secretPath}
- environment={environment}
+ environments={environments}
+ isSingleEnvironmentMode={isSingleEnvironmentMode}
/>
)}
@@ -215,7 +219,8 @@ export const CreateDynamicSecretForm = ({
onCancel={handleFormReset}
projectSlug={projectSlug}
secretPath={secretPath}
- environment={environment}
+ environments={environments}
+ isSingleEnvironmentMode={isSingleEnvironmentMode}
/>
)}
@@ -233,7 +238,8 @@ export const CreateDynamicSecretForm = ({
onCancel={handleFormReset}
projectSlug={projectSlug}
secretPath={secretPath}
- environment={environment}
+ environments={environments}
+ isSingleEnvironmentMode={isSingleEnvironmentMode}
/>
)}
@@ -251,7 +257,8 @@ export const CreateDynamicSecretForm = ({
onCancel={handleFormReset}
projectSlug={projectSlug}
secretPath={secretPath}
- environment={environment}
+ environments={environments}
+ isSingleEnvironmentMode={isSingleEnvironmentMode}
/>
)}
@@ -269,7 +276,8 @@ export const CreateDynamicSecretForm = ({
onCancel={handleFormReset}
projectSlug={projectSlug}
secretPath={secretPath}
- environment={environment}
+ environments={environments}
+ isSingleEnvironmentMode={isSingleEnvironmentMode}
/>
)}
@@ -287,7 +295,8 @@ export const CreateDynamicSecretForm = ({
onCancel={handleFormReset}
projectSlug={projectSlug}
secretPath={secretPath}
- environment={environment}
+ environments={environments}
+ isSingleEnvironmentMode={isSingleEnvironmentMode}
/>
)}
@@ -305,7 +314,8 @@ export const CreateDynamicSecretForm = ({
onCancel={handleFormReset}
projectSlug={projectSlug}
secretPath={secretPath}
- environment={environment}
+ environments={environments}
+ isSingleEnvironmentMode={isSingleEnvironmentMode}
/>
)}
@@ -323,7 +333,8 @@ export const CreateDynamicSecretForm = ({
onCancel={handleFormReset}
projectSlug={projectSlug}
secretPath={secretPath}
- environment={environment}
+ environments={environments}
+ isSingleEnvironmentMode={isSingleEnvironmentMode}
/>
)}
@@ -341,7 +352,8 @@ export const CreateDynamicSecretForm = ({
onCancel={handleFormReset}
projectSlug={projectSlug}
secretPath={secretPath}
- environment={environment}
+ environments={environments}
+ isSingleEnvironmentMode={isSingleEnvironmentMode}
/>
)}
@@ -359,7 +371,8 @@ export const CreateDynamicSecretForm = ({
onCancel={handleFormReset}
projectSlug={projectSlug}
secretPath={secretPath}
- environment={environment}
+ environments={environments}
+ isSingleEnvironmentMode={isSingleEnvironmentMode}
/>
)}
@@ -377,7 +390,8 @@ export const CreateDynamicSecretForm = ({
onCancel={handleFormReset}
projectSlug={projectSlug}
secretPath={secretPath}
- environment={environment}
+ environments={environments}
+ isSingleEnvironmentMode={isSingleEnvironmentMode}
/>
)}
@@ -395,7 +409,8 @@ export const CreateDynamicSecretForm = ({
onCancel={handleFormReset}
projectSlug={projectSlug}
secretPath={secretPath}
- environment={environment}
+ environments={environments}
+ isSingleEnvironmentMode={isSingleEnvironmentMode}
/>
)}
@@ -413,7 +428,8 @@ export const CreateDynamicSecretForm = ({
onCancel={handleFormReset}
projectSlug={projectSlug}
secretPath={secretPath}
- environment={environment}
+ environments={environments}
+ isSingleEnvironmentMode={isSingleEnvironmentMode}
/>
)}
@@ -432,7 +448,8 @@ export const CreateDynamicSecretForm = ({
onCancel={handleFormReset}
projectSlug={projectSlug}
secretPath={secretPath}
- environment={environment}
+ environments={environments}
+ isSingleEnvironmentMode={isSingleEnvironmentMode}
/>
)}
@@ -450,7 +467,8 @@ export const CreateDynamicSecretForm = ({
onCancel={handleFormReset}
projectSlug={projectSlug}
secretPath={secretPath}
- environment={environment}
+ environments={environments}
+ isSingleEnvironmentMode={isSingleEnvironmentMode}
/>
)}
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/ElasticSearchInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/ElasticSearchInputForm.tsx
index eb0a7008f..a7c84cb50 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/ElasticSearchInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/ElasticSearchInputForm.tsx
@@ -9,6 +9,7 @@ import { TtlFormLabel } from "@app/components/features";
import { createNotification } from "@app/components/notifications";
import {
Button,
+ FilterableSelect,
FormControl,
FormLabel,
IconButton,
@@ -19,6 +20,7 @@ import {
} from "@app/components/v2";
import { useCreateDynamicSecret } from "@app/hooks/api";
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
+import { WorkspaceEnv } from "@app/hooks/api/types";
const authMethods = [
{
@@ -73,7 +75,8 @@ const formSchema = z.object({
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
}),
- name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase")
+ name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"),
+ environment: z.object({ name: z.string(), slug: z.string() })
});
type TForm = z.infer;
@@ -82,15 +85,17 @@ type Props = {
onCancel: () => void;
secretPath: string;
projectSlug: string;
- environment: string;
+ environments: WorkspaceEnv[];
+ isSingleEnvironmentMode?: boolean;
};
export const ElasticSearchInputForm = ({
onCompleted,
onCancel,
- environment,
+ environments,
secretPath,
- projectSlug
+ projectSlug,
+ isSingleEnvironmentMode
}: Props) => {
const {
control,
@@ -107,13 +112,20 @@ export const ElasticSearchInputForm = ({
},
roles: ["superuser"],
port: 443
- }
+ },
+ environment: isSingleEnvironmentMode ? environments[0] : undefined
}
});
const createDynamicSecret = useCreateDynamicSecret();
- const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => {
+ const handleCreateDynamicSecret = async ({
+ name,
+ maxTTL,
+ provider,
+ defaultTTL,
+ environment
+ }: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
try {
@@ -124,7 +136,7 @@ export const ElasticSearchInputForm = ({
path: secretPath,
defaultTTL,
projectSlug,
- environmentSlug: environment
+ environmentSlug: environment.slug
});
onCompleted();
} catch {
@@ -406,6 +418,29 @@ export const ElasticSearchInputForm = ({
)}
/>
+ {!isSingleEnvironmentMode && (
+ (
+
+ option.name}
+ getOptionValue={(option) => option.slug}
+ menuPlacement="top"
+ />
+
+ )}
+ />
+ )}
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/LdapInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/LdapInputForm.tsx
index ef74db5b8..18f8c62dc 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/LdapInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/LdapInputForm.tsx
@@ -7,9 +7,18 @@ import { z } from "zod";
import { TtlFormLabel } from "@app/components/features";
import { createNotification } from "@app/components/notifications";
-import { Button, FormControl, Input, Select, SelectItem, TextArea } from "@app/components/v2";
+import {
+ Button,
+ FilterableSelect,
+ FormControl,
+ Input,
+ Select,
+ SelectItem,
+ TextArea
+} from "@app/components/v2";
import { useCreateDynamicSecret } from "@app/hooks/api";
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
+import { WorkspaceEnv } from "@app/hooks/api/types";
enum CredentialType {
Dynamic = "dynamic",
@@ -69,7 +78,8 @@ const formSchema = z.object({
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
}),
- name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase")
+ name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"),
+ environment: z.object({ name: z.string(), slug: z.string() })
});
type TForm = z.infer;
@@ -79,7 +89,8 @@ type Props = {
onCancel: () => void;
secretPath: string;
projectSlug: string;
- environment: string;
+ environments: WorkspaceEnv[];
+ isSingleEnvironmentMode?: boolean;
};
export const LdapInputForm = ({
@@ -87,7 +98,8 @@ export const LdapInputForm = ({
onCancel,
secretPath,
projectSlug,
- environment
+ environments,
+ isSingleEnvironmentMode
}: Props) => {
const {
control,
@@ -107,7 +119,8 @@ export const LdapInputForm = ({
revocationLdif: "",
rollbackLdif: "",
credentialType: CredentialType.Dynamic
- }
+ },
+ environment: isSingleEnvironmentMode ? environments[0] : undefined
}
});
@@ -115,7 +128,13 @@ export const LdapInputForm = ({
const createDynamicSecret = useCreateDynamicSecret();
- const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => {
+ const handleCreateDynamicSecret = async ({
+ name,
+ maxTTL,
+ provider,
+ defaultTTL,
+ environment
+ }: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
try {
@@ -126,7 +145,7 @@ export const LdapInputForm = ({
path: secretPath,
defaultTTL,
projectSlug,
- environmentSlug: environment
+ environmentSlug: environment.slug
});
onCompleted();
} catch {
@@ -369,6 +388,29 @@ export const LdapInputForm = ({
)}
/>
)}
+ {!isSingleEnvironmentMode && (
+ (
+
+ option.name}
+ getOptionValue={(option) => option.slug}
+ menuPlacement="top"
+ />
+
+ )}
+ />
+ )}
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoAtlasInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoAtlasInputForm.tsx
index 4de9b8acc..821802954 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoAtlasInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoAtlasInputForm.tsx
@@ -13,6 +13,7 @@ import {
AccordionItem,
AccordionTrigger,
Button,
+ FilterableSelect,
FormControl,
FormLabel,
IconButton,
@@ -22,6 +23,7 @@ import {
} from "@app/components/v2";
import { useCreateDynamicSecret } from "@app/hooks/api";
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
+import { WorkspaceEnv } from "@app/hooks/api/types";
const formSchema = z.object({
provider: z.object({
@@ -63,7 +65,8 @@ const formSchema = z.object({
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
}),
- name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase")
+ name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"),
+ environment: z.object({ name: z.string(), slug: z.string() })
});
type TForm = z.infer;
@@ -72,7 +75,8 @@ type Props = {
onCancel: () => void;
secretPath: string;
projectSlug: string;
- environment: string;
+ environments: WorkspaceEnv[];
+ isSingleEnvironmentMode?: boolean;
};
const ATLAS_SCOPE_TYPES = [
@@ -93,9 +97,10 @@ const ATLAS_SCOPE_TYPES = [
export const MongoAtlasInputForm = ({
onCompleted,
onCancel,
- environment,
+ environments,
secretPath,
- projectSlug
+ projectSlug,
+ isSingleEnvironmentMode
}: Props) => {
const {
control,
@@ -108,7 +113,8 @@ export const MongoAtlasInputForm = ({
defaultValues: {
provider: {
roles: [{ databaseName: "", roleName: "" }]
- }
+ },
+ environment: isSingleEnvironmentMode ? environments[0] : undefined
}
});
@@ -124,7 +130,13 @@ export const MongoAtlasInputForm = ({
const createDynamicSecret = useCreateDynamicSecret();
- const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => {
+ const handleCreateDynamicSecret = async ({
+ name,
+ maxTTL,
+ provider,
+ defaultTTL,
+ environment
+ }: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
try {
@@ -135,7 +147,7 @@ export const MongoAtlasInputForm = ({
path: secretPath,
defaultTTL,
projectSlug,
- environmentSlug: environment
+ environmentSlug: environment.slug
});
onCompleted();
} catch {
@@ -438,6 +450,29 @@ export const MongoAtlasInputForm = ({
+ {!isSingleEnvironmentMode && (
+ (
+
+ option.name}
+ getOptionValue={(option) => option.slug}
+ menuPlacement="top"
+ />
+
+ )}
+ />
+ )}
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoDBInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoDBInputForm.tsx
index b2418f629..789338c87 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoDBInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoDBInputForm.tsx
@@ -7,9 +7,18 @@ import { z } from "zod";
import { TtlFormLabel } from "@app/components/features";
import { createNotification } from "@app/components/notifications";
-import { Button, FormControl, FormLabel, IconButton, Input, SecretInput } from "@app/components/v2";
+import {
+ Button,
+ FilterableSelect,
+ FormControl,
+ FormLabel,
+ IconButton,
+ Input,
+ SecretInput
+} from "@app/components/v2";
import { useCreateDynamicSecret } from "@app/hooks/api";
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
+import { WorkspaceEnv } from "@app/hooks/api/types";
const formSchema = z.object({
provider: z.object({
@@ -46,7 +55,8 @@ const formSchema = z.object({
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
}),
- name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase")
+ name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"),
+ environment: z.object({ name: z.string(), slug: z.string() })
});
type TForm = z.infer;
@@ -55,15 +65,17 @@ type Props = {
onCancel: () => void;
secretPath: string;
projectSlug: string;
- environment: string;
+ environments: WorkspaceEnv[];
+ isSingleEnvironmentMode?: boolean;
};
export const MongoDBDatabaseInputForm = ({
onCompleted,
onCancel,
- environment,
+ environments,
secretPath,
- projectSlug
+ projectSlug,
+ isSingleEnvironmentMode
}: Props) => {
const {
control,
@@ -76,7 +88,8 @@ export const MongoDBDatabaseInputForm = ({
defaultValues: {
provider: {
roles: [{ roleName: "readWrite" }]
- }
+ },
+ environment: isSingleEnvironmentMode ? environments[0] : undefined
}
});
@@ -87,7 +100,13 @@ export const MongoDBDatabaseInputForm = ({
const createDynamicSecret = useCreateDynamicSecret();
- const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => {
+ const handleCreateDynamicSecret = async ({
+ name,
+ maxTTL,
+ provider,
+ defaultTTL,
+ environment
+ }: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
try {
@@ -105,7 +124,7 @@ export const MongoDBDatabaseInputForm = ({
path: secretPath,
defaultTTL,
projectSlug,
- environmentSlug: environment
+ environmentSlug: environment.slug
});
onCompleted();
} catch {
@@ -321,6 +340,29 @@ export const MongoDBDatabaseInputForm = ({
)}
/>
+ {!isSingleEnvironmentMode && (
+ (
+
+ option.name}
+ getOptionValue={(option) => option.slug}
+ menuPlacement="top"
+ />
+
+ )}
+ />
+ )}
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RabbitMqInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RabbitMqInputForm.tsx
index 21f8b40de..b593730cf 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RabbitMqInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RabbitMqInputForm.tsx
@@ -7,9 +7,18 @@ import { z } from "zod";
import { TtlFormLabel } from "@app/components/features";
import { createNotification } from "@app/components/notifications";
-import { Button, FormControl, FormLabel, IconButton, Input, SecretInput } from "@app/components/v2";
+import {
+ Button,
+ FilterableSelect,
+ FormControl,
+ FormLabel,
+ IconButton,
+ Input,
+ SecretInput
+} from "@app/components/v2";
import { useCreateDynamicSecret } from "@app/hooks/api";
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
+import { WorkspaceEnv } from "@app/hooks/api/types";
const formSchema = z.object({
provider: z.object({
@@ -50,7 +59,8 @@ const formSchema = z.object({
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
}),
- name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase")
+ name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"),
+ environment: z.object({ name: z.string(), slug: z.string() })
});
type TForm = z.infer;
@@ -59,15 +69,17 @@ type Props = {
onCancel: () => void;
secretPath: string;
projectSlug: string;
- environment: string;
+ environments: WorkspaceEnv[];
+ isSingleEnvironmentMode?: boolean;
};
export const RabbitMqInputForm = ({
onCompleted,
onCancel,
- environment,
+ environments,
secretPath,
- projectSlug
+ projectSlug,
+ isSingleEnvironmentMode
}: Props) => {
const {
control,
@@ -89,13 +101,20 @@ export const RabbitMqInputForm = ({
}
},
tags: []
- }
+ },
+ environment: isSingleEnvironmentMode ? environments[0] : undefined
}
});
const createDynamicSecret = useCreateDynamicSecret();
- const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => {
+ const handleCreateDynamicSecret = async ({
+ name,
+ maxTTL,
+ provider,
+ defaultTTL,
+ environment
+ }: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
try {
@@ -106,7 +125,7 @@ export const RabbitMqInputForm = ({
path: secretPath,
defaultTTL,
projectSlug,
- environmentSlug: environment
+ environmentSlug: environment.slug
});
onCompleted();
} catch {
@@ -405,6 +424,29 @@ export const RabbitMqInputForm = ({
)}
/>
+ {!isSingleEnvironmentMode && (
+ (
+
+ option.name}
+ getOptionValue={(option) => option.slug}
+ menuPlacement="top"
+ />
+
+ )}
+ />
+ )}
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RedisInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RedisInputForm.tsx
index 9d9933b44..cec85381e 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RedisInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RedisInputForm.tsx
@@ -11,6 +11,7 @@ import {
AccordionItem,
AccordionTrigger,
Button,
+ FilterableSelect,
FormControl,
Input,
SecretInput,
@@ -18,6 +19,7 @@ import {
} from "@app/components/v2";
import { useCreateDynamicSecret } from "@app/hooks/api";
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
+import { WorkspaceEnv } from "@app/hooks/api/types";
const formSchema = z.object({
provider: z.object({
@@ -50,7 +52,8 @@ const formSchema = z.object({
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
}),
- name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase")
+ name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"),
+ environment: z.object({ name: z.string(), slug: z.string() })
});
type TForm = z.infer;
@@ -59,15 +62,17 @@ type Props = {
onCancel: () => void;
secretPath: string;
projectSlug: string;
- environment: string;
+ environments: WorkspaceEnv[];
+ isSingleEnvironmentMode?: boolean;
};
export const RedisInputForm = ({
onCompleted,
onCancel,
- environment,
+ environments,
secretPath,
- projectSlug
+ projectSlug,
+ isSingleEnvironmentMode
}: Props) => {
const {
control,
@@ -81,13 +86,20 @@ export const RedisInputForm = ({
port: 6379,
creationStatement: "ACL SETUSER {{username}} on >{{password}} ~* &* +@all",
revocationStatement: "ACL DELUSER {{username}}"
- }
+ },
+ environment: isSingleEnvironmentMode ? environments[0] : undefined
}
});
const createDynamicSecret = useCreateDynamicSecret();
- const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => {
+ const handleCreateDynamicSecret = async ({
+ name,
+ maxTTL,
+ provider,
+ defaultTTL,
+ environment
+ }: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
try {
@@ -98,7 +110,7 @@ export const RedisInputForm = ({
path: secretPath,
defaultTTL,
projectSlug,
- environmentSlug: environment
+ environmentSlug: environment.slug
});
onCompleted();
} catch {
@@ -313,6 +325,29 @@ export const RedisInputForm = ({
+ {!isSingleEnvironmentMode && (
+ (
+
+ option.name}
+ getOptionValue={(option) => option.slug}
+ menuPlacement="top"
+ />
+
+ )}
+ />
+ )}
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapAseInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapAseInputForm.tsx
index efe6e0269..e1ebebc6f 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapAseInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapAseInputForm.tsx
@@ -11,12 +11,14 @@ import {
AccordionItem,
AccordionTrigger,
Button,
+ FilterableSelect,
FormControl,
Input,
TextArea
} from "@app/components/v2";
import { useCreateDynamicSecret } from "@app/hooks/api";
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
+import { WorkspaceEnv } from "@app/hooks/api/types";
const formSchema = z.object({
provider: z.object({
@@ -48,7 +50,8 @@ const formSchema = z.object({
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
}),
- name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase")
+ name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"),
+ environment: z.object({ name: z.string(), slug: z.string() })
});
type TForm = z.infer;
@@ -57,15 +60,17 @@ type Props = {
onCancel: () => void;
secretPath: string;
projectSlug: string;
- environment: string;
+ environments: WorkspaceEnv[];
+ isSingleEnvironmentMode?: boolean;
};
export const SapAseInputForm = ({
onCompleted,
onCancel,
- environment,
+ environments,
secretPath,
- projectSlug
+ projectSlug,
+ isSingleEnvironmentMode
}: Props) => {
const {
control,
@@ -82,13 +87,20 @@ sp_adduser '{{username}}', '{{username}}', null;
sp_role 'grant', 'mon_role', '{{username}}';`,
revocationStatement: `sp_dropuser '{{username}}';
sp_droplogin '{{username}}';`
- }
+ },
+ environment: isSingleEnvironmentMode ? environments[0] : undefined
}
});
const createDynamicSecret = useCreateDynamicSecret();
- const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => {
+ const handleCreateDynamicSecret = async ({
+ name,
+ maxTTL,
+ provider,
+ defaultTTL,
+ environment
+ }: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
try {
@@ -99,7 +111,7 @@ sp_droplogin '{{username}}';`
path: secretPath,
defaultTTL,
projectSlug,
- environmentSlug: environment
+ environmentSlug: environment.slug
});
onCompleted();
} catch {
@@ -291,6 +303,29 @@ sp_droplogin '{{username}}';`
+ {!isSingleEnvironmentMode && (
+ (
+
+ option.name}
+ getOptionValue={(option) => option.slug}
+ menuPlacement="top"
+ />
+
+ )}
+ />
+ )}
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapHanaInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapHanaInputForm.tsx
index 1b0bd64b8..27dd20c81 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapHanaInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapHanaInputForm.tsx
@@ -11,6 +11,7 @@ import {
AccordionItem,
AccordionTrigger,
Button,
+ FilterableSelect,
FormControl,
Input,
SecretInput,
@@ -18,6 +19,7 @@ import {
} from "@app/components/v2";
import { useCreateDynamicSecret } from "@app/hooks/api";
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
+import { WorkspaceEnv } from "@app/hooks/api/types";
const formSchema = z.object({
provider: z.object({
@@ -50,7 +52,8 @@ const formSchema = z.object({
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
}),
- name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase")
+ name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"),
+ environment: z.object({ name: z.string(), slug: z.string() })
});
type TForm = z.infer;
@@ -59,15 +62,17 @@ type Props = {
onCancel: () => void;
secretPath: string;
projectSlug: string;
- environment: string;
+ environments: WorkspaceEnv[];
+ isSingleEnvironmentMode?: boolean;
};
export const SapHanaInputForm = ({
onCompleted,
onCancel,
- environment,
+ environments,
secretPath,
- projectSlug
+ projectSlug,
+ isSingleEnvironmentMode
}: Props) => {
const {
control,
@@ -82,13 +87,20 @@ GRANT "MONITORING" TO {{username}};`,
revocationStatement: `REVOKE "MONITORING" FROM {{username}};
DROP USER {{username}};`,
renewStatement: "ALTER USER {{username}} VALID UNTIL '{{expiration}}';"
- }
+ },
+ environment: isSingleEnvironmentMode ? environments[0] : undefined
}
});
const createDynamicSecret = useCreateDynamicSecret();
- const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => {
+ const handleCreateDynamicSecret = async ({
+ name,
+ maxTTL,
+ provider,
+ defaultTTL,
+ environment
+ }: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
try {
@@ -99,7 +111,7 @@ DROP USER {{username}};`,
path: secretPath,
defaultTTL,
projectSlug,
- environmentSlug: environment
+ environmentSlug: environment.slug
});
onCompleted();
} catch {
@@ -311,6 +323,29 @@ DROP USER {{username}};`,
+ {!isSingleEnvironmentMode && (
+ (
+
+ option.name}
+ getOptionValue={(option) => option.slug}
+ menuPlacement="top"
+ />
+
+ )}
+ />
+ )}
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SnowflakeInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SnowflakeInputForm.tsx
index a5f8b25d9..33e0b9ce5 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SnowflakeInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SnowflakeInputForm.tsx
@@ -13,12 +13,14 @@ import {
AccordionItem,
AccordionTrigger,
Button,
+ FilterableSelect,
FormControl,
Input,
TextArea
} from "@app/components/v2";
import { useCreateDynamicSecret } from "@app/hooks/api";
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
+import { WorkspaceEnv } from "@app/hooks/api/types";
const formSchema = z.object({
provider: z.object({
@@ -54,7 +56,8 @@ const formSchema = z.object({
.string()
.trim()
.min(1)
- .refine((val) => val.toLowerCase() === val, "Must be lowercase")
+ .refine((val) => val.toLowerCase() === val, "Must be lowercase"),
+ environment: z.object({ name: z.string(), slug: z.string() })
});
type TForm = z.infer;
@@ -63,15 +66,17 @@ type Props = {
onCancel: () => void;
secretPath: string;
projectSlug: string;
- environment: string;
+ environments: WorkspaceEnv[];
+ isSingleEnvironmentMode?: boolean;
};
export const SnowflakeInputForm = ({
onCompleted,
onCancel,
- environment,
+ environments,
secretPath,
- projectSlug
+ projectSlug,
+ isSingleEnvironmentMode
}: Props) => {
const {
control,
@@ -85,13 +90,20 @@ export const SnowflakeInputForm = ({
"CREATE USER {{username}} PASSWORD = '{{password}}' DEFAULT_ROLE = public DEFAULT_SECONDARY_ROLES = ('ALL') MUST_CHANGE_PASSWORD = FALSE DAYS_TO_EXPIRY = {{expiration}};",
revocationStatement: "DROP USER {{username}};",
renewStatement: "ALTER USER {{username}} SET DAYS_TO_EXPIRY = {{expiration}};"
- }
+ },
+ environment: isSingleEnvironmentMode ? environments[0] : undefined
}
});
const createDynamicSecret = useCreateDynamicSecret();
- const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => {
+ const handleCreateDynamicSecret = async ({
+ name,
+ maxTTL,
+ provider,
+ defaultTTL,
+ environment
+ }: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
try {
@@ -102,7 +114,7 @@ export const SnowflakeInputForm = ({
path: secretPath,
defaultTTL,
projectSlug,
- environmentSlug: environment
+ environmentSlug: environment.slug
});
onCompleted();
} catch (err) {
@@ -314,6 +326,29 @@ export const SnowflakeInputForm = ({
+ {!isSingleEnvironmentMode && (
+ (
+
+ option.name}
+ getOptionValue={(option) => option.slug}
+ menuPlacement="top"
+ />
+
+ )}
+ />
+ )}
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SqlDatabaseInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SqlDatabaseInputForm.tsx
index 4af92b210..732713a1a 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SqlDatabaseInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SqlDatabaseInputForm.tsx
@@ -12,6 +12,7 @@ import {
AccordionItem,
AccordionTrigger,
Button,
+ FilterableSelect,
FormControl,
Input,
SecretInput,
@@ -22,6 +23,7 @@ import {
import { useWorkspace } from "@app/context";
import { gatewaysQueryKeys, useCreateDynamicSecret } from "@app/hooks/api";
import { DynamicSecretProviders, SqlProviders } from "@app/hooks/api/dynamicSecret/types";
+import { WorkspaceEnv } from "@app/hooks/api/types";
const passwordRequirementsSchema = z
.object({
@@ -79,7 +81,8 @@ const formSchema = z.object({
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
}),
- name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase")
+ name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"),
+ environment: z.object({ name: z.string(), slug: z.string() })
});
type TForm = z.infer;
@@ -88,7 +91,8 @@ type Props = {
onCancel: () => void;
secretPath: string;
projectSlug: string;
- environment: string;
+ environments: WorkspaceEnv[];
+ isSingleEnvironmentMode?: boolean;
};
const getSqlStatements = (provider: SqlProviders) => {
@@ -145,9 +149,10 @@ const getDefaultPort = (provider: SqlProviders) => {
export const SqlDatabaseInputForm = ({
onCompleted,
onCancel,
- environment,
+ environments,
secretPath,
- projectSlug
+ projectSlug,
+ isSingleEnvironmentMode
}: Props) => {
const { currentWorkspace } = useWorkspace();
@@ -172,7 +177,8 @@ export const SqlDatabaseInputForm = ({
},
allowedSymbols: "-_.~!*"
}
- }
+ },
+ environment: isSingleEnvironmentMode ? environments[0] : undefined
}
});
@@ -181,9 +187,16 @@ export const SqlDatabaseInputForm = ({
gatewaysQueryKeys.listProjectGateways({ projectId: currentWorkspace.id })
);
- const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => {
+ const handleCreateDynamicSecret = async ({
+ name,
+ maxTTL,
+ provider,
+ defaultTTL,
+ environment
+ }: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
+
try {
await createDynamicSecret.mutateAsync({
provider: { type: DynamicSecretProviders.SqlDatabase, inputs: provider },
@@ -192,7 +205,7 @@ export const SqlDatabaseInputForm = ({
path: secretPath,
defaultTTL,
projectSlug,
- environmentSlug: environment
+ environmentSlug: environment.slug
});
onCompleted();
} catch {
@@ -649,6 +662,29 @@ export const SqlDatabaseInputForm = ({
+ {!isSingleEnvironmentMode && (
+ (
+
+ option.name}
+ getOptionValue={(option) => option.slug}
+ menuPlacement="top"
+ />
+
+ )}
+ />
+ )}
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/TotpInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/TotpInputForm.tsx
index 01d5398f0..e1423c111 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/TotpInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/TotpInputForm.tsx
@@ -5,9 +5,17 @@ import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { createNotification } from "@app/components/notifications";
-import { Button, FormControl, Input, Select, SelectItem } from "@app/components/v2";
+import {
+ Button,
+ FilterableSelect,
+ FormControl,
+ Input,
+ Select,
+ SelectItem
+} from "@app/components/v2";
import { useCreateDynamicSecret } from "@app/hooks/api";
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
+import { WorkspaceEnv } from "@app/hooks/api/types";
enum ConfigType {
URL = "url",
@@ -52,7 +60,8 @@ const formSchema = z.object({
.string()
.trim()
.min(1)
- .refine((val) => val.toLowerCase() === val, "Must be lowercase")
+ .refine((val) => val.toLowerCase() === val, "Must be lowercase"),
+ environment: z.object({ name: z.string(), slug: z.string() })
});
type TForm = z.infer;
@@ -62,15 +71,17 @@ type Props = {
onCancel: () => void;
secretPath: string;
projectSlug: string;
- environment: string;
+ environments: WorkspaceEnv[];
+ isSingleEnvironmentMode?: boolean;
};
export const TotpInputForm = ({
onCompleted,
onCancel,
- environment,
+ environments,
secretPath,
- projectSlug
+ projectSlug,
+ isSingleEnvironmentMode
}: Props) => {
const {
control,
@@ -82,7 +93,8 @@ export const TotpInputForm = ({
defaultValues: {
provider: {
configType: ConfigType.URL
- }
+ },
+ environment: isSingleEnvironmentMode ? environments[0] : undefined
}
});
@@ -90,7 +102,7 @@ export const TotpInputForm = ({
const createDynamicSecret = useCreateDynamicSecret();
- const handleCreateDynamicSecret = async ({ name, provider }: TForm) => {
+ const handleCreateDynamicSecret = async ({ name, provider, environment }: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
try {
@@ -101,7 +113,7 @@ export const TotpInputForm = ({
path: secretPath,
defaultTTL: "1m",
projectSlug,
- environmentSlug: environment
+ environmentSlug: environment.slug
});
onCompleted();
} catch (err) {
@@ -295,6 +307,29 @@ export const TotpInputForm = ({
>
)}
+ {!isSingleEnvironmentMode && (
+ (
+
+ option.name}
+ getOptionValue={(option) => option.slug}
+ menuPlacement="top"
+ />
+
+ )}
+ />
+ )}
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretItem.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretItem.tsx
index 629c23048..01d477097 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretItem.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretItem.tsx
@@ -306,7 +306,10 @@ export const SecretItem = memo(
)}
/>
)}
-
+
Sync Options
- {AdditionalSyncOptionsComponent && (
-
- {(isAllowed) => (
-
-
-
- )}
-
- )}
+
+ {(isAllowed) => (
+
+
+
+ )}
+
@@ -85,6 +84,11 @@ export const SecretSyncOptionsSection = ({ secretSync, onEditOptions }: Props) =
{/* {prependPrefix}
{appendSuffix} */}
{AdditionalSyncOptionsComponent}
+ {disableSecretDeletion && (
+
+ Disabled
+
+ )}
diff --git a/sink/oidc-server/main.js b/sink/oidc-server/main.js
new file mode 100644
index 000000000..0d5f10922
--- /dev/null
+++ b/sink/oidc-server/main.js
@@ -0,0 +1,87 @@
+import Provider from "oidc-provider";
+import express from "express";
+
+const configuration = {
+ jwks: {
+ keys: [
+ {
+ kty: "RSA",
+ use: "sig",
+ alg: "RS256",
+ d: "EF2Kky61jzvMYQ_B6ImXzCsQ8uQzbFJrGnB2azlpr_CFStjjUVKP4EKrSCVEasD6SGNJV2QSiNJr7j05nvuGmHMKa__rbU8fqP4qbDahUgCgWOq-zS5tGK6Ifk4II_cZ_V1F-TnrvmcOKMWBiSV-p8i72KpXXucbHGNRwASVs7--M55wp_m1UsybI2jSQ4IgyvGzTnvMmQ_GsX-XoD8u0zGU_4eN3DGc8l6hdxxuSymH0fEeL1Aj0LoCj6teRGF37a2sBQdU6mkNNAuyyirkoDqGZCGJToQLqX4F1FafnzjeIgfdneRa-vuaV380Hhr2rorWnQyBqOO27M5O_VAkJbfRaWJVrXTJ69ZgkU4GPdeYdklVL0HkU6laziTNqNMeAjnt4m51sWokVyJpvdWcb_vJ4NSCsRo7kHOz7g-UvWTXa8UW0DTDliq_TJ3rN4Gv0vn9tBlFfaeuLPpK4VNmRRDRXY_fcuzlnQwYExL9a4V_vCyGmabdb7PrUFPBcjR5",
+ dp: "SX52TkZEc_eLIk5gYrKjAC643LJIw1RxMBWWewRSGLn_rbrH1he3hy7AGDUV6Uon7zkNh9R5GBVuxmlluBRAGbrhIXAAf8sWeyma3F6FIAt-MH_VkfW5K2p88PLOyVGljlv8-Z3wzdKYOlDP4yFU18LqGMqaRSDLDGhILkuZhjLYA40sfYJeJTi_HVP5UyWL4ohayqUWCT2W3DgeDDThYHmufOaqlrSLhUst6uez_cDz0BXAYIZvUuPVL_n1-_px",
+ dq: "K1KYU77I6yyPA2u32rc0exp_TCG59hhpWxrmXN8yTXWyq_xYBhCJA_nHdY8UV25Hmd7q0iX2i8y2cCAFNWA5UWiSiNg9-fKRLI2nz53IM4dGfssOLwUk66wzX8r_u3XiLZsO7XNNtQZdcZmF0YuNTtzEdiNDhaOyHiwwHgShL36WNmUn00mZR__G5Qk60VvI8vsbvJU9xRnWuEVS1wRgyD7v6Nl9nIxb8N7oibCdTJLmgnRXPWvArsW0cJ-NURfr",
+ e: "AQAB",
+ n: "2QwX-NBMkQYedGpbPvHL7Ca0isvfmLC7lSc8XSOCLmCUIf6Bk_pdCNx2kxsmT81IoA8CfvJLHQj5vWKoVDFMLfwo4IujvsC3m2IrEg6jERE-YHfC3W5jKZtmzQYpfx5vC2_XTmcyPigtyaNVsftGfycES3B_tvphNsFmQcJjVGOsJQXXqh_TDv6FMcH4m9pngyw6wfe3GgAKA0dRTSfD0h7wLdNCeuid53lLpkQypTNdZ6_PiCMu2gr_cH5M0MPZtBb2TW12_2zOabExK1lI5-HvdPtbMT4Qzs2nd2NkjcWmlbKRZzq6IzyWt7W2EnfZDsi61PHECtTb-EQN2icl8Wnsp-0Bw66yviAOj0gn3X5hRLx-TknT_PnWMou17l5GoAojKDezcTW0iLlrfs2ixFlY28u7WklUN8uYhHvwgON6fsdefG-3bPpiRLBPZ_tgXa4doALsCwfXu2oz0vYktk31A-UYv92uJsKSUbK0_8ODTN0rslCqCYN_1a_aVt2P",
+ p: "--L5BX8juLlGJk8hdPgEUmJjD7SsZuMrdq3cSibkkbaWUE5CQQ7vhLPr2dWCS1jUnY9WyoCx9QCZvhTHjORX50ykkOyBso9VJjWvYPjsrPpF7_Y6V0dKlblDmbbmRT9BW-MgjbwTivu3c2OpMXh2XLF-FOTq3t3Brs7SRnhTkD6GBDFf3X95J0PF7NELa9z2-kzPSDYz3k-9FepXnRPBM_ViDzlRw4eKUdylVuhzGbC2TRSmab9BRP0wipQKd-f5",
+ q: "3Jd5CRJpQV3xUi3FiHHAwcjfsRkfXMrxfaXt0PjX2xWzxscYiDcyCF6VhHTAGsiq5SOtCp3l5mg6A9PzdR53AzM2-706D82fMwiUZvsLOVTepXkgriP_xw7rDlkOeAvjB80sL2G9scFliTzzRZ8I8E79A8DxZihfB75AIN9ijklEihnwxfhp2EgO5MYEyQRcqU1TT8wD8ekLMzd-kJUWyTz3BogiVJH__BQoB6kaDyjvQoxBgwh0hi72t9H5XqPH",
+ qi: "cwK0jhzwbu8BaTmTQhwfGiqwNN3v9F4nUQ4dtnBYRI6zlki4cLb2Mf9-VhyEsUYhhdTm8R7RwO9m5Xct3gEfozdk35wuvkVwkZgL3Uho5asao0xi4aENeUk5DCkU-paO3yLSDhIs9YYuYIDjUX6QuMCPjomypuE3SRm-Dg1PGOxYvX3w_P-0kd5iBFrm4jwGTZViFOr8tl_dXgDRDWDgofOYOYcmUv2_0zt1aO3j5dhEpwdkyuDMLfVZNpJQyopJ",
+ kid: "f262a3214213d194c92991d6735b153b",
+ },
+ ],
+ },
+ features: {
+ clientCredentials: {
+ enabled: true,
+ },
+ introspection: {
+ enabled: true,
+ },
+ resourceIndicators: {
+ enabled: true,
+ getResourceServerInfo(ctx, resourceIndicator) {
+ if (resourceIndicator === "urn:api") {
+ return {
+ scope: "read",
+ audience: "urn:api",
+ accessTokenTTL: 1 * 60 * 60, // 1 hour
+ accessTokenFormat: "jwt",
+ };
+ }
+
+ throw new errors.InvalidTarget();
+ },
+ },
+ },
+ clients: [
+ {
+ client_id: "app",
+ client_secret: "a_secret",
+ grant_types: ["client_credentials"],
+ redirect_uris: [],
+ response_types: [],
+ },
+ {
+ client_id: "oidc_client",
+ client_secret: "a_different_secret",
+ grant_types: ["authorization_code"],
+ response_types: ["code"],
+ redirect_uris: ["http://localhost:3001/cb"],
+ },
+ ],
+ claims: {
+ profile: [
+ "birthdate",
+ "family_name",
+ "gender",
+ "given_name",
+ "locale",
+ "middle_name",
+ "name",
+ "nickname",
+ "picture",
+ "preferred_username",
+ "profile",
+ "updated_at",
+ "website",
+ "zoneinfo",
+ ],
+ email: ["email", "email_verified"],
+ },
+};
+
+const oidc = new Provider("http://localhost:3000", configuration);
+
+const app = express();
+app.use("/oidc", oidc.callback());
+app.listen(3000);
diff --git a/sink/oidc-server/package-lock.json b/sink/oidc-server/package-lock.json
new file mode 100644
index 000000000..2be29633b
--- /dev/null
+++ b/sink/oidc-server/package-lock.json
@@ -0,0 +1,1708 @@
+{
+ "name": "oidc-server",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "oidc-server",
+ "version": "1.0.0",
+ "license": "ISC",
+ "dependencies": {
+ "axios": "^1.8.3",
+ "dotenv": "^16.4.7",
+ "express": "^4.21.2",
+ "form-data": "^4.0.2",
+ "jose": "^6.0.10",
+ "oidc-provider": "^8.8.1"
+ }
+ },
+ "node_modules/@koa/cors": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/@koa/cors/-/cors-5.0.0.tgz",
+ "integrity": "sha512-x/iUDjcS90W69PryLDIMgFyV21YLTnG9zOpPXS7Bkt2b8AsY3zZsIpOLBkYr9fBcF3HbkKaER5hOBZLfpLgYNw==",
+ "license": "MIT",
+ "dependencies": {
+ "vary": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 14.0.0"
+ }
+ },
+ "node_modules/@koa/router": {
+ "version": "13.1.0",
+ "resolved": "https://registry.npmjs.org/@koa/router/-/router-13.1.0.tgz",
+ "integrity": "sha512-mNVu1nvkpSd8Q8gMebGbCkDWJ51ODetrFvLKYusej+V0ByD4btqHYnPIzTBLXnQMVUlm/oxVwqmWBY3zQfZilw==",
+ "license": "MIT",
+ "dependencies": {
+ "http-errors": "^2.0.0",
+ "koa-compose": "^4.1.0",
+ "path-to-regexp": "^6.3.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/@koa/router/node_modules/path-to-regexp": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz",
+ "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==",
+ "license": "MIT"
+ },
+ "node_modules/@sindresorhus/is": {
+ "version": "5.6.0",
+ "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz",
+ "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/is?sponsor=1"
+ }
+ },
+ "node_modules/@szmarczak/http-timer": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz",
+ "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==",
+ "license": "MIT",
+ "dependencies": {
+ "defer-to-connect": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=14.16"
+ }
+ },
+ "node_modules/@types/http-cache-semantics": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz",
+ "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==",
+ "license": "MIT"
+ },
+ "node_modules/accepts": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+ "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "~2.1.34",
+ "negotiator": "0.6.3"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/array-flatten": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
+ "license": "MIT"
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "license": "MIT"
+ },
+ "node_modules/axios": {
+ "version": "1.8.3",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.3.tgz",
+ "integrity": "sha512-iP4DebzoNlP/YN2dpwCgb8zoCmhtkajzS48JvwmkSkXvPI3DHc7m+XYL5tGnSlJtR6nImXZmdCuN5aP8dh1d8A==",
+ "license": "MIT",
+ "dependencies": {
+ "follow-redirects": "^1.15.6",
+ "form-data": "^4.0.0",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
+ "node_modules/body-parser": {
+ "version": "1.20.3",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
+ "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "3.1.2",
+ "content-type": "~1.0.5",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "http-errors": "2.0.0",
+ "iconv-lite": "0.4.24",
+ "on-finished": "2.4.1",
+ "qs": "6.13.0",
+ "raw-body": "2.5.2",
+ "type-is": "~1.6.18",
+ "unpipe": "1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/cache-content-type": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/cache-content-type/-/cache-content-type-1.0.1.tgz",
+ "integrity": "sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "^2.1.18",
+ "ylru": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
+ "node_modules/cacheable-lookup": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz",
+ "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.16"
+ }
+ },
+ "node_modules/cacheable-request": {
+ "version": "10.2.14",
+ "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz",
+ "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/http-cache-semantics": "^4.0.2",
+ "get-stream": "^6.0.1",
+ "http-cache-semantics": "^4.1.1",
+ "keyv": "^4.5.3",
+ "mimic-response": "^4.0.0",
+ "normalize-url": "^8.0.0",
+ "responselike": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=14.16"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/call-bound": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "get-intrinsic": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/co": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
+ "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==",
+ "license": "MIT",
+ "engines": {
+ "iojs": ">= 1.0.0",
+ "node": ">= 0.12.0"
+ }
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "license": "MIT",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/content-disposition": {
+ "version": "0.5.4",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
+ "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "5.2.1"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/content-type": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
+ "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
+ "license": "MIT"
+ },
+ "node_modules/cookies": {
+ "version": "0.9.1",
+ "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.9.1.tgz",
+ "integrity": "sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "~2.0.0",
+ "keygrip": "~1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/decompress-response": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
+ "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
+ "license": "MIT",
+ "dependencies": {
+ "mimic-response": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/decompress-response/node_modules/mimic-response": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
+ "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/deep-equal": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz",
+ "integrity": "sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw==",
+ "license": "MIT"
+ },
+ "node_modules/defer-to-connect": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz",
+ "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/delegates": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
+ "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==",
+ "license": "MIT"
+ },
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/destroy": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/dotenv": {
+ "version": "16.4.7",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz",
+ "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://dotenvx.com"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+ "license": "MIT"
+ },
+ "node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-set-tostringtag": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "license": "MIT"
+ },
+ "node_modules/eta": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/eta/-/eta-3.5.0.tgz",
+ "integrity": "sha512-e3x3FBvGzeCIHhF+zhK8FZA2vC5uFn6b4HJjegUbIWrDb4mJ7JjTGMJY9VGIbRVpmSwHopNiaJibhjIr+HfLug==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/eta-dev/eta?sponsor=1"
+ }
+ },
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/express": {
+ "version": "4.21.2",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
+ "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "~1.3.8",
+ "array-flatten": "1.1.1",
+ "body-parser": "1.20.3",
+ "content-disposition": "0.5.4",
+ "content-type": "~1.0.4",
+ "cookie": "0.7.1",
+ "cookie-signature": "1.0.6",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "finalhandler": "1.3.1",
+ "fresh": "0.5.2",
+ "http-errors": "2.0.0",
+ "merge-descriptors": "1.0.3",
+ "methods": "~1.1.2",
+ "on-finished": "2.4.1",
+ "parseurl": "~1.3.3",
+ "path-to-regexp": "0.1.12",
+ "proxy-addr": "~2.0.7",
+ "qs": "6.13.0",
+ "range-parser": "~1.2.1",
+ "safe-buffer": "5.2.1",
+ "send": "0.19.0",
+ "serve-static": "1.16.2",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "type-is": "~1.6.18",
+ "utils-merge": "1.0.1",
+ "vary": "~1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/finalhandler": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
+ "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "2.6.9",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "on-finished": "2.4.1",
+ "parseurl": "~1.3.3",
+ "statuses": "2.0.1",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/follow-redirects": {
+ "version": "1.15.9",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
+ "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
+ "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/form-data-encoder": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz",
+ "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14.17"
+ }
+ },
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fresh": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/get-stream": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
+ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/got": {
+ "version": "13.0.0",
+ "resolved": "https://registry.npmjs.org/got/-/got-13.0.0.tgz",
+ "integrity": "sha512-XfBk1CxOOScDcMr9O1yKkNaQyy865NbYs+F7dr4H0LZMVgCj2Le59k6PqbNHoL5ToeaEQUYh6c6yMfVcc6SJxA==",
+ "license": "MIT",
+ "dependencies": {
+ "@sindresorhus/is": "^5.2.0",
+ "@szmarczak/http-timer": "^5.0.1",
+ "cacheable-lookup": "^7.0.0",
+ "cacheable-request": "^10.2.8",
+ "decompress-response": "^6.0.0",
+ "form-data-encoder": "^2.1.2",
+ "get-stream": "^6.0.1",
+ "http2-wrapper": "^2.1.10",
+ "lowercase-keys": "^3.0.0",
+ "p-cancelable": "^3.0.0",
+ "responselike": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/got?sponsor=1"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-tostringtag": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "license": "MIT",
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/http-assert": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.5.0.tgz",
+ "integrity": "sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w==",
+ "license": "MIT",
+ "dependencies": {
+ "deep-equal": "~1.0.1",
+ "http-errors": "~1.8.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/http-assert/node_modules/depd": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
+ "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/http-assert/node_modules/http-errors": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz",
+ "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "~1.1.2",
+ "inherits": "2.0.4",
+ "setprototypeof": "1.2.0",
+ "statuses": ">= 1.5.0 < 2",
+ "toidentifier": "1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/http-assert/node_modules/statuses": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
+ "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/http-cache-semantics": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz",
+ "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==",
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
+ "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "2.0.0",
+ "inherits": "2.0.4",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "toidentifier": "1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/http2-wrapper": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz",
+ "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==",
+ "license": "MIT",
+ "dependencies": {
+ "quick-lru": "^5.1.1",
+ "resolve-alpn": "^1.2.0"
+ },
+ "engines": {
+ "node": ">=10.19.0"
+ }
+ },
+ "node_modules/http2-wrapper/node_modules/quick-lru": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz",
+ "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "license": "ISC"
+ },
+ "node_modules/ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/is-generator-function": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz",
+ "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "get-proto": "^1.0.0",
+ "has-tostringtag": "^1.0.2",
+ "safe-regex-test": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-regex": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
+ "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "gopd": "^1.2.0",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/jose": {
+ "version": "6.0.10",
+ "resolved": "https://registry.npmjs.org/jose/-/jose-6.0.10.tgz",
+ "integrity": "sha512-skIAxZqcMkOrSwjJvplIPYrlXGpxTPnro2/QWTDCxAdWQrSTV5/KqspMWmi5WAx5+ULswASJiZ0a+1B/Lxt9cw==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/panva"
+ }
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "license": "MIT"
+ },
+ "node_modules/keygrip": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz",
+ "integrity": "sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==",
+ "license": "MIT",
+ "dependencies": {
+ "tsscmp": "1.0.6"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "license": "MIT",
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/koa": {
+ "version": "2.16.0",
+ "resolved": "https://registry.npmjs.org/koa/-/koa-2.16.0.tgz",
+ "integrity": "sha512-Afhqq0Vq3W7C+/rW6IqHVBDLzqObwZ07JaUNUEF8yCQ6afiyFE3RAy+i7V0E46XOWlH7vPWn/x0vsZwNy6PWxw==",
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "^1.3.5",
+ "cache-content-type": "^1.0.0",
+ "content-disposition": "~0.5.2",
+ "content-type": "^1.0.4",
+ "cookies": "~0.9.0",
+ "debug": "^4.3.2",
+ "delegates": "^1.0.0",
+ "depd": "^2.0.0",
+ "destroy": "^1.0.4",
+ "encodeurl": "^1.0.2",
+ "escape-html": "^1.0.3",
+ "fresh": "~0.5.2",
+ "http-assert": "^1.3.0",
+ "http-errors": "^1.6.3",
+ "is-generator-function": "^1.0.7",
+ "koa-compose": "^4.1.0",
+ "koa-convert": "^2.0.0",
+ "on-finished": "^2.3.0",
+ "only": "~0.0.2",
+ "parseurl": "^1.3.2",
+ "statuses": "^1.5.0",
+ "type-is": "^1.6.16",
+ "vary": "^1.1.2"
+ },
+ "engines": {
+ "node": "^4.8.4 || ^6.10.1 || ^7.10.1 || >= 8.1.4"
+ }
+ },
+ "node_modules/koa-compose": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz",
+ "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==",
+ "license": "MIT"
+ },
+ "node_modules/koa-convert": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/koa-convert/-/koa-convert-2.0.0.tgz",
+ "integrity": "sha512-asOvN6bFlSnxewce2e/DK3p4tltyfC4VM7ZwuTuepI7dEQVcvpyFuBcEARu1+Hxg8DIwytce2n7jrZtRlPrARA==",
+ "license": "MIT",
+ "dependencies": {
+ "co": "^4.6.0",
+ "koa-compose": "^4.1.0"
+ },
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/koa/node_modules/debug": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
+ "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/koa/node_modules/encodeurl": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+ "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/koa/node_modules/http-errors": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz",
+ "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "~1.1.2",
+ "inherits": "2.0.4",
+ "setprototypeof": "1.2.0",
+ "statuses": ">= 1.5.0 < 2",
+ "toidentifier": "1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/koa/node_modules/http-errors/node_modules/depd": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
+ "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/koa/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/koa/node_modules/statuses": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
+ "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/lowercase-keys": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz",
+ "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==",
+ "license": "MIT",
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/media-typer": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+ "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/merge-descriptors": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
+ "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/methods": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+ "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+ "license": "MIT",
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mimic-response": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz",
+ "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==",
+ "license": "MIT",
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "5.1.3",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.3.tgz",
+ "integrity": "sha512-zAbEOEr7u2CbxwoMRlz/pNSpRP0FdAU4pRaYunCdEezWohXFs+a0Xw7RfkKaezMsmSM1vttcLthJtwRnVtOfHQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.js"
+ },
+ "engines": {
+ "node": "^18 || >=20"
+ }
+ },
+ "node_modules/negotiator": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/normalize-url": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.1.tgz",
+ "integrity": "sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/object-hash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
+ "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/oidc-provider": {
+ "version": "8.8.1",
+ "resolved": "https://registry.npmjs.org/oidc-provider/-/oidc-provider-8.8.1.tgz",
+ "integrity": "sha512-qVChpayTwojUREJxLkFofUSK8kiSRIdzPrVSsoGibqRHl/YO60ege94OZS8vh7zaK+zxcG/Gu8UMaYB5ulohCQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@koa/cors": "^5.0.0",
+ "@koa/router": "^13.1.0",
+ "debug": "^4.4.0",
+ "eta": "^3.5.0",
+ "got": "^13.0.0",
+ "jose": "^5.9.6",
+ "jsesc": "^3.1.0",
+ "koa": "^2.15.4",
+ "nanoid": "^5.0.9",
+ "object-hash": "^3.0.0",
+ "oidc-token-hash": "^5.0.3",
+ "quick-lru": "^7.0.0",
+ "raw-body": "^3.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/panva"
+ }
+ },
+ "node_modules/oidc-provider/node_modules/debug": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
+ "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/oidc-provider/node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/oidc-provider/node_modules/jose": {
+ "version": "5.10.0",
+ "resolved": "https://registry.npmjs.org/jose/-/jose-5.10.0.tgz",
+ "integrity": "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/panva"
+ }
+ },
+ "node_modules/oidc-provider/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/oidc-provider/node_modules/raw-body": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz",
+ "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "3.1.2",
+ "http-errors": "2.0.0",
+ "iconv-lite": "0.6.3",
+ "unpipe": "1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/oidc-token-hash": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.1.0.tgz",
+ "integrity": "sha512-y0W+X7Ppo7oZX6eovsRkuzcSM40Bicg2JEJkDJ4irIt1wsYAP5MLSNv+QAogO8xivMffw/9OvV3um1pxXgt1uA==",
+ "license": "MIT",
+ "engines": {
+ "node": "^10.13.0 || >=12.0.0"
+ }
+ },
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "license": "MIT",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/only": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/only/-/only-0.0.2.tgz",
+ "integrity": "sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ=="
+ },
+ "node_modules/p-cancelable": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz",
+ "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20"
+ }
+ },
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/path-to-regexp": {
+ "version": "0.1.12",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
+ "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
+ "license": "MIT"
+ },
+ "node_modules/proxy-addr": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+ "license": "MIT",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+ "license": "MIT"
+ },
+ "node_modules/qs": {
+ "version": "6.13.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
+ "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "side-channel": "^1.0.6"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/quick-lru": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-7.0.0.tgz",
+ "integrity": "sha512-MX8gB7cVYTrYcFfAnfLlhRd0+Toyl8yX8uBx1MrX7K0jegiz9TumwOK27ldXrgDlHRdVi+MqU9Ssw6dr4BNreg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/raw-body": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
+ "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "3.1.2",
+ "http-errors": "2.0.0",
+ "iconv-lite": "0.4.24",
+ "unpipe": "1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/resolve-alpn": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz",
+ "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==",
+ "license": "MIT"
+ },
+ "node_modules/responselike": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz",
+ "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==",
+ "license": "MIT",
+ "dependencies": {
+ "lowercase-keys": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/safe-regex-test": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz",
+ "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "is-regex": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "license": "MIT"
+ },
+ "node_modules/send": {
+ "version": "0.19.0",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
+ "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "fresh": "0.5.2",
+ "http-errors": "2.0.0",
+ "mime": "1.6.0",
+ "ms": "2.1.3",
+ "on-finished": "2.4.1",
+ "range-parser": "~1.2.1",
+ "statuses": "2.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/send/node_modules/encodeurl": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+ "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/send/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/serve-static": {
+ "version": "1.16.2",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
+ "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
+ "license": "MIT",
+ "dependencies": {
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "parseurl": "~1.3.3",
+ "send": "0.19.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+ "license": "ISC"
+ },
+ "node_modules/side-channel": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3",
+ "side-channel-list": "^1.0.0",
+ "side-channel-map": "^1.0.1",
+ "side-channel-weakmap": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-list": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
+ "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-map": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-weakmap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3",
+ "side-channel-map": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/statuses": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+ "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/tsscmp": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz",
+ "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.6.x"
+ }
+ },
+ "node_modules/type-is": {
+ "version": "1.6.18",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+ "license": "MIT",
+ "dependencies": {
+ "media-typer": "0.3.0",
+ "mime-types": "~2.1.24"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/utils-merge": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/ylru": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/ylru/-/ylru-1.4.0.tgz",
+ "integrity": "sha512-2OQsPNEmBCvXuFlIni/a+Rn+R2pHW9INm0BxXJ4hVDA8TirqMj+J/Rp9ItLatT/5pZqWwefVrTQcHpixsxnVlA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ }
+ }
+}
diff --git a/sink/oidc-server/package.json b/sink/oidc-server/package.json
new file mode 100644
index 000000000..514629d46
--- /dev/null
+++ b/sink/oidc-server/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "oidc-server",
+ "version": "1.0.0",
+ "main": "index.js",
+ "type": "module",
+ "scripts": {
+ "start": "node main.js"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "description": "",
+ "dependencies": {
+ "axios": "^1.8.3",
+ "dotenv": "^16.4.7",
+ "express": "^4.21.2",
+ "form-data": "^4.0.2",
+ "jose": "^6.0.10",
+ "oidc-provider": "^8.8.1"
+ }
+}
diff --git a/sink/oidc-server/test-infisical.js b/sink/oidc-server/test-infisical.js
new file mode 100644
index 000000000..a2d8df022
--- /dev/null
+++ b/sink/oidc-server/test-infisical.js
@@ -0,0 +1,80 @@
+import axios from "axios";
+import { Buffer } from "buffer";
+import querystring from "querystring";
+
+// Configuration
+const config = {
+ issuer: "http://localhost:3000/oidc",
+ tokenEndpoint: "http://localhost:3000/oidc/token",
+ clientId: "app",
+ clientSecret: "a_secret",
+};
+
+// Client credentials flow for machine identity
+async function getMachineToken() {
+ try {
+ // Use application/x-www-form-urlencoded format as required by the OIDC spec
+ const data = querystring.stringify({
+ grant_type: "client_credentials",
+ scope: "read",
+ resource: "urn:api",
+ });
+
+ const authHeader =
+ "Basic " +
+ Buffer.from(`${config.clientId}:${config.clientSecret}`).toString(
+ "base64",
+ );
+
+ const response = await axios.post(config.tokenEndpoint, data, {
+ headers: {
+ "Content-Type": "application/x-www-form-urlencoded",
+ Authorization: authHeader,
+ },
+ });
+
+ console.log("Successfully obtained token:");
+ console.log("Access Token:", response.data.access_token);
+ console.log("Token Type:", response.data.token_type);
+ console.log("Expires In:", response.data.expires_in, "seconds");
+ console.log("Scope:", response.data.scope);
+
+ return response.data;
+ } catch (error) {
+ console.error("Error obtaining token:");
+ if (error.response && error.response.data) {
+ console.error(error.response.data);
+ } else {
+ console.error(error.message);
+ }
+ throw error;
+ }
+}
+
+// Test the machine identity authentication
+async function testMachineIdentity() {
+ try {
+ // Get token using client credentials
+ const token = await getMachineToken();
+
+ const loginData = querystring.stringify({
+ identityId: "5d81d5cc-602f-4af7-b242-ab7c1331b430",
+ jwt: token.access_token,
+ });
+
+ const response = await axios({
+ method: "post",
+ url: `http://localhost:8080/api/v1/auth/oidc-auth/login`,
+ headers: {
+ "Content-Type": "application/x-www-form-urlencoded",
+ },
+ data: loginData,
+ });
+ console.log(response.data);
+ } catch (error) {
+ console.error("Error in test:", error.message);
+ }
+}
+
+// Run the test
+testMachineIdentity();