merge conflicts

This commit is contained in:
x
2025-05-01 10:48:33 -04:00
129 changed files with 2759 additions and 177 deletions
@@ -5,6 +5,7 @@ export enum AppConnection {
GCP = "gcp",
AzureKeyVault = "azure-key-vault",
AzureAppConfiguration = "azure-app-configuration",
AzureClientSecrets = "azure-client-secrets",
Humanitec = "humanitec",
TerraformCloud = "terraform-cloud",
Vercel = "vercel",
@@ -23,6 +23,11 @@ import {
getAzureAppConfigurationConnectionListItem,
validateAzureAppConfigurationConnectionCredentials
} from "./azure-app-configuration";
import {
AzureClientSecretsConnectionMethod,
getAzureClientSecretsConnectionListItem,
validateAzureClientSecretsConnectionCredentials
} from "./azure-client-secrets";
import {
AzureKeyVaultConnectionMethod,
getAzureKeyVaultConnectionListItem,
@@ -81,6 +86,7 @@ export const listAppConnectionOptions = () => {
getPostgresConnectionListItem(),
getMsSqlConnectionListItem(),
getCamundaConnectionListItem(),
getAzureClientSecretsConnectionListItem(),
getWindmillConnectionListItem(),
getAuth0ConnectionListItem(),
getHCVaultConnectionListItem(),
@@ -142,6 +148,8 @@ export const validateAppConnectionCredentials = async (
[AppConnection.AzureKeyVault]: validateAzureKeyVaultConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.AzureAppConfiguration]:
validateAzureAppConfigurationConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.AzureClientSecrets]:
validateAzureClientSecretsConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.Humanitec]: validateHumanitecConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.Postgres]: validateSqlConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.MsSql]: validateSqlConnectionCredentials as TAppConnectionCredentialsValidator,
@@ -164,6 +172,7 @@ export const getAppConnectionMethodName = (method: TAppConnection["method"]) =>
return "GitHub App";
case AzureKeyVaultConnectionMethod.OAuth:
case AzureAppConfigurationConnectionMethod.OAuth:
case AzureClientSecretsConnectionMethod.OAuth:
case GitHubConnectionMethod.OAuth:
return "OAuth";
case AwsConnectionMethod.AccessKey:
@@ -236,6 +245,7 @@ export const TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM: Record<
[AppConnection.TerraformCloud]: platformManagedCredentialsNotSupported,
[AppConnection.Camunda]: platformManagedCredentialsNotSupported,
[AppConnection.Vercel]: platformManagedCredentialsNotSupported,
[AppConnection.AzureClientSecrets]: platformManagedCredentialsNotSupported,
[AppConnection.Windmill]: platformManagedCredentialsNotSupported,
[AppConnection.Auth0]: platformManagedCredentialsNotSupported,
[AppConnection.HCVault]: platformManagedCredentialsNotSupported,
@@ -6,6 +6,7 @@ export const APP_CONNECTION_NAME_MAP: Record<AppConnection, string> = {
[AppConnection.GCP]: "GCP",
[AppConnection.AzureKeyVault]: "Azure Key Vault",
[AppConnection.AzureAppConfiguration]: "Azure App Configuration",
[AppConnection.AzureClientSecrets]: "Azure Client Secrets",
[AppConnection.Databricks]: "Databricks",
[AppConnection.Humanitec]: "Humanitec",
[AppConnection.TerraformCloud]: "Terraform Cloud",
@@ -32,6 +32,8 @@ import { ValidateAuth0ConnectionCredentialsSchema } from "./auth0";
import { ValidateAwsConnectionCredentialsSchema } from "./aws";
import { awsConnectionService } from "./aws/aws-connection-service";
import { ValidateAzureAppConfigurationConnectionCredentialsSchema } from "./azure-app-configuration";
import { ValidateAzureClientSecretsConnectionCredentialsSchema } from "./azure-client-secrets";
import { azureClientSecretsConnectionService } from "./azure-client-secrets/azure-client-secrets-service";
import { ValidateAzureKeyVaultConnectionCredentialsSchema } from "./azure-key-vault";
import { ValidateCamundaConnectionCredentialsSchema } from "./camunda";
import { camundaConnectionService } from "./camunda/camunda-connection-service";
@@ -78,6 +80,7 @@ const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record<AppConnection, TValidateAp
[AppConnection.Postgres]: ValidatePostgresConnectionCredentialsSchema,
[AppConnection.MsSql]: ValidateMsSqlConnectionCredentialsSchema,
[AppConnection.Camunda]: ValidateCamundaConnectionCredentialsSchema,
[AppConnection.AzureClientSecrets]: ValidateAzureClientSecretsConnectionCredentialsSchema,
[AppConnection.Windmill]: ValidateWindmillConnectionCredentialsSchema,
[AppConnection.Auth0]: ValidateAuth0ConnectionCredentialsSchema,
[AppConnection.HCVault]: ValidateHCVaultConnectionCredentialsSchema,
@@ -457,9 +460,10 @@ export const appConnectionServiceFactory = ({
terraformCloud: terraformCloudConnectionService(connectAppConnectionById),
camunda: camundaConnectionService(connectAppConnectionById, appConnectionDAL, kmsService),
vercel: vercelConnectionService(connectAppConnectionById),
windmill: windmillConnectionService(connectAppConnectionById),
azureClientSecrets: azureClientSecretsConnectionService(connectAppConnectionById, appConnectionDAL, kmsService),
auth0: auth0ConnectionService(connectAppConnectionById, appConnectionDAL, kmsService),
hcvault: hcVaultConnectionService(connectAppConnectionById),
windmill: windmillConnectionService(connectAppConnectionById),
teamcity: teamcityConnectionService(connectAppConnectionById)
};
};
@@ -21,6 +21,12 @@ import {
TAzureAppConfigurationConnectionInput,
TValidateAzureAppConfigurationConnectionCredentialsSchema
} from "./azure-app-configuration";
import {
TAzureClientSecretsConnection,
TAzureClientSecretsConnectionConfig,
TAzureClientSecretsConnectionInput,
TValidateAzureClientSecretsConnectionCredentialsSchema
} from "./azure-client-secrets";
import {
TAzureKeyVaultConnection,
TAzureKeyVaultConnectionConfig,
@@ -113,6 +119,7 @@ export type TAppConnection = { id: string } & (
| TPostgresConnection
| TMsSqlConnection
| TCamundaConnection
| TAzureClientSecretsConnection
| TWindmillConnection
| TAuth0Connection
| THCVaultConnection
@@ -137,6 +144,7 @@ export type TAppConnectionInput = { id: string } & (
| TPostgresConnectionInput
| TMsSqlConnectionInput
| TCamundaConnectionInput
| TAzureClientSecretsConnectionInput
| TWindmillConnectionInput
| TAuth0ConnectionInput
| THCVaultConnectionInput
@@ -161,12 +169,13 @@ export type TAppConnectionConfig =
| TGcpConnectionConfig
| TAzureKeyVaultConnectionConfig
| TAzureAppConfigurationConnectionConfig
| TAzureClientSecretsConnectionConfig
| TDatabricksConnectionConfig
| THumanitecConnectionConfig
| TTerraformCloudConnectionConfig
| TVercelConnectionConfig
| TSqlConnectionConfig
| TCamundaConnectionConfig
| TVercelConnectionConfig
| TWindmillConnectionConfig
| TAuth0ConnectionConfig
| THCVaultConnectionConfig
@@ -179,13 +188,14 @@ export type TValidateAppConnectionCredentialsSchema =
| TValidateGcpConnectionCredentialsSchema
| TValidateAzureKeyVaultConnectionCredentialsSchema
| TValidateAzureAppConfigurationConnectionCredentialsSchema
| TValidateAzureClientSecretsConnectionCredentialsSchema
| TValidateDatabricksConnectionCredentialsSchema
| TValidateHumanitecConnectionCredentialsSchema
| TValidatePostgresConnectionCredentialsSchema
| TValidateMsSqlConnectionCredentialsSchema
| TValidateCamundaConnectionCredentialsSchema
| TValidateTerraformCloudConnectionCredentialsSchema
| TValidateVercelConnectionCredentialsSchema
| TValidateTerraformCloudConnectionCredentialsSchema
| TValidateWindmillConnectionCredentialsSchema
| TValidateAuth0ConnectionCredentialsSchema
| TValidateHCVaultConnectionCredentialsSchema
@@ -0,0 +1,3 @@
export enum AzureClientSecretsConnectionMethod {
OAuth = "oauth"
}
@@ -0,0 +1,169 @@
import { AxiosError, AxiosResponse } from "axios";
import { getConfig } from "@app/lib/config/env";
import { request } from "@app/lib/config/request";
import { BadRequestError, InternalServerError, NotFoundError } from "@app/lib/errors";
import {
decryptAppConnectionCredentials,
encryptAppConnectionCredentials,
getAppConnectionMethodName
} from "@app/services/app-connection/app-connection-fns";
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { TAppConnectionDALFactory } from "../app-connection-dal";
import { AppConnection } from "../app-connection-enums";
import { AzureClientSecretsConnectionMethod } from "./azure-client-secrets-connection-enums";
import {
ExchangeCodeAzureResponse,
TAzureClientSecretsConnectionConfig,
TAzureClientSecretsConnectionCredentials
} from "./azure-client-secrets-connection-types";
export const getAzureClientSecretsConnectionListItem = () => {
const { INF_APP_CONNECTION_AZURE_CLIENT_ID } = getConfig();
return {
name: "Azure Client Secrets" as const,
app: AppConnection.AzureClientSecrets as const,
methods: Object.values(AzureClientSecretsConnectionMethod) as [AzureClientSecretsConnectionMethod.OAuth],
oauthClientId: INF_APP_CONNECTION_AZURE_CLIENT_ID
};
};
export const getAzureConnectionAccessToken = async (
connectionId: string,
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById" | "updateById">,
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
) => {
const appCfg = getConfig();
if (!appCfg.INF_APP_CONNECTION_AZURE_CLIENT_ID || !appCfg.INF_APP_CONNECTION_AZURE_CLIENT_SECRET) {
throw new BadRequestError({
message: `Azure environment variables have not been configured`
});
}
const appConnection = await appConnectionDAL.findById(connectionId);
if (!appConnection) {
throw new NotFoundError({ message: `Connection with ID '${connectionId}' not found` });
}
if (appConnection.app !== AppConnection.AzureClientSecrets) {
throw new BadRequestError({
message: `Connection with ID '${connectionId}' is not an Azure Client Secrets connection`
});
}
const credentials = (await decryptAppConnectionCredentials({
orgId: appConnection.orgId,
kmsService,
encryptedCredentials: appConnection.encryptedCredentials
})) as TAzureClientSecretsConnectionCredentials;
const { refreshToken } = credentials;
const currentTime = Date.now();
const { data } = await request.post<ExchangeCodeAzureResponse>(
IntegrationUrls.AZURE_TOKEN_URL.replace("common", credentials.tenantId || "common"),
new URLSearchParams({
grant_type: "refresh_token",
scope: `openid offline_access https://graph.microsoft.com/.default`,
client_id: appCfg.INF_APP_CONNECTION_AZURE_CLIENT_ID,
client_secret: appCfg.INF_APP_CONNECTION_AZURE_CLIENT_SECRET,
refresh_token: refreshToken
})
);
const updatedCredentials = {
...credentials,
accessToken: data.access_token,
expiresAt: currentTime + data.expires_in * 1000,
refreshToken: data.refresh_token
};
const encryptedCredentials = await encryptAppConnectionCredentials({
credentials: updatedCredentials,
orgId: appConnection.orgId,
kmsService
});
await appConnectionDAL.updateById(appConnection.id, { encryptedCredentials });
return data.access_token;
};
export const validateAzureClientSecretsConnectionCredentials = async (config: TAzureClientSecretsConnectionConfig) => {
const { credentials: inputCredentials, method } = config;
const { INF_APP_CONNECTION_AZURE_CLIENT_ID, INF_APP_CONNECTION_AZURE_CLIENT_SECRET, SITE_URL } = getConfig();
if (!SITE_URL) {
throw new InternalServerError({ message: "SITE_URL env var is required to complete Azure OAuth flow" });
}
if (!INF_APP_CONNECTION_AZURE_CLIENT_ID || !INF_APP_CONNECTION_AZURE_CLIENT_SECRET) {
throw new InternalServerError({
message: `Azure ${getAppConnectionMethodName(method)} environment variables have not been configured`
});
}
let tokenResp: AxiosResponse<ExchangeCodeAzureResponse> | null = null;
let tokenError: AxiosError | null = null;
try {
tokenResp = await request.post<ExchangeCodeAzureResponse>(
IntegrationUrls.AZURE_TOKEN_URL.replace("common", inputCredentials.tenantId || "common"),
new URLSearchParams({
grant_type: "authorization_code",
code: inputCredentials.code,
scope: `openid offline_access https://graph.microsoft.com/.default`,
client_id: INF_APP_CONNECTION_AZURE_CLIENT_ID,
client_secret: INF_APP_CONNECTION_AZURE_CLIENT_SECRET,
redirect_uri: `${SITE_URL}/organization/app-connections/azure/oauth/callback`
})
);
} catch (e: unknown) {
if (e instanceof AxiosError) {
tokenError = e;
} else {
throw new BadRequestError({
message: `Unable to validate connection: verify credentials`
});
}
}
if (tokenError) {
if (tokenError instanceof AxiosError) {
throw new BadRequestError({
message: `Failed to get access token: ${
(tokenError?.response?.data as { error_description?: string })?.error_description || "Unknown error"
}`
});
} else {
throw new InternalServerError({
message: "Failed to get access token"
});
}
}
if (!tokenResp) {
throw new InternalServerError({
message: `Failed to get access token: Token was empty with no error`
});
}
switch (method) {
case AzureClientSecretsConnectionMethod.OAuth:
return {
tenantId: inputCredentials.tenantId,
accessToken: tokenResp.data.access_token,
refreshToken: tokenResp.data.refresh_token,
expiresAt: Date.now() + tokenResp.data.expires_in * 1000
};
default:
throw new InternalServerError({
message: `Unhandled Azure connection method: ${method as AzureClientSecretsConnectionMethod}`
});
}
};
@@ -0,0 +1,80 @@
import { z } from "zod";
import { AppConnections } from "@app/lib/api-docs";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import {
BaseAppConnectionSchema,
GenericCreateAppConnectionFieldsSchema,
GenericUpdateAppConnectionFieldsSchema
} from "@app/services/app-connection/app-connection-schemas";
import { AzureClientSecretsConnectionMethod } from "./azure-client-secrets-connection-enums";
export const AzureClientSecretsConnectionOAuthInputCredentialsSchema = z.object({
code: z.string().trim().min(1, "OAuth code required").describe(AppConnections.CREDENTIALS.AZURE_CLIENT_SECRETS.code),
tenantId: z
.string()
.trim()
.min(1, "Tenant ID required")
.describe(AppConnections.CREDENTIALS.AZURE_CLIENT_SECRETS.tenantId)
});
export const AzureClientSecretsConnectionOAuthOutputCredentialsSchema = z.object({
tenantId: z.string(),
accessToken: z.string(),
refreshToken: z.string(),
expiresAt: z.number()
});
export const ValidateAzureClientSecretsConnectionCredentialsSchema = z.discriminatedUnion("method", [
z.object({
method: z
.literal(AzureClientSecretsConnectionMethod.OAuth)
.describe(AppConnections.CREATE(AppConnection.AzureClientSecrets).method),
credentials: AzureClientSecretsConnectionOAuthInputCredentialsSchema.describe(
AppConnections.CREATE(AppConnection.AzureClientSecrets).credentials
)
})
]);
export const CreateAzureClientSecretsConnectionSchema = ValidateAzureClientSecretsConnectionCredentialsSchema.and(
GenericCreateAppConnectionFieldsSchema(AppConnection.AzureClientSecrets)
);
export const UpdateAzureClientSecretsConnectionSchema = z
.object({
credentials: AzureClientSecretsConnectionOAuthInputCredentialsSchema.optional().describe(
AppConnections.UPDATE(AppConnection.AzureClientSecrets).credentials
)
})
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.AzureClientSecrets));
const BaseAzureClientSecretsConnectionSchema = BaseAppConnectionSchema.extend({
app: z.literal(AppConnection.AzureClientSecrets)
});
export const AzureClientSecretsConnectionSchema = z.intersection(
BaseAzureClientSecretsConnectionSchema,
z.discriminatedUnion("method", [
z.object({
method: z.literal(AzureClientSecretsConnectionMethod.OAuth),
credentials: AzureClientSecretsConnectionOAuthOutputCredentialsSchema
})
])
);
export const SanitizedAzureClientSecretsConnectionSchema = z.discriminatedUnion("method", [
BaseAzureClientSecretsConnectionSchema.extend({
method: z.literal(AzureClientSecretsConnectionMethod.OAuth),
credentials: AzureClientSecretsConnectionOAuthOutputCredentialsSchema.pick({
tenantId: true
})
})
]);
export const AzureClientSecretsConnectionListItemSchema = z.object({
name: z.literal("Azure Client Secrets"),
app: z.literal(AppConnection.AzureClientSecrets),
methods: z.nativeEnum(AzureClientSecretsConnectionMethod).array(),
oauthClientId: z.string().optional()
});
@@ -0,0 +1,65 @@
import { z } from "zod";
import { DiscriminativePick } from "@app/lib/types";
import { AppConnection } from "../app-connection-enums";
import {
AzureClientSecretsConnectionOAuthOutputCredentialsSchema,
AzureClientSecretsConnectionSchema,
CreateAzureClientSecretsConnectionSchema,
ValidateAzureClientSecretsConnectionCredentialsSchema
} from "./azure-client-secrets-connection-schemas";
export type TAzureClientSecretsConnection = z.infer<typeof AzureClientSecretsConnectionSchema>;
export type TAzureClientSecretsConnectionInput = z.infer<typeof CreateAzureClientSecretsConnectionSchema> & {
app: AppConnection.AzureClientSecrets;
};
export type TValidateAzureClientSecretsConnectionCredentialsSchema =
typeof ValidateAzureClientSecretsConnectionCredentialsSchema;
export type TAzureClientSecretsConnectionConfig = DiscriminativePick<
TAzureClientSecretsConnectionInput,
"method" | "app" | "credentials"
> & {
orgId: string;
};
export type TAzureClientSecretsConnectionCredentials = z.infer<
typeof AzureClientSecretsConnectionOAuthOutputCredentialsSchema
>;
export interface ExchangeCodeAzureResponse {
token_type: string;
scope: string;
expires_in: number;
ext_expires_in: number;
access_token: string;
refresh_token: string;
id_token: string;
}
export interface TAzureRegisteredApp {
id: string;
appId: string;
displayName: string;
description?: string;
createdDateTime: string;
identifierUris?: string[];
signInAudience?: string;
}
export interface TAzureListRegisteredAppsResponse {
"@odata.context": string;
"@odata.nextLink"?: string;
value: TAzureRegisteredApp[];
}
export interface TAzureClientSecret {
keyId: string;
displayName?: string;
startDateTime: string;
endDateTime: string;
secretText?: string;
}
@@ -0,0 +1,68 @@
import { request } from "@app/lib/config/request";
import { OrgServiceActor } from "@app/lib/types";
import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { getAzureConnectionAccessToken } from "@app/services/app-connection/azure-client-secrets/azure-client-secrets-connection-fns";
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import {
TAzureClientSecretsConnection,
TAzureListRegisteredAppsResponse,
TAzureRegisteredApp
} from "./azure-client-secrets-connection-types";
type TGetAppConnectionFunc = (
app: AppConnection,
connectionId: string,
actor: OrgServiceActor
) => Promise<TAzureClientSecretsConnection>;
const listAzureRegisteredApps = async (
appConnection: TAzureClientSecretsConnection,
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById" | "update" | "updateById">,
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
) => {
const accessToken = await getAzureConnectionAccessToken(appConnection.id, appConnectionDAL, kmsService);
const graphEndpoint = `https://graph.microsoft.com/v1.0/applications`;
const apps: TAzureRegisteredApp[] = [];
let nextLink = graphEndpoint;
while (nextLink) {
// eslint-disable-next-line no-await-in-loop
const { data: appsPage } = await request.get<TAzureListRegisteredAppsResponse>(nextLink, {
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json"
}
});
apps.push(...appsPage.value);
nextLink = appsPage["@odata.nextLink"] || "";
}
return apps;
};
export const azureClientSecretsConnectionService = (
getAppConnection: TGetAppConnectionFunc,
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById" | "update" | "updateById">,
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
) => {
const listApps = async (connectionId: string, actor: OrgServiceActor) => {
const appConnection = await getAppConnection(AppConnection.AzureClientSecrets, connectionId, actor);
const apps = await listAzureRegisteredApps(appConnection, appConnectionDAL, kmsService);
return apps.map((app) => ({
id: app.id,
name: app.displayName,
appId: app.appId
}));
};
return {
listApps
};
};
@@ -0,0 +1,4 @@
export * from "./azure-client-secrets-connection-enums";
export * from "./azure-client-secrets-connection-fns";
export * from "./azure-client-secrets-connection-schemas";
export * from "./azure-client-secrets-connection-types";
@@ -38,8 +38,12 @@ export const getAzureConnectionAccessToken = async (
throw new NotFoundError({ message: `Connection with ID '${connectionId}' not found` });
}
if (appConnection.app !== AppConnection.AzureKeyVault && appConnection.app !== AppConnection.AzureAppConfiguration) {
throw new BadRequestError({ message: `Connection with ID '${connectionId}' is not an Azure Key Vault connection` });
if (
appConnection.app !== AppConnection.AzureKeyVault &&
appConnection.app !== AppConnection.AzureAppConfiguration &&
appConnection.app !== AppConnection.AzureClientSecrets
) {
throw new BadRequestError({ message: `Connection with ID '${connectionId}' is not a valid Azure connection` });
}
const credentials = (await decryptAppConnectionCredentials({
@@ -69,6 +69,5 @@ export const listTeamCityProjects = async (appConnection: TTeamCityConnection) =
}
);
// Filter out the root project. Should not be seen by users.
return resp.data.project.filter((proj) => proj.id !== "_Root");
return resp.data.project;
};
@@ -12,7 +12,7 @@ import { generateSrpServerKey, srpCheckClientProof } from "@app/lib/crypto";
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
import { getUserPrivateKey } from "@app/lib/crypto/srp";
import { BadRequestError, DatabaseError, ForbiddenRequestError, UnauthorizedError } from "@app/lib/errors";
import { removeTrailingSlash } from "@app/lib/fn";
import { getMinExpiresIn, removeTrailingSlash } from "@app/lib/fn";
import { logger } from "@app/lib/logger";
import { getUserAgentType } from "@app/server/plugins/audit-log";
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
@@ -143,6 +143,17 @@ export const authLoginServiceFactory = ({
);
if (!tokenSession) throw new Error("Failed to create token");
let tokenSessionExpiresIn: string | number = cfg.JWT_AUTH_LIFETIME;
let refreshTokenExpiresIn: string | number = cfg.JWT_REFRESH_LIFETIME;
if (organizationId) {
const org = await orgDAL.findById(organizationId);
if (org && org.userTokenExpiration) {
tokenSessionExpiresIn = getMinExpiresIn(cfg.JWT_AUTH_LIFETIME, org.userTokenExpiration);
refreshTokenExpiresIn = org.userTokenExpiration;
}
}
const accessToken = jwt.sign(
{
authMethod,
@@ -155,7 +166,7 @@ export const authLoginServiceFactory = ({
mfaMethod
},
cfg.AUTH_SECRET,
{ expiresIn: cfg.JWT_AUTH_LIFETIME }
{ expiresIn: tokenSessionExpiresIn }
);
const refreshToken = jwt.sign(
@@ -170,7 +181,7 @@ export const authLoginServiceFactory = ({
mfaMethod
},
cfg.AUTH_SECRET,
{ expiresIn: cfg.JWT_REFRESH_LIFETIME }
{ expiresIn: refreshTokenExpiresIn }
);
return { access: accessToken, refresh: refreshToken };
@@ -10,6 +10,7 @@ import { getConfig } from "@app/lib/config/env";
import { infisicalSymmetricDecrypt, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
import { generateUserSrpKeys, getUserPrivateKey } from "@app/lib/crypto/srp";
import { ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { getMinExpiresIn } from "@app/lib/fn";
import { isDisposableEmail } from "@app/lib/validator";
import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal";
import { TProjectDALFactory } from "@app/services/project/project-dal";
@@ -46,7 +47,7 @@ type TAuthSignupDep = {
projectDAL: Pick<TProjectDALFactory, "findProjectGhostUser" | "findProjectById">;
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
orgService: Pick<TOrgServiceFactory, "createOrganization">;
orgService: Pick<TOrgServiceFactory, "createOrganization" | "findOrganizationById">;
orgDAL: TOrgDALFactory;
tokenService: TAuthTokenServiceFactory;
smtpService: TSmtpService;
@@ -320,6 +321,17 @@ export const authSignupServiceFactory = ({
projectBotDAL
});
let tokenSessionExpiresIn: string | number = appCfg.JWT_AUTH_LIFETIME;
let refreshTokenExpiresIn: string | number = appCfg.JWT_REFRESH_LIFETIME;
if (organizationId) {
const org = await orgService.findOrganizationById(user.id, organizationId, authMethod, organizationId);
if (org && org.userTokenExpiration) {
tokenSessionExpiresIn = getMinExpiresIn(appCfg.JWT_AUTH_LIFETIME, org.userTokenExpiration);
refreshTokenExpiresIn = org.userTokenExpiration;
}
}
const tokenSession = await tokenService.getUserTokenSession({
userAgent,
ip,
@@ -337,7 +349,7 @@ export const authSignupServiceFactory = ({
organizationId
},
appCfg.AUTH_SECRET,
{ expiresIn: appCfg.JWT_AUTH_LIFETIME }
{ expiresIn: tokenSessionExpiresIn }
);
const refreshToken = jwt.sign(
@@ -350,7 +362,7 @@ export const authSignupServiceFactory = ({
organizationId
},
appCfg.AUTH_SECRET,
{ expiresIn: appCfg.JWT_REFRESH_LIFETIME }
{ expiresIn: refreshTokenExpiresIn }
);
return { user: updateduser.info, accessToken, refreshToken, organizationId };
+2 -1
View File
@@ -17,5 +17,6 @@ export const sanitizedOrganizationSchema = OrganizationsSchema.pick({
shouldUseNewPrivilegeSystem: true,
privilegeUpgradeInitiatedByUsername: true,
privilegeUpgradeInitiatedAt: true,
bypassOrgAuthEnabled: true
bypassOrgAuthEnabled: true,
userTokenExpiration: true
});
+8 -2
View File
@@ -170,8 +170,12 @@ export const orgServiceFactory = ({
actorOrgId: string | undefined
) => {
await permissionService.getUserOrgPermission(userId, orgId, actorAuthMethod, actorOrgId);
const appCfg = getConfig();
const org = await orgDAL.findOrgById(orgId);
if (!org) throw new NotFoundError({ message: `Organization with ID '${orgId}' not found` });
if (!org.userTokenExpiration) {
return { ...org, userTokenExpiration: appCfg.JWT_REFRESH_LIFETIME };
}
return org;
};
/*
@@ -350,7 +354,8 @@ export const orgServiceFactory = ({
enforceMfa,
selectedMfaMethod,
allowSecretSharingOutsideOrganization,
bypassOrgAuthEnabled
bypassOrgAuthEnabled,
userTokenExpiration
}
}: TUpdateOrgDTO) => {
const appCfg = getConfig();
@@ -451,7 +456,8 @@ export const orgServiceFactory = ({
enforceMfa,
selectedMfaMethod,
allowSecretSharingOutsideOrganization,
bypassOrgAuthEnabled
bypassOrgAuthEnabled,
userTokenExpiration
});
if (!org) throw new NotFoundError({ message: `Organization with ID '${orgId}' not found` });
return org;
+1
View File
@@ -74,6 +74,7 @@ export type TUpdateOrgDTO = {
selectedMfaMethod: MfaMethod;
allowSecretSharingOutsideOrganization: boolean;
bypassOrgAuthEnabled: boolean;
userTokenExpiration: string;
}>;
} & TOrgPermission;
@@ -171,6 +171,19 @@ export const secretImportDALFactory = (db: TDbClient) => {
}
};
const getFolderImports = async (secretPath: string, environmentId: string, tx?: Knex) => {
try {
const folderImports = await (tx || db.replicaNode())(TableName.SecretImport)
.where({ importPath: secretPath, importEnv: environmentId })
.join(TableName.SecretFolder, `${TableName.SecretImport}.folderId`, `${TableName.SecretFolder}.id`)
.join(TableName.Environment, `${TableName.SecretFolder}.envId`, `${TableName.Environment}.id`)
.select(db.ref("id").withSchema(TableName.SecretFolder).as("folderId"));
return folderImports;
} catch (error) {
throw new DatabaseError({ error, name: "get secret imports" });
}
};
const getFolderIsImportedBy = async (
secretPath: string,
environmentId: string,
@@ -203,7 +216,8 @@ export const secretImportDALFactory = (db: TDbClient) => {
db.ref("name").withSchema(TableName.Environment).as("envName"),
db.ref("slug").withSchema(TableName.Environment).as("envSlug"),
db.ref("id").withSchema(TableName.SecretFolder).as("folderId"),
db.ref("secretKey").withSchema(TableName.SecretReferenceV2).as("referencedSecretKey")
db.ref("secretKey").withSchema(TableName.SecretReferenceV2).as("referencedSecretKey"),
db.ref("environment").withSchema(TableName.SecretReferenceV2).as("referencedSecretEnv")
);
const folderResults = folderImports.map(({ envName, envSlug, folderName, folderId }) => ({
@@ -214,13 +228,14 @@ export const secretImportDALFactory = (db: TDbClient) => {
}));
const secretResults = secretReferences.map(
({ envName, envSlug, secretId, folderName, folderId, referencedSecretKey }) => ({
({ envName, envSlug, secretId, folderName, folderId, referencedSecretKey, referencedSecretEnv }) => ({
envName,
envSlug,
secretId,
folderName,
folderId,
referencedSecretKey
referencedSecretKey,
referencedSecretEnv
})
);
@@ -235,6 +250,7 @@ export const secretImportDALFactory = (db: TDbClient) => {
secrets: {
secretId: string;
referencedSecretKey: string;
referencedSecretEnv: string;
}[];
folderId: string;
folderImported: boolean;
@@ -264,7 +280,11 @@ export const secretImportDALFactory = (db: TDbClient) => {
if ("secretId" in item && item.secretId) {
updatedAcc[env].folders[folder].secrets = [
...updatedAcc[env].folders[folder].secrets,
{ secretId: item.secretId, referencedSecretKey: item.referencedSecretKey }
{
secretId: item.secretId,
referencedSecretKey: item.referencedSecretKey,
referencedSecretEnv: item.referencedSecretEnv
}
];
} else {
updatedAcc[env].folders[folder].folderImported = true;
@@ -309,6 +329,7 @@ export const secretImportDALFactory = (db: TDbClient) => {
findLastImportPosition,
updateAllPosition,
getProjectImportCount,
getFolderIsImportedBy
getFolderIsImportedBy,
getFolderImports
};
};
@@ -808,7 +808,7 @@ export const secretImportServiceFactory = ({
actorOrgId,
secrets
}: TGetSecretImportsDTO & {
secrets: { secretKey: string; secretValue: string }[] | undefined;
secrets: { secretKey: string; secretValue: string; id: string }[] | undefined;
}) => {
const { permission } = await permissionService.getProjectPermission({
actor,
@@ -877,7 +877,8 @@ export const secretImportServiceFactory = ({
)
.map((otherSecret) => ({
secretId: secret.secretKey,
referencedSecretKey: otherSecret.secretKey
referencedSecretKey: otherSecret.secretKey,
referencedSecretEnv: environment
}));
}) || [];
if (locallyReferenced.length > 0) {
@@ -56,11 +56,12 @@ export type FolderResult = {
export type SecretResult = {
secretId: string;
referencedSecretKey: string;
referencedSecretEnv: string;
} & FolderResult;
export type FolderInfo = {
folderName: string;
secrets?: { secretId: string; referencedSecretKey: string }[];
secrets?: { secretId: string; referencedSecretKey: string; referencedSecretEnv: string }[];
folderId: string;
folderImported: boolean;
envSlug?: string;
@@ -23,6 +23,7 @@ import {
TDeleteSecretSyncDTO,
TFindSecretSyncByIdDTO,
TFindSecretSyncByNameDTO,
TListSecretSyncsByFolderId,
TListSecretSyncsByProjectId,
TSecretSync,
TTriggerSecretSyncImportSecretsByIdDTO,
@@ -31,12 +32,14 @@ import {
TUpdateSecretSyncDTO
} from "@app/services/secret-sync/secret-sync-types";
import { TSecretImportDALFactory } from "../secret-import/secret-import-dal";
import { TSecretSyncDALFactory } from "./secret-sync-dal";
import { SECRET_SYNC_CONNECTION_MAP, SECRET_SYNC_NAME_MAP } from "./secret-sync-maps";
import { TSecretSyncQueueFactory } from "./secret-sync-queue";
type TSecretSyncServiceFactoryDep = {
secretSyncDAL: TSecretSyncDALFactory;
secretImportDAL: TSecretImportDALFactory;
appConnectionService: Pick<TAppConnectionServiceFactory, "connectAppConnectionById">;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getOrgPermission">;
projectBotService: Pick<TProjectBotServiceFactory, "getBotKey">;
@@ -53,6 +56,7 @@ export type TSecretSyncServiceFactory = ReturnType<typeof secretSyncServiceFacto
export const secretSyncServiceFactory = ({
secretSyncDAL,
folderDAL,
secretImportDAL,
permissionService,
appConnectionService,
projectBotService,
@@ -85,6 +89,37 @@ export const secretSyncServiceFactory = ({
return secretSyncs as TSecretSync[];
};
const listSecretSyncsBySecretPath = async (
{ projectId, secretPath, environment }: TListSecretSyncsByFolderId,
actor: OrgServiceActor
) => {
const { permission } = await permissionService.getProjectPermission({
actor: actor.type,
actorId: actor.id,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretManager,
projectId
});
if (permission.cannot(ProjectPermissionSecretSyncActions.Read, ProjectPermissionSub.SecretSyncs)) {
return [];
}
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
if (!folder) return [];
const folderImports = await secretImportDAL.getFolderImports(secretPath, folder.envId);
const secretSyncs = await secretSyncDAL.find({
$in: {
folderId: folderImports.map((folderImport) => folderImport.folderId).concat(folder.id)
}
});
return secretSyncs as TSecretSync[];
};
const findSecretSyncById = async ({ destination, syncId }: TFindSecretSyncByIdDTO, actor: OrgServiceActor) => {
const secretSync = await secretSyncDAL.findById(syncId);
@@ -518,6 +553,7 @@ export const secretSyncServiceFactory = ({
return {
listSecretSyncOptions,
listSecretSyncsByProjectId,
listSecretSyncsBySecretPath,
findSecretSyncById,
findSecretSyncByName,
createSecretSync,
@@ -154,6 +154,13 @@ export type TListSecretSyncsByProjectId = {
destination?: SecretSync;
};
export type TListSecretSyncsByFolderId = {
projectId: string;
secretPath: string;
environment: string;
destination?: SecretSync;
};
export type TFindSecretSyncByIdDTO = {
syncId: string;
destination: SecretSync;