Compare commits

..

4 Commits

Author SHA1 Message Date
22f32e060b filter out random request ID value 2025-06-01 21:31:26 +04:00
b4f26aac25 fix: tests failing 2025-06-01 21:26:16 +04:00
b634a6c371 requested changes 2025-06-01 21:10:05 +04:00
080ae5ce6f fix(cli): improve error handling 2025-06-01 20:22:15 +04:00
160 changed files with 1519 additions and 3960 deletions

View File

@ -1,21 +0,0 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const hasColumn = await knex.schema.hasColumn(TableName.DynamicSecret, "usernameTemplate");
if (!hasColumn) {
await knex.schema.alterTable(TableName.DynamicSecret, (t) => {
t.string("usernameTemplate").nullable();
});
}
}
export async function down(knex: Knex): Promise<void> {
const hasColumn = await knex.schema.hasColumn(TableName.DynamicSecret, "usernameTemplate");
if (hasColumn) {
await knex.schema.alterTable(TableName.DynamicSecret, (t) => {
t.dropColumn("usernameTemplate");
});
}
}

View File

@ -28,8 +28,7 @@ export const DynamicSecretsSchema = z.object({
updatedAt: z.date(), updatedAt: z.date(),
encryptedInput: zodBuffer, encryptedInput: zodBuffer,
projectGatewayId: z.string().uuid().nullable().optional(), projectGatewayId: z.string().uuid().nullable().optional(),
gatewayId: z.string().uuid().nullable().optional(), gatewayId: z.string().uuid().nullable().optional()
usernameTemplate: z.string().nullable().optional()
}); });
export type TDynamicSecrets = z.infer<typeof DynamicSecretsSchema>; export type TDynamicSecrets = z.infer<typeof DynamicSecretsSchema>;

View File

@ -6,8 +6,6 @@ import { ApiDocsTags, DYNAMIC_SECRETS } from "@app/lib/api-docs";
import { daysToMillisecond } from "@app/lib/dates"; import { daysToMillisecond } from "@app/lib/dates";
import { removeTrailingSlash } from "@app/lib/fn"; import { removeTrailingSlash } from "@app/lib/fn";
import { ms } from "@app/lib/ms"; import { ms } from "@app/lib/ms";
import { isValidHandleBarTemplate } from "@app/lib/template/validate-handlebars";
import { CharacterType, characterValidator } from "@app/lib/validator/validate-string";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { slugSchema } from "@app/server/lib/schemas"; import { slugSchema } from "@app/server/lib/schemas";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
@ -15,28 +13,6 @@ import { SanitizedDynamicSecretSchema } from "@app/server/routes/sanitizedSchema
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
import { ResourceMetadataSchema } from "@app/services/resource-metadata/resource-metadata-schema"; import { ResourceMetadataSchema } from "@app/services/resource-metadata/resource-metadata-schema";
const validateUsernameTemplateCharacters = characterValidator([
CharacterType.AlphaNumeric,
CharacterType.Underscore,
CharacterType.Hyphen,
CharacterType.OpenBrace,
CharacterType.CloseBrace,
CharacterType.CloseBracket,
CharacterType.OpenBracket,
CharacterType.Fullstop
]);
const userTemplateSchema = z
.string()
.trim()
.max(255)
.refine((el) => validateUsernameTemplateCharacters(el))
.refine((el) =>
isValidHandleBarTemplate(el, {
allowedExpressions: (val) => ["randomUsername", "unixTimestamp"].includes(val)
})
);
export const registerDynamicSecretRouter = async (server: FastifyZodProvider) => { export const registerDynamicSecretRouter = async (server: FastifyZodProvider) => {
server.route({ server.route({
method: "POST", method: "POST",
@ -76,8 +52,7 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) =>
path: z.string().describe(DYNAMIC_SECRETS.CREATE.path).trim().default("/").transform(removeTrailingSlash), path: z.string().describe(DYNAMIC_SECRETS.CREATE.path).trim().default("/").transform(removeTrailingSlash),
environmentSlug: z.string().describe(DYNAMIC_SECRETS.CREATE.environmentSlug).min(1), environmentSlug: z.string().describe(DYNAMIC_SECRETS.CREATE.environmentSlug).min(1),
name: slugSchema({ min: 1, max: 64, field: "Name" }).describe(DYNAMIC_SECRETS.CREATE.name), name: slugSchema({ min: 1, max: 64, field: "Name" }).describe(DYNAMIC_SECRETS.CREATE.name),
metadata: ResourceMetadataSchema.optional(), metadata: ResourceMetadataSchema.optional()
usernameTemplate: userTemplateSchema.optional()
}), }),
response: { response: {
200: z.object({ 200: z.object({
@ -98,6 +73,39 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) =>
} }
}); });
server.route({
method: "POST",
url: "/entra-id/users",
config: {
rateLimit: readLimit
},
schema: {
body: z.object({
tenantId: z.string().min(1).describe("The tenant ID of the Azure Entra ID"),
applicationId: z.string().min(1).describe("The application ID of the Azure Entra ID App Registration"),
clientSecret: z.string().min(1).describe("The client secret of the Azure Entra ID App Registration")
}),
response: {
200: z
.object({
name: z.string().min(1).describe("The name of the user"),
id: z.string().min(1).describe("The ID of the user"),
email: z.string().min(1).describe("The email of the user")
})
.array()
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const data = await server.services.dynamicSecret.fetchAzureEntraIdUsers({
tenantId: req.body.tenantId,
applicationId: req.body.applicationId,
clientSecret: req.body.clientSecret
});
return data;
}
});
server.route({ server.route({
method: "PATCH", method: "PATCH",
url: "/:name", url: "/:name",
@ -142,8 +150,7 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) =>
}) })
.nullable(), .nullable(),
newName: z.string().describe(DYNAMIC_SECRETS.UPDATE.newName).optional(), newName: z.string().describe(DYNAMIC_SECRETS.UPDATE.newName).optional(),
metadata: ResourceMetadataSchema.optional(), metadata: ResourceMetadataSchema.optional()
usernameTemplate: userTemplateSchema.nullable().optional()
}) })
}), }),
response: { response: {
@ -321,37 +328,4 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) =>
return { leases }; return { leases };
} }
}); });
server.route({
method: "POST",
url: "/entra-id/users",
config: {
rateLimit: readLimit
},
schema: {
body: z.object({
tenantId: z.string().min(1).describe("The tenant ID of the Azure Entra ID"),
applicationId: z.string().min(1).describe("The application ID of the Azure Entra ID App Registration"),
clientSecret: z.string().min(1).describe("The client secret of the Azure Entra ID App Registration")
}),
response: {
200: z
.object({
name: z.string().min(1).describe("The name of the user"),
id: z.string().min(1).describe("The ID of the user"),
email: z.string().min(1).describe("The email of the user")
})
.array()
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const data = await server.services.dynamicSecret.fetchAzureEntraIdUsers({
tenantId: req.body.tenantId,
applicationId: req.body.applicationId,
clientSecret: req.body.clientSecret
});
return data;
}
});
}; };

View File

@ -132,11 +132,7 @@ export const dynamicSecretLeaseServiceFactory = ({
let result; let result;
try { try {
result = await selectedProvider.create({ result = await selectedProvider.create(decryptedStoredInput, expireAt.getTime());
inputs: decryptedStoredInput,
expireAt: expireAt.getTime(),
usernameTemplate: dynamicSecretCfg.usernameTemplate
});
} catch (error: unknown) { } catch (error: unknown) {
if (error && typeof error === "object" && error !== null && "sqlMessage" in error) { if (error && typeof error === "object" && error !== null && "sqlMessage" in error) {
throw new BadRequestError({ message: error.sqlMessage as string }); throw new BadRequestError({ message: error.sqlMessage as string });

View File

@ -11,8 +11,6 @@ export const verifyHostInputValidity = async (host: string, isGateway = false) =
if (appCfg.isDevelopmentMode) return [host]; if (appCfg.isDevelopmentMode) return [host];
if (isGateway) return [host];
const reservedHosts = [appCfg.DB_HOST || getDbConnectionHost(appCfg.DB_CONNECTION_URI)].concat( const reservedHosts = [appCfg.DB_HOST || getDbConnectionHost(appCfg.DB_CONNECTION_URI)].concat(
(appCfg.DB_READ_REPLICAS || []).map((el) => getDbConnectionHost(el.DB_CONNECTION_URI)), (appCfg.DB_READ_REPLICAS || []).map((el) => getDbConnectionHost(el.DB_CONNECTION_URI)),
getDbConnectionHost(appCfg.REDIS_URL), getDbConnectionHost(appCfg.REDIS_URL),
@ -60,7 +58,7 @@ export const verifyHostInputValidity = async (host: string, isGateway = false) =
} }
} }
if (!(appCfg.DYNAMIC_SECRET_ALLOW_INTERNAL_IP || appCfg.ALLOW_INTERNAL_IP_CONNECTIONS)) { if (!isGateway && !(appCfg.DYNAMIC_SECRET_ALLOW_INTERNAL_IP || appCfg.ALLOW_INTERNAL_IP_CONNECTIONS)) {
const isInternalIp = inputHostIps.some((el) => isPrivateIp(el)); const isInternalIp = inputHostIps.some((el) => isPrivateIp(el));
if (isInternalIp) throw new BadRequestError({ message: "Invalid db host" }); if (isInternalIp) throw new BadRequestError({ message: "Invalid db host" });
} }

View File

@ -78,8 +78,7 @@ export const dynamicSecretServiceFactory = ({
actorOrgId, actorOrgId,
defaultTTL, defaultTTL,
actorAuthMethod, actorAuthMethod,
metadata, metadata
usernameTemplate
}: TCreateDynamicSecretDTO) => { }: TCreateDynamicSecretDTO) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId); const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` }); if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
@ -164,8 +163,7 @@ export const dynamicSecretServiceFactory = ({
defaultTTL, defaultTTL,
folderId: folder.id, folderId: folder.id,
name, name,
gatewayId: selectedGatewayId, gatewayId: selectedGatewayId
usernameTemplate
}, },
tx tx
); );
@ -201,8 +199,7 @@ export const dynamicSecretServiceFactory = ({
newName, newName,
actorOrgId, actorOrgId,
actorAuthMethod, actorAuthMethod,
metadata, metadata
usernameTemplate
}: TUpdateDynamicSecretDTO) => { }: TUpdateDynamicSecretDTO) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId); const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` }); if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
@ -314,8 +311,7 @@ export const dynamicSecretServiceFactory = ({
defaultTTL, defaultTTL,
name: newName ?? name, name: newName ?? name,
status: null, status: null,
gatewayId: selectedGatewayId, gatewayId: selectedGatewayId
usernameTemplate
}, },
tx tx
); );

View File

@ -22,7 +22,6 @@ export type TCreateDynamicSecretDTO = {
name: string; name: string;
projectSlug: string; projectSlug: string;
metadata?: ResourceMetadataDTO; metadata?: ResourceMetadataDTO;
usernameTemplate?: string | null;
} & Omit<TProjectPermission, "projectId">; } & Omit<TProjectPermission, "projectId">;
export type TUpdateDynamicSecretDTO = { export type TUpdateDynamicSecretDTO = {
@ -35,7 +34,6 @@ export type TUpdateDynamicSecretDTO = {
inputs?: TProvider["inputs"]; inputs?: TProvider["inputs"];
projectSlug: string; projectSlug: string;
metadata?: ResourceMetadataDTO; metadata?: ResourceMetadataDTO;
usernameTemplate?: string | null;
} & Omit<TProjectPermission, "projectId">; } & Omit<TProjectPermission, "projectId">;
export type TDeleteDynamicSecretDTO = { export type TDeleteDynamicSecretDTO = {

View File

@ -132,15 +132,9 @@ const generatePassword = () => {
return customAlphabet(charset, 64)(); return customAlphabet(charset, 64)();
}; };
const generateUsername = (usernameTemplate?: string | null) => { const generateUsername = () => {
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-"; const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-";
const randomUsername = `inf-${customAlphabet(charset, 32)()}`; return `inf-${customAlphabet(charset, 32)()}`; // Username must start with an ascii letter, so we prepend the username with "inf-"
if (!usernameTemplate) return randomUsername;
return handlebars.compile(usernameTemplate)({
randomUsername,
unixTimestamp: Math.floor(Date.now() / 100)
});
}; };
export const AwsElastiCacheDatabaseProvider = (): TDynamicProviderFns => { export const AwsElastiCacheDatabaseProvider = (): TDynamicProviderFns => {
@ -174,14 +168,13 @@ export const AwsElastiCacheDatabaseProvider = (): TDynamicProviderFns => {
return true; return true;
}; };
const create = async (data: { inputs: unknown; expireAt: number; usernameTemplate?: string | null }) => { const create = async (inputs: unknown, expireAt: number) => {
const { inputs, expireAt, usernameTemplate } = data;
const providerInputs = await validateProviderInputs(inputs); const providerInputs = await validateProviderInputs(inputs);
if (!(await validateConnection(providerInputs))) { if (!(await validateConnection(providerInputs))) {
throw new BadRequestError({ message: "Failed to establish connection" }); throw new BadRequestError({ message: "Failed to establish connection" });
} }
const leaseUsername = generateUsername(usernameTemplate); const leaseUsername = generateUsername();
const leasePassword = generatePassword(); const leasePassword = generatePassword();
const leaseExpiration = new Date(expireAt).toISOString(); const leaseExpiration = new Date(expireAt).toISOString();

View File

@ -16,7 +16,6 @@ import {
PutUserPolicyCommand, PutUserPolicyCommand,
RemoveUserFromGroupCommand RemoveUserFromGroupCommand
} from "@aws-sdk/client-iam"; } from "@aws-sdk/client-iam";
import handlebars from "handlebars";
import { z } from "zod"; import { z } from "zod";
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
@ -24,14 +23,8 @@ import { alphaNumericNanoId } from "@app/lib/nanoid";
import { DynamicSecretAwsIamSchema, TDynamicProviderFns } from "./models"; import { DynamicSecretAwsIamSchema, TDynamicProviderFns } from "./models";
const generateUsername = (usernameTemplate?: string | null) => { const generateUsername = () => {
const randomUsername = alphaNumericNanoId(32); return alphaNumericNanoId(32);
if (!usernameTemplate) return randomUsername;
return handlebars.compile(usernameTemplate)({
randomUsername,
unixTimestamp: Math.floor(Date.now() / 100)
});
}; };
export const AwsIamProvider = (): TDynamicProviderFns => { export const AwsIamProvider = (): TDynamicProviderFns => {
@ -60,13 +53,11 @@ export const AwsIamProvider = (): TDynamicProviderFns => {
return isConnected; return isConnected;
}; };
const create = async (data: { inputs: unknown; expireAt: number; usernameTemplate?: string | null }) => { const create = async (inputs: unknown) => {
const { inputs, usernameTemplate } = data;
const providerInputs = await validateProviderInputs(inputs); const providerInputs = await validateProviderInputs(inputs);
const client = await $getClient(providerInputs); const client = await $getClient(providerInputs);
const username = generateUsername(usernameTemplate); const username = generateUsername();
const { policyArns, userGroups, policyDocument, awsPath, permissionBoundaryPolicyArn } = providerInputs; const { policyArns, userGroups, policyDocument, awsPath, permissionBoundaryPolicyArn } = providerInputs;
const createUserRes = await client.send( const createUserRes = await client.send(
new CreateUserCommand({ new CreateUserCommand({

View File

@ -55,7 +55,7 @@ export const AzureEntraIDProvider = (): TDynamicProviderFns & {
return data.success; return data.success;
}; };
const create = async ({ inputs }: { inputs: unknown }) => { const create = async (inputs: unknown) => {
const providerInputs = await validateProviderInputs(inputs); const providerInputs = await validateProviderInputs(inputs);
const data = await $getToken(providerInputs.tenantId, providerInputs.applicationId, providerInputs.clientSecret); const data = await $getToken(providerInputs.tenantId, providerInputs.applicationId, providerInputs.clientSecret);
if (!data.success) { if (!data.success) {
@ -88,7 +88,7 @@ export const AzureEntraIDProvider = (): TDynamicProviderFns & {
const revoke = async (inputs: unknown, entityId: string) => { const revoke = async (inputs: unknown, entityId: string) => {
// Creates a new password // Creates a new password
await create({ inputs }); await create(inputs);
return { entityId }; return { entityId };
}; };

View File

@ -14,14 +14,8 @@ const generatePassword = (size = 48) => {
return customAlphabet(charset, 48)(size); return customAlphabet(charset, 48)(size);
}; };
const generateUsername = (usernameTemplate?: string | null) => { const generateUsername = () => {
const randomUsername = alphaNumericNanoId(32); // Username must start with an ascii letter, so we prepend the username with "inf-" return alphaNumericNanoId(32);
if (!usernameTemplate) return randomUsername;
return handlebars.compile(usernameTemplate)({
randomUsername,
unixTimestamp: Math.floor(Date.now() / 100)
});
}; };
export const CassandraProvider = (): TDynamicProviderFns => { export const CassandraProvider = (): TDynamicProviderFns => {
@ -75,12 +69,11 @@ export const CassandraProvider = (): TDynamicProviderFns => {
return isConnected; return isConnected;
}; };
const create = async (data: { inputs: unknown; expireAt: number; usernameTemplate?: string | null }) => { const create = async (inputs: unknown, expireAt: number) => {
const { inputs, expireAt, usernameTemplate } = data;
const providerInputs = await validateProviderInputs(inputs); const providerInputs = await validateProviderInputs(inputs);
const client = await $getClient(providerInputs); const client = await $getClient(providerInputs);
const username = generateUsername(usernameTemplate); const username = generateUsername();
const password = generatePassword(); const password = generatePassword();
const { keyspace } = providerInputs; const { keyspace } = providerInputs;
const expiration = new Date(expireAt).toISOString(); const expiration = new Date(expireAt).toISOString();

View File

@ -1,5 +1,4 @@
import { Client as ElasticSearchClient } from "@elastic/elasticsearch"; import { Client as ElasticSearchClient } from "@elastic/elasticsearch";
import handlebars from "handlebars";
import { customAlphabet } from "nanoid"; import { customAlphabet } from "nanoid";
import { z } from "zod"; import { z } from "zod";
@ -13,14 +12,8 @@ const generatePassword = () => {
return customAlphabet(charset, 64)(); return customAlphabet(charset, 64)();
}; };
const generateUsername = (usernameTemplate?: string | null) => { const generateUsername = () => {
const randomUsername = alphaNumericNanoId(32); // Username must start with an ascii letter, so we prepend the username with "inf-" return alphaNumericNanoId(32);
if (!usernameTemplate) return randomUsername;
return handlebars.compile(usernameTemplate)({
randomUsername,
unixTimestamp: Math.floor(Date.now() / 100)
});
}; };
export const ElasticSearchProvider = (): TDynamicProviderFns => { export const ElasticSearchProvider = (): TDynamicProviderFns => {
@ -71,12 +64,11 @@ export const ElasticSearchProvider = (): TDynamicProviderFns => {
return infoResponse; return infoResponse;
}; };
const create = async (data: { inputs: unknown; usernameTemplate?: string | null }) => { const create = async (inputs: unknown) => {
const { inputs, usernameTemplate } = data;
const providerInputs = await validateProviderInputs(inputs); const providerInputs = await validateProviderInputs(inputs);
const connection = await $getClient(providerInputs); const connection = await $getClient(providerInputs);
const username = generateUsername(usernameTemplate); const username = generateUsername();
const password = generatePassword(); const password = generatePassword();
await connection.security.putUser({ await connection.security.putUser({

View File

@ -116,7 +116,7 @@ export const KubernetesProvider = ({ gatewayService }: TKubernetesProviderDTO):
} }
}; };
const create = async ({ inputs, expireAt }: { inputs: unknown; expireAt: number }) => { const create = async (inputs: unknown, expireAt: number) => {
const providerInputs = await validateProviderInputs(inputs); const providerInputs = await validateProviderInputs(inputs);
const tokenRequestCallback = async (host: string, port: number) => { const tokenRequestCallback = async (host: string, port: number) => {

View File

@ -22,14 +22,8 @@ const encodePassword = (password?: string) => {
return base64Password; return base64Password;
}; };
const generateUsername = (usernameTemplate?: string | null) => { const generateUsername = () => {
const randomUsername = alphaNumericNanoId(32); // Username must start with an ascii letter, so we prepend the username with "inf-" return alphaNumericNanoId(20);
if (!usernameTemplate) return randomUsername;
return handlebars.compile(usernameTemplate)({
randomUsername,
unixTimestamp: Math.floor(Date.now() / 100)
});
}; };
const generateLDIF = ({ const generateLDIF = ({
@ -196,8 +190,7 @@ export const LdapProvider = (): TDynamicProviderFns => {
return dnArray; return dnArray;
}; };
const create = async (data: { inputs: unknown; usernameTemplate?: string | null }) => { const create = async (inputs: unknown) => {
const { inputs, usernameTemplate } = data;
const providerInputs = await validateProviderInputs(inputs); const providerInputs = await validateProviderInputs(inputs);
const client = await $getClient(providerInputs); const client = await $getClient(providerInputs);
@ -224,7 +217,7 @@ export const LdapProvider = (): TDynamicProviderFns => {
}); });
} }
} else { } else {
const username = generateUsername(usernameTemplate); const username = generateUsername();
const password = generatePassword(); const password = generatePassword();
const generatedLdif = generateLDIF({ username, password, ldifTemplate: providerInputs.creationLdif }); const generatedLdif = generateLDIF({ username, password, ldifTemplate: providerInputs.creationLdif });

View File

@ -360,11 +360,7 @@ export const DynamicSecretProviderSchema = z.discriminatedUnion("type", [
]); ]);
export type TDynamicProviderFns = { export type TDynamicProviderFns = {
create: (arg: { create: (inputs: unknown, expireAt: number) => Promise<{ entityId: string; data: unknown }>;
inputs: unknown;
expireAt: number;
usernameTemplate?: string | null;
}) => Promise<{ entityId: string; data: unknown }>;
validateConnection: (inputs: unknown) => Promise<boolean>; validateConnection: (inputs: unknown) => Promise<boolean>;
validateProviderInputs: (inputs: object) => Promise<unknown>; validateProviderInputs: (inputs: object) => Promise<unknown>;
revoke: (inputs: unknown, entityId: string) => Promise<{ entityId: string }>; revoke: (inputs: unknown, entityId: string) => Promise<{ entityId: string }>;

View File

@ -1,5 +1,4 @@
import axios, { AxiosError } from "axios"; import axios, { AxiosError } from "axios";
import handlebars from "handlebars";
import { customAlphabet } from "nanoid"; import { customAlphabet } from "nanoid";
import { z } from "zod"; import { z } from "zod";
@ -13,14 +12,8 @@ const generatePassword = (size = 48) => {
return customAlphabet(charset, 48)(size); return customAlphabet(charset, 48)(size);
}; };
const generateUsername = (usernameTemplate?: string | null) => { const generateUsername = () => {
const randomUsername = alphaNumericNanoId(32); return alphaNumericNanoId(32);
if (!usernameTemplate) return randomUsername;
return handlebars.compile(usernameTemplate)({
randomUsername,
unixTimestamp: Math.floor(Date.now() / 100)
});
}; };
export const MongoAtlasProvider = (): TDynamicProviderFns => { export const MongoAtlasProvider = (): TDynamicProviderFns => {
@ -64,12 +57,11 @@ export const MongoAtlasProvider = (): TDynamicProviderFns => {
return isConnected; return isConnected;
}; };
const create = async (data: { inputs: unknown; expireAt: number; usernameTemplate?: string | null }) => { const create = async (inputs: unknown, expireAt: number) => {
const { inputs, expireAt, usernameTemplate } = data;
const providerInputs = await validateProviderInputs(inputs); const providerInputs = await validateProviderInputs(inputs);
const client = await $getClient(providerInputs); const client = await $getClient(providerInputs);
const username = generateUsername(usernameTemplate); const username = generateUsername();
const password = generatePassword(); const password = generatePassword();
const expiration = new Date(expireAt).toISOString(); const expiration = new Date(expireAt).toISOString();
await client({ await client({

View File

@ -1,4 +1,3 @@
import handlebars from "handlebars";
import { MongoClient } from "mongodb"; import { MongoClient } from "mongodb";
import { customAlphabet } from "nanoid"; import { customAlphabet } from "nanoid";
import { z } from "zod"; import { z } from "zod";
@ -13,14 +12,8 @@ const generatePassword = (size = 48) => {
return customAlphabet(charset, 48)(size); return customAlphabet(charset, 48)(size);
}; };
const generateUsername = (usernameTemplate?: string | null) => { const generateUsername = () => {
const randomUsername = alphaNumericNanoId(32); return alphaNumericNanoId(32);
if (!usernameTemplate) return randomUsername;
return handlebars.compile(usernameTemplate)({
randomUsername,
unixTimestamp: Math.floor(Date.now() / 100)
});
}; };
export const MongoDBProvider = (): TDynamicProviderFns => { export const MongoDBProvider = (): TDynamicProviderFns => {
@ -60,12 +53,11 @@ export const MongoDBProvider = (): TDynamicProviderFns => {
return isConnected; return isConnected;
}; };
const create = async (data: { inputs: unknown; usernameTemplate?: string | null }) => { const create = async (inputs: unknown) => {
const { inputs, usernameTemplate } = data;
const providerInputs = await validateProviderInputs(inputs); const providerInputs = await validateProviderInputs(inputs);
const client = await $getClient(providerInputs); const client = await $getClient(providerInputs);
const username = generateUsername(usernameTemplate); const username = generateUsername();
const password = generatePassword(); const password = generatePassword();
const db = client.db(providerInputs.database); const db = client.db(providerInputs.database);

View File

@ -1,5 +1,4 @@
import axios, { Axios } from "axios"; import axios, { Axios } from "axios";
import handlebars from "handlebars";
import https from "https"; import https from "https";
import { customAlphabet } from "nanoid"; import { customAlphabet } from "nanoid";
import { z } from "zod"; import { z } from "zod";
@ -15,14 +14,8 @@ const generatePassword = () => {
return customAlphabet(charset, 64)(); return customAlphabet(charset, 64)();
}; };
const generateUsername = (usernameTemplate?: string | null) => { const generateUsername = () => {
const randomUsername = alphaNumericNanoId(32); // Username must start with an ascii letter, so we prepend the username with "inf-" return alphaNumericNanoId(32);
if (!usernameTemplate) return randomUsername;
return handlebars.compile(usernameTemplate)({
randomUsername,
unixTimestamp: Math.floor(Date.now() / 100)
});
}; };
type TCreateRabbitMQUser = { type TCreateRabbitMQUser = {
@ -117,12 +110,11 @@ export const RabbitMqProvider = (): TDynamicProviderFns => {
return infoResponse; return infoResponse;
}; };
const create = async (data: { inputs: unknown; usernameTemplate?: string | null }) => { const create = async (inputs: unknown) => {
const { inputs, usernameTemplate } = data;
const providerInputs = await validateProviderInputs(inputs); const providerInputs = await validateProviderInputs(inputs);
const connection = await $getClient(providerInputs); const connection = await $getClient(providerInputs);
const username = generateUsername(usernameTemplate); const username = generateUsername();
const password = generatePassword(); const password = generatePassword();
await createRabbitMqUser({ await createRabbitMqUser({

View File

@ -15,14 +15,8 @@ const generatePassword = () => {
return customAlphabet(charset, 64)(); return customAlphabet(charset, 64)();
}; };
const generateUsername = (usernameTemplate?: string | null) => { const generateUsername = () => {
const randomUsername = alphaNumericNanoId(32); // Username must start with an ascii letter, so we prepend the username with "inf-" return alphaNumericNanoId(32);
if (!usernameTemplate) return randomUsername;
return handlebars.compile(usernameTemplate)({
randomUsername,
unixTimestamp: Math.floor(Date.now() / 100)
});
}; };
const executeTransactions = async (connection: Redis, commands: string[]): Promise<(string | null)[] | null> => { const executeTransactions = async (connection: Redis, commands: string[]): Promise<(string | null)[] | null> => {
@ -121,12 +115,11 @@ export const RedisDatabaseProvider = (): TDynamicProviderFns => {
return pingResponse; return pingResponse;
}; };
const create = async (data: { inputs: unknown; expireAt: number; usernameTemplate?: string | null }) => { const create = async (inputs: unknown, expireAt: number) => {
const { inputs, expireAt, usernameTemplate } = data;
const providerInputs = await validateProviderInputs(inputs); const providerInputs = await validateProviderInputs(inputs);
const connection = await $getClient(providerInputs); const connection = await $getClient(providerInputs);
const username = generateUsername(usernameTemplate); const username = generateUsername();
const password = generatePassword(); const password = generatePassword();
const expiration = new Date(expireAt).toISOString(); const expiration = new Date(expireAt).toISOString();

View File

@ -15,14 +15,8 @@ const generatePassword = (size = 48) => {
return customAlphabet(charset, 48)(size); return customAlphabet(charset, 48)(size);
}; };
const generateUsername = (usernameTemplate?: string | null) => { const generateUsername = () => {
const randomUsername = `inf_${alphaNumericNanoId(25)}`; // Username must start with an ascii letter, so we prepend the username with "inf-" return alphaNumericNanoId(25);
if (!usernameTemplate) return randomUsername;
return handlebars.compile(usernameTemplate)({
randomUsername,
unixTimestamp: Math.floor(Date.now() / 100)
});
}; };
enum SapCommands { enum SapCommands {
@ -87,12 +81,11 @@ export const SapAseProvider = (): TDynamicProviderFns => {
return true; return true;
}; };
const create = async (data: { inputs: unknown; usernameTemplate?: string | null }) => { const create = async (inputs: unknown) => {
const { inputs, usernameTemplate } = data;
const providerInputs = await validateProviderInputs(inputs); const providerInputs = await validateProviderInputs(inputs);
const username = generateUsername(usernameTemplate); const username = `inf_${generateUsername()}`;
const password = generatePassword(); const password = `${generatePassword()}`;
const client = await $getClient(providerInputs); const client = await $getClient(providerInputs);
const masterClient = await $getClient(providerInputs, true); const masterClient = await $getClient(providerInputs, true);

View File

@ -21,14 +21,8 @@ const generatePassword = (size = 48) => {
return customAlphabet(charset, 48)(size); return customAlphabet(charset, 48)(size);
}; };
const generateUsername = (usernameTemplate?: string | null) => { const generateUsername = () => {
const randomUsername = alphaNumericNanoId(32); // Username must start with an ascii letter, so we prepend the username with "inf-" return alphaNumericNanoId(32);
if (!usernameTemplate) return randomUsername;
return handlebars.compile(usernameTemplate)({
randomUsername,
unixTimestamp: Math.floor(Date.now() / 100)
});
}; };
export const SapHanaProvider = (): TDynamicProviderFns => { export const SapHanaProvider = (): TDynamicProviderFns => {
@ -97,11 +91,10 @@ export const SapHanaProvider = (): TDynamicProviderFns => {
return testResult; return testResult;
}; };
const create = async (data: { inputs: unknown; expireAt: number; usernameTemplate?: string | null }) => { const create = async (inputs: unknown, expireAt: number) => {
const { inputs, expireAt, usernameTemplate } = data;
const providerInputs = await validateProviderInputs(inputs); const providerInputs = await validateProviderInputs(inputs);
const username = generateUsername(usernameTemplate); const username = generateUsername();
const password = generatePassword(); const password = generatePassword();
const expiration = new Date(expireAt).toISOString(); const expiration = new Date(expireAt).toISOString();

View File

@ -17,14 +17,8 @@ const generatePassword = (size = 48) => {
return customAlphabet(charset, 48)(size); return customAlphabet(charset, 48)(size);
}; };
const generateUsername = (usernameTemplate?: string | null) => { const generateUsername = () => {
const randomUsername = `infisical_${alphaNumericNanoId(32)}`; // Username must start with an ascii letter, so we prepend the username with "inf-" return `infisical_${alphaNumericNanoId(32)}`; // username must start with alpha character, hence prefix
if (!usernameTemplate) return randomUsername;
return handlebars.compile(usernameTemplate)({
randomUsername,
unixTimestamp: Math.floor(Date.now() / 100)
});
}; };
const getDaysToExpiry = (expiryDate: Date) => { const getDaysToExpiry = (expiryDate: Date) => {
@ -88,13 +82,12 @@ export const SnowflakeProvider = (): TDynamicProviderFns => {
return isValidConnection; return isValidConnection;
}; };
const create = async (data: { inputs: unknown; expireAt: number; usernameTemplate?: string | null }) => { const create = async (inputs: unknown, expireAt: number) => {
const { inputs, expireAt, usernameTemplate } = data;
const providerInputs = await validateProviderInputs(inputs); const providerInputs = await validateProviderInputs(inputs);
const client = await $getClient(providerInputs); const client = await $getClient(providerInputs);
const username = generateUsername(usernameTemplate); const username = generateUsername();
const password = generatePassword(); const password = generatePassword();
try { try {

View File

@ -104,21 +104,11 @@ const generatePassword = (provider: SqlProviders, requirements?: PasswordRequire
} }
}; };
const generateUsername = (provider: SqlProviders, usernameTemplate?: string | null) => { const generateUsername = (provider: SqlProviders) => {
let randomUsername = "";
// For oracle, the client assumes everything is upper case when not using quotes around the password // For oracle, the client assumes everything is upper case when not using quotes around the password
if (provider === SqlProviders.Oracle) { if (provider === SqlProviders.Oracle) return alphaNumericNanoId(32).toUpperCase();
randomUsername = alphaNumericNanoId(32).toUpperCase();
} else {
randomUsername = alphaNumericNanoId(32);
}
if (!usernameTemplate) return randomUsername;
return handlebars.compile(usernameTemplate)({ return alphaNumericNanoId(32);
randomUsername,
unixTimestamp: Math.floor(Date.now() / 100)
});
}; };
type TSqlDatabaseProviderDTO = { type TSqlDatabaseProviderDTO = {
@ -220,12 +210,9 @@ export const SqlDatabaseProvider = ({ gatewayService }: TSqlDatabaseProviderDTO)
return isConnected; return isConnected;
}; };
const create = async (data: { inputs: unknown; expireAt: number; usernameTemplate?: string | null }) => { const create = async (inputs: unknown, expireAt: number) => {
const { inputs, expireAt, usernameTemplate } = data;
const providerInputs = await validateProviderInputs(inputs); const providerInputs = await validateProviderInputs(inputs);
const username = generateUsername(providerInputs.client, usernameTemplate); const username = generateUsername(providerInputs.client);
const password = generatePassword(providerInputs.client, providerInputs.passwordRequirements); const password = generatePassword(providerInputs.client, providerInputs.passwordRequirements);
const gatewayCallback = async (host = providerInputs.host, port = providerInputs.port) => { const gatewayCallback = async (host = providerInputs.host, port = providerInputs.port) => {
const db = await $getClient({ ...providerInputs, port, host }); const db = await $getClient({ ...providerInputs, port, host });

View File

@ -44,7 +44,7 @@ const createQuicConnection = async (
if (!certs || certs.length === 0) return quic.native.CryptoError.CertificateRequired; if (!certs || certs.length === 0) return quic.native.CryptoError.CertificateRequired;
const serverCertificate = new crypto.X509Certificate(Buffer.from(certs[0])); const serverCertificate = new crypto.X509Certificate(Buffer.from(certs[0]));
const caCertificate = new crypto.X509Certificate(tlsOptions.ca); const caCertificate = new crypto.X509Certificate(tlsOptions.ca);
const isValidServerCertificate = serverCertificate.verify(caCertificate.publicKey); const isValidServerCertificate = serverCertificate.checkIssued(caCertificate);
if (!isValidServerCertificate) return quic.native.CryptoError.BadCertificate; if (!isValidServerCertificate) return quic.native.CryptoError.BadCertificate;
const subjectDetails = parseSubjectDetails(serverCertificate.subject); const subjectDetails = parseSubjectDetails(serverCertificate.subject);

View File

@ -19,15 +19,3 @@ export const validateHandlebarTemplate = (templateName: string, template: string
throw new BadRequestError({ message: `Template sanitization failed: ${templateName}` }); throw new BadRequestError({ message: `Template sanitization failed: ${templateName}` });
}); });
}; };
export const isValidHandleBarTemplate = (template: string, dto: SanitizationArg) => {
const parsedAst = handlebars.parse(template);
return parsedAst.body.every((el) => {
if (el.type === "ContentStatement") return true;
if (el.type === "MustacheStatement" && "path" in el) {
const { path } = el as { type: "MustacheStatement"; path: { type: "PathExpression"; original: string } };
if (path.type === "PathExpression" && dto?.allowedExpressions?.(path.original)) return true;
}
return false;
});
};

View File

@ -741,14 +741,12 @@ export const registerRoutes = async (
userAliasDAL, userAliasDAL,
identityTokenAuthDAL, identityTokenAuthDAL,
identityAccessTokenDAL, identityAccessTokenDAL,
orgMembershipDAL,
identityOrgMembershipDAL, identityOrgMembershipDAL,
authService: loginService, authService: loginService,
serverCfgDAL: superAdminDAL, serverCfgDAL: superAdminDAL,
kmsRootConfigDAL, kmsRootConfigDAL,
orgService, orgService,
keyStore, keyStore,
orgDAL,
licenseService, licenseService,
kmsService, kmsService,
microsoftTeamsService, microsoftTeamsService,

View File

@ -235,9 +235,11 @@ export const SanitizedDynamicSecretSchema = DynamicSecretsSchema.omit({
inputIV: true, inputIV: true,
inputTag: true, inputTag: true,
algorithm: true algorithm: true
}).extend({ }).merge(
z.object({
metadata: ResourceMetadataSchema.optional() metadata: ResourceMetadataSchema.optional()
}); })
);
export const SanitizedAuditLogStreamSchema = z.object({ export const SanitizedAuditLogStreamSchema = z.object({
id: z.string(), id: z.string(),

View File

@ -1,13 +1,7 @@
import DOMPurify from "isomorphic-dompurify"; import DOMPurify from "isomorphic-dompurify";
import { z } from "zod"; import { z } from "zod";
import { import { IdentitiesSchema, OrganizationsSchema, SuperAdminSchema, UsersSchema } from "@app/db/schemas";
IdentitiesSchema,
OrganizationsSchema,
OrgMembershipsSchema,
SuperAdminSchema,
UsersSchema
} from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
import { invalidateCacheLimit, readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { invalidateCacheLimit, readLimit, writeLimit } from "@app/server/config/rateLimiter";
@ -167,129 +161,6 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
} }
}); });
server.route({
method: "GET",
url: "/organization-management/organizations",
config: {
rateLimit: readLimit
},
schema: {
querystring: z.object({
searchTerm: z.string().default(""),
offset: z.coerce.number().default(0),
limit: z.coerce.number().max(100).default(20)
}),
response: {
200: z.object({
organizations: OrganizationsSchema.extend({
members: z
.object({
user: z.object({
id: z.string(),
email: z.string().nullish(),
username: z.string(),
firstName: z.string().nullish(),
lastName: z.string().nullish()
}),
membershipId: z.string(),
role: z.string(),
roleId: z.string().nullish()
})
.array(),
projects: z
.object({
name: z.string(),
id: z.string(),
slug: z.string(),
createdAt: z.date()
})
.array()
}).array()
})
}
},
onRequest: (req, res, done) => {
verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN])(req, res, () => {
verifySuperAdmin(req, res, done);
});
},
handler: async (req) => {
const organizations = await server.services.superAdmin.getOrganizations({
...req.query
});
return {
organizations
};
}
});
server.route({
method: "DELETE",
url: "/organization-management/organizations/:organizationId/memberships/:membershipId",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
organizationId: z.string(),
membershipId: z.string()
}),
response: {
200: z.object({
organizationMembership: OrgMembershipsSchema
})
}
},
onRequest: (req, res, done) => {
verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN])(req, res, () => {
verifySuperAdmin(req, res, done);
});
},
handler: async (req) => {
const organizationMembership = await server.services.superAdmin.deleteOrganizationMembership(
req.params.organizationId,
req.params.membershipId,
req.permission.id,
req.permission.type
);
return {
organizationMembership
};
}
});
server.route({
method: "DELETE",
url: "/organization-management/organizations/:organizationId",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
organizationId: z.string()
}),
response: {
200: z.object({
organization: OrganizationsSchema
})
}
},
onRequest: (req, res, done) => {
verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN])(req, res, () => {
verifySuperAdmin(req, res, done);
});
},
handler: async (req) => {
const organization = await server.services.superAdmin.deleteOrganization(req.params.organizationId);
return {
organization
};
}
});
server.route({ server.route({
method: "GET", method: "GET",
url: "/identity-management/identities", url: "/identity-management/identities",

View File

@ -196,10 +196,8 @@ export const orgAdminServiceFactory = ({
.filter( .filter(
(member) => member.roles.some((role) => role.role === ProjectMembershipRole.Admin) && member.userId !== actorId (member) => member.roles.some((role) => role.role === ProjectMembershipRole.Admin) && member.userId !== actorId
) )
.map((el) => el.user.email!) .map((el) => el.user.email!);
.filter(Boolean);
if (filteredProjectMembers.length) {
await smtpService.sendMail({ await smtpService.sendMail({
template: SmtpTemplates.OrgAdminProjectDirectAccess, template: SmtpTemplates.OrgAdminProjectDirectAccess,
recipients: filteredProjectMembers, recipients: filteredProjectMembers,
@ -209,7 +207,6 @@ export const orgAdminServiceFactory = ({
email: projectMembers.find((el) => el.userId === actorId)?.user?.username email: projectMembers.find((el) => el.userId === actorId)?.user?.username
} }
}); });
}
return { isExistingMember: false, membership: updatedMembership }; return { isExistingMember: false, membership: updatedMembership };
}; };

View File

@ -2,7 +2,6 @@ import { Knex } from "knex";
import { TDbClient } from "@app/db"; import { TDbClient } from "@app/db";
import { import {
OrganizationsSchema,
OrgMembershipRole, OrgMembershipRole,
TableName, TableName,
TOrganizations, TOrganizations,
@ -13,15 +12,7 @@ import {
TUserEncryptionKeys TUserEncryptionKeys
} from "@app/db/schemas"; } from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors"; import { DatabaseError } from "@app/lib/errors";
import { import { buildFindFilter, ormify, selectAllTableCols, TFindFilter, TFindOpt, withTransaction } from "@app/lib/knex";
buildFindFilter,
ormify,
selectAllTableCols,
sqlNestRelationships,
TFindFilter,
TFindOpt,
withTransaction
} from "@app/lib/knex";
import { generateKnexQueryFromScim } from "@app/lib/knex/scim"; import { generateKnexQueryFromScim } from "@app/lib/knex/scim";
import { OrgAuthMethod } from "./org-types"; import { OrgAuthMethod } from "./org-types";
@ -31,110 +22,6 @@ export type TOrgDALFactory = ReturnType<typeof orgDALFactory>;
export const orgDALFactory = (db: TDbClient) => { export const orgDALFactory = (db: TDbClient) => {
const orgOrm = ormify(db, TableName.Organization); const orgOrm = ormify(db, TableName.Organization);
const findOrganizationsByFilter = async ({
limit,
offset,
searchTerm,
sortBy
}: {
limit: number;
offset: number;
searchTerm: string;
sortBy?: keyof TOrganizations;
}) => {
try {
const query = db.replicaNode()(TableName.Organization);
// Build the subquery for limited organization IDs
const orgSubquery = db.replicaNode().select("id").from(TableName.Organization);
if (searchTerm) {
void orgSubquery.where((qb) => {
void qb.whereILike(`${TableName.Organization}.name`, `%${searchTerm}%`);
});
}
if (sortBy) {
void orgSubquery.orderBy(sortBy);
}
void orgSubquery.limit(limit).offset(offset);
// Main query with joins, limited to the subquery results
const docs = await query
.whereIn(`${TableName.Organization}.id`, orgSubquery)
.leftJoin(TableName.Project, `${TableName.Organization}.id`, `${TableName.Project}.orgId`)
.leftJoin(TableName.OrgMembership, `${TableName.Organization}.id`, `${TableName.OrgMembership}.orgId`)
.leftJoin(TableName.Users, `${TableName.OrgMembership}.userId`, `${TableName.Users}.id`)
.leftJoin(TableName.OrgRoles, `${TableName.OrgMembership}.roleId`, `${TableName.OrgRoles}.id`)
.where((qb) => {
void qb.where(`${TableName.Users}.isGhost`, false).orWhereNull(`${TableName.Users}.id`);
})
.select(selectAllTableCols(TableName.Organization))
.select(db.ref("name").withSchema(TableName.Project).as("projectName"))
.select(db.ref("id").withSchema(TableName.Project).as("projectId"))
.select(db.ref("slug").withSchema(TableName.Project).as("projectSlug"))
.select(db.ref("createdAt").withSchema(TableName.Project).as("projectCreatedAt"))
.select(db.ref("email").withSchema(TableName.Users).as("userEmail"))
.select(db.ref("username").withSchema(TableName.Users).as("username"))
.select(db.ref("firstName").withSchema(TableName.Users).as("firstName"))
.select(db.ref("lastName").withSchema(TableName.Users).as("lastName"))
.select(db.ref("id").withSchema(TableName.Users).as("userId"))
.select(db.ref("id").withSchema(TableName.OrgMembership).as("orgMembershipId"))
.select(db.ref("role").withSchema(TableName.OrgMembership).as("orgMembershipRole"))
.select(db.ref("roleId").withSchema(TableName.OrgMembership).as("orgMembershipRoleId"))
.select(db.ref("name").withSchema(TableName.OrgRoles).as("orgMembershipRoleName"));
const formattedDocs = sqlNestRelationships({
data: docs,
key: "id",
parentMapper: (data) => OrganizationsSchema.parse(data),
childrenMapper: [
{
key: "projectId",
label: "projects" as const,
mapper: ({ projectId, projectName, projectSlug, projectCreatedAt }) => ({
id: projectId,
name: projectName,
slug: projectSlug,
createdAt: projectCreatedAt
})
},
{
key: "userId",
label: "members" as const,
mapper: ({
userId,
userEmail,
username,
firstName,
lastName,
orgMembershipId,
orgMembershipRole,
orgMembershipRoleName,
orgMembershipRoleId
}) => ({
user: {
id: userId,
email: userEmail,
username,
firstName,
lastName
},
membershipId: orgMembershipId,
role: orgMembershipRoleName || orgMembershipRole, // custom role name or pre-defined role name
roleId: orgMembershipRoleId
})
}
]
});
return formattedDocs;
} catch (error) {
throw new DatabaseError({ error, name: "Find organizations by filter" });
}
};
const findOrgById = async (orgId: string) => { const findOrgById = async (orgId: string) => {
try { try {
const org = (await db const org = (await db
@ -620,7 +507,6 @@ export const orgDALFactory = (db: TDbClient) => {
findOrgById, findOrgById,
findOrgBySlug, findOrgBySlug,
findAllOrgsByUserId, findAllOrgsByUserId,
findOrganizationsByFilter,
ghostUserExists, ghostUserExists,
findOrgMembersByUsername, findOrgMembersByUsername,
findOrgMembersByRole, findOrgMembersByRole,

View File

@ -11,7 +11,7 @@ import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { TIdentityDALFactory } from "@app/services/identity/identity-dal"; import { TIdentityDALFactory } from "@app/services/identity/identity-dal";
import { TAuthLoginFactory } from "../auth/auth-login-service"; import { TAuthLoginFactory } from "../auth/auth-login-service";
import { ActorType, AuthMethod, AuthTokenType } from "../auth/auth-type"; import { AuthMethod, AuthTokenType } from "../auth/auth-type";
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal"; import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal"; import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types"; import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types";
@ -21,9 +21,7 @@ import { TKmsRootConfigDALFactory } from "../kms/kms-root-config-dal";
import { TKmsServiceFactory } from "../kms/kms-service"; import { TKmsServiceFactory } from "../kms/kms-service";
import { RootKeyEncryptionStrategy } from "../kms/kms-types"; import { RootKeyEncryptionStrategy } from "../kms/kms-types";
import { TMicrosoftTeamsServiceFactory } from "../microsoft-teams/microsoft-teams-service"; import { TMicrosoftTeamsServiceFactory } from "../microsoft-teams/microsoft-teams-service";
import { TOrgDALFactory } from "../org/org-dal";
import { TOrgServiceFactory } from "../org/org-service"; import { TOrgServiceFactory } from "../org/org-service";
import { TOrgMembershipDALFactory } from "../org-membership/org-membership-dal";
import { TUserDALFactory } from "../user/user-dal"; import { TUserDALFactory } from "../user/user-dal";
import { TUserAliasDALFactory } from "../user-alias/user-alias-dal"; import { TUserAliasDALFactory } from "../user-alias/user-alias-dal";
import { UserAliasType } from "../user-alias/user-alias-types"; import { UserAliasType } from "../user-alias/user-alias-types";
@ -35,8 +33,7 @@ import {
TAdminBootstrapInstanceDTO, TAdminBootstrapInstanceDTO,
TAdminGetIdentitiesDTO, TAdminGetIdentitiesDTO,
TAdminGetUsersDTO, TAdminGetUsersDTO,
TAdminSignUpDTO, TAdminSignUpDTO
TGetOrganizationsDTO
} from "./super-admin-types"; } from "./super-admin-types";
type TSuperAdminServiceFactoryDep = { type TSuperAdminServiceFactoryDep = {
@ -44,8 +41,6 @@ type TSuperAdminServiceFactoryDep = {
identityTokenAuthDAL: TIdentityTokenAuthDALFactory; identityTokenAuthDAL: TIdentityTokenAuthDALFactory;
identityAccessTokenDAL: TIdentityAccessTokenDALFactory; identityAccessTokenDAL: TIdentityAccessTokenDALFactory;
identityOrgMembershipDAL: TIdentityOrgDALFactory; identityOrgMembershipDAL: TIdentityOrgDALFactory;
orgDAL: TOrgDALFactory;
orgMembershipDAL: TOrgMembershipDALFactory;
serverCfgDAL: TSuperAdminDALFactory; serverCfgDAL: TSuperAdminDALFactory;
userDAL: TUserDALFactory; userDAL: TUserDALFactory;
userAliasDAL: Pick<TUserAliasDALFactory, "findOne">; userAliasDAL: Pick<TUserAliasDALFactory, "findOne">;
@ -78,8 +73,6 @@ export const superAdminServiceFactory = ({
serverCfgDAL, serverCfgDAL,
userDAL, userDAL,
identityDAL, identityDAL,
orgDAL,
orgMembershipDAL,
userAliasDAL, userAliasDAL,
authService, authService,
orgService, orgService,
@ -528,47 +521,6 @@ export const superAdminServiceFactory = ({
return updatedUser; return updatedUser;
}; };
const getOrganizations = async ({ offset, limit, searchTerm }: TGetOrganizationsDTO) => {
const organizations = await orgDAL.findOrganizationsByFilter({
offset,
searchTerm,
sortBy: "name",
limit
});
return organizations;
};
const deleteOrganization = async (organizationId: string) => {
const organization = await orgDAL.deleteById(organizationId);
return organization;
};
const deleteOrganizationMembership = async (
organizationId: string,
membershipId: string,
actorId: string,
actorType: ActorType
) => {
if (actorType === ActorType.USER) {
const orgMembership = await orgMembershipDAL.findById(membershipId);
if (!orgMembership) {
throw new NotFoundError({ name: "Organization Membership", message: "Organization membership not found" });
}
if (orgMembership.userId === actorId) {
throw new BadRequestError({
message: "You cannot remove yourself from the organization from the instance management panel."
});
}
}
const [organizationMembership] = await orgMembershipDAL.delete({
orgId: organizationId,
id: membershipId
});
return organizationMembership;
};
const getIdentities = async ({ offset, limit, searchTerm }: TAdminGetIdentitiesDTO) => { const getIdentities = async ({ offset, limit, searchTerm }: TAdminGetIdentitiesDTO) => {
const identities = await identityDAL.getIdentitiesByFilter({ const identities = await identityDAL.getIdentitiesByFilter({
limit, limit,
@ -711,9 +663,6 @@ export const superAdminServiceFactory = ({
deleteIdentitySuperAdminAccess, deleteIdentitySuperAdminAccess,
deleteUserSuperAdminAccess, deleteUserSuperAdminAccess,
invalidateCache, invalidateCache,
checkIfInvalidatingCache, checkIfInvalidatingCache
getOrganizations,
deleteOrganization,
deleteOrganizationMembership
}; };
}; };

View File

@ -35,12 +35,6 @@ export type TAdminGetIdentitiesDTO = {
searchTerm: string; searchTerm: string;
}; };
export type TGetOrganizationsDTO = {
offset: number;
limit: number;
searchTerm: string;
};
export enum LoginMethod { export enum LoginMethod {
EMAIL = "email", EMAIL = "email",
GOOGLE = "google", GOOGLE = "google",

View File

@ -5,8 +5,6 @@ go 1.23.0
toolchain go1.23.5 toolchain go1.23.5
require ( require (
github.com/BobuSumisu/aho-corasick v1.0.3
github.com/Masterminds/sprig/v3 v3.3.0
github.com/bradleyjkemp/cupaloy/v2 v2.8.0 github.com/bradleyjkemp/cupaloy/v2 v2.8.0
github.com/charmbracelet/lipgloss v0.9.1 github.com/charmbracelet/lipgloss v0.9.1
github.com/creack/pty v1.1.21 github.com/creack/pty v1.1.21
@ -21,10 +19,10 @@ require (
github.com/muesli/mango-cobra v1.2.0 github.com/muesli/mango-cobra v1.2.0
github.com/muesli/reflow v0.3.0 github.com/muesli/reflow v0.3.0
github.com/muesli/roff v0.1.0 github.com/muesli/roff v0.1.0
github.com/petar-dambovaliev/aho-corasick v0.0.0-20211021192214-5ab2d9280aa9
github.com/pion/dtls/v3 v3.0.4 github.com/pion/dtls/v3 v3.0.4
github.com/pion/logging v0.2.3 github.com/pion/logging v0.2.3
github.com/pion/turn/v4 v4.0.0 github.com/pion/turn/v4 v4.0.0
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
github.com/posthog/posthog-go v0.0.0-20221221115252-24dfed35d71a github.com/posthog/posthog-go v0.0.0-20221221115252-24dfed35d71a
github.com/quic-go/quic-go v0.50.0 github.com/quic-go/quic-go v0.50.0
github.com/rs/cors v1.11.0 github.com/rs/cors v1.11.0
@ -32,9 +30,7 @@ require (
github.com/spf13/cobra v1.6.1 github.com/spf13/cobra v1.6.1
github.com/spf13/viper v1.8.1 github.com/spf13/viper v1.8.1
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.10.0
github.com/wasilibs/go-re2 v1.10.0
golang.org/x/crypto v0.36.0 golang.org/x/crypto v0.36.0
golang.org/x/exp v0.0.0-20250228200357-dead58393ab7
golang.org/x/sys v0.31.0 golang.org/x/sys v0.31.0
golang.org/x/term v0.30.0 golang.org/x/term v0.30.0
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0
@ -47,8 +43,10 @@ require (
cloud.google.com/go/compute/metadata v0.4.0 // indirect cloud.google.com/go/compute/metadata v0.4.0 // indirect
cloud.google.com/go/iam v1.1.11 // indirect cloud.google.com/go/iam v1.1.11 // indirect
dario.cat/mergo v1.0.1 // indirect dario.cat/mergo v1.0.1 // indirect
github.com/BobuSumisu/aho-corasick v1.0.3 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.3.0 // indirect github.com/Masterminds/semver/v3 v3.3.0 // indirect
github.com/Masterminds/sprig/v3 v3.3.0 // indirect
github.com/alessio/shellescape v1.4.1 // indirect github.com/alessio/shellescape v1.4.1 // indirect
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef // indirect github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef // indirect
github.com/aws/aws-sdk-go-v2 v1.27.2 // indirect github.com/aws/aws-sdk-go-v2 v1.27.2 // indirect
@ -67,7 +65,7 @@ require (
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/chzyer/readline v1.5.1 // indirect github.com/chzyer/readline v1.5.1 // indirect
github.com/danieljoos/wincred v1.2.0 // indirect github.com/danieljoos/wincred v1.2.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dvsekhvalnov/jose2go v1.6.0 // indirect github.com/dvsekhvalnov/jose2go v1.6.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.4.9 // indirect github.com/fsnotify/fsnotify v1.4.9 // indirect
@ -107,15 +105,13 @@ require (
github.com/pion/stun/v3 v3.0.0 // indirect github.com/pion/stun/v3 v3.0.0 // indirect
github.com/pion/transport/v3 v3.0.7 // indirect github.com/pion/transport/v3 v3.0.7 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect
github.com/shopspring/decimal v1.4.0 // indirect github.com/shopspring/decimal v1.4.0 // indirect
github.com/spf13/afero v1.6.0 // indirect github.com/spf13/afero v1.6.0 // indirect
github.com/spf13/cast v1.7.0 // indirect github.com/spf13/cast v1.7.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/subosito/gotenv v1.2.0 // indirect github.com/subosito/gotenv v1.2.0 // indirect
github.com/tetratelabs/wazero v1.9.0 // indirect
github.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52 // indirect
github.com/wlynxg/anet v0.0.5 // indirect github.com/wlynxg/anet v0.0.5 // indirect
github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // indirect github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // indirect
go.mongodb.org/mongo-driver v1.10.0 // indirect go.mongodb.org/mongo-driver v1.10.0 // indirect
@ -126,6 +122,7 @@ require (
go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect
go.opentelemetry.io/otel/trace v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect
go.uber.org/mock v0.5.0 // indirect go.uber.org/mock v0.5.0 // indirect
golang.org/x/exp v0.0.0-20250228200357-dead58393ab7 // indirect
golang.org/x/mod v0.23.0 // indirect golang.org/x/mod v0.23.0 // indirect
golang.org/x/net v0.35.0 // indirect golang.org/x/net v0.35.0 // indirect
golang.org/x/oauth2 v0.21.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect

View File

@ -127,9 +127,8 @@ github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr
github.com/danieljoos/wincred v1.2.0 h1:ozqKHaLK0W/ii4KVbbvluM91W2H3Sh0BncbUNPS7jLE= github.com/danieljoos/wincred v1.2.0 h1:ozqKHaLK0W/ii4KVbbvluM91W2H3Sh0BncbUNPS7jLE=
github.com/danieljoos/wincred v1.2.0/go.mod h1:FzQLLMKBFdvu+osBrnFODiv32YGwCfx0SkRa/eYHgec= github.com/danieljoos/wincred v1.2.0/go.mod h1:FzQLLMKBFdvu+osBrnFODiv32YGwCfx0SkRa/eYHgec=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ= github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ=
github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI= github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI=
github.com/dvsekhvalnov/jose2go v1.6.0 h1:Y9gnSnP4qEI0+/uQkHvFXeD2PLPJeXEL+ySMEA2EjTY= github.com/dvsekhvalnov/jose2go v1.6.0 h1:Y9gnSnP4qEI0+/uQkHvFXeD2PLPJeXEL+ySMEA2EjTY=
@ -148,11 +147,11 @@ github.com/fatih/semgroup v1.2.0 h1:h/OLXwEM+3NNyAdZEpMiH1OzfplU09i2qXPVThGZvyg=
github.com/fatih/semgroup v1.2.0/go.mod h1:1KAD4iIYfXjE4U13B48VM4z9QUwV5Tt8O4rS879kgm8= github.com/fatih/semgroup v1.2.0/go.mod h1:1KAD4iIYfXjE4U13B48VM4z9QUwV5Tt8O4rS879kgm8=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gitleaks/go-gitdiff v0.8.0 h1:7aExTZm+K/M/EQKOyYcub8rIAdWK6ONxPGuRzxmWW+0=
github.com/gitleaks/go-gitdiff v0.8.0/go.mod h1:pKz0X4YzCKZs30BL+weqBIG7mx0jl4tF1uXV9ZyNvrA=
github.com/gitleaks/go-gitdiff v0.9.1 h1:ni6z6/3i9ODT685OLCTf+s/ERlWUNWQF4x1pvoNICw0= github.com/gitleaks/go-gitdiff v0.9.1 h1:ni6z6/3i9ODT685OLCTf+s/ERlWUNWQF4x1pvoNICw0=
github.com/gitleaks/go-gitdiff v0.9.1/go.mod h1:pKz0X4YzCKZs30BL+weqBIG7mx0jl4tF1uXV9ZyNvrA= github.com/gitleaks/go-gitdiff v0.9.1/go.mod h1:pKz0X4YzCKZs30BL+weqBIG7mx0jl4tF1uXV9ZyNvrA=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
@ -308,8 +307,6 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@ -376,6 +373,8 @@ github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlR
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ= github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ=
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/petar-dambovaliev/aho-corasick v0.0.0-20211021192214-5ab2d9280aa9 h1:lL+y4Xv20pVlCGyLzNHRC0I0rIHhIL1lTvHizoS/dU8=
github.com/petar-dambovaliev/aho-corasick v0.0.0-20211021192214-5ab2d9280aa9/go.mod h1:EHPiTAKtiFmrMldLUNswFwfZ2eJIYBHktdaUTZxYWRw=
github.com/pion/dtls/v3 v3.0.4 h1:44CZekewMzfrn9pmGrj5BNnTMDCFwr+6sLH+cCuLM7U= github.com/pion/dtls/v3 v3.0.4 h1:44CZekewMzfrn9pmGrj5BNnTMDCFwr+6sLH+cCuLM7U=
github.com/pion/dtls/v3 v3.0.4/go.mod h1:R373CsjxWqNPf6MEkfdy3aSe9niZvL/JaKlGeFphtMg= github.com/pion/dtls/v3 v3.0.4/go.mod h1:R373CsjxWqNPf6MEkfdy3aSe9niZvL/JaKlGeFphtMg=
github.com/pion/logging v0.2.3 h1:gHuf0zpoh1GW67Nr6Gj4cv5Z9ZscU7g/EaoC/Ke/igI= github.com/pion/logging v0.2.3 h1:gHuf0zpoh1GW67Nr6Gj4cv5Z9ZscU7g/EaoC/Ke/igI=
@ -386,15 +385,12 @@ github.com/pion/stun/v3 v3.0.0 h1:4h1gwhWLWuZWOJIJR9s2ferRO+W3zA/b6ijOI6mKzUw=
github.com/pion/stun/v3 v3.0.0/go.mod h1:HvCN8txt8mwi4FBvS3EmDghW6aQJ24T+y+1TKjB5jyU= github.com/pion/stun/v3 v3.0.0/go.mod h1:HvCN8txt8mwi4FBvS3EmDghW6aQJ24T+y+1TKjB5jyU=
github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0= github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0=
github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo= github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/posthog/posthog-go v0.0.0-20221221115252-24dfed35d71a h1:Ey0XWvrg6u6hyIn1Kd/jCCmL+bMv9El81tvuGBbxZGg= github.com/posthog/posthog-go v0.0.0-20221221115252-24dfed35d71a h1:Ey0XWvrg6u6hyIn1Kd/jCCmL+bMv9El81tvuGBbxZGg=
github.com/posthog/posthog-go v0.0.0-20221221115252-24dfed35d71a/go.mod h1:oa2sAs9tGai3VldabTV0eWejt/O4/OOD7azP8GaikqU= github.com/posthog/posthog-go v0.0.0-20221221115252-24dfed35d71a/go.mod h1:oa2sAs9tGai3VldabTV0eWejt/O4/OOD7azP8GaikqU=
@ -406,8 +402,6 @@ github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rs/cors v1.11.0 h1:0B9GE/r9Bc2UxRMMtymBkHTenPkHDv0CW4Y98GBY+po= github.com/rs/cors v1.11.0 h1:0B9GE/r9Bc2UxRMMtymBkHTenPkHDv0CW4Y98GBY+po=
github.com/rs/cors v1.11.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/cors v1.11.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
@ -426,6 +420,7 @@ github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIK
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY=
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
@ -456,15 +451,9 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I=
github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM=
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/wasilibs/go-re2 v1.10.0 h1:vQZEBYZOCA9jdBMmrO4+CvqyCj0x4OomXTJ4a5/urQ0=
github.com/wasilibs/go-re2 v1.10.0/go.mod h1:k+5XqO2bCJS+QpGOnqugyfwC04nw0jaglmjrrkG8U6o=
github.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52 h1:OvLBa8SqJnZ6P+mjlzc2K7PM22rRUPE1x32G9DTPrC4=
github.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52/go.mod h1:jMeV4Vpbi8osrE/pKUxRZkVaA0EX7NZN0A9/oRzgpgY=
github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU= github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
@ -672,7 +661,6 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=

View File

@ -12,6 +12,35 @@ import (
const USER_AGENT = "cli" const USER_AGENT = "cli"
const (
operationCallGetRawSecretsV3 = "CallGetRawSecretsV3"
operationCallGetEncryptedWorkspaceKey = "CallGetEncryptedWorkspaceKey"
operationCallGetServiceTokenDetails = "CallGetServiceTokenDetails"
operationCallLogin1V3 = "CallLogin1V3"
operationCallVerifyMfaToken = "CallVerifyMfaToken"
operationCallLogin2V3 = "CallLogin2V3"
operationCallGetAllOrganizations = "CallGetAllOrganizations"
operationCallSelectOrganization = "CallSelectOrganization"
operationCallGetAllWorkSpacesUserBelongsTo = "CallGetAllWorkSpacesUserBelongsTo"
operationCallGetProjectById = "CallGetProjectById"
operationCallIsAuthenticated = "CallIsAuthenticated"
operationCallGetNewAccessTokenWithRefreshToken = "CallGetNewAccessTokenWithRefreshToken"
operationCallGetFoldersV1 = "CallGetFoldersV1"
operationCallCreateFolderV1 = "CallCreateFolderV1"
operationCallDeleteFolderV1 = "CallDeleteFolderV1"
operationCallDeleteSecretsV3 = "CallDeleteSecretsV3"
operationCallCreateServiceToken = "CallCreateServiceToken"
operationCallUniversalAuthLogin = "CallUniversalAuthLogin"
operationCallMachineIdentityRefreshAccessToken = "CallMachineIdentityRefreshAccessToken"
operationCallFetchSingleSecretByName = "CallFetchSingleSecretByName"
operationCallCreateRawSecretsV3 = "CallCreateRawSecretsV3"
operationCallUpdateRawSecretsV3 = "CallUpdateRawSecretsV3"
operationCallRegisterGatewayIdentityV1 = "CallRegisterGatewayIdentityV1"
operationCallExchangeRelayCertV1 = "CallExchangeRelayCertV1"
operationCallGatewayHeartBeatV1 = "CallGatewayHeartBeatV1"
operationCallBootstrapInstance = "CallBootstrapInstance"
)
func CallGetEncryptedWorkspaceKey(httpClient *resty.Client, request GetEncryptedWorkspaceKeyRequest) (GetEncryptedWorkspaceKeyResponse, error) { func CallGetEncryptedWorkspaceKey(httpClient *resty.Client, request GetEncryptedWorkspaceKeyRequest) (GetEncryptedWorkspaceKeyResponse, error) {
endpoint := fmt.Sprintf("%v/v2/workspace/%v/encrypted-key", config.INFISICAL_URL, request.WorkspaceId) endpoint := fmt.Sprintf("%v/v2/workspace/%v/encrypted-key", config.INFISICAL_URL, request.WorkspaceId)
var result GetEncryptedWorkspaceKeyResponse var result GetEncryptedWorkspaceKeyResponse
@ -22,11 +51,11 @@ func CallGetEncryptedWorkspaceKey(httpClient *resty.Client, request GetEncrypted
Get(endpoint) Get(endpoint)
if err != nil { if err != nil {
return GetEncryptedWorkspaceKeyResponse{}, fmt.Errorf("CallGetEncryptedWorkspaceKey: Unable to complete api request [err=%s]", err) return GetEncryptedWorkspaceKeyResponse{}, NewGenericRequestError(operationCallGetEncryptedWorkspaceKey, err)
} }
if response.IsError() { if response.IsError() {
return GetEncryptedWorkspaceKeyResponse{}, fmt.Errorf("CallGetEncryptedWorkspaceKey: Unsuccessful response [%v %v] [status-code=%v]", response.Request.Method, response.Request.URL, response.StatusCode()) return GetEncryptedWorkspaceKeyResponse{}, NewAPIErrorWithResponse(operationCallGetEncryptedWorkspaceKey, response, nil)
} }
return result, nil return result, nil
@ -41,11 +70,11 @@ func CallGetServiceTokenDetailsV2(httpClient *resty.Client) (GetServiceTokenDeta
Get(fmt.Sprintf("%v/v2/service-token", config.INFISICAL_URL)) Get(fmt.Sprintf("%v/v2/service-token", config.INFISICAL_URL))
if err != nil { if err != nil {
return GetServiceTokenDetailsResponse{}, fmt.Errorf("CallGetServiceTokenDetails: Unable to complete api request [err=%s]", err) return GetServiceTokenDetailsResponse{}, NewGenericRequestError(operationCallGetServiceTokenDetails, err)
} }
if response.IsError() { if response.IsError() {
return GetServiceTokenDetailsResponse{}, fmt.Errorf("CallGetServiceTokenDetails: Unsuccessful response: [response=%s]", response) return GetServiceTokenDetailsResponse{}, NewAPIErrorWithResponse(operationCallGetServiceTokenDetails, response, nil)
} }
return tokenDetailsResponse, nil return tokenDetailsResponse, nil
@ -61,11 +90,11 @@ func CallLogin1V2(httpClient *resty.Client, request GetLoginOneV2Request) (GetLo
Post(fmt.Sprintf("%v/v3/auth/login1", config.INFISICAL_URL)) Post(fmt.Sprintf("%v/v3/auth/login1", config.INFISICAL_URL))
if err != nil { if err != nil {
return GetLoginOneV2Response{}, fmt.Errorf("CallLogin1V3: Unable to complete api request [err=%s]", err) return GetLoginOneV2Response{}, NewGenericRequestError(operationCallLogin1V3, err)
} }
if response.IsError() { if response.IsError() {
return GetLoginOneV2Response{}, fmt.Errorf("CallLogin1V3: Unsuccessful response: [response=%s]", response) return GetLoginOneV2Response{}, NewAPIErrorWithResponse(operationCallLogin1V3, response, nil)
} }
return loginOneV2Response, nil return loginOneV2Response, nil
@ -99,7 +128,7 @@ func CallVerifyMfaToken(httpClient *resty.Client, request VerifyMfaTokenRequest)
} }
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("CallVerifyMfaToken: Unable to complete api request [err=%s]", err) return nil, nil, NewGenericRequestError(operationCallVerifyMfaToken, err)
} }
if response.IsError() { if response.IsError() {
@ -135,11 +164,11 @@ func CallLogin2V2(httpClient *resty.Client, request GetLoginTwoV2Request) (GetLo
} }
if err != nil { if err != nil {
return GetLoginTwoV2Response{}, fmt.Errorf("CallLogin2V3: Unable to complete api request [err=%s]", err) return GetLoginTwoV2Response{}, NewGenericRequestError(operationCallLogin2V3, err)
} }
if response.IsError() { if response.IsError() {
return GetLoginTwoV2Response{}, fmt.Errorf("CallLogin2V3: Unsuccessful response: [response=%s]", response) return GetLoginTwoV2Response{}, NewAPIErrorWithResponse(operationCallLogin2V3, response, nil)
} }
return loginTwoV2Response, nil return loginTwoV2Response, nil
@ -154,11 +183,11 @@ func CallGetAllOrganizations(httpClient *resty.Client) (GetOrganizationsResponse
Get(fmt.Sprintf("%v/v1/organization", config.INFISICAL_URL)) Get(fmt.Sprintf("%v/v1/organization", config.INFISICAL_URL))
if err != nil { if err != nil {
return GetOrganizationsResponse{}, err return GetOrganizationsResponse{}, NewGenericRequestError(operationCallGetAllOrganizations, err)
} }
if response.IsError() { if response.IsError() {
return GetOrganizationsResponse{}, fmt.Errorf("CallGetAllOrganizations: Unsuccessful response: [response=%v]", response) return GetOrganizationsResponse{}, NewAPIErrorWithResponse(operationCallGetAllOrganizations, response, nil)
} }
return orgResponse, nil return orgResponse, nil
@ -175,11 +204,11 @@ func CallSelectOrganization(httpClient *resty.Client, request SelectOrganization
Post(fmt.Sprintf("%v/v3/auth/select-organization", config.INFISICAL_URL)) Post(fmt.Sprintf("%v/v3/auth/select-organization", config.INFISICAL_URL))
if err != nil { if err != nil {
return SelectOrganizationResponse{}, err return SelectOrganizationResponse{}, NewGenericRequestError(operationCallSelectOrganization, err)
} }
if response.IsError() { if response.IsError() {
return SelectOrganizationResponse{}, fmt.Errorf("CallSelectOrganization: Unsuccessful response: [response=%v]", response) return SelectOrganizationResponse{}, NewAPIErrorWithResponse(operationCallSelectOrganization, response, nil)
} }
return selectOrgResponse, nil return selectOrgResponse, nil
@ -214,11 +243,11 @@ func CallGetProjectById(httpClient *resty.Client, id string) (Project, error) {
Get(fmt.Sprintf("%v/v1/workspace/%s", config.INFISICAL_URL, id)) Get(fmt.Sprintf("%v/v1/workspace/%s", config.INFISICAL_URL, id))
if err != nil { if err != nil {
return Project{}, err return Project{}, NewGenericRequestError(operationCallGetProjectById, err)
} }
if response.IsError() { if response.IsError() {
return Project{}, fmt.Errorf("CallGetProjectById: Unsuccessful response: [response=%v]", response) return Project{}, NewAPIErrorWithResponse(operationCallGetProjectById, response, nil)
} }
return projectResponse.Project, nil return projectResponse.Project, nil
@ -237,7 +266,7 @@ func CallIsAuthenticated(httpClient *resty.Client) bool {
} }
if response.IsError() { if response.IsError() {
log.Debug().Msgf("CallIsAuthenticated: Unsuccessful response: [response=%v]", response) log.Debug().Msgf("%s: Unsuccessful response: [response=%v]", operationCallIsAuthenticated, response)
return false return false
} }
@ -257,11 +286,11 @@ func CallGetNewAccessTokenWithRefreshToken(httpClient *resty.Client, refreshToke
Post(fmt.Sprintf("%v/v1/auth/token", config.INFISICAL_URL)) Post(fmt.Sprintf("%v/v1/auth/token", config.INFISICAL_URL))
if err != nil { if err != nil {
return GetNewAccessTokenWithRefreshTokenResponse{}, err return GetNewAccessTokenWithRefreshTokenResponse{}, NewGenericRequestError(operationCallGetNewAccessTokenWithRefreshToken, err)
} }
if response.IsError() { if response.IsError() {
return GetNewAccessTokenWithRefreshTokenResponse{}, fmt.Errorf("CallGetNewAccessTokenWithRefreshToken: Unsuccessful response: [response=%v]", response) return GetNewAccessTokenWithRefreshTokenResponse{}, NewAPIErrorWithResponse(operationCallGetNewAccessTokenWithRefreshToken, response, nil)
} }
return newAccessToken, nil return newAccessToken, nil
@ -280,11 +309,11 @@ func CallGetFoldersV1(httpClient *resty.Client, request GetFoldersV1Request) (Ge
response, err := httpRequest.Get(fmt.Sprintf("%v/v1/folders", config.INFISICAL_URL)) response, err := httpRequest.Get(fmt.Sprintf("%v/v1/folders", config.INFISICAL_URL))
if err != nil { if err != nil {
return GetFoldersV1Response{}, fmt.Errorf("CallGetFoldersV1: Unable to complete api request [err=%v]", err) return GetFoldersV1Response{}, NewGenericRequestError(operationCallGetFoldersV1, err)
} }
if response.IsError() { if response.IsError() {
return GetFoldersV1Response{}, fmt.Errorf("CallGetFoldersV1: Unsuccessful [response=%s]", response) return GetFoldersV1Response{}, NewAPIErrorWithResponse(operationCallGetFoldersV1, response, nil)
} }
return foldersResponse, nil return foldersResponse, nil
@ -300,11 +329,11 @@ func CallCreateFolderV1(httpClient *resty.Client, request CreateFolderV1Request)
response, err := httpRequest.Post(fmt.Sprintf("%v/v1/folders", config.INFISICAL_URL)) response, err := httpRequest.Post(fmt.Sprintf("%v/v1/folders", config.INFISICAL_URL))
if err != nil { if err != nil {
return CreateFolderV1Response{}, fmt.Errorf("CallCreateFolderV1: Unable to complete api request [err=%s]", err) return CreateFolderV1Response{}, NewGenericRequestError(operationCallCreateFolderV1, err)
} }
if response.IsError() { if response.IsError() {
return CreateFolderV1Response{}, fmt.Errorf("CallCreateFolderV1: Unsuccessful [response=%s]", response.String()) return CreateFolderV1Response{}, NewAPIErrorWithResponse(operationCallCreateFolderV1, response, nil)
} }
return folderResponse, nil return folderResponse, nil
@ -321,11 +350,11 @@ func CallDeleteFolderV1(httpClient *resty.Client, request DeleteFolderV1Request)
response, err := httpRequest.Delete(fmt.Sprintf("%v/v1/folders/%v", config.INFISICAL_URL, request.FolderName)) response, err := httpRequest.Delete(fmt.Sprintf("%v/v1/folders/%v", config.INFISICAL_URL, request.FolderName))
if err != nil { if err != nil {
return DeleteFolderV1Response{}, fmt.Errorf("CallDeleteFolderV1: Unable to complete api request [err=%s]", err) return DeleteFolderV1Response{}, NewGenericRequestError(operationCallDeleteFolderV1, err)
} }
if response.IsError() { if response.IsError() {
return DeleteFolderV1Response{}, fmt.Errorf("CallDeleteFolderV1: Unsuccessful [response=%s]", response.String()) return DeleteFolderV1Response{}, NewAPIErrorWithResponse(operationCallDeleteFolderV1, response, nil)
} }
return folderResponse, nil return folderResponse, nil
@ -342,11 +371,12 @@ func CallDeleteSecretsRawV3(httpClient *resty.Client, request DeleteSecretV3Requ
Delete(fmt.Sprintf("%v/v3/secrets/raw/%s", config.INFISICAL_URL, request.SecretName)) Delete(fmt.Sprintf("%v/v3/secrets/raw/%s", config.INFISICAL_URL, request.SecretName))
if err != nil { if err != nil {
return fmt.Errorf("CallDeleteSecretsV3: Unable to complete api request [err=%s]", err) return NewGenericRequestError(operationCallDeleteSecretsV3, err)
} }
if response.IsError() { if response.IsError() {
return fmt.Errorf("CallDeleteSecretsV3: Unsuccessful response. Please make sure your secret path, workspace and environment name are all correct [response=%s]", response) additionalContext := "Please make sure your secret path, workspace and environment name are all correct."
return NewAPIErrorWithResponse(operationCallDeleteSecretsV3, response, &additionalContext)
} }
return nil return nil
@ -362,11 +392,11 @@ func CallCreateServiceToken(httpClient *resty.Client, request CreateServiceToken
Post(fmt.Sprintf("%v/v2/service-token/", config.INFISICAL_URL)) Post(fmt.Sprintf("%v/v2/service-token/", config.INFISICAL_URL))
if err != nil { if err != nil {
return CreateServiceTokenResponse{}, fmt.Errorf("CallCreateServiceToken: Unable to complete api request [err=%s]", err) return CreateServiceTokenResponse{}, NewGenericRequestError(operationCallCreateServiceToken, err)
} }
if response.IsError() { if response.IsError() {
return CreateServiceTokenResponse{}, fmt.Errorf("CallCreateServiceToken: Unsuccessful response [%v %v] [status-code=%v]", response.Request.Method, response.Request.URL, response.StatusCode()) return CreateServiceTokenResponse{}, NewAPIErrorWithResponse(operationCallCreateServiceToken, response, nil)
} }
return createServiceTokenResponse, nil return createServiceTokenResponse, nil
@ -382,11 +412,11 @@ func CallUniversalAuthLogin(httpClient *resty.Client, request UniversalAuthLogin
Post(fmt.Sprintf("%v/v1/auth/universal-auth/login/", config.INFISICAL_URL)) Post(fmt.Sprintf("%v/v1/auth/universal-auth/login/", config.INFISICAL_URL))
if err != nil { if err != nil {
return UniversalAuthLoginResponse{}, fmt.Errorf("CallUniversalAuthLogin: Unable to complete api request [err=%s]", err) return UniversalAuthLoginResponse{}, NewGenericRequestError(operationCallUniversalAuthLogin, err)
} }
if response.IsError() { if response.IsError() {
return UniversalAuthLoginResponse{}, fmt.Errorf("CallUniversalAuthLogin: Unsuccessful response [%v %v] [status-code=%v] [response=%v]", response.Request.Method, response.Request.URL, response.StatusCode(), response.String()) return UniversalAuthLoginResponse{}, NewAPIErrorWithResponse(operationCallUniversalAuthLogin, response, nil)
} }
return universalAuthLoginResponse, nil return universalAuthLoginResponse, nil
@ -402,11 +432,11 @@ func CallMachineIdentityRefreshAccessToken(httpClient *resty.Client, request Uni
Post(fmt.Sprintf("%v/v1/auth/token/renew", config.INFISICAL_URL)) Post(fmt.Sprintf("%v/v1/auth/token/renew", config.INFISICAL_URL))
if err != nil { if err != nil {
return UniversalAuthRefreshResponse{}, fmt.Errorf("CallMachineIdentityRefreshAccessToken: Unable to complete api request [err=%s]", err) return UniversalAuthRefreshResponse{}, NewGenericRequestError(operationCallMachineIdentityRefreshAccessToken, err)
} }
if response.IsError() { if response.IsError() {
return UniversalAuthRefreshResponse{}, fmt.Errorf("CallMachineIdentityRefreshAccessToken: Unsuccessful response [%v %v] [status-code=%v] [response=%v]", response.Request.Method, response.Request.URL, response.StatusCode(), response.String()) return UniversalAuthRefreshResponse{}, NewAPIErrorWithResponse(operationCallMachineIdentityRefreshAccessToken, response, nil)
} }
return universalAuthRefreshResponse, nil return universalAuthRefreshResponse, nil
@ -441,19 +471,19 @@ func CallGetRawSecretsV3(httpClient *resty.Client, request GetRawSecretsV3Reques
response, err := req.Get(fmt.Sprintf("%v/v3/secrets/raw", config.INFISICAL_URL)) response, err := req.Get(fmt.Sprintf("%v/v3/secrets/raw", config.INFISICAL_URL))
if err != nil { if err != nil {
return GetRawSecretsV3Response{}, fmt.Errorf("CallGetRawSecretsV3: Unable to complete api request [err=%w]", err) return GetRawSecretsV3Response{}, NewGenericRequestError(operationCallGetRawSecretsV3, err)
} }
if response.IsError() && if response.IsError() &&
(strings.Contains(response.String(), "bot_not_found_error") || (strings.Contains(response.String(), "bot_not_found_error") ||
strings.Contains(strings.ToLower(response.String()), "failed to find bot key") || strings.Contains(strings.ToLower(response.String()), "failed to find bot key") ||
strings.Contains(strings.ToLower(response.String()), "bot is not active")) { strings.Contains(strings.ToLower(response.String()), "bot is not active")) {
return GetRawSecretsV3Response{}, fmt.Errorf(`Project with id %s is incompatible with your current CLI version. Upgrade your project by visiting the project settings page. If you're self-hosting and project upgrade option isn't yet available, contact your administrator to upgrade your Infisical instance to the latest release. additionalContext := fmt.Sprintf(`Project with id %s is incompatible with your current CLI version. Upgrade your project by visiting the project settings page. If you're self-hosting and project upgrade option isn't yet available, contact your administrator to upgrade your Infisical instance to the latest release.`, request.WorkspaceId)
`, request.WorkspaceId) return GetRawSecretsV3Response{}, NewAPIErrorWithResponse(operationCallGetRawSecretsV3, response, &additionalContext)
} }
if response.IsError() { if response.IsError() {
return GetRawSecretsV3Response{}, fmt.Errorf("CallGetRawSecretsV3: Unsuccessful response [%v %v] [status-code=%v] [response=%v]", response.Request.Method, response.Request.URL, response.StatusCode(), response.String()) return GetRawSecretsV3Response{}, NewAPIErrorWithResponse(operationCallGetRawSecretsV3, response, nil)
} }
getRawSecretsV3Response.ETag = response.Header().Get(("etag")) getRawSecretsV3Response.ETag = response.Header().Get(("etag"))
@ -477,11 +507,11 @@ func CallFetchSingleSecretByName(httpClient *resty.Client, request GetRawSecretV
Get(fmt.Sprintf("%v/v3/secrets/raw/%s", config.INFISICAL_URL, request.SecretName)) Get(fmt.Sprintf("%v/v3/secrets/raw/%s", config.INFISICAL_URL, request.SecretName))
if err != nil { if err != nil {
return GetRawSecretV3ByNameResponse{}, fmt.Errorf("CallFetchSingleSecretByName: Unable to complete api request [err=%w]", err) return GetRawSecretV3ByNameResponse{}, NewGenericRequestError(operationCallFetchSingleSecretByName, err)
} }
if response.IsError() { if response.IsError() {
return GetRawSecretV3ByNameResponse{}, fmt.Errorf("CallFetchSingleSecretByName: Unsuccessful response [%v %v] [status-code=%v] [response=%v]", response.Request.Method, response.Request.URL, response.StatusCode(), response.String()) return GetRawSecretV3ByNameResponse{}, NewAPIErrorWithResponse(operationCallFetchSingleSecretByName, response, nil)
} }
getRawSecretV3ByNameResponse.ETag = response.Header().Get(("etag")) getRawSecretV3ByNameResponse.ETag = response.Header().Get(("etag"))
@ -517,11 +547,11 @@ func CallCreateRawSecretsV3(httpClient *resty.Client, request CreateRawSecretV3R
Post(fmt.Sprintf("%v/v3/secrets/raw/%s", config.INFISICAL_URL, request.SecretName)) Post(fmt.Sprintf("%v/v3/secrets/raw/%s", config.INFISICAL_URL, request.SecretName))
if err != nil { if err != nil {
return fmt.Errorf("CallCreateRawSecretsV3: Unable to complete api request [err=%w]", err) return NewGenericRequestError(operationCallCreateRawSecretsV3, err)
} }
if response.IsError() { if response.IsError() {
return fmt.Errorf("CallCreateRawSecretsV3: Unsuccessful response [%v %v] [status-code=%v] [response=%v]", response.Request.Method, response.Request.URL, response.StatusCode(), response.String()) return NewAPIErrorWithResponse(operationCallCreateRawSecretsV3, response, nil)
} }
return nil return nil
@ -535,11 +565,11 @@ func CallUpdateRawSecretsV3(httpClient *resty.Client, request UpdateRawSecretByN
Patch(fmt.Sprintf("%v/v3/secrets/raw/%s", config.INFISICAL_URL, request.SecretName)) Patch(fmt.Sprintf("%v/v3/secrets/raw/%s", config.INFISICAL_URL, request.SecretName))
if err != nil { if err != nil {
return fmt.Errorf("CallUpdateRawSecretsV3: Unable to complete api request [err=%w]", err) return NewGenericRequestError(operationCallUpdateRawSecretsV3, err)
} }
if response.IsError() { if response.IsError() {
return fmt.Errorf("CallUpdateRawSecretsV3: Unsuccessful response [%v %v] [status-code=%v] [response=%v]", response.Request.Method, response.Request.URL, response.StatusCode(), response.String()) return NewAPIErrorWithResponse(operationCallUpdateRawSecretsV3, response, nil)
} }
return nil return nil
@ -554,11 +584,11 @@ func CallRegisterGatewayIdentityV1(httpClient *resty.Client) (*GetRelayCredentia
Post(fmt.Sprintf("%v/v1/gateways/register-identity", config.INFISICAL_URL)) Post(fmt.Sprintf("%v/v1/gateways/register-identity", config.INFISICAL_URL))
if err != nil { if err != nil {
return nil, fmt.Errorf("CallRegisterGatewayIdentityV1: Unable to complete api request [err=%w]", err) return nil, NewGenericRequestError(operationCallRegisterGatewayIdentityV1, err)
} }
if response.IsError() { if response.IsError() {
return nil, fmt.Errorf("CallRegisterGatewayIdentityV1: Unsuccessful response [%v %v] [status-code=%v] [response=%v]", response.Request.Method, response.Request.URL, response.StatusCode(), response.String()) return nil, NewAPIErrorWithResponse(operationCallRegisterGatewayIdentityV1, response, nil)
} }
return &resBody, nil return &resBody, nil
@ -574,11 +604,11 @@ func CallExchangeRelayCertV1(httpClient *resty.Client, request ExchangeRelayCert
Post(fmt.Sprintf("%v/v1/gateways/exchange-cert", config.INFISICAL_URL)) Post(fmt.Sprintf("%v/v1/gateways/exchange-cert", config.INFISICAL_URL))
if err != nil { if err != nil {
return nil, fmt.Errorf("CallExchangeRelayCertV1: Unable to complete api request [err=%w]", err) return nil, NewGenericRequestError(operationCallExchangeRelayCertV1, err)
} }
if response.IsError() { if response.IsError() {
return nil, fmt.Errorf("CallExchangeRelayCertV1: Unsuccessful response [%v %v] [status-code=%v] [response=%v]", response.Request.Method, response.Request.URL, response.StatusCode(), response.String()) return nil, NewAPIErrorWithResponse(operationCallExchangeRelayCertV1, response, nil)
} }
return &resBody, nil return &resBody, nil
@ -591,11 +621,11 @@ func CallGatewayHeartBeatV1(httpClient *resty.Client) error {
Post(fmt.Sprintf("%v/v1/gateways/heartbeat", config.INFISICAL_URL)) Post(fmt.Sprintf("%v/v1/gateways/heartbeat", config.INFISICAL_URL))
if err != nil { if err != nil {
return fmt.Errorf("CallGatewayHeartBeatV1: Unable to complete api request [err=%w]", err) return NewGenericRequestError(operationCallGatewayHeartBeatV1, err)
} }
if response.IsError() { if response.IsError() {
return fmt.Errorf("CallGatewayHeartBeatV1: Unsuccessful response [%v %v] [status-code=%v] [response=%v]", response.Request.Method, response.Request.URL, response.StatusCode(), response.String()) return NewAPIErrorWithResponse(operationCallGatewayHeartBeatV1, response, nil)
} }
return nil return nil
@ -611,11 +641,11 @@ func CallBootstrapInstance(httpClient *resty.Client, request BootstrapInstanceRe
Post(fmt.Sprintf("%v/v1/admin/bootstrap", request.Domain)) Post(fmt.Sprintf("%v/v1/admin/bootstrap", request.Domain))
if err != nil { if err != nil {
return nil, fmt.Errorf("CallBootstrapInstance: Unable to complete api request [err=%w]", err) return nil, NewGenericRequestError(operationCallBootstrapInstance, err)
} }
if response.IsError() { if response.IsError() {
return nil, fmt.Errorf("CallBootstrapInstance: Unsuccessful response [%v %v] [status-code=%v] [response=%v]", response.Request.Method, response.Request.URL, response.StatusCode(), response.String()) return nil, NewAPIErrorWithResponse(operationCallBootstrapInstance, response, nil)
} }
return resBody, nil return resBody, nil

View File

@ -0,0 +1,80 @@
package api
import (
"fmt"
"github.com/go-resty/resty/v2"
"github.com/infisical/go-sdk/packages/util"
)
type GenericRequestError struct {
err error
operation string
}
func (e *GenericRequestError) Error() string {
return fmt.Sprintf("%s: Unable to complete api request [err=%v]", e.operation, e.err)
}
func NewGenericRequestError(operation string, err error) *GenericRequestError {
return &GenericRequestError{err: err, operation: operation}
}
// APIError represents an error response from the API
type APIError struct {
AdditionalContext string `json:"additionalContext,omitempty"`
Operation string `json:"operation"`
Method string `json:"method"`
URL string `json:"url"`
StatusCode int `json:"statusCode"`
ErrorMessage string `json:"message,omitempty"`
ReqId string `json:"reqId,omitempty"`
}
func (e *APIError) Error() string {
msg := fmt.Sprintf(
"%s Unsuccessful response [%v %v] [status-code=%v] [request-id=%v]",
e.Operation,
e.Method,
e.URL,
e.StatusCode,
e.ReqId,
)
if e.ErrorMessage != "" {
msg = fmt.Sprintf("%s [message=\"%s\"]", msg, e.ErrorMessage)
}
if e.AdditionalContext != "" {
msg = fmt.Sprintf("%s [additional-context=\"%s\"]", msg, e.AdditionalContext)
}
return msg
}
func NewAPIErrorWithResponse(operation string, res *resty.Response, additionalContext *string) error {
errorMessage := util.TryParseErrorBody(res)
reqId := util.TryExtractReqId(res)
if res == nil {
return NewGenericRequestError(operation, fmt.Errorf("response is nil"))
}
apiError := &APIError{
Operation: operation,
Method: res.Request.Method,
URL: res.Request.URL,
StatusCode: res.StatusCode(),
ReqId: reqId,
}
if additionalContext != nil && *additionalContext != "" {
apiError.AdditionalContext = *additionalContext
}
if errorMessage != "" {
apiError.ErrorMessage = errorMessage
}
return apiError
}

View File

@ -80,9 +80,8 @@ func getDynamicSecretList(cmd *cobra.Command, args []string) {
} }
if loggedInUserDetails.LoginExpired { if loggedInUserDetails.LoginExpired {
loggedInUserDetails = util.EstablishUserLoginSession() util.PrintErrorMessageAndExit("Your login session has expired, please run [infisical login] and try again")
} }
infisicalToken = loggedInUserDetails.UserCredentials.JTWToken infisicalToken = loggedInUserDetails.UserCredentials.JTWToken
} }
@ -198,7 +197,7 @@ func createDynamicSecretLeaseByName(cmd *cobra.Command, args []string) {
} }
if loggedInUserDetails.LoginExpired { if loggedInUserDetails.LoginExpired {
loggedInUserDetails = util.EstablishUserLoginSession() util.PrintErrorMessageAndExit("Your login session has expired, please run [infisical login] and try again")
} }
infisicalToken = loggedInUserDetails.UserCredentials.JTWToken infisicalToken = loggedInUserDetails.UserCredentials.JTWToken
} }
@ -329,9 +328,8 @@ func renewDynamicSecretLeaseByName(cmd *cobra.Command, args []string) {
} }
if loggedInUserDetails.LoginExpired { if loggedInUserDetails.LoginExpired {
loggedInUserDetails = util.EstablishUserLoginSession() util.PrintErrorMessageAndExit("Your login session has expired, please run [infisical login] and try again")
} }
infisicalToken = loggedInUserDetails.UserCredentials.JTWToken infisicalToken = loggedInUserDetails.UserCredentials.JTWToken
} }
@ -437,9 +435,8 @@ func revokeDynamicSecretLeaseByName(cmd *cobra.Command, args []string) {
} }
if loggedInUserDetails.LoginExpired { if loggedInUserDetails.LoginExpired {
loggedInUserDetails = util.EstablishUserLoginSession() util.PrintErrorMessageAndExit("Your login session has expired, please run [infisical login] and try again")
} }
infisicalToken = loggedInUserDetails.UserCredentials.JTWToken infisicalToken = loggedInUserDetails.UserCredentials.JTWToken
} }
@ -544,7 +541,7 @@ func listDynamicSecretLeaseByName(cmd *cobra.Command, args []string) {
} }
if loggedInUserDetails.LoginExpired { if loggedInUserDetails.LoginExpired {
loggedInUserDetails = util.EstablishUserLoginSession() util.PrintErrorMessageAndExit("Your login session has expired, please run [infisical login] and try again")
} }
infisicalToken = loggedInUserDetails.UserCredentials.JTWToken infisicalToken = loggedInUserDetails.UserCredentials.JTWToken
} }

View File

@ -46,7 +46,7 @@ var initCmd = &cobra.Command{
} }
if userCreds.LoginExpired { if userCreds.LoginExpired {
userCreds = util.EstablishUserLoginSession() util.PrintErrorMessageAndExit("Your login session has expired, please run [infisical login] and try again")
} }
httpClient, err := util.GetRestyClientWithCustomHeaders() httpClient, err := util.GetRestyClientWithCustomHeaders()

View File

@ -9,7 +9,6 @@ import (
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"os" "os"
"runtime"
"slices" "slices"
"strings" "strings"
"time" "time"
@ -21,8 +20,6 @@ import (
"net/url" "net/url"
"regexp" "regexp"
browser "github.com/pkg/browser"
"github.com/Infisical/infisical-merge/packages/api" "github.com/Infisical/infisical-merge/packages/api"
"github.com/Infisical/infisical-merge/packages/config" "github.com/Infisical/infisical-merge/packages/config"
"github.com/Infisical/infisical-merge/packages/crypto" "github.com/Infisical/infisical-merge/packages/crypto"
@ -984,17 +981,7 @@ func browserCliLogin() (models.UserCredentials, error) {
callbackPort := listener.Addr().(*net.TCPAddr).Port callbackPort := listener.Addr().(*net.TCPAddr).Port
url := fmt.Sprintf("%s?callback_port=%d", config.INFISICAL_LOGIN_URL, callbackPort) url := fmt.Sprintf("%s?callback_port=%d", config.INFISICAL_LOGIN_URL, callbackPort)
defaultPrintStatement := fmt.Sprintf("\n\nTo complete your login, open this address in your browser: %v \n", url) fmt.Printf("\n\nTo complete your login, open this address in your browser: %v \n", url)
if runtime.GOOS == "darwin" || runtime.GOOS == "windows" {
if err := browser.OpenURL(url); err != nil {
fmt.Print(defaultPrintStatement)
} else {
fmt.Printf("\n\nPlease proceed to your browser to complete the login process.\nIf the browser doesn't open automatically, please open this address in your browser: %v \n", url)
}
} else {
fmt.Print(defaultPrintStatement)
}
//flow channels //flow channels
success := make(chan models.UserCredentials) success := make(chan models.UserCredentials)

View File

@ -237,7 +237,7 @@ var secretsSetCmd = &cobra.Command{
} }
if loggedInUserDetails.LoginExpired { if loggedInUserDetails.LoginExpired {
loggedInUserDetails = util.EstablishUserLoginSession() util.PrintErrorMessageAndExit("Your login session has expired, please run [infisical login] and try again")
} }
secretOperations, err = util.SetRawSecrets(processedArgs, secretType, environmentName, secretsPath, projectId, &models.TokenDetails{ secretOperations, err = util.SetRawSecrets(processedArgs, secretType, environmentName, secretsPath, projectId, &models.TokenDetails{
@ -325,7 +325,7 @@ var secretsDeleteCmd = &cobra.Command{
} }
if loggedInUserDetails.LoginExpired { if loggedInUserDetails.LoginExpired {
loggedInUserDetails = util.EstablishUserLoginSession() util.PrintErrorMessageAndExit("Your login session has expired, please run [infisical login] and try again")
} }
httpClient.SetAuthToken(loggedInUserDetails.UserCredentials.JTWToken) httpClient.SetAuthToken(loggedInUserDetails.UserCredentials.JTWToken)

View File

@ -184,7 +184,7 @@ func issueCredentials(cmd *cobra.Command, args []string) {
} }
if loggedInUserDetails.LoginExpired { if loggedInUserDetails.LoginExpired {
loggedInUserDetails = util.EstablishUserLoginSession() util.PrintErrorMessageAndExit("Your login session has expired, please run [infisical login] and try again")
} }
infisicalToken = loggedInUserDetails.UserCredentials.JTWToken infisicalToken = loggedInUserDetails.UserCredentials.JTWToken
} }
@ -417,7 +417,7 @@ func signKey(cmd *cobra.Command, args []string) {
} }
if loggedInUserDetails.LoginExpired { if loggedInUserDetails.LoginExpired {
loggedInUserDetails = util.EstablishUserLoginSession() util.PrintErrorMessageAndExit("Your login session has expired, please run [infisical login] and try again")
} }
infisicalToken = loggedInUserDetails.UserCredentials.JTWToken infisicalToken = loggedInUserDetails.UserCredentials.JTWToken
} }
@ -626,7 +626,7 @@ func sshConnect(cmd *cobra.Command, args []string) {
} }
if loggedInUserDetails.LoginExpired { if loggedInUserDetails.LoginExpired {
loggedInUserDetails = util.EstablishUserLoginSession() util.PrintErrorMessageAndExit("Your login session has expired, please run [infisical login] and try again")
} }
infisicalToken = loggedInUserDetails.UserCredentials.JTWToken infisicalToken = loggedInUserDetails.UserCredentials.JTWToken
} }
@ -879,7 +879,7 @@ func sshAddHost(cmd *cobra.Command, args []string) {
util.HandleError(err, "Unable to authenticate") util.HandleError(err, "Unable to authenticate")
} }
if loggedInUserDetails.LoginExpired { if loggedInUserDetails.LoginExpired {
loggedInUserDetails = util.EstablishUserLoginSession() util.PrintErrorMessageAndExit("Your login session has expired, please run [infisical login]")
} }
infisicalToken = loggedInUserDetails.UserCredentials.JTWToken infisicalToken = loggedInUserDetails.UserCredentials.JTWToken
} }

View File

@ -47,7 +47,7 @@ var tokensCreateCmd = &cobra.Command{
} }
if loggedInUserDetails.LoginExpired { if loggedInUserDetails.LoginExpired {
loggedInUserDetails = util.EstablishUserLoginSession() util.PrintErrorMessageAndExit("Your login session has expired, please run [infisical login] and try again")
} }
tokenOnly, err := cmd.Flags().GetBool("token-only") tokenOnly, err := cmd.Flags().GetBool("token-only")

View File

@ -111,9 +111,8 @@ var userGetTokenCmd = &cobra.Command{
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
loggedInUserDetails, err := util.GetCurrentLoggedInUserDetails(true) loggedInUserDetails, err := util.GetCurrentLoggedInUserDetails(true)
if loggedInUserDetails.LoginExpired { if loggedInUserDetails.LoginExpired {
loggedInUserDetails = util.EstablishUserLoginSession() util.PrintErrorMessageAndExit("Your login session has expired, please run [infisical login] and try again")
} }
if err != nil { if err != nil {
util.HandleError(err, "[infisical user get token]: Unable to get logged in user token") util.HandleError(err, "[infisical user get token]: Unable to get logged in user token")
} }

View File

@ -1,13 +1,5 @@
package util package util
import (
"fmt"
"os"
"os/exec"
"github.com/rs/zerolog/log"
)
type AuthStrategyType string type AuthStrategyType string
var AuthStrategy = struct { var AuthStrategy = struct {
@ -51,36 +43,3 @@ func IsAuthMethodValid(authMethod string, allowUserAuth bool) (isValid bool, str
} }
return false, "" return false, ""
} }
// EstablishUserLoginSession handles the login flow to either create a new session or restore an expired one.
// It returns fresh user details if login is successful.
func EstablishUserLoginSession() LoggedInUserDetails {
log.Info().Msg("No valid login session found, triggering login flow")
exePath, err := os.Executable()
if err != nil {
PrintErrorMessageAndExit(fmt.Sprintf("Failed to determine executable path: %v", err))
}
// Spawn infisical login command
loginCmd := exec.Command(exePath, "login", "--silent")
loginCmd.Stdin = os.Stdin
loginCmd.Stdout = os.Stdout
loginCmd.Stderr = os.Stderr
err = loginCmd.Run()
if err != nil {
PrintErrorMessageAndExit(fmt.Sprintf("Failed to automatically trigger login flow. Please run [infisical login] manually to login."))
}
loggedInUserDetails, err := GetCurrentLoggedInUserDetails(true)
if err != nil {
PrintErrorMessageAndExit("You must be logged in to run this command. To login, run [infisical login]")
}
if loggedInUserDetails.LoginExpired {
PrintErrorMessageAndExit("Your login session has expired. Please run [infisical login]")
}
return loggedInUserDetails
}

View File

@ -25,7 +25,7 @@ func GetAllFolders(params models.GetAllFoldersParameters) ([]models.SingleFolder
} }
if loggedInUserDetails.LoginExpired { if loggedInUserDetails.LoginExpired {
loggedInUserDetails = EstablishUserLoginSession() PrintErrorMessageAndExit("Your login session has expired, please run [infisical login] and try again")
} }
workspaceFile, err := GetWorkSpaceFromFile() workspaceFile, err := GetWorkSpaceFromFile()
@ -194,7 +194,7 @@ func CreateFolder(params models.CreateFolderParameters) (models.SingleFolder, er
} }
if loggedInUserDetails.LoginExpired { if loggedInUserDetails.LoginExpired {
loggedInUserDetails = EstablishUserLoginSession() PrintErrorMessageAndExit("Your login session has expired, please run [infisical login] and try again")
} }
params.InfisicalToken = loggedInUserDetails.UserCredentials.JTWToken params.InfisicalToken = loggedInUserDetails.UserCredentials.JTWToken
@ -244,7 +244,7 @@ func DeleteFolder(params models.DeleteFolderParameters) ([]models.SingleFolder,
} }
if loggedInUserDetails.LoginExpired { if loggedInUserDetails.LoginExpired {
loggedInUserDetails = EstablishUserLoginSession() PrintErrorMessageAndExit("Your login session has expired, please run [infisical login] and try again")
} }
params.InfisicalToken = loggedInUserDetails.UserCredentials.JTWToken params.InfisicalToken = loggedInUserDetails.UserCredentials.JTWToken

View File

@ -174,7 +174,7 @@ func RequireLogin() {
configFile, _ := GetConfigFile() configFile, _ := GetConfigFile()
if configFile.LoggedInUserEmail == "" { if configFile.LoggedInUserEmail == "" {
EstablishUserLoginSession() PrintErrorMessageAndExit("You must be logged in to run this command. To login, run [infisical login]")
} }
} }

View File

@ -273,7 +273,7 @@ func GetAllEnvironmentVariables(params models.GetAllSecretsParameters, projectCo
} }
if isConnected && loggedInUserDetails.LoginExpired { if isConnected && loggedInUserDetails.LoginExpired {
loggedInUserDetails = EstablishUserLoginSession() PrintErrorMessageAndExit("Your login session has expired, please run [infisical login] and try again")
} }
var infisicalDotJson models.WorkspaceConfigFile var infisicalDotJson models.WorkspaceConfigFile

View File

@ -1,4 +1,4 @@
error: CallGetRawSecretsV3: Unsuccessful response [GET https://app.infisical.com/api/v3/secrets/raw?environment=invalid-env&expandSecretReferences=true&include_imports=true&recursive=true&secretPath=%2F&workspaceId=bef697d4-849b-4a75-b284-0922f87f8ba2] [status-code=404] [response={"error":"NotFound","message":"Environment with slug 'invalid-env' in project with ID bef697d4-849b-4a75-b284-0922f87f8ba2 not found","statusCode":404}] error: CallGetRawSecretsV3 Unsuccessful response [GET https://app.infisical.com/api/v3/secrets/raw?environment=invalid-env&expandSecretReferences=true&include_imports=true&recursive=true&secretPath=%2F&workspaceId=bef697d4-849b-4a75-b284-0922f87f8ba2] [status-code=404] [request-id=<unknown-value>] [message="Environment with slug 'invalid-env' in project with ID bef697d4-849b-4a75-b284-0922f87f8ba2 not found"]
If this issue continues, get support at https://infisical.com/slack If this issue continues, get support at https://infisical.com/slack

View File

@ -6,6 +6,7 @@ import (
"log" "log"
"os" "os"
"os/exec" "os/exec"
"regexp"
"strings" "strings"
) )
@ -71,7 +72,11 @@ func SetupCli() {
} }
func FilterRequestID(input string) string { func FilterRequestID(input string) string {
// Find the JSON part of the error message requestIDPattern := regexp.MustCompile(`\[request-id=[^\]]+\]`)
reqIDPattern := regexp.MustCompile(`\[reqId=[^\]]+\]`)
input = requestIDPattern.ReplaceAllString(input, "[request-id=<unknown-value>]")
input = reqIDPattern.ReplaceAllString(input, "[reqId=<unknown-value>]")
start := strings.Index(input, "{") start := strings.Index(input, "{")
end := strings.LastIndex(input, "}") + 1 end := strings.LastIndex(input, "}") + 1

View File

@ -60,7 +60,7 @@ The Infisical AWS ElastiCache dynamic secret allows you to generate AWS ElastiCa
<Step title="Click on the 'Add Dynamic Secret' button"> <Step title="Click on the 'Add Dynamic Secret' button">
![Add Dynamic Secret Button](../../../images/platform/dynamic-secrets/add-dynamic-secret-button.png) ![Add Dynamic Secret Button](../../../images/platform/dynamic-secrets/add-dynamic-secret-button.png)
</Step> </Step>
<Step title="Select AWS ElastiCache"> <Step title="Select 'AWS ElastiCache'">
![Dynamic Secret Modal](../../../images/platform/dynamic-secrets/dynamic-secret-modal-aws-elasti-cache.png) ![Dynamic Secret Modal](../../../images/platform/dynamic-secrets/dynamic-secret-modal-aws-elasti-cache.png)
</Step> </Step>
<Step title="Provide the inputs for dynamic secret parameters"> <Step title="Provide the inputs for dynamic secret parameters">
@ -94,17 +94,9 @@ The Infisical AWS ElastiCache dynamic secret allows you to generate AWS ElastiCa
</Step> </Step>
<Step title="(Optional) Modify ElastiCache Statements"> <Step title="(Optional) Modify ElastiCache Statements">
![Modify ElastiCache Statements Modal](/images/platform/dynamic-secrets/modify-elasticache-statement.png) If you want to provide specific privileges for the generated dynamic credentials, you can modify the ElastiCache statement to your needs. This is useful if you want to only give access to a specific table(s).
<ParamField path="Username Template" type="string" default="{{randomUsername}}">
Specifies a template for generating usernames. This field allows customization of how usernames are automatically created.
Allowed template variables are ![Modify ElastiCache Statements Modal](/images/platform/dynamic-secrets/modify-elasticache-statement.png)
- `{{randomUsername}}`: Random username string
- `{{unixTimestamp}}`: Current Unix timestamp
</ParamField>
<ParamField path="Customize ElastiCache Statement" type="string">
If you want to provide specific privileges for the generated dynamic credentials, you can modify the ElastiCache statement to your needs. This is useful if you want to only give access to a specific resource.
</ParamField>
</Step> </Step>
<Step title="Click `Submit`"> <Step title="Click `Submit`">
After submitting the form, you will see a dynamic secret created in the dashboard. After submitting the form, you will see a dynamic secret created in the dashboard.

View File

@ -105,14 +105,6 @@ Replace **\<account id\>** with your AWS account id and **\<aws-scope-path\>** w
The AWS IAM inline policy that should be attached to the created users. Multiple values can be provided by separating them with commas The AWS IAM inline policy that should be attached to the created users. Multiple values can be provided by separating them with commas
</ParamField> </ParamField>
<ParamField path="Username Template" type="string" default="{{randomUsername}}">
Specifies a template for generating usernames. This field allows customization of how usernames are automatically created.
Allowed template variables are
- `{{randomUsername}}`: Random username string
- `{{unixTimestamp}}`: Current Unix timestamp
</ParamField>
![Dynamic Secret Setup Modal](../../../images/platform/dynamic-secrets/dynamic-secret-setup-modal-aws-iam.png) ![Dynamic Secret Setup Modal](../../../images/platform/dynamic-secrets/dynamic-secret-setup-modal-aws-iam.png)
</Step> </Step>

View File

@ -78,23 +78,15 @@ The above configuration allows user creation and granting permissions.
</Step> </Step>
<Step title="(Optional) Modify CQL Statements"> <Step title="(Optional) Modify CQL Statements">
![Modify CQL Statements Modal](../../../images/platform/dynamic-secrets/modify-cql-statements.png)
<ParamField path="Username Template" type="string" default="{{randomUsername}}">
Specifies a template for generating usernames. This field allows customization of how usernames are automatically created.
Allowed template variables are
- `{{randomUsername}}`: Random username string
- `{{unixTimestamp}}`: Current Unix timestamp
</ParamField>
<ParamField path="Customize CQL Statement" type="string">
If you want to provide specific privileges for the generated dynamic credentials, you can modify the CQL statement to your needs. This is useful if you want to only give access to a specific key-space(s). If you want to provide specific privileges for the generated dynamic credentials, you can modify the CQL statement to your needs. This is useful if you want to only give access to a specific key-space(s).
</ParamField>
![Modify CQL Statements Modal](../../../images/platform/dynamic-secrets/modify-cql-statements.png)
</Step> </Step>
<Step title="Click 'Submit'"> <Step title="Click 'Submit'">
After submitting the form, you will see a dynamic secret created in the dashboard. After submitting the form, you will see a dynamic secret created in the dashboard.
<Note> <Note>
If this step fails, you may have to add the CA certificate. If this step fails, you may have to add the CA certficate.
</Note> </Note>
![Dynamic Secret](../../../images/platform/dynamic-secrets/dynamic-secret.png) ![Dynamic Secret](../../../images/platform/dynamic-secrets/dynamic-secret.png)

View File

@ -7,14 +7,13 @@ The Infisical Elasticsearch dynamic secret allows you to generate Elasticsearch
## Prerequisites ## Prerequisites
1. Create a role with at least `manage_security` and `monitor` permissions. 1. Create a role with at least `manage_security` and `monitor` permissions.
2. Assign the newly created role to your API key or user that you'll use later in the dynamic secret configuration. 2. Assign the newly created role to your API key or user that you'll use later in the dynamic secret configuration.
<Note> <Note>
For testing purposes, you can also use a highly privileged role like For testing purposes, you can also use a highly privileged role like `superuser`, that will have full control over the cluster. This is not recommended in production environments following the principle of least privilege.
`superuser`, that will have full control over the cluster. This is not
recommended in production environments following the principle of least
privilege.
</Note> </Note>
## Set up Dynamic Secrets with Elasticsearch ## Set up Dynamic Secrets with Elasticsearch
@ -47,55 +46,39 @@ The Infisical Elasticsearch dynamic secret allows you to generate Elasticsearch
</ParamField> </ParamField>
<ParamField path="Port" type="string" required> <ParamField path="Port" type="string" required>
The port that your Elasticsearch instance is running on. _(Example: 9200)_
</ParamField>
The port that your Elasticsearch instance is running on. _(Example: 9200)_ <ParamField path="Roles" type="string[]" required>
</ParamField> The roles that the new user that is created when a lease is provisioned will be assigned to. This is a required field. This defaults to `superuser`, which is highly privileged. It is recommended to create a new role with the least privileges required for the lease.
</ParamField>
<ParamField path="Roles" type="string[]" required>
The roles that the new user that is created when a lease is provisioned will
be assigned to. This is a required field. This defaults to `superuser`, which
is highly privileged. It is recommended to create a new role with the least
privileges required for the lease.
</ParamField>
<ParamField path="Authentication Method" type="API Key | Username/Password" required> <ParamField path="Authentication Method" type="API Key | Username/Password" required>
Select the authentication method you want to use to connect to your Elasticsearch instance. Select the authentication method you want to use to connect to your Elasticsearch instance.
</ParamField> </ParamField>
<ParamField path="Username" type="string" required> <ParamField path="Username" type="string" required>
The username of the user that will be used to provision new dynamic secret The username of the user that will be used to provision new dynamic secret leases. Only required if you selected the `Username/Password` authentication method.
leases. Only required if you selected the `Username/Password` authentication </ParamField>
method.
</ParamField>
<ParamField path="Password" type="string" required> <ParamField path="Password" type="string" required>
The password of the user that will be used to provision new dynamic secret The password of the user that will be used to provision new dynamic secret leases. Only required if you selected the `Username/Password` authentication method.
leases. Only required if you selected the `Username/Password` authentication </ParamField>
method.
</ParamField>
<ParamField path="API Key ID" required> <ParamField path="API Key ID" required>
The ID of the API key that will be used to provision new dynamic secret The ID of the API key that will be used to provision new dynamic secret leases. Only required if you selected the `API Key` authentication method.
leases. Only required if you selected the `API Key` authentication method. </ParamField>
</ParamField>
<ParamField path="API Key" required> <ParamField path="API Key" required>
The API key that will be used to provision new dynamic secret leases. Only The API key that will be used to provision new dynamic secret leases. Only required if you selected the `API Key` authentication method.
required if you selected the `API Key` authentication method. </ParamField>
</ParamField>
<ParamField path="CA(SSL)" type="string"> <ParamField path="CA(SSL)" type="string">
A CA may be required if your DB requires it for incoming connections. This is often the case when connecting to a managed service. A CA may be required if your DB requires it for incoming connections. This is often the case when connecting to a managed service.
</ParamField> </ParamField>
<ParamField path="Username Template" type="string" default="{{randomUsername}}">
Specifies a template for generating usernames. This field allows customization of how usernames are automatically created.
Allowed template variables are ![Dynamic Secret Setup Modal](../../../images/platform/dynamic-secrets/dynamic-secret-input-modal-elastic-search.png)
- `{{randomUsername}}`: Random username string
- `{{unixTimestamp}}`: Current Unix timestamp
</ParamField>
![Dynamic Secret Setup Modal](../../../images/platform/dynamic-secrets/dynamic-secret-input-modal-elastic-search.png)
</Step> </Step>
<Step title="Click `Submit`"> <Step title="Click `Submit`">
@ -126,23 +109,19 @@ The port that your Elasticsearch instance is running on. _(Example: 9200)_
Once you click the `Submit` button, a new secret lease will be generated and the credentials from it will be shown to you. Once you click the `Submit` button, a new secret lease will be generated and the credentials from it will be shown to you.
![Provision Lease](/images/platform/dynamic-secrets/lease-values.png) ![Provision Lease](/images/platform/dynamic-secrets/lease-values.png)
</Step> </Step>
</Steps> </Steps>
## Audit or Revoke Leases ## Audit or Revoke Leases
Once you have created one or more leases, you will be able to access them by clicking on the respective dynamic secret item on the dashboard. Once you have created one or more leases, you will be able to access them by clicking on the respective dynamic secret item on the dashboard.
This will allow you to see the expiration time of the lease or delete a lease before it's set time to live. This will allow you to see the expiration time of the lease or delete a lease before it's set time to live.
![Provision Lease](/images/platform/dynamic-secrets/lease-data.png) ![Provision Lease](/images/platform/dynamic-secrets/lease-data.png)
## Renew Leases ## Renew Leases
To extend the life of the generated dynamic secret leases past its initial time to live, simply click on the **Renew** button as illustrated below. To extend the life of the generated dynamic secret leases past its initial time to live, simply click on the **Renew** button as illustrated below.
![Provision Lease](/images/platform/dynamic-secrets/dynamic-secret-lease-renew.png) ![Provision Lease](/images/platform/dynamic-secrets/dynamic-secret-lease-renew.png)
<Warning> <Warning>
Lease renewals cannot exceed the maximum TTL set when configuring the dynamic Lease renewals cannot exceed the maximum TTL set when configuring the dynamic secret
secret
</Warning> </Warning>

View File

@ -122,13 +122,6 @@ The Infisical LDAP dynamic secret allows you to generate user credentials on dem
dn: CN={{Username}},OU=Test Create,DC=infisical,DC=com dn: CN={{Username}},OU=Test Create,DC=infisical,DC=com
changetype: delete changetype: delete
``` ```
</ParamField>
<ParamField path="Username Template" type="string" default="{{randomUsername}}">
Specifies a template for generating usernames. This field allows customization of how usernames are automatically created.
Allowed template variables are
- `{{randomUsername}}`: Random username string
- `{{unixTimestamp}}`: Current Unix timestamp
</ParamField> </ParamField>
</Step> </Step>

View File

@ -6,10 +6,11 @@ description: "Learn how to dynamically generate Mongo Atlas Database user creden
The Infisical Mongo Atlas dynamic secret allows you to generate Mongo Atlas Database credentials on demand based on configured role. The Infisical Mongo Atlas dynamic secret allows you to generate Mongo Atlas Database credentials on demand based on configured role.
## Prerequisite ## Prerequisite
Create a project scopped API Key with the required permission in your Mongo Atlas following the [official doc](https://www.mongodb.com/docs/atlas/configure-api-access/#grant-programmatic-access-to-a-project).
Create a project scoped API Key with the required permission in your Mongo Atlas following the [official doc](https://www.mongodb.com/docs/atlas/configure-api-access/#grant-programmatic-access-to-a-project). <Info>
The API Key must have permission to manage users in the project.
<Info>The API Key must have permission to manage users in the project.</Info> </Info>
## Set up Dynamic Secrets with Mongo Atlas ## Set up Dynamic Secrets with Mongo Atlas
@ -60,34 +61,20 @@ Create a project scoped API Key with the required permission in your Mongo Atlas
</Step> </Step>
<Step title="(Optional) Modify Access Scope"> <Step title="(Optional) Modify Access Scope">
![Modify Scope Modal](../../../images/platform/dynamic-secrets/advanced-option-atlas.png)
<ParamField path="Username Template" type="string" default="{{randomUsername}}">
Specifies a template for generating usernames. This field allows customization of how usernames are automatically created.
Allowed template variables are
- `{{randomUsername}}`: Random username string
- `{{unixTimestamp}}`: Current Unix timestamp
</ParamField>
<ParamField path="Customize Scope" type="string">
List that contains clusters, MongoDB Atlas Data Lakes, and MongoDB Atlas Streams Instances that this database user can access. If omitted, MongoDB Cloud grants the database user access to all the clusters, MongoDB Atlas Data Lakes, and MongoDB Atlas Streams Instances in the project. List that contains clusters, MongoDB Atlas Data Lakes, and MongoDB Atlas Streams Instances that this database user can access. If omitted, MongoDB Cloud grants the database user access to all the clusters, MongoDB Atlas Data Lakes, and MongoDB Atlas Streams Instances in the project.
![Modify Scope Modal](../../../images/platform/dynamic-secrets/advanced-option-atlas.png)
- **Label**: Human-readable label that identifies the cluster or MongoDB Atlas Data Lake that this database user can access. - **Label**: Human-readable label that identifies the cluster or MongoDB Atlas Data Lake that this database user can access.
- **Type**: Category of resource that this database user can access. - **Type**: Category of resource that this database user can access.
</ParamField>
</Step> </Step>
<Step title="Click 'Submit'"> <Step title="Click 'Submit'">
After submitting the form, you will see a dynamic secret created in the dashboard. After submitting the form, you will see a dynamic secret created in the dashboard.
<Note> <Note>
If this step fails, you may have to add the CA certificate. If this step fails, you may have to add the CA certficate.
</Note> </Note>
![Dynamic Secret](../../../images/platform/dynamic-secrets/dynamic-secret.png) ![Dynamic Secret](../../../images/platform/dynamic-secrets/dynamic-secret.png)
</Step> </Step>
<Step title="Generate dynamic secrets"> <Step title="Generate dynamic secrets">
Once you've successfully configured the dynamic secret, you're ready to generate on-demand credentials. Once you've successfully configured the dynamic secret, you're ready to generate on-demand credentials.
@ -102,30 +89,26 @@ Create a project scoped API Key with the required permission in your Mongo Atlas
![Provision Lease](/images/platform/dynamic-secrets/provision-lease.png) ![Provision Lease](/images/platform/dynamic-secrets/provision-lease.png)
<Tip> <Tip>
Ensure that the TTL for the lease falls within the maximum TTL defined when configuring the dynamic secret. Ensure that the TTL for the lease fall within the maximum TTL defined when configuring the dynamic secret.
</Tip> </Tip>
Once you click the `Submit` button, a new secret lease will be generated and the credentials for it will be shown to you. Once you click the `Submit` button, a new secret lease will be generated and the credentials for it will be shown to you.
![Provision Lease](/images/platform/dynamic-secrets/lease-values.png) ![Provision Lease](/images/platform/dynamic-secrets/lease-values.png)
</Step> </Step>
</Steps> </Steps>
## Audit or Revoke Leases ## Audit or Revoke Leases
Once you have created one or more leases, you will be able to access them by clicking on the respective dynamic secret item on the dashboard. Once you have created one or more leases, you will be able to access them by clicking on the respective dynamic secret item on the dashboard.
This will allow you to see the expiration time of the lease or delete a lease before it's set time to live. This will allow you to see the expiration time of the lease or delete a lease before it's set time to live.
![Provision Lease](/images/platform/dynamic-secrets/lease-data.png) ![Provision Lease](/images/platform/dynamic-secrets/lease-data.png)
## Renew Leases ## Renew Leases
To extend the life of the generated dynamic secret leases past its initial time to live, simply click on the **Renew** button as illustrated below. To extend the life of the generated dynamic secret leases past its initial time to live, simply click on the **Renew** button as illustrated below.
![Provision Lease](/images/platform/dynamic-secrets/dynamic-secret-lease-renew.png) ![Provision Lease](/images/platform/dynamic-secrets/dynamic-secret-lease-renew.png)
<Warning> <Warning>
Lease renewals cannot exceed the maximum TTL set when configuring the dynamic Lease renewals cannot exceed the maximum TTL set when configuring the dynamic secret
secret
</Warning> </Warning>

View File

@ -66,13 +66,6 @@ Create a user with the required permission in your MongoDB instance. This user w
<ParamField path="CA(SSL)" type="string"> <ParamField path="CA(SSL)" type="string">
A CA may be required if your DB requires it for incoming connections. A CA may be required if your DB requires it for incoming connections.
</ParamField> </ParamField>
<ParamField path="Username Template" type="string" default="{{randomUsername}}">
Specifies a template for generating usernames. This field allows customization of how usernames are automatically created.
Allowed template variables are
- `{{randomUsername}}`: Random username string
- `{{unixTimestamp}}`: Current Unix timestamp
</ParamField>
![Dynamic Secret Setup Modal](../../../images/platform/dynamic-secrets/dynamic-secret-mongodb.png) ![Dynamic Secret Setup Modal](../../../images/platform/dynamic-secrets/dynamic-secret-mongodb.png)
</Step> </Step>

View File

@ -71,23 +71,15 @@ Create a user with the required permission in your SQL instance. This user will
</Step> </Step>
<Step title="(Optional) Modify SQL Statements"> <Step title="(Optional) Modify SQL Statements">
![Modify SQL Statements Modal](../../../images/platform/dynamic-secrets/modify-sql-statements-mssql.png)
<ParamField path="Username Template" type="string" default="{{randomUsername}}">
Specifies a template for generating usernames. This field allows customization of how usernames are automatically created.
Allowed template variables are
- `{{randomUsername}}`: Random username string
- `{{unixTimestamp}}`: Current Unix timestamp
</ParamField>
<ParamField path="Customize SQL Statement" type="string">
If you want to provide specific privileges for the generated dynamic credentials, you can modify the SQL statement to your needs. This is useful if you want to only give access to a specific table(s). If you want to provide specific privileges for the generated dynamic credentials, you can modify the SQL statement to your needs. This is useful if you want to only give access to a specific table(s).
</ParamField>
![Modify SQL Statements Modal](../../../images/platform/dynamic-secrets/modify-sql-statements-mssql.png)
</Step> </Step>
<Step title="Click 'Submit'"> <Step title="Click 'Submit'">
After submitting the form, you will see a dynamic secret created in the dashboard. After submitting the form, you will see a dynamic secret created in the dashboard.
<Note> <Note>
If this step fails, you may have to add the CA certificate. If this step fails, you may have to add the CA certficate.
</Note> </Note>
![Dynamic Secret](../../../images/platform/dynamic-secrets/dynamic-secret.png) ![Dynamic Secret](../../../images/platform/dynamic-secrets/dynamic-secret.png)

View File

@ -68,17 +68,9 @@ Create a user with the required permission in your SQL instance. This user will
</Step> </Step>
<Step title="(Optional) Modify SQL Statements"> <Step title="(Optional) Modify SQL Statements">
![Modify SQL Statements Modal](../../../images/platform/dynamic-secrets/modify-sql-statement-mysql.png)
<ParamField path="Username Template" type="string" default="{{randomUsername}}">
Specifies a template for generating usernames. This field allows customization of how usernames are automatically created.
Allowed template variables are
- `{{randomUsername}}`: Random username string
- `{{unixTimestamp}}`: Current Unix timestamp
</ParamField>
<ParamField path="Customize SQL Statement" type="string">
If you want to provide specific privileges for the generated dynamic credentials, you can modify the SQL statement to your needs. This is useful if you want to only give access to a specific table(s). If you want to provide specific privileges for the generated dynamic credentials, you can modify the SQL statement to your needs. This is useful if you want to only give access to a specific table(s).
</ParamField>
![Modify SQL Statements Modal](/images/platform/dynamic-secrets/modify-sql-statement-mysql.png)
</Step> </Step>
<Step title="Click `Submit`"> <Step title="Click `Submit`">
After submitting the form, you will see a dynamic secret created in the dashboard. After submitting the form, you will see a dynamic secret created in the dashboard.

View File

@ -70,23 +70,13 @@ Create a user with the required permission in your SQL instance. This user will
</Step> </Step>
<Step title="(Optional) Modify SQL Statements"> <Step title="(Optional) Modify SQL Statements">
![Modify SQL Statements Modal](../../../images/platform/dynamic-secrets/modify-sql-statement-oracle.png)
<ParamField path="Username Template" type="string" default="{{randomUsername}}">
Specifies a template for generating usernames. This field allows customization of how usernames are automatically created.
Allowed template variables are
- `{{randomUsername}}`: Random username string
- `{{unixTimestamp}}`: Current Unix timestamp
</ParamField>
<ParamField path="Customize SQL Statement" type="string">
If you want to provide specific privileges for the generated dynamic credentials, you can modify the SQL statement to your needs. This is useful if you want to only give access to a specific table(s). If you want to provide specific privileges for the generated dynamic credentials, you can modify the SQL statement to your needs. This is useful if you want to only give access to a specific table(s).
</ParamField>
</Step> </Step>
<Step title="Click 'Submit'"> <Step title="Click 'Submit'">
After submitting the form, you will see a dynamic secret created in the dashboard. After submitting the form, you will see a dynamic secret created in the dashboard.
<Note> <Note>
If this step fails, you may have to add the CA certificate. If this step fails, you may have to add the CA certficate.
</Note> </Note>
![Dynamic Secret](../../../images/platform/dynamic-secrets/dynamic-secret.png) ![Dynamic Secret](../../../images/platform/dynamic-secrets/dynamic-secret.png)

View File

@ -71,23 +71,15 @@ Create a user with the required permission in your SQL instance. This user will
</Step> </Step>
<Step title="(Optional) Modify SQL Statements"> <Step title="(Optional) Modify SQL Statements">
![Modify SQL Statements Modal](../../../images/platform/dynamic-secrets/modify-sql-statements.png)
<ParamField path="Username Template" type="string" default="{{randomUsername}}">
Specifies a template for generating usernames. This field allows customization of how usernames are automatically created.
Allowed template variables are
- `{{randomUsername}}`: Random username string
- `{{unixTimestamp}}`: Current Unix timestamp
</ParamField>
<ParamField path="Customize SQL Statement" type="string">
If you want to provide specific privileges for the generated dynamic credentials, you can modify the SQL statement to your needs. This is useful if you want to only give access to a specific table(s). If you want to provide specific privileges for the generated dynamic credentials, you can modify the SQL statement to your needs. This is useful if you want to only give access to a specific table(s).
</ParamField>
![Modify SQL Statements Modal](../../../images/platform/dynamic-secrets/modify-sql-statements.png)
</Step> </Step>
<Step title="Click 'Submit'"> <Step title="Click 'Submit'">
After submitting the form, you will see a dynamic secret created in the dashboard. After submitting the form, you will see a dynamic secret created in the dashboard.
<Note> <Note>
If this step fails, you may have to add the CA certificate. If this step fails, you may have to add the CA certficate.
</Note> </Note>
![Dynamic Secret](../../../images/platform/dynamic-secrets/dynamic-secret.png) ![Dynamic Secret](../../../images/platform/dynamic-secrets/dynamic-secret.png)

View File

@ -9,6 +9,7 @@ The Infisical RabbitMQ dynamic secret allows you to generate RabbitMQ credential
1. Ensure that the `management` plugin is enabled on your RabbitMQ instance. This is required for the dynamic secret to work. 1. Ensure that the `management` plugin is enabled on your RabbitMQ instance. This is required for the dynamic secret to work.
## Set up Dynamic Secrets with RabbitMQ ## Set up Dynamic Secrets with RabbitMQ
<Steps> <Steps>
@ -18,8 +19,8 @@ The Infisical RabbitMQ dynamic secret allows you to generate RabbitMQ credential
<Step title="Click on the 'Add Dynamic Secret' button"> <Step title="Click on the 'Add Dynamic Secret' button">
![Add Dynamic Secret Button](../../../images/platform/dynamic-secrets/add-dynamic-secret-button.png) ![Add Dynamic Secret Button](../../../images/platform/dynamic-secrets/add-dynamic-secret-button.png)
</Step> </Step>
<Step title="Select RabbitMQ"> <Step title="Select 'RabbitMQ'">
![Dynamic Secret Modal](../../../images/platform/dynamic-secrets/dynamic-secret-rabbit-mq-modal.png) ![Dynamic Secret Modal](../../../images/platform/dynamic-secrets/dynamic-secret-modal-rabbit-mq.png)
</Step> </Step>
<Step title="Provide the inputs for dynamic secret parameters"> <Step title="Provide the inputs for dynamic secret parameters">
<ParamField path="Secret Name" type="string" required> <ParamField path="Secret Name" type="string" required>
@ -39,45 +40,34 @@ The Infisical RabbitMQ dynamic secret allows you to generate RabbitMQ credential
</ParamField> </ParamField>
<ParamField path="Port" type="string" required> <ParamField path="Port" type="string" required>
The port that the RabbitMQ management plugin is listening on. This is `15672` by default.
</ParamField>
The port that the RabbitMQ management plugin is listening on. This is `15672` by default. <ParamField path="Virtual host name" type="string" required>
</ParamField> The name of the virtual host that the user will be assigned to. This defaults to `/`.
</ParamField>
<ParamField path="Virtual host name" type="string" required>
The name of the virtual host that the user will be assigned to. This defaults
to `/`.
</ParamField>
<ParamField path="Virtual host permissions (Read/Write/Configure)" type="string" required> <ParamField path="Virtual host permissions (Read/Write/Configure)" type="string" required>
The permissions that the user will have on the virtual host. This defaults to `.*`. The permissions that the user will have on the virtual host. This defaults to `.*`.
The three permission fields all take a regular expression _(regex)_, that should match resource names for which the user is granted read / write / configuration permissions The three permission fields all take a regular expression _(regex)_, that should match resource names for which the user is granted read / write / configuration permissions
</ParamField> </ParamField>
<ParamField path="Username" type="string" required>
The username of the user that will be used to provision new dynamic secret
leases.
</ParamField>
<ParamField path="Password" type="string" required> <ParamField path="Username" type="string" required>
The password of the user that will be used to provision new dynamic secret The username of the user that will be used to provision new dynamic secret leases.
leases. </ParamField>
</ParamField>
<ParamField path="Username Template" type="string" default="{{randomUsername}}"> <ParamField path="Password" type="string" required>
Specifies a template for generating usernames. This field allows customization of how usernames are automatically created. The password of the user that will be used to provision new dynamic secret leases.
</ParamField>
Allowed template variables are <ParamField path="CA(SSL)" type="string">
- `{{randomUsername}}`: Random username string
- `{{unixTimestamp}}`: Current Unix timestamp
</ParamField>
<ParamField path="CA(SSL)" type="string">
A CA may be required if your DB requires it for incoming connections. This is often the case when connecting to a managed service. A CA may be required if your DB requires it for incoming connections. This is often the case when connecting to a managed service.
</ParamField> </ParamField>
![Dynamic Secret Setup Modal](../../../images/platform/dynamic-secrets/dynamic-secret-input-modal-rabbit-mq.png)
![Dynamic Secret Setup Modal](../../../images/platform/dynamic-secrets/dynamic-secret-input-modal-rabbit-mq.png)
</Step> </Step>
<Step title="Click `Submit`"> <Step title="Click `Submit`">
@ -108,23 +98,19 @@ Allowed template variables are
Once you click the `Submit` button, a new secret lease will be generated and the credentials from it will be shown to you. Once you click the `Submit` button, a new secret lease will be generated and the credentials from it will be shown to you.
![Provision Lease](/images/platform/dynamic-secrets/lease-values.png) ![Provision Lease](/images/platform/dynamic-secrets/lease-values.png)
</Step> </Step>
</Steps> </Steps>
## Audit or Revoke Leases ## Audit or Revoke Leases
Once you have created one or more leases, you will be able to access them by clicking on the respective dynamic secret item on the dashboard. Once you have created one or more leases, you will be able to access them by clicking on the respective dynamic secret item on the dashboard.
This will allow you to see the expiration time of the lease or delete a lease before it's set time to live. This will allow you to see the expiration time of the lease or delete a lease before it's set time to live.
![Provision Lease](/images/platform/dynamic-secrets/lease-data.png) ![Provision Lease](/images/platform/dynamic-secrets/lease-data.png)
## Renew Leases ## Renew Leases
To extend the life of the generated dynamic secret leases past its initial time to live, simply click on the **Renew** button as illustrated below. To extend the life of the generated dynamic secret leases past its initial time to live, simply click on the **Renew** button as illustrated below.
![Provision Lease](/images/platform/dynamic-secrets/dynamic-secret-lease-renew.png) ![Provision Lease](/images/platform/dynamic-secrets/dynamic-secret-lease-renew.png)
<Warning> <Warning>
Lease renewals cannot exceed the maximum TTL set when configuring the dynamic Lease renewals cannot exceed the maximum TTL set when configuring the dynamic secret
secret
</Warning> </Warning>

View File

@ -56,17 +56,9 @@ Create a user with the required permission in your Redis instance. This user wil
</Step> </Step>
<Step title="(Optional) Modify Redis Statements"> <Step title="(Optional) Modify Redis Statements">
![Modify Redis Statements Modal](/images/platform/dynamic-secrets/modify-redis-statement.png)
<ParamField path="Username Template" type="string" default="{{randomUsername}}">
Specifies a template for generating usernames. This field allows customization of how usernames are automatically created.
Allowed template variables are
- `{{randomUsername}}`: Random username string
- `{{unixTimestamp}}`: Current Unix timestamp
</ParamField>
<ParamField path="Customize Redis Statement" type="string">
If you want to provide specific privileges for the generated dynamic credentials, you can modify the Redis statement to your needs. This is useful if you want to only give access to a specific table(s). If you want to provide specific privileges for the generated dynamic credentials, you can modify the Redis statement to your needs. This is useful if you want to only give access to a specific table(s).
</ParamField>
![Modify Redis Statements Modal](/images/platform/dynamic-secrets/modify-redis-statement.png)
</Step> </Step>
<Step title="Click `Submit`"> <Step title="Click `Submit`">
After submitting the form, you will see a dynamic secret created in the dashboard. After submitting the form, you will see a dynamic secret created in the dashboard.

View File

@ -62,22 +62,13 @@ The Infisical SAP ASE dynamic secret allows you to generate SAP ASE database cre
![Dynamic Secret Setup Modal](../../../images/platform/dynamic-secrets/sap-ase/dynamic-secret-sap-ase-setup-modal.png) ![Dynamic Secret Setup Modal](../../../images/platform/dynamic-secrets/sap-ase/dynamic-secret-sap-ase-setup-modal.png)
</Step> </Step>
<Step title="(Optional) Modify SAP SQL Statements"> <Step title="(Optional) Modify SQL Statements">
If you want to provide specific privileges for the generated dynamic credentials, you can modify the SQL statement to your needs.
![Modify SQL Statements Modal](../../../images/platform/dynamic-secrets/sap-ase/dynamic-secret-sap-ase-statements.png) ![Modify SQL Statements Modal](../../../images/platform/dynamic-secrets/sap-ase/dynamic-secret-sap-ase-statements.png)
<ParamField path="Username Template" type="string" default="{{randomUsername}}">
Specifies a template for generating usernames. This field allows customization of how usernames are automatically created.
Allowed template variables are <Warning>
- `{{randomUsername}}`: Random username string Due to SAP ASE limitations, the attached SQL statements are not executed as a transaction.
- `{{unixTimestamp}}`: Current Unix timestamp </Warning>
</ParamField>
<ParamField path="Customize Statement" type="string">
If you want to provide specific privileges for the generated dynamic credentials, you can modify the SQL statement to your needs.
<Warning>
Due to SAP ASE limitations, the attached SQL statements are not executed as a transaction.
</Warning>
</ParamField>
</Step> </Step>
<Step title="Click 'Submit'"> <Step title="Click 'Submit'">

View File

@ -62,25 +62,14 @@ The Infisical SAP HANA dynamic secret allows you to generate SAP HANA database c
![Dynamic Secret Setup Modal](../../../images/platform/dynamic-secrets/dynamic-secret-setup-modal-sap-hana.png) ![Dynamic Secret Setup Modal](../../../images/platform/dynamic-secrets/dynamic-secret-setup-modal-sap-hana.png)
</Step> </Step>
<Step title="(Optional) Modify SAP SQL Statements"> <Step title="(Optional) Modify SQL Statements">
![Modify SQL Statements Modal](../../../images/platform/dynamic-secrets/modify-sap-hana-sql-statements.png)
<ParamField path="Username Template" type="string" default="{{randomUsername}}">
Specifies a template for generating usernames. This field allows customization of how usernames are automatically created.
Allowed template variables are
- `{{randomUsername}}`: Random username string
- `{{unixTimestamp}}`: Current Unix timestamp
</ParamField>
<ParamField path="Customize Statement" type="string">
If you want to provide specific privileges for the generated dynamic credentials, you can modify the SQL statement to your needs. If you want to provide specific privileges for the generated dynamic credentials, you can modify the SQL statement to your needs.
![Modify SQL Statements Modal](../../../images/platform/dynamic-secrets/modify-sap-hana-sql-statements.png)
<Warning> <Warning>
Due to SAP HANA limitations, the attached SQL statements are not executed as a transaction. Due to SAP HANA limitations, the attached SQL statements are not executed as a transaction.
</Warning> </Warning>
</ParamField>
</Step> </Step>
<Step title="Click 'Submit'"> <Step title="Click 'Submit'">
After submitting the form, you will see a dynamic secret created in the dashboard. After submitting the form, you will see a dynamic secret created in the dashboard.

View File

@ -8,26 +8,21 @@ Infisical's Snowflake dynamic secrets allow you to generate Snowflake user crede
## Snowflake Prerequisites ## Snowflake Prerequisites
<Note> <Note>
Infisical requires a Snowflake user in your account with the USERADMIN role. Infisical requires a Snowflake user in your account with the USERADMIN role. This user will act as a service account for Infisical and facilitate the creation of new users as needed.
This user will act as a service account for Infisical and facilitate the
creation of new users as needed.
</Note> </Note>
<Steps> <Steps>
<Step title="Navigate to Snowflake's User Dashboard and press the '+ User' button"> <Step title="Navigate to Snowflake's User Dashboard and press the '+ User' button">
![Snowflake User ![Snowflake User Dashboard](/images/platform/dynamic-secrets/snowflake/dynamic-secret-snowflake-users-page.png)
Dashboard](/images/platform/dynamic-secrets/snowflake/dynamic-secret-snowflake-users-page.png)
</Step> </Step>
<Step title="Create a Snowflake user with the USERADMIN role for Infisical"> <Step title="Create a Snowflake user with the USERADMIN role for Infisical">
<Warning> <Warning>
Be sure to uncheck "Force user to change password on first time login" Be sure to uncheck "Force user to change password on first time login"
</Warning> </Warning>
![Snowflake Create Service ![Snowflake Create Service User](/images/platform/dynamic-secrets/snowflake/dynamic-secret-snowflake-create-service-user.png)
User](/images/platform/dynamic-secrets/snowflake/dynamic-secret-snowflake-create-service-user.png)
</Step> </Step>
<Step title="Click on the Account Menu in the bottom left and take note of your Account and Organization identifiers"> <Step title="Click on the Account Menu in the bottom left and take note of your Account and Organization identifiers">
![Snowflake Account And Organization ![Snowflake Account And Organization Identifiers](/images/platform/dynamic-secrets/snowflake/dynamic-secret-snowflake-identifiers.png)
Identifiers](/images/platform/dynamic-secrets/snowflake/dynamic-secret-snowflake-identifiers.png)
</Step> </Step>
</Steps> </Steps>
@ -76,23 +71,10 @@ Infisical's Snowflake dynamic secrets allow you to generate Snowflake user crede
</Step> </Step>
<Step title="(Optional) Modify SQL Statements"> <Step title="(Optional) Modify SQL Statements">
![Modify SQL Statements Modal](/images/platform/dynamic-secrets/snowflake/dynamic-secret-snowflake-sql-statements.png)
<ParamField path="Username Template" type="string" default="{{randomUsername}}">
Specifies a template for generating usernames. This field allows customization of how usernames are automatically created.
Allowed template variables are
- `{{randomUsername}}`: Random username string
- `{{unixTimestamp}}`: Current Unix timestamp
</ParamField>
<ParamField path="Customize Statement" type="string">
If you want to provide specific privileges for the generated dynamic credentials, you can modify the SQL If you want to provide specific privileges for the generated dynamic credentials, you can modify the SQL
statement to your needs. statement to your needs.
![Modify SQL Statements Modal](/images/platform/dynamic-secrets/snowflake/dynamic-secret-snowflake-sql-statements.png)
</ParamField>
</Step> </Step>
<Step title="Click 'Submit'"> <Step title="Click 'Submit'">
After submitting the form, you will see a dynamic secret created in the dashboard. After submitting the form, you will see a dynamic secret created in the dashboard.
</Step> </Step>
@ -122,7 +104,6 @@ Infisical's Snowflake dynamic secrets allow you to generate Snowflake user crede
![Provision Lease](/images/platform/dynamic-secrets/lease-values.png) ![Provision Lease](/images/platform/dynamic-secrets/lease-values.png)
</Step> </Step>
</Steps> </Steps>
## Audit or Revoke Leases ## Audit or Revoke Leases

Binary file not shown.

Before

Width:  |  Height:  |  Size: 482 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 459 KiB

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 445 KiB

After

Width:  |  Height:  |  Size: 419 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 532 KiB

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 526 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 501 KiB

After

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 495 KiB

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 555 KiB

After

Width:  |  Height:  |  Size: 607 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 566 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 577 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 572 KiB

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 595 KiB

After

Width:  |  Height:  |  Size: 210 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 568 KiB

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 546 KiB

After

Width:  |  Height:  |  Size: 666 KiB

View File

@ -1,2 +1,16 @@
export * from "./mutation"; export {
export * from "./queries"; useAdminDeleteUser,
useAdminGrantServerAdminAccess,
useAdminRemoveIdentitySuperAdminAccess,
useCreateAdminUser,
useInvalidateCache,
useRemoveUserServerAdminAccess,
useUpdateServerConfig,
useUpdateServerEncryptionStrategy
} from "./mutation";
export {
useAdminGetUsers,
useGetAdminIntegrationsConfig,
useGetServerConfig,
useGetServerRootKmsEncryptionDetails
} from "./queries";

View File

@ -63,41 +63,6 @@ export const useAdminDeleteUser = () => {
queryClient.invalidateQueries({ queryClient.invalidateQueries({
queryKey: [adminStandaloneKeys.getUsers] queryKey: [adminStandaloneKeys.getUsers]
}); });
queryClient.invalidateQueries({ queryKey: adminStandaloneKeys.getOrganizations });
}
});
};
export const useAdminDeleteOrganizationMembership = () => {
const queryClient = useQueryClient();
return useMutation<object, object, { organizationId: string; membershipId: string }>({
mutationFn: async ({ organizationId, membershipId }) => {
await apiRequest.delete(
`/api/v1/admin/organization-management/organizations/${organizationId}/memberships/${membershipId}`
);
return {};
},
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: [adminStandaloneKeys.getOrganizations]
});
}
});
};
export const useAdminDeleteOrganization = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (organizationId: string) => {
await apiRequest.delete(
`/api/v1/admin/organization-management/organizations/${organizationId}`
);
},
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: [adminStandaloneKeys.getOrganizations]
});
} }
}); });
}; };

View File

@ -6,10 +6,8 @@ import { Identity } from "@app/hooks/api/identities/types";
import { User } from "../types"; import { User } from "../types";
import { import {
AdminGetIdentitiesFilters, AdminGetIdentitiesFilters,
AdminGetOrganizationsFilters,
AdminGetUsersFilters, AdminGetUsersFilters,
AdminIntegrationsConfig, AdminIntegrationsConfig,
OrganizationWithProjects,
TGetInvalidatingCacheStatus, TGetInvalidatingCacheStatus,
TGetServerRootKmsEncryptionDetails, TGetServerRootKmsEncryptionDetails,
TServerConfig TServerConfig
@ -17,15 +15,12 @@ import {
export const adminStandaloneKeys = { export const adminStandaloneKeys = {
getUsers: "get-users", getUsers: "get-users",
getOrganizations: "get-organizations",
getIdentities: "get-identities" getIdentities: "get-identities"
}; };
export const adminQueryKeys = { export const adminQueryKeys = {
serverConfig: () => ["server-config"] as const, serverConfig: () => ["server-config"] as const,
getUsers: (filters: AdminGetUsersFilters) => [adminStandaloneKeys.getUsers, { filters }] as const, getUsers: (filters: AdminGetUsersFilters) => [adminStandaloneKeys.getUsers, { filters }] as const,
getOrganizations: (filters: AdminGetOrganizationsFilters) =>
[adminStandaloneKeys.getOrganizations, { filters }] as const,
getIdentities: (filters: AdminGetIdentitiesFilters) => getIdentities: (filters: AdminGetIdentitiesFilters) =>
[adminStandaloneKeys.getIdentities, { filters }] as const, [adminStandaloneKeys.getIdentities, { filters }] as const,
getAdminSlackConfig: () => ["admin-slack-config"] as const, getAdminSlackConfig: () => ["admin-slack-config"] as const,
@ -39,28 +34,6 @@ export const fetchServerConfig = async () => {
return data.config; return data.config;
}; };
export const useAdminGetOrganizations = (filters: AdminGetOrganizationsFilters) => {
return useInfiniteQuery({
initialPageParam: 0,
queryKey: adminQueryKeys.getOrganizations(filters),
queryFn: async ({ pageParam }) => {
const { data } = await apiRequest.get<{ organizations: OrganizationWithProjects[] }>(
"/api/v1/admin/organization-management/organizations",
{
params: {
...filters,
offset: pageParam
}
}
);
return data.organizations;
},
getNextPageParam: (lastPage, pages) =>
lastPage.length !== 0 ? pages.length * filters.limit : undefined
});
};
export const useGetServerConfig = ({ export const useGetServerConfig = ({
options = {} options = {}
}: { }: {

View File

@ -1,5 +1,3 @@
import { Organization } from "../types";
export enum LoginMethod { export enum LoginMethod {
EMAIL = "email", EMAIL = "email",
GOOGLE = "google", GOOGLE = "google",
@ -10,27 +8,6 @@ export enum LoginMethod {
OIDC = "oidc" OIDC = "oidc"
} }
export type OrganizationWithProjects = Organization & {
members: {
user: {
id: string;
email: string | null;
username: string;
firstName: string | null;
lastName: string | null;
};
membershipId: string;
role: string;
roleId: string | null;
}[];
projects: {
name: string;
id: string;
slug: string;
createdAt: string;
}[];
};
export type TServerConfig = { export type TServerConfig = {
initialized: boolean; initialized: boolean;
allowSignUp: boolean; allowSignUp: boolean;
@ -74,11 +51,6 @@ export type TCreateAdminUserDTO = {
salt: string; salt: string;
}; };
export type AdminGetOrganizationsFilters = {
limit: number;
searchTerm: string;
};
export type AdminGetUsersFilters = { export type AdminGetUsersFilters = {
limit: number; limit: number;
searchTerm: string; searchTerm: string;

View File

@ -13,7 +13,6 @@ export type TDynamicSecret = {
status?: DynamicSecretStatus; status?: DynamicSecretStatus;
statusDetails?: string; statusDetails?: string;
maxTTL: string; maxTTL: string;
usernameTemplate?: string | null;
metadata?: { key: string; value: string }[]; metadata?: { key: string; value: string }[];
}; };
@ -288,7 +287,6 @@ export type TCreateDynamicSecretDTO = {
environmentSlug: string; environmentSlug: string;
name: string; name: string;
metadata?: { key: string; value: string }[]; metadata?: { key: string; value: string }[];
usernameTemplate?: string;
}; };
export type TUpdateDynamicSecretDTO = { export type TUpdateDynamicSecretDTO = {
@ -302,7 +300,6 @@ export type TUpdateDynamicSecretDTO = {
defaultTTL?: string; defaultTTL?: string;
maxTTL?: string | null; maxTTL?: string | null;
inputs?: unknown; inputs?: unknown;
usernameTemplate?: string | null;
}; };
}; };

View File

@ -1,4 +1,3 @@
import { useState } from "react";
import { MutationCache, QueryClient } from "@tanstack/react-query"; import { MutationCache, QueryClient } from "@tanstack/react-query";
import axios from "axios"; import axios from "axios";
@ -19,15 +18,22 @@ export const SIGNUP_TEMP_TOKEN_CACHE_KEY = ["infisical__signup-temp-token"];
export const MFA_TEMP_TOKEN_CACHE_KEY = ["infisical__mfa-temp-token"]; export const MFA_TEMP_TOKEN_CACHE_KEY = ["infisical__mfa-temp-token"];
export const AUTH_TOKEN_CACHE_KEY = ["infisical__auth-token"]; export const AUTH_TOKEN_CACHE_KEY = ["infisical__auth-token"];
function ValidationErrorModal({ serverResponse }: { serverResponse: TApiErrors }) { export const onRequestError = (error: unknown) => {
const [open, setOpen] = useState(true); if (axios.isAxiosError(error)) {
const serverResponse = error.response?.data as TApiErrors;
if (serverResponse.error !== ApiErrorTypes.ValidationError) { if (serverResponse?.error === ApiErrorTypes.ValidationError) {
return null; createNotification(
} {
title: "Validation Error",
return ( type: "error",
<Modal isOpen={open} onOpenChange={setOpen}> text: "Please check the input and try again.",
callToAction: (
<Modal>
<ModalTrigger asChild>
<Button variant="outline_bg" size="xs">
Show more
</Button>
</ModalTrigger>
<ModalContent title="Validation Error Details"> <ModalContent title="Validation Error Details">
<TableContainer> <TableContainer>
<Table> <Table>
@ -49,19 +55,7 @@ function ValidationErrorModal({ serverResponse }: { serverResponse: TApiErrors }
</TableContainer> </TableContainer>
</ModalContent> </ModalContent>
</Modal> </Modal>
); ),
}
export const onRequestError = (error: unknown) => {
if (axios.isAxiosError(error)) {
const serverResponse = error.response?.data as TApiErrors;
if (serverResponse?.error === ApiErrorTypes.ValidationError) {
createNotification(
{
title: "Validation Error",
type: "error",
text: "Please check the input and try again.",
callToAction: <ValidationErrorModal serverResponse={serverResponse} />,
copyActions: [ copyActions: [
{ {
value: serverResponse.reqId, value: serverResponse.reqId,

View File

@ -1,23 +1,27 @@
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { faMobile } from "@fortawesome/free-solid-svg-icons"; import { faArrowLeft, faInfo, faMobile, faQuestion } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Outlet, useRouterState } from "@tanstack/react-router"; import { Link, Outlet } from "@tanstack/react-router";
import { WishForm } from "@app/components/features/WishForm";
import { Banner } from "@app/components/page-frames/Banner"; import { Banner } from "@app/components/page-frames/Banner";
import { BreadcrumbContainer, TBreadcrumbFormat } from "@app/components/v2"; import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger
} from "@app/components/v2";
import { envConfig } from "@app/config/env";
import { useServerConfig } from "@app/context"; import { useServerConfig } from "@app/context";
import { ProjectType } from "@app/hooks/api/workspace/types";
import { InsecureConnectionBanner } from "../OrganizationLayout/components/InsecureConnectionBanner"; import { InsecureConnectionBanner } from "../OrganizationLayout/components/InsecureConnectionBanner";
import { AdminSidebar } from "./Sidebar"; import { INFISICAL_SUPPORT_OPTIONS } from "../OrganizationLayout/components/MinimizedOrgSidebar/MinimizedOrgSidebar";
export const AdminLayout = () => { export const AdminLayout = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { config } = useServerConfig(); const { config } = useServerConfig();
const matches = useRouterState({ select: (s) => s.matches.at(-1)?.context });
const breadcrumbs = matches && "breadcrumbs" in matches ? matches.breadcrumbs : undefined;
const containerHeight = config.pageFrameContent ? "h-[94vh]" : "h-screen"; const containerHeight = config.pageFrameContent ? "h-[94vh]" : "h-screen";
return ( return (
@ -26,11 +30,54 @@ export const AdminLayout = () => {
<div className={`dark hidden ${containerHeight} w-full flex-col overflow-x-hidden md:flex`}> <div className={`dark hidden ${containerHeight} w-full flex-col overflow-x-hidden md:flex`}>
{!window.isSecureContext && <InsecureConnectionBanner />} {!window.isSecureContext && <InsecureConnectionBanner />}
<div className="flex flex-grow flex-col overflow-y-hidden md:flex-row"> <div className="flex flex-grow flex-col overflow-y-hidden md:flex-row">
<AdminSidebar /> <aside className="dark w-full border-r border-mineshaft-600 bg-gradient-to-tr from-mineshaft-700 via-mineshaft-800 to-mineshaft-900 md:w-60">
<main className="flex-1 overflow-y-auto overflow-x-hidden bg-bunker-800 px-4 pb-4 dark:[color-scheme:dark]"> <nav className="items-between flex h-full flex-col justify-between overflow-y-auto dark:[color-scheme:dark]">
{breadcrumbs ? ( <div className="flex-grow">
<BreadcrumbContainer breadcrumbs={breadcrumbs as TBreadcrumbFormat[]} /> <Link to={`/organization/${ProjectType.SecretManager}/overview` as const}>
) : null} <div className="my-6 flex cursor-default items-center justify-center pr-2 text-sm text-mineshaft-300 hover:text-mineshaft-100">
<FontAwesomeIcon icon={faArrowLeft} className="pr-3" />
Back to organization
</div>
</Link>
</div>
<div className="relative mt-10 flex w-full cursor-default flex-col items-center px-3 text-sm text-mineshaft-400">
{(window.location.origin.includes("https://app.infisical.com") ||
window.location.origin.includes("https://gamma.infisical.com")) && <WishForm />}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<div className="mb-2 w-full pl-5 duration-200 hover:text-mineshaft-200">
<FontAwesomeIcon icon={faQuestion} className="mr-3 px-[0.1rem]" />
Help & Support
</div>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="p-1">
{INFISICAL_SUPPORT_OPTIONS.map(([icon, text, url]) => (
<DropdownMenuItem key={url as string}>
<a
target="_blank"
rel="noopener noreferrer"
href={String(url)}
className="flex w-full items-center rounded-md font-normal text-mineshaft-300 duration-200"
>
<div className="relative flex w-full cursor-pointer select-none items-center justify-start rounded-md">
{icon}
<div className="text-sm">{text}</div>
</div>
</a>
</DropdownMenuItem>
))}
{envConfig.PLATFORM_VERSION && (
<div className="mb-2 mt-2 w-full cursor-default pl-5 text-sm duration-200 hover:text-mineshaft-200">
<FontAwesomeIcon icon={faInfo} className="mr-4 px-[0.1rem]" />
Version: {envConfig.PLATFORM_VERSION}
</div>
)}
</DropdownMenuContent>
</DropdownMenu>
</div>
</nav>
</aside>
<main className="flex-1 overflow-y-auto overflow-x-hidden bg-bunker-800 dark:[color-scheme:dark]">
<Outlet /> <Outlet />
</main> </main>
</div> </div>

View File

@ -1,141 +0,0 @@
import { faArrowLeft, faInfo, faQuestion } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link, useMatchRoute } from "@tanstack/react-router";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
Menu,
MenuGroup,
MenuItem
} from "@app/components/v2";
import { envConfig } from "@app/config/env";
import { ProjectType } from "@app/hooks/api/workspace/types";
import { INFISICAL_SUPPORT_OPTIONS } from "../OrganizationLayout/components/MinimizedOrgSidebar/MinimizedOrgSidebar";
const generalTabs = [
{
label: "General",
icon: "settings-cog",
link: "/admin/"
},
{
label: "Encryption",
icon: "lock-closed",
link: "/admin/encryption"
},
{
label: "Authentication",
icon: "check",
link: "/admin/authentication"
},
{
label: "Integrations",
icon: "sliding-carousel",
link: "/admin/integrations"
},
{
label: "Caching",
icon: "note",
link: "/admin/caching"
}
];
const resourceTabs = [
{
label: "Organizations",
icon: "groups",
link: "/admin/resources/organizations"
},
{
label: "User Identities",
icon: "user",
link: "/admin/resources/user-identities"
},
{
label: "Machine Identities",
icon: "key-user",
link: "/admin/resources/machine-identities"
}
];
export const AdminSidebar = () => {
const matchRoute = useMatchRoute();
return (
<aside className="dark w-full border-r border-mineshaft-600 bg-gradient-to-tr from-mineshaft-700 via-mineshaft-800 to-mineshaft-900 md:w-60">
<nav className="items-between flex h-full flex-col justify-between overflow-y-auto dark:[color-scheme:dark]">
<div className="flex-grow">
<Link to={`/organization/${ProjectType.SecretManager}/overview` as const}>
<div className="my-6 flex cursor-default items-center justify-center pr-2 text-sm text-mineshaft-300 hover:text-mineshaft-100">
<FontAwesomeIcon icon={faArrowLeft} className="pr-3" />
Back to organization
</div>
</Link>
<Menu>
<MenuGroup title="General">
{generalTabs.map((tab) => {
const isActive = matchRoute({ to: tab.link, fuzzy: false });
return (
<Link key={tab.link} to={tab.link}>
<MenuItem isSelected={Boolean(isActive)} icon={tab.icon}>
{tab.label}
</MenuItem>
</Link>
);
})}
</MenuGroup>
<MenuGroup title="Resources">
{resourceTabs.map((tab) => {
const isActive = matchRoute({ to: tab.link, fuzzy: false });
return (
<Link key={tab.link} to={tab.link}>
<MenuItem isSelected={Boolean(isActive)} icon={tab.icon}>
{tab.label}
</MenuItem>
</Link>
);
})}
</MenuGroup>
</Menu>
</div>
<div className="relative mb-4 mt-10 flex w-full cursor-default flex-col items-center px-3 text-sm text-mineshaft-400">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<div className="mb-2 w-full pl-5 duration-200 hover:text-mineshaft-200">
<FontAwesomeIcon icon={faQuestion} className="mr-3 px-[0.1rem]" />
Help & Support
</div>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="p-1">
{INFISICAL_SUPPORT_OPTIONS.map(([icon, text, url]) => (
<DropdownMenuItem key={url as string}>
<a
target="_blank"
rel="noopener noreferrer"
href={String(url)}
className="flex w-full items-center rounded-md font-normal text-mineshaft-300 duration-200"
>
<div className="relative flex w-full cursor-pointer select-none items-center justify-start rounded-md">
{icon}
<div className="text-sm">{text}</div>
</div>
</a>
</DropdownMenuItem>
))}
{envConfig.PLATFORM_VERSION && (
<div className="mb-2 mt-2 w-full cursor-default pl-5 text-sm duration-200 hover:text-mineshaft-200">
<FontAwesomeIcon icon={faInfo} className="mr-4 px-[0.1rem]" />
Version: {envConfig.PLATFORM_VERSION}
</div>
)}
</DropdownMenuContent>
</DropdownMenu>
</div>
</nav>
</aside>
);
};

View File

@ -1,27 +0,0 @@
import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next";
import { PageHeader } from "@app/components/v2";
import { AuthenticationPageForm } from "./components";
export const AuthenticationPage = () => {
const { t } = useTranslation();
return (
<div className="h-full bg-bunker-800">
<Helmet>
<title>{t("common.head-title", { title: "Admin" })}</title>
</Helmet>
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
<div className="mx-auto mb-6 w-full max-w-7xl">
<PageHeader
title="Authentication"
description="Manage authentication settings for your Infisical instance."
/>
<AuthenticationPageForm />
</div>
</div>
</div>
);
};

View File

@ -1 +0,0 @@
export { AuthenticationPageForm } from "./AuthenticationPageForm";

View File

@ -1,25 +0,0 @@
import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { AuthenticationPage } from "./AuthenticationPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/admin/_admin-layout/authentication"
)({
component: AuthenticationPage,
beforeLoad: async () => {
return {
breadcrumbs: [
{
label: "Admin",
link: linkOptions({ to: "/admin" })
},
{
label: "Authentication",
link: linkOptions({
to: "/admin/authentication"
})
}
]
};
}
});

View File

@ -1,24 +0,0 @@
import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next";
import { PageHeader } from "@app/components/v2";
import { CachingPageForm } from "./components";
export const CachingPage = () => {
const { t } = useTranslation();
return (
<div className="h-full bg-bunker-800">
<Helmet>
<title>{t("common.head-title", { title: "Admin" })}</title>
</Helmet>
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
<div className="mx-auto mb-6 w-full max-w-7xl">
<PageHeader title="Caching" description="Manage caching for your Infisical instance." />
<CachingPageForm />
</div>
</div>
</div>
);
};

View File

@ -1 +0,0 @@
export { CachingPageForm } from "./CachingPageForm";

View File

@ -1,25 +0,0 @@
import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { CachingPage } from "./CachingPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/admin/_admin-layout/caching"
)({
component: CachingPage,
beforeLoad: async () => {
return {
breadcrumbs: [
{
label: "Admin",
link: linkOptions({ to: "/admin" })
},
{
label: "Caching",
link: linkOptions({
to: "/admin/caching"
})
}
]
};
}
});

View File

@ -1,27 +0,0 @@
import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next";
import { PageHeader } from "@app/components/v2";
import { EncryptionPageForm } from "./components";
export const EncryptionPage = () => {
const { t } = useTranslation();
return (
<div className="h-full bg-bunker-800">
<Helmet>
<title>{t("common.head-title", { title: "Admin" })}</title>
</Helmet>
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
<div className="mx-auto mb-6 w-full max-w-7xl">
<PageHeader
title="Encryption"
description="Manage encryption settings for your Infisical instance."
/>
<EncryptionPageForm />
</div>
</div>
</div>
);
};

View File

@ -1 +0,0 @@
export { EncryptionPageForm } from "./EncryptionPageForm";

View File

@ -1,25 +0,0 @@
import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { EncryptionPage } from "./EncryptionPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/admin/_admin-layout/encryption"
)({
component: EncryptionPage,
beforeLoad: async () => {
return {
breadcrumbs: [
{
label: "Admin",
link: linkOptions({ to: "/admin" })
},
{
label: "Encryption",
link: linkOptions({
to: "/admin/encryption"
})
}
]
};
}
});

View File

@ -1,27 +0,0 @@
import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next";
import { PageHeader } from "@app/components/v2";
import { GeneralPageForm } from "./components";
export const GeneralPage = () => {
const { t } = useTranslation();
return (
<div className="h-full bg-bunker-800">
<Helmet>
<title>{t("common.head-title", { title: "Admin" })}</title>
</Helmet>
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
<div className="mx-auto mb-6 w-full max-w-7xl">
<PageHeader
title="General"
description="Manage general settings for your Infisical instance."
/>
<GeneralPageForm />
</div>
</div>
</div>
);
};

View File

@ -1,309 +0,0 @@
import { Controller, useForm } from "react-hook-form";
import { faAt } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { createNotification } from "@app/components/notifications";
import {
Button,
FormControl,
Input,
Select,
SelectClear,
SelectItem,
Switch,
TextArea
} from "@app/components/v2";
import { useServerConfig } from "@app/context";
import { useGetOrganizations, useUpdateServerConfig } from "@app/hooks/api";
enum SignUpModes {
Disabled = "disabled",
Anyone = "anyone"
}
const formSchema = z.object({
signUpMode: z.nativeEnum(SignUpModes),
allowedSignUpDomain: z.string().optional().nullable(),
trustSamlEmails: z.boolean(),
trustLdapEmails: z.boolean(),
trustOidcEmails: z.boolean(),
defaultAuthOrgId: z.string(),
authConsentContent: z.string().optional().default(""),
pageFrameContent: z.string().optional().default("")
});
type TDashboardForm = z.infer<typeof formSchema>;
export const GeneralPageForm = () => {
const data = useServerConfig();
const { config } = data;
const {
control,
handleSubmit,
watch,
formState: { isSubmitting, isDirty }
} = useForm<TDashboardForm>({
resolver: zodResolver(formSchema),
values: {
// eslint-disable-next-line
signUpMode: config.allowSignUp ? SignUpModes.Anyone : SignUpModes.Disabled,
allowedSignUpDomain: config.allowedSignUpDomain,
trustSamlEmails: config.trustSamlEmails,
trustLdapEmails: config.trustLdapEmails,
trustOidcEmails: config.trustOidcEmails,
defaultAuthOrgId: config.defaultAuthOrgId ?? "",
authConsentContent: config.authConsentContent ?? "",
pageFrameContent: config.pageFrameContent ?? ""
}
});
const signUpMode = watch("signUpMode");
const defaultAuthOrgId = watch("defaultAuthOrgId");
const { mutateAsync: updateServerConfig } = useUpdateServerConfig();
const organizations = useGetOrganizations();
const onFormSubmit = async (formData: TDashboardForm) => {
try {
const {
allowedSignUpDomain,
trustSamlEmails,
trustLdapEmails,
trustOidcEmails,
authConsentContent,
pageFrameContent
} = formData;
await updateServerConfig({
defaultAuthOrgId: defaultAuthOrgId || null,
allowSignUp: signUpMode !== SignUpModes.Disabled,
allowedSignUpDomain: signUpMode === SignUpModes.Anyone ? allowedSignUpDomain : null,
trustSamlEmails,
trustLdapEmails,
trustOidcEmails,
authConsentContent,
pageFrameContent
});
createNotification({
text: "Successfully changed sign up setting.",
type: "success"
});
} catch (e) {
console.error(e);
createNotification({
type: "error",
text: "Failed to update sign up setting."
});
}
};
return (
<form
className="space-y-8 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"
onSubmit={handleSubmit(onFormSubmit)}
>
<div className="flex flex-col justify-start">
<div className="mb-2 text-xl font-semibold text-mineshaft-100">Allow user signups</div>
<div className="mb-4 max-w-sm text-sm text-mineshaft-400">
Select if you want users to be able to signup freely into your Infisical instance.
</div>
<Controller
control={control}
name="signUpMode"
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
<FormControl className="max-w-sm" errorText={error?.message} isError={Boolean(error)}>
<Select
className="w-full bg-mineshaft-700"
dropdownContainerClassName="bg-mineshaft-800"
defaultValue={field.value}
onValueChange={(e) => onChange(e)}
{...field}
>
<SelectItem value={SignUpModes.Disabled}>Disabled</SelectItem>
<SelectItem value={SignUpModes.Anyone}>Anyone</SelectItem>
</Select>
</FormControl>
)}
/>
</div>
{signUpMode === "anyone" && (
<div className="flex flex-col justify-start">
<div className="mb-4 text-xl font-semibold text-mineshaft-100">
Restrict signup by email domain(s)
</div>
<Controller
control={control}
defaultValue=""
name="allowedSignUpDomain"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Leave blank to allow any email domains"
className="w-72"
isError={Boolean(error)}
errorText={error?.message}
>
<Input
{...field}
value={field.value || ""}
placeholder="gmail.com, aws.com, redhat.com"
leftIcon={<FontAwesomeIcon icon={faAt} />}
/>
</FormControl>
)}
/>
</div>
)}
<div className="flex flex-col justify-start">
<div className="mb-2 text-xl font-semibold text-mineshaft-100">Default organization</div>
<div className="mb-4 max-w-sm text-sm text-mineshaft-400">
Select the default organization you want to set for SAML/LDAP/OIDC/Github logins. When
selected, user logins will be automatically scoped to the selected organization.
</div>
<Controller
control={control}
name="defaultAuthOrgId"
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
<FormControl className="max-w-sm" errorText={error?.message} isError={Boolean(error)}>
<Select
placeholder="Allow all organizations"
className="w-full bg-mineshaft-700"
dropdownContainerClassName="bg-mineshaft-800"
defaultValue={field.value ?? " "}
onValueChange={(e) => onChange(e)}
{...field}
>
<SelectClear
selectValue={defaultAuthOrgId}
onClear={() => {
onChange("");
}}
>
Allow all organizations
</SelectClear>
{organizations.data?.map((org) => (
<SelectItem key={org.id} value={org.id}>
{org.name}
</SelectItem>
))}
</Select>
</FormControl>
)}
/>
</div>
<div className="flex flex-col justify-start">
<div className="mb-2 text-xl font-semibold text-mineshaft-100">Trust emails</div>
<div className="mb-4 max-w-sm text-sm text-mineshaft-400">
Select if you want Infisical to trust external emails from SAML/LDAP/OIDC identity
providers. If set to false, then Infisical will prompt SAML/LDAP/OIDC provisioned users to
verify their email upon their first login.
</div>
<Controller
control={control}
name="trustSamlEmails"
render={({ field, fieldState: { error } }) => {
return (
<FormControl isError={Boolean(error)} errorText={error?.message}>
<Switch
id="trust-saml-emails"
onCheckedChange={(value) => field.onChange(value)}
isChecked={field.value}
>
<p className="w-full">Trust SAML emails</p>
</Switch>
</FormControl>
);
}}
/>
<Controller
control={control}
name="trustLdapEmails"
render={({ field, fieldState: { error } }) => {
return (
<FormControl isError={Boolean(error)} errorText={error?.message}>
<Switch
id="trust-ldap-emails"
onCheckedChange={(value) => field.onChange(value)}
isChecked={field.value}
>
<p className="w-full">Trust LDAP emails</p>
</Switch>
</FormControl>
);
}}
/>
<Controller
control={control}
name="trustOidcEmails"
render={({ field, fieldState: { error } }) => {
return (
<FormControl isError={Boolean(error)} errorText={error?.message}>
<Switch
id="trust-oidc-emails"
onCheckedChange={(value) => field.onChange(value)}
isChecked={field.value}
>
<p className="w-full">Trust OIDC emails</p>
</Switch>
</FormControl>
);
}}
/>
</div>
<div className="flex flex-col justify-start">
<div className="mb-2 text-xl font-semibold text-mineshaft-100">Notices</div>
<div className="mb-4 max-w-lg text-sm text-mineshaft-400">
Configure system-wide notification banners and security messages. These settings control
the text displayed to users during authentication and throughout their session
</div>
<Controller
render={({ field, fieldState: { error } }) => (
<FormControl
isError={Boolean(error)}
errorText={error?.message}
label="Auth Consent Content"
tooltipText="Formatting supported: HTML, Markdown, Plain text"
>
<TextArea
placeholder="**Auth Consent Message**"
{...field}
rows={3}
className="thin-scrollbar h-48 max-w-lg !resize-none bg-mineshaft-800"
/>
</FormControl>
)}
control={control}
name="authConsentContent"
/>
<Controller
render={({ field, fieldState: { error } }) => (
<FormControl
isError={Boolean(error)}
errorText={error?.message}
label="Page Frame Content"
tooltipText="Formatting supported: HTML, Markdown, Plain text"
>
<TextArea
placeholder='<div style="background-color: red">TOP SECRET</div>'
{...field}
rows={3}
className="thin-scrollbar h-48 max-w-lg !resize-none bg-mineshaft-800"
/>
</FormControl>
)}
control={control}
name="pageFrameContent"
/>
</div>
<Button type="submit" isLoading={isSubmitting} isDisabled={isSubmitting || !isDirty}>
Save
</Button>
</form>
);
};

View File

@ -1 +0,0 @@
export { GeneralPageForm } from "./GeneralPageForm";

View File

@ -1,23 +0,0 @@
import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { GeneralPage } from "./GeneralPage";
export const Route = createFileRoute("/_authenticate/_inject-org-details/admin/_admin-layout/")({
component: GeneralPage,
beforeLoad: async () => {
return {
breadcrumbs: [
{
label: "Admin",
link: linkOptions({ to: "/admin" })
},
{
label: "General",
link: linkOptions({
to: "/admin"
})
}
]
};
}
});

Some files were not shown because too many files have changed in this diff Show More