mirror of
https://github.com/Infisical/infisical.git
synced 2025-07-10 11:43:04 +00:00
Compare commits
36 Commits
password-r
...
infisical/
Author | SHA1 | Date | |
---|---|---|---|
0191eb48f3 | |||
9d39910152 | |||
9137fa4ca5 | |||
78da7ec343 | |||
a678ebb4ac | |||
83dd38db49 | |||
06f5af1200 | |||
f903e5b3d4 | |||
c6f8915d3f | |||
65b1354ef1 | |||
cda8579ca4 | |||
1b1acdcb0b | |||
a8f08730a1 | |||
9af9050aa2 | |||
cc564119e0 | |||
189b0dd5ee | |||
9cbef2c07b | |||
9a960a85cd | |||
42648a134c | |||
defb66ce65 | |||
a3d06fdf1b | |||
9049c441d6 | |||
51ecc9dfa0 | |||
13c9879fb6 | |||
23b20ebdab | |||
37d490ede3 | |||
7ab67db84d | |||
3a17281e37 | |||
abfe185a5b | |||
f6c10683a5 | |||
abbf541c9f | |||
fcdd121a58 | |||
5bfd92bf8d | |||
45af2c0b49 | |||
13d2cbd8b0 | |||
abfc5736fd |
21
backend/src/db/migrations/20250627010508_env-overrides.ts
Normal file
21
backend/src/db/migrations/20250627010508_env-overrides.ts
Normal file
@ -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.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,21 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const hasColumn = await knex.schema.hasColumn(TableName.OrgMembership, "lastInvitedAt");
|
||||||
|
if (hasColumn) {
|
||||||
|
await knex.schema.alterTable(TableName.OrgMembership, (t) => {
|
||||||
|
t.datetime("lastInvitedAt").nullable().defaultTo(knex.fn.now()).alter();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const hasColumn = await knex.schema.hasColumn(TableName.OrgMembership, "lastInvitedAt");
|
||||||
|
if (hasColumn) {
|
||||||
|
await knex.schema.alterTable(TableName.OrgMembership, (t) => {
|
||||||
|
t.datetime("lastInvitedAt").nullable().alter();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -34,7 +34,8 @@ export const SuperAdminSchema = z.object({
|
|||||||
encryptedGitHubAppConnectionClientSecret: zodBuffer.nullable().optional(),
|
encryptedGitHubAppConnectionClientSecret: zodBuffer.nullable().optional(),
|
||||||
encryptedGitHubAppConnectionSlug: zodBuffer.nullable().optional(),
|
encryptedGitHubAppConnectionSlug: zodBuffer.nullable().optional(),
|
||||||
encryptedGitHubAppConnectionId: zodBuffer.nullable().optional(),
|
encryptedGitHubAppConnectionId: zodBuffer.nullable().optional(),
|
||||||
encryptedGitHubAppConnectionPrivateKey: zodBuffer.nullable().optional()
|
encryptedGitHubAppConnectionPrivateKey: zodBuffer.nullable().optional(),
|
||||||
|
encryptedEnvOverrides: zodBuffer.nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TSuperAdmin = z.infer<typeof SuperAdminSchema>;
|
export type TSuperAdmin = z.infer<typeof SuperAdminSchema>;
|
||||||
|
@ -17,6 +17,7 @@ import { z } from "zod";
|
|||||||
import { LdapGroupMapsSchema } from "@app/db/schemas";
|
import { LdapGroupMapsSchema } from "@app/db/schemas";
|
||||||
import { TLDAPConfig } from "@app/ee/services/ldap-config/ldap-config-types";
|
import { TLDAPConfig } from "@app/ee/services/ldap-config/ldap-config-types";
|
||||||
import { isValidLdapFilter, searchGroups } from "@app/ee/services/ldap-config/ldap-fns";
|
import { isValidLdapFilter, searchGroups } from "@app/ee/services/ldap-config/ldap-fns";
|
||||||
|
import { ApiDocsTags, LdapSso } from "@app/lib/api-docs";
|
||||||
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 { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
@ -132,10 +133,18 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => {
|
|||||||
config: {
|
config: {
|
||||||
rateLimit: readLimit
|
rateLimit: readLimit
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
schema: {
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.LdapSso],
|
||||||
|
description: "Get LDAP config",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
organizationId: z.string().trim()
|
organizationId: z.string().trim().describe(LdapSso.GET_CONFIG.organizationId)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@ -172,23 +181,32 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => {
|
|||||||
config: {
|
config: {
|
||||||
rateLimit: writeLimit
|
rateLimit: writeLimit
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
schema: {
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.LdapSso],
|
||||||
|
description: "Create LDAP config",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
body: z.object({
|
body: z.object({
|
||||||
organizationId: z.string().trim(),
|
organizationId: z.string().trim().describe(LdapSso.CREATE_CONFIG.organizationId),
|
||||||
isActive: z.boolean(),
|
isActive: z.boolean().describe(LdapSso.CREATE_CONFIG.isActive),
|
||||||
url: z.string().trim(),
|
url: z.string().trim().describe(LdapSso.CREATE_CONFIG.url),
|
||||||
bindDN: z.string().trim(),
|
bindDN: z.string().trim().describe(LdapSso.CREATE_CONFIG.bindDN),
|
||||||
bindPass: z.string().trim(),
|
bindPass: z.string().trim().describe(LdapSso.CREATE_CONFIG.bindPass),
|
||||||
uniqueUserAttribute: z.string().trim().default("uidNumber"),
|
uniqueUserAttribute: z.string().trim().default("uidNumber").describe(LdapSso.CREATE_CONFIG.uniqueUserAttribute),
|
||||||
searchBase: z.string().trim(),
|
searchBase: z.string().trim().describe(LdapSso.CREATE_CONFIG.searchBase),
|
||||||
searchFilter: z.string().trim().default("(uid={{username}})"),
|
searchFilter: z.string().trim().default("(uid={{username}})").describe(LdapSso.CREATE_CONFIG.searchFilter),
|
||||||
groupSearchBase: z.string().trim(),
|
groupSearchBase: z.string().trim().describe(LdapSso.CREATE_CONFIG.groupSearchBase),
|
||||||
groupSearchFilter: z
|
groupSearchFilter: z
|
||||||
.string()
|
.string()
|
||||||
.trim()
|
.trim()
|
||||||
.default("(|(memberUid={{.Username}})(member={{.UserDN}})(uniqueMember={{.UserDN}}))"),
|
.default("(|(memberUid={{.Username}})(member={{.UserDN}})(uniqueMember={{.UserDN}}))")
|
||||||
caCert: z.string().trim().default("")
|
.describe(LdapSso.CREATE_CONFIG.groupSearchFilter),
|
||||||
|
caCert: z.string().trim().default("").describe(LdapSso.CREATE_CONFIG.caCert)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: SanitizedLdapConfigSchema
|
200: SanitizedLdapConfigSchema
|
||||||
@ -214,23 +232,31 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => {
|
|||||||
config: {
|
config: {
|
||||||
rateLimit: writeLimit
|
rateLimit: writeLimit
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
schema: {
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.LdapSso],
|
||||||
|
description: "Update LDAP config",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
body: z
|
body: z
|
||||||
.object({
|
.object({
|
||||||
isActive: z.boolean(),
|
isActive: z.boolean().describe(LdapSso.UPDATE_CONFIG.isActive),
|
||||||
url: z.string().trim(),
|
url: z.string().trim().describe(LdapSso.UPDATE_CONFIG.url),
|
||||||
bindDN: z.string().trim(),
|
bindDN: z.string().trim().describe(LdapSso.UPDATE_CONFIG.bindDN),
|
||||||
bindPass: z.string().trim(),
|
bindPass: z.string().trim().describe(LdapSso.UPDATE_CONFIG.bindPass),
|
||||||
uniqueUserAttribute: z.string().trim(),
|
uniqueUserAttribute: z.string().trim().describe(LdapSso.UPDATE_CONFIG.uniqueUserAttribute),
|
||||||
searchBase: z.string().trim(),
|
searchBase: z.string().trim().describe(LdapSso.UPDATE_CONFIG.searchBase),
|
||||||
searchFilter: z.string().trim(),
|
searchFilter: z.string().trim().describe(LdapSso.UPDATE_CONFIG.searchFilter),
|
||||||
groupSearchBase: z.string().trim(),
|
groupSearchBase: z.string().trim().describe(LdapSso.UPDATE_CONFIG.groupSearchBase),
|
||||||
groupSearchFilter: z.string().trim(),
|
groupSearchFilter: z.string().trim().describe(LdapSso.UPDATE_CONFIG.groupSearchFilter),
|
||||||
caCert: z.string().trim()
|
caCert: z.string().trim().describe(LdapSso.UPDATE_CONFIG.caCert)
|
||||||
})
|
})
|
||||||
.partial()
|
.partial()
|
||||||
.merge(z.object({ organizationId: z.string() })),
|
.merge(z.object({ organizationId: z.string().trim().describe(LdapSso.UPDATE_CONFIG.organizationId) })),
|
||||||
response: {
|
response: {
|
||||||
200: SanitizedLdapConfigSchema
|
200: SanitizedLdapConfigSchema
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { OidcConfigsSchema } from "@app/db/schemas";
|
import { OidcConfigsSchema } from "@app/db/schemas";
|
||||||
import { OIDCConfigurationType, OIDCJWTSignatureAlgorithm } from "@app/ee/services/oidc/oidc-config-types";
|
import { OIDCConfigurationType, OIDCJWTSignatureAlgorithm } from "@app/ee/services/oidc/oidc-config-types";
|
||||||
|
import { ApiDocsTags, OidcSSo } from "@app/lib/api-docs";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { authRateLimit, readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { authRateLimit, readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
@ -153,10 +154,18 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => {
|
|||||||
config: {
|
config: {
|
||||||
rateLimit: readLimit
|
rateLimit: readLimit
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
schema: {
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.OidcSso],
|
||||||
|
description: "Get OIDC config",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
orgSlug: z.string().trim()
|
organizationId: z.string().trim().describe(OidcSSo.GET_CONFIG.organizationId)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: SanitizedOidcConfigSchema.pick({
|
200: SanitizedOidcConfigSchema.pick({
|
||||||
@ -180,9 +189,8 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const { orgSlug } = req.query;
|
|
||||||
const oidc = await server.services.oidc.getOidc({
|
const oidc = await server.services.oidc.getOidc({
|
||||||
orgSlug,
|
organizationId: req.query.organizationId,
|
||||||
type: "external",
|
type: "external",
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
@ -200,8 +208,16 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => {
|
|||||||
config: {
|
config: {
|
||||||
rateLimit: writeLimit
|
rateLimit: writeLimit
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
schema: {
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.OidcSso],
|
||||||
|
description: "Update OIDC config",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
body: z
|
body: z
|
||||||
.object({
|
.object({
|
||||||
allowedEmailDomains: z
|
allowedEmailDomains: z
|
||||||
@ -216,22 +232,26 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => {
|
|||||||
.split(",")
|
.split(",")
|
||||||
.map((id) => id.trim())
|
.map((id) => id.trim())
|
||||||
.join(", ");
|
.join(", ");
|
||||||
}),
|
})
|
||||||
discoveryURL: z.string().trim(),
|
.describe(OidcSSo.UPDATE_CONFIG.allowedEmailDomains),
|
||||||
configurationType: z.nativeEnum(OIDCConfigurationType),
|
discoveryURL: z.string().trim().describe(OidcSSo.UPDATE_CONFIG.discoveryURL),
|
||||||
issuer: z.string().trim(),
|
configurationType: z.nativeEnum(OIDCConfigurationType).describe(OidcSSo.UPDATE_CONFIG.configurationType),
|
||||||
authorizationEndpoint: z.string().trim(),
|
issuer: z.string().trim().describe(OidcSSo.UPDATE_CONFIG.issuer),
|
||||||
jwksUri: z.string().trim(),
|
authorizationEndpoint: z.string().trim().describe(OidcSSo.UPDATE_CONFIG.authorizationEndpoint),
|
||||||
tokenEndpoint: z.string().trim(),
|
jwksUri: z.string().trim().describe(OidcSSo.UPDATE_CONFIG.jwksUri),
|
||||||
userinfoEndpoint: z.string().trim(),
|
tokenEndpoint: z.string().trim().describe(OidcSSo.UPDATE_CONFIG.tokenEndpoint),
|
||||||
clientId: z.string().trim(),
|
userinfoEndpoint: z.string().trim().describe(OidcSSo.UPDATE_CONFIG.userinfoEndpoint),
|
||||||
clientSecret: z.string().trim(),
|
clientId: z.string().trim().describe(OidcSSo.UPDATE_CONFIG.clientId),
|
||||||
isActive: z.boolean(),
|
clientSecret: z.string().trim().describe(OidcSSo.UPDATE_CONFIG.clientSecret),
|
||||||
manageGroupMemberships: z.boolean().optional(),
|
isActive: z.boolean().describe(OidcSSo.UPDATE_CONFIG.isActive),
|
||||||
jwtSignatureAlgorithm: z.nativeEnum(OIDCJWTSignatureAlgorithm).optional()
|
manageGroupMemberships: z.boolean().optional().describe(OidcSSo.UPDATE_CONFIG.manageGroupMemberships),
|
||||||
|
jwtSignatureAlgorithm: z
|
||||||
|
.nativeEnum(OIDCJWTSignatureAlgorithm)
|
||||||
|
.optional()
|
||||||
|
.describe(OidcSSo.UPDATE_CONFIG.jwtSignatureAlgorithm)
|
||||||
})
|
})
|
||||||
.partial()
|
.partial()
|
||||||
.merge(z.object({ orgSlug: z.string() })),
|
.merge(z.object({ organizationId: z.string().describe(OidcSSo.UPDATE_CONFIG.organizationId) })),
|
||||||
response: {
|
response: {
|
||||||
200: SanitizedOidcConfigSchema.pick({
|
200: SanitizedOidcConfigSchema.pick({
|
||||||
id: true,
|
id: true,
|
||||||
@ -267,8 +287,16 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => {
|
|||||||
config: {
|
config: {
|
||||||
rateLimit: writeLimit
|
rateLimit: writeLimit
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
schema: {
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.OidcSso],
|
||||||
|
description: "Create OIDC config",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
body: z
|
body: z
|
||||||
.object({
|
.object({
|
||||||
allowedEmailDomains: z
|
allowedEmailDomains: z
|
||||||
@ -283,23 +311,34 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => {
|
|||||||
.split(",")
|
.split(",")
|
||||||
.map((id) => id.trim())
|
.map((id) => id.trim())
|
||||||
.join(", ");
|
.join(", ");
|
||||||
}),
|
})
|
||||||
configurationType: z.nativeEnum(OIDCConfigurationType),
|
.describe(OidcSSo.CREATE_CONFIG.allowedEmailDomains),
|
||||||
issuer: z.string().trim().optional().default(""),
|
configurationType: z.nativeEnum(OIDCConfigurationType).describe(OidcSSo.CREATE_CONFIG.configurationType),
|
||||||
discoveryURL: z.string().trim().optional().default(""),
|
issuer: z.string().trim().optional().default("").describe(OidcSSo.CREATE_CONFIG.issuer),
|
||||||
authorizationEndpoint: z.string().trim().optional().default(""),
|
discoveryURL: z.string().trim().optional().default("").describe(OidcSSo.CREATE_CONFIG.discoveryURL),
|
||||||
jwksUri: z.string().trim().optional().default(""),
|
authorizationEndpoint: z
|
||||||
tokenEndpoint: z.string().trim().optional().default(""),
|
.string()
|
||||||
userinfoEndpoint: z.string().trim().optional().default(""),
|
.trim()
|
||||||
clientId: z.string().trim(),
|
.optional()
|
||||||
clientSecret: z.string().trim(),
|
.default("")
|
||||||
isActive: z.boolean(),
|
.describe(OidcSSo.CREATE_CONFIG.authorizationEndpoint),
|
||||||
orgSlug: z.string().trim(),
|
jwksUri: z.string().trim().optional().default("").describe(OidcSSo.CREATE_CONFIG.jwksUri),
|
||||||
manageGroupMemberships: z.boolean().optional().default(false),
|
tokenEndpoint: z.string().trim().optional().default("").describe(OidcSSo.CREATE_CONFIG.tokenEndpoint),
|
||||||
|
userinfoEndpoint: z.string().trim().optional().default("").describe(OidcSSo.CREATE_CONFIG.userinfoEndpoint),
|
||||||
|
clientId: z.string().trim().describe(OidcSSo.CREATE_CONFIG.clientId),
|
||||||
|
clientSecret: z.string().trim().describe(OidcSSo.CREATE_CONFIG.clientSecret),
|
||||||
|
isActive: z.boolean().describe(OidcSSo.CREATE_CONFIG.isActive),
|
||||||
|
organizationId: z.string().trim().describe(OidcSSo.CREATE_CONFIG.organizationId),
|
||||||
|
manageGroupMemberships: z
|
||||||
|
.boolean()
|
||||||
|
.optional()
|
||||||
|
.default(false)
|
||||||
|
.describe(OidcSSo.CREATE_CONFIG.manageGroupMemberships),
|
||||||
jwtSignatureAlgorithm: z
|
jwtSignatureAlgorithm: z
|
||||||
.nativeEnum(OIDCJWTSignatureAlgorithm)
|
.nativeEnum(OIDCJWTSignatureAlgorithm)
|
||||||
.optional()
|
.optional()
|
||||||
.default(OIDCJWTSignatureAlgorithm.RS256)
|
.default(OIDCJWTSignatureAlgorithm.RS256)
|
||||||
|
.describe(OidcSSo.CREATE_CONFIG.jwtSignatureAlgorithm)
|
||||||
})
|
})
|
||||||
.superRefine((data, ctx) => {
|
.superRefine((data, ctx) => {
|
||||||
if (data.configurationType === OIDCConfigurationType.CUSTOM) {
|
if (data.configurationType === OIDCConfigurationType.CUSTOM) {
|
||||||
|
@ -13,6 +13,7 @@ import { FastifyRequest } from "fastify";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { SamlProviders, TGetSamlCfgDTO } from "@app/ee/services/saml-config/saml-config-types";
|
import { SamlProviders, TGetSamlCfgDTO } from "@app/ee/services/saml-config/saml-config-types";
|
||||||
|
import { ApiDocsTags, SamlSso } from "@app/lib/api-docs";
|
||||||
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 { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
@ -149,8 +150,8 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
|||||||
firstName,
|
firstName,
|
||||||
lastName: lastName as string,
|
lastName: lastName as string,
|
||||||
relayState: (req.body as { RelayState?: string }).RelayState,
|
relayState: (req.body as { RelayState?: string }).RelayState,
|
||||||
authProvider: (req as unknown as FastifyRequest).ssoConfig?.authProvider as string,
|
authProvider: (req as unknown as FastifyRequest).ssoConfig?.authProvider,
|
||||||
orgId: (req as unknown as FastifyRequest).ssoConfig?.orgId as string,
|
orgId: (req as unknown as FastifyRequest).ssoConfig?.orgId,
|
||||||
metadata: userMetadata
|
metadata: userMetadata
|
||||||
});
|
});
|
||||||
cb(null, { isUserCompleted, providerAuthToken });
|
cb(null, { isUserCompleted, providerAuthToken });
|
||||||
@ -262,14 +263,21 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
|||||||
config: {
|
config: {
|
||||||
rateLimit: readLimit
|
rateLimit: readLimit
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
schema: {
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.SamlSso],
|
||||||
|
description: "Get SAML config",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
organizationId: z.string().trim()
|
organizationId: z.string().trim().describe(SamlSso.GET_CONFIG.organizationId)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z
|
200: z.object({
|
||||||
.object({
|
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
organization: z.string(),
|
organization: z.string(),
|
||||||
orgId: z.string(),
|
orgId: z.string(),
|
||||||
@ -280,7 +288,6 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
|||||||
cert: z.string(),
|
cert: z.string(),
|
||||||
lastUsed: z.date().nullable().optional()
|
lastUsed: z.date().nullable().optional()
|
||||||
})
|
})
|
||||||
.optional()
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
@ -302,15 +309,23 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
|||||||
config: {
|
config: {
|
||||||
rateLimit: writeLimit
|
rateLimit: writeLimit
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
schema: {
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.SamlSso],
|
||||||
|
description: "Create SAML config",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
body: z.object({
|
body: z.object({
|
||||||
organizationId: z.string(),
|
organizationId: z.string().trim().describe(SamlSso.CREATE_CONFIG.organizationId),
|
||||||
authProvider: z.nativeEnum(SamlProviders),
|
authProvider: z.nativeEnum(SamlProviders).describe(SamlSso.CREATE_CONFIG.authProvider),
|
||||||
isActive: z.boolean(),
|
isActive: z.boolean().describe(SamlSso.CREATE_CONFIG.isActive),
|
||||||
entryPoint: z.string(),
|
entryPoint: z.string().trim().describe(SamlSso.CREATE_CONFIG.entryPoint),
|
||||||
issuer: z.string(),
|
issuer: z.string().trim().describe(SamlSso.CREATE_CONFIG.issuer),
|
||||||
cert: z.string()
|
cert: z.string().trim().describe(SamlSso.CREATE_CONFIG.cert)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: SanitizedSamlConfigSchema
|
200: SanitizedSamlConfigSchema
|
||||||
@ -341,18 +356,26 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
|||||||
config: {
|
config: {
|
||||||
rateLimit: writeLimit
|
rateLimit: writeLimit
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
schema: {
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.SamlSso],
|
||||||
|
description: "Update SAML config",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
body: z
|
body: z
|
||||||
.object({
|
.object({
|
||||||
authProvider: z.nativeEnum(SamlProviders),
|
authProvider: z.nativeEnum(SamlProviders).describe(SamlSso.UPDATE_CONFIG.authProvider),
|
||||||
isActive: z.boolean(),
|
isActive: z.boolean().describe(SamlSso.UPDATE_CONFIG.isActive),
|
||||||
entryPoint: z.string(),
|
entryPoint: z.string().trim().describe(SamlSso.UPDATE_CONFIG.entryPoint),
|
||||||
issuer: z.string(),
|
issuer: z.string().trim().describe(SamlSso.UPDATE_CONFIG.issuer),
|
||||||
cert: z.string()
|
cert: z.string().trim().describe(SamlSso.UPDATE_CONFIG.cert)
|
||||||
})
|
})
|
||||||
.partial()
|
.partial()
|
||||||
.merge(z.object({ organizationId: z.string() })),
|
.merge(z.object({ organizationId: z.string().trim().describe(SamlSso.UPDATE_CONFIG.organizationId) })),
|
||||||
response: {
|
response: {
|
||||||
200: SanitizedSamlConfigSchema
|
200: SanitizedSamlConfigSchema
|
||||||
}
|
}
|
||||||
|
@ -107,34 +107,26 @@ export const oidcConfigServiceFactory = ({
|
|||||||
kmsService
|
kmsService
|
||||||
}: TOidcConfigServiceFactoryDep) => {
|
}: TOidcConfigServiceFactoryDep) => {
|
||||||
const getOidc = async (dto: TGetOidcCfgDTO) => {
|
const getOidc = async (dto: TGetOidcCfgDTO) => {
|
||||||
const org = await orgDAL.findOne({ slug: dto.orgSlug });
|
const oidcCfg = await oidcConfigDAL.findOne({
|
||||||
if (!org) {
|
orgId: dto.organizationId
|
||||||
|
});
|
||||||
|
if (!oidcCfg) {
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
message: `Organization with slug '${dto.orgSlug}' not found`,
|
message: `OIDC configuration for organization with ID '${dto.organizationId}' not found`
|
||||||
name: "OrgNotFound"
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dto.type === "external") {
|
if (dto.type === "external") {
|
||||||
const { permission } = await permissionService.getOrgPermission(
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
dto.actor,
|
dto.actor,
|
||||||
dto.actorId,
|
dto.actorId,
|
||||||
org.id,
|
dto.organizationId,
|
||||||
dto.actorAuthMethod,
|
dto.actorAuthMethod,
|
||||||
dto.actorOrgId
|
dto.actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Sso);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Sso);
|
||||||
}
|
}
|
||||||
|
|
||||||
const oidcCfg = await oidcConfigDAL.findOne({
|
|
||||||
orgId: org.id
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!oidcCfg) {
|
|
||||||
throw new NotFoundError({
|
|
||||||
message: `OIDC configuration for organization with slug '${dto.orgSlug}' not found`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const { decryptor } = await kmsService.createCipherPairWithDataKey({
|
const { decryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
type: KmsDataKey.Organization,
|
type: KmsDataKey.Organization,
|
||||||
orgId: oidcCfg.orgId
|
orgId: oidcCfg.orgId
|
||||||
@ -465,7 +457,7 @@ export const oidcConfigServiceFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const updateOidcCfg = async ({
|
const updateOidcCfg = async ({
|
||||||
orgSlug,
|
organizationId,
|
||||||
allowedEmailDomains,
|
allowedEmailDomains,
|
||||||
configurationType,
|
configurationType,
|
||||||
discoveryURL,
|
discoveryURL,
|
||||||
@ -484,13 +476,11 @@ export const oidcConfigServiceFactory = ({
|
|||||||
manageGroupMemberships,
|
manageGroupMemberships,
|
||||||
jwtSignatureAlgorithm
|
jwtSignatureAlgorithm
|
||||||
}: TUpdateOidcCfgDTO) => {
|
}: TUpdateOidcCfgDTO) => {
|
||||||
const org = await orgDAL.findOne({
|
const org = await orgDAL.findOne({ id: organizationId });
|
||||||
slug: orgSlug
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!org) {
|
if (!org) {
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
message: `Organization with slug '${orgSlug}' not found`
|
message: `Organization with ID '${organizationId}' not found`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -555,7 +545,7 @@ export const oidcConfigServiceFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const createOidcCfg = async ({
|
const createOidcCfg = async ({
|
||||||
orgSlug,
|
organizationId,
|
||||||
allowedEmailDomains,
|
allowedEmailDomains,
|
||||||
configurationType,
|
configurationType,
|
||||||
discoveryURL,
|
discoveryURL,
|
||||||
@ -574,12 +564,10 @@ export const oidcConfigServiceFactory = ({
|
|||||||
manageGroupMemberships,
|
manageGroupMemberships,
|
||||||
jwtSignatureAlgorithm
|
jwtSignatureAlgorithm
|
||||||
}: TCreateOidcCfgDTO) => {
|
}: TCreateOidcCfgDTO) => {
|
||||||
const org = await orgDAL.findOne({
|
const org = await orgDAL.findOne({ id: organizationId });
|
||||||
slug: orgSlug
|
|
||||||
});
|
|
||||||
if (!org) {
|
if (!org) {
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
message: `Organization with slug '${orgSlug}' not found`
|
message: `Organization with ID '${organizationId}' not found`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -639,7 +627,7 @@ export const oidcConfigServiceFactory = ({
|
|||||||
|
|
||||||
const oidcCfg = await getOidc({
|
const oidcCfg = await getOidc({
|
||||||
type: "internal",
|
type: "internal",
|
||||||
orgSlug
|
organizationId: org.id
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!oidcCfg || !oidcCfg.isActive) {
|
if (!oidcCfg || !oidcCfg.isActive) {
|
||||||
|
@ -26,11 +26,11 @@ export type TOidcLoginDTO = {
|
|||||||
export type TGetOidcCfgDTO =
|
export type TGetOidcCfgDTO =
|
||||||
| ({
|
| ({
|
||||||
type: "external";
|
type: "external";
|
||||||
orgSlug: string;
|
organizationId: string;
|
||||||
} & TGenericPermission)
|
} & TGenericPermission)
|
||||||
| {
|
| {
|
||||||
type: "internal";
|
type: "internal";
|
||||||
orgSlug: string;
|
organizationId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TCreateOidcCfgDTO = {
|
export type TCreateOidcCfgDTO = {
|
||||||
@ -45,7 +45,7 @@ export type TCreateOidcCfgDTO = {
|
|||||||
clientId: string;
|
clientId: string;
|
||||||
clientSecret: string;
|
clientSecret: string;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
orgSlug: string;
|
organizationId: string;
|
||||||
manageGroupMemberships: boolean;
|
manageGroupMemberships: boolean;
|
||||||
jwtSignatureAlgorithm: OIDCJWTSignatureAlgorithm;
|
jwtSignatureAlgorithm: OIDCJWTSignatureAlgorithm;
|
||||||
} & TGenericPermission;
|
} & TGenericPermission;
|
||||||
@ -62,7 +62,7 @@ export type TUpdateOidcCfgDTO = Partial<{
|
|||||||
clientId: string;
|
clientId: string;
|
||||||
clientSecret: string;
|
clientSecret: string;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
orgSlug: string;
|
organizationId: string;
|
||||||
manageGroupMemberships: boolean;
|
manageGroupMemberships: boolean;
|
||||||
jwtSignatureAlgorithm: OIDCJWTSignatureAlgorithm;
|
jwtSignatureAlgorithm: OIDCJWTSignatureAlgorithm;
|
||||||
}> &
|
}> &
|
||||||
|
@ -148,10 +148,18 @@ export const samlConfigServiceFactory = ({
|
|||||||
let samlConfig: TSamlConfigs | undefined;
|
let samlConfig: TSamlConfigs | undefined;
|
||||||
if (dto.type === "org") {
|
if (dto.type === "org") {
|
||||||
samlConfig = await samlConfigDAL.findOne({ orgId: dto.orgId });
|
samlConfig = await samlConfigDAL.findOne({ orgId: dto.orgId });
|
||||||
if (!samlConfig) return;
|
if (!samlConfig) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `SAML configuration for organization with ID '${dto.orgId}' not found`
|
||||||
|
});
|
||||||
|
}
|
||||||
} else if (dto.type === "orgSlug") {
|
} else if (dto.type === "orgSlug") {
|
||||||
const org = await orgDAL.findOne({ slug: dto.orgSlug });
|
const org = await orgDAL.findOne({ slug: dto.orgSlug });
|
||||||
if (!org) return;
|
if (!org) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Organization with slug '${dto.orgSlug}' not found`
|
||||||
|
});
|
||||||
|
}
|
||||||
samlConfig = await samlConfigDAL.findOne({ orgId: org.id });
|
samlConfig = await samlConfigDAL.findOne({ orgId: org.id });
|
||||||
} else if (dto.type === "ssoId") {
|
} else if (dto.type === "ssoId") {
|
||||||
// TODO:
|
// TODO:
|
||||||
|
@ -61,8 +61,7 @@ export type TSamlLoginDTO = {
|
|||||||
export type TSamlConfigServiceFactory = {
|
export type TSamlConfigServiceFactory = {
|
||||||
createSamlCfg: (arg: TCreateSamlCfgDTO) => Promise<TSamlConfigs>;
|
createSamlCfg: (arg: TCreateSamlCfgDTO) => Promise<TSamlConfigs>;
|
||||||
updateSamlCfg: (arg: TUpdateSamlCfgDTO) => Promise<TSamlConfigs>;
|
updateSamlCfg: (arg: TUpdateSamlCfgDTO) => Promise<TSamlConfigs>;
|
||||||
getSaml: (arg: TGetSamlCfgDTO) => Promise<
|
getSaml: (arg: TGetSamlCfgDTO) => Promise<{
|
||||||
| {
|
|
||||||
id: string;
|
id: string;
|
||||||
organization: string;
|
organization: string;
|
||||||
orgId: string;
|
orgId: string;
|
||||||
@ -72,9 +71,7 @@ export type TSamlConfigServiceFactory = {
|
|||||||
issuer: string;
|
issuer: string;
|
||||||
cert: string;
|
cert: string;
|
||||||
lastUsed: Date | null | undefined;
|
lastUsed: Date | null | undefined;
|
||||||
}
|
}>;
|
||||||
| undefined
|
|
||||||
>;
|
|
||||||
samlLogin: (arg: TSamlLoginDTO) => Promise<{
|
samlLogin: (arg: TSamlLoginDTO) => Promise<{
|
||||||
isUserCompleted: boolean;
|
isUserCompleted: boolean;
|
||||||
providerAuthToken: string;
|
providerAuthToken: string;
|
||||||
|
@ -66,7 +66,10 @@ export enum ApiDocsTags {
|
|||||||
KmsKeys = "KMS Keys",
|
KmsKeys = "KMS Keys",
|
||||||
KmsEncryption = "KMS Encryption",
|
KmsEncryption = "KMS Encryption",
|
||||||
KmsSigning = "KMS Signing",
|
KmsSigning = "KMS Signing",
|
||||||
SecretScanning = "Secret Scanning"
|
SecretScanning = "Secret Scanning",
|
||||||
|
OidcSso = "OIDC SSO",
|
||||||
|
SamlSso = "SAML SSO",
|
||||||
|
LdapSso = "LDAP SSO"
|
||||||
}
|
}
|
||||||
|
|
||||||
export const GROUPS = {
|
export const GROUPS = {
|
||||||
@ -2662,3 +2665,113 @@ export const SecretScanningConfigs = {
|
|||||||
content: "The contents of the Secret Scanning Configuration file."
|
content: "The contents of the Secret Scanning Configuration file."
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const OidcSSo = {
|
||||||
|
GET_CONFIG: {
|
||||||
|
organizationId: "The ID of the organization to get the OIDC config for."
|
||||||
|
},
|
||||||
|
UPDATE_CONFIG: {
|
||||||
|
organizationId: "The ID of the organization to update the OIDC config for.",
|
||||||
|
allowedEmailDomains:
|
||||||
|
"A list of allowed email domains that users can use to authenticate with. This field is comma separated. Example: 'example.com,acme.com'",
|
||||||
|
discoveryURL: "The URL of the OIDC discovery endpoint.",
|
||||||
|
configurationType: "The configuration type to use for the OIDC configuration.",
|
||||||
|
issuer:
|
||||||
|
"The issuer for the OIDC configuration. This is only supported when the OIDC configuration type is set to 'custom'.",
|
||||||
|
authorizationEndpoint:
|
||||||
|
"The endpoint to use for OIDC authorization. This is only supported when the OIDC configuration type is set to 'custom'.",
|
||||||
|
jwksUri: "The URL of the OIDC JWKS endpoint.",
|
||||||
|
tokenEndpoint: "The token endpoint to use for OIDC token exchange.",
|
||||||
|
userinfoEndpoint: "The userinfo endpoint to get user information from the OIDC provider.",
|
||||||
|
clientId: "The client ID to use for OIDC authentication.",
|
||||||
|
clientSecret: "The client secret to use for OIDC authentication.",
|
||||||
|
isActive: "Whether to enable or disable this OIDC configuration.",
|
||||||
|
manageGroupMemberships:
|
||||||
|
"Whether to manage group memberships for the OIDC configuration. If enabled, users will automatically be assigned groups when they sign in, based on which groups they are a member of in the OIDC provider.",
|
||||||
|
jwtSignatureAlgorithm: "The algorithm to use for JWT signature verification."
|
||||||
|
},
|
||||||
|
CREATE_CONFIG: {
|
||||||
|
organizationId: "The ID of the organization to create the OIDC config for.",
|
||||||
|
allowedEmailDomains:
|
||||||
|
"A list of allowed email domains that users can use to authenticate with. This field is comma separated.",
|
||||||
|
discoveryURL: "The URL of the OIDC discovery endpoint.",
|
||||||
|
configurationType: "The configuration type to use for the OIDC configuration.",
|
||||||
|
issuer:
|
||||||
|
"The issuer for the OIDC configuration. This is only supported when the OIDC configuration type is set to 'custom'.",
|
||||||
|
authorizationEndpoint:
|
||||||
|
"The authorization endpoint to use for OIDC authorization. This is only supported when the OIDC configuration type is set to 'custom'.",
|
||||||
|
jwksUri: "The URL of the OIDC JWKS endpoint.",
|
||||||
|
tokenEndpoint: "The token endpoint to use for OIDC token exchange.",
|
||||||
|
userinfoEndpoint: "The userinfo endpoint to get user information from the OIDC provider.",
|
||||||
|
clientId: "The client ID to use for OIDC authentication.",
|
||||||
|
clientSecret: "The client secret to use for OIDC authentication.",
|
||||||
|
isActive: "Whether to enable or disable this OIDC configuration.",
|
||||||
|
manageGroupMemberships:
|
||||||
|
"Whether to manage group memberships for the OIDC configuration. If enabled, users will automatically be assigned groups when they sign in, based on which groups they are a member of in the OIDC provider.",
|
||||||
|
jwtSignatureAlgorithm: "The algorithm to use for JWT signature verification."
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SamlSso = {
|
||||||
|
GET_CONFIG: {
|
||||||
|
organizationId: "The ID of the organization to get the SAML config for."
|
||||||
|
},
|
||||||
|
UPDATE_CONFIG: {
|
||||||
|
organizationId: "The ID of the organization to update the SAML config for.",
|
||||||
|
authProvider: "Authentication provider to use for SAML authentication.",
|
||||||
|
isActive: "Whether to enable or disable this SAML configuration.",
|
||||||
|
entryPoint:
|
||||||
|
"The entry point for the SAML authentication. This is the URL that the user will be redirected to after they have authenticated with the SAML provider.",
|
||||||
|
issuer: "The SAML provider issuer URL or entity ID.",
|
||||||
|
cert: "The certificate to use for SAML authentication."
|
||||||
|
},
|
||||||
|
CREATE_CONFIG: {
|
||||||
|
organizationId: "The ID of the organization to create the SAML config for.",
|
||||||
|
authProvider: "Authentication provider to use for SAML authentication.",
|
||||||
|
isActive: "Whether to enable or disable this SAML configuration.",
|
||||||
|
entryPoint:
|
||||||
|
"The entry point for the SAML authentication. This is the URL that the user will be redirected to after they have authenticated with the SAML provider.",
|
||||||
|
issuer: "The SAML provider issuer URL or entity ID.",
|
||||||
|
cert: "The certificate to use for SAML authentication."
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LdapSso = {
|
||||||
|
GET_CONFIG: {
|
||||||
|
organizationId: "The ID of the organization to get the LDAP config for."
|
||||||
|
},
|
||||||
|
CREATE_CONFIG: {
|
||||||
|
organizationId: "The ID of the organization to create the LDAP config for.",
|
||||||
|
isActive: "Whether to enable or disable this LDAP configuration.",
|
||||||
|
url: "The LDAP server to connect to such as `ldap://ldap.your-org.com`, `ldaps://ldap.myorg.com:636` (for connection over SSL/TLS), etc.",
|
||||||
|
bindDN:
|
||||||
|
"The distinguished name of the object to bind when performing the user search such as `cn=infisical,ou=Users,dc=acme,dc=com`",
|
||||||
|
bindPass: "The password to use along with Bind DN when performing the user search.",
|
||||||
|
searchBase: "The base DN to use for the user search such as `ou=Users,dc=acme,dc=com`",
|
||||||
|
uniqueUserAttribute:
|
||||||
|
"The attribute to use as the unique identifier of LDAP users such as `sAMAccountName`, `cn`, `uid`, `objectGUID`. If left blank, defaults to uidNumber",
|
||||||
|
searchFilter:
|
||||||
|
"The template used to construct the LDAP user search filter such as `(uid={{username}})` uses literal `{{username}}` to have the given username used in the search. The default is `(uid={{username}})` which is compatible with several common directory schemas.",
|
||||||
|
groupSearchBase: "LDAP search base to use for group membership search such as `ou=Groups,dc=acme,dc=com`",
|
||||||
|
groupSearchFilter:
|
||||||
|
"The template used when constructing the group membership query such as `(&(objectClass=posixGroup)(memberUid={{.Username}}))`. The template can access the following context variables: `[UserDN, UserName]`. The default is `(|(memberUid={{.Username}})(member={{.UserDN}})(uniqueMember={{.UserDN}}))` which is compatible with several common directory schemas.",
|
||||||
|
caCert: "The CA certificate to use when verifying the LDAP server certificate."
|
||||||
|
},
|
||||||
|
UPDATE_CONFIG: {
|
||||||
|
organizationId: "The ID of the organization to update the LDAP config for.",
|
||||||
|
isActive: "Whether to enable or disable this LDAP configuration.",
|
||||||
|
url: "The LDAP server to connect to such as `ldap://ldap.your-org.com`, `ldaps://ldap.myorg.com:636` (for connection over SSL/TLS), etc.",
|
||||||
|
bindDN:
|
||||||
|
"The distinguished name of object to bind when performing the user search such as `cn=infisical,ou=Users,dc=acme,dc=com`",
|
||||||
|
bindPass: "The password to use along with Bind DN when performing the user search.",
|
||||||
|
uniqueUserAttribute:
|
||||||
|
"The attribute to use as the unique identifier of LDAP users such as `sAMAccountName`, `cn`, `uid`, `objectGUID`. If left blank, defaults to uidNumber",
|
||||||
|
searchFilter:
|
||||||
|
"The template used to construct the LDAP user search filter such as `(uid={{username}})` uses literal `{{username}}` to have the given username used in the search. The default is `(uid={{username}})` which is compatible with several common directory schemas.",
|
||||||
|
searchBase: "The base DN to use for the user search such as `ou=Users,dc=acme,dc=com`",
|
||||||
|
groupSearchBase: "LDAP search base to use for group membership search such as `ou=Groups,dc=acme,dc=com`",
|
||||||
|
groupSearchFilter:
|
||||||
|
"The template used when constructing the group membership query such as `(&(objectClass=posixGroup)(memberUid={{.Username}}))`. The template can access the following context variables: `[UserDN, UserName]`. The default is `(|(memberUid={{.Username}})(member={{.UserDN}})(uniqueMember={{.UserDN}}))` which is compatible with several common directory schemas.",
|
||||||
|
caCert: "The CA certificate to use when verifying the LDAP server certificate."
|
||||||
|
}
|
||||||
|
};
|
||||||
|
@ -2,6 +2,7 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { QueueWorkerProfile } from "@app/lib/types";
|
import { QueueWorkerProfile } from "@app/lib/types";
|
||||||
|
|
||||||
|
import { BadRequestError } from "../errors";
|
||||||
import { removeTrailingSlash } from "../fn";
|
import { removeTrailingSlash } from "../fn";
|
||||||
import { CustomLogger } from "../logger/logger";
|
import { CustomLogger } from "../logger/logger";
|
||||||
import { zpStr } from "../zod";
|
import { zpStr } from "../zod";
|
||||||
@ -341,8 +342,11 @@ const envSchema = z
|
|||||||
|
|
||||||
export type TEnvConfig = Readonly<z.infer<typeof envSchema>>;
|
export type TEnvConfig = Readonly<z.infer<typeof envSchema>>;
|
||||||
let envCfg: TEnvConfig;
|
let envCfg: TEnvConfig;
|
||||||
|
let originalEnvConfig: TEnvConfig;
|
||||||
|
|
||||||
export const getConfig = () => envCfg;
|
export const getConfig = () => envCfg;
|
||||||
|
export const getOriginalConfig = () => originalEnvConfig;
|
||||||
|
|
||||||
// cannot import singleton logger directly as it needs config to load various transport
|
// cannot import singleton logger directly as it needs config to load various transport
|
||||||
export const initEnvConfig = (logger?: CustomLogger) => {
|
export const initEnvConfig = (logger?: CustomLogger) => {
|
||||||
const parsedEnv = envSchema.safeParse(process.env);
|
const parsedEnv = envSchema.safeParse(process.env);
|
||||||
@ -352,10 +356,115 @@ export const initEnvConfig = (logger?: CustomLogger) => {
|
|||||||
process.exit(-1);
|
process.exit(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
envCfg = Object.freeze(parsedEnv.data);
|
const config = Object.freeze(parsedEnv.data);
|
||||||
|
envCfg = config;
|
||||||
|
|
||||||
|
if (!originalEnvConfig) {
|
||||||
|
originalEnvConfig = config;
|
||||||
|
}
|
||||||
|
|
||||||
return envCfg;
|
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 = () => {
|
export const formatSmtpConfig = () => {
|
||||||
const tlsOptions: {
|
const tlsOptions: {
|
||||||
rejectUnauthorized: boolean;
|
rejectUnauthorized: boolean;
|
||||||
|
@ -300,6 +300,7 @@ import { injectIdentity } from "../plugins/auth/inject-identity";
|
|||||||
import { injectPermission } from "../plugins/auth/inject-permission";
|
import { injectPermission } from "../plugins/auth/inject-permission";
|
||||||
import { injectRateLimits } from "../plugins/inject-rate-limits";
|
import { injectRateLimits } from "../plugins/inject-rate-limits";
|
||||||
import { registerV1Routes } from "./v1";
|
import { registerV1Routes } from "./v1";
|
||||||
|
import { initializeOauthConfigSync } from "./v1/sso-router";
|
||||||
import { registerV2Routes } from "./v2";
|
import { registerV2Routes } from "./v2";
|
||||||
import { registerV3Routes } from "./v3";
|
import { registerV3Routes } from "./v3";
|
||||||
|
|
||||||
@ -1910,6 +1911,7 @@ export const registerRoutes = async (
|
|||||||
await hsmService.startService();
|
await hsmService.startService();
|
||||||
|
|
||||||
await telemetryQueue.startTelemetryCheck();
|
await telemetryQueue.startTelemetryCheck();
|
||||||
|
await telemetryQueue.startAggregatedEventsJob();
|
||||||
await dailyResourceCleanUp.startCleanUp();
|
await dailyResourceCleanUp.startCleanUp();
|
||||||
await dailyExpiringPkiItemAlert.startSendingAlerts();
|
await dailyExpiringPkiItemAlert.startSendingAlerts();
|
||||||
await pkiSubscriberQueue.startDailyAutoRenewalJob();
|
await pkiSubscriberQueue.startDailyAutoRenewalJob();
|
||||||
@ -2046,6 +2048,16 @@ 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", {
|
server.decorate<FastifyZodProvider["store"]>("store", {
|
||||||
user: userDAL,
|
user: userDAL,
|
||||||
kmipClient: kmipClientDAL
|
kmipClient: kmipClientDAL
|
||||||
|
@ -8,7 +8,7 @@ import {
|
|||||||
SuperAdminSchema,
|
SuperAdminSchema,
|
||||||
UsersSchema
|
UsersSchema
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig, overridableKeys } 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";
|
||||||
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
||||||
@ -42,7 +42,8 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
|
|||||||
encryptedGitHubAppConnectionClientSecret: true,
|
encryptedGitHubAppConnectionClientSecret: true,
|
||||||
encryptedGitHubAppConnectionSlug: true,
|
encryptedGitHubAppConnectionSlug: true,
|
||||||
encryptedGitHubAppConnectionId: true,
|
encryptedGitHubAppConnectionId: true,
|
||||||
encryptedGitHubAppConnectionPrivateKey: true
|
encryptedGitHubAppConnectionPrivateKey: true,
|
||||||
|
encryptedEnvOverrides: true
|
||||||
}).extend({
|
}).extend({
|
||||||
isMigrationModeOn: z.boolean(),
|
isMigrationModeOn: z.boolean(),
|
||||||
defaultAuthOrgSlug: z.string().nullable(),
|
defaultAuthOrgSlug: z.string().nullable(),
|
||||||
@ -110,11 +111,14 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
|
|||||||
.refine((content) => DOMPurify.sanitize(content) === content, {
|
.refine((content) => DOMPurify.sanitize(content) === content, {
|
||||||
message: "Page frame content contains unsafe HTML."
|
message: "Page frame content contains unsafe HTML."
|
||||||
})
|
})
|
||||||
.optional()
|
.optional(),
|
||||||
|
envOverrides: z.record(z.enum(Array.from(overridableKeys) as [string, ...string[]]), z.string()).optional()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
config: SuperAdminSchema.extend({
|
config: SuperAdminSchema.omit({
|
||||||
|
encryptedEnvOverrides: true
|
||||||
|
}).extend({
|
||||||
defaultAuthOrgSlug: z.string().nullable()
|
defaultAuthOrgSlug: z.string().nullable()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -381,6 +385,41 @@ 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({
|
server.route({
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
url: "/user-management/users/:userId",
|
url: "/user-management/users/:userId",
|
||||||
|
@ -382,7 +382,8 @@ export const registerSyncSecretsEndpoints = <T extends TSecretSync, I extends TS
|
|||||||
{
|
{
|
||||||
syncId,
|
syncId,
|
||||||
destination,
|
destination,
|
||||||
importBehavior
|
importBehavior,
|
||||||
|
auditLogInfo: req.auditLogInfo
|
||||||
},
|
},
|
||||||
req.permission
|
req.permission
|
||||||
)) as T;
|
)) as T;
|
||||||
@ -415,7 +416,8 @@ export const registerSyncSecretsEndpoints = <T extends TSecretSync, I extends TS
|
|||||||
const secretSync = (await server.services.secretSync.triggerSecretSyncRemoveSecretsById(
|
const secretSync = (await server.services.secretSync.triggerSecretSyncRemoveSecretsById(
|
||||||
{
|
{
|
||||||
syncId,
|
syncId,
|
||||||
destination
|
destination,
|
||||||
|
auditLogInfo: req.auditLogInfo
|
||||||
},
|
},
|
||||||
req.permission
|
req.permission
|
||||||
)) as T;
|
)) as T;
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
import { Authenticator } from "@fastify/passport";
|
import { Authenticator } from "@fastify/passport";
|
||||||
import fastifySession from "@fastify/session";
|
import fastifySession from "@fastify/session";
|
||||||
import RedisStore from "connect-redis";
|
import RedisStore from "connect-redis";
|
||||||
|
import { CronJob } from "cron";
|
||||||
import { Strategy as GitLabStrategy } from "passport-gitlab2";
|
import { Strategy as GitLabStrategy } from "passport-gitlab2";
|
||||||
import { Strategy as GoogleStrategy } from "passport-google-oauth20";
|
import { Strategy as GoogleStrategy } from "passport-google-oauth20";
|
||||||
import { Strategy as OAuth2Strategy } from "passport-oauth2";
|
import { Strategy as OAuth2Strategy } from "passport-oauth2";
|
||||||
@ -25,27 +26,14 @@ import { AuthMethod } from "@app/services/auth/auth-type";
|
|||||||
import { OrgAuthMethod } from "@app/services/org/org-types";
|
import { OrgAuthMethod } from "@app/services/org/org-types";
|
||||||
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
|
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
|
||||||
|
|
||||||
export const registerSsoRouter = async (server: FastifyZodProvider) => {
|
const passport = new Authenticator({ key: "sso", userProperty: "passportUser" });
|
||||||
|
|
||||||
|
let serverInstance: FastifyZodProvider | null = null;
|
||||||
|
|
||||||
|
export const registerOauthMiddlewares = (server: FastifyZodProvider) => {
|
||||||
|
serverInstance = server;
|
||||||
const appCfg = getConfig();
|
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
|
// passport oauth strategy for Google
|
||||||
const isGoogleOauthActive = Boolean(appCfg.CLIENT_ID_GOOGLE_LOGIN && appCfg.CLIENT_SECRET_GOOGLE_LOGIN);
|
const isGoogleOauthActive = Boolean(appCfg.CLIENT_ID_GOOGLE_LOGIN && appCfg.CLIENT_SECRET_GOOGLE_LOGIN);
|
||||||
if (isGoogleOauthActive) {
|
if (isGoogleOauthActive) {
|
||||||
@ -176,6 +164,49 @@ export const registerSsoRouter = async (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({
|
server.route({
|
||||||
url: "/redirect/google",
|
url: "/redirect/google",
|
||||||
|
@ -93,6 +93,7 @@ export const identityProjectServiceFactory = ({
|
|||||||
projectId
|
projectId
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (requestedRoleChange !== ProjectMembershipRole.NoAccess) {
|
||||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||||
membership.shouldUseNewPrivilegeSystem,
|
membership.shouldUseNewPrivilegeSystem,
|
||||||
ProjectPermissionIdentityActions.GrantPrivileges,
|
ProjectPermissionIdentityActions.GrantPrivileges,
|
||||||
@ -111,6 +112,7 @@ export const identityProjectServiceFactory = ({
|
|||||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// validate custom roles input
|
// validate custom roles input
|
||||||
const customInputRoles = roles.filter(
|
const customInputRoles = roles.filter(
|
||||||
|
@ -69,6 +69,7 @@ export const identityServiceFactory = ({
|
|||||||
orgId
|
orgId
|
||||||
);
|
);
|
||||||
const isCustomRole = Boolean(customRole);
|
const isCustomRole = Boolean(customRole);
|
||||||
|
if (role !== OrgMembershipRole.NoAccess) {
|
||||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||||
membership.shouldUseNewPrivilegeSystem,
|
membership.shouldUseNewPrivilegeSystem,
|
||||||
OrgPermissionIdentityActions.GrantPrivileges,
|
OrgPermissionIdentityActions.GrantPrivileges,
|
||||||
@ -86,6 +87,7 @@ export const identityServiceFactory = ({
|
|||||||
),
|
),
|
||||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
details: { missingPermissions: permissionBoundary.missingPermissions }
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const plan = await licenseService.getPlan(orgId);
|
const plan = await licenseService.getPlan(orgId);
|
||||||
|
|
||||||
@ -187,6 +189,7 @@ export const identityServiceFactory = ({
|
|||||||
),
|
),
|
||||||
details: { missingPermissions: appliedRolePermissionBoundary.missingPermissions }
|
details: { missingPermissions: appliedRolePermissionBoundary.missingPermissions }
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isCustomRole) customRole = customOrgRole;
|
if (isCustomRole) customRole = customOrgRole;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,8 +122,8 @@ export const orgMembershipDALFactory = (db: TDbClient) => {
|
|||||||
.orWhere((qb) => {
|
.orWhere((qb) => {
|
||||||
// lastInvitedAt is older than 1 week ago AND createdAt is younger than 1 month ago
|
// lastInvitedAt is older than 1 week ago AND createdAt is younger than 1 month ago
|
||||||
void qb
|
void qb
|
||||||
.where(`${TableName.OrgMembership}.lastInvitedAt`, "<", oneMonthAgo)
|
.where(`${TableName.OrgMembership}.lastInvitedAt`, "<", oneWeekAgo)
|
||||||
.where(`${TableName.OrgMembership}.createdAt`, ">", oneWeekAgo);
|
.where(`${TableName.OrgMembership}.createdAt`, ">", oneMonthAgo);
|
||||||
});
|
});
|
||||||
|
|
||||||
return memberships;
|
return memberships;
|
||||||
@ -135,9 +135,22 @@ export const orgMembershipDALFactory = (db: TDbClient) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updateLastInvitedAtByIds = async (membershipIds: string[]) => {
|
||||||
|
try {
|
||||||
|
if (membershipIds.length === 0) return;
|
||||||
|
await db(TableName.OrgMembership).whereIn("id", membershipIds).update({ lastInvitedAt: new Date() });
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({
|
||||||
|
error,
|
||||||
|
name: "Update last invited at by ids"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...orgMembershipOrm,
|
...orgMembershipOrm,
|
||||||
findOrgMembershipById,
|
findOrgMembershipById,
|
||||||
findRecentInvitedMemberships
|
findRecentInvitedMemberships,
|
||||||
|
updateLastInvitedAtByIds
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -109,7 +109,12 @@ type TOrgServiceFactoryDep = {
|
|||||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "delete" | "insertMany" | "findLatestProjectKey" | "create">;
|
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "delete" | "insertMany" | "findLatestProjectKey" | "create">;
|
||||||
orgMembershipDAL: Pick<
|
orgMembershipDAL: Pick<
|
||||||
TOrgMembershipDALFactory,
|
TOrgMembershipDALFactory,
|
||||||
"findOrgMembershipById" | "findOne" | "findById" | "findRecentInvitedMemberships" | "updateById"
|
| "findOrgMembershipById"
|
||||||
|
| "findOne"
|
||||||
|
| "findById"
|
||||||
|
| "findRecentInvitedMemberships"
|
||||||
|
| "updateById"
|
||||||
|
| "updateLastInvitedAtByIds"
|
||||||
>;
|
>;
|
||||||
incidentContactDAL: TIncidentContactsDALFactory;
|
incidentContactDAL: TIncidentContactsDALFactory;
|
||||||
samlConfigDAL: Pick<TSamlConfigDALFactory, "findOne">;
|
samlConfigDAL: Pick<TSamlConfigDALFactory, "findOne">;
|
||||||
@ -763,6 +768,10 @@ export const orgServiceFactory = ({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await orgMembershipDAL.updateById(inviteeOrgMembership.id, {
|
||||||
|
lastInvitedAt: new Date()
|
||||||
|
});
|
||||||
|
|
||||||
return { signupToken: undefined };
|
return { signupToken: undefined };
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1433,6 +1442,7 @@ export const orgServiceFactory = ({
|
|||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
|
|
||||||
const orgCache: Record<string, { name: string; id: string } | undefined> = {};
|
const orgCache: Record<string, { name: string; id: string } | undefined> = {};
|
||||||
|
const notifiedUsers: string[] = [];
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
invitedUsers.map(async (invitedUser) => {
|
invitedUsers.map(async (invitedUser) => {
|
||||||
@ -1463,13 +1473,12 @@ export const orgServiceFactory = ({
|
|||||||
callback_url: `${appCfg.SITE_URL}/signupinvite`
|
callback_url: `${appCfg.SITE_URL}/signupinvite`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
notifiedUsers.push(invitedUser.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
await orgMembershipDAL.updateById(invitedUser.id, {
|
|
||||||
lastInvitedAt: new Date()
|
|
||||||
});
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await orgMembershipDAL.updateLastInvitedAtByIds(notifiedUsers);
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -5,7 +5,13 @@ import jwt from "jsonwebtoken";
|
|||||||
import { IdentityAuthMethod, OrgMembershipRole, TSuperAdmin, TSuperAdminUpdate } from "@app/db/schemas";
|
import { IdentityAuthMethod, OrgMembershipRole, TSuperAdmin, TSuperAdminUpdate } from "@app/db/schemas";
|
||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { PgSqlLock, TKeyStoreFactory } from "@app/keystore/keystore";
|
import { PgSqlLock, TKeyStoreFactory } from "@app/keystore/keystore";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import {
|
||||||
|
getConfig,
|
||||||
|
getOriginalConfig,
|
||||||
|
overrideEnvConfig,
|
||||||
|
overwriteSchema,
|
||||||
|
validateOverrides
|
||||||
|
} from "@app/lib/config/env";
|
||||||
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
||||||
import { generateUserSrpKeys, getUserPrivateKey } from "@app/lib/crypto/srp";
|
import { generateUserSrpKeys, getUserPrivateKey } from "@app/lib/crypto/srp";
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
@ -33,6 +39,7 @@ import { TInvalidateCacheQueueFactory } from "./invalidate-cache-queue";
|
|||||||
import { TSuperAdminDALFactory } from "./super-admin-dal";
|
import { TSuperAdminDALFactory } from "./super-admin-dal";
|
||||||
import {
|
import {
|
||||||
CacheType,
|
CacheType,
|
||||||
|
EnvOverrides,
|
||||||
LoginMethod,
|
LoginMethod,
|
||||||
TAdminBootstrapInstanceDTO,
|
TAdminBootstrapInstanceDTO,
|
||||||
TAdminGetIdentitiesDTO,
|
TAdminGetIdentitiesDTO,
|
||||||
@ -234,6 +241,45 @@ export const superAdminServiceFactory = ({
|
|||||||
adminIntegrationsConfig = config;
|
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 (
|
const updateServerCfg = async (
|
||||||
data: TSuperAdminUpdate & {
|
data: TSuperAdminUpdate & {
|
||||||
slackClientId?: string;
|
slackClientId?: string;
|
||||||
@ -246,6 +292,7 @@ export const superAdminServiceFactory = ({
|
|||||||
gitHubAppConnectionSlug?: string;
|
gitHubAppConnectionSlug?: string;
|
||||||
gitHubAppConnectionId?: string;
|
gitHubAppConnectionId?: string;
|
||||||
gitHubAppConnectionPrivateKey?: string;
|
gitHubAppConnectionPrivateKey?: string;
|
||||||
|
envOverrides?: Record<string, string>;
|
||||||
},
|
},
|
||||||
userId: string
|
userId: string
|
||||||
) => {
|
) => {
|
||||||
@ -374,6 +421,17 @@ export const superAdminServiceFactory = ({
|
|||||||
gitHubAppConnectionSettingsUpdated = true;
|
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);
|
const updatedServerCfg = await serverCfgDAL.updateById(ADMIN_CONFIG_DB_UUID, updatedData);
|
||||||
|
|
||||||
await keyStore.setItemWithExpiry(ADMIN_CONFIG_KEY, ADMIN_CONFIG_KEY_EXP, JSON.stringify(updatedServerCfg));
|
await keyStore.setItemWithExpiry(ADMIN_CONFIG_KEY, ADMIN_CONFIG_KEY_EXP, JSON.stringify(updatedServerCfg));
|
||||||
@ -382,6 +440,10 @@ export const superAdminServiceFactory = ({
|
|||||||
await $syncAdminIntegrationConfig();
|
await $syncAdminIntegrationConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (envOverridesUpdated) {
|
||||||
|
await $syncEnvConfig();
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
updatedServerCfg.encryptedMicrosoftTeamsAppId &&
|
updatedServerCfg.encryptedMicrosoftTeamsAppId &&
|
||||||
updatedServerCfg.encryptedMicrosoftTeamsClientSecret &&
|
updatedServerCfg.encryptedMicrosoftTeamsClientSecret &&
|
||||||
@ -814,6 +876,18 @@ export const superAdminServiceFactory = ({
|
|||||||
return job;
|
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 {
|
return {
|
||||||
initServerCfg,
|
initServerCfg,
|
||||||
updateServerCfg,
|
updateServerCfg,
|
||||||
@ -833,6 +907,9 @@ export const superAdminServiceFactory = ({
|
|||||||
getOrganizations,
|
getOrganizations,
|
||||||
deleteOrganization,
|
deleteOrganization,
|
||||||
deleteOrganizationMembership,
|
deleteOrganizationMembership,
|
||||||
initializeAdminIntegrationConfigSync
|
initializeAdminIntegrationConfigSync,
|
||||||
|
initializeEnvConfigSync,
|
||||||
|
getEnvOverrides,
|
||||||
|
getEnvOverridesOrganized
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { TEnvConfig } from "@app/lib/config/env";
|
||||||
|
|
||||||
export type TAdminSignUpDTO = {
|
export type TAdminSignUpDTO = {
|
||||||
email: string;
|
email: string;
|
||||||
password: string;
|
password: string;
|
||||||
@ -74,3 +76,10 @@ export type TAdminIntegrationConfig = {
|
|||||||
privateKey: string;
|
privateKey: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface EnvOverrides {
|
||||||
|
[key: string]: {
|
||||||
|
name: string;
|
||||||
|
fields: { key: keyof TEnvConfig; value: string; hasEnvEntry: boolean; description?: string }[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@ -71,6 +71,15 @@ export const telemetryQueueServiceFactory = ({
|
|||||||
QueueName.TelemetryInstanceStats // just a job id
|
QueueName.TelemetryInstanceStats // just a job id
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (postHog) {
|
||||||
|
await queueService.queue(QueueName.TelemetryInstanceStats, QueueJobs.TelemetryInstanceStats, undefined, {
|
||||||
|
jobId: QueueName.TelemetryInstanceStats,
|
||||||
|
repeat: { pattern: "0 0 * * *", utc: true }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const startAggregatedEventsJob = async () => {
|
||||||
// clear previous aggregated events job
|
// clear previous aggregated events job
|
||||||
await queueService.stopRepeatableJob(
|
await queueService.stopRepeatableJob(
|
||||||
QueueName.TelemetryAggregatedEvents,
|
QueueName.TelemetryAggregatedEvents,
|
||||||
@ -80,11 +89,6 @@ export const telemetryQueueServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (postHog) {
|
if (postHog) {
|
||||||
await queueService.queue(QueueName.TelemetryInstanceStats, QueueJobs.TelemetryInstanceStats, undefined, {
|
|
||||||
jobId: QueueName.TelemetryInstanceStats,
|
|
||||||
repeat: { pattern: "0 0 * * *", utc: true }
|
|
||||||
});
|
|
||||||
|
|
||||||
// Start aggregated events job (runs every five minutes)
|
// Start aggregated events job (runs every five minutes)
|
||||||
await queueService.queue(QueueName.TelemetryAggregatedEvents, QueueJobs.TelemetryAggregatedEvents, undefined, {
|
await queueService.queue(QueueName.TelemetryAggregatedEvents, QueueJobs.TelemetryAggregatedEvents, undefined, {
|
||||||
jobId: QueueName.TelemetryAggregatedEvents,
|
jobId: QueueName.TelemetryAggregatedEvents,
|
||||||
@ -102,6 +106,7 @@ export const telemetryQueueServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
startTelemetryCheck
|
startTelemetryCheck,
|
||||||
|
startAggregatedEventsJob
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -14,7 +14,7 @@ export const TELEMETRY_SECRET_PROCESSED_KEY = "telemetry-secret-processed";
|
|||||||
export const TELEMETRY_SECRET_OPERATIONS_KEY = "telemetry-secret-operations";
|
export const TELEMETRY_SECRET_OPERATIONS_KEY = "telemetry-secret-operations";
|
||||||
|
|
||||||
export const POSTHOG_AGGREGATED_EVENTS = [PostHogEventTypes.SecretPulled];
|
export const POSTHOG_AGGREGATED_EVENTS = [PostHogEventTypes.SecretPulled];
|
||||||
const TELEMETRY_AGGREGATED_KEY_EXP = 900; // 15mins
|
const TELEMETRY_AGGREGATED_KEY_EXP = 600; // 10mins
|
||||||
|
|
||||||
// Bucket configuration
|
// Bucket configuration
|
||||||
const TELEMETRY_BUCKET_COUNT = 30;
|
const TELEMETRY_BUCKET_COUNT = 30;
|
||||||
@ -102,13 +102,6 @@ To opt into telemetry, you can set "TELEMETRY_ENABLED=true" within the environme
|
|||||||
const instanceType = licenseService.getInstanceType();
|
const instanceType = licenseService.getInstanceType();
|
||||||
// capture posthog only when its cloud or signup event happens in self-hosted
|
// capture posthog only when its cloud or signup event happens in self-hosted
|
||||||
if (instanceType === InstanceType.Cloud || event.event === PostHogEventTypes.UserSignedUp) {
|
if (instanceType === InstanceType.Cloud || event.event === PostHogEventTypes.UserSignedUp) {
|
||||||
if (event.organizationId) {
|
|
||||||
try {
|
|
||||||
postHog.groupIdentify({ groupType: "organization", groupKey: event.organizationId });
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(error, "Failed to identify PostHog organization");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (POSTHOG_AGGREGATED_EVENTS.includes(event.event)) {
|
if (POSTHOG_AGGREGATED_EVENTS.includes(event.event)) {
|
||||||
const eventKey = createTelemetryEventKey(event.event, event.distinctId);
|
const eventKey = createTelemetryEventKey(event.event, event.distinctId);
|
||||||
await keyStore.setItemWithExpiry(
|
await keyStore.setItemWithExpiry(
|
||||||
@ -122,6 +115,13 @@ To opt into telemetry, you can set "TELEMETRY_ENABLED=true" within the environme
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
if (event.organizationId) {
|
||||||
|
try {
|
||||||
|
postHog.groupIdentify({ groupType: "organization", groupKey: event.organizationId });
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(error, "Failed to identify PostHog organization");
|
||||||
|
}
|
||||||
|
}
|
||||||
postHog.capture({
|
postHog.capture({
|
||||||
event: event.event,
|
event: event.event,
|
||||||
distinctId: event.distinctId,
|
distinctId: event.distinctId,
|
||||||
|
@ -35,6 +35,7 @@ const (
|
|||||||
GitHubPlatform
|
GitHubPlatform
|
||||||
GitLabPlatform
|
GitLabPlatform
|
||||||
AzureDevOpsPlatform
|
AzureDevOpsPlatform
|
||||||
|
BitBucketPlatform
|
||||||
// TODO: Add others.
|
// TODO: Add others.
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -45,6 +46,7 @@ func (p Platform) String() string {
|
|||||||
"github",
|
"github",
|
||||||
"gitlab",
|
"gitlab",
|
||||||
"azuredevops",
|
"azuredevops",
|
||||||
|
"bitbucket",
|
||||||
}[p]
|
}[p]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,6 +62,8 @@ func PlatformFromString(s string) (Platform, error) {
|
|||||||
return GitLabPlatform, nil
|
return GitLabPlatform, nil
|
||||||
case "azuredevops":
|
case "azuredevops":
|
||||||
return AzureDevOpsPlatform, nil
|
return AzureDevOpsPlatform, nil
|
||||||
|
case "bitbucket":
|
||||||
|
return BitBucketPlatform, nil
|
||||||
default:
|
default:
|
||||||
return UnknownPlatform, fmt.Errorf("invalid scm platform value: %s", s)
|
return UnknownPlatform, fmt.Errorf("invalid scm platform value: %s", s)
|
||||||
}
|
}
|
||||||
|
@ -208,6 +208,8 @@ func platformFromHost(u *url.URL) scm.Platform {
|
|||||||
return scm.GitLabPlatform
|
return scm.GitLabPlatform
|
||||||
case "dev.azure.com", "visualstudio.com":
|
case "dev.azure.com", "visualstudio.com":
|
||||||
return scm.AzureDevOpsPlatform
|
return scm.AzureDevOpsPlatform
|
||||||
|
case "bitbucket.org":
|
||||||
|
return scm.BitBucketPlatform
|
||||||
default:
|
default:
|
||||||
return scm.UnknownPlatform
|
return scm.UnknownPlatform
|
||||||
}
|
}
|
||||||
|
@ -112,6 +112,15 @@ func createScmLink(scmPlatform scm.Platform, remoteUrl string, finding report.Fi
|
|||||||
// This is a bit dirty, but Azure DevOps does not highlight the line when the lineStartColumn and lineEndColumn are not provided
|
// This is a bit dirty, but Azure DevOps does not highlight the line when the lineStartColumn and lineEndColumn are not provided
|
||||||
link += "&lineStartColumn=1&lineEndColumn=10000000&type=2&lineStyle=plain&_a=files"
|
link += "&lineStartColumn=1&lineEndColumn=10000000&type=2&lineStyle=plain&_a=files"
|
||||||
return link
|
return link
|
||||||
|
case scm.BitBucketPlatform:
|
||||||
|
link := fmt.Sprintf("%s/src/%s/%s", remoteUrl, finding.Commit, filePath)
|
||||||
|
if finding.StartLine != 0 {
|
||||||
|
link += fmt.Sprintf("#lines-%d", finding.StartLine)
|
||||||
|
}
|
||||||
|
if finding.EndLine != finding.StartLine {
|
||||||
|
link += fmt.Sprintf(":%d", finding.EndLine)
|
||||||
|
}
|
||||||
|
return link
|
||||||
default:
|
default:
|
||||||
// This should never happen.
|
// This should never happen.
|
||||||
return ""
|
return ""
|
||||||
|
@ -337,9 +337,7 @@ var scanCmd = &cobra.Command{
|
|||||||
if gitCmd, err = sources.NewGitLogCmd(source, logOpts); err != nil {
|
if gitCmd, err = sources.NewGitLogCmd(source, logOpts); err != nil {
|
||||||
logging.Fatal().Err(err).Msg("could not create Git cmd")
|
logging.Fatal().Err(err).Msg("could not create Git cmd")
|
||||||
}
|
}
|
||||||
if scmPlatform, err = scm.PlatformFromString("github"); err != nil {
|
scmPlatform = scm.UnknownPlatform
|
||||||
logging.Fatal().Err(err).Send()
|
|
||||||
}
|
|
||||||
remote = detect.NewRemoteInfo(scmPlatform, source)
|
remote = detect.NewRemoteInfo(scmPlatform, source)
|
||||||
|
|
||||||
if findings, err = detector.DetectGit(gitCmd, remote); err != nil {
|
if findings, err = detector.DetectGit(gitCmd, remote); err != nil {
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Create LDAP SSO Config"
|
||||||
|
openapi: "POST /api/v1/ldap/config"
|
||||||
|
---
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get LDAP SSO Config"
|
||||||
|
openapi: "GET /api/v1/ldap/config"
|
||||||
|
---
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Update LDAP SSO Config"
|
||||||
|
openapi: "PATCH /api/v1/ldap/config"
|
||||||
|
---
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Create OIDC Config"
|
||||||
|
openapi: "POST /api/v1/sso/oidc/config"
|
||||||
|
---
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get OIDC Config"
|
||||||
|
openapi: "GET /api/v1/sso/oidc/config"
|
||||||
|
---
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Update OIDC Config"
|
||||||
|
openapi: "PATCH /api/v1/sso/oidc/config"
|
||||||
|
---
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Create SAML SSO Config"
|
||||||
|
openapi: "POST /api/v1/sso/config"
|
||||||
|
---
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get SAML SSO Config"
|
||||||
|
openapi: "GET /api/v1/sso/config"
|
||||||
|
---
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Update SAML SSO Config"
|
||||||
|
openapi: "PATCH /api/v1/sso/config"
|
||||||
|
---
|
@ -851,6 +851,30 @@
|
|||||||
{
|
{
|
||||||
"group": "Organizations",
|
"group": "Organizations",
|
||||||
"pages": [
|
"pages": [
|
||||||
|
{
|
||||||
|
"group": "OIDC SSO",
|
||||||
|
"pages": [
|
||||||
|
"api-reference/endpoints/organizations/oidc-sso/get-oidc-config",
|
||||||
|
"api-reference/endpoints/organizations/oidc-sso/update-oidc-config",
|
||||||
|
"api-reference/endpoints/organizations/oidc-sso/create-oidc-config"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": "LDAP SSO",
|
||||||
|
"pages": [
|
||||||
|
"api-reference/endpoints/organizations/ldap-sso/get-ldap-config",
|
||||||
|
"api-reference/endpoints/organizations/ldap-sso/update-ldap-config",
|
||||||
|
"api-reference/endpoints/organizations/ldap-sso/create-ldap-config"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": "SAML SSO",
|
||||||
|
"pages": [
|
||||||
|
"api-reference/endpoints/organizations/saml-sso/get-saml-config",
|
||||||
|
"api-reference/endpoints/organizations/saml-sso/update-saml-config",
|
||||||
|
"api-reference/endpoints/organizations/saml-sso/create-saml-config"
|
||||||
|
]
|
||||||
|
},
|
||||||
"api-reference/endpoints/organizations/memberships",
|
"api-reference/endpoints/organizations/memberships",
|
||||||
"api-reference/endpoints/organizations/update-membership",
|
"api-reference/endpoints/organizations/update-membership",
|
||||||
"api-reference/endpoints/organizations/delete-membership",
|
"api-reference/endpoints/organizations/delete-membership",
|
||||||
|
BIN
docs/images/self-hosting/configuration/overrides/page.png
Normal file
BIN
docs/images/self-hosting/configuration/overrides/page.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 868 KiB |
@ -794,3 +794,9 @@ 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
|
The TLS header used to propagate the client certificate from the load balancer
|
||||||
to the server.
|
to the server.
|
||||||
</ParamField>
|
</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).
|
||||||
|
|
||||||
|

|
||||||
|
@ -33,7 +33,7 @@ export const ContentLoader = ({ text, frequency = 2000, className }: Props) => {
|
|||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Lottie icon="infisical_loading" className="h-32 w-32" />
|
<Lottie isAutoPlay icon="infisical_loading" className="h-32 w-32" />
|
||||||
{text && isTextArray && (
|
{text && isTextArray && (
|
||||||
<AnimatePresence mode="wait">
|
<AnimatePresence mode="wait">
|
||||||
<motion.div
|
<motion.div
|
||||||
|
42
frontend/src/components/v2/HighlightText/HighlightText.tsx
Normal file
42
frontend/src/components/v2/HighlightText/HighlightText.tsx
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
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
frontend/src/components/v2/HighlightText/index.tsx
Normal file
1
frontend/src/components/v2/HighlightText/index.tsx
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { HighlightText } from "./HighlightText";
|
@ -43,6 +43,7 @@ export const Tooltip = ({
|
|||||||
onOpenChange={onOpenChange}
|
onOpenChange={onOpenChange}
|
||||||
>
|
>
|
||||||
<TooltipPrimitive.Trigger asChild={asChild}>{children}</TooltipPrimitive.Trigger>
|
<TooltipPrimitive.Trigger asChild={asChild}>{children}</TooltipPrimitive.Trigger>
|
||||||
|
<TooltipPrimitive.Portal>
|
||||||
<TooltipPrimitive.Content
|
<TooltipPrimitive.Content
|
||||||
side={position}
|
side={position}
|
||||||
align="center"
|
align="center"
|
||||||
@ -60,6 +61,7 @@ export const Tooltip = ({
|
|||||||
{content}
|
{content}
|
||||||
<TooltipPrimitive.Arrow width={11} height={5} className="fill-mineshaft-600" />
|
<TooltipPrimitive.Arrow width={11} height={5} className="fill-mineshaft-600" />
|
||||||
</TooltipPrimitive.Content>
|
</TooltipPrimitive.Content>
|
||||||
|
</TooltipPrimitive.Portal>
|
||||||
</TooltipPrimitive.Root>
|
</TooltipPrimitive.Root>
|
||||||
) : (
|
) : (
|
||||||
// eslint-disable-next-line react/jsx-no-useless-fragment
|
// eslint-disable-next-line react/jsx-no-useless-fragment
|
||||||
|
@ -10,6 +10,7 @@ import {
|
|||||||
AdminGetUsersFilters,
|
AdminGetUsersFilters,
|
||||||
AdminIntegrationsConfig,
|
AdminIntegrationsConfig,
|
||||||
OrganizationWithProjects,
|
OrganizationWithProjects,
|
||||||
|
TGetEnvOverrides,
|
||||||
TGetInvalidatingCacheStatus,
|
TGetInvalidatingCacheStatus,
|
||||||
TGetServerRootKmsEncryptionDetails,
|
TGetServerRootKmsEncryptionDetails,
|
||||||
TServerConfig
|
TServerConfig
|
||||||
@ -31,7 +32,8 @@ export const adminQueryKeys = {
|
|||||||
getAdminSlackConfig: () => ["admin-slack-config"] as const,
|
getAdminSlackConfig: () => ["admin-slack-config"] as const,
|
||||||
getServerEncryptionStrategies: () => ["server-encryption-strategies"] as const,
|
getServerEncryptionStrategies: () => ["server-encryption-strategies"] as const,
|
||||||
getInvalidateCache: () => ["admin-invalidate-cache"] as const,
|
getInvalidateCache: () => ["admin-invalidate-cache"] as const,
|
||||||
getAdminIntegrationsConfig: () => ["admin-integrations-config"] as const
|
getAdminIntegrationsConfig: () => ["admin-integrations-config"] as const,
|
||||||
|
getEnvOverrides: () => ["env-overrides"] as const
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchServerConfig = async () => {
|
export const fetchServerConfig = async () => {
|
||||||
@ -163,3 +165,13 @@ export const useGetInvalidatingCacheStatus = (enabled = true) => {
|
|||||||
refetchInterval: (data) => (data ? 3000 : false)
|
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,6 +48,7 @@ export type TServerConfig = {
|
|||||||
authConsentContent?: string;
|
authConsentContent?: string;
|
||||||
pageFrameContent?: string;
|
pageFrameContent?: string;
|
||||||
invalidatingCache: boolean;
|
invalidatingCache: boolean;
|
||||||
|
envOverrides?: Record<string, string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TUpdateServerConfigDTO = {
|
export type TUpdateServerConfigDTO = {
|
||||||
@ -61,6 +62,7 @@ export type TUpdateServerConfigDTO = {
|
|||||||
gitHubAppConnectionSlug?: string;
|
gitHubAppConnectionSlug?: string;
|
||||||
gitHubAppConnectionId?: string;
|
gitHubAppConnectionId?: string;
|
||||||
gitHubAppConnectionPrivateKey?: string;
|
gitHubAppConnectionPrivateKey?: string;
|
||||||
|
envOverrides?: Record<string, string>;
|
||||||
} & Partial<TServerConfig>;
|
} & Partial<TServerConfig>;
|
||||||
|
|
||||||
export type TCreateAdminUserDTO = {
|
export type TCreateAdminUserDTO = {
|
||||||
@ -138,3 +140,10 @@ export type TInvalidateCacheDTO = {
|
|||||||
export type TGetInvalidatingCacheStatus = {
|
export type TGetInvalidatingCacheStatus = {
|
||||||
invalidating: boolean;
|
invalidating: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface TGetEnvOverrides {
|
||||||
|
[key: string]: {
|
||||||
|
name: string;
|
||||||
|
fields: { key: string; value: string; hasEnvEntry: boolean; description?: string }[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@ -21,7 +21,7 @@ export const useUpdateOIDCConfig = () => {
|
|||||||
clientId,
|
clientId,
|
||||||
clientSecret,
|
clientSecret,
|
||||||
isActive,
|
isActive,
|
||||||
orgSlug,
|
organizationId,
|
||||||
manageGroupMemberships,
|
manageGroupMemberships,
|
||||||
jwtSignatureAlgorithm
|
jwtSignatureAlgorithm
|
||||||
}: {
|
}: {
|
||||||
@ -36,7 +36,7 @@ export const useUpdateOIDCConfig = () => {
|
|||||||
clientSecret?: string;
|
clientSecret?: string;
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
configurationType?: string;
|
configurationType?: string;
|
||||||
orgSlug: string;
|
organizationId: string;
|
||||||
manageGroupMemberships?: boolean;
|
manageGroupMemberships?: boolean;
|
||||||
jwtSignatureAlgorithm?: OIDCJWTSignatureAlgorithm;
|
jwtSignatureAlgorithm?: OIDCJWTSignatureAlgorithm;
|
||||||
}) => {
|
}) => {
|
||||||
@ -50,7 +50,7 @@ export const useUpdateOIDCConfig = () => {
|
|||||||
tokenEndpoint,
|
tokenEndpoint,
|
||||||
userinfoEndpoint,
|
userinfoEndpoint,
|
||||||
clientId,
|
clientId,
|
||||||
orgSlug,
|
organizationId,
|
||||||
clientSecret,
|
clientSecret,
|
||||||
isActive,
|
isActive,
|
||||||
manageGroupMemberships,
|
manageGroupMemberships,
|
||||||
@ -60,7 +60,7 @@ export const useUpdateOIDCConfig = () => {
|
|||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
onSuccess(_, dto) {
|
onSuccess(_, dto) {
|
||||||
queryClient.invalidateQueries({ queryKey: oidcConfigKeys.getOIDCConfig(dto.orgSlug) });
|
queryClient.invalidateQueries({ queryKey: oidcConfigKeys.getOIDCConfig(dto.organizationId) });
|
||||||
queryClient.invalidateQueries({ queryKey: organizationKeys.getUserOrganizations });
|
queryClient.invalidateQueries({ queryKey: organizationKeys.getUserOrganizations });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -81,7 +81,7 @@ export const useCreateOIDCConfig = () => {
|
|||||||
clientId,
|
clientId,
|
||||||
clientSecret,
|
clientSecret,
|
||||||
isActive,
|
isActive,
|
||||||
orgSlug,
|
organizationId,
|
||||||
manageGroupMemberships,
|
manageGroupMemberships,
|
||||||
jwtSignatureAlgorithm
|
jwtSignatureAlgorithm
|
||||||
}: {
|
}: {
|
||||||
@ -95,7 +95,7 @@ export const useCreateOIDCConfig = () => {
|
|||||||
clientId: string;
|
clientId: string;
|
||||||
clientSecret: string;
|
clientSecret: string;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
orgSlug: string;
|
organizationId: string;
|
||||||
allowedEmailDomains?: string;
|
allowedEmailDomains?: string;
|
||||||
manageGroupMemberships?: boolean;
|
manageGroupMemberships?: boolean;
|
||||||
jwtSignatureAlgorithm?: OIDCJWTSignatureAlgorithm;
|
jwtSignatureAlgorithm?: OIDCJWTSignatureAlgorithm;
|
||||||
@ -112,7 +112,7 @@ export const useCreateOIDCConfig = () => {
|
|||||||
clientId,
|
clientId,
|
||||||
clientSecret,
|
clientSecret,
|
||||||
isActive,
|
isActive,
|
||||||
orgSlug,
|
organizationId,
|
||||||
manageGroupMemberships,
|
manageGroupMemberships,
|
||||||
jwtSignatureAlgorithm
|
jwtSignatureAlgorithm
|
||||||
});
|
});
|
||||||
@ -120,7 +120,7 @@ export const useCreateOIDCConfig = () => {
|
|||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
onSuccess(_, dto) {
|
onSuccess(_, dto) {
|
||||||
queryClient.invalidateQueries({ queryKey: oidcConfigKeys.getOIDCConfig(dto.orgSlug) });
|
queryClient.invalidateQueries({ queryKey: oidcConfigKeys.getOIDCConfig(dto.organizationId) });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -5,18 +5,18 @@ import { apiRequest } from "@app/config/request";
|
|||||||
import { OIDCConfigData } from "./types";
|
import { OIDCConfigData } from "./types";
|
||||||
|
|
||||||
export const oidcConfigKeys = {
|
export const oidcConfigKeys = {
|
||||||
getOIDCConfig: (orgSlug: string) => [{ orgSlug }, "organization-oidc"] as const,
|
getOIDCConfig: (orgId: string) => [{ orgId }, "organization-oidc"] as const,
|
||||||
getOIDCManageGroupMembershipsEnabled: (orgId: string) =>
|
getOIDCManageGroupMembershipsEnabled: (orgId: string) =>
|
||||||
["oidc-manage-group-memberships", orgId] as const
|
["oidc-manage-group-memberships", orgId] as const
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useGetOIDCConfig = (orgSlug: string) => {
|
export const useGetOIDCConfig = (orgId: string) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: oidcConfigKeys.getOIDCConfig(orgSlug),
|
queryKey: oidcConfigKeys.getOIDCConfig(orgId),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
try {
|
try {
|
||||||
const { data } = await apiRequest.get<OIDCConfigData>(
|
const { data } = await apiRequest.get<OIDCConfigData>(
|
||||||
`/api/v1/sso/oidc/config?orgSlug=${orgSlug}`
|
`/api/v1/sso/oidc/config?organizationId=${orgId}`
|
||||||
);
|
);
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
|
@ -29,6 +29,11 @@ const generalTabs = [
|
|||||||
label: "Caching",
|
label: "Caching",
|
||||||
icon: "note",
|
icon: "note",
|
||||||
link: "/admin/caching"
|
link: "/admin/caching"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Environment Variables",
|
||||||
|
icon: "unlock",
|
||||||
|
link: "/admin/environment"
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -113,7 +113,7 @@ export const ProjectSelect = () => {
|
|||||||
<div>
|
<div>
|
||||||
<FontAwesomeIcon icon={faCube} className="text-xs" />
|
<FontAwesomeIcon icon={faCube} className="text-xs" />
|
||||||
</div>
|
</div>
|
||||||
<Tooltip content={currentWorkspace.name}>
|
<Tooltip content={currentWorkspace.name} className="max-w-96">
|
||||||
<div className="max-w-32 overflow-hidden text-ellipsis whitespace-nowrap">
|
<div className="max-w-32 overflow-hidden text-ellipsis whitespace-nowrap">
|
||||||
{currentWorkspace?.name}
|
{currentWorkspace?.name}
|
||||||
</div>
|
</div>
|
||||||
@ -176,7 +176,7 @@ export const ProjectSelect = () => {
|
|||||||
>
|
>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<div className="flex flex-1 items-center justify-between overflow-hidden">
|
<div className="flex flex-1 items-center justify-between overflow-hidden">
|
||||||
<Tooltip content={workspace.name}>
|
<Tooltip side="right" className="break-words" content={workspace.name}>
|
||||||
<div className="max-w-40 overflow-hidden truncate whitespace-nowrap">
|
<div className="max-w-40 overflow-hidden truncate whitespace-nowrap">
|
||||||
{workspace.name}
|
{workspace.name}
|
||||||
</div>
|
</div>
|
||||||
|
@ -63,7 +63,7 @@ const router = createRouter({
|
|||||||
context: { serverConfig: null, queryClient },
|
context: { serverConfig: null, queryClient },
|
||||||
defaultPendingComponent: () => (
|
defaultPendingComponent: () => (
|
||||||
<div className="flex h-screen w-screen items-center justify-center bg-bunker-800">
|
<div className="flex h-screen w-screen items-center justify-center bg-bunker-800">
|
||||||
<Lottie icon="infisical_loading" className="h-32 w-32" />
|
<Lottie isAutoPlay icon="infisical_loading" className="h-32 w-32" />
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
defaultNotFoundComponent: NotFoundPage,
|
defaultNotFoundComponent: NotFoundPage,
|
||||||
|
27
frontend/src/pages/admin/EnvironmentPage/EnvironmentPage.tsx
Normal file
27
frontend/src/pages/admin/EnvironmentPage/EnvironmentPage.tsx
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
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>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,264 @@
|
|||||||
|
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>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1 @@
|
|||||||
|
export { EnvironmentPageForm } from "./EnvironmentPageForm";
|
25
frontend/src/pages/admin/EnvironmentPage/route.tsx
Normal file
25
frontend/src/pages/admin/EnvironmentPage/route.tsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
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"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
@ -105,7 +105,7 @@ export const OIDCModal = ({ popUp, handlePopUpClose, handlePopUpToggle, hideDele
|
|||||||
const { mutateAsync: updateMutateAsync, isPending: updateIsLoading } = useUpdateOIDCConfig();
|
const { mutateAsync: updateMutateAsync, isPending: updateIsLoading } = useUpdateOIDCConfig();
|
||||||
const [isDeletePopupOpen, setIsDeletePopupOpen] = useToggle(false);
|
const [isDeletePopupOpen, setIsDeletePopupOpen] = useToggle(false);
|
||||||
|
|
||||||
const { data } = useGetOIDCConfig(currentOrg?.slug ?? "");
|
const { data } = useGetOIDCConfig(currentOrg?.id ?? "");
|
||||||
|
|
||||||
const { control, handleSubmit, reset, setValue, watch } = useForm<OIDCFormData>({
|
const { control, handleSubmit, reset, setValue, watch } = useForm<OIDCFormData>({
|
||||||
resolver: zodResolver(schema),
|
resolver: zodResolver(schema),
|
||||||
@ -134,7 +134,7 @@ export const OIDCModal = ({ popUp, handlePopUpClose, handlePopUpToggle, hideDele
|
|||||||
clientId: "",
|
clientId: "",
|
||||||
clientSecret: "",
|
clientSecret: "",
|
||||||
isActive: false,
|
isActive: false,
|
||||||
orgSlug: currentOrg.slug
|
organizationId: currentOrg.id
|
||||||
});
|
});
|
||||||
|
|
||||||
createNotification({
|
createNotification({
|
||||||
@ -196,7 +196,7 @@ export const OIDCModal = ({ popUp, handlePopUpClose, handlePopUpToggle, hideDele
|
|||||||
clientId,
|
clientId,
|
||||||
clientSecret,
|
clientSecret,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
orgSlug: currentOrg.slug,
|
organizationId: currentOrg.id,
|
||||||
jwtSignatureAlgorithm
|
jwtSignatureAlgorithm
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -212,7 +212,7 @@ export const OIDCModal = ({ popUp, handlePopUpClose, handlePopUpToggle, hideDele
|
|||||||
clientId,
|
clientId,
|
||||||
clientSecret,
|
clientSecret,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
orgSlug: currentOrg.slug,
|
organizationId: currentOrg.id,
|
||||||
jwtSignatureAlgorithm
|
jwtSignatureAlgorithm
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ export const OrgOIDCSection = (): JSX.Element => {
|
|||||||
const { currentOrg } = useOrganization();
|
const { currentOrg } = useOrganization();
|
||||||
const { subscription } = useSubscription();
|
const { subscription } = useSubscription();
|
||||||
|
|
||||||
const { data, isPending } = useGetOIDCConfig(currentOrg?.slug ?? "");
|
const { data, isPending } = useGetOIDCConfig(currentOrg?.id ?? "");
|
||||||
const { mutateAsync } = useUpdateOIDCConfig();
|
const { mutateAsync } = useUpdateOIDCConfig();
|
||||||
const { mutateAsync: updateOrg } = useUpdateOrg();
|
const { mutateAsync: updateOrg } = useUpdateOrg();
|
||||||
|
|
||||||
@ -41,7 +41,7 @@ export const OrgOIDCSection = (): JSX.Element => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await mutateAsync({
|
await mutateAsync({
|
||||||
orgSlug: currentOrg?.slug,
|
organizationId: currentOrg?.id,
|
||||||
isActive: value
|
isActive: value
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -114,7 +114,7 @@ export const OrgOIDCSection = (): JSX.Element => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await mutateAsync({
|
await mutateAsync({
|
||||||
orgSlug: currentOrg?.slug,
|
organizationId: currentOrg?.id,
|
||||||
manageGroupMemberships: value
|
manageGroupMemberships: value
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ export const OrgSsoTab = withPermission(
|
|||||||
const { subscription } = useSubscription();
|
const { subscription } = useSubscription();
|
||||||
|
|
||||||
const { data: oidcConfig, isPending: isLoadingOidcConfig } = useGetOIDCConfig(
|
const { data: oidcConfig, isPending: isLoadingOidcConfig } = useGetOIDCConfig(
|
||||||
currentOrg?.slug ?? ""
|
currentOrg?.id ?? ""
|
||||||
);
|
);
|
||||||
const { data: samlConfig, isPending: isLoadingSamlConfig } = useGetSSOConfig(
|
const { data: samlConfig, isPending: isLoadingSamlConfig } = useGetSSOConfig(
|
||||||
currentOrg?.id ?? ""
|
currentOrg?.id ?? ""
|
||||||
|
@ -891,7 +891,7 @@ export const OverviewPage = () => {
|
|||||||
if (isProjectV3 && visibleEnvs.length > 0 && isOverviewLoading) {
|
if (isProjectV3 && visibleEnvs.length > 0 && isOverviewLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto flex h-screen w-full items-center justify-center px-8 text-mineshaft-50 dark:[color-scheme:dark]">
|
<div className="container mx-auto flex h-screen w-full items-center justify-center px-8 text-mineshaft-50 dark:[color-scheme:dark]">
|
||||||
<Lottie icon="infisical_loading" className="h-32 w-32" />
|
<Lottie isAutoPlay icon="infisical_loading" className="h-32 w-32" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -253,7 +253,7 @@ export const SecretDropzone = ({
|
|||||||
>
|
>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="mb-16 flex items-center justify-center pt-16">
|
<div className="mb-16 flex items-center justify-center pt-16">
|
||||||
<Lottie icon="infisical_loading" className="h-32 w-32" />
|
<Lottie isAutoPlay icon="infisical_loading" className="h-32 w-32" />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col items-center justify-center space-y-2">
|
<div className="flex flex-col items-center justify-center space-y-2">
|
||||||
|
@ -43,6 +43,7 @@ import { Route as authProviderSuccessPageRouteImport } from './pages/auth/Provid
|
|||||||
import { Route as authProviderErrorPageRouteImport } from './pages/auth/ProviderErrorPage/route'
|
import { Route as authProviderErrorPageRouteImport } from './pages/auth/ProviderErrorPage/route'
|
||||||
import { Route as userPersonalSettingsPageRouteImport } from './pages/user/PersonalSettingsPage/route'
|
import { Route as userPersonalSettingsPageRouteImport } from './pages/user/PersonalSettingsPage/route'
|
||||||
import { Route as adminIntegrationsPageRouteImport } from './pages/admin/IntegrationsPage/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 adminEncryptionPageRouteImport } from './pages/admin/EncryptionPage/route'
|
||||||
import { Route as adminCachingPageRouteImport } from './pages/admin/CachingPage/route'
|
import { Route as adminCachingPageRouteImport } from './pages/admin/CachingPage/route'
|
||||||
import { Route as adminAuthenticationPageRouteImport } from './pages/admin/AuthenticationPage/route'
|
import { Route as adminAuthenticationPageRouteImport } from './pages/admin/AuthenticationPage/route'
|
||||||
@ -565,6 +566,12 @@ const adminIntegrationsPageRouteRoute = adminIntegrationsPageRouteImport.update(
|
|||||||
} as any,
|
} as any,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const adminEnvironmentPageRouteRoute = adminEnvironmentPageRouteImport.update({
|
||||||
|
id: '/environment',
|
||||||
|
path: '/environment',
|
||||||
|
getParentRoute: () => adminLayoutRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
const adminEncryptionPageRouteRoute = adminEncryptionPageRouteImport.update({
|
const adminEncryptionPageRouteRoute = adminEncryptionPageRouteImport.update({
|
||||||
id: '/encryption',
|
id: '/encryption',
|
||||||
path: '/encryption',
|
path: '/encryption',
|
||||||
@ -2180,6 +2187,13 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof adminEncryptionPageRouteImport
|
preLoaderRoute: typeof adminEncryptionPageRouteImport
|
||||||
parentRoute: typeof adminLayoutImport
|
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': {
|
'/_authenticate/_inject-org-details/admin/_admin-layout/integrations': {
|
||||||
id: '/_authenticate/_inject-org-details/admin/_admin-layout/integrations'
|
id: '/_authenticate/_inject-org-details/admin/_admin-layout/integrations'
|
||||||
path: '/integrations'
|
path: '/integrations'
|
||||||
@ -4120,6 +4134,7 @@ interface adminLayoutRouteChildren {
|
|||||||
adminAuthenticationPageRouteRoute: typeof adminAuthenticationPageRouteRoute
|
adminAuthenticationPageRouteRoute: typeof adminAuthenticationPageRouteRoute
|
||||||
adminCachingPageRouteRoute: typeof adminCachingPageRouteRoute
|
adminCachingPageRouteRoute: typeof adminCachingPageRouteRoute
|
||||||
adminEncryptionPageRouteRoute: typeof adminEncryptionPageRouteRoute
|
adminEncryptionPageRouteRoute: typeof adminEncryptionPageRouteRoute
|
||||||
|
adminEnvironmentPageRouteRoute: typeof adminEnvironmentPageRouteRoute
|
||||||
adminIntegrationsPageRouteRoute: typeof adminIntegrationsPageRouteRoute
|
adminIntegrationsPageRouteRoute: typeof adminIntegrationsPageRouteRoute
|
||||||
adminMachineIdentitiesResourcesPageRouteRoute: typeof adminMachineIdentitiesResourcesPageRouteRoute
|
adminMachineIdentitiesResourcesPageRouteRoute: typeof adminMachineIdentitiesResourcesPageRouteRoute
|
||||||
adminOrganizationResourcesPageRouteRoute: typeof adminOrganizationResourcesPageRouteRoute
|
adminOrganizationResourcesPageRouteRoute: typeof adminOrganizationResourcesPageRouteRoute
|
||||||
@ -4131,6 +4146,7 @@ const adminLayoutRouteChildren: adminLayoutRouteChildren = {
|
|||||||
adminAuthenticationPageRouteRoute: adminAuthenticationPageRouteRoute,
|
adminAuthenticationPageRouteRoute: adminAuthenticationPageRouteRoute,
|
||||||
adminCachingPageRouteRoute: adminCachingPageRouteRoute,
|
adminCachingPageRouteRoute: adminCachingPageRouteRoute,
|
||||||
adminEncryptionPageRouteRoute: adminEncryptionPageRouteRoute,
|
adminEncryptionPageRouteRoute: adminEncryptionPageRouteRoute,
|
||||||
|
adminEnvironmentPageRouteRoute: adminEnvironmentPageRouteRoute,
|
||||||
adminIntegrationsPageRouteRoute: adminIntegrationsPageRouteRoute,
|
adminIntegrationsPageRouteRoute: adminIntegrationsPageRouteRoute,
|
||||||
adminMachineIdentitiesResourcesPageRouteRoute:
|
adminMachineIdentitiesResourcesPageRouteRoute:
|
||||||
adminMachineIdentitiesResourcesPageRouteRoute,
|
adminMachineIdentitiesResourcesPageRouteRoute,
|
||||||
@ -4333,6 +4349,7 @@ export interface FileRoutesByFullPath {
|
|||||||
'/admin/authentication': typeof adminAuthenticationPageRouteRoute
|
'/admin/authentication': typeof adminAuthenticationPageRouteRoute
|
||||||
'/admin/caching': typeof adminCachingPageRouteRoute
|
'/admin/caching': typeof adminCachingPageRouteRoute
|
||||||
'/admin/encryption': typeof adminEncryptionPageRouteRoute
|
'/admin/encryption': typeof adminEncryptionPageRouteRoute
|
||||||
|
'/admin/environment': typeof adminEnvironmentPageRouteRoute
|
||||||
'/admin/integrations': typeof adminIntegrationsPageRouteRoute
|
'/admin/integrations': typeof adminIntegrationsPageRouteRoute
|
||||||
'/organization/app-connections': typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRouteWithChildren
|
'/organization/app-connections': typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRouteWithChildren
|
||||||
'/organization/gateways': typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationGatewaysRouteWithChildren
|
'/organization/gateways': typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationGatewaysRouteWithChildren
|
||||||
@ -4529,6 +4546,7 @@ export interface FileRoutesByTo {
|
|||||||
'/admin/authentication': typeof adminAuthenticationPageRouteRoute
|
'/admin/authentication': typeof adminAuthenticationPageRouteRoute
|
||||||
'/admin/caching': typeof adminCachingPageRouteRoute
|
'/admin/caching': typeof adminCachingPageRouteRoute
|
||||||
'/admin/encryption': typeof adminEncryptionPageRouteRoute
|
'/admin/encryption': typeof adminEncryptionPageRouteRoute
|
||||||
|
'/admin/environment': typeof adminEnvironmentPageRouteRoute
|
||||||
'/admin/integrations': typeof adminIntegrationsPageRouteRoute
|
'/admin/integrations': typeof adminIntegrationsPageRouteRoute
|
||||||
'/projects/$projectId': typeof projectLayoutGeneralRouteWithChildren
|
'/projects/$projectId': typeof projectLayoutGeneralRouteWithChildren
|
||||||
'/secret-manager/$projectId': typeof AuthenticateInjectOrgDetailsOrgLayoutSecretManagerProjectIdRouteWithChildren
|
'/secret-manager/$projectId': typeof AuthenticateInjectOrgDetailsOrgLayoutSecretManagerProjectIdRouteWithChildren
|
||||||
@ -4725,6 +4743,7 @@ export interface FileRoutesById {
|
|||||||
'/_authenticate/_inject-org-details/admin/_admin-layout/authentication': typeof adminAuthenticationPageRouteRoute
|
'/_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/caching': typeof adminCachingPageRouteRoute
|
||||||
'/_authenticate/_inject-org-details/admin/_admin-layout/encryption': typeof adminEncryptionPageRouteRoute
|
'/_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/admin/_admin-layout/integrations': typeof adminIntegrationsPageRouteRoute
|
||||||
'/_authenticate/_inject-org-details/_org-layout/organization/app-connections': typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRouteWithChildren
|
'/_authenticate/_inject-org-details/_org-layout/organization/app-connections': typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRouteWithChildren
|
||||||
'/_authenticate/_inject-org-details/_org-layout/organization/gateways': typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationGatewaysRouteWithChildren
|
'/_authenticate/_inject-org-details/_org-layout/organization/gateways': typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationGatewaysRouteWithChildren
|
||||||
@ -4934,6 +4953,7 @@ export interface FileRouteTypes {
|
|||||||
| '/admin/authentication'
|
| '/admin/authentication'
|
||||||
| '/admin/caching'
|
| '/admin/caching'
|
||||||
| '/admin/encryption'
|
| '/admin/encryption'
|
||||||
|
| '/admin/environment'
|
||||||
| '/admin/integrations'
|
| '/admin/integrations'
|
||||||
| '/organization/app-connections'
|
| '/organization/app-connections'
|
||||||
| '/organization/gateways'
|
| '/organization/gateways'
|
||||||
@ -5129,6 +5149,7 @@ export interface FileRouteTypes {
|
|||||||
| '/admin/authentication'
|
| '/admin/authentication'
|
||||||
| '/admin/caching'
|
| '/admin/caching'
|
||||||
| '/admin/encryption'
|
| '/admin/encryption'
|
||||||
|
| '/admin/environment'
|
||||||
| '/admin/integrations'
|
| '/admin/integrations'
|
||||||
| '/projects/$projectId'
|
| '/projects/$projectId'
|
||||||
| '/secret-manager/$projectId'
|
| '/secret-manager/$projectId'
|
||||||
@ -5323,6 +5344,7 @@ export interface FileRouteTypes {
|
|||||||
| '/_authenticate/_inject-org-details/admin/_admin-layout/authentication'
|
| '/_authenticate/_inject-org-details/admin/_admin-layout/authentication'
|
||||||
| '/_authenticate/_inject-org-details/admin/_admin-layout/caching'
|
| '/_authenticate/_inject-org-details/admin/_admin-layout/caching'
|
||||||
| '/_authenticate/_inject-org-details/admin/_admin-layout/encryption'
|
| '/_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/integrations'
|
||||||
| '/_authenticate/_inject-org-details/_org-layout/organization/app-connections'
|
| '/_authenticate/_inject-org-details/_org-layout/organization/app-connections'
|
||||||
| '/_authenticate/_inject-org-details/_org-layout/organization/gateways'
|
| '/_authenticate/_inject-org-details/_org-layout/organization/gateways'
|
||||||
@ -5729,6 +5751,7 @@ export const routeTree = rootRoute
|
|||||||
"/_authenticate/_inject-org-details/admin/_admin-layout/authentication",
|
"/_authenticate/_inject-org-details/admin/_admin-layout/authentication",
|
||||||
"/_authenticate/_inject-org-details/admin/_admin-layout/caching",
|
"/_authenticate/_inject-org-details/admin/_admin-layout/caching",
|
||||||
"/_authenticate/_inject-org-details/admin/_admin-layout/encryption",
|
"/_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/integrations",
|
||||||
"/_authenticate/_inject-org-details/admin/_admin-layout/resources/machine-identities",
|
"/_authenticate/_inject-org-details/admin/_admin-layout/resources/machine-identities",
|
||||||
"/_authenticate/_inject-org-details/admin/_admin-layout/resources/organizations",
|
"/_authenticate/_inject-org-details/admin/_admin-layout/resources/organizations",
|
||||||
@ -5775,6 +5798,10 @@ export const routeTree = rootRoute
|
|||||||
"filePath": "admin/EncryptionPage/route.tsx",
|
"filePath": "admin/EncryptionPage/route.tsx",
|
||||||
"parent": "/_authenticate/_inject-org-details/admin/_admin-layout"
|
"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": {
|
"/_authenticate/_inject-org-details/admin/_admin-layout/integrations": {
|
||||||
"filePath": "admin/IntegrationsPage/route.tsx",
|
"filePath": "admin/IntegrationsPage/route.tsx",
|
||||||
"parent": "/_authenticate/_inject-org-details/admin/_admin-layout"
|
"parent": "/_authenticate/_inject-org-details/admin/_admin-layout"
|
||||||
|
@ -8,6 +8,7 @@ const adminRoute = route("/admin", [
|
|||||||
index("admin/GeneralPage/route.tsx"),
|
index("admin/GeneralPage/route.tsx"),
|
||||||
route("/encryption", "admin/EncryptionPage/route.tsx"),
|
route("/encryption", "admin/EncryptionPage/route.tsx"),
|
||||||
route("/authentication", "admin/AuthenticationPage/route.tsx"),
|
route("/authentication", "admin/AuthenticationPage/route.tsx"),
|
||||||
|
route("/environment", "admin/EnvironmentPage/route.tsx"),
|
||||||
route("/integrations", "admin/IntegrationsPage/route.tsx"),
|
route("/integrations", "admin/IntegrationsPage/route.tsx"),
|
||||||
route("/caching", "admin/CachingPage/route.tsx"),
|
route("/caching", "admin/CachingPage/route.tsx"),
|
||||||
route("/resources/organizations", "admin/OrganizationResourcesPage/route.tsx"),
|
route("/resources/organizations", "admin/OrganizationResourcesPage/route.tsx"),
|
||||||
|
Reference in New Issue
Block a user