mirror of
https://github.com/Infisical/infisical.git
synced 2025-07-22 13:29:55 +00:00
Compare commits
5 Commits
misc/allow
...
daniel/app
Author | SHA1 | Date | |
---|---|---|---|
97c96acea5 | |||
5e24015f2a | |||
e71b136859 | |||
7d2d69fc7d | |||
0569c7e692 |
@ -1,21 +0,0 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TableName } from "../schemas";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
const hasColumn = await knex.schema.hasColumn(TableName.SuperAdmin, "encryptedEnvOverrides");
|
||||
if (!hasColumn) {
|
||||
await knex.schema.alterTable(TableName.SuperAdmin, (t) => {
|
||||
t.binary("encryptedEnvOverrides").nullable();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
const hasColumn = await knex.schema.hasColumn(TableName.SuperAdmin, "encryptedEnvOverrides");
|
||||
if (hasColumn) {
|
||||
await knex.schema.alterTable(TableName.SuperAdmin, (t) => {
|
||||
t.dropColumn("encryptedEnvOverrides");
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TableName } from "../schemas";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
const existingSecretApprovalPolicies = await knex(TableName.SecretApprovalPolicy)
|
||||
.whereNull("secretPath")
|
||||
.orWhere("secretPath", "");
|
||||
|
||||
const existingAccessApprovalPolicies = await knex(TableName.AccessApprovalPolicy)
|
||||
.whereNull("secretPath")
|
||||
.orWhere("secretPath", "");
|
||||
|
||||
// update all the secret approval policies secretPath to be "/**"
|
||||
if (existingSecretApprovalPolicies.length) {
|
||||
await knex(TableName.SecretApprovalPolicy)
|
||||
.whereIn(
|
||||
"id",
|
||||
existingSecretApprovalPolicies.map((el) => el.id)
|
||||
)
|
||||
.update({
|
||||
secretPath: "/**"
|
||||
});
|
||||
}
|
||||
|
||||
// update all the access approval policies secretPath to be "/**"
|
||||
if (existingAccessApprovalPolicies.length) {
|
||||
await knex(TableName.AccessApprovalPolicy)
|
||||
.whereIn(
|
||||
"id",
|
||||
existingAccessApprovalPolicies.map((el) => el.id)
|
||||
)
|
||||
.update({
|
||||
secretPath: "/**"
|
||||
});
|
||||
}
|
||||
|
||||
await knex.schema.alterTable(TableName.SecretApprovalPolicy, (table) => {
|
||||
table.string("secretPath").notNullable().alter();
|
||||
});
|
||||
|
||||
await knex.schema.alterTable(TableName.AccessApprovalPolicy, (table) => {
|
||||
table.string("secretPath").notNullable().alter();
|
||||
});
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
await knex.schema.alterTable(TableName.SecretApprovalPolicy, (table) => {
|
||||
table.string("secretPath").nullable().alter();
|
||||
});
|
||||
|
||||
await knex.schema.alterTable(TableName.AccessApprovalPolicy, (table) => {
|
||||
table.string("secretPath").nullable().alter();
|
||||
});
|
||||
}
|
@ -11,7 +11,7 @@ export const AccessApprovalPoliciesSchema = z.object({
|
||||
id: z.string().uuid(),
|
||||
name: z.string(),
|
||||
approvals: z.number().default(1),
|
||||
secretPath: z.string().nullable().optional(),
|
||||
secretPath: z.string(),
|
||||
envId: z.string().uuid(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
|
@ -10,7 +10,7 @@ import { TImmutableDBKeys } from "./models";
|
||||
export const SecretApprovalPoliciesSchema = z.object({
|
||||
id: z.string().uuid(),
|
||||
name: z.string(),
|
||||
secretPath: z.string().nullable().optional(),
|
||||
secretPath: z.string(),
|
||||
approvals: z.number().default(1),
|
||||
envId: z.string().uuid(),
|
||||
createdAt: z.date(),
|
||||
|
@ -34,8 +34,7 @@ export const SuperAdminSchema = z.object({
|
||||
encryptedGitHubAppConnectionClientSecret: zodBuffer.nullable().optional(),
|
||||
encryptedGitHubAppConnectionSlug: zodBuffer.nullable().optional(),
|
||||
encryptedGitHubAppConnectionId: zodBuffer.nullable().optional(),
|
||||
encryptedGitHubAppConnectionPrivateKey: zodBuffer.nullable().optional(),
|
||||
encryptedEnvOverrides: zodBuffer.nullable().optional()
|
||||
encryptedGitHubAppConnectionPrivateKey: zodBuffer.nullable().optional()
|
||||
});
|
||||
|
||||
export type TSuperAdmin = z.infer<typeof SuperAdminSchema>;
|
||||
|
@ -2,6 +2,7 @@ import { nanoid } from "nanoid";
|
||||
import { z } from "zod";
|
||||
|
||||
import { ApproverType, BypasserType } from "@app/ee/services/access-approval-policy/access-approval-policy-types";
|
||||
import { removeTrailingSlash } from "@app/lib/fn";
|
||||
import { EnforcementLevel } from "@app/lib/types";
|
||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
@ -19,7 +20,7 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
||||
body: z.object({
|
||||
projectSlug: z.string().trim(),
|
||||
name: z.string().optional(),
|
||||
secretPath: z.string().trim().default("/"),
|
||||
secretPath: z.string().trim().min(1, { message: "Secret path cannot be empty" }).transform(removeTrailingSlash),
|
||||
environment: z.string(),
|
||||
approvers: z
|
||||
.discriminatedUnion("type", [
|
||||
@ -174,8 +175,9 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
||||
secretPath: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, { message: "Secret path cannot be empty" })
|
||||
.optional()
|
||||
.transform((val) => (val === "" ? "/" : val)),
|
||||
.transform((val) => (val ? removeTrailingSlash(val) : val)),
|
||||
approvers: z
|
||||
.discriminatedUnion("type", [
|
||||
z.object({
|
||||
|
@ -23,10 +23,8 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
||||
environment: z.string(),
|
||||
secretPath: z
|
||||
.string()
|
||||
.optional()
|
||||
.nullable()
|
||||
.default("/")
|
||||
.transform((val) => (val ? removeTrailingSlash(val) : val)),
|
||||
.min(1, { message: "Secret path cannot be empty" })
|
||||
.transform((val) => removeTrailingSlash(val)),
|
||||
approvers: z
|
||||
.discriminatedUnion("type", [
|
||||
z.object({ type: z.literal(ApproverType.Group), id: z.string() }),
|
||||
@ -100,10 +98,10 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
||||
approvals: z.number().min(1).default(1),
|
||||
secretPath: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, { message: "Secret path cannot be empty" })
|
||||
.optional()
|
||||
.nullable()
|
||||
.transform((val) => (val ? removeTrailingSlash(val) : val))
|
||||
.transform((val) => (val === "" ? "/" : val)),
|
||||
.transform((val) => (val ? removeTrailingSlash(val) : undefined)),
|
||||
enforcementLevel: z.nativeEnum(EnforcementLevel).optional(),
|
||||
allowedSelfApprovals: z.boolean().default(true)
|
||||
}),
|
||||
|
@ -53,7 +53,7 @@ export interface TAccessApprovalPolicyDALFactory
|
||||
envId: string;
|
||||
enforcementLevel: string;
|
||||
allowedSelfApprovals: boolean;
|
||||
secretPath?: string | null | undefined;
|
||||
secretPath: string;
|
||||
deletedAt?: Date | null | undefined;
|
||||
environment: {
|
||||
id: string;
|
||||
@ -93,7 +93,7 @@ export interface TAccessApprovalPolicyDALFactory
|
||||
envId: string;
|
||||
enforcementLevel: string;
|
||||
allowedSelfApprovals: boolean;
|
||||
secretPath?: string | null | undefined;
|
||||
secretPath: string;
|
||||
deletedAt?: Date | null | undefined;
|
||||
environment: {
|
||||
id: string;
|
||||
@ -116,7 +116,7 @@ export interface TAccessApprovalPolicyDALFactory
|
||||
envId: string;
|
||||
enforcementLevel: string;
|
||||
allowedSelfApprovals: boolean;
|
||||
secretPath?: string | null | undefined;
|
||||
secretPath: string;
|
||||
deletedAt?: Date | null | undefined;
|
||||
}>;
|
||||
findLastValidPolicy: (
|
||||
@ -138,7 +138,7 @@ export interface TAccessApprovalPolicyDALFactory
|
||||
envId: string;
|
||||
enforcementLevel: string;
|
||||
allowedSelfApprovals: boolean;
|
||||
secretPath?: string | null | undefined;
|
||||
secretPath: string;
|
||||
deletedAt?: Date | null | undefined;
|
||||
}
|
||||
| undefined
|
||||
@ -190,7 +190,7 @@ export interface TAccessApprovalPolicyServiceFactory {
|
||||
envId: string;
|
||||
enforcementLevel: string;
|
||||
allowedSelfApprovals: boolean;
|
||||
secretPath?: string | null | undefined;
|
||||
secretPath: string;
|
||||
deletedAt?: Date | null | undefined;
|
||||
}>;
|
||||
deleteAccessApprovalPolicy: ({
|
||||
@ -214,7 +214,7 @@ export interface TAccessApprovalPolicyServiceFactory {
|
||||
envId: string;
|
||||
enforcementLevel: string;
|
||||
allowedSelfApprovals: boolean;
|
||||
secretPath?: string | null | undefined;
|
||||
secretPath: string;
|
||||
deletedAt?: Date | null | undefined;
|
||||
environment: {
|
||||
id: string;
|
||||
@ -252,7 +252,7 @@ export interface TAccessApprovalPolicyServiceFactory {
|
||||
envId: string;
|
||||
enforcementLevel: string;
|
||||
allowedSelfApprovals: boolean;
|
||||
secretPath?: string | null | undefined;
|
||||
secretPath: string;
|
||||
deletedAt?: Date | null | undefined;
|
||||
}>;
|
||||
getAccessApprovalPolicyByProjectSlug: ({
|
||||
@ -286,7 +286,7 @@ export interface TAccessApprovalPolicyServiceFactory {
|
||||
envId: string;
|
||||
enforcementLevel: string;
|
||||
allowedSelfApprovals: boolean;
|
||||
secretPath?: string | null | undefined;
|
||||
secretPath: string;
|
||||
deletedAt?: Date | null | undefined;
|
||||
environment: {
|
||||
id: string;
|
||||
@ -337,7 +337,7 @@ export interface TAccessApprovalPolicyServiceFactory {
|
||||
envId: string;
|
||||
enforcementLevel: string;
|
||||
allowedSelfApprovals: boolean;
|
||||
secretPath?: string | null | undefined;
|
||||
secretPath: string;
|
||||
deletedAt?: Date | null | undefined;
|
||||
environment: {
|
||||
id: string;
|
||||
|
@ -60,6 +60,26 @@ export const accessApprovalPolicyServiceFactory = ({
|
||||
accessApprovalRequestReviewerDAL,
|
||||
orgMembershipDAL
|
||||
}: TAccessApprovalPolicyServiceFactoryDep): TAccessApprovalPolicyServiceFactory => {
|
||||
const $policyExists = async ({
|
||||
envId,
|
||||
secretPath,
|
||||
policyId
|
||||
}: {
|
||||
envId: string;
|
||||
secretPath: string;
|
||||
policyId?: string;
|
||||
}) => {
|
||||
const policy = await accessApprovalPolicyDAL
|
||||
.findOne({
|
||||
envId,
|
||||
secretPath,
|
||||
deletedAt: null
|
||||
})
|
||||
.catch(() => null);
|
||||
|
||||
return policyId ? policy && policy.id !== policyId : Boolean(policy);
|
||||
};
|
||||
|
||||
const createAccessApprovalPolicy: TAccessApprovalPolicyServiceFactory["createAccessApprovalPolicy"] = async ({
|
||||
name,
|
||||
actor,
|
||||
@ -106,6 +126,12 @@ export const accessApprovalPolicyServiceFactory = ({
|
||||
const env = await projectEnvDAL.findOne({ slug: environment, projectId: project.id });
|
||||
if (!env) throw new NotFoundError({ message: `Environment with slug '${environment}' not found` });
|
||||
|
||||
if (await $policyExists({ envId: env.id, secretPath })) {
|
||||
throw new BadRequestError({
|
||||
message: `A policy for secret path '${secretPath}' already exists in environment '${environment}'`
|
||||
});
|
||||
}
|
||||
|
||||
let approverUserIds = userApprovers;
|
||||
if (userApproverNames.length) {
|
||||
const approverUsersInDB = await userDAL.find({
|
||||
@ -279,7 +305,11 @@ export const accessApprovalPolicyServiceFactory = ({
|
||||
) as { username: string; sequence?: number }[];
|
||||
|
||||
const accessApprovalPolicy = await accessApprovalPolicyDAL.findById(policyId);
|
||||
if (!accessApprovalPolicy) throw new BadRequestError({ message: "Approval policy not found" });
|
||||
if (!accessApprovalPolicy) {
|
||||
throw new NotFoundError({
|
||||
message: `Access approval policy with ID '${policyId}' not found`
|
||||
});
|
||||
}
|
||||
|
||||
const currentApprovals = approvals || accessApprovalPolicy.approvals;
|
||||
if (
|
||||
@ -290,9 +320,18 @@ export const accessApprovalPolicyServiceFactory = ({
|
||||
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
|
||||
}
|
||||
|
||||
if (!accessApprovalPolicy) {
|
||||
throw new NotFoundError({ message: `Secret approval policy with ID '${policyId}' not found` });
|
||||
if (
|
||||
await $policyExists({
|
||||
envId: accessApprovalPolicy.envId,
|
||||
secretPath: secretPath || accessApprovalPolicy.secretPath,
|
||||
policyId: accessApprovalPolicy.id
|
||||
})
|
||||
) {
|
||||
throw new BadRequestError({
|
||||
message: `A policy for secret path '${secretPath}' already exists in environment '${accessApprovalPolicy.environment.slug}'`
|
||||
});
|
||||
}
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor,
|
||||
actorId,
|
||||
|
@ -122,7 +122,7 @@ export interface TAccessApprovalPolicyServiceFactory {
|
||||
envId: string;
|
||||
enforcementLevel: string;
|
||||
allowedSelfApprovals: boolean;
|
||||
secretPath?: string | null | undefined;
|
||||
secretPath: string;
|
||||
deletedAt?: Date | null | undefined;
|
||||
}>;
|
||||
deleteAccessApprovalPolicy: ({
|
||||
@ -146,7 +146,7 @@ export interface TAccessApprovalPolicyServiceFactory {
|
||||
envId: string;
|
||||
enforcementLevel: string;
|
||||
allowedSelfApprovals: boolean;
|
||||
secretPath?: string | null | undefined;
|
||||
secretPath: string;
|
||||
deletedAt?: Date | null | undefined;
|
||||
environment: {
|
||||
id: string;
|
||||
@ -218,7 +218,7 @@ export interface TAccessApprovalPolicyServiceFactory {
|
||||
envId: string;
|
||||
enforcementLevel: string;
|
||||
allowedSelfApprovals: boolean;
|
||||
secretPath?: string | null | undefined;
|
||||
secretPath: string;
|
||||
deletedAt?: Date | null | undefined;
|
||||
environment: {
|
||||
id: string;
|
||||
@ -269,7 +269,7 @@ export interface TAccessApprovalPolicyServiceFactory {
|
||||
envId: string;
|
||||
enforcementLevel: string;
|
||||
allowedSelfApprovals: boolean;
|
||||
secretPath?: string | null | undefined;
|
||||
secretPath: string;
|
||||
deletedAt?: Date | null | undefined;
|
||||
environment: {
|
||||
id: string;
|
||||
|
@ -55,6 +55,26 @@ export const secretApprovalPolicyServiceFactory = ({
|
||||
licenseService,
|
||||
secretApprovalRequestDAL
|
||||
}: TSecretApprovalPolicyServiceFactoryDep) => {
|
||||
const $policyExists = async ({
|
||||
envId,
|
||||
secretPath,
|
||||
policyId
|
||||
}: {
|
||||
envId: string;
|
||||
secretPath: string;
|
||||
policyId?: string;
|
||||
}) => {
|
||||
const policy = await secretApprovalPolicyDAL
|
||||
.findOne({
|
||||
envId,
|
||||
secretPath,
|
||||
deletedAt: null
|
||||
})
|
||||
.catch(() => null);
|
||||
|
||||
return policyId ? policy && policy.id !== policyId : Boolean(policy);
|
||||
};
|
||||
|
||||
const createSecretApprovalPolicy = async ({
|
||||
name,
|
||||
actor,
|
||||
@ -106,10 +126,17 @@ export const secretApprovalPolicyServiceFactory = ({
|
||||
}
|
||||
|
||||
const env = await projectEnvDAL.findOne({ slug: environment, projectId });
|
||||
if (!env)
|
||||
if (!env) {
|
||||
throw new NotFoundError({
|
||||
message: `Environment with slug '${environment}' not found in project with ID ${projectId}`
|
||||
});
|
||||
}
|
||||
|
||||
if (await $policyExists({ envId: env.id, secretPath })) {
|
||||
throw new BadRequestError({
|
||||
message: `A policy for secret path '${secretPath}' already exists in environment '${environment}'`
|
||||
});
|
||||
}
|
||||
|
||||
let groupBypassers: string[] = [];
|
||||
let bypasserUserIds: string[] = [];
|
||||
@ -260,6 +287,18 @@ export const secretApprovalPolicyServiceFactory = ({
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
await $policyExists({
|
||||
envId: secretApprovalPolicy.envId,
|
||||
secretPath: secretPath || secretApprovalPolicy.secretPath,
|
||||
policyId: secretApprovalPolicy.id
|
||||
})
|
||||
) {
|
||||
throw new BadRequestError({
|
||||
message: `A policy for secret path '${secretPath}' already exists in environment '${secretApprovalPolicy.environment.slug}'`
|
||||
});
|
||||
}
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor,
|
||||
actorId,
|
||||
|
@ -4,7 +4,7 @@ import { ApproverType, BypasserType } from "../access-approval-policy/access-app
|
||||
|
||||
export type TCreateSapDTO = {
|
||||
approvals: number;
|
||||
secretPath?: string | null;
|
||||
secretPath: string;
|
||||
environment: string;
|
||||
approvers: ({ type: ApproverType.Group; id: string } | { type: ApproverType.User; id?: string; username?: string })[];
|
||||
bypassers?: (
|
||||
@ -20,7 +20,7 @@ export type TCreateSapDTO = {
|
||||
export type TUpdateSapDTO = {
|
||||
secretPolicyId: string;
|
||||
approvals?: number;
|
||||
secretPath?: string | null;
|
||||
secretPath?: string;
|
||||
approvers: ({ type: ApproverType.Group; id: string } | { type: ApproverType.User; id?: string; username?: string })[];
|
||||
bypassers?: (
|
||||
| { type: BypasserType.Group; id: string }
|
||||
|
@ -2,7 +2,6 @@ import { z } from "zod";
|
||||
|
||||
import { QueueWorkerProfile } from "@app/lib/types";
|
||||
|
||||
import { BadRequestError } from "../errors";
|
||||
import { removeTrailingSlash } from "../fn";
|
||||
import { CustomLogger } from "../logger/logger";
|
||||
import { zpStr } from "../zod";
|
||||
@ -342,11 +341,8 @@ const envSchema = z
|
||||
|
||||
export type TEnvConfig = Readonly<z.infer<typeof envSchema>>;
|
||||
let envCfg: TEnvConfig;
|
||||
let originalEnvConfig: TEnvConfig;
|
||||
|
||||
export const getConfig = () => envCfg;
|
||||
export const getOriginalConfig = () => originalEnvConfig;
|
||||
|
||||
// cannot import singleton logger directly as it needs config to load various transport
|
||||
export const initEnvConfig = (logger?: CustomLogger) => {
|
||||
const parsedEnv = envSchema.safeParse(process.env);
|
||||
@ -356,115 +352,10 @@ export const initEnvConfig = (logger?: CustomLogger) => {
|
||||
process.exit(-1);
|
||||
}
|
||||
|
||||
const config = Object.freeze(parsedEnv.data);
|
||||
envCfg = config;
|
||||
|
||||
if (!originalEnvConfig) {
|
||||
originalEnvConfig = config;
|
||||
}
|
||||
|
||||
envCfg = Object.freeze(parsedEnv.data);
|
||||
return envCfg;
|
||||
};
|
||||
|
||||
// A list of environment variables that can be overwritten
|
||||
export const overwriteSchema: {
|
||||
[key: string]: {
|
||||
name: string;
|
||||
fields: { key: keyof TEnvConfig; description?: string }[];
|
||||
};
|
||||
} = {
|
||||
azure: {
|
||||
name: "Azure",
|
||||
fields: [
|
||||
{
|
||||
key: "INF_APP_CONNECTION_AZURE_CLIENT_ID",
|
||||
description: "The Application (Client) ID of your Azure application."
|
||||
},
|
||||
{
|
||||
key: "INF_APP_CONNECTION_AZURE_CLIENT_SECRET",
|
||||
description: "The Client Secret of your Azure application."
|
||||
}
|
||||
]
|
||||
},
|
||||
google_sso: {
|
||||
name: "Google SSO",
|
||||
fields: [
|
||||
{
|
||||
key: "CLIENT_ID_GOOGLE_LOGIN",
|
||||
description: "The Client ID of your GCP OAuth2 application."
|
||||
},
|
||||
{
|
||||
key: "CLIENT_SECRET_GOOGLE_LOGIN",
|
||||
description: "The Client Secret of your GCP OAuth2 application."
|
||||
}
|
||||
]
|
||||
},
|
||||
github_sso: {
|
||||
name: "GitHub SSO",
|
||||
fields: [
|
||||
{
|
||||
key: "CLIENT_ID_GITHUB_LOGIN",
|
||||
description: "The Client ID of your GitHub OAuth application."
|
||||
},
|
||||
{
|
||||
key: "CLIENT_SECRET_GITHUB_LOGIN",
|
||||
description: "The Client Secret of your GitHub OAuth application."
|
||||
}
|
||||
]
|
||||
},
|
||||
gitlab_sso: {
|
||||
name: "GitLab SSO",
|
||||
fields: [
|
||||
{
|
||||
key: "CLIENT_ID_GITLAB_LOGIN",
|
||||
description: "The Client ID of your GitLab application."
|
||||
},
|
||||
{
|
||||
key: "CLIENT_SECRET_GITLAB_LOGIN",
|
||||
description: "The Secret of your GitLab application."
|
||||
},
|
||||
{
|
||||
key: "CLIENT_GITLAB_LOGIN_URL",
|
||||
description:
|
||||
"The URL of your self-hosted instance of GitLab where the OAuth application is registered. If no URL is passed in, this will default to https://gitlab.com."
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
export const overridableKeys = new Set(
|
||||
Object.values(overwriteSchema).flatMap(({ fields }) => fields.map(({ key }) => key))
|
||||
);
|
||||
|
||||
export const validateOverrides = (config: Record<string, string>) => {
|
||||
const allowedOverrides = Object.fromEntries(
|
||||
Object.entries(config).filter(([key]) => overridableKeys.has(key as keyof z.input<typeof envSchema>))
|
||||
);
|
||||
|
||||
const tempEnv: Record<string, unknown> = { ...process.env, ...allowedOverrides };
|
||||
const parsedResult = envSchema.safeParse(tempEnv);
|
||||
|
||||
if (!parsedResult.success) {
|
||||
const errorDetails = parsedResult.error.issues
|
||||
.map((issue) => `Key: "${issue.path.join(".")}", Error: ${issue.message}`)
|
||||
.join("\n");
|
||||
throw new BadRequestError({ message: errorDetails });
|
||||
}
|
||||
};
|
||||
|
||||
export const overrideEnvConfig = (config: Record<string, string>) => {
|
||||
const allowedOverrides = Object.fromEntries(
|
||||
Object.entries(config).filter(([key]) => overridableKeys.has(key as keyof z.input<typeof envSchema>))
|
||||
);
|
||||
|
||||
const tempEnv: Record<string, unknown> = { ...process.env, ...allowedOverrides };
|
||||
const parsedResult = envSchema.safeParse(tempEnv);
|
||||
|
||||
if (parsedResult.success) {
|
||||
envCfg = Object.freeze(parsedResult.data);
|
||||
}
|
||||
};
|
||||
|
||||
export const formatSmtpConfig = () => {
|
||||
const tlsOptions: {
|
||||
rejectUnauthorized: boolean;
|
||||
|
@ -300,7 +300,6 @@ import { injectIdentity } from "../plugins/auth/inject-identity";
|
||||
import { injectPermission } from "../plugins/auth/inject-permission";
|
||||
import { injectRateLimits } from "../plugins/inject-rate-limits";
|
||||
import { registerV1Routes } from "./v1";
|
||||
import { initializeOauthConfigSync } from "./v1/sso-router";
|
||||
import { registerV2Routes } from "./v2";
|
||||
import { registerV3Routes } from "./v3";
|
||||
|
||||
@ -2047,16 +2046,6 @@ export const registerRoutes = async (
|
||||
}
|
||||
}
|
||||
|
||||
const configSyncJob = await superAdminService.initializeEnvConfigSync();
|
||||
if (configSyncJob) {
|
||||
cronJobs.push(configSyncJob);
|
||||
}
|
||||
|
||||
const oauthConfigSyncJob = await initializeOauthConfigSync();
|
||||
if (oauthConfigSyncJob) {
|
||||
cronJobs.push(oauthConfigSyncJob);
|
||||
}
|
||||
|
||||
server.decorate<FastifyZodProvider["store"]>("store", {
|
||||
user: userDAL,
|
||||
kmipClient: kmipClientDAL
|
||||
|
@ -8,7 +8,7 @@ import {
|
||||
SuperAdminSchema,
|
||||
UsersSchema
|
||||
} from "@app/db/schemas";
|
||||
import { getConfig, overridableKeys } from "@app/lib/config/env";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { invalidateCacheLimit, readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
||||
@ -42,8 +42,7 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
|
||||
encryptedGitHubAppConnectionClientSecret: true,
|
||||
encryptedGitHubAppConnectionSlug: true,
|
||||
encryptedGitHubAppConnectionId: true,
|
||||
encryptedGitHubAppConnectionPrivateKey: true,
|
||||
encryptedEnvOverrides: true
|
||||
encryptedGitHubAppConnectionPrivateKey: true
|
||||
}).extend({
|
||||
isMigrationModeOn: z.boolean(),
|
||||
defaultAuthOrgSlug: z.string().nullable(),
|
||||
@ -111,14 +110,11 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
|
||||
.refine((content) => DOMPurify.sanitize(content) === content, {
|
||||
message: "Page frame content contains unsafe HTML."
|
||||
})
|
||||
.optional(),
|
||||
envOverrides: z.record(z.enum(Array.from(overridableKeys) as [string, ...string[]]), z.string()).optional()
|
||||
.optional()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
config: SuperAdminSchema.omit({
|
||||
encryptedEnvOverrides: true
|
||||
}).extend({
|
||||
config: SuperAdminSchema.extend({
|
||||
defaultAuthOrgSlug: z.string().nullable()
|
||||
})
|
||||
})
|
||||
@ -385,41 +381,6 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "GET",
|
||||
url: "/env-overrides",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
response: {
|
||||
200: z.record(
|
||||
z.string(),
|
||||
z.object({
|
||||
name: z.string(),
|
||||
fields: z
|
||||
.object({
|
||||
key: z.string(),
|
||||
value: z.string(),
|
||||
hasEnvEntry: z.boolean(),
|
||||
description: z.string().optional()
|
||||
})
|
||||
.array()
|
||||
})
|
||||
)
|
||||
}
|
||||
},
|
||||
onRequest: (req, res, done) => {
|
||||
verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN])(req, res, () => {
|
||||
verifySuperAdmin(req, res, done);
|
||||
});
|
||||
},
|
||||
handler: async () => {
|
||||
const envOverrides = await server.services.superAdmin.getEnvOverridesOrganized();
|
||||
return envOverrides;
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "DELETE",
|
||||
url: "/user-management/users/:userId",
|
||||
|
@ -382,8 +382,7 @@ export const registerSyncSecretsEndpoints = <T extends TSecretSync, I extends TS
|
||||
{
|
||||
syncId,
|
||||
destination,
|
||||
importBehavior,
|
||||
auditLogInfo: req.auditLogInfo
|
||||
importBehavior
|
||||
},
|
||||
req.permission
|
||||
)) as T;
|
||||
@ -416,8 +415,7 @@ export const registerSyncSecretsEndpoints = <T extends TSecretSync, I extends TS
|
||||
const secretSync = (await server.services.secretSync.triggerSecretSyncRemoveSecretsById(
|
||||
{
|
||||
syncId,
|
||||
destination,
|
||||
auditLogInfo: req.auditLogInfo
|
||||
destination
|
||||
},
|
||||
req.permission
|
||||
)) as T;
|
||||
|
@ -9,7 +9,6 @@
|
||||
import { Authenticator } from "@fastify/passport";
|
||||
import fastifySession from "@fastify/session";
|
||||
import RedisStore from "connect-redis";
|
||||
import { CronJob } from "cron";
|
||||
import { Strategy as GitLabStrategy } from "passport-gitlab2";
|
||||
import { Strategy as GoogleStrategy } from "passport-google-oauth20";
|
||||
import { Strategy as OAuth2Strategy } from "passport-oauth2";
|
||||
@ -26,14 +25,27 @@ import { AuthMethod } from "@app/services/auth/auth-type";
|
||||
import { OrgAuthMethod } from "@app/services/org/org-types";
|
||||
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
|
||||
|
||||
const passport = new Authenticator({ key: "sso", userProperty: "passportUser" });
|
||||
|
||||
let serverInstance: FastifyZodProvider | null = null;
|
||||
|
||||
export const registerOauthMiddlewares = (server: FastifyZodProvider) => {
|
||||
serverInstance = server;
|
||||
export const registerSsoRouter = async (server: FastifyZodProvider) => {
|
||||
const appCfg = getConfig();
|
||||
|
||||
const passport = new Authenticator({ key: "sso", userProperty: "passportUser" });
|
||||
const redisStore = new RedisStore({
|
||||
client: server.redis,
|
||||
prefix: "oauth-session:",
|
||||
ttl: 600 // 10 minutes
|
||||
});
|
||||
|
||||
await server.register(fastifySession, {
|
||||
secret: appCfg.COOKIE_SECRET_SIGN_KEY,
|
||||
store: redisStore,
|
||||
cookie: {
|
||||
secure: appCfg.HTTPS_ENABLED,
|
||||
sameSite: "lax" // we want cookies to be sent to Infisical in redirects originating from IDP server
|
||||
}
|
||||
});
|
||||
await server.register(passport.initialize());
|
||||
await server.register(passport.secureSession());
|
||||
|
||||
// passport oauth strategy for Google
|
||||
const isGoogleOauthActive = Boolean(appCfg.CLIENT_ID_GOOGLE_LOGIN && appCfg.CLIENT_SECRET_GOOGLE_LOGIN);
|
||||
if (isGoogleOauthActive) {
|
||||
@ -164,49 +176,6 @@ export const registerOauthMiddlewares = (server: FastifyZodProvider) => {
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const refreshOauthConfig = () => {
|
||||
if (!serverInstance) {
|
||||
logger.warn("Cannot refresh OAuth config: server instance not available");
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("Refreshing OAuth configuration...");
|
||||
registerOauthMiddlewares(serverInstance);
|
||||
};
|
||||
|
||||
export const initializeOauthConfigSync = async () => {
|
||||
logger.info("Setting up background sync process for oauth configuration");
|
||||
|
||||
// sync every 5 minutes
|
||||
const job = new CronJob("*/5 * * * *", refreshOauthConfig);
|
||||
job.start();
|
||||
|
||||
return job;
|
||||
};
|
||||
|
||||
export const registerSsoRouter = async (server: FastifyZodProvider) => {
|
||||
const appCfg = getConfig();
|
||||
|
||||
const redisStore = new RedisStore({
|
||||
client: server.redis,
|
||||
prefix: "oauth-session:",
|
||||
ttl: 600 // 10 minutes
|
||||
});
|
||||
|
||||
await server.register(fastifySession, {
|
||||
secret: appCfg.COOKIE_SECRET_SIGN_KEY,
|
||||
store: redisStore,
|
||||
cookie: {
|
||||
secure: appCfg.HTTPS_ENABLED,
|
||||
sameSite: "lax" // we want cookies to be sent to Infisical in redirects originating from IDP server
|
||||
}
|
||||
});
|
||||
await server.register(passport.initialize());
|
||||
await server.register(passport.secureSession());
|
||||
|
||||
registerOauthMiddlewares(server);
|
||||
|
||||
server.route({
|
||||
url: "/redirect/google",
|
||||
|
@ -93,25 +93,23 @@ export const identityProjectServiceFactory = ({
|
||||
projectId
|
||||
);
|
||||
|
||||
if (requestedRoleChange !== ProjectMembershipRole.NoAccess) {
|
||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||
membership.shouldUseNewPrivilegeSystem,
|
||||
ProjectPermissionIdentityActions.GrantPrivileges,
|
||||
ProjectPermissionSub.Identity,
|
||||
permission,
|
||||
rolePermission
|
||||
);
|
||||
if (!permissionBoundary.isValid)
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to assign to role",
|
||||
membership.shouldUseNewPrivilegeSystem,
|
||||
ProjectPermissionIdentityActions.GrantPrivileges,
|
||||
ProjectPermissionSub.Identity
|
||||
),
|
||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||
});
|
||||
}
|
||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||
membership.shouldUseNewPrivilegeSystem,
|
||||
ProjectPermissionIdentityActions.GrantPrivileges,
|
||||
ProjectPermissionSub.Identity,
|
||||
permission,
|
||||
rolePermission
|
||||
);
|
||||
if (!permissionBoundary.isValid)
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to assign to role",
|
||||
membership.shouldUseNewPrivilegeSystem,
|
||||
ProjectPermissionIdentityActions.GrantPrivileges,
|
||||
ProjectPermissionSub.Identity
|
||||
),
|
||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||
});
|
||||
}
|
||||
|
||||
// validate custom roles input
|
||||
|
@ -69,25 +69,23 @@ export const identityServiceFactory = ({
|
||||
orgId
|
||||
);
|
||||
const isCustomRole = Boolean(customRole);
|
||||
if (role !== OrgMembershipRole.NoAccess) {
|
||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||
membership.shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.GrantPrivileges,
|
||||
OrgPermissionSubjects.Identity,
|
||||
permission,
|
||||
rolePermission
|
||||
);
|
||||
if (!permissionBoundary.isValid)
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to create identity",
|
||||
membership.shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.GrantPrivileges,
|
||||
OrgPermissionSubjects.Identity
|
||||
),
|
||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||
});
|
||||
}
|
||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||
membership.shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.GrantPrivileges,
|
||||
OrgPermissionSubjects.Identity,
|
||||
permission,
|
||||
rolePermission
|
||||
);
|
||||
if (!permissionBoundary.isValid)
|
||||
throw new PermissionBoundaryError({
|
||||
message: constructPermissionErrorMessage(
|
||||
"Failed to create identity",
|
||||
membership.shouldUseNewPrivilegeSystem,
|
||||
OrgPermissionIdentityActions.GrantPrivileges,
|
||||
OrgPermissionSubjects.Identity
|
||||
),
|
||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||
});
|
||||
|
||||
const plan = await licenseService.getPlan(orgId);
|
||||
|
||||
@ -189,7 +187,6 @@ export const identityServiceFactory = ({
|
||||
),
|
||||
details: { missingPermissions: appliedRolePermissionBoundary.missingPermissions }
|
||||
});
|
||||
|
||||
if (isCustomRole) customRole = customOrgRole;
|
||||
}
|
||||
|
||||
|
@ -5,13 +5,7 @@ import jwt from "jsonwebtoken";
|
||||
import { IdentityAuthMethod, OrgMembershipRole, TSuperAdmin, TSuperAdminUpdate } from "@app/db/schemas";
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import { PgSqlLock, TKeyStoreFactory } from "@app/keystore/keystore";
|
||||
import {
|
||||
getConfig,
|
||||
getOriginalConfig,
|
||||
overrideEnvConfig,
|
||||
overwriteSchema,
|
||||
validateOverrides
|
||||
} from "@app/lib/config/env";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
||||
import { generateUserSrpKeys, getUserPrivateKey } from "@app/lib/crypto/srp";
|
||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||
@ -39,7 +33,6 @@ import { TInvalidateCacheQueueFactory } from "./invalidate-cache-queue";
|
||||
import { TSuperAdminDALFactory } from "./super-admin-dal";
|
||||
import {
|
||||
CacheType,
|
||||
EnvOverrides,
|
||||
LoginMethod,
|
||||
TAdminBootstrapInstanceDTO,
|
||||
TAdminGetIdentitiesDTO,
|
||||
@ -241,45 +234,6 @@ export const superAdminServiceFactory = ({
|
||||
adminIntegrationsConfig = config;
|
||||
};
|
||||
|
||||
const getEnvOverrides = async () => {
|
||||
const serverCfg = await serverCfgDAL.findById(ADMIN_CONFIG_DB_UUID);
|
||||
|
||||
if (!serverCfg || !serverCfg.encryptedEnvOverrides) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const decrypt = kmsService.decryptWithRootKey();
|
||||
|
||||
const overrides = JSON.parse(decrypt(serverCfg.encryptedEnvOverrides).toString()) as Record<string, string>;
|
||||
|
||||
return overrides;
|
||||
};
|
||||
|
||||
const getEnvOverridesOrganized = async (): Promise<EnvOverrides> => {
|
||||
const overrides = await getEnvOverrides();
|
||||
const ogConfig = getOriginalConfig();
|
||||
|
||||
return Object.fromEntries(
|
||||
Object.entries(overwriteSchema).map(([groupKey, groupDef]) => [
|
||||
groupKey,
|
||||
{
|
||||
name: groupDef.name,
|
||||
fields: groupDef.fields.map(({ key, description }) => ({
|
||||
key,
|
||||
description,
|
||||
value: overrides[key] || "",
|
||||
hasEnvEntry: !!(ogConfig as unknown as Record<string, string | undefined>)[key]
|
||||
}))
|
||||
}
|
||||
])
|
||||
);
|
||||
};
|
||||
|
||||
const $syncEnvConfig = async () => {
|
||||
const config = await getEnvOverrides();
|
||||
overrideEnvConfig(config);
|
||||
};
|
||||
|
||||
const updateServerCfg = async (
|
||||
data: TSuperAdminUpdate & {
|
||||
slackClientId?: string;
|
||||
@ -292,7 +246,6 @@ export const superAdminServiceFactory = ({
|
||||
gitHubAppConnectionSlug?: string;
|
||||
gitHubAppConnectionId?: string;
|
||||
gitHubAppConnectionPrivateKey?: string;
|
||||
envOverrides?: Record<string, string>;
|
||||
},
|
||||
userId: string
|
||||
) => {
|
||||
@ -421,17 +374,6 @@ export const superAdminServiceFactory = ({
|
||||
gitHubAppConnectionSettingsUpdated = true;
|
||||
}
|
||||
|
||||
let envOverridesUpdated = false;
|
||||
if (data.envOverrides !== undefined) {
|
||||
// Verify input format
|
||||
validateOverrides(data.envOverrides);
|
||||
|
||||
const encryptedEnvOverrides = encryptWithRoot(Buffer.from(JSON.stringify(data.envOverrides)));
|
||||
updatedData.encryptedEnvOverrides = encryptedEnvOverrides;
|
||||
updatedData.envOverrides = undefined;
|
||||
envOverridesUpdated = true;
|
||||
}
|
||||
|
||||
const updatedServerCfg = await serverCfgDAL.updateById(ADMIN_CONFIG_DB_UUID, updatedData);
|
||||
|
||||
await keyStore.setItemWithExpiry(ADMIN_CONFIG_KEY, ADMIN_CONFIG_KEY_EXP, JSON.stringify(updatedServerCfg));
|
||||
@ -440,10 +382,6 @@ export const superAdminServiceFactory = ({
|
||||
await $syncAdminIntegrationConfig();
|
||||
}
|
||||
|
||||
if (envOverridesUpdated) {
|
||||
await $syncEnvConfig();
|
||||
}
|
||||
|
||||
if (
|
||||
updatedServerCfg.encryptedMicrosoftTeamsAppId &&
|
||||
updatedServerCfg.encryptedMicrosoftTeamsClientSecret &&
|
||||
@ -876,18 +814,6 @@ export const superAdminServiceFactory = ({
|
||||
return job;
|
||||
};
|
||||
|
||||
const initializeEnvConfigSync = async () => {
|
||||
logger.info("Setting up background sync process for environment overrides");
|
||||
|
||||
await $syncEnvConfig();
|
||||
|
||||
// sync every 5 minutes
|
||||
const job = new CronJob("*/5 * * * *", $syncEnvConfig);
|
||||
job.start();
|
||||
|
||||
return job;
|
||||
};
|
||||
|
||||
return {
|
||||
initServerCfg,
|
||||
updateServerCfg,
|
||||
@ -907,9 +833,6 @@ export const superAdminServiceFactory = ({
|
||||
getOrganizations,
|
||||
deleteOrganization,
|
||||
deleteOrganizationMembership,
|
||||
initializeAdminIntegrationConfigSync,
|
||||
initializeEnvConfigSync,
|
||||
getEnvOverrides,
|
||||
getEnvOverridesOrganized
|
||||
initializeAdminIntegrationConfigSync
|
||||
};
|
||||
};
|
||||
|
@ -1,5 +1,3 @@
|
||||
import { TEnvConfig } from "@app/lib/config/env";
|
||||
|
||||
export type TAdminSignUpDTO = {
|
||||
email: string;
|
||||
password: string;
|
||||
@ -76,10 +74,3 @@ export type TAdminIntegrationConfig = {
|
||||
privateKey: string;
|
||||
};
|
||||
};
|
||||
|
||||
export interface EnvOverrides {
|
||||
[key: string]: {
|
||||
name: string;
|
||||
fields: { key: keyof TEnvConfig; value: string; hasEnvEntry: boolean; description?: string }[];
|
||||
};
|
||||
}
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 868 KiB |
@ -794,9 +794,3 @@ If export type is set to `otlp`, you will have to configure a value for `OTEL_EX
|
||||
The TLS header used to propagate the client certificate from the load balancer
|
||||
to the server.
|
||||
</ParamField>
|
||||
|
||||
## Environment Variable Overrides
|
||||
|
||||
If you can't directly access and modify environment variables, you can update them using the [Server Admin Console](/documentation/platform/admin-panel/server-admin).
|
||||
|
||||

|
||||
|
@ -1,42 +0,0 @@
|
||||
export const HighlightText = ({
|
||||
text,
|
||||
highlight,
|
||||
highlightClassName
|
||||
}: {
|
||||
text: string | undefined | null;
|
||||
highlight: string;
|
||||
highlightClassName?: string;
|
||||
}) => {
|
||||
if (!text) return null;
|
||||
const searchTerm = highlight.toLowerCase().trim();
|
||||
|
||||
if (!searchTerm) return <span>{text}</span>;
|
||||
|
||||
const parts: React.ReactNode[] = [];
|
||||
let lastIndex = 0;
|
||||
|
||||
const escapedSearchTerm = searchTerm.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
const regex = new RegExp(escapedSearchTerm, "gi");
|
||||
|
||||
text.replace(regex, (match: string, offset: number) => {
|
||||
if (offset > lastIndex) {
|
||||
parts.push(<span key={`pre-${lastIndex}`}>{text.substring(lastIndex, offset)}</span>);
|
||||
}
|
||||
|
||||
parts.push(
|
||||
<span key={`match-${offset}`} className={highlightClassName || "bg-yellow/30"}>
|
||||
{match}
|
||||
</span>
|
||||
);
|
||||
|
||||
lastIndex = offset + match.length;
|
||||
|
||||
return match;
|
||||
});
|
||||
|
||||
if (lastIndex < text.length) {
|
||||
parts.push(<span key={`post-${lastIndex}`}>{text.substring(lastIndex)}</span>);
|
||||
}
|
||||
|
||||
return parts;
|
||||
};
|
@ -1 +0,0 @@
|
||||
export { HighlightText } from "./HighlightText";
|
@ -170,7 +170,7 @@ export type TCreateAccessPolicyDTO = {
|
||||
approvers?: Approver[];
|
||||
bypassers?: Bypasser[];
|
||||
approvals?: number;
|
||||
secretPath?: string;
|
||||
secretPath: string;
|
||||
enforcementLevel?: EnforcementLevel;
|
||||
allowedSelfApprovals: boolean;
|
||||
approvalsRequired?: { numberOfApprovals: number; stepNumber: number }[];
|
||||
|
@ -10,7 +10,6 @@ import {
|
||||
AdminGetUsersFilters,
|
||||
AdminIntegrationsConfig,
|
||||
OrganizationWithProjects,
|
||||
TGetEnvOverrides,
|
||||
TGetInvalidatingCacheStatus,
|
||||
TGetServerRootKmsEncryptionDetails,
|
||||
TServerConfig
|
||||
@ -32,8 +31,7 @@ export const adminQueryKeys = {
|
||||
getAdminSlackConfig: () => ["admin-slack-config"] as const,
|
||||
getServerEncryptionStrategies: () => ["server-encryption-strategies"] as const,
|
||||
getInvalidateCache: () => ["admin-invalidate-cache"] as const,
|
||||
getAdminIntegrationsConfig: () => ["admin-integrations-config"] as const,
|
||||
getEnvOverrides: () => ["env-overrides"] as const
|
||||
getAdminIntegrationsConfig: () => ["admin-integrations-config"] as const
|
||||
};
|
||||
|
||||
export const fetchServerConfig = async () => {
|
||||
@ -165,13 +163,3 @@ export const useGetInvalidatingCacheStatus = (enabled = true) => {
|
||||
refetchInterval: (data) => (data ? 3000 : false)
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetEnvOverrides = () => {
|
||||
return useQuery({
|
||||
queryKey: adminQueryKeys.getEnvOverrides(),
|
||||
queryFn: async () => {
|
||||
const { data } = await apiRequest.get<TGetEnvOverrides>("/api/v1/admin/env-overrides");
|
||||
return data;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -48,7 +48,6 @@ export type TServerConfig = {
|
||||
authConsentContent?: string;
|
||||
pageFrameContent?: string;
|
||||
invalidatingCache: boolean;
|
||||
envOverrides?: Record<string, string>;
|
||||
};
|
||||
|
||||
export type TUpdateServerConfigDTO = {
|
||||
@ -62,7 +61,6 @@ export type TUpdateServerConfigDTO = {
|
||||
gitHubAppConnectionSlug?: string;
|
||||
gitHubAppConnectionId?: string;
|
||||
gitHubAppConnectionPrivateKey?: string;
|
||||
envOverrides?: Record<string, string>;
|
||||
} & Partial<TServerConfig>;
|
||||
|
||||
export type TCreateAdminUserDTO = {
|
||||
@ -140,10 +138,3 @@ export type TInvalidateCacheDTO = {
|
||||
export type TGetInvalidatingCacheStatus = {
|
||||
invalidating: boolean;
|
||||
};
|
||||
|
||||
export interface TGetEnvOverrides {
|
||||
[key: string]: {
|
||||
name: string;
|
||||
fields: { key: string; value: string; hasEnvEntry: boolean; description?: string }[];
|
||||
};
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ export type TCreateSecretPolicyDTO = {
|
||||
workspaceId: string;
|
||||
name?: string;
|
||||
environment: string;
|
||||
secretPath?: string | null;
|
||||
secretPath: string;
|
||||
approvers?: Approver[];
|
||||
bypassers?: Bypasser[];
|
||||
approvals?: number;
|
||||
@ -62,7 +62,7 @@ export type TUpdateSecretPolicyDTO = {
|
||||
name?: string;
|
||||
approvers?: Approver[];
|
||||
bypassers?: Bypasser[];
|
||||
secretPath?: string | null;
|
||||
secretPath?: string;
|
||||
approvals?: number;
|
||||
allowedSelfApprovals?: boolean;
|
||||
enforcementLevel?: EnforcementLevel;
|
||||
|
@ -29,11 +29,6 @@ const generalTabs = [
|
||||
label: "Caching",
|
||||
icon: "note",
|
||||
link: "/admin/caching"
|
||||
},
|
||||
{
|
||||
label: "Environment Variables",
|
||||
icon: "unlock",
|
||||
link: "/admin/environment"
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -1,27 +0,0 @@
|
||||
import { Helmet } from "react-helmet";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { PageHeader } from "@app/components/v2";
|
||||
|
||||
import { EnvironmentPageForm } from "./components";
|
||||
|
||||
export const EnvironmentPage = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="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="Environment Variables"
|
||||
description="Manage the environment variables for your Infisical instance."
|
||||
/>
|
||||
<EnvironmentPageForm />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -1,264 +0,0 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { Control, Controller, useForm, useWatch } from "react-hook-form";
|
||||
import {
|
||||
faArrowUpRightFromSquare,
|
||||
faBookOpen,
|
||||
faChevronRight,
|
||||
faExclamationTriangle,
|
||||
faMagnifyingGlass
|
||||
} 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, SecretInput, Tooltip } from "@app/components/v2";
|
||||
import { HighlightText } from "@app/components/v2/HighlightText";
|
||||
import { useGetEnvOverrides, useUpdateServerConfig } from "@app/hooks/api";
|
||||
|
||||
type TForm = Record<string, string>;
|
||||
|
||||
export const GroupContainer = ({
|
||||
group,
|
||||
control,
|
||||
search
|
||||
}: {
|
||||
group: {
|
||||
fields: {
|
||||
key: string;
|
||||
value: string;
|
||||
hasEnvEntry: boolean;
|
||||
description?: string;
|
||||
}[];
|
||||
name: string;
|
||||
};
|
||||
control: Control<TForm, any, TForm>;
|
||||
search: string;
|
||||
}) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<div
|
||||
key={group.name}
|
||||
className="overflow-clip border border-b-0 border-mineshaft-600 bg-mineshaft-800 first:rounded-t-md last:rounded-b-md last:border-b"
|
||||
>
|
||||
<div
|
||||
className="flex h-14 cursor-pointer items-center px-5 py-4 text-sm text-gray-300"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={() => setOpen((o) => !o)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
setOpen((o) => !o);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
className={`mr-8 transition-transform duration-100 ${open || search ? "rotate-90" : ""}`}
|
||||
icon={faChevronRight}
|
||||
/>
|
||||
|
||||
<div className="flex-grow select-none text-base">{group.name}</div>
|
||||
</div>
|
||||
|
||||
{(open || search) && (
|
||||
<div className="flex flex-col">
|
||||
{group.fields.map((field) => (
|
||||
<div
|
||||
key={field.key}
|
||||
className="flex items-center justify-between gap-4 border-t border-mineshaft-500 bg-mineshaft-700/50 p-4"
|
||||
>
|
||||
<div className="flex max-w-lg flex-col">
|
||||
<span className="text-sm">
|
||||
<HighlightText text={field.key} highlight={search} />
|
||||
</span>
|
||||
<span className="text-sm text-mineshaft-400">
|
||||
<HighlightText text={field.description} highlight={search} />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex grow items-center justify-end gap-2">
|
||||
{field.hasEnvEntry && (
|
||||
<Tooltip
|
||||
content="Setting this value will override an existing environment variable"
|
||||
className="text-center"
|
||||
>
|
||||
<FontAwesomeIcon icon={faExclamationTriangle} className="text-yellow" />
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
<Controller
|
||||
control={control}
|
||||
name={field.key}
|
||||
render={({ field: formGenField, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
className="mb-0 w-full max-w-sm"
|
||||
>
|
||||
<SecretInput
|
||||
{...formGenField}
|
||||
autoComplete="off"
|
||||
containerClassName="text-bunker-300 hover:border-mineshaft-400 border border-mineshaft-600 bg-bunker-600 px-2 py-1.5"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const EnvironmentPageForm = () => {
|
||||
const { data: envOverrides } = useGetEnvOverrides();
|
||||
const { mutateAsync: updateServerConfig } = useUpdateServerConfig();
|
||||
const [search, setSearch] = useState("");
|
||||
|
||||
const allFields = useMemo(() => {
|
||||
if (!envOverrides) return [];
|
||||
return Object.values(envOverrides).flatMap((group) => group.fields);
|
||||
}, [envOverrides]);
|
||||
|
||||
const formSchema = useMemo(() => {
|
||||
return z.object(Object.fromEntries(allFields.map((field) => [field.key, z.string()])));
|
||||
}, [allFields]);
|
||||
|
||||
const defaultValues = useMemo(() => {
|
||||
const values: Record<string, string> = {};
|
||||
allFields.forEach((field) => {
|
||||
values[field.key] = field.value ?? "";
|
||||
});
|
||||
return values;
|
||||
}, [allFields]);
|
||||
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
reset,
|
||||
formState: { isSubmitting, isDirty }
|
||||
} = useForm<TForm>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues
|
||||
});
|
||||
|
||||
const formValues = useWatch({ control });
|
||||
|
||||
const filteredData = useMemo(() => {
|
||||
if (!envOverrides) return [];
|
||||
|
||||
const searchTerm = search.toLowerCase().trim();
|
||||
if (!searchTerm) {
|
||||
return Object.values(envOverrides);
|
||||
}
|
||||
|
||||
return Object.values(envOverrides)
|
||||
.map((group) => {
|
||||
const filteredFields = group.fields.filter(
|
||||
(field) =>
|
||||
field.key.toLowerCase().includes(searchTerm) ||
|
||||
(field.description ?? "").toLowerCase().includes(searchTerm)
|
||||
);
|
||||
|
||||
if (filteredFields.length > 0) {
|
||||
return { ...group, fields: filteredFields };
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.filter(Boolean);
|
||||
}, [search, formValues, envOverrides]);
|
||||
|
||||
useEffect(() => {
|
||||
reset(defaultValues);
|
||||
}, [defaultValues, reset]);
|
||||
|
||||
const onSubmit = useCallback(
|
||||
async (formData: TForm) => {
|
||||
try {
|
||||
const filteredFormData = Object.fromEntries(
|
||||
Object.entries(formData).filter(([, value]) => value !== "")
|
||||
);
|
||||
await updateServerConfig({
|
||||
envOverrides: filteredFormData
|
||||
});
|
||||
|
||||
createNotification({
|
||||
type: "success",
|
||||
text: "Environment overrides updated successfully. It can take up to 5 minutes to take effect."
|
||||
});
|
||||
|
||||
reset(formData);
|
||||
} catch (error) {
|
||||
const errorMessage =
|
||||
(error as any)?.response?.data?.message ||
|
||||
(error as any)?.message ||
|
||||
"An unknown error occurred";
|
||||
createNotification({
|
||||
type: "error",
|
||||
title: "Failed to update environment overrides",
|
||||
text: errorMessage
|
||||
});
|
||||
}
|
||||
},
|
||||
[reset, updateServerConfig]
|
||||
);
|
||||
|
||||
return (
|
||||
<form
|
||||
className="flex flex-col gap-4 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
>
|
||||
<div className="flex w-full flex-row items-center justify-between">
|
||||
<div>
|
||||
<div className="flex items-start gap-1">
|
||||
<p className="text-xl font-semibold text-mineshaft-100">Overrides</p>
|
||||
<a
|
||||
href="https://infisical.com/docs/self-hosting/configuration/envars#environment-variable-overrides"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<div className="ml-1 mt-[0.32rem] inline-block rounded-md bg-yellow/20 px-1.5 text-sm text-yellow opacity-80 hover:opacity-100">
|
||||
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5" />
|
||||
<span>Docs</span>
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowUpRightFromSquare}
|
||||
className="mb-[0.07rem] ml-1.5 text-[10px]"
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<p className="text-sm text-bunker-300">
|
||||
Override specific environment variables. After saving, it may take up to 5 minutes for
|
||||
variables to propagate throughout every container.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row gap-2">
|
||||
<Button
|
||||
type="submit"
|
||||
variant="outline_bg"
|
||||
isLoading={isSubmitting}
|
||||
isDisabled={isSubmitting || !isDirty}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Input
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
|
||||
placeholder="Search for keys, descriptions, and values..."
|
||||
className="flex-1"
|
||||
/>
|
||||
<div className="flex flex-col">
|
||||
{filteredData.map((group) => (
|
||||
<GroupContainer group={group!} control={control} search={search} />
|
||||
))}
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
@ -1 +0,0 @@
|
||||
export { EnvironmentPageForm } from "./EnvironmentPageForm";
|
@ -1,25 +0,0 @@
|
||||
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||
|
||||
import { EnvironmentPage } from "./EnvironmentPage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_authenticate/_inject-org-details/admin/_admin-layout/environment"
|
||||
)({
|
||||
component: EnvironmentPage,
|
||||
beforeLoad: () => {
|
||||
return {
|
||||
breadcrumbs: [
|
||||
{
|
||||
label: "Admin",
|
||||
link: linkOptions({ to: "/admin" })
|
||||
},
|
||||
{
|
||||
label: "Environment",
|
||||
link: linkOptions({
|
||||
to: "/admin/environment"
|
||||
})
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
});
|
@ -1,33 +1,19 @@
|
||||
import { Helmet } from "react-helmet";
|
||||
import { Link } from "@tanstack/react-router";
|
||||
|
||||
export const EmailNotVerifiedPage = () => {
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col justify-center bg-gradient-to-tr from-mineshaft-600 via-mineshaft-800 to-bunker-700 px-6 pb-28">
|
||||
<div className="flex flex-col justify-between bg-bunker-800 md:h-screen">
|
||||
<Helmet>
|
||||
<title>Request a New Invite</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Helmet>
|
||||
<Link to="/">
|
||||
<div className="mb-4 mt-20 flex justify-center">
|
||||
<img src="/images/gradientLogo.svg" className="h-[90px] w-[120px]" alt="Infisical Logo" />
|
||||
</div>
|
||||
</Link>
|
||||
<div className="mx-auto flex w-full flex-col items-center justify-center">
|
||||
<h1 className="mb-2 bg-gradient-to-b from-white to-bunker-200 bg-clip-text text-center text-xl font-medium text-transparent">
|
||||
Your email was not verified
|
||||
</h1>
|
||||
<p className="w-max justify-center text-center text-sm text-gray-400">
|
||||
Please try again. <br /> Note: If it still doesn't work, please reach out to us at
|
||||
support@infisical.com
|
||||
<div className="flex h-screen w-screen flex-col items-center justify-center text-gray-200">
|
||||
<p className="text-6xl">Oops.</p>
|
||||
<p className="mb-1 mt-2 text-xl">Your email was not verified. </p>
|
||||
<p className="text-xl">Please try again.</p>
|
||||
<p className="text-md mt-8 max-w-sm text-center text-gray-600">
|
||||
Note: If it still doesn't work, please reach out to us at support@infisical.com
|
||||
</p>
|
||||
<div className="mt-6 flex flex-row text-sm text-bunker-400">
|
||||
<Link to="/login">
|
||||
<span className="cursor-pointer duration-200 hover:text-bunker-200 hover:underline hover:decoration-primary-700 hover:underline-offset-4">
|
||||
Back to Login
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { Link, useNavigate } from "@tanstack/react-router";
|
||||
import { useNavigate } from "@tanstack/react-router";
|
||||
import { z } from "zod";
|
||||
|
||||
import { UserEncryptionVersion } from "@app/hooks/api/auth/types";
|
||||
@ -36,12 +36,7 @@ export const PasswordResetPage = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col justify-center bg-gradient-to-tr from-mineshaft-600 via-mineshaft-800 to-bunker-700 px-6 pb-28">
|
||||
<Link to="/">
|
||||
<div className="mb-4 mt-20 flex justify-center">
|
||||
<img src="/images/gradientLogo.svg" className="h-[90px] w-[120px]" alt="Infisical Logo" />
|
||||
</div>
|
||||
</Link>
|
||||
<div className="flex h-screen w-full flex-col items-center justify-center bg-bunker-800">
|
||||
{step === Steps.ConfirmEmail && (
|
||||
<ConfirmEmailStep
|
||||
onComplete={(verifyToken, userEncryptionVersion) => {
|
||||
|
@ -19,21 +19,17 @@ export const ConfirmEmailStep = ({ onComplete }: Props) => {
|
||||
isPending: isVerifyPasswordResetLoading
|
||||
} = useVerifyPasswordResetCode();
|
||||
return (
|
||||
<div className="mx-auto flex w-full flex-col items-center justify-center">
|
||||
<h1 className="mb-2 bg-gradient-to-b from-white to-bunker-200 bg-clip-text text-center text-xl font-medium text-transparent">
|
||||
<div className="mx-1 my-32 flex w-full max-w-xs flex-col items-center rounded-xl bg-bunker px-4 py-6 drop-shadow-xl md:max-w-lg md:px-6">
|
||||
<p className="mb-8 flex justify-center bg-gradient-to-br from-sky-400 to-primary bg-clip-text text-center text-4xl font-semibold text-transparent">
|
||||
Confirm your email
|
||||
</h1>
|
||||
<p className="mb-8 w-max justify-center text-center text-sm text-gray-400">
|
||||
Reset password for <span className="italic">{email}</span>.
|
||||
</p>
|
||||
<div className="w-1/4 min-w-[21.2rem] rounded-md text-center md:min-w-[20.1rem] lg:w-1/6">
|
||||
<img
|
||||
src="/images/envelope.svg"
|
||||
style={{ height: "262px", width: "410px" }}
|
||||
alt="verify email"
|
||||
/>
|
||||
<div className="mx-auto mb-2 mt-4 flex max-h-24 max-w-md flex-col items-center justify-center px-4 text-lg md:p-2">
|
||||
<Button
|
||||
type="submit"
|
||||
size="sm"
|
||||
isFullWidth
|
||||
className="h-10"
|
||||
colorSchema="primary"
|
||||
variant="solid"
|
||||
onClick={async () => {
|
||||
try {
|
||||
const response = await verifyPasswordResetCodeMutateAsync({
|
||||
@ -48,6 +44,7 @@ export const ConfirmEmailStep = ({ onComplete }: Props) => {
|
||||
}
|
||||
}}
|
||||
isLoading={isVerifyPasswordResetLoading}
|
||||
size="lg"
|
||||
>
|
||||
Confirm Email
|
||||
</Button>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import crypto from "crypto";
|
||||
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { faCheck, faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||
import { faCheck, faX } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useSearch } from "@tanstack/react-router";
|
||||
@ -169,15 +169,17 @@ export const EnterPasswordStep = ({
|
||||
return (
|
||||
<form
|
||||
onSubmit={handleSubmit(resetPasswordHandler)}
|
||||
className="mx-auto flex w-full flex-col items-center justify-center"
|
||||
className="mx-1 my-32 flex w-full max-w-xs flex-col items-center rounded-xl bg-bunker px-4 pb-3 pt-6 drop-shadow-xl md:max-w-lg md:px-6"
|
||||
>
|
||||
<h1 className="mb-2 bg-gradient-to-b from-white to-bunker-200 bg-clip-text text-center text-xl font-medium text-transparent">
|
||||
<p className="mx-auto flex w-max justify-center text-2xl font-semibold text-bunker-100 md:text-3xl">
|
||||
Enter new password
|
||||
</h1>
|
||||
<p className="w-max justify-center text-center text-sm text-gray-400">
|
||||
Make sure you save it somewhere safe.
|
||||
</p>
|
||||
<div className="mt-8 w-1/4 min-w-[21.2rem] rounded-md text-center md:min-w-[20.1rem] lg:w-1/6">
|
||||
<div className="mt-1 flex flex-row items-center justify-center md:mx-2 md:pb-4">
|
||||
<p className="flex w-max max-w-md justify-center text-sm text-gray-400">
|
||||
Make sure you save it somewhere safe.
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-4 flex max-h-24 w-full items-center justify-center rounded-lg md:mt-0 md:max-h-28 md:p-2">
|
||||
<Controller
|
||||
control={control}
|
||||
name="password"
|
||||
@ -200,20 +202,6 @@ export const EnterPasswordStep = ({
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-1/4 min-w-[21.2rem] rounded-md text-center md:min-w-[20.1rem] lg:w-1/6">
|
||||
<Button
|
||||
type="submit"
|
||||
size="sm"
|
||||
isFullWidth
|
||||
className="h-10"
|
||||
colorSchema="primary"
|
||||
variant="solid"
|
||||
isLoading={isSubmitting || isLoading || isLoadingV2}
|
||||
isDisabled={isSubmitting || isLoading || isLoadingV2}
|
||||
>
|
||||
Change Password
|
||||
</Button>
|
||||
</div>
|
||||
{passwordErrorTooShort ||
|
||||
passwordErrorTooLong ||
|
||||
passwordErrorNoLetterChar ||
|
||||
@ -222,33 +210,33 @@ export const EnterPasswordStep = ({
|
||||
passwordErrorEscapeChar ||
|
||||
passwordErrorLowEntropy ||
|
||||
passwordErrorBreached ? (
|
||||
<div className="mt-4 rounded border border-mineshaft-600 bg-mineshaft-800 p-4 drop-shadow">
|
||||
<div className="mb-1 ml-2 text-sm text-gray-300">Password should contain:</div>
|
||||
<div className="ml-2 flex flex-row items-center justify-start">
|
||||
<div className="mx-2 mb-2 mt-3 flex w-full max-w-md flex-col items-start rounded-md bg-white/5 px-2 py-2">
|
||||
<div className="mb-1 text-sm text-gray-400">Password should contain:</div>
|
||||
<div className="ml-1 flex flex-row items-center justify-start">
|
||||
{passwordErrorTooShort ? (
|
||||
<FontAwesomeIcon icon={faXmark} className="mr-2.5 text-lg text-red" />
|
||||
<FontAwesomeIcon icon={faX} className="text-md mr-2.5 text-red" />
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faCheck} className="text-md mr-2 text-green" />
|
||||
<FontAwesomeIcon icon={faCheck} className="text-md mr-2 text-primary" />
|
||||
)}
|
||||
<div className={`${passwordErrorTooShort ? "text-gray-400" : "text-gray-600"} text-sm`}>
|
||||
at least 14 characters
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-2 flex flex-row items-center justify-start">
|
||||
<div className="ml-1 flex flex-row items-center justify-start">
|
||||
{passwordErrorTooLong ? (
|
||||
<FontAwesomeIcon icon={faXmark} className="mr-2.5 text-lg text-red" />
|
||||
<FontAwesomeIcon icon={faX} className="text-md mr-2.5 text-red" />
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faCheck} className="text-md mr-2 text-green" />
|
||||
<FontAwesomeIcon icon={faCheck} className="text-md mr-2 text-primary" />
|
||||
)}
|
||||
<div className={`${passwordErrorTooLong ? "text-gray-400" : "text-gray-600"} text-sm`}>
|
||||
at most 100 characters
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-2 flex flex-row items-center justify-start">
|
||||
<div className="ml-1 flex flex-row items-center justify-start">
|
||||
{passwordErrorNoLetterChar ? (
|
||||
<FontAwesomeIcon icon={faXmark} className="mr-2.5 text-lg text-red" />
|
||||
<FontAwesomeIcon icon={faX} className="text-md mr-2.5 text-red" />
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faCheck} className="text-md mr-2 text-green" />
|
||||
<FontAwesomeIcon icon={faCheck} className="text-md mr-2 text-primary" />
|
||||
)}
|
||||
<div
|
||||
className={`${passwordErrorNoLetterChar ? "text-gray-400" : "text-gray-600"} text-sm`}
|
||||
@ -256,11 +244,11 @@ export const EnterPasswordStep = ({
|
||||
at least 1 letter character
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-2 flex flex-row items-center justify-start">
|
||||
<div className="ml-1 flex flex-row items-center justify-start">
|
||||
{passwordErrorNoNumOrSpecialChar ? (
|
||||
<FontAwesomeIcon icon={faXmark} className="mr-2.5 text-lg text-red" />
|
||||
<FontAwesomeIcon icon={faX} className="text-md mr-2.5 text-red" />
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faCheck} className="text-md mr-2 text-green" />
|
||||
<FontAwesomeIcon icon={faCheck} className="text-md mr-2 text-primary" />
|
||||
)}
|
||||
<div
|
||||
className={`${
|
||||
@ -270,11 +258,11 @@ export const EnterPasswordStep = ({
|
||||
at least 1 number or special character
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-2 flex flex-row items-center justify-start">
|
||||
<div className="ml-1 flex flex-row items-center justify-start">
|
||||
{passwordErrorRepeatedChar ? (
|
||||
<FontAwesomeIcon icon={faXmark} className="mr-2.5 text-lg text-red" />
|
||||
<FontAwesomeIcon icon={faX} className="text-md mr-2.5 text-red" />
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faCheck} className="text-md mr-2 text-green" />
|
||||
<FontAwesomeIcon icon={faCheck} className="text-md mr-2 text-primary" />
|
||||
)}
|
||||
<div
|
||||
className={`${passwordErrorRepeatedChar ? "text-gray-400" : "text-gray-600"} text-sm`}
|
||||
@ -282,11 +270,11 @@ export const EnterPasswordStep = ({
|
||||
at most 3 repeated, consecutive characters
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-2 flex flex-row items-center justify-start">
|
||||
<div className="ml-1 flex flex-row items-center justify-start">
|
||||
{passwordErrorEscapeChar ? (
|
||||
<FontAwesomeIcon icon={faXmark} className="mr-2.5 text-lg text-red" />
|
||||
<FontAwesomeIcon icon={faX} className="text-md mr-2.5 text-red" />
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faCheck} className="text-md mr-2 text-green" />
|
||||
<FontAwesomeIcon icon={faCheck} className="text-md mr-2 text-primary" />
|
||||
)}
|
||||
<div
|
||||
className={`${passwordErrorEscapeChar ? "text-gray-400" : "text-gray-600"} text-sm`}
|
||||
@ -294,11 +282,11 @@ export const EnterPasswordStep = ({
|
||||
No escape characters allowed.
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-2 flex flex-row items-center justify-start">
|
||||
<div className="ml-1 flex flex-row items-center justify-start">
|
||||
{passwordErrorLowEntropy ? (
|
||||
<FontAwesomeIcon icon={faXmark} className="mr-2.5 text-lg text-red" />
|
||||
<FontAwesomeIcon icon={faX} className="text-md mr-2.5 text-red" />
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faCheck} className="text-md mr-2 text-green" />
|
||||
<FontAwesomeIcon icon={faCheck} className="text-md mr-2 text-primary" />
|
||||
)}
|
||||
<div
|
||||
className={`${passwordErrorLowEntropy ? "text-gray-400" : "text-gray-600"} text-sm`}
|
||||
@ -306,18 +294,32 @@ export const EnterPasswordStep = ({
|
||||
Password contains personal info.
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-2 flex flex-row items-center justify-start">
|
||||
<div className="ml-1 flex flex-row items-center justify-start">
|
||||
{passwordErrorBreached ? (
|
||||
<FontAwesomeIcon icon={faXmark} className="mr-2.5 text-lg text-red" />
|
||||
<FontAwesomeIcon icon={faX} className="text-md mr-2.5 text-red" />
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faCheck} className="text-md mr-2 text-green" />
|
||||
<FontAwesomeIcon icon={faCheck} className="text-md mr-2 text-primary" />
|
||||
)}
|
||||
<div className={`${passwordErrorBreached ? "text-gray-400" : "text-gray-600"} text-sm`}>
|
||||
Password was found in a data breach.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
) : (
|
||||
<div className="py-2" />
|
||||
)}
|
||||
<div className="mx-auto mt-4 flex max-h-20 w-full max-w-md flex-col items-center justify-center text-sm md:p-2">
|
||||
<div className="text-l m-8 mt-6 px-8 py-3 text-lg">
|
||||
<Button
|
||||
type="submit"
|
||||
colorSchema="secondary"
|
||||
isLoading={isSubmitting || isLoading || isLoadingV2}
|
||||
isDisabled={isSubmitting || isLoading || isLoadingV2}
|
||||
>
|
||||
Change Password
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
@ -43,15 +43,18 @@ export const InputBackupKeyStep = ({ verificationToken, onComplete }: Props) =>
|
||||
return (
|
||||
<form
|
||||
onSubmit={handleSubmit(getEncryptedKeyHandler)}
|
||||
className="mx-auto flex w-full flex-col items-center justify-center"
|
||||
className="mx-1 my-32 flex w-full max-w-xs flex-col items-center rounded-xl bg-bunker px-4 pb-3 pt-6 drop-shadow-xl md:max-w-lg md:px-6"
|
||||
>
|
||||
<h1 className="mb-2 bg-gradient-to-b from-white to-bunker-200 bg-clip-text text-center text-xl font-medium text-transparent">
|
||||
<p className="mx-auto mb-4 flex w-max justify-center text-2xl font-semibold text-bunker-100">
|
||||
Enter your backup key
|
||||
</h1>
|
||||
<p className="w-max justify-center text-center text-sm text-gray-400">
|
||||
You can find it in your emergency kit you downloaded during signup.
|
||||
</p>
|
||||
<div className="mt-8 w-1/4 min-w-[21.2rem] rounded-md text-center md:min-w-[20.1rem] lg:w-1/6">
|
||||
<div className="mt-4 flex flex-row items-center justify-center md:mx-2 md:pb-4">
|
||||
<p className="flex w-full px-4 text-center text-sm text-gray-400 sm:max-w-md">
|
||||
You can find it in your emergency kit. You had to download the emergency kit during
|
||||
signup.
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-4 flex max-h-24 w-full items-center justify-center rounded-lg md:mt-0 md:max-h-28 md:p-2">
|
||||
<Controller
|
||||
control={control}
|
||||
name="backupKey"
|
||||
@ -72,17 +75,12 @@ export const InputBackupKeyStep = ({ verificationToken, onComplete }: Props) =>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-1/4 min-w-[21.2rem] rounded-md text-center md:min-w-[20.1rem] lg:w-1/6">
|
||||
<Button
|
||||
type="submit"
|
||||
size="sm"
|
||||
isFullWidth
|
||||
className="h-10"
|
||||
colorSchema="primary"
|
||||
variant="solid"
|
||||
>
|
||||
Submit Backup Key
|
||||
</Button>
|
||||
<div className="mx-auto mt-4 flex max-h-20 w-full max-w-md flex-col items-center justify-center text-sm md:p-2">
|
||||
<div className="text-l m-8 mt-6 px-8 py-3 text-lg">
|
||||
<Button type="submit" colorSchema="secondary">
|
||||
Submit Backup Key
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
|
@ -2,7 +2,8 @@ import { FormEvent, useState } from "react";
|
||||
import { Helmet } from "react-helmet";
|
||||
import { Link } from "@tanstack/react-router";
|
||||
|
||||
import { Button, EmailServiceSetupModal, Input } from "@app/components/v2";
|
||||
import InputField from "@app/components/basic/InputField";
|
||||
import { Button, EmailServiceSetupModal } from "@app/components/v2";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { useSendPasswordResetEmail } from "@app/hooks/api";
|
||||
import { useFetchServerStatus } from "@app/hooks/api/serverDetails";
|
||||
@ -43,9 +44,9 @@ export const VerifyEmailPage = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col justify-center bg-gradient-to-tr from-mineshaft-600 via-mineshaft-800 to-bunker-700 px-6 pb-28">
|
||||
<div className="flex h-screen flex-col justify-start bg-bunker-800 px-6">
|
||||
<Helmet>
|
||||
<title>Reset Password</title>
|
||||
<title>Login</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
<meta property="og:image" content="/images/message.png" />
|
||||
<meta property="og:title" content="Verify your email in Infisical" />
|
||||
@ -55,80 +56,66 @@ export const VerifyEmailPage = () => {
|
||||
/>
|
||||
</Helmet>
|
||||
<Link to="/">
|
||||
<div className="mb-4 mt-20 flex justify-center">
|
||||
<div className="mb-8 mt-20 flex cursor-pointer justify-center">
|
||||
<img
|
||||
src="/images/gradientLogo.svg"
|
||||
src="/images/biglogo.png"
|
||||
style={{
|
||||
height: "90px",
|
||||
width: "120px"
|
||||
}}
|
||||
alt="Infisical Logo"
|
||||
alt="long logo"
|
||||
/>
|
||||
</div>
|
||||
</Link>
|
||||
{step === 1 && (
|
||||
<form
|
||||
onSubmit={onSubmit}
|
||||
className="mx-auto flex w-full flex-col items-center justify-center"
|
||||
className="h-7/12 mx-auto w-full max-w-md rounded-xl bg-bunker px-6 py-4 pt-8 drop-shadow-xl"
|
||||
>
|
||||
<h1 className="mb-2 bg-gradient-to-b from-white to-bunker-200 bg-clip-text text-center text-xl font-medium text-transparent">
|
||||
<p className="mx-auto mb-6 flex w-max justify-center text-2xl font-semibold text-bunker-100 md:text-3xl">
|
||||
Forgot your password?
|
||||
</h1>
|
||||
<p className="w-max justify-center text-center text-sm text-gray-400">
|
||||
Enter your email to start the password reset process. <br /> You will receive an email
|
||||
with instructions.
|
||||
</p>
|
||||
<div className="mt-8 w-1/4 min-w-[21.2rem] rounded-md text-center md:min-w-[20.1rem] lg:w-1/6">
|
||||
<Input
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
<div className="mt-4 flex flex-row items-center justify-center md:mx-2 md:pb-4">
|
||||
<p className="flex w-max justify-center text-center text-sm text-gray-400">
|
||||
Enter your email to start the password reset process. You will receive an email with
|
||||
instructions.
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-4 flex max-h-24 w-full items-center justify-center rounded-lg md:mt-0 md:max-h-28 md:p-2">
|
||||
<InputField
|
||||
label="Email"
|
||||
onChangeHandler={setEmail}
|
||||
type="email"
|
||||
placeholder="Enter your email..."
|
||||
value={email}
|
||||
placeholder=""
|
||||
isRequired
|
||||
autoComplete="username"
|
||||
className="h-10"
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-4 w-1/4 min-w-[21.2rem] rounded-md text-center md:min-w-[20.1rem] lg:w-1/6">
|
||||
<Button
|
||||
type="submit"
|
||||
size="sm"
|
||||
isFullWidth
|
||||
className="h-10"
|
||||
colorSchema="primary"
|
||||
variant="solid"
|
||||
isLoading={loading}
|
||||
>
|
||||
Continue
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mt-6 flex flex-row text-sm text-bunker-400">
|
||||
<Link to="/login">
|
||||
<span className="cursor-pointer duration-200 hover:text-bunker-200 hover:underline hover:decoration-primary-700 hover:underline-offset-4">
|
||||
Back to Login
|
||||
</span>
|
||||
</Link>
|
||||
<div className="mx-auto mt-4 flex max-h-20 w-full max-w-md flex-col items-center justify-center text-sm md:p-2">
|
||||
<div className="text-l m-8 mt-6 px-8 py-3 text-lg">
|
||||
<Button type="submit" size="lg" onClick={() => {}} isLoading={loading}>
|
||||
Continue
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
{step === 2 && (
|
||||
<div className="mx-auto flex w-full flex-col items-center justify-center">
|
||||
<h1 className="mb-2 bg-gradient-to-b from-white to-bunker-200 bg-clip-text text-center text-xl font-medium text-transparent">
|
||||
Look for an email in your inbox
|
||||
</h1>
|
||||
<p className="w-max max-w-lg justify-center text-center text-sm text-gray-400">
|
||||
If the email is in our system, you will receive an email at{" "}
|
||||
<span className="italic">{email}</span> with instructions on how to reset your password.
|
||||
<div className="h-7/12 mx-auto w-full max-w-md rounded-xl bg-bunker px-6 py-4 pt-8 drop-shadow-xl">
|
||||
<p className="mx-auto mb-6 flex w-max justify-center text-xl font-semibold text-bunker-100 md:text-2xl">
|
||||
Look for an email in your inbox.
|
||||
</p>
|
||||
<div className="mt-6 flex flex-row text-sm text-bunker-400">
|
||||
<Link to="/login">
|
||||
<span className="cursor-pointer duration-200 hover:text-bunker-200 hover:underline hover:decoration-primary-700 hover:underline-offset-4">
|
||||
Back to Login
|
||||
</span>
|
||||
</Link>
|
||||
<div className="mt-4 flex flex-row items-center justify-center md:mx-2 md:pb-4">
|
||||
<p className="w-max text-center text-sm text-gray-400">
|
||||
If the email is in our system, you will receive an email at{" "}
|
||||
<span className="italic">{email}</span> with instructions on how to reset your
|
||||
password.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<EmailServiceSetupModal
|
||||
isOpen={popUp.setUpEmail?.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("setUpEmail", isOpen)}
|
||||
|
@ -55,7 +55,7 @@ const formSchema = z
|
||||
.object({
|
||||
environment: z.object({ slug: z.string(), name: z.string() }),
|
||||
name: z.string().optional(),
|
||||
secretPath: z.string().optional(),
|
||||
secretPath: z.string().trim().min(1),
|
||||
approvals: z.number().min(1).default(1),
|
||||
userApprovers: z
|
||||
.object({ type: z.literal(ApproverType.User), id: z.string() })
|
||||
@ -93,20 +93,19 @@ const formSchema = z
|
||||
.optional()
|
||||
})
|
||||
.superRefine((data, ctx) => {
|
||||
if (
|
||||
data.policyType === PolicyType.ChangePolicy &&
|
||||
!(data.groupApprovers.length || data.userApprovers.length)
|
||||
) {
|
||||
ctx.addIssue({
|
||||
path: ["userApprovers"],
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "At least one approver should be provided"
|
||||
});
|
||||
ctx.addIssue({
|
||||
path: ["groupApprovers"],
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "At least one approver should be provided"
|
||||
});
|
||||
if (data.policyType === PolicyType.ChangePolicy) {
|
||||
if (!(data.groupApprovers.length || data.userApprovers.length)) {
|
||||
ctx.addIssue({
|
||||
path: ["userApprovers"],
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "At least one approver should be provided"
|
||||
});
|
||||
ctx.addIssue({
|
||||
path: ["groupApprovers"],
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "At least one approver should be provided"
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -127,6 +126,7 @@ const Form = ({
|
||||
control,
|
||||
handleSubmit,
|
||||
watch,
|
||||
resetField,
|
||||
formState: { isSubmitting }
|
||||
} = useForm<TFormSchema>({
|
||||
resolver: zodResolver(formSchema),
|
||||
@ -177,6 +177,7 @@ const Form = ({
|
||||
: undefined,
|
||||
defaultValues: !editValues
|
||||
? {
|
||||
secretPath: "/",
|
||||
sequenceApprovers: [{ approvals: 1 }]
|
||||
}
|
||||
: undefined
|
||||
@ -405,7 +406,10 @@ const Form = ({
|
||||
<Select
|
||||
isDisabled={isEditMode}
|
||||
value={value}
|
||||
onValueChange={(val) => onChange(val as PolicyType)}
|
||||
onValueChange={(val) => {
|
||||
onChange(val as PolicyType);
|
||||
resetField("secretPath");
|
||||
}}
|
||||
className="w-full border border-mineshaft-500"
|
||||
>
|
||||
{Object.values(PolicyType).map((policyType) => {
|
||||
@ -465,6 +469,7 @@ const Form = ({
|
||||
<FormControl
|
||||
tooltipText="Secret paths support glob patterns. For example, '/**' will match all paths."
|
||||
label="Secret Path"
|
||||
isRequired
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
className="flex-1"
|
||||
|
@ -43,7 +43,6 @@ import { Route as authProviderSuccessPageRouteImport } from './pages/auth/Provid
|
||||
import { Route as authProviderErrorPageRouteImport } from './pages/auth/ProviderErrorPage/route'
|
||||
import { Route as userPersonalSettingsPageRouteImport } from './pages/user/PersonalSettingsPage/route'
|
||||
import { Route as adminIntegrationsPageRouteImport } from './pages/admin/IntegrationsPage/route'
|
||||
import { Route as adminEnvironmentPageRouteImport } from './pages/admin/EnvironmentPage/route'
|
||||
import { Route as adminEncryptionPageRouteImport } from './pages/admin/EncryptionPage/route'
|
||||
import { Route as adminCachingPageRouteImport } from './pages/admin/CachingPage/route'
|
||||
import { Route as adminAuthenticationPageRouteImport } from './pages/admin/AuthenticationPage/route'
|
||||
@ -566,12 +565,6 @@ const adminIntegrationsPageRouteRoute = adminIntegrationsPageRouteImport.update(
|
||||
} as any,
|
||||
)
|
||||
|
||||
const adminEnvironmentPageRouteRoute = adminEnvironmentPageRouteImport.update({
|
||||
id: '/environment',
|
||||
path: '/environment',
|
||||
getParentRoute: () => adminLayoutRoute,
|
||||
} as any)
|
||||
|
||||
const adminEncryptionPageRouteRoute = adminEncryptionPageRouteImport.update({
|
||||
id: '/encryption',
|
||||
path: '/encryption',
|
||||
@ -2187,13 +2180,6 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof adminEncryptionPageRouteImport
|
||||
parentRoute: typeof adminLayoutImport
|
||||
}
|
||||
'/_authenticate/_inject-org-details/admin/_admin-layout/environment': {
|
||||
id: '/_authenticate/_inject-org-details/admin/_admin-layout/environment'
|
||||
path: '/environment'
|
||||
fullPath: '/admin/environment'
|
||||
preLoaderRoute: typeof adminEnvironmentPageRouteImport
|
||||
parentRoute: typeof adminLayoutImport
|
||||
}
|
||||
'/_authenticate/_inject-org-details/admin/_admin-layout/integrations': {
|
||||
id: '/_authenticate/_inject-org-details/admin/_admin-layout/integrations'
|
||||
path: '/integrations'
|
||||
@ -4134,7 +4120,6 @@ interface adminLayoutRouteChildren {
|
||||
adminAuthenticationPageRouteRoute: typeof adminAuthenticationPageRouteRoute
|
||||
adminCachingPageRouteRoute: typeof adminCachingPageRouteRoute
|
||||
adminEncryptionPageRouteRoute: typeof adminEncryptionPageRouteRoute
|
||||
adminEnvironmentPageRouteRoute: typeof adminEnvironmentPageRouteRoute
|
||||
adminIntegrationsPageRouteRoute: typeof adminIntegrationsPageRouteRoute
|
||||
adminMachineIdentitiesResourcesPageRouteRoute: typeof adminMachineIdentitiesResourcesPageRouteRoute
|
||||
adminOrganizationResourcesPageRouteRoute: typeof adminOrganizationResourcesPageRouteRoute
|
||||
@ -4146,7 +4131,6 @@ const adminLayoutRouteChildren: adminLayoutRouteChildren = {
|
||||
adminAuthenticationPageRouteRoute: adminAuthenticationPageRouteRoute,
|
||||
adminCachingPageRouteRoute: adminCachingPageRouteRoute,
|
||||
adminEncryptionPageRouteRoute: adminEncryptionPageRouteRoute,
|
||||
adminEnvironmentPageRouteRoute: adminEnvironmentPageRouteRoute,
|
||||
adminIntegrationsPageRouteRoute: adminIntegrationsPageRouteRoute,
|
||||
adminMachineIdentitiesResourcesPageRouteRoute:
|
||||
adminMachineIdentitiesResourcesPageRouteRoute,
|
||||
@ -4349,7 +4333,6 @@ export interface FileRoutesByFullPath {
|
||||
'/admin/authentication': typeof adminAuthenticationPageRouteRoute
|
||||
'/admin/caching': typeof adminCachingPageRouteRoute
|
||||
'/admin/encryption': typeof adminEncryptionPageRouteRoute
|
||||
'/admin/environment': typeof adminEnvironmentPageRouteRoute
|
||||
'/admin/integrations': typeof adminIntegrationsPageRouteRoute
|
||||
'/organization/app-connections': typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRouteWithChildren
|
||||
'/organization/gateways': typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationGatewaysRouteWithChildren
|
||||
@ -4546,7 +4529,6 @@ export interface FileRoutesByTo {
|
||||
'/admin/authentication': typeof adminAuthenticationPageRouteRoute
|
||||
'/admin/caching': typeof adminCachingPageRouteRoute
|
||||
'/admin/encryption': typeof adminEncryptionPageRouteRoute
|
||||
'/admin/environment': typeof adminEnvironmentPageRouteRoute
|
||||
'/admin/integrations': typeof adminIntegrationsPageRouteRoute
|
||||
'/projects/$projectId': typeof projectLayoutGeneralRouteWithChildren
|
||||
'/secret-manager/$projectId': typeof AuthenticateInjectOrgDetailsOrgLayoutSecretManagerProjectIdRouteWithChildren
|
||||
@ -4743,7 +4725,6 @@ export interface FileRoutesById {
|
||||
'/_authenticate/_inject-org-details/admin/_admin-layout/authentication': typeof adminAuthenticationPageRouteRoute
|
||||
'/_authenticate/_inject-org-details/admin/_admin-layout/caching': typeof adminCachingPageRouteRoute
|
||||
'/_authenticate/_inject-org-details/admin/_admin-layout/encryption': typeof adminEncryptionPageRouteRoute
|
||||
'/_authenticate/_inject-org-details/admin/_admin-layout/environment': typeof adminEnvironmentPageRouteRoute
|
||||
'/_authenticate/_inject-org-details/admin/_admin-layout/integrations': typeof adminIntegrationsPageRouteRoute
|
||||
'/_authenticate/_inject-org-details/_org-layout/organization/app-connections': typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRouteWithChildren
|
||||
'/_authenticate/_inject-org-details/_org-layout/organization/gateways': typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationGatewaysRouteWithChildren
|
||||
@ -4953,7 +4934,6 @@ export interface FileRouteTypes {
|
||||
| '/admin/authentication'
|
||||
| '/admin/caching'
|
||||
| '/admin/encryption'
|
||||
| '/admin/environment'
|
||||
| '/admin/integrations'
|
||||
| '/organization/app-connections'
|
||||
| '/organization/gateways'
|
||||
@ -5149,7 +5129,6 @@ export interface FileRouteTypes {
|
||||
| '/admin/authentication'
|
||||
| '/admin/caching'
|
||||
| '/admin/encryption'
|
||||
| '/admin/environment'
|
||||
| '/admin/integrations'
|
||||
| '/projects/$projectId'
|
||||
| '/secret-manager/$projectId'
|
||||
@ -5344,7 +5323,6 @@ export interface FileRouteTypes {
|
||||
| '/_authenticate/_inject-org-details/admin/_admin-layout/authentication'
|
||||
| '/_authenticate/_inject-org-details/admin/_admin-layout/caching'
|
||||
| '/_authenticate/_inject-org-details/admin/_admin-layout/encryption'
|
||||
| '/_authenticate/_inject-org-details/admin/_admin-layout/environment'
|
||||
| '/_authenticate/_inject-org-details/admin/_admin-layout/integrations'
|
||||
| '/_authenticate/_inject-org-details/_org-layout/organization/app-connections'
|
||||
| '/_authenticate/_inject-org-details/_org-layout/organization/gateways'
|
||||
@ -5751,7 +5729,6 @@ export const routeTree = rootRoute
|
||||
"/_authenticate/_inject-org-details/admin/_admin-layout/authentication",
|
||||
"/_authenticate/_inject-org-details/admin/_admin-layout/caching",
|
||||
"/_authenticate/_inject-org-details/admin/_admin-layout/encryption",
|
||||
"/_authenticate/_inject-org-details/admin/_admin-layout/environment",
|
||||
"/_authenticate/_inject-org-details/admin/_admin-layout/integrations",
|
||||
"/_authenticate/_inject-org-details/admin/_admin-layout/resources/machine-identities",
|
||||
"/_authenticate/_inject-org-details/admin/_admin-layout/resources/organizations",
|
||||
@ -5798,10 +5775,6 @@ export const routeTree = rootRoute
|
||||
"filePath": "admin/EncryptionPage/route.tsx",
|
||||
"parent": "/_authenticate/_inject-org-details/admin/_admin-layout"
|
||||
},
|
||||
"/_authenticate/_inject-org-details/admin/_admin-layout/environment": {
|
||||
"filePath": "admin/EnvironmentPage/route.tsx",
|
||||
"parent": "/_authenticate/_inject-org-details/admin/_admin-layout"
|
||||
},
|
||||
"/_authenticate/_inject-org-details/admin/_admin-layout/integrations": {
|
||||
"filePath": "admin/IntegrationsPage/route.tsx",
|
||||
"parent": "/_authenticate/_inject-org-details/admin/_admin-layout"
|
||||
|
@ -8,7 +8,6 @@ const adminRoute = route("/admin", [
|
||||
index("admin/GeneralPage/route.tsx"),
|
||||
route("/encryption", "admin/EncryptionPage/route.tsx"),
|
||||
route("/authentication", "admin/AuthenticationPage/route.tsx"),
|
||||
route("/environment", "admin/EnvironmentPage/route.tsx"),
|
||||
route("/integrations", "admin/IntegrationsPage/route.tsx"),
|
||||
route("/caching", "admin/CachingPage/route.tsx"),
|
||||
route("/resources/organizations", "admin/OrganizationResourcesPage/route.tsx"),
|
||||
|
Reference in New Issue
Block a user