Compare commits
29 Commits
daniel/cli
...
infisical/
Author | SHA1 | Date | |
---|---|---|---|
739ef8e05a | |||
644659bc10 | |||
21e4fa83ef | |||
a6a6c72397 | |||
4061feba21 | |||
90a415722c | |||
f3d5790e2c | |||
0d0fddb53a | |||
9f2e379d4d | |||
14e898351f | |||
16e0aa13c8 | |||
dc130ecd7f | |||
b70c6b6260 | |||
a701635f08 | |||
9eb98dd276 | |||
96e9bc3b2f | |||
90d213a8ab | |||
52a26b51af | |||
abedb4b53c | |||
29561d37e9 | |||
0885620981 | |||
f67511fa19 | |||
44367f9149 | |||
286dc39ed2 | |||
90c36eeded | |||
b5c3f17ec1 | |||
99d88f7687 | |||
8e3559828f | |||
93d7c812e7 |
@ -0,0 +1,21 @@
|
|||||||
|
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");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -28,7 +28,8 @@ 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>;
|
||||||
|
@ -6,6 +6,8 @@ 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";
|
||||||
@ -13,6 +15,28 @@ 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",
|
||||||
@ -52,7 +76,8 @@ 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({
|
||||||
@ -73,39 +98,6 @@ 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",
|
||||||
@ -150,7 +142,8 @@ 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: {
|
||||||
@ -328,4 +321,37 @@ 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;
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
@ -132,7 +132,11 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
|
|
||||||
let result;
|
let result;
|
||||||
try {
|
try {
|
||||||
result = await selectedProvider.create(decryptedStoredInput, expireAt.getTime());
|
result = await selectedProvider.create({
|
||||||
|
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 });
|
||||||
|
@ -11,6 +11,8 @@ 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),
|
||||||
@ -58,7 +60,7 @@ export const verifyHostInputValidity = async (host: string, isGateway = false) =
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isGateway && !(appCfg.DYNAMIC_SECRET_ALLOW_INTERNAL_IP || appCfg.ALLOW_INTERNAL_IP_CONNECTIONS)) {
|
if (!(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" });
|
||||||
}
|
}
|
||||||
|
@ -78,7 +78,8 @@ 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` });
|
||||||
@ -163,7 +164,8 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
defaultTTL,
|
defaultTTL,
|
||||||
folderId: folder.id,
|
folderId: folder.id,
|
||||||
name,
|
name,
|
||||||
gatewayId: selectedGatewayId
|
gatewayId: selectedGatewayId,
|
||||||
|
usernameTemplate
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
@ -199,7 +201,8 @@ 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` });
|
||||||
@ -311,7 +314,8 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
defaultTTL,
|
defaultTTL,
|
||||||
name: newName ?? name,
|
name: newName ?? name,
|
||||||
status: null,
|
status: null,
|
||||||
gatewayId: selectedGatewayId
|
gatewayId: selectedGatewayId,
|
||||||
|
usernameTemplate
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
@ -22,6 +22,7 @@ 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 = {
|
||||||
@ -34,6 +35,7 @@ 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 = {
|
||||||
|
@ -132,9 +132,15 @@ const generatePassword = () => {
|
|||||||
return customAlphabet(charset, 64)();
|
return customAlphabet(charset, 64)();
|
||||||
};
|
};
|
||||||
|
|
||||||
const generateUsername = () => {
|
const generateUsername = (usernameTemplate?: string | null) => {
|
||||||
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-";
|
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-";
|
||||||
return `inf-${customAlphabet(charset, 32)()}`; // Username must start with an ascii letter, so we prepend the username with "inf-"
|
const randomUsername = `inf-${customAlphabet(charset, 32)()}`;
|
||||||
|
if (!usernameTemplate) return randomUsername;
|
||||||
|
|
||||||
|
return handlebars.compile(usernameTemplate)({
|
||||||
|
randomUsername,
|
||||||
|
unixTimestamp: Math.floor(Date.now() / 100)
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AwsElastiCacheDatabaseProvider = (): TDynamicProviderFns => {
|
export const AwsElastiCacheDatabaseProvider = (): TDynamicProviderFns => {
|
||||||
@ -168,13 +174,14 @@ export const AwsElastiCacheDatabaseProvider = (): TDynamicProviderFns => {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const create = async (inputs: unknown, expireAt: number) => {
|
const create = async (data: { inputs: unknown; expireAt: number; usernameTemplate?: string | null }) => {
|
||||||
|
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();
|
const leaseUsername = generateUsername(usernameTemplate);
|
||||||
const leasePassword = generatePassword();
|
const leasePassword = generatePassword();
|
||||||
const leaseExpiration = new Date(expireAt).toISOString();
|
const leaseExpiration = new Date(expireAt).toISOString();
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ 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";
|
||||||
@ -23,8 +24,14 @@ import { alphaNumericNanoId } from "@app/lib/nanoid";
|
|||||||
|
|
||||||
import { DynamicSecretAwsIamSchema, TDynamicProviderFns } from "./models";
|
import { DynamicSecretAwsIamSchema, TDynamicProviderFns } from "./models";
|
||||||
|
|
||||||
const generateUsername = () => {
|
const generateUsername = (usernameTemplate?: string | null) => {
|
||||||
return alphaNumericNanoId(32);
|
const randomUsername = 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 => {
|
||||||
@ -53,11 +60,13 @@ export const AwsIamProvider = (): TDynamicProviderFns => {
|
|||||||
return isConnected;
|
return isConnected;
|
||||||
};
|
};
|
||||||
|
|
||||||
const create = async (inputs: unknown) => {
|
const create = async (data: { inputs: unknown; expireAt: number; usernameTemplate?: string | null }) => {
|
||||||
|
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();
|
const username = generateUsername(usernameTemplate);
|
||||||
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({
|
||||||
|
@ -55,7 +55,7 @@ export const AzureEntraIDProvider = (): TDynamicProviderFns & {
|
|||||||
return data.success;
|
return data.success;
|
||||||
};
|
};
|
||||||
|
|
||||||
const create = async (inputs: unknown) => {
|
const create = async ({ inputs }: { 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 };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -14,8 +14,14 @@ const generatePassword = (size = 48) => {
|
|||||||
return customAlphabet(charset, 48)(size);
|
return customAlphabet(charset, 48)(size);
|
||||||
};
|
};
|
||||||
|
|
||||||
const generateUsername = () => {
|
const generateUsername = (usernameTemplate?: string | null) => {
|
||||||
return alphaNumericNanoId(32);
|
const randomUsername = alphaNumericNanoId(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 CassandraProvider = (): TDynamicProviderFns => {
|
export const CassandraProvider = (): TDynamicProviderFns => {
|
||||||
@ -69,11 +75,12 @@ export const CassandraProvider = (): TDynamicProviderFns => {
|
|||||||
return isConnected;
|
return isConnected;
|
||||||
};
|
};
|
||||||
|
|
||||||
const create = async (inputs: unknown, expireAt: number) => {
|
const create = async (data: { inputs: unknown; expireAt: number; usernameTemplate?: string | null }) => {
|
||||||
|
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();
|
const username = generateUsername(usernameTemplate);
|
||||||
const password = generatePassword();
|
const password = generatePassword();
|
||||||
const { keyspace } = providerInputs;
|
const { keyspace } = providerInputs;
|
||||||
const expiration = new Date(expireAt).toISOString();
|
const expiration = new Date(expireAt).toISOString();
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
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";
|
||||||
|
|
||||||
@ -12,8 +13,14 @@ const generatePassword = () => {
|
|||||||
return customAlphabet(charset, 64)();
|
return customAlphabet(charset, 64)();
|
||||||
};
|
};
|
||||||
|
|
||||||
const generateUsername = () => {
|
const generateUsername = (usernameTemplate?: string | null) => {
|
||||||
return alphaNumericNanoId(32);
|
const randomUsername = alphaNumericNanoId(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 ElasticSearchProvider = (): TDynamicProviderFns => {
|
export const ElasticSearchProvider = (): TDynamicProviderFns => {
|
||||||
@ -64,11 +71,12 @@ export const ElasticSearchProvider = (): TDynamicProviderFns => {
|
|||||||
return infoResponse;
|
return infoResponse;
|
||||||
};
|
};
|
||||||
|
|
||||||
const create = async (inputs: unknown) => {
|
const create = async (data: { inputs: unknown; usernameTemplate?: string | null }) => {
|
||||||
|
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();
|
const username = generateUsername(usernameTemplate);
|
||||||
const password = generatePassword();
|
const password = generatePassword();
|
||||||
|
|
||||||
await connection.security.putUser({
|
await connection.security.putUser({
|
||||||
|
@ -116,7 +116,7 @@ export const KubernetesProvider = ({ gatewayService }: TKubernetesProviderDTO):
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const create = async (inputs: unknown, expireAt: number) => {
|
const create = async ({ inputs, expireAt }: { 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) => {
|
||||||
|
@ -22,8 +22,14 @@ const encodePassword = (password?: string) => {
|
|||||||
return base64Password;
|
return base64Password;
|
||||||
};
|
};
|
||||||
|
|
||||||
const generateUsername = () => {
|
const generateUsername = (usernameTemplate?: string | null) => {
|
||||||
return alphaNumericNanoId(20);
|
const randomUsername = alphaNumericNanoId(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)
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const generateLDIF = ({
|
const generateLDIF = ({
|
||||||
@ -190,7 +196,8 @@ export const LdapProvider = (): TDynamicProviderFns => {
|
|||||||
return dnArray;
|
return dnArray;
|
||||||
};
|
};
|
||||||
|
|
||||||
const create = async (inputs: unknown) => {
|
const create = async (data: { inputs: unknown; usernameTemplate?: string | null }) => {
|
||||||
|
const { inputs, usernameTemplate } = data;
|
||||||
const providerInputs = await validateProviderInputs(inputs);
|
const providerInputs = await validateProviderInputs(inputs);
|
||||||
const client = await $getClient(providerInputs);
|
const client = await $getClient(providerInputs);
|
||||||
|
|
||||||
@ -217,7 +224,7 @@ export const LdapProvider = (): TDynamicProviderFns => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const username = generateUsername();
|
const username = generateUsername(usernameTemplate);
|
||||||
const password = generatePassword();
|
const password = generatePassword();
|
||||||
const generatedLdif = generateLDIF({ username, password, ldifTemplate: providerInputs.creationLdif });
|
const generatedLdif = generateLDIF({ username, password, ldifTemplate: providerInputs.creationLdif });
|
||||||
|
|
||||||
|
@ -360,7 +360,11 @@ export const DynamicSecretProviderSchema = z.discriminatedUnion("type", [
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
export type TDynamicProviderFns = {
|
export type TDynamicProviderFns = {
|
||||||
create: (inputs: unknown, expireAt: number) => Promise<{ entityId: string; data: unknown }>;
|
create: (arg: {
|
||||||
|
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 }>;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
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";
|
||||||
|
|
||||||
@ -12,8 +13,14 @@ const generatePassword = (size = 48) => {
|
|||||||
return customAlphabet(charset, 48)(size);
|
return customAlphabet(charset, 48)(size);
|
||||||
};
|
};
|
||||||
|
|
||||||
const generateUsername = () => {
|
const generateUsername = (usernameTemplate?: string | null) => {
|
||||||
return alphaNumericNanoId(32);
|
const randomUsername = 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 => {
|
||||||
@ -57,11 +64,12 @@ export const MongoAtlasProvider = (): TDynamicProviderFns => {
|
|||||||
return isConnected;
|
return isConnected;
|
||||||
};
|
};
|
||||||
|
|
||||||
const create = async (inputs: unknown, expireAt: number) => {
|
const create = async (data: { inputs: unknown; expireAt: number; usernameTemplate?: string | null }) => {
|
||||||
|
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();
|
const username = generateUsername(usernameTemplate);
|
||||||
const password = generatePassword();
|
const password = generatePassword();
|
||||||
const expiration = new Date(expireAt).toISOString();
|
const expiration = new Date(expireAt).toISOString();
|
||||||
await client({
|
await client({
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
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";
|
||||||
@ -12,8 +13,14 @@ const generatePassword = (size = 48) => {
|
|||||||
return customAlphabet(charset, 48)(size);
|
return customAlphabet(charset, 48)(size);
|
||||||
};
|
};
|
||||||
|
|
||||||
const generateUsername = () => {
|
const generateUsername = (usernameTemplate?: string | null) => {
|
||||||
return alphaNumericNanoId(32);
|
const randomUsername = 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 => {
|
||||||
@ -53,11 +60,12 @@ export const MongoDBProvider = (): TDynamicProviderFns => {
|
|||||||
return isConnected;
|
return isConnected;
|
||||||
};
|
};
|
||||||
|
|
||||||
const create = async (inputs: unknown) => {
|
const create = async (data: { inputs: unknown; usernameTemplate?: string | null }) => {
|
||||||
|
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();
|
const username = generateUsername(usernameTemplate);
|
||||||
const password = generatePassword();
|
const password = generatePassword();
|
||||||
|
|
||||||
const db = client.db(providerInputs.database);
|
const db = client.db(providerInputs.database);
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
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";
|
||||||
@ -14,8 +15,14 @@ const generatePassword = () => {
|
|||||||
return customAlphabet(charset, 64)();
|
return customAlphabet(charset, 64)();
|
||||||
};
|
};
|
||||||
|
|
||||||
const generateUsername = () => {
|
const generateUsername = (usernameTemplate?: string | null) => {
|
||||||
return alphaNumericNanoId(32);
|
const randomUsername = alphaNumericNanoId(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)
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
type TCreateRabbitMQUser = {
|
type TCreateRabbitMQUser = {
|
||||||
@ -110,11 +117,12 @@ export const RabbitMqProvider = (): TDynamicProviderFns => {
|
|||||||
return infoResponse;
|
return infoResponse;
|
||||||
};
|
};
|
||||||
|
|
||||||
const create = async (inputs: unknown) => {
|
const create = async (data: { inputs: unknown; usernameTemplate?: string | null }) => {
|
||||||
|
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();
|
const username = generateUsername(usernameTemplate);
|
||||||
const password = generatePassword();
|
const password = generatePassword();
|
||||||
|
|
||||||
await createRabbitMqUser({
|
await createRabbitMqUser({
|
||||||
|
@ -15,8 +15,14 @@ const generatePassword = () => {
|
|||||||
return customAlphabet(charset, 64)();
|
return customAlphabet(charset, 64)();
|
||||||
};
|
};
|
||||||
|
|
||||||
const generateUsername = () => {
|
const generateUsername = (usernameTemplate?: string | null) => {
|
||||||
return alphaNumericNanoId(32);
|
const randomUsername = alphaNumericNanoId(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)
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const executeTransactions = async (connection: Redis, commands: string[]): Promise<(string | null)[] | null> => {
|
const executeTransactions = async (connection: Redis, commands: string[]): Promise<(string | null)[] | null> => {
|
||||||
@ -115,11 +121,12 @@ export const RedisDatabaseProvider = (): TDynamicProviderFns => {
|
|||||||
return pingResponse;
|
return pingResponse;
|
||||||
};
|
};
|
||||||
|
|
||||||
const create = async (inputs: unknown, expireAt: number) => {
|
const create = async (data: { inputs: unknown; expireAt: number; usernameTemplate?: string | null }) => {
|
||||||
|
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();
|
const username = generateUsername(usernameTemplate);
|
||||||
const password = generatePassword();
|
const password = generatePassword();
|
||||||
const expiration = new Date(expireAt).toISOString();
|
const expiration = new Date(expireAt).toISOString();
|
||||||
|
|
||||||
|
@ -15,8 +15,14 @@ const generatePassword = (size = 48) => {
|
|||||||
return customAlphabet(charset, 48)(size);
|
return customAlphabet(charset, 48)(size);
|
||||||
};
|
};
|
||||||
|
|
||||||
const generateUsername = () => {
|
const generateUsername = (usernameTemplate?: string | null) => {
|
||||||
return alphaNumericNanoId(25);
|
const randomUsername = `inf_${alphaNumericNanoId(25)}`; // 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)
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
enum SapCommands {
|
enum SapCommands {
|
||||||
@ -81,11 +87,12 @@ export const SapAseProvider = (): TDynamicProviderFns => {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const create = async (inputs: unknown) => {
|
const create = async (data: { inputs: unknown; usernameTemplate?: string | null }) => {
|
||||||
|
const { inputs, usernameTemplate } = data;
|
||||||
const providerInputs = await validateProviderInputs(inputs);
|
const providerInputs = await validateProviderInputs(inputs);
|
||||||
|
|
||||||
const username = `inf_${generateUsername()}`;
|
const username = generateUsername(usernameTemplate);
|
||||||
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);
|
||||||
|
@ -21,8 +21,14 @@ const generatePassword = (size = 48) => {
|
|||||||
return customAlphabet(charset, 48)(size);
|
return customAlphabet(charset, 48)(size);
|
||||||
};
|
};
|
||||||
|
|
||||||
const generateUsername = () => {
|
const generateUsername = (usernameTemplate?: string | null) => {
|
||||||
return alphaNumericNanoId(32);
|
const randomUsername = alphaNumericNanoId(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 SapHanaProvider = (): TDynamicProviderFns => {
|
export const SapHanaProvider = (): TDynamicProviderFns => {
|
||||||
@ -91,10 +97,11 @@ export const SapHanaProvider = (): TDynamicProviderFns => {
|
|||||||
return testResult;
|
return testResult;
|
||||||
};
|
};
|
||||||
|
|
||||||
const create = async (inputs: unknown, expireAt: number) => {
|
const create = async (data: { inputs: unknown; expireAt: number; usernameTemplate?: string | null }) => {
|
||||||
|
const { inputs, expireAt, usernameTemplate } = data;
|
||||||
const providerInputs = await validateProviderInputs(inputs);
|
const providerInputs = await validateProviderInputs(inputs);
|
||||||
|
|
||||||
const username = generateUsername();
|
const username = generateUsername(usernameTemplate);
|
||||||
const password = generatePassword();
|
const password = generatePassword();
|
||||||
const expiration = new Date(expireAt).toISOString();
|
const expiration = new Date(expireAt).toISOString();
|
||||||
|
|
||||||
|
@ -17,8 +17,14 @@ const generatePassword = (size = 48) => {
|
|||||||
return customAlphabet(charset, 48)(size);
|
return customAlphabet(charset, 48)(size);
|
||||||
};
|
};
|
||||||
|
|
||||||
const generateUsername = () => {
|
const generateUsername = (usernameTemplate?: string | null) => {
|
||||||
return `infisical_${alphaNumericNanoId(32)}`; // username must start with alpha character, hence prefix
|
const randomUsername = `infisical_${alphaNumericNanoId(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)
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const getDaysToExpiry = (expiryDate: Date) => {
|
const getDaysToExpiry = (expiryDate: Date) => {
|
||||||
@ -82,12 +88,13 @@ export const SnowflakeProvider = (): TDynamicProviderFns => {
|
|||||||
return isValidConnection;
|
return isValidConnection;
|
||||||
};
|
};
|
||||||
|
|
||||||
const create = async (inputs: unknown, expireAt: number) => {
|
const create = async (data: { inputs: unknown; expireAt: number; usernameTemplate?: string | null }) => {
|
||||||
|
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();
|
const username = generateUsername(usernameTemplate);
|
||||||
const password = generatePassword();
|
const password = generatePassword();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -104,11 +104,21 @@ const generatePassword = (provider: SqlProviders, requirements?: PasswordRequire
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const generateUsername = (provider: SqlProviders) => {
|
const generateUsername = (provider: SqlProviders, usernameTemplate?: string | null) => {
|
||||||
// For oracle, the client assumes everything is upper case when not using quotes around the password
|
let randomUsername = "";
|
||||||
if (provider === SqlProviders.Oracle) return alphaNumericNanoId(32).toUpperCase();
|
|
||||||
|
|
||||||
return alphaNumericNanoId(32);
|
// For oracle, the client assumes everything is upper case when not using quotes around the password
|
||||||
|
if (provider === SqlProviders.Oracle) {
|
||||||
|
randomUsername = alphaNumericNanoId(32).toUpperCase();
|
||||||
|
} else {
|
||||||
|
randomUsername = alphaNumericNanoId(32);
|
||||||
|
}
|
||||||
|
if (!usernameTemplate) return randomUsername;
|
||||||
|
|
||||||
|
return handlebars.compile(usernameTemplate)({
|
||||||
|
randomUsername,
|
||||||
|
unixTimestamp: Math.floor(Date.now() / 100)
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
type TSqlDatabaseProviderDTO = {
|
type TSqlDatabaseProviderDTO = {
|
||||||
@ -210,9 +220,12 @@ export const SqlDatabaseProvider = ({ gatewayService }: TSqlDatabaseProviderDTO)
|
|||||||
return isConnected;
|
return isConnected;
|
||||||
};
|
};
|
||||||
|
|
||||||
const create = async (inputs: unknown, expireAt: number) => {
|
const create = async (data: { inputs: unknown; expireAt: number; usernameTemplate?: string | null }) => {
|
||||||
|
const { inputs, expireAt, usernameTemplate } = data;
|
||||||
|
|
||||||
const providerInputs = await validateProviderInputs(inputs);
|
const providerInputs = await validateProviderInputs(inputs);
|
||||||
const username = generateUsername(providerInputs.client);
|
const username = generateUsername(providerInputs.client, usernameTemplate);
|
||||||
|
|
||||||
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 });
|
||||||
|
@ -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.checkIssued(caCertificate);
|
const isValidServerCertificate = serverCertificate.verify(caCertificate.publicKey);
|
||||||
if (!isValidServerCertificate) return quic.native.CryptoError.BadCertificate;
|
if (!isValidServerCertificate) return quic.native.CryptoError.BadCertificate;
|
||||||
|
|
||||||
const subjectDetails = parseSubjectDetails(serverCertificate.subject);
|
const subjectDetails = parseSubjectDetails(serverCertificate.subject);
|
||||||
|
@ -19,3 +19,15 @@ 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;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
@ -741,12 +741,14 @@ 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,
|
||||||
|
@ -235,11 +235,9 @@ export const SanitizedDynamicSecretSchema = DynamicSecretsSchema.omit({
|
|||||||
inputIV: true,
|
inputIV: true,
|
||||||
inputTag: true,
|
inputTag: true,
|
||||||
algorithm: true
|
algorithm: true
|
||||||
}).merge(
|
}).extend({
|
||||||
z.object({
|
|
||||||
metadata: ResourceMetadataSchema.optional()
|
metadata: ResourceMetadataSchema.optional()
|
||||||
})
|
});
|
||||||
);
|
|
||||||
|
|
||||||
export const SanitizedAuditLogStreamSchema = z.object({
|
export const SanitizedAuditLogStreamSchema = z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
|
@ -1,7 +1,13 @@
|
|||||||
import DOMPurify from "isomorphic-dompurify";
|
import DOMPurify from "isomorphic-dompurify";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { IdentitiesSchema, OrganizationsSchema, SuperAdminSchema, UsersSchema } from "@app/db/schemas";
|
import {
|
||||||
|
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";
|
||||||
@ -161,6 +167,129 @@ 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",
|
||||||
|
@ -196,8 +196,10 @@ 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,
|
||||||
@ -207,6 +209,7 @@ 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 };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ import { Knex } from "knex";
|
|||||||
|
|
||||||
import { TDbClient } from "@app/db";
|
import { TDbClient } from "@app/db";
|
||||||
import {
|
import {
|
||||||
|
OrganizationsSchema,
|
||||||
OrgMembershipRole,
|
OrgMembershipRole,
|
||||||
TableName,
|
TableName,
|
||||||
TOrganizations,
|
TOrganizations,
|
||||||
@ -12,7 +13,15 @@ import {
|
|||||||
TUserEncryptionKeys
|
TUserEncryptionKeys
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import { DatabaseError } from "@app/lib/errors";
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
import { buildFindFilter, ormify, selectAllTableCols, TFindFilter, TFindOpt, withTransaction } from "@app/lib/knex";
|
import {
|
||||||
|
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";
|
||||||
@ -22,6 +31,110 @@ 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
|
||||||
@ -507,6 +620,7 @@ export const orgDALFactory = (db: TDbClient) => {
|
|||||||
findOrgById,
|
findOrgById,
|
||||||
findOrgBySlug,
|
findOrgBySlug,
|
||||||
findAllOrgsByUserId,
|
findAllOrgsByUserId,
|
||||||
|
findOrganizationsByFilter,
|
||||||
ghostUserExists,
|
ghostUserExists,
|
||||||
findOrgMembersByUsername,
|
findOrgMembersByUsername,
|
||||||
findOrgMembersByRole,
|
findOrgMembersByRole,
|
||||||
|
@ -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 { AuthMethod, AuthTokenType } from "../auth/auth-type";
|
import { ActorType, 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,7 +21,9 @@ 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";
|
||||||
@ -33,7 +35,8 @@ import {
|
|||||||
TAdminBootstrapInstanceDTO,
|
TAdminBootstrapInstanceDTO,
|
||||||
TAdminGetIdentitiesDTO,
|
TAdminGetIdentitiesDTO,
|
||||||
TAdminGetUsersDTO,
|
TAdminGetUsersDTO,
|
||||||
TAdminSignUpDTO
|
TAdminSignUpDTO,
|
||||||
|
TGetOrganizationsDTO
|
||||||
} from "./super-admin-types";
|
} from "./super-admin-types";
|
||||||
|
|
||||||
type TSuperAdminServiceFactoryDep = {
|
type TSuperAdminServiceFactoryDep = {
|
||||||
@ -41,6 +44,8 @@ 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">;
|
||||||
@ -73,6 +78,8 @@ export const superAdminServiceFactory = ({
|
|||||||
serverCfgDAL,
|
serverCfgDAL,
|
||||||
userDAL,
|
userDAL,
|
||||||
identityDAL,
|
identityDAL,
|
||||||
|
orgDAL,
|
||||||
|
orgMembershipDAL,
|
||||||
userAliasDAL,
|
userAliasDAL,
|
||||||
authService,
|
authService,
|
||||||
orgService,
|
orgService,
|
||||||
@ -521,6 +528,47 @@ 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,
|
||||||
@ -663,6 +711,9 @@ export const superAdminServiceFactory = ({
|
|||||||
deleteIdentitySuperAdminAccess,
|
deleteIdentitySuperAdminAccess,
|
||||||
deleteUserSuperAdminAccess,
|
deleteUserSuperAdminAccess,
|
||||||
invalidateCache,
|
invalidateCache,
|
||||||
checkIfInvalidatingCache
|
checkIfInvalidatingCache,
|
||||||
|
getOrganizations,
|
||||||
|
deleteOrganization,
|
||||||
|
deleteOrganizationMembership
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -35,6 +35,12 @@ 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",
|
||||||
|
15
cli/go.mod
@ -5,6 +5,8 @@ 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
|
||||||
@ -19,10 +21,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
|
||||||
@ -30,7 +32,9 @@ 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
|
||||||
@ -43,10 +47,8 @@ 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
|
||||||
@ -65,7 +67,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.1 // indirect
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // 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
|
||||||
@ -105,13 +107,15 @@ 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.0 // indirect
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // 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
|
||||||
@ -122,7 +126,6 @@ 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
|
||||||
|
26
cli/go.sum
@ -127,8 +127,9 @@ 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=
|
||||||
@ -147,11 +148,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=
|
||||||
@ -307,6 +308,8 @@ 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=
|
||||||
@ -373,8 +376,6 @@ 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=
|
||||||
@ -385,12 +386,15 @@ 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=
|
||||||
@ -402,6 +406,8 @@ 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=
|
||||||
@ -420,7 +426,6 @@ 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=
|
||||||
@ -451,9 +456,15 @@ 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=
|
||||||
@ -661,6 +672,7 @@ 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=
|
||||||
|
@ -12,35 +12,6 @@ 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
|
||||||
@ -51,11 +22,11 @@ func CallGetEncryptedWorkspaceKey(httpClient *resty.Client, request GetEncrypted
|
|||||||
Get(endpoint)
|
Get(endpoint)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return GetEncryptedWorkspaceKeyResponse{}, NewGenericRequestError(operationCallGetEncryptedWorkspaceKey, err)
|
return GetEncryptedWorkspaceKeyResponse{}, fmt.Errorf("CallGetEncryptedWorkspaceKey: Unable to complete api request [err=%s]", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if response.IsError() {
|
if response.IsError() {
|
||||||
return GetEncryptedWorkspaceKeyResponse{}, NewAPIErrorWithResponse(operationCallGetEncryptedWorkspaceKey, response, nil)
|
return GetEncryptedWorkspaceKeyResponse{}, fmt.Errorf("CallGetEncryptedWorkspaceKey: Unsuccessful response [%v %v] [status-code=%v]", response.Request.Method, response.Request.URL, response.StatusCode())
|
||||||
}
|
}
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
@ -70,11 +41,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{}, NewGenericRequestError(operationCallGetServiceTokenDetails, err)
|
return GetServiceTokenDetailsResponse{}, fmt.Errorf("CallGetServiceTokenDetails: Unable to complete api request [err=%s]", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if response.IsError() {
|
if response.IsError() {
|
||||||
return GetServiceTokenDetailsResponse{}, NewAPIErrorWithResponse(operationCallGetServiceTokenDetails, response, nil)
|
return GetServiceTokenDetailsResponse{}, fmt.Errorf("CallGetServiceTokenDetails: Unsuccessful response: [response=%s]", response)
|
||||||
}
|
}
|
||||||
|
|
||||||
return tokenDetailsResponse, nil
|
return tokenDetailsResponse, nil
|
||||||
@ -90,11 +61,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{}, NewGenericRequestError(operationCallLogin1V3, err)
|
return GetLoginOneV2Response{}, fmt.Errorf("CallLogin1V3: Unable to complete api request [err=%s]", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if response.IsError() {
|
if response.IsError() {
|
||||||
return GetLoginOneV2Response{}, NewAPIErrorWithResponse(operationCallLogin1V3, response, nil)
|
return GetLoginOneV2Response{}, fmt.Errorf("CallLogin1V3: Unsuccessful response: [response=%s]", response)
|
||||||
}
|
}
|
||||||
|
|
||||||
return loginOneV2Response, nil
|
return loginOneV2Response, nil
|
||||||
@ -128,7 +99,7 @@ func CallVerifyMfaToken(httpClient *resty.Client, request VerifyMfaTokenRequest)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, NewGenericRequestError(operationCallVerifyMfaToken, err)
|
return nil, nil, fmt.Errorf("CallVerifyMfaToken: Unable to complete api request [err=%s]", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if response.IsError() {
|
if response.IsError() {
|
||||||
@ -164,11 +135,11 @@ func CallLogin2V2(httpClient *resty.Client, request GetLoginTwoV2Request) (GetLo
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return GetLoginTwoV2Response{}, NewGenericRequestError(operationCallLogin2V3, err)
|
return GetLoginTwoV2Response{}, fmt.Errorf("CallLogin2V3: Unable to complete api request [err=%s]", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if response.IsError() {
|
if response.IsError() {
|
||||||
return GetLoginTwoV2Response{}, NewAPIErrorWithResponse(operationCallLogin2V3, response, nil)
|
return GetLoginTwoV2Response{}, fmt.Errorf("CallLogin2V3: Unsuccessful response: [response=%s]", response)
|
||||||
}
|
}
|
||||||
|
|
||||||
return loginTwoV2Response, nil
|
return loginTwoV2Response, nil
|
||||||
@ -183,11 +154,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{}, NewGenericRequestError(operationCallGetAllOrganizations, err)
|
return GetOrganizationsResponse{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if response.IsError() {
|
if response.IsError() {
|
||||||
return GetOrganizationsResponse{}, NewAPIErrorWithResponse(operationCallGetAllOrganizations, response, nil)
|
return GetOrganizationsResponse{}, fmt.Errorf("CallGetAllOrganizations: Unsuccessful response: [response=%v]", response)
|
||||||
}
|
}
|
||||||
|
|
||||||
return orgResponse, nil
|
return orgResponse, nil
|
||||||
@ -204,11 +175,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{}, NewGenericRequestError(operationCallSelectOrganization, err)
|
return SelectOrganizationResponse{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if response.IsError() {
|
if response.IsError() {
|
||||||
return SelectOrganizationResponse{}, NewAPIErrorWithResponse(operationCallSelectOrganization, response, nil)
|
return SelectOrganizationResponse{}, fmt.Errorf("CallSelectOrganization: Unsuccessful response: [response=%v]", response)
|
||||||
}
|
}
|
||||||
|
|
||||||
return selectOrgResponse, nil
|
return selectOrgResponse, nil
|
||||||
@ -243,11 +214,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{}, NewGenericRequestError(operationCallGetProjectById, err)
|
return Project{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if response.IsError() {
|
if response.IsError() {
|
||||||
return Project{}, NewAPIErrorWithResponse(operationCallGetProjectById, response, nil)
|
return Project{}, fmt.Errorf("CallGetProjectById: Unsuccessful response: [response=%v]", response)
|
||||||
}
|
}
|
||||||
|
|
||||||
return projectResponse.Project, nil
|
return projectResponse.Project, nil
|
||||||
@ -266,7 +237,7 @@ func CallIsAuthenticated(httpClient *resty.Client) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if response.IsError() {
|
if response.IsError() {
|
||||||
log.Debug().Msgf("%s: Unsuccessful response: [response=%v]", operationCallIsAuthenticated, response)
|
log.Debug().Msgf("CallIsAuthenticated: Unsuccessful response: [response=%v]", response)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -286,11 +257,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{}, NewGenericRequestError(operationCallGetNewAccessTokenWithRefreshToken, err)
|
return GetNewAccessTokenWithRefreshTokenResponse{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if response.IsError() {
|
if response.IsError() {
|
||||||
return GetNewAccessTokenWithRefreshTokenResponse{}, NewAPIErrorWithResponse(operationCallGetNewAccessTokenWithRefreshToken, response, nil)
|
return GetNewAccessTokenWithRefreshTokenResponse{}, fmt.Errorf("CallGetNewAccessTokenWithRefreshToken: Unsuccessful response: [response=%v]", response)
|
||||||
}
|
}
|
||||||
|
|
||||||
return newAccessToken, nil
|
return newAccessToken, nil
|
||||||
@ -309,11 +280,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{}, NewGenericRequestError(operationCallGetFoldersV1, err)
|
return GetFoldersV1Response{}, fmt.Errorf("CallGetFoldersV1: Unable to complete api request [err=%v]", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if response.IsError() {
|
if response.IsError() {
|
||||||
return GetFoldersV1Response{}, NewAPIErrorWithResponse(operationCallGetFoldersV1, response, nil)
|
return GetFoldersV1Response{}, fmt.Errorf("CallGetFoldersV1: Unsuccessful [response=%s]", response)
|
||||||
}
|
}
|
||||||
|
|
||||||
return foldersResponse, nil
|
return foldersResponse, nil
|
||||||
@ -329,11 +300,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{}, NewGenericRequestError(operationCallCreateFolderV1, err)
|
return CreateFolderV1Response{}, fmt.Errorf("CallCreateFolderV1: Unable to complete api request [err=%s]", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if response.IsError() {
|
if response.IsError() {
|
||||||
return CreateFolderV1Response{}, NewAPIErrorWithResponse(operationCallCreateFolderV1, response, nil)
|
return CreateFolderV1Response{}, fmt.Errorf("CallCreateFolderV1: Unsuccessful [response=%s]", response.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
return folderResponse, nil
|
return folderResponse, nil
|
||||||
@ -350,11 +321,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{}, NewGenericRequestError(operationCallDeleteFolderV1, err)
|
return DeleteFolderV1Response{}, fmt.Errorf("CallDeleteFolderV1: Unable to complete api request [err=%s]", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if response.IsError() {
|
if response.IsError() {
|
||||||
return DeleteFolderV1Response{}, NewAPIErrorWithResponse(operationCallDeleteFolderV1, response, nil)
|
return DeleteFolderV1Response{}, fmt.Errorf("CallDeleteFolderV1: Unsuccessful [response=%s]", response.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
return folderResponse, nil
|
return folderResponse, nil
|
||||||
@ -371,12 +342,11 @@ 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 NewGenericRequestError(operationCallDeleteSecretsV3, err)
|
return fmt.Errorf("CallDeleteSecretsV3: Unable to complete api request [err=%s]", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if response.IsError() {
|
if response.IsError() {
|
||||||
additionalContext := "Please make sure your secret path, workspace and environment name are all correct."
|
return fmt.Errorf("CallDeleteSecretsV3: Unsuccessful response. Please make sure your secret path, workspace and environment name are all correct [response=%s]", response)
|
||||||
return NewAPIErrorWithResponse(operationCallDeleteSecretsV3, response, &additionalContext)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -392,11 +362,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{}, NewGenericRequestError(operationCallCreateServiceToken, err)
|
return CreateServiceTokenResponse{}, fmt.Errorf("CallCreateServiceToken: Unable to complete api request [err=%s]", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if response.IsError() {
|
if response.IsError() {
|
||||||
return CreateServiceTokenResponse{}, NewAPIErrorWithResponse(operationCallCreateServiceToken, response, nil)
|
return CreateServiceTokenResponse{}, fmt.Errorf("CallCreateServiceToken: Unsuccessful response [%v %v] [status-code=%v]", response.Request.Method, response.Request.URL, response.StatusCode())
|
||||||
}
|
}
|
||||||
|
|
||||||
return createServiceTokenResponse, nil
|
return createServiceTokenResponse, nil
|
||||||
@ -412,11 +382,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{}, NewGenericRequestError(operationCallUniversalAuthLogin, err)
|
return UniversalAuthLoginResponse{}, fmt.Errorf("CallUniversalAuthLogin: Unable to complete api request [err=%s]", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if response.IsError() {
|
if response.IsError() {
|
||||||
return UniversalAuthLoginResponse{}, NewAPIErrorWithResponse(operationCallUniversalAuthLogin, response, nil)
|
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, nil
|
return universalAuthLoginResponse, nil
|
||||||
@ -432,11 +402,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{}, NewGenericRequestError(operationCallMachineIdentityRefreshAccessToken, err)
|
return UniversalAuthRefreshResponse{}, fmt.Errorf("CallMachineIdentityRefreshAccessToken: Unable to complete api request [err=%s]", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if response.IsError() {
|
if response.IsError() {
|
||||||
return UniversalAuthRefreshResponse{}, NewAPIErrorWithResponse(operationCallMachineIdentityRefreshAccessToken, response, nil)
|
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, nil
|
return universalAuthRefreshResponse, nil
|
||||||
@ -471,19 +441,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{}, NewGenericRequestError(operationCallGetRawSecretsV3, err)
|
return GetRawSecretsV3Response{}, fmt.Errorf("CallGetRawSecretsV3: Unable to complete api request [err=%w]", 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")) {
|
||||||
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)
|
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.
|
||||||
return GetRawSecretsV3Response{}, NewAPIErrorWithResponse(operationCallGetRawSecretsV3, response, &additionalContext)
|
`, request.WorkspaceId)
|
||||||
}
|
}
|
||||||
|
|
||||||
if response.IsError() {
|
if response.IsError() {
|
||||||
return GetRawSecretsV3Response{}, NewAPIErrorWithResponse(operationCallGetRawSecretsV3, response, nil)
|
return GetRawSecretsV3Response{}, fmt.Errorf("CallGetRawSecretsV3: Unsuccessful response [%v %v] [status-code=%v] [response=%v]", response.Request.Method, response.Request.URL, response.StatusCode(), response.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
getRawSecretsV3Response.ETag = response.Header().Get(("etag"))
|
getRawSecretsV3Response.ETag = response.Header().Get(("etag"))
|
||||||
@ -507,11 +477,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{}, NewGenericRequestError(operationCallFetchSingleSecretByName, err)
|
return GetRawSecretV3ByNameResponse{}, fmt.Errorf("CallFetchSingleSecretByName: Unable to complete api request [err=%w]", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if response.IsError() {
|
if response.IsError() {
|
||||||
return GetRawSecretV3ByNameResponse{}, NewAPIErrorWithResponse(operationCallFetchSingleSecretByName, response, nil)
|
return GetRawSecretV3ByNameResponse{}, fmt.Errorf("CallFetchSingleSecretByName: Unsuccessful response [%v %v] [status-code=%v] [response=%v]", response.Request.Method, response.Request.URL, response.StatusCode(), response.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
getRawSecretV3ByNameResponse.ETag = response.Header().Get(("etag"))
|
getRawSecretV3ByNameResponse.ETag = response.Header().Get(("etag"))
|
||||||
@ -547,11 +517,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 NewGenericRequestError(operationCallCreateRawSecretsV3, err)
|
return fmt.Errorf("CallCreateRawSecretsV3: Unable to complete api request [err=%w]", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if response.IsError() {
|
if response.IsError() {
|
||||||
return NewAPIErrorWithResponse(operationCallCreateRawSecretsV3, response, nil)
|
return fmt.Errorf("CallCreateRawSecretsV3: Unsuccessful response [%v %v] [status-code=%v] [response=%v]", response.Request.Method, response.Request.URL, response.StatusCode(), response.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -565,11 +535,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 NewGenericRequestError(operationCallUpdateRawSecretsV3, err)
|
return fmt.Errorf("CallUpdateRawSecretsV3: Unable to complete api request [err=%w]", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if response.IsError() {
|
if response.IsError() {
|
||||||
return NewAPIErrorWithResponse(operationCallUpdateRawSecretsV3, response, nil)
|
return fmt.Errorf("CallUpdateRawSecretsV3: Unsuccessful response [%v %v] [status-code=%v] [response=%v]", response.Request.Method, response.Request.URL, response.StatusCode(), response.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -584,11 +554,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, NewGenericRequestError(operationCallRegisterGatewayIdentityV1, err)
|
return nil, fmt.Errorf("CallRegisterGatewayIdentityV1: Unable to complete api request [err=%w]", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if response.IsError() {
|
if response.IsError() {
|
||||||
return nil, NewAPIErrorWithResponse(operationCallRegisterGatewayIdentityV1, response, nil)
|
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 &resBody, nil
|
return &resBody, nil
|
||||||
@ -604,11 +574,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, NewGenericRequestError(operationCallExchangeRelayCertV1, err)
|
return nil, fmt.Errorf("CallExchangeRelayCertV1: Unable to complete api request [err=%w]", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if response.IsError() {
|
if response.IsError() {
|
||||||
return nil, NewAPIErrorWithResponse(operationCallExchangeRelayCertV1, response, nil)
|
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 &resBody, nil
|
return &resBody, nil
|
||||||
@ -621,11 +591,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 NewGenericRequestError(operationCallGatewayHeartBeatV1, err)
|
return fmt.Errorf("CallGatewayHeartBeatV1: Unable to complete api request [err=%w]", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if response.IsError() {
|
if response.IsError() {
|
||||||
return NewAPIErrorWithResponse(operationCallGatewayHeartBeatV1, response, nil)
|
return fmt.Errorf("CallGatewayHeartBeatV1: Unsuccessful response [%v %v] [status-code=%v] [response=%v]", response.Request.Method, response.Request.URL, response.StatusCode(), response.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -641,11 +611,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, NewGenericRequestError(operationCallBootstrapInstance, err)
|
return nil, fmt.Errorf("CallBootstrapInstance: Unable to complete api request [err=%w]", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if response.IsError() {
|
if response.IsError() {
|
||||||
return nil, NewAPIErrorWithResponse(operationCallBootstrapInstance, response, nil)
|
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 resBody, nil
|
return resBody, nil
|
||||||
|
@ -1,80 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
@ -9,6 +9,7 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"os"
|
"os"
|
||||||
|
"runtime"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -20,6 +21,8 @@ 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"
|
||||||
@ -981,7 +984,17 @@ 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)
|
||||||
|
|
||||||
fmt.Printf("\n\nTo complete your login, open this address in your browser: %v \n", url)
|
defaultPrintStatement := fmt.Sprintf("\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)
|
||||||
|
@ -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] [request-id=<unknown-value>] [message="Environment with slug 'invalid-env' in project with ID bef697d4-849b-4a75-b284-0922f87f8ba2 not found"]
|
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}]
|
||||||
|
|
||||||
|
|
||||||
If this issue continues, get support at https://infisical.com/slack
|
If this issue continues, get support at https://infisical.com/slack
|
||||||
|
@ -6,7 +6,6 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -72,11 +71,7 @@ func SetupCli() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func FilterRequestID(input string) string {
|
func FilterRequestID(input string) string {
|
||||||
requestIDPattern := regexp.MustCompile(`\[request-id=[^\]]+\]`)
|
// Find the JSON part of the error message
|
||||||
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
|
||||||
|
|
||||||
|
@ -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">
|
||||||

|

|
||||||
</Step>
|
</Step>
|
||||||
<Step title="Select 'AWS ElastiCache'">
|
<Step title="Select AWS ElastiCache">
|
||||||

|

|
||||||
</Step>
|
</Step>
|
||||||
<Step title="Provide the inputs for dynamic secret parameters">
|
<Step title="Provide the inputs for dynamic secret parameters">
|
||||||
@ -94,9 +94,17 @@ 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">
|
||||||
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
|
||||||
|
- `{{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.
|
||||||
|
@ -105,6 +105,14 @@ 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>
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
|
@ -78,15 +78,23 @@ The above configuration allows user creation and granting permissions.
|
|||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
<Step title="(Optional) Modify CQL Statements">
|
<Step title="(Optional) Modify CQL Statements">
|
||||||
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 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).
|
||||||
|
</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 certficate.
|
If this step fails, you may have to add the CA certificate.
|
||||||
</Note>
|
</Note>
|
||||||
|
|
||||||

|

|
||||||
|
@ -7,13 +7,14 @@ 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 `superuser`, that will have full control over the cluster. This is not recommended in production environments following the principle of least privilege.
|
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.
|
||||||
</Note>
|
</Note>
|
||||||
|
|
||||||
## Set up Dynamic Secrets with Elasticsearch
|
## Set up Dynamic Secrets with Elasticsearch
|
||||||
@ -46,39 +47,55 @@ 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>
|
|
||||||
|
|
||||||
<ParamField path="Roles" type="string[]" required>
|
The port that your Elasticsearch instance is running on. _(Example: 9200)_
|
||||||
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>
|
|
||||||
|
<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 leases. Only required if you selected the `Username/Password` authentication method.
|
The username of the user that will be used to provision new dynamic secret
|
||||||
</ParamField>
|
leases. Only required if you selected the `Username/Password` authentication
|
||||||
|
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 leases. Only required if you selected the `Username/Password` authentication method.
|
The password of the user that will be used to provision new dynamic secret
|
||||||
</ParamField>
|
leases. Only required if you selected the `Username/Password` authentication
|
||||||
|
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 leases. Only required if you selected the `API Key` authentication method.
|
The ID of the API key that will be used to provision new dynamic secret
|
||||||
</ParamField>
|
leases. Only required if you selected the `API Key` authentication method.
|
||||||
|
</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 required if you selected the `API Key` authentication method.
|
The API key that will be used to provision new dynamic secret leases. Only
|
||||||
</ParamField>
|
required if you selected the `API Key` authentication method.
|
||||||
|
</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
|
||||||
|
- `{{randomUsername}}`: Random username string
|
||||||
|
- `{{unixTimestamp}}`: Current Unix timestamp
|
||||||
|
</ParamField>
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
<Step title="Click `Submit`">
|
<Step title="Click `Submit`">
|
||||||
@ -109,19 +126,23 @@ The Infisical Elasticsearch dynamic secret allows you to generate Elasticsearch
|
|||||||
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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
</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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## 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.
|
||||||

|

|
||||||
|
|
||||||
<Warning>
|
<Warning>
|
||||||
Lease renewals cannot exceed the maximum TTL set when configuring the dynamic secret
|
Lease renewals cannot exceed the maximum TTL set when configuring the dynamic
|
||||||
|
secret
|
||||||
</Warning>
|
</Warning>
|
||||||
|
@ -122,6 +122,13 @@ 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>
|
||||||
|
|
||||||
|
@ -6,11 +6,10 @@ 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).
|
|
||||||
|
|
||||||
<Info>
|
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).
|
||||||
The API Key must have permission to manage users in the project.
|
|
||||||
</Info>
|
<Info>The API Key must have permission to manage users in the project.</Info>
|
||||||
|
|
||||||
## Set up Dynamic Secrets with Mongo Atlas
|
## Set up Dynamic Secrets with Mongo Atlas
|
||||||
|
|
||||||
@ -61,20 +60,34 @@ Create a project scopped API Key with the required permission in your Mongo Atla
|
|||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
<Step title="(Optional) Modify Access Scope">
|
<Step title="(Optional) Modify Access Scope">
|
||||||
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.
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
<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.
|
||||||
- **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 certficate.
|
If this step fails, you may have to add the CA certificate.
|
||||||
</Note>
|
</Note>
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
</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.
|
||||||
@ -89,26 +102,30 @@ Create a project scopped API Key with the required permission in your Mongo Atla
|
|||||||

|

|
||||||
|
|
||||||
<Tip>
|
<Tip>
|
||||||
Ensure that the TTL for the lease fall within the maximum TTL defined when configuring the dynamic secret.
|
Ensure that the TTL for the lease falls 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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
</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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## 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.
|
||||||

|

|
||||||
|
|
||||||
<Warning>
|
<Warning>
|
||||||
Lease renewals cannot exceed the maximum TTL set when configuring the dynamic secret
|
Lease renewals cannot exceed the maximum TTL set when configuring the dynamic
|
||||||
|
secret
|
||||||
</Warning>
|
</Warning>
|
||||||
|
@ -66,6 +66,13 @@ 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>
|
||||||
|
|
||||||

|

|
||||||
</Step>
|
</Step>
|
||||||
|
@ -71,15 +71,23 @@ 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">
|
||||||
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 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).
|
||||||
|
</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 certficate.
|
If this step fails, you may have to add the CA certificate.
|
||||||
</Note>
|
</Note>
|
||||||
|
|
||||||

|

|
||||||
|
@ -68,9 +68,17 @@ 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">
|
||||||
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 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).
|
||||||
|
</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.
|
||||||
|
@ -70,13 +70,23 @@ 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">
|
||||||
|

|
||||||
|
<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 certficate.
|
If this step fails, you may have to add the CA certificate.
|
||||||
</Note>
|
</Note>
|
||||||
|
|
||||||

|

|
||||||
|
@ -71,15 +71,23 @@ 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">
|
||||||
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 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).
|
||||||
|
</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 certficate.
|
If this step fails, you may have to add the CA certificate.
|
||||||
</Note>
|
</Note>
|
||||||
|
|
||||||

|

|
||||||
|
@ -9,7 +9,6 @@ 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>
|
||||||
@ -19,8 +18,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">
|
||||||

|

|
||||||
</Step>
|
</Step>
|
||||||
<Step title="Select 'RabbitMQ'">
|
<Step title="Select RabbitMQ">
|
||||||

|

|
||||||
</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>
|
||||||
@ -40,34 +39,45 @@ 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>
|
|
||||||
|
|
||||||
<ParamField path="Virtual host name" type="string" required>
|
The port that the RabbitMQ management plugin is listening on. This is `15672` by default.
|
||||||
The name of the virtual host that the user will be assigned to. This defaults to `/`.
|
</ParamField>
|
||||||
</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="Username" type="string" required>
|
<ParamField path="Password" type="string" required>
|
||||||
The username of the user that will be used to provision new dynamic secret leases.
|
The password of the user that will be used to provision new dynamic secret
|
||||||
</ParamField>
|
leases.
|
||||||
|
</ParamField>
|
||||||
|
|
||||||
<ParamField path="Password" type="string" required>
|
<ParamField path="Username Template" type="string" default="{{randomUsername}}">
|
||||||
The password of the user that will be used to provision new dynamic secret leases.
|
Specifies a template for generating usernames. This field allows customization of how usernames are automatically created.
|
||||||
</ParamField>
|
|
||||||
|
|
||||||
<ParamField path="CA(SSL)" type="string">
|
Allowed template variables are
|
||||||
|
- `{{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>
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
<Step title="Click `Submit`">
|
<Step title="Click `Submit`">
|
||||||
@ -98,19 +108,23 @@ The Infisical RabbitMQ dynamic secret allows you to generate RabbitMQ credential
|
|||||||
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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
</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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## 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.
|
||||||

|

|
||||||
|
|
||||||
<Warning>
|
<Warning>
|
||||||
Lease renewals cannot exceed the maximum TTL set when configuring the dynamic secret
|
Lease renewals cannot exceed the maximum TTL set when configuring the dynamic
|
||||||
|
secret
|
||||||
</Warning>
|
</Warning>
|
||||||
|
@ -56,9 +56,17 @@ 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">
|
||||||
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 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).
|
||||||
|
</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.
|
||||||
|
@ -62,13 +62,22 @@ The Infisical SAP ASE dynamic secret allows you to generate SAP ASE database cre
|
|||||||

|

|
||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
<Step title="(Optional) Modify SQL Statements">
|
<Step title="(Optional) Modify SAP SQL Statements">
|
||||||
If you want to provide specific privileges for the generated dynamic credentials, you can modify the SQL statement to your needs.
|
|
||||||

|

|
||||||
|
<ParamField path="Username Template" type="string" default="{{randomUsername}}">
|
||||||
|
Specifies a template for generating usernames. This field allows customization of how usernames are automatically created.
|
||||||
|
|
||||||
<Warning>
|
Allowed template variables are
|
||||||
Due to SAP ASE limitations, the attached SQL statements are not executed as a transaction.
|
- `{{randomUsername}}`: Random username string
|
||||||
</Warning>
|
- `{{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.
|
||||||
|
<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'">
|
||||||
|
@ -62,14 +62,25 @@ The Infisical SAP HANA dynamic secret allows you to generate SAP HANA database c
|
|||||||

|

|
||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
<Step title="(Optional) Modify SQL Statements">
|
<Step title="(Optional) Modify SAP SQL Statements">
|
||||||
If you want to provide specific privileges for the generated dynamic credentials, you can modify the SQL statement to your needs.
|
|
||||||

|

|
||||||
|
<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.
|
||||||
|
|
||||||
<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.
|
||||||
|
@ -8,21 +8,26 @@ 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. This user will act as a service account for Infisical and facilitate the creation of new users as needed.
|
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.
|
||||||
</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">
|
||||||

|

|
||||||
</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>
|
||||||

|

|
||||||
</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">
|
||||||

|

|
||||||
</Step>
|
</Step>
|
||||||
</Steps>
|
</Steps>
|
||||||
|
|
||||||
@ -71,10 +76,23 @@ 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">
|
||||||
|

|
||||||
|
<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.
|
||||||

|
|
||||||
|
</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>
|
||||||
@ -104,6 +122,7 @@ Infisical's Snowflake dynamic secrets allow you to generate Snowflake user crede
|
|||||||

|

|
||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
|
|
||||||
</Steps>
|
</Steps>
|
||||||
|
|
||||||
## Audit or Revoke Leases
|
## Audit or Revoke Leases
|
||||||
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 482 KiB |
Before Width: | Height: | Size: 173 KiB After Width: | Height: | Size: 459 KiB |
Before Width: | Height: | Size: 419 KiB After Width: | Height: | Size: 445 KiB |
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 532 KiB |
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 526 KiB |
Before Width: | Height: | Size: 192 KiB After Width: | Height: | Size: 501 KiB |
Before Width: | Height: | Size: 170 KiB After Width: | Height: | Size: 495 KiB |
Before Width: | Height: | Size: 607 KiB After Width: | Height: | Size: 555 KiB |
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 566 KiB |
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 577 KiB |
Before Width: | Height: | Size: 152 KiB After Width: | Height: | Size: 572 KiB |
Before Width: | Height: | Size: 210 KiB After Width: | Height: | Size: 595 KiB |
Before Width: | Height: | Size: 200 KiB After Width: | Height: | Size: 568 KiB |
Before Width: | Height: | Size: 666 KiB After Width: | Height: | Size: 546 KiB |
@ -1,16 +1,2 @@
|
|||||||
export {
|
export * from "./mutation";
|
||||||
useAdminDeleteUser,
|
export * from "./queries";
|
||||||
useAdminGrantServerAdminAccess,
|
|
||||||
useAdminRemoveIdentitySuperAdminAccess,
|
|
||||||
useCreateAdminUser,
|
|
||||||
useInvalidateCache,
|
|
||||||
useRemoveUserServerAdminAccess,
|
|
||||||
useUpdateServerConfig,
|
|
||||||
useUpdateServerEncryptionStrategy
|
|
||||||
} from "./mutation";
|
|
||||||
export {
|
|
||||||
useAdminGetUsers,
|
|
||||||
useGetAdminIntegrationsConfig,
|
|
||||||
useGetServerConfig,
|
|
||||||
useGetServerRootKmsEncryptionDetails
|
|
||||||
} from "./queries";
|
|
||||||
|
@ -63,6 +63,41 @@ 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]
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -6,8 +6,10 @@ 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
|
||||||
@ -15,12 +17,15 @@ 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,
|
||||||
@ -34,6 +39,28 @@ 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 = {}
|
||||||
}: {
|
}: {
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { Organization } from "../types";
|
||||||
|
|
||||||
export enum LoginMethod {
|
export enum LoginMethod {
|
||||||
EMAIL = "email",
|
EMAIL = "email",
|
||||||
GOOGLE = "google",
|
GOOGLE = "google",
|
||||||
@ -8,6 +10,27 @@ 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;
|
||||||
@ -51,6 +74,11 @@ 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;
|
||||||
|
@ -13,6 +13,7 @@ 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 }[];
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -287,6 +288,7 @@ 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 = {
|
||||||
@ -300,6 +302,7 @@ export type TUpdateDynamicSecretDTO = {
|
|||||||
defaultTTL?: string;
|
defaultTTL?: string;
|
||||||
maxTTL?: string | null;
|
maxTTL?: string | null;
|
||||||
inputs?: unknown;
|
inputs?: unknown;
|
||||||
|
usernameTemplate?: string | null;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,27 +1,23 @@
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { faArrowLeft, faInfo, faMobile, faQuestion } from "@fortawesome/free-solid-svg-icons";
|
import { faMobile } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { Link, Outlet } from "@tanstack/react-router";
|
import { Outlet, useRouterState } 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 {
|
import { BreadcrumbContainer, TBreadcrumbFormat } from "@app/components/v2";
|
||||||
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 { INFISICAL_SUPPORT_OPTIONS } from "../OrganizationLayout/components/MinimizedOrgSidebar/MinimizedOrgSidebar";
|
import { AdminSidebar } from "./Sidebar";
|
||||||
|
|
||||||
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 (
|
||||||
@ -30,54 +26,11 @@ 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">
|
||||||
<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">
|
<AdminSidebar />
|
||||||
<nav className="items-between flex h-full flex-col justify-between overflow-y-auto dark:[color-scheme:dark]">
|
<main className="flex-1 overflow-y-auto overflow-x-hidden bg-bunker-800 px-4 pb-4 dark:[color-scheme:dark]">
|
||||||
<div className="flex-grow">
|
{breadcrumbs ? (
|
||||||
<Link to={`/organization/${ProjectType.SecretManager}/overview` as const}>
|
<BreadcrumbContainer breadcrumbs={breadcrumbs as TBreadcrumbFormat[]} />
|
||||||
<div className="my-6 flex cursor-default items-center justify-center pr-2 text-sm text-mineshaft-300 hover:text-mineshaft-100">
|
) : null}
|
||||||
<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>
|
||||||
|
141
frontend/src/layouts/AdminLayout/Sidebar.tsx
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
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>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,27 @@
|
|||||||
|
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>
|
||||||
|
);
|
||||||
|
};
|
@ -20,7 +20,7 @@ const formSchema = z.object({
|
|||||||
|
|
||||||
type TAuthForm = z.infer<typeof formSchema>;
|
type TAuthForm = z.infer<typeof formSchema>;
|
||||||
|
|
||||||
export const AuthPanel = () => {
|
export const AuthenticationPageForm = () => {
|
||||||
const { config } = useServerConfig();
|
const { config } = useServerConfig();
|
||||||
const { enabledLoginMethods } = config;
|
const { enabledLoginMethods } = config;
|
||||||
const { mutateAsync: updateServerConfig } = useUpdateServerConfig();
|
const { mutateAsync: updateServerConfig } = useUpdateServerConfig();
|
@ -0,0 +1 @@
|
|||||||
|
export { AuthenticationPageForm } from "./AuthenticationPageForm";
|
25
frontend/src/pages/admin/AuthenticationPage/route.tsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
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"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
24
frontend/src/pages/admin/CachingPage/CachingPage.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
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>
|
||||||
|
);
|
||||||
|
};
|
@ -10,7 +10,7 @@ import { useInvalidateCache } from "@app/hooks/api";
|
|||||||
import { useGetInvalidatingCacheStatus } from "@app/hooks/api/admin/queries";
|
import { useGetInvalidatingCacheStatus } from "@app/hooks/api/admin/queries";
|
||||||
import { CacheType } from "@app/hooks/api/admin/types";
|
import { CacheType } from "@app/hooks/api/admin/types";
|
||||||
|
|
||||||
export const CachingPanel = () => {
|
export const CachingPageForm = () => {
|
||||||
const { mutateAsync: invalidateCache } = useInvalidateCache();
|
const { mutateAsync: invalidateCache } = useInvalidateCache();
|
||||||
const { user } = useUser();
|
const { user } = useUser();
|
||||||
|
|
1
frontend/src/pages/admin/CachingPage/components/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { CachingPageForm } from "./CachingPageForm";
|
25
frontend/src/pages/admin/CachingPage/route.tsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
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"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
27
frontend/src/pages/admin/EncryptionPage/EncryptionPage.tsx
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
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>
|
||||||
|
);
|
||||||
|
};
|
@ -8,11 +8,11 @@ import { createNotification } from "@app/components/notifications";
|
|||||||
import { Button, FormControl, Select, SelectItem } from "@app/components/v2";
|
import { Button, FormControl, Select, SelectItem } from "@app/components/v2";
|
||||||
import { useSubscription } from "@app/context";
|
import { useSubscription } from "@app/context";
|
||||||
import { usePopUp } from "@app/hooks";
|
import { usePopUp } from "@app/hooks";
|
||||||
import { useUpdateServerEncryptionStrategy } from "@app/hooks/api";
|
|
||||||
import {
|
import {
|
||||||
RootKeyEncryptionStrategy,
|
useGetServerRootKmsEncryptionDetails,
|
||||||
TGetServerRootKmsEncryptionDetails
|
useUpdateServerEncryptionStrategy
|
||||||
} from "@app/hooks/api/admin/types";
|
} from "@app/hooks/api";
|
||||||
|
import { RootKeyEncryptionStrategy } from "@app/hooks/api/admin/types";
|
||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
encryptionStrategy: z.nativeEnum(RootKeyEncryptionStrategy)
|
encryptionStrategy: z.nativeEnum(RootKeyEncryptionStrategy)
|
||||||
@ -25,11 +25,9 @@ const strategies: Record<RootKeyEncryptionStrategy, string> = {
|
|||||||
|
|
||||||
type TForm = z.infer<typeof formSchema>;
|
type TForm = z.infer<typeof formSchema>;
|
||||||
|
|
||||||
type Props = {
|
export const EncryptionPageForm = () => {
|
||||||
rootKmsDetails?: TGetServerRootKmsEncryptionDetails;
|
const { data: rootKmsDetails } = useGetServerRootKmsEncryptionDetails();
|
||||||
};
|
|
||||||
|
|
||||||
export const EncryptionPanel = ({ rootKmsDetails }: Props) => {
|
|
||||||
const { mutateAsync: updateEncryptionStrategy } = useUpdateServerEncryptionStrategy();
|
const { mutateAsync: updateEncryptionStrategy } = useUpdateServerEncryptionStrategy();
|
||||||
const { subscription } = useSubscription();
|
const { subscription } = useSubscription();
|
||||||
|
|
@ -0,0 +1 @@
|
|||||||
|
export { EncryptionPageForm } from "./EncryptionPageForm";
|
25
frontend/src/pages/admin/EncryptionPage/route.tsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
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"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
27
frontend/src/pages/admin/GeneralPage/GeneralPage.tsx
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
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>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,309 @@
|
|||||||
|
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>
|
||||||
|
);
|
||||||
|
};
|
1
frontend/src/pages/admin/GeneralPage/components/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { GeneralPageForm } from "./GeneralPageForm";
|
23
frontend/src/pages/admin/GeneralPage/route.tsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
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"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
@ -0,0 +1,27 @@
|
|||||||
|
import { Helmet } from "react-helmet";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
import { PageHeader } from "@app/components/v2";
|
||||||
|
|
||||||
|
import { IntegrationsPageForm } from "./components";
|
||||||
|
|
||||||
|
export const IntegrationsPage = () => {
|
||||||
|
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="Integrations"
|
||||||
|
description="Manage integrations for your Infisical instance."
|
||||||
|
/>
|
||||||
|
<IntegrationsPageForm />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -3,7 +3,7 @@ import { useGetAdminIntegrationsConfig } from "@app/hooks/api";
|
|||||||
import { MicrosoftTeamsIntegrationForm } from "./MicrosoftTeamsIntegrationForm";
|
import { MicrosoftTeamsIntegrationForm } from "./MicrosoftTeamsIntegrationForm";
|
||||||
import { SlackIntegrationForm } from "./SlackIntegrationForm";
|
import { SlackIntegrationForm } from "./SlackIntegrationForm";
|
||||||
|
|
||||||
export const IntegrationPanel = () => {
|
export const IntegrationsPageForm = () => {
|
||||||
const { data: adminIntegrationsConfig } = useGetAdminIntegrationsConfig();
|
const { data: adminIntegrationsConfig } = useGetAdminIntegrationsConfig();
|
||||||
|
|
||||||
return (
|
return (
|
@ -0,0 +1 @@
|
|||||||
|
export { IntegrationsPageForm } from "./IntegrationsPageForm";
|
25
frontend/src/pages/admin/IntegrationsPage/route.tsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
import { IntegrationsPage } from "./IntegrationsPage";
|
||||||
|
|
||||||
|
export const Route = createFileRoute(
|
||||||
|
"/_authenticate/_inject-org-details/admin/_admin-layout/integrations"
|
||||||
|
)({
|
||||||
|
component: IntegrationsPage,
|
||||||
|
beforeLoad: async () => {
|
||||||
|
return {
|
||||||
|
breadcrumbs: [
|
||||||
|
{
|
||||||
|
label: "Admin",
|
||||||
|
link: linkOptions({ to: "/admin" })
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Integrations",
|
||||||
|
link: linkOptions({
|
||||||
|
to: "/admin/integrations"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
@ -0,0 +1,27 @@
|
|||||||
|
import { Helmet } from "react-helmet";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
import { PageHeader } from "@app/components/v2";
|
||||||
|
|
||||||
|
import { MachineIdentitiesTable } from "./components";
|
||||||
|
|
||||||
|
export const MachineIdentitiesResourcesPage = () => {
|
||||||
|
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="Machine Identities"
|
||||||
|
description="Manage all machine identities within your Infisical instance."
|
||||||
|
/>
|
||||||
|
<MachineIdentitiesTable />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -120,7 +120,7 @@ const IdentityPanelTable = ({
|
|||||||
<Button
|
<Button
|
||||||
className="mt-4 py-3 text-sm"
|
className="mt-4 py-3 text-sm"
|
||||||
isFullWidth
|
isFullWidth
|
||||||
variant="star"
|
variant="outline_bg"
|
||||||
isLoading={isFetchingNextPage}
|
isLoading={isFetchingNextPage}
|
||||||
isDisabled={isFetchingNextPage || !hasNextPage}
|
isDisabled={isFetchingNextPage || !hasNextPage}
|
||||||
onClick={() => fetchNextPage()}
|
onClick={() => fetchNextPage()}
|
||||||
@ -133,7 +133,7 @@ const IdentityPanelTable = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const IdentityPanel = () => {
|
export const MachineIdentitiesTable = () => {
|
||||||
const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
|
const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
|
||||||
"removeServerAdmin"
|
"removeServerAdmin"
|
||||||
] as const);
|
] as const);
|
||||||
@ -161,9 +161,6 @@ export const IdentityPanel = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
<div className="mb-4">
|
|
||||||
<p className="text-xl font-semibold text-mineshaft-100">Identities</p>
|
|
||||||
</div>
|
|
||||||
<IdentityPanelTable handlePopUpOpen={handlePopUpOpen} />
|
<IdentityPanelTable handlePopUpOpen={handlePopUpOpen} />
|
||||||
<DeleteActionModal
|
<DeleteActionModal
|
||||||
isOpen={popUp.removeServerAdmin.isOpen}
|
isOpen={popUp.removeServerAdmin.isOpen}
|